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 listInvalidAddress()
— if token address is zeroInvalidAmount()
— 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)
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 listInvalidAddress()
— if token address is zeroInvalidAmount()
— 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)
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 zeroInsufficientBalance()
— if treasury balance is less than requested amountTokenNotSupported()
— 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)
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:
- Measuring Balance Before: Recording treasury balance before transfer
- Executing Transfer: Calling
safeTransferFrom
with requested amount - Measuring Balance After: Recording treasury balance after transfer
- Calculating Actual Amount:
actualAmount = balanceAfter - balanceBefore
- 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:
- Fee Collection: FeeCollector collects fees from protocol activities
- Fee Splitting: Fees are split according to configured percentages
- Treasury Distribution: FeeCollector calls
distributeRewards()
to send treasury's share - 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