Executing Monetary Policy
Once the Trustee vote is computed and the winning proposal is determined, the new monetary policy is enacted immediately upon the start of the next generation (which coincides with the end of the reveal window). Random Inflation and Interest Lockups both create respective contracts to handle the process. Linear Supply Change is enacted instantly, directly to the token contract.
Monetary Policy Execution - Walkthrough
When the generation increments, the CurrencyTimer.sol
contract and the ECO.sol
is notified of the generation increment from the TimedPolicies.sol
contract. The TimedPolicies.sol
contract calls the notifyGenerationIncrease()
in both contracts, which executes the relevant monetary policy functions.
The ECO.sol
contract is notified first. Below are comments in the code explaining what each step does.
To recap, the contract updates the generation, executes the compute()
function if it hasn't been executed to see which Trustee proposal won, then enacts linear inflation.
The CurrencyTimer.sol
contract is notified next. Below are comments in the code explaining what each step does.
To recap, the contract updates the generation, executes the compute()
function if it hasn't been executed to see which Trustee proposal won, clones a new CurrencyGovernance
contract, then creates Random Inflation and/or Lockup Contract clones if required.
Monetary Policy - Cloned Contracts
Linear Inflation is executed immediately in the core policy contracts, but Random Inflation and Lockup Contracts are managed by cloned contracts. This section explains how those contracts work.
Random Inflation Contracts
When Random Inflation is enacted, a RandomInflation.sol
, VDFVerifier.sol
, and InflationRootHashProposal.sol
contract are cloned. The first of which works as a hub for managing the initiation and payout of Random Inflation, and the other two are helpers for the VDF and gamified Merkle Tree process, respectively.
The VDF allows us to determine a fair blind seed for determining who receives inflation. A prime number is submitted by anyone willing to help facilitate the process to RandomInflation.sol
. It must be within the 1000 numbers after the block hash (as uint256
) of the block before it is submitted. That serves as the starting point for our VDF. Then, anyone may perform a Pietrzak modular exponentiation puzzle which is verified on chain in the VDFVerifier.sol
contract. The final value from this is then read by RandomInflation.sol
to act as the seed for Random Inflation.
Parallel to this process the InflationRootHashProposal.sol
contract gamifies the process of submitting a Merkle hash of all the past ECO voting power for every user (including delegation, snapshotted at the generation increment that enacted Random Inflation). This Merkle tree has a deterministic generation process so anyone can and should do so off-chain and check their value against any submitted value. If the submitted value doesn't match, a challenger may check addresses and, using a binary search, can find the offending difference in minimal attempts. People who propose the Merkle tree root and people who challenge both submit respective amounts of ECO as collateral. If the proposer is correct and defends every challenge, they get their collateral back as well as the ECO from every challenger. If the challengers correctly show that the Merkle hash is false, they split the total value based on how many challenges they submitted.
Once the Merkle hash is accepted and the seed finalized from the VDF, all the recipients of inflation may be calculated. Note that the Merkle tree is deterministic, so cannot be used to rig the outcome, and the VDF takes long enough to compute that you cannot compute the outcome of Random Inflation in the 1 block window of submitting the VDF start point. This means that the results of Random Inflation can be known before the contracts are ready to start claiming, but at that point, the outcome cannot be changed.
Claiming is done via hashing together the random seed and a claim number (starting at 0 and going up to the number of recipients), taking the value of that hash cast as uint256
modulo the total sum of all ECO voting power and then using the Merkle tree as an ordering of summed balances to see where in the list of all ECO does this number land. A claimant must provide a proof of the Merkle tree position that has won as well as the leaf data and the claim number being claimed. If all this data checks out, the number is marked as claimed and the recipient address is paid out the ECO reward as determined in the monetary policy.
Random Inflation is preallocated with the amount of ECO needed to pay out the contract in its entirety, and this pool is affected by Linear Supply Changes.
Given that Random Inflation drips out over 28 days, this means the Random Inflation contract can have a surplus of ECO at the end, or become insolvent and stop random inflation claiming. Surpluses will need to be swept into the community treasury on a regular cadence.
Lockup Contracts
Lockup contracts are managed by Lockup.sol
clones, which manage deposits and interest payouts independently of the core smart contract system and must be tracked separately.
Lockup Contracts are open for 48 hours for deposits. Relevant lockup functions:
deposit(uint256 _amount)
- allows the deposit of ECO funds into the lockup if within the 48-hour window. This function requires creating an allowance for the transfer of the funds before calling.depositFor(uint256 _amount, address _benefactor)
- allows the deposit of ECO funds into the lockup for a benefactor if within the 48-hour deposit window. This function requires creating an allowance for the transfer of the funds before calling.withdraw()
- allows withdrawal of ECO funds from the contract. A penalty will be paid if this is before the lockup term is over, equal to the interest that would have been paid out.withdrawFor(address _who)
- allows withdrawal of ECO funds from the contract on someone else's behalf. This cannot trigger a penalty.
Last updated