Skip to content

Treasury

Overview

The Treasury is the central fund management contract for the RAAC Protocol. It serves as the primary repository for protocol funds, managing deposits, withdrawals, and fund allocation tracking with role-based access control. The Treasury implements the IDistributionTarget interface, making it compatible with the FeeCollector for automated fund distribution.

Purpose

  • Centralized Fund Management: Secure storage and management of protocol treasury funds
  • Multi-Token Support: Handle various supported tokens with individual balance tracking
  • Role-Based Access Control: Secure fund management through distinct manager roles
  • Fee Distribution Integration: Receive funds from FeeCollector through distributeRewards
  • Fee-on-Transfer Handling: Properly handle tokens with transfer fees

Architecture

flowchart TD
    A[FeeCollector] -->|distributeRewards| B[Treasury]
    C[External Deposits] -->|deposit| B
    B -->|withdraw| D[Authorized Recipients]

    E[Supported Tokens] -->|Whitelist| B
    F[Manager Role] -->|Controls| B

    B -->|Track| G[Token Balances]
    B -->|Emit| H[Deposit/Withdraw Events]

Supported Tokens

The Treasury accepts the same tokens as the FeeCollector:

Token Description
scrvUSD Curve SCRVUSD vault shares
crvUSD Curve USD stablecoin
iRAAC RAAC index token from RWA Vault

Token Management

Tokens must be explicitly added to the supported tokens list before they can be deposited or withdrawn.
This provides additional security by preventing accidental deposits of unsupported tokens.

CRITICAL: Pull-Based System - Direct Transfers Result in Permanent Loss

The Treasury is a pull-based system that does NOT track direct token transfers.
Any direct transfer of tokens to the Treasury address will result in PERMANENT LOSS of funds.
The contract only tracks and accounts for deposits made through the deposit() function or distributeRewards() from FeeCollector.
NEVER send tokens directly to the Treasury address.

Key Functions

deposit

deposit(address token, uint256 amount)

Summary
Deposits tokens into the treasury from the caller's balance. This is the primary method for external deposits.

Guarded Method
- Callable by any address - Protected by nonReentrant and validation checks

Parameters

Name Type Description
token address Token address to deposit (must be supported)
amount uint256 Amount of tokens to deposit

Emits
- Deposited(address indexed token, uint256 amount)

Reverts

  • TokenNotSupported() — if token not in supported tokens list
  • InvalidAddress() — if token address is zero
  • InvalidAmount() — if amount is zero
  • ERC20 transfer failures

Requirements

  • Caller must have approved Treasury to spend tokens
  • Token must be in supported tokens list
  • Handles fee-on-transfer tokens by measuring actual received amount
Example (TypeScript / ethers)
// Approve Treasury to spend tokens
await token.approve(treasury.target, amount);

// Deposit tokens to treasury
const tx = await treasury.deposit(tokenAddress, amount);
await tx.wait();
Source code
function deposit(address token, uint256 amount) public override nonReentrant {
    _deposit(msg.sender, token, amount);
}

distributeRewards

distributeRewards(address token, uint256 amount) → bool

Summary
Receives funds from the FeeCollector as part of the fee distribution process. This method implements the IDistributionTarget interface.

Guarded Method
- Callable by FeeCollector contract - Protected by nonReentrant and validation checks

Parameters

Name Type Description
token address Token address being distributed
amount uint256 Amount of tokens being distributed

Returns
bool — Always returns true on successful deposit

Emits
- Deposited(address indexed token, uint256 amount)

Reverts

  • TokenNotSupported() — if token not in supported tokens list
  • InvalidAddress() — if token address is zero
  • InvalidAmount() — if amount is zero
  • ERC20 transfer failures

Notes

  • This method is specifically designed for FeeCollector integration
  • Internally calls _deposit() with the FeeCollector as the source
  • Handles fee-on-transfer tokens properly
Example (TypeScript / ethers)
// Called by FeeCollector during fee distribution
const success = await treasury.distributeRewards(tokenAddress, amount);
console.log("Distribution successful:", success);
Source code
function distributeRewards(address token, uint256 amount) 
    external 
    nonReentrant 
    returns (bool) 
{
    _deposit(msg.sender, token, amount);
    return true;
}

withdraw

withdraw(address token, uint256 amount, address recipient)

Summary
Withdraws tokens from the treasury to a specified recipient. Only callable by accounts with MANAGER_ROLE.

Guarded Method
- Callable only by MANAGER_ROLE - Protected by nonReentrant and validation checks

Parameters

Name Type Description
token address Token address to withdraw
amount uint256 Amount of tokens to withdraw
recipient address Address to receive the withdrawn tokens

Emits
- Withdrawn(address indexed token, uint256 amount, address indexed recipient)

Reverts

  • InvalidAddress() — if token or recipient address is zero
  • InsufficientBalance() — if treasury balance is less than requested amount
  • TokenNotSupported() — if token not in supported tokens list
  • ERC20 transfer failures

Requirements

  • Caller must have MANAGER_ROLE
  • Treasury must have sufficient balance for the requested amount
  • Token must be in supported tokens list
Example (TypeScript / ethers)
// Withdraw tokens from treasury (requires MANAGER_ROLE)
const tx = await treasury.withdraw(
    tokenAddress,
    amount,
    recipientAddress
);
await tx.wait();
Source code
function withdraw(
    address token,
    uint256 amount,
    address recipient
) external override nonReentrant onlyRole(MANAGER_ROLE) {
    if (token == address(0)) revert InvalidAddress();
    if (recipient == address(0)) revert InvalidRecipient();
    if (_balances[token] < amount) revert InsufficientBalance();
    if (!isTokenSupported[token]) revert TokenNotSupported();

    _balances[token] -= amount;
    IERC20(token).safeTransfer(recipient, amount);

    emit Withdrawn(token, amount, recipient);
}

Private Methods (Role-Protected)

Token Management

Function Role Description
addSupportedToken MANAGER_ROLE Add token to supported tokens list
removeSupportedToken MANAGER_ROLE Remove token from supported tokens list

Token Removal Responsibility

The protocol takes responsibility for withdrawing all funds before removing a token from the supported list.
Ensure all balances are zero before calling removeSupportedToken.


Public Read Methods

Balance Information

Function Returns Description
getBalance(address token) uint256 Get treasury balance for specific token

Configuration Status

Function Returns Description
isTokenSupported(address token) bool Check if token is supported
supportedTokens() address[] Get list of all supported tokens

Interface Support

Function Returns Description
supportsInterface(bytes4 interfaceId) bool Check if contract supports given interface

Internal Functions

_deposit

Item Details
Purpose Internal deposit logic with fee-on-transfer handling
Validation Checks token support, address validity, and amount
Fee Handling Measures actual received amount vs. requested amount
State Update Updates internal _balances mapping
Source code
function _deposit(address from, address token, uint256 amount) internal {
    if (!isTokenSupported[token]) revert TokenNotSupported();
    if (token == address(0)) revert InvalidAddress();
    if (amount == 0) revert InvalidAmount();

    // Fee-on-transfer check
    uint256 balanceBefore = IERC20(token).balanceOf(address(this));
    IERC20(token).safeTransferFrom(from, address(this), amount);
    uint256 balanceAfter = IERC20(token).balanceOf(address(this));
    uint256 actualAmount = balanceAfter - balanceBefore;
    _balances[token] += actualAmount;

    emit Deposited(token, actualAmount);
}

Access Control Roles

Role Permissions
DEFAULT_ADMIN_ROLE Full administrative control, can grant/revoke all roles
MANAGER_ROLE Withdraw funds, manage supported tokens

Fee-on-Transfer Handling

The Treasury properly handles tokens with transfer fees by:

  1. Measuring Balance Before: Recording treasury balance before transfer
  2. Executing Transfer: Calling safeTransferFrom with requested amount
  3. Measuring Balance After: Recording treasury balance after transfer
  4. Calculating Actual Amount: actualAmount = balanceAfter - balanceBefore
  5. Updating Internal Balance: Using actual received amount, not requested amount

This ensures accurate balance tracking even for tokens that charge fees on transfer.

Integration with FeeCollector

The Treasury implements the IDistributionTarget interface, making it compatible with the FeeCollector's distribution system:

  1. Fee Collection: FeeCollector collects fees from protocol activities
  2. Fee Splitting: Fees are split according to configured percentages
  3. Treasury Distribution: FeeCollector calls distributeRewards() to send treasury's share
  4. Balance Tracking: Treasury updates its internal balance tracking

Events

Event Parameters Description
Deposited token, amount Emitted when tokens are deposited
Withdrawn token, amount, recipient Emitted when tokens are withdrawn
TokenAdded token Emitted when token is added to supported list
TokenRemoved token Emitted when token is removed from supported list

Security Features

  • Reentrancy Protection: All external functions protected by nonReentrant
  • Role-Based Access: Withdrawals restricted to MANAGER_ROLE
  • Input Validation: Comprehensive validation of addresses and amounts
  • Fee-on-Transfer Safety: Proper handling of tokens with transfer fees
  • Interface Compliance: Implements IDistributionTarget for FeeCollector integration