Skip to main content

MultistrategyLockedVault

Git Source

Inherits: MultistrategyVault, IMultistrategyLockedVault

Author: Golem Foundation

A locked vault with custody-based rage quit mechanism and two-step cooldown period changes

*This vault implements a secure custody system that prevents rage quit cooldown bypass attacks and provides user protection through a two-step governance process for cooldown period changes.

Custody Mechanism:

  1. Share Locking During Rage Quit:
  • Users must initiate rage quit for a specific number of shares
  • Those shares are placed in custody and cannot be transferred
  • Locked shares are tracked separately from the user's transferable balance
  • Transfer restrictions prevent bypassing the cooldown period
  • Only one active rage quit per user (no multiple concurrent rage quits)
  1. Custody Lifecycle:
  • Initiation: User specifies exact number of shares to lock for rage quit
  • Cooldown: Shares remain locked and non-transferable during cooldown period
  • Unlock: After cooldown, user can withdraw/redeem up to their custodied amount
  • Withdrawal: Users can make multiple withdrawals from the same custody
  • Completion: Custody is cleared when all locked shares are withdrawn
  1. Transfer Restrictions:
  • Users cannot transfer locked shares to other addresses
  • Available shares = total balance - locked shares
  • Prevents rage quit cooldown bypass through share transfers
  • Use getTransferableShares() to check available balance for transfers
  1. Withdrawal Rules:
  • Users can only withdraw shares if they have active custody
  • Withdrawal amount cannot exceed remaining custodied shares
  • Multiple partial withdrawals are allowed from the same custody
  • New rage quit required after custody is fully withdrawn
  • maxWithdraw() and maxRedeem() return 0 if no custody or still in cooldown
  1. Utility Functions:
  • getTransferableShares(user): Returns shares available for transfer
  • getRageQuitableShares(user): Returns shares available for rage quit initiation
  • custodyInfo(user): Returns custody details (locked shares, unlock time)

Two-Step Cooldown Period Changes:

  1. Grace Period Protection:
  • Governance proposes cooldown period changes with 14-day delay
  • Users can rage quit under current terms during grace period
  • Protects users from unfavorable governance decisions
  1. Change Process:
  • Propose: Governance proposes new period, starts grace period
  • Grace Period: 14 days for users to exit under current terms
  • Finalize: Anyone can finalize change after grace period
  • Cancel: Governance can cancel during grace period
  1. User Protection:
  • Users who rage quit before finalization use old cooldown period
  • Users who rage quit after finalization use new cooldown period
  • No retroactive application of cooldown changes

Governance:

  • Regen Governance: Has control over rage quit cooldown period changes
  • Direct Transfer: Governance can be transferred immediately (no 2-step process)
  • Access Control: Only regen governance can propose/cancel cooldown changes

Example Scenarios:

Scenario A - Basic Custody Flow:**

  1. User has 1000 shares, initiates rage quit for 500 shares
  2. 500 shares locked in custody, 500 shares remain transferable
  3. getTransferableShares(user) returns 500, getRageQuitableShares(user) returns 0
  4. After cooldown, user can withdraw up to 500 shares
  5. User withdraws 300 shares, 200 shares remain in custody
  6. User can later withdraw remaining 200 shares without new rage quit Scenario B - Two-Step Cooldown Change:**
  7. Current cooldown: 7 days, governance proposes 14 days
  8. Grace period: Users have 14 days to rage quit under 7-day terms
  9. User A rage quits during grace period → uses 7-day cooldown
  10. Change finalized after grace period
  11. User B rage quits after finalization → uses 14-day cooldown Scenario C - Utility Function Usage:**
  12. User has 1000 shares, no active rage quit
  13. getTransferableShares(user) returns 1000
  14. getRageQuitableShares(user) returns 1000
  15. User initiates rage quit for 400 shares
  16. getTransferableShares(user) returns 600
  17. getRageQuitableShares(user) returns 0 (already has active rage quit)*

Note: security-contact: [email protected]

State Variables

custodyInfo

Mapping of user addresses to their custody information

Tracks locked shares and unlock timestamp for each user's rage quit Only one active custody per user (no concurrent rage quits)

mapping(address => CustodyInfo) public custodyInfo;

regenGovernance

Address of regen governance controlling rage quit parameters

Can propose/cancel rage quit cooldown period changes Set during initialize() to the roleManager address

address public regenGovernance;

rageQuitCooldownPeriod

Current active cooldown period for rage quits

In seconds. Applied to new rage quits when initiateRageQuit() is called Can be changed via two-step process (propose + finalize after grace period)

uint256 public rageQuitCooldownPeriod;

pendingRageQuitCooldownPeriod

Pending new cooldown period awaiting finalization

Set to 0 when no change is pending. Non-zero indicates active proposal Requires RAGE_QUIT_COOLDOWN_CHANGE_DELAY to elapse before finalization

uint256 public pendingRageQuitCooldownPeriod;

rageQuitCooldownPeriodChangeTimestamp

Timestamp when current cooldown period change was proposed

Unix timestamp in seconds. Used to calculate grace period expiration Set to 0 when no change is pending

uint256 public rageQuitCooldownPeriodChangeTimestamp;

INITIAL_RAGE_QUIT_COOLDOWN_PERIOD

Initial rage quit cooldown period set at deployment

7 days in seconds. Applied until governance changes it

uint256 public constant INITIAL_RAGE_QUIT_COOLDOWN_PERIOD = 7 days;

RANGE_MINIMUM_RAGE_QUIT_COOLDOWN_PERIOD

Minimum allowed rage quit cooldown period

1 day in seconds. Prevents cooldown from being set too short

uint256 public constant RANGE_MINIMUM_RAGE_QUIT_COOLDOWN_PERIOD = 1 days;

RANGE_MAXIMUM_RAGE_QUIT_COOLDOWN_PERIOD

Maximum allowed rage quit cooldown period

30 days in seconds. Prevents cooldown from being set too long

uint256 public constant RANGE_MAXIMUM_RAGE_QUIT_COOLDOWN_PERIOD = 30 days;

RAGE_QUIT_COOLDOWN_CHANGE_DELAY

Grace period delay for cooldown changes

14 days in seconds. Users have this time to rage quit under old terms

uint256 public constant RAGE_QUIT_COOLDOWN_CHANGE_DELAY = 14 days;

Functions

onlyRegenGovernance

Modifier to restrict access to regen governance only

Note: modifier: Reverts with NotRegenGovernance if caller is not regen governance

modifier onlyRegenGovernance();

initialize

Initializes the locked vault with custody mechanism

*Extends MultistrategyVault.initialize() with custody features INITIALIZATION:

  1. Sets initial rage quit cooldown to INITIAL_RAGE_QUIT_COOLDOWN_PERIOD (7 days)
  2. Calls parent initialize() for standard vault setup
  3. Sets _roleManager as regenGovernance address DUAL ROLE:
  • _roleManager becomes both roleManager AND regenGovernance
  • roleManager: Controls vault roles (from parent)
  • regenGovernance: Controls rage quit parameters (this contract)*

Note: security: Can only be called once per deployment

function initialize(
address _asset,
string memory _name,
string memory _symbol,
address _roleManager,
uint256 _profitMaxUnlockTime
) public override(MultistrategyVault, IMultistrategyVault);

Parameters

NameTypeDescription
_assetaddressAddress of underlying asset token (cannot be zero)
_namestringHuman-readable vault token name
_symbolstringVault token symbol ticker
_roleManageraddressAddress for role management AND regen governance
_profitMaxUnlockTimeuint256Profit unlock duration in seconds (0-31556952)

proposeRageQuitCooldownPeriodChange

Proposes a new rage quit cooldown period (step 1 of 2)

*Initiates two-step change process with grace period for user protection VALIDATION:

  • Must be within valid range (1-30 days)
  • Must differ from current cooldown period PROCESS:
  1. Validates new period is within allowed range
  2. Sets pendingRageQuitCooldownPeriod
  3. Records proposal timestamp
  4. Users have 14 days to rage quit under current terms
  5. After 14 days, anyone can finalize the change*

Notes:

  • security: Only callable by regenGovernance

  • security: 14-day grace period protects users from unfavorable changes

function proposeRageQuitCooldownPeriodChange(uint256 _rageQuitCooldownPeriod) external onlyRegenGovernance;

Parameters

NameTypeDescription
_rageQuitCooldownPerioduint256New cooldown period in seconds (86400-2592000, i.e., 1-30 days)

finalizeRageQuitCooldownPeriodChange

Finalizes the rage quit cooldown period change (step 2 of 2)

*Permissionless - anyone can call after grace period expires REQUIREMENTS:

  • Must have pending change (pendingRageQuitCooldownPeriod != 0)
  • Grace period (14 days) must have elapsed EFFECTS:
  • Updates rageQuitCooldownPeriod to pending value
  • Clears pending state
  • New rage quits use new cooldown period immediately*
function finalizeRageQuitCooldownPeriodChange() external;

cancelRageQuitCooldownPeriodChange

Cancels a pending rage quit cooldown period change

*Only callable during grace period (before finalization) REQUIREMENTS:

  • Must have pending change
  • Grace period must NOT have elapsed yet EFFECTS:
  • Clears all pending change state
  • Current cooldown period remains unchanged*

Notes:

  • security: Only callable by regenGovernance

  • security: Cannot cancel after grace period expires

function cancelRageQuitCooldownPeriodChange() external onlyRegenGovernance;

initiateRageQuit

Initiates rage quit by locking shares in custody

*Creates custody entry with current cooldown period REQUIREMENTS:

  • shares > 0
  • shares <= user's balance
  • User must NOT have existing active custody EFFECTS:
  • Locks specified shares (become non-transferable)
  • Sets unlock time = current timestamp + rageQuitCooldownPeriod
  • User can withdraw after unlock time IMPORTANT:
  • Uses CURRENT cooldown period (not pending)
  • Locked shares cannot be transferred
  • Only one custody per user at a time*

Notes:

  • security: Reentrancy protected

  • security: Prevents cooldown bypass via transfers

function initiateRageQuit(uint256 shares) external nonReentrant;

Parameters

NameTypeDescription
sharesuint256Number of shares to lock for rage quit

cancelRageQuit

Cancels rage quit and releases custodied shares

*Clears custody, making all shares transferable again REQUIREMENTS:

  • Must have active custody (lockedShares > 0) EFFECTS:
  • Deletes entire custody entry
  • All shares become transferable
  • User can initiate new rage quit if desired*
function cancelRageQuit() external;

withdraw

Override withdrawal functions to handle custodied shares

function withdraw(uint256 assets, address receiver, address owner, uint256 maxLoss, address[] calldata strategiesArray)
public
override(MultistrategyVault, IMultistrategyVault)
nonReentrant
returns (uint256);

redeem

Override redeem function to handle custodied shares

function redeem(uint256 shares, address receiver, address owner, uint256 maxLoss, address[] calldata strategiesArray)
public
override(MultistrategyVault, IMultistrategyVault)
nonReentrant
returns (uint256);

setRegenGovernance

Set the regen governance address that can manage rage quit parameters

*Regen governance has exclusive control over:

  • Proposing rage quit cooldown period changes
  • Cancelling pending cooldown period changes*

Note: governance: Only current regen governance can call this function

function setRegenGovernance(address _regenGovernance) external override onlyRegenGovernance;

Parameters

NameTypeDescription
_regenGovernanceaddressNew address to become regen governance

_processCustodyWithdrawal

Process withdrawal of shares from custody during withdraw/redeem operations

*Internal function that enforces custody withdrawal rules:

  • Owner must have active custody (lockedShares > 0)
  • Shares must still be locked (current time < unlockTime)
  • Withdrawal amount cannot exceed remaining custodied shares
  • Updates custody state by reducing locked shares
  • Clears custody when all locked shares are withdrawn*

Note: security: Prevents unauthorized withdrawals and custody bypass

function _processCustodyWithdrawal(address owner, uint256 shares) internal;

Parameters

NameTypeDescription
owneraddressAddress of the share owner attempting withdrawal
sharesuint256Number of shares being withdrawn/redeemed

_transfer

Override ERC20 transfer to enforce custody transfer restrictions

*Implements custody-based transfer restrictions:

  • Calculates available shares (total balance - locked shares)
  • Prevents transfer if amount exceeds available shares
  • Allows normal transfers for non-custodied shares
  • Critical security feature preventing rage quit cooldown bypass*

Note: security: Prevents users from bypassing cooldown by transferring locked shares

function _transfer(address sender_, address receiver_, uint256 amount_) internal override;

Parameters

NameTypeDescription
sender_addressAddress attempting to send shares
receiver_addressAddress that would receive shares
amount_uint256Number of shares being transferred

maxWithdraw

Get the maximum amount of assets that can be withdrawn by an owner

*This override accounts for custody constraints - returns 0 if:

  • Custody is still in cooldown period
  • Otherwise returns min of parent calculation and custodied shares in asset terms*
function maxWithdraw(address owner_, uint256 maxLoss_, address[] calldata strategiesArray_)
external
view
override(MultistrategyVault, IMultistrategyVault)
returns (uint256);

Parameters

NameTypeDescription
owner_addressAddress owning shares to check withdrawal limits for
maxLoss_uint256Custom max_loss if any
strategiesArray_address[]Custom strategies queue if any

Returns

NameTypeDescription
&lt;none&gt;uint256Maximum amount of assets withdrawable

maxRedeem

Get the maximum amount of shares that can be redeemed by an owner

*This override accounts for custody constraints - returns 0 if:

  • Custody is still in cooldown period
  • Otherwise returns min of balance and custodied shares*
function maxRedeem(address owner_, uint256 maxLoss_, address[] calldata strategiesArray_)
external
view
override(MultistrategyVault, IMultistrategyVault)
returns (uint256);

Parameters

NameTypeDescription
owner_addressAddress owning shares to check redemption limits for
maxLoss_uint256Custom max_loss if any
strategiesArray_address[]Custom strategies queue if any

Returns

NameTypeDescription
&lt;none&gt;uint256Maximum amount of shares redeemable

getTransferableShares

Get the amount of shares that can be transferred by a user

Returns total balance minus shares currently locked in custody

function getTransferableShares(address user) external view returns (uint256);

Parameters

NameTypeDescription
useraddressAddress to check transferable shares for

Returns

NameTypeDescription
&lt;none&gt;uint256Amount of shares available for transfer (not locked in custody)

getRageQuitableShares

Get the amount of shares available for rage quit initiation

Returns 0 if user already has active custody, otherwise returns full balance

function getRageQuitableShares(address user) external view returns (uint256);

Parameters

NameTypeDescription
useraddressAddress to check rage quitable shares for

Returns

NameTypeDescription
&lt;none&gt;uint256Amount of shares available for initiating rage quit