Skip to main content

BaseAllocationMechanism

Git Source

Inherits: IBaseAllocationStrategy

Title: Base Allocation Mechanism

Author: Golem Foundation

Abstract base for allocation/voting mechanisms using lightweight proxy pattern

Follows Yearn V3 architecture: minimal proxy delegating to shared TokenizedAllocationMechanism

ARCHITECTURE PATTERN:

  • Inheritors only implement custom hooks (no shared logic duplication)
  • Shared logic lives in TokenizedAllocationMechanism (implementation)
  • Each mechanism is a lightweight proxy with custom behavior via hooks
  • Delegatecall pattern: proxy storage, implementation logic

HOOK SYSTEM:

13 abstract hooks to implement:

Registration:

  • _beforeSignupHook: Allow/block user registration
  • _getVotingPowerHook: Calculate voting power on signup

Proposing:

  • _beforeProposeHook: Allow/block proposal creation
  • _validateProposalHook: Validate proposal exists

Voting:

  • _processVoteHook: Process vote and update voting power
  • _hasQuorumHook: Check if proposal reached quorum

Distribution:

  • _convertVotesToShares: Convert votes to vault shares
  • _getRecipientAddressHook: Get recipient for proposal
  • _requestCustomDistributionHook: Custom share distribution

Finalization:

  • _beforeFinalizeVoteTallyHook: Pre-finalization checks

Withdrawal:

  • _availableWithdrawLimit: Enforce timelock/grace period

Accounting:

  • _calculateTotalAssetsHook: Total assets including matching pools

LIFECYCLE:

  1. Deploy: Constructor calls TokenizedAllocationMechanism.initialize()
  2. Registration: Users deposit assets and receive voting power
  3. Propose: Create proposals for funding allocation
  4. Vote: Users cast votes (Against/For/Abstain)
  5. Finalize: Tally votes, determine winning proposals
  6. Queue: Mint shares for successful proposals (after timelock)
  7. Redeem: Recipients redeem shares for assets (during grace period)

TIMELOCK & GRACE PERIOD:

  • Timelock: Security delay before redemptions begin
  • Grace Period: Window during which redemptions are allowed
  • After grace period: Unredeemed shares become non-withdrawable

Notes:

  • security-contact: [email protected]

  • security: All hooks called via delegatecall from implementation

  • security: onlySelf modifier prevents direct external calls to hooks

Quick Start — What Matters Most

This is the proxy contract that mechanism authors inherit. Storage lives here; lifecycle logic is in the shared TokenizedAllocationMechanism.

Implement these five hook functions (the most critical ones):

  • _beforeSignupHook — Allow/block user registration
  • _getVotingPowerHook — Calculate voting power per user
  • _processVoteHook — Process votes and update voting power
  • _convertVotesToShares — Convert votes into vault share amounts
  • _getRecipientAddressHook — Determine who receives minted shares

State Variables

tokenizedAllocationAddress

Address of the shared TokenizedAllocationMechanism implementation

address internal immutable tokenizedAllocationAddress

asset

Underlying asset for the allocation mechanism

IERC20 internal immutable asset

Functions

constructor

Initializes the allocation mechanism with implementation and configuration

constructor(address _implementation, AllocationConfig memory _config) ;

Parameters

NameTypeDescription
_implementationaddressAddress of the TokenizedAllocationMechanism implementation
_configAllocationConfigConfiguration parameters for the allocation mechanism

_beforeSignupHook

REQUIRED: Determines if a user can register

Called before signup to implement access control IMPLEMENTATION GUIDANCE:

  • Return true to allow registration
  • Return false to block (will revert signup)
  • Can check allowlists, blocklists, KYC status, etc. COMMON PATTERNS:
  • Open: Always return true (permissionless)
  • Allowlist: Check mapping(address => bool) allowedUsers
  • Minimum deposit: Check deposit >= minimumAmount
function _beforeSignupHook(address user) internal virtual returns (bool);

Parameters

NameTypeDescription
useraddressAddress attempting to register

Returns

NameTypeDescription
<none>boolallow True to allow registration, false to block

_beforeProposeHook

REQUIRED: Determines if an address can create proposals

Called before propose to implement proposer restrictions IMPLEMENTATION GUIDANCE:

  • Return true to allow proposal creation
  • Return false to block (will revert)
  • Can check minimum voting power, specific roles, etc. COMMON PATTERNS:
  • Open: Always return true
  • Minimum power: Check votingPower >= threshold
  • Registered only: Check user has deposited
function _beforeProposeHook(address proposer) internal view virtual returns (bool);

Parameters

NameTypeDescription
proposeraddressAddress attempting to create proposal

Returns

NameTypeDescription
<none>boolallow True to allow proposal, false to block

_getVotingPowerHook

REQUIRED: Calculates voting power assigned on registration

Called during signup to determine user's initial voting power IMPLEMENTATION GUIDANCE:

  • Must return value > 0 for successful registration
  • Should be deterministic based on deposit amount
  • Consider manipulation resistance (flash loan protection) COMMON PATTERNS:
  • Linear: power = deposit (1 token = 1 vote)
  • Quadratic: power = sqrt(deposit) (reduces whale influence)
  • Capped: power = min(deposit, maxPower)
  • Time-weighted: power = f(deposit, stakeDuration) SECURITY CONSIDERATIONS:
  • Prevent flash loan manipulation (use checkpoints/locks)
  • Validate deposit amount > 0
  • Ensure calculation cannot overflow
function _getVotingPowerHook(address user, uint256 deposit) internal view virtual returns (uint256);

Parameters

NameTypeDescription
useraddressAddress registering (can be used for user-specific logic)
deposituint256Amount of underlying tokens deposited (in asset base units)

Returns

NameTypeDescription
<none>uint256power Voting power to assign (0 = registration fails)

_validateProposalHook

REQUIRED: Validates that a proposal ID exists

Called to verify proposal exists before operations

function _validateProposalHook(uint256 pid) internal view virtual returns (bool);

Parameters

NameTypeDescription
piduint256Proposal ID to validate

Returns

NameTypeDescription
<none>boolvalid True if proposal exists and is valid

_processVoteHook

REQUIRED: Processes a vote and updates voting power

Called when user casts a vote. Must update vote tallies and return updated voting power IMPLEMENTATION GUIDANCE:

  • Record vote in proposal's tally
  • Deduct used voting power from voter's available power
  • Return new voting power (must be <= oldPower)
  • Prevent double voting or power overflow VOTING MODELS:
  • One-time: Use full power once (newPower = 0)
  • Proportional: Deduct weight from power (newPower = oldPower - weight)
  • Quadratic: Deduct weight squared (cost increases with usage)
function _processVoteHook(
uint256 pid,
address voter,
TokenizedAllocationMechanism.VoteType choice,
uint256 weight,
uint256 oldPower
) internal virtual returns (uint256 newPower);

Parameters

NameTypeDescription
piduint256Proposal ID being voted on
voteraddressAddress casting the vote
choiceTokenizedAllocationMechanism.VoteTypeVote type (Against=0, For=1, Abstain=2)
weightuint256Amount of voting power to use
oldPoweruint256Voter's current voting power before this vote

Returns

NameTypeDescription
newPoweruint256Voter's voting power after this vote (must be <= oldPower)

_hasQuorumHook

REQUIRED: Checks if proposal reached quorum

Called to determine if proposal has enough votes to pass

function _hasQuorumHook(uint256 pid) internal view virtual returns (bool);

Parameters

NameTypeDescription
piduint256Proposal ID

Returns

NameTypeDescription
<none>boolhasQuorum True if proposal has sufficient votes

_convertVotesToShares

REQUIRED: Converts votes to vault shares for winning proposal

Called when queuing successful proposal. Determines funding allocation COMMON PATTERNS:

  • Direct: shares = votes (1 vote = 1 asset)
  • Quadratic: shares = votes^2 (quadratic funding)
  • Matched: shares = votes + matching(votes)
function _convertVotesToShares(uint256 pid) internal view virtual returns (uint256 sharesToMint);

Parameters

NameTypeDescription
piduint256Proposal ID being queued

Returns

NameTypeDescription
sharesToMintuint256Vault shares to allocate (in share base units)

_beforeFinalizeVoteTallyHook

REQUIRED: Pre-finalization validation hook

Called before finalizeVoteTally to enforce any custom rules

function _beforeFinalizeVoteTallyHook() internal virtual returns (bool);

Returns

NameTypeDescription
<none>boolallow True to proceed with finalization

_getRecipientAddressHook

REQUIRED: Returns recipient address for a proposal

Called during redemption to determine where shares go

function _getRecipientAddressHook(uint256 pid) internal view virtual returns (address recipient);

Parameters

NameTypeDescription
piduint256Proposal ID

Returns

NameTypeDescription
recipientaddressAddress to receive the minted shares

_requestCustomDistributionHook

OPTIONAL: Custom distribution instead of standard share minting

Called during queue. Return (true, amount) to skip default minting USE CASES:

  • Direct asset transfer instead of shares
  • Multi-recipient distribution
  • Custom vesting schedules DEFAULT BEHAVIOR: Return (false, 0) to use standard share minting
function _requestCustomDistributionHook(address recipient, uint256 sharesToMint)
internal
virtual
returns (bool handled, uint256 assetsTransferred);

Parameters

NameTypeDescription
recipientaddressAddress of the recipient
sharesToMintuint256Number of shares to distribute

Returns

NameTypeDescription
handledboolTrue if custom distribution performed (skips default minting)
assetsTransferreduint256Amount of assets transferred (for totalAssets accounting)

_availableWithdrawLimit

Hook to get the available withdraw limit for a share owner

Default implementation enforces timelock and grace period boundaries

Can be overridden for custom withdrawal limit logic

function _availableWithdrawLimit(
address /* shareOwner */
)
internal
view
virtual
returns (uint256);

Returns

NameTypeDescription
<none>uint256limit Available withdraw limit (type(uint256).max for unlimited, 0 for blocked)

_calculateTotalAssetsHook

Hook to calculate total assets including any matching pools or custom logic

function _calculateTotalAssetsHook() internal view virtual returns (uint256);

Returns

NameTypeDescription
<none>uint256totalAssets Total assets for this allocation mechanism

onlySelf

Ensures function can only be called via delegatecall from TokenizedAllocationMechanism

In delegatecall context, msg.sender is the proxy address (address(this)), not the caller

modifier onlySelf() ;

beforeSignupHook

External wrapper for _beforeSignupHook, called by TokenizedAllocationMechanism via delegatecall

function beforeSignupHook(address user) external onlySelf returns (bool);

Parameters

NameTypeDescription
useraddressAddress attempting to register

Returns

NameTypeDescription
<none>boolallow True to allow registration, false to block

beforeProposeHook

External wrapper for _beforeProposeHook, called by TokenizedAllocationMechanism via delegatecall

function beforeProposeHook(address proposer) external view onlySelf returns (bool);

Parameters

NameTypeDescription
proposeraddressAddress attempting to create proposal

Returns

NameTypeDescription
<none>boolallow True to allow proposal, false to block

getVotingPowerHook

External wrapper for _getVotingPowerHook, called by TokenizedAllocationMechanism via delegatecall

function getVotingPowerHook(address user, uint256 deposit) external view onlySelf returns (uint256);

Parameters

NameTypeDescription
useraddressAddress registering
deposituint256Amount deposited in asset base units

Returns

NameTypeDescription
<none>uint256power Voting power to assign

validateProposalHook

External wrapper for _validateProposalHook, called by TokenizedAllocationMechanism via delegatecall

function validateProposalHook(uint256 pid) external view onlySelf returns (bool);

Parameters

NameTypeDescription
piduint256Proposal ID to validate

Returns

NameTypeDescription
<none>boolvalid True if proposal exists and is valid

processVoteHook

External wrapper for _processVoteHook, called by TokenizedAllocationMechanism via delegatecall

function processVoteHook(uint256 pid, address voter, uint8 choice, uint256 weight, uint256 oldPower)
external
onlySelf
returns (uint256);

Parameters

NameTypeDescription
piduint256Proposal ID being voted on
voteraddressAddress casting the vote
choiceuint8Vote type (0=Against, 1=For, 2=Abstain)
weightuint256Amount of voting power to use
oldPoweruint256Voter's current voting power

Returns

NameTypeDescription
<none>uint256newPower Voter's voting power after this vote

hasQuorumHook

External wrapper for _hasQuorumHook, called by TokenizedAllocationMechanism via delegatecall

function hasQuorumHook(uint256 pid) external view onlySelf returns (bool);

Parameters

NameTypeDescription
piduint256Proposal ID

Returns

NameTypeDescription
<none>boolhasQuorum True if proposal has sufficient votes

convertVotesToShares

External wrapper for _convertVotesToShares, called by TokenizedAllocationMechanism via delegatecall

function convertVotesToShares(uint256 pid) external view onlySelf returns (uint256);

Parameters

NameTypeDescription
piduint256Proposal ID being queued

Returns

NameTypeDescription
<none>uint256sharesToMint Vault shares to allocate in share base units

beforeFinalizeVoteTallyHook

External wrapper for _beforeFinalizeVoteTallyHook, called by TokenizedAllocationMechanism via delegatecall

function beforeFinalizeVoteTallyHook() external onlySelf returns (bool);

Returns

NameTypeDescription
<none>boolallow True to proceed with finalization

getRecipientAddressHook

External wrapper for _getRecipientAddressHook, called by TokenizedAllocationMechanism via delegatecall

function getRecipientAddressHook(uint256 pid) external view onlySelf returns (address);

Parameters

NameTypeDescription
piduint256Proposal ID

Returns

NameTypeDescription
<none>addressrecipient Address to receive the minted shares

requestCustomDistributionHook

External wrapper for _requestCustomDistributionHook, called by TokenizedAllocationMechanism via delegatecall

function requestCustomDistributionHook(address recipient, uint256 sharesToMint)
external
onlySelf
returns (bool handled, uint256 assetsTransferred);

Parameters

NameTypeDescription
recipientaddressAddress of the recipient
sharesToMintuint256Number of shares to distribute

Returns

NameTypeDescription
handledboolTrue if custom distribution performed
assetsTransferreduint256Amount of assets transferred

availableWithdrawLimit

External wrapper for _availableWithdrawLimit, called by TokenizedAllocationMechanism via delegatecall

function availableWithdrawLimit(address shareOwner) external view onlySelf returns (uint256);

Parameters

NameTypeDescription
shareOwneraddressAddress to check withdraw limit for

Returns

NameTypeDescription
<none>uint256limit Available withdraw limit (type(uint256).max for unlimited, 0 for blocked)

calculateTotalAssetsHook

External wrapper for _calculateTotalAssetsHook, called by TokenizedAllocationMechanism via delegatecall

function calculateTotalAssetsHook() external view onlySelf returns (uint256);

Returns

NameTypeDescription
<none>uint256totalAssets Total assets for this allocation mechanism

_tokenizedAllocation

Access TokenizedAllocationMechanism interface for internal calls

Uses current contract address since storage is local

function _tokenizedAllocation() internal view returns (TokenizedAllocationMechanism);

_getGracePeriod

Get grace period from configuration

function _getGracePeriod() internal view returns (uint256);

Returns

NameTypeDescription
<none>uint256Grace period in seconds

_getGlobalRedemptionStart

Get global redemption start timestamp

function _getGlobalRedemptionStart() internal view returns (uint256);

Returns

NameTypeDescription
<none>uint256Timestamp when redemption window opens (0 if not finalized)

fallback

Delegates all undefined function calls to TokenizedAllocationMechanism

This enables the proxy pattern where shared logic lives in the implementation

fallback() external payable virtual;

receive

Receive function to accept ETH

receive() external payable virtual;

_getProposalCount

Get the current proposal count

Helper for concrete implementations to access storage

function _getProposalCount() internal view returns (uint256);

_proposalExists

Check if a proposal exists

Helper for concrete implementations

function _proposalExists(uint256 pid) internal view returns (bool);

_getProposal

Get proposal details

Helper for concrete implementations

function _getProposal(uint256 pid) internal view returns (TokenizedAllocationMechanism.Proposal memory);

_getVotingPower

Get voting power for an address

Helper for concrete implementations

function _getVotingPower(address user) internal view returns (uint256);

_getQuorumShares

Get quorum shares requirement

Helper for concrete implementations

function _getQuorumShares() internal view returns (uint256);

Events

AllocationMechanismInitialized

Emitted when the allocation mechanism is initialized

event AllocationMechanismInitialized(
address indexed implementation, address indexed asset, string name, string symbol
);