Skip to content

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 veRAAC while attributing ve-power to depositors via VeBoost
  • 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 down leRAAC maturity 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 zero
  • SameBlockInteraction() — if called in the same block as another interaction
JavaScript
// Approve RAAC spending first
const amount = ethers.parseUnits("1000", 18);
await raac.approve(liquidLockerAddress, amount);

// Deposit RAAC into the liquid locker
const tx = await liquidLocker.deposit(amount);
await tx.wait();

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 zero
  • InsufficientCollateral() — if health check fails
  • SameBlockInteraction() — 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 zero
  • InsufficientBalance() — if leRAAC balance is insufficient
JavaScript
// Approve leRAAC spending first
const repayAmount = ethers.parseUnits("200", 18);
await leRAAC.approve(liquidLockerAddress, repayAmount);

// Repay leRAAC debt
const tx = await liquidLocker.repay(repayAmount);
await tx.wait();

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 zero
  • InsufficientToUnlock() — if amount exceeds locked balance
  • InsufficientCollateral() — if health check fails after unlock reservation
  • SameBlockInteraction() — if called in the same block as another interaction
JavaScript
// Request to unlock RAAC (will be available after lock period matures)
const unlockAmount = ethers.parseUnits("300", 18);
const tx = await liquidLocker.requestUnlock(unlockAmount);
await tx.wait();

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 available
  • SameBlockInteraction() — if called in the same block as another interaction
JavaScript
// Withdraw all matured unlocked RAAC
const tx = await liquidLocker.withdrawUnlocked();
await tx.wait();

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)
JavaScript
// Approve RAAC spending first
const donationAmount = ethers.parseUnits("100", 18);
await raac.approve(liquidLockerAddress, donationAmount);

// Donate RAAC to be distributed as rewards
const tx = await liquidLocker.donate(donationAmount);
await tx.wait();

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
JavaScript
// Get user's pending unlock schedule
const locks = await liquidLocker.getUserLocks(myAddress);

for (const lock of locks) {
    console.log(`Amount: ${ethers.formatUnits(lock.pendingUnlock, 18)}`);
    console.log(`Unlock Epoch: ${lock.unlockEpoch}`);
    console.log(`Reserved: ${lock.reserved}`);
}

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 global totalLockedGlobal)
  • Debt: borrowing mints leRAAC and tracks debt as UserInfo.totalDebt (and global totalDebtGlobal)
  • Rewards: rewards are tracked with accRewardPerShare and per-user rewardPerSharePaid and rewards. 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 forward
    • reserved=true: entries created from requestUnlock() (user exit intent). When they mature, they are credited into UserInfo.totalUnlocked and can be withdrawn via withdrawUnlocked()

Borrow Health Check

Borrowing and unlock requests enforce an account health invariant:

\[ (\text{totalDeposited} - \text{newUnlock}) \times \text{reserveRate} \geq (\text{totalDebt} + \text{newBorrow}) \times \text{FEE\_PRECISION} \]

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:

  1. veRAAC.claimReward() to pull rewards
  2. Swap reward tokens into an intermediate token and then into RAAC (either via zap or via manual routes with approvedTargets)
  3. Apply fee splits:
    • Treasury fee (with optional splitting to bot locker / clever recipient / treasury)
    • Harvest bounty to the caller-provided recipient
  4. Remaining RAAC is distributed via _distribute():
    • Increases accRewardPerShare for locker depositors
    • Calls maturityVault.distribute(address(this), amount) to pay down maturity-vault debt

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 bufferEpochSpan epochs

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 satisfied
  • noSameBlock may cause failures for complex multi-call flows within the same block
  • Swap routing requires approvedTargets to be enabled for any low-level call route 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)