USUAL Distribution
High-Level Overview
This contract manages a Distribution process of the Usual token. The contract should be called daily to calculate how many new tokens are created, distributed to on-chain vaults (UsualX and UsualS) and how many tokens are available to claim for the off-chain users with a valid merkle proof.
The core features of this contract include:
Daily Distribution: The contract should be called daily to calculate how many new tokens are created, distributed to On Chain vaults (UsualX and UsualS) and how many tokens are available to claim for the off-chain users with a valid merkle proof. The amount distributed scales based on how much longer (after one day) it has been since the last distribution to account for calling the function at greater than 24 hour periods.
Merkle Proof for Off-Chain Users: Ensures that only eligible users, validated through merkle proofs, can receive tokens from the distribution.
Challengeable Queue of Merkle Proofs: The contract should have a queue of merkle proofs that can be challenged by a specific role (e.g. governance) to ensure that the off-chain distribution is fair and correct.
Access Control: Each functionality should be controlled by a role with minimal required permissions.
Contract Summary
2.1 Inherited Contracts
Initializable: For upgradeable contract initialization.
PausableUpgradeable: Allows contract execution to be paused or unpaused by an authorized role.
ReentrancyGuardUpgradeable: Provides protection against reentrancy attacks.
IDistributionModule: Interface that defines all functions that can be called by anyone.
IDistributionAllocator: Interface that defines the functions that can be only called by the
DISTRIBUTION_ALLOCATOR_ROLE
IDistributionOperator: Interface that defines the functions that can be only called by the
DISTRIBUTION_OPERATOR_ROLE
. Those functions are used to daily distribute the Usual tokens to on-chain and off-chain buckets.IOffChainDistributionChallenger: Interface that defines the functions that can be only called by the
OFF_CHAIN_DISTRIBUTION_CHALLENGER_ROLE
. Those functions are used to challenge the off-chain distribution in the queue.
Functionality Description
Public/External Functions
initialize: Initializes the contract with the registry address and sets the initial
rate0
valuepause: Admin function to pause the contract. Can be only called by
PAUSING_CONTRACTS_ROLE
unpause: Admin function to unpause the contract. Can be only called by
DEFAULT_ADMIN_ROLE
IDistributionModule
getBucketsDistribution: Returns the current buckets distribution percentages in basis points. Off-chain buckets are LST, LYT, IYT, Bribe, Ecosystem, DAO, and Market Makers. On-Chain buckets are UsualX and UsualS.
calculateUsualDist: Helper view function that calculates a simulated Usual Distribution for the provided
ratet
andp90Rate
values. It returnsst
,rt
andkappa
values that were used in the calculation.calculateSt: Helper view function that returns a calculated
st
value based on providedsupplyPpt
andpt
values.calculateRt: Helper view function that returns a calculated
rt
value based on providedratet
andp90Rate
values.calculateMt: Helper view function that returns a calculated
mt
value based on providedst
,rt
andkappa
values.getOffChainDistributionData: Returns the currently approved off-chain distribution
timestamp
andmerkleRoot
values. That can be used to pre-validate the merkle proof before calling theclaimOffChainDistribution
function.getOffChainDistributionQueue: Returns the current off-chain distribution queue. The queue is a list of
timestamp
andmerkleRoot
values that are used to claim the off-chain distribution.getOffChainDistributionMintCap: Returns the current off-chain distribution mint cap value. This value is the maximum amount of tokens that can be minted by through the off-chain distribution. It is reduced with every successful claim.
approveUnchallengedOffChainDistribution: Approves the latest unchallenged off-chain distribution from the queue that was in the queue for more than
USUAL_DISTRIBUTION_CHALLENGE_PERIOD
. All distributions that were in the queue for more thanUSUAL_DISTRIBUTION_CHALLENGE_PERIOD
are removed from the queue. Even if anyone can call this function, usually it will be called by theDISTRIBUTION_OPERATOR_ROLE
.getOffChainTokensClaimed: Returns the amount of the tokens claimed by the given
account
address in the off-chain distribution until now.claimOffChainDistribution: Allows to claim the Usual tokens from the latest approved off-chain distribution. Caller should provide a valid merkle proof for the provided
account
andamount
. Theamount
is the total amount of tokens that were assigned for the givenaccount
address since the beginning of the distribution. The Usual tokens are minted when the proof is valid and the givenaccount
has any tokens to claim in the approved off-chain distribution. The givenaccount
will only receive the tokens that were not claimed by them before based on the value returned by thegetOffChainTokensClaimed
function. The mint cap should be reduced by the claimed amount. ThegetOffChainTokensClaimed
should return increased value.
IDistributionAllocator
setBucketsDistribution: Allows to set the new buckets distribution percentages in basis points. The sum of all values should be equal to
BASIS_POINT_BASE
. The function can be only called by theDISTRIBUTION_ALLOCATOR_ROLE
. Off-chain buckets are LST, LYT, IYT, Bribe, Ecosystem, DAO, and Market Makers. On-Chain buckets are Usual+ and Usual*.setD: Set
D
parameter used it the emissions calculation formula. The function can be only called by theDISTRIBUTION_ALLOCATOR_ROLE
.getD: Returns the current
D
parameter value.setM0: Set
m0
parameter used it the emissions calculation formula. The function can be only called by theDISTRIBUTION_ALLOCATOR_ROLE
.getM0: Returns the current
m0
parameter value.setRateMin: Set
rateMin
parameter used it the emissions calculation formula. The function can be only called by theDISTRIBUTION_ALLOCATOR_ROLE
.getRateMin: Returns the current
rateMin
parameter value.setBaseGamma: Set
baseGamma
parameter used it the emissions calculation formula. The function can be only called by theDISTRIBUTION_ALLOCATOR_ROLE
.getBaseGamma: Returns the current
baseGamma
parameter value.
IDistributionOperator
distributeUsualToBuckets: Calculates the Usual emissions based on the current state of parameters and provided
ratet
andp90Rate
values. For on-chain buckets (Usual+ and Usual*) the tokens are minted directly to the bucket and a vault specific distribution is started. For off-chain buckets (LST, LYT, IYT, Bribe, Ecosystem, DAO, and Market Makers) mint is delayed until the off-chain distribution is approved and tokens are claimed. The function can be only called by theDISTRIBUTION_OPERATOR_ROLE
.queueOffChainDistribution: Queues the off-chain distribution for the given
merkleRoot
. The function can be only called by theDISTRIBUTION_OPERATOR_ROLE
. This function cannot be called more than once per 24 hours.resetOffChainDistributionQueue: Removes all off-chain distributions from the queue. This is an emergency functionality is queue ever gets to big to be pruned during a
approveUnchallengedOffChainDistribution
call. The function can be only called by theDISTRIBUTION_OPERATOR_ROLE
.
IOffChainDistributionChallenger
challengeOffChainDistribution: Challenges all off-chain distributions in the queue that are older than specified timestamp. They are marked as challenged and cannot be approved. The function can be only called by the
DISTRIBUTION_CHALLENGER_ROLE
.challengeAndProposeOffChainDistribution: Challenges all off-chain distributions in the queue that are older than specified timestamp. They are marked as challenged and cannot be approved. The new off-chain distribution is proposed with the given
merkleRoot
and it still has to wait in the queue forUSUAL_DISTRIBUTION_CHALLENGE_PERIOD
before it can be approved. The function can be only called by theDISTRIBUTION_CHALLENGER_ROLE
.
Functionality Breakdown
Setting up calculations parameters
DISTRIBUTION_ALLOCATOR_ROLE
can change all the values that are used in the emissions calculations formula. There is no timelock for those changes.DISTRIBUTION_ALLOCATOR_ROLE
can change the distribution buckets shares percentages. The sum of all values should be equal to 100% (in basis points). There is no timelock for those changes.
Daily Emissions
DISTRIBUTION_OPERATOR_ROLE
is required to call thedistributeUsualToBuckets
every 24 hours to calculate the new emissions and distribute them to the on-chain buckets and increase the off-chain buckets mint cap that can be claimed after successful approval of the off-chain distribution that is unchallenged and in the queue for more thanUSUAL_DISTRIBUTION_CHALLENGE_PERIOD
.DISTRIBUTION_OPERATOR_ROLE
is required to call thequeueOffChainDistribution
with a validmerkleRoot
as soon as the off-chain distribution mint cap is increased.
Off-Chain Distribution Queue, Approval and Challenges
Once
DISTRIBUTOR_OPERATOR_ROLE
puts a new off-chain distribution in the queue, it has to wait forUSUAL_DISTRIBUTION_CHALLENGE_PERIOD
before it can be approved.Anyone can call the
approveUnchallengedOffChainDistribution
function to approve the latest unchallenged off-chain distribution from the queue that was in the queue for more thanUSUAL_DISTRIBUTION_CHALLENGE_PERIOD
. This function will remove all distributions that were in the queue for more thanUSUAL_DISTRIBUTION_CHALLENGE_PERIOD
which impacts the gas cost of the function. In normal circumstances, there should be only one distribution in the queue that is older thanUSUAL_DISTRIBUTION_CHALLENGE_PERIOD
and has to be removed.DISTRIBUTION_CHALLENGER_ROLE
can call thechallengeOffChainDistribution
function to challenge all off-chain distributions in the queue that are older than specified timestamp. They are marked as challenged and cannot be approved. In rare cases, a new off-chain distribution can be proposed and it is put in the queue.The queue should be treated as unordered.
Merkle Tree Root
The merkle tree root that is queued should be calculated off-chain and should include
account
andamount
values for each user that is eligible for claiming the off-chain distribution. Theamount
value should be the total amount of tokens that were assigned for the givenaccount
address since the beginning of the distribution. Theamount
value for the givenaccount
should never decrease. Theaccount
address should be in the format0x123...
and theamount
should be in wei.
Merkle Proof Validation
All claims are validated against a Merkle tree using the MerkleProof
library, ensuring that only eligible users can receive tokens. Separate Merkle roots are set for the initial and vesting periods.
Admin Control
Administrators have control over setting key contract parameters, such as the Merkle roots and penalty percentages, as well as pausing the contract in case of emergencies.
Constants
DEFAULT_ADMIN_ROLE: Role required to unpause the contract
PAUSING_CONTRACTS_ROLE: Role required to pause the contract
DISTRIBUTION_ALLOCATOR_ROLE: Role required to set the distribution parameters and buckets distribution percentages
DISTRIBUTION_OPERATOR_ROLE: Role required to distribute the Usual tokens to buckets and queue the off-chain distribution
DISTRIBUTION_CHALLENGER_ROLE: Role required to challenge the off-chain distribution in the queue
USUAL_DISTRIBUTION_CHALLENGE_PERIOD: The period in seconds that the off-chain distribution has to wait in the queue before it can be approved. It is 7 days by default.
CONTRACT_REGISTRY_ACCESS: This constant is used to define the address of the registry access contract.
CONTRACT_ORACLE: This constant is used to define the address of the oracle contract.
CONTRACT_USD0: This constant is used to define the address of the USD0 contract.
CONTRACT_USD0PP: This constant is used to define the address of the USD0PP contract.
CONTRACT_USUAL: This constant is used to define the address of the Usual contract.
CONTRACT_USUALSP: This constant is used to define the address of the UsualSP contract.
CONTRACT_USUALX: This constant is used to define the address of the UsualX contract.
CONTRACT_DAO_COLLATERAL: This constant is used to define the address of the DAO collateral contract.
BASIS_POINT_BASE: The base value for calculating percentages in basis points.
BPS_SCALAR: The scalar value for calculating percentages in basis points.
SCALAR_ONE: The scalar value for calculating percentages with 10^18 precision.
REWARD_PERIOD_SCALAR: The scalar value for calculating time since last distribution above 1 day in seconds.
Safeguard Implementation
Reentrancy Guard
All external function handling token mints and transfer are protected with ReentrancyGuard
to prevent reentrancy attacks.
Access Control
The contract uses CheckAccessControl
to enforce role-based access control, ensuring only authorized addresses can perform actions that require specific permissions. Each role has minimal required permissions.
Pausability
The contract is pausable by PAUSING_CONTRACTS_ROLE
, enabling the ability to pause all operations in case of an emergency. The contract can be resumed only by DEFAULT_ADMIN_ROLE
.
Off-chain distribution queue and challenge mechanism
The off-chain distribution can be validated for USUAL_DISTRIBUTION_CHALLENGE_PERIOD
before it can be approved. If an invalid distribution is proposed, it can be challenged by the DISTRIBUTION_CHALLENGER_ROLE
making it impossible to be approved.
If the queue ever gets too big, the resetOffChainDistributionQueue
function can be called by the DISTRIBUTION_OPERATOR_ROLE
to remove all off-chain distributions from the queue.
Restrictions how often certain functions can be called
There is a 24 hours limit for the calculateUsualDist
function to be called. To prevent unwanted emissions and distribution.
Last updated