RAACLiquidLocker¶
Overview¶
RAACLiquidLocker is an upgradeable locker that accepts RAAC deposits, locks the underlying into veRAAC on a fixed epoch schedule, and allows users to borrow leRAAC against their locked collateral. The locker also harvests veRAAC rewards, swaps rewards into RAAC, and distributes RAAC to both locker depositors (as rewards) and to the RAACMaturityVault (to pay down leRAAC maturity debt).
Purpose¶
- Lock RAAC into
veRAACwhile attributing ve-power to depositors viaVeBoost - Provide a collateralized borrowing facility by minting
leRAAC - Manage unlock scheduling and withdrawals on epoch boundaries
- Harvest rewards from
veRAAC, swap them into RAAC, take protocol/bot fees, and distribute the remainder - Push RAAC into
RAACMaturityVault.distribute()to pay downleRAACmaturity debt - Allow treasury-controlled governance interactions (gauge voting / governance voting)
Functions¶
User Functions¶
deposit¶
deposit(uint256 _amount)
Summary
Deposits RAAC and locks it into veRAAC for lockDurationEpochs.
Access
External
Parameters
| Name | Type | Description |
|---|---|---|
| _amount | uint256 | The amount of RAAC to deposit |
Emits
Deposit(address depositor, address recipient, uint256 amount)
Reverts
NonZeroValue()— if amount is zeroSameBlockInteraction()— if called in the same block as another interaction
borrow¶
borrow(uint256 _amount, bool _depositMaturityVault)
Summary
Borrows leRAAC against locked collateral. Optionally deposits the borrowed amount directly into the maturity vault.
Access
External
Parameters
| Name | Type | Description |
|---|---|---|
| _amount | uint256 | The amount of leRAAC to borrow |
| _depositMaturityVault | bool | If true, deposits borrowed leRAAC into vault |
Emits
Borrow(address borrower, address recipient, uint256 amount)
Reverts
NonZeroValue()— if amount is zeroInsufficientCollateral()— if health check failsSameBlockInteraction()— if called in the same block as another interaction
JavaScript
// Borrow leRAAC against your locked RAAC
const borrowAmount = ethers.parseUnits("500", 18);
// Option 1: Borrow leRAAC to your wallet
const tx1 = await liquidLocker.borrow(borrowAmount, false);
await tx1.wait();
// Option 2: Borrow and deposit directly into maturity vault
const tx2 = await liquidLocker.borrow(borrowAmount, true);
await tx2.wait();
repay¶
repay(uint256 _leRAACAmount)
Summary
Repays leRAAC debt by burning leRAAC. A repay fee may apply.
Access
External
Parameters
| Name | Type | Description |
|---|---|---|
| _leRAACAmount | uint256 | The amount of leRAAC to repay |
Emits
Repay(address account, uint256 leRAACAmount)
Reverts
NonZeroValue()— if amount is zeroInsufficientBalance()— if leRAAC balance is insufficient
requestUnlock¶
requestUnlock(uint256 _amount)
Summary
Reserves part of locked collateral to unlock at maturity epoch(s). This reduces available collateral immediately.
Access
External
Parameters
| Name | Type | Description |
|---|---|---|
| _amount | uint256 | The amount of RAAC to schedule for unlock |
Emits
Unlock(address account, uint256 amount)
Reverts
NonZeroValue()— if amount is zeroInsufficientToUnlock()— if amount exceeds locked balanceInsufficientCollateral()— if health check fails after unlock reservationSameBlockInteraction()— if called in the same block as another interaction
withdrawUnlocked¶
withdrawUnlocked()
Summary
Withdraws all matured reserved RAAC to the caller.
Access
External
Emits
Withdraw(address account, uint256 amount)
Reverts
InsufficientUnlocked()— if no matured funds availableSameBlockInteraction()— if called in the same block as another interaction
donate¶
donate(uint256 _amount)
Summary
Donates RAAC to be distributed as rewards to locker depositors.
Access
External
Parameters
| Name | Type | Description |
|---|---|---|
| _amount | uint256 | The amount of RAAC to donate |
Emits
- Distribution event (internal)
harvest¶
harvest(address _recipient, uint256 _minimumOut) → uint256
Summary
Claims veRAAC rewards, swaps them into RAAC, takes fees, and distributes the remainder to depositors and the maturity vault.
Access
External
Parameters
| Name | Type | Description |
|---|---|---|
| _recipient | address | The address to receive the harvest bounty |
| _minimumOut | uint256 | Minimum RAAC output from swap |
Returns
| Type | Description |
|---|---|
| uint256 | Harvested RAAC amount (pre-fees) |
Emits
Harvest(address caller, uint256 reward, uint256 platformFee, uint256 harvestBounty)
Reverts
InsufficientOutput()— if swap output is below minimum
JavaScript
// Harvest rewards (caller receives bounty)
const minOutput = ethers.parseUnits("10", 18);
const tx = await liquidLocker.harvest(myAddress, minOutput);
const receipt = await tx.wait();
// Get harvested amount from event
const harvestEvent = receipt.logs.find(log =>
log.fragment?.name === 'Harvest'
);
console.log("Harvested:", harvestEvent.args.reward);
getUserInfo¶
getUserInfo(address _account) → (uint256, uint256, uint256, uint256, uint256)
Summary
Returns a derived snapshot of a user's position.
Access
External View
Parameters
| Name | Type | Description |
|---|---|---|
| _account | address | The account to query |
Returns
| Name | Type | Description |
|---|---|---|
| totalDeposited | uint256 | Total locked collateral |
| totalPendingUnlocked | uint256 | Reserved but not yet matured |
| totalUnlocked | uint256 | Matured and available to withdraw |
| totalBorrowed | uint256 | Outstanding leRAAC debt |
| totalReward | uint256 | Accrued reward balance |
JavaScript
// Get user's current position
const [
totalDeposited,
totalPendingUnlocked,
totalUnlocked,
totalBorrowed,
totalReward
] = await liquidLocker.getUserInfo(myAddress);
console.log("Deposited:", ethers.formatUnits(totalDeposited, 18));
console.log("Pending Unlock:", ethers.formatUnits(totalPendingUnlocked, 18));
console.log("Unlocked:", ethers.formatUnits(totalUnlocked, 18));
console.log("Borrowed:", ethers.formatUnits(totalBorrowed, 18));
console.log("Rewards:", ethers.formatUnits(totalReward, 18));
getUserLocks¶
getUserLocks(address _account) → EpochUnlockInfo[]
Summary
Returns future pending unlock entries for a user.
Access
External View
Parameters
| Name | Type | Description |
|---|---|---|
| _account | address | The account to query |
Returns
| Type | Description |
|---|---|
| EpochUnlockInfo[] | Array of pending unlock entries |
On-Behalf-Of Functions¶
depositOnBehalfOf¶
depositOnBehalfOf(address _user, uint256 _amount)
Summary
Deposits RAAC for _user (on-behalf-of flow).
Access
External — Keeper + ON_BEHALF_OF_ROLE
Parameters
| Name | Type | Description |
|---|---|---|
| _user | address | The beneficiary address |
| _amount | uint256 | The amount of RAAC to deposit |
Emits
Deposit(address depositor, address recipient, uint256 amount)
borrowOnBehalfOf¶
borrowOnBehalfOf(address _user, uint256 _amount, bool _depositMaturityVault)
Summary
Borrows leRAAC for _user (on-behalf-of flow).
Access
External — Keeper + ON_BEHALF_OF_ROLE
Parameters
| Name | Type | Description |
|---|---|---|
| _user | address | The beneficiary address |
| _amount | uint256 | The amount of leRAAC to borrow |
| _depositMaturityVault | bool | If true, deposits into vault |
Emits
Borrow(address borrower, address recipient, uint256 amount)
Keeper Functions¶
processVaultExpiredLocks¶
processVaultExpiredLocks()
Summary
Releases expired veRAAC locks for vault funds and relocks excess liquidity beyond buffer.
Access
External — Keeper only
Treasury Functions¶
voteGauge¶
voteGauge(address _controller, address _gauge, uint256 _weight)
Summary
Votes on a gauge via a controller.
Access
External — Treasury only
Parameters
| Name | Type | Description |
|---|---|---|
| _controller | address | The gauge controller address |
| _gauge | address | The gauge to vote for |
| _weight | uint256 | The vote weight |
Emits
GaugeVote(address controller, address gauge, uint256 weight)
voteGovernance¶
voteGovernance(address _governance, uint256 _proposalId, uint8 _support)
Summary
Casts governance vote using delegated voting power.
Access
External — Treasury only
Parameters
| Name | Type | Description |
|---|---|---|
| _governance | address | The governance contract |
| _proposalId | uint256 | The proposal ID |
| _support | uint8 | Vote direction (0=against, 1=for, 2=abstain) |
Emits
GovernanceVote(address governance, uint256 proposalId, uint8 support, uint256 votingPower)
Admin Functions¶
initialize¶
initialize(...)
Summary
Initializes the contract (upgradeable pattern).
Access
External — Initializer
Parameters
| Name | Type | Description |
|---|---|---|
| _raac | address | RAAC token address |
| _leRAAC | address | leRAAC token address |
| _veRAAC | address | veRAAC token address |
| _maturityVault | address | Maturity vault address |
| _zap | address | Zap contract address |
| _intermediateRewardToken | address | Intermediate reward token address |
| _treasury | address | Treasury address |
| _cleverFeeRecipient | address | Clever fee recipient address |
| _raacBotLocker | address | Bot locker address |
| _veBoost | address | VeBoost contract address |
| _treasuryFeePercentage | uint256 | Treasury fee percentage |
| _harvestBountyPercentage | uint256 | Harvest bounty percentage |
| _feeSharesBots | uint256 | Bot fee share |
| _feeSharesClever | uint256 | Clever fee share |
| _feeSharesTreasury | uint256 | Treasury fee share |
updateWhitelist¶
updateWhitelist(address _keeper, bool _status)
Summary
Adds or removes an address as keeper.
Access
External — Owner only
Parameters
| Name | Type | Description |
|---|---|---|
| _keeper | address | The keeper address |
| _status | bool | true to add, false to remove |
Emits
UpdateWhitelist(address whitelist, bool status)
Implementation Details¶
Core Accounting Model¶
- Collateral: user collateral is tracked as
UserInfo.totalLocked(and globaltotalLockedGlobal) - Debt: borrowing mints
leRAACand tracks debt asUserInfo.totalDebt(and globaltotalDebtGlobal) - Rewards: rewards are tracked with
accRewardPerShareand per-userrewardPerSharePaidandrewards. Rewards are used to automatically pay down debt - Unlock scheduling: the contract records epoch unlock entries in
UserInfo.pendingUnlockList:reserved=false: entries created from deposits (rolling locks). When they mature, they are auto-relocked by pushing their epoch forwardreserved=true: entries created fromrequestUnlock()(user exit intent). When they mature, they are credited intoUserInfo.totalUnlockedand can be withdrawn viawithdrawUnlocked()
Borrow Health Check¶
Borrowing and unlock requests enforce an account health invariant:
Same-Block Interaction Protection¶
Many user entrypoints use noSameBlock which prevents multiple interactions within the same block (SameBlockInteraction) to reduce manipulation opportunities.
Reward Harvesting and Distribution¶
Harvest workflow:
veRAAC.claimReward()to pull rewards- Swap reward tokens into an intermediate token and then into RAAC (either via
zapor via manual routes withapprovedTargets) - Apply fee splits:
- Treasury fee (with optional splitting to bot locker / clever recipient / treasury)
- Harvest bounty to the caller-provided recipient
- Remaining RAAC is distributed via
_distribute():- Increases
accRewardPerSharefor locker depositors - Calls
maturityVault.distribute(address(this), amount)to pay down maturity-vault debt
- Increases
Vault Liquidity Buffer Relocking¶
processVaultExpiredLocks() can withdraw expired veRAAC locks owned by the locker contract and relock only the excess above a rolling liquidity buffer that accounts for:
- Currently matured user withdrawals (
totalUnlockedGlobal) - Matured-but-not-yet-credited reserved entries
- Scheduled reserved unlocks in the next
bufferEpochSpanepochs
Data Structures¶
EpochUnlockInfo¶
| Field | Type | Description |
|---|---|---|
| pendingUnlock | uint192 | Amount of RAAC represented by this entry |
| unlockEpoch | uint64 | Epoch number (weeks) at which it matures |
| reserved | bool | true if user-reserved unlock, false if rolling lock |
UserInfo¶
| Field | Type | Description |
|---|---|---|
| totalDebt | uint128 | Outstanding leRAAC debt (after reward offsetting) |
| rewards | uint128 | Reward balance (after debt offsetting) |
| rewardPerSharePaid | uint256 | Checkpoint of accRewardPerShare |
| totalLocked | uint112 | Locked collateral amount |
| totalUnlocked | uint112 | Matured reserved amount available to withdraw |
| nextUnlockIndex | uint32 | Cursor into pendingUnlockList for efficient processing |
| lastProcessedEpoch | uint64 | Anti-manipulation epoch checkpoint |
| pendingUnlockList | EpochUnlockInfo[] | Per-user epoch unlock schedule |
ConvertParam¶
| Field | Type | Description |
|---|---|---|
| target | address | Call target for route execution |
| spender | address | Spender to approve for swap |
| data | bytes | Calldata for the swap route |
Constants¶
| Constant | Value | Description |
|---|---|---|
| PRECISION | 1e18 | Precision for reward-per-share math |
| FEE_PRECISION | 1e9 | Precision for fee percentages |
| MAX_REPAY_FEE | 1e8 | Max repay fee (10%) |
| MAX_TREASURY_FEE | 5e8 | Max treasury fee (50%) |
| MAX_HARVEST_BOUNTY | 1e8 | Max harvest bounty (10%) |
| EPOCH_DURATION | 7 days | Epoch length used for unlock rounding |
| ON_BEHALF_OF_ROLE | keccak256("ON_BEHALF_OF_ROLE") |
AccessControl role for acting on behalf of users |
Events¶
| Event Name | Description | Parameters |
|---|---|---|
| Deposit | When RAAC is deposited and locked | depositor, recipient, amount |
| Unlock | When a user reserves collateral for unlock | account, amount |
| Withdraw | When a user withdraws matured reserved RAAC | account, amount |
| Repay | When a user repays debt | account, leRAACAmount |
| Borrow | When leRAAC is borrowed |
borrower, recipient, amount |
| Claim | When rewards are claimed | account, amount |
| Harvest | When rewards are harvested and distributed | caller, reward, platformFee, harvestBounty |
| UpdateWhitelist | When keeper whitelist is updated | whitelist, status |
| UpdateRepayFeePercentage | When repay fee is updated | feePercentage |
| UpdatePlatformFeePercentage | When treasury/platform fee is updated | feePercentage |
| UpdateHarvestBountyPercentage | When harvest bounty is updated | percentage |
| UpdateTreasury | When treasury is updated | treasury |
| UpdateBotLocker | When bot locker is updated | locker |
| UpdateCleverFeeRecipient | When clever fee recipient is updated | newRecipient |
| UpdateZap | When zap is updated | zap |
| UpdateBufferEpochSpan | When buffer span is updated | span |
| UpdateIntermediateRewardToken | When intermediate reward token is updated | intermediateRewardToken |
| UpdateVeBoost | When VeBoost is updated | veBoost |
| UpdateFeeShares | When fee shares are updated | feeSharesBots, feeSharesClever, feeSharesTreasury |
| GaugeVote | When a gauge vote is cast | controller, gauge, weight |
| GovernanceVote | When a governance vote is cast | governance, proposalId, support, votingPower |
Error Conditions¶
| Error Name | Description |
|---|---|
| InvalidAmount | Invalid amount / overflow guard |
| NonZeroValue | Amount must be non-zero |
| InvalidAddress | Invalid address |
| InvalidPercentage | Invalid percentage |
| Unauthorized | Caller not authorized |
| InsufficientBalance | Insufficient balance |
| EpochNotReached | Epoch not reached |
| InsufficientCollateral | Health check failed |
| ZeroAddress | Zero address provided |
| FeeTooLarge | Fee exceeds maximum |
| InsufficientToUnlock | Unlock amount exceeds locked |
| InsufficientUnlocked | No matured unlocked funds |
| InsufficientOutput | Swap output below minimum |
| NotApproved | Route target not approved |
| SameBlockInteraction | Same-block interaction attempted |
| InvalidSlippageTolerance | Slippage tolerance invalid |
| InvalidTimestamp | Timestamp/epoch regression detected |
| InvalidToken | Invalid token in configuration |
| FeeSharesNotMatchingTreasuryFee | Fee shares do not sum to treasury fee |
Access Control Roles¶
| Role / Mechanism | Description |
|---|---|
Owner (OwnableUpgradeable) |
Can update configuration (fees, zap, treasury, approved targets, keepers, etc.) |
DEFAULT_ADMIN_ROLE |
Set to initializer caller; manages AccessControl permissions |
ON_BEHALF_OF_ROLE |
Allows acting on behalf of users for deposit/borrow paths |
Keeper whitelist (isKeeper) |
Allows running operational functions like processVaultExpiredLocks() |
Treasury (onlyTreasury) |
Can call governance voting functions |
Usage Notes¶
- Users should expect epoch-based unlock timing; unlock maturity is aligned to
EPOCH_DURATION requestUnlock()reduces collateral immediately; ensure health constraints remain satisfiednoSameBlockmay cause failures for complex multi-call flows within the same block- Swap routing requires
approvedTargetsto be enabled for any low-levelcallroute execution
Dependencies¶
- OpenZeppelin Upgradeable:
OwnableUpgradeable,AccessControlUpgradeable,ReentrancyGuardUpgradeable - Token ops:
IERC20,SafeERC20 - RAAC interfaces:
IveRAACToken(veRAAC locking + reward claim)ILiquidEscrowedRAAC(leRAAC mint/burn)IRAACMaturityVault(debt paydown + optional deposit)IVeBoost(ve power delegation)IZap(reward swaps)ILLamaLocker(bot locker reward distribution)IGaugeController,IGovernance(voting integrations)