From 1d553de9bcbef315967563348e188c905f8a055e Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Fri, 21 Jun 2024 11:00:33 +0200 Subject: [PATCH] Soloseng/update-celo-L2-distribution-logic (#11045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * modified L2 CELO distribution logic * rename functions * ++ more name changes * change more variable names * ∆ gold to celo in contract * contract rename * forgotten names --- packages/protocol/contractPackages.ts | 4 +- ...edule.sol => CeloDistributionSchedule.sol} | 123 ++-- ... ICeloDistributionScheduleInitializer.sol} | 2 +- .../common/interfaces/IGoldToken.sol | 14 +- .../protocol/contracts/common/GoldToken.sol | 32 +- ...dule.sol => ICeloDistributionSchedule.sol} | 2 +- ....sol => CeloDistributionScheduleProxy.sol} | 2 +- packages/protocol/lib/registry-utils.ts | 2 +- .../protocol/migrations_sol/Migration.s.sol | 10 +- .../protocol/migrations_sol/constants.sol | 2 +- ...dule.ts => 28_celoDistributionSchedule.ts} | 6 +- .../initializationData/release12.json | 2 +- packages/protocol/scripts/consts.ts | 4 +- .../common/CeloDistributionSchedule.t.sol | 688 ++++++++++++++++++ .../test-sol/unit/common/GoldToken.t.sol | 286 ++++---- .../unit/common/MintGoldSchedule.t.sol | 613 ---------------- 16 files changed, 930 insertions(+), 862 deletions(-) rename packages/protocol/contracts-0.8/common/{MintGoldSchedule.sol => CeloDistributionSchedule.sol} (73%) rename packages/protocol/contracts-0.8/common/interfaces/{IMintGoldScheduleInitializer.sol => ICeloDistributionScheduleInitializer.sol} (69%) rename packages/protocol/contracts/common/interfaces/{IMintGoldSchedule.sol => ICeloDistributionSchedule.sol} (94%) rename packages/protocol/contracts/common/proxies/{MintGoldScheduleProxy.sol => CeloDistributionScheduleProxy.sol} (65%) rename packages/protocol/migrations_ts/{28_mintGoldSchedule.ts => 28_celoDistributionSchedule.ts} (64%) create mode 100644 packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol delete mode 100644 packages/protocol/test-sol/unit/common/MintGoldSchedule.t.sol diff --git a/packages/protocol/contractPackages.ts b/packages/protocol/contractPackages.ts index ac7e96deb20..45a2e650558 100644 --- a/packages/protocol/contractPackages.ts +++ b/packages/protocol/contractPackages.ts @@ -48,12 +48,12 @@ export const SOLIDITY_08_PACKAGE = { proxiesPath: '/', // Proxies are still with 0.5 contracts // Proxies shouldn't have to be added to a list manually // https://github.com/celo-org/celo-monorepo/issues/10555 - contracts: ['GasPriceMinimum', 'FeeCurrencyDirectory', 'MintGoldSchedule'], + contracts: ['GasPriceMinimum', 'FeeCurrencyDirectory', 'CeloDistributionSchedule'], proxyContracts: [ 'GasPriceMinimumProxy', 'FeeCurrencyDirectoryProxy', 'MentoFeeCurrencyAdapterV1', - 'MintGoldScheduleProxy', + 'CeloDistributionScheduleProxy', ], truffleConfig: 'truffle-config0.8.js', } satisfies ContractPackage diff --git a/packages/protocol/contracts-0.8/common/MintGoldSchedule.sol b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol similarity index 73% rename from packages/protocol/contracts-0.8/common/MintGoldSchedule.sol rename to packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol index e423cf40b1d..58b76f2750f 100644 --- a/packages/protocol/contracts-0.8/common/MintGoldSchedule.sol +++ b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol @@ -12,13 +12,13 @@ import "../../contracts/common/Initializable.sol"; import "../../contracts-0.8/common/interfaces/IGoldToken.sol"; /** - * @title Contract for minting new CELO token based on a schedule. + * @title Contract for distributing CELO token based on a schedule. */ -contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2Check { +contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2Check { using FixidityLib for FixidityLib.Fraction; - uint256 constant GENESIS_GOLD_SUPPLY = 600000000 ether; // 600 million Gold - uint256 constant GOLD_SUPPLY_CAP = 1000000000 ether; // 1 billion Gold + uint256 constant GENESIS_CELO_SUPPLY = 600000000 ether; // 600 million Celo + uint256 constant CELO_SUPPLY_CAP = 1000000000 ether; // 1 billion Celo uint256 constant YEARS_LINEAR = 15; uint256 constant SECONDS_LINEAR = YEARS_LINEAR * 365 * 1 days; @@ -27,7 +27,7 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 uint256 public l2StartTime; uint256 public totalSupplyAtL2Start; - uint256 public totalMintedBySchedule; + uint256 public totalDistributedBySchedule; address public communityRewardFund; address public carbonOffsettingPartner; @@ -38,7 +38,7 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 event CarbonOffsettingFundSet(address indexed partner, uint256 fraction); modifier whenActivated() { - require(areDependenciesSet, "Minting schedule has not been activated."); + require(areDependenciesSet, "Distribution schedule has not been activated."); _; } @@ -49,14 +49,14 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 constructor(bool test) public Initializable(test) {} /** - * @notice A constructor for initialising a new instance of a MintGoldSchedule contract. + * @notice A constructor for initialising a new instance of a CeloDistributionSchedule contract. */ function initialize() external initializer { _transferOwnership(msg.sender); } /** - * @notice Sets the minting schedule dependencies during L2 transition. + * @notice Sets the distribution schedule dependencies during L2 transition. * @param _l2StartTime The timestamp of L1 to L2 transition. * @param _communityRewardFraction The percentage of rewards that go the community funds. * @param _carbonOffsettingPartner The address of the carbon offsetting partner. @@ -70,6 +70,7 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 uint256 _carbonOffsettingFraction, address registryAddress ) external onlyOwner onlyL2 { + require(address(this).balance > 0, "Contract does not have CELO balance."); require(!areDependenciesSet, "Contract has already been activated."); require(registryAddress != address(0), "The registry address cannot be the zero address"); require(block.timestamp > _l2StartTime, "L2 start time cannot be set to a future date."); @@ -83,32 +84,38 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 } /** - * @notice Mints CELO to the community and carbon offsetting funds according to the predefined schedule. + * @notice Distributes CELO to the community and carbon offsetting funds according to the predefined schedule. */ - function mintAccordingToSchedule() external nonReentrant onlyL2 returns (bool) { + function distributeAccordingToSchedule() external nonReentrant onlyL2 returns (bool) { ( - uint256 targetGoldTotalSupply, - uint256 communityRewardFundMintAmount, - uint256 carbonOffsettingPartnerMintAmount - ) = getTargetGoldTotalSupply(); - - uint256 mintableAmount = Math.min( - getRemainingBalanceToMint(), - targetGoldTotalSupply - getGoldToken().totalSupply() + uint256 targetCeloTotalSupply, + uint256 communityRewardFundDistributionAmount, + uint256 carbonOffsettingPartnerDistributionAmount + ) = getTargetCeloTotalSupply(); + + uint256 distributableAmount = Math.min( + getRemainingBalanceToDistribute(), + targetCeloTotalSupply - getGoldToken().totalSupply() ); - require(mintableAmount > 0, "Mintable amount must be greater than zero"); - totalMintedBySchedule += mintableAmount; + require(distributableAmount > 0, "Distributable amount must be greater than zero."); + require(address(this).balance > distributableAmount, "Contract balance is insufficient."); - IGoldToken goldToken = IGoldToken(address(getGoldToken())); + totalDistributedBySchedule += distributableAmount; + + IGoldToken celoToken = IGoldToken(address(getGoldToken())); + + celoToken.increaseSupply( + communityRewardFundDistributionAmount + carbonOffsettingPartnerDistributionAmount + ); require( - goldToken.mint(communityRewardFund, communityRewardFundMintAmount), - "Failed to mint to community partner." + celoToken.transfer(communityRewardFund, communityRewardFundDistributionAmount), + "Failed to transfer to community partner." ); require( - goldToken.mint(carbonOffsettingPartner, carbonOffsettingPartnerMintAmount), - "Failed to mint to carbon offsetting partner." + celoToken.transfer(carbonOffsettingPartner, carbonOffsettingPartnerDistributionAmount), + "Failed to transfer to carbon offsetting partner." ); return true; } @@ -129,6 +136,13 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 return carbonOffsettingFraction.unwrap(); } + /** + * @return The total balance distributed by the CeloDistributionSchedule contract. + */ + function getTotalDistributedBySchedule() external view returns (uint256) { + return totalDistributedBySchedule; + } + /** * @notice Returns the storage, major, minor, and patch version of the contract. * @return Storage version of the contract. @@ -200,40 +214,33 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 } /** - * @notice Calculates remaining CELO balance to mint. - * @return The remaining CELO balance to mint. - */ - function getRemainingBalanceToMint() public view returns (uint256) { - return GOLD_SUPPLY_CAP - getGoldToken().totalSupply(); - } - - /** - * @return The total balance minted by the MintGoldSchedule contract. + * @notice Calculates remaining CELO balance to distribute. + * @return The remaining CELO balance to distribute. */ - function getTotalMintedBySchedule() public view returns (uint256) { - return totalMintedBySchedule; + function getRemainingBalanceToDistribute() public view returns (uint256) { + return CELO_SUPPLY_CAP - getGoldToken().totalSupply(); } /** - * @return The currently mintable amount. + * @return The currently distributable amount. */ - function getMintableAmount() public view returns (uint256) { - (uint256 targetGoldTotalSupply, , ) = getTargetGoldTotalSupply(); - return targetGoldTotalSupply - getGoldToken().totalSupply(); + function getDistributableAmount() public view returns (uint256) { + (uint256 targetCeloTotalSupply, , ) = getTargetCeloTotalSupply(); + return targetCeloTotalSupply - getGoldToken().totalSupply(); } /** * @notice Returns the target CELO supply according to the target schedule. - * @return targetGoldTotalSupply The target total CELO supply according to the target schedule. - * @return communityTargetRewards The community reward that can be minted according to the target schedule. - * @return carbonFundTargetRewards The carbon offsetting reward that can be minted according to the target schedule. + * @return targetCeloTotalSupply The target total CELO supply according to the target schedule. + * @return communityTargetRewards The community reward that can be distributed according to the target schedule. + * @return carbonFundTargetRewards The carbon offsetting reward that can be distributed according to the target schedule. */ - function getTargetGoldTotalSupply() + function getTargetCeloTotalSupply() public view whenActivated returns ( - uint256 targetGoldTotalSupply, + uint256 targetCeloTotalSupply, uint256 communityTargetRewards, uint256 carbonFundTargetRewards ) @@ -243,20 +250,20 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 uint256 timeSinceL2Start = block.timestamp - l2StartTime; uint256 totalL2LinearSecondsAvailable = SECONDS_LINEAR - (l2StartTime - GENESIS_START_TIME); - uint256 mintedOnL1 = totalSupplyAtL2Start - GENESIS_GOLD_SUPPLY; + uint256 mintedOnL1 = totalSupplyAtL2Start - GENESIS_CELO_SUPPLY; bool isLinearDistribution = timeSinceL2Start < totalL2LinearSecondsAvailable; if (isLinearDistribution) { ( - targetGoldTotalSupply, + targetCeloTotalSupply, communityTargetRewards, carbonFundTargetRewards ) = _calculateTargetReward(timeSinceL2Start, totalL2LinearSecondsAvailable, mintedOnL1); - return (targetGoldTotalSupply, communityTargetRewards, carbonFundTargetRewards); + return (targetCeloTotalSupply, communityTargetRewards, carbonFundTargetRewards); } else { ( - targetGoldTotalSupply, + targetCeloTotalSupply, communityTargetRewards, carbonFundTargetRewards ) = _calculateTargetReward( @@ -265,13 +272,13 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 mintedOnL1 ); - bool hasNotYetMintedAllLinearRewards = totalMintedBySchedule + - GENESIS_GOLD_SUPPLY + + bool hasNotYetDistributedAllLinearRewards = totalDistributedBySchedule + + GENESIS_CELO_SUPPLY + mintedOnL1 < - targetGoldTotalSupply; + targetCeloTotalSupply; - if (hasNotYetMintedAllLinearRewards) { - return (targetGoldTotalSupply, communityTargetRewards, carbonFundTargetRewards); + if (hasNotYetDistributedAllLinearRewards) { + return (targetCeloTotalSupply, communityTargetRewards, carbonFundTargetRewards); } revert("Block reward calculation for years 15-30 unimplemented"); return (0, 0, 0); @@ -286,7 +293,7 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 internal view returns ( - uint256 targetGoldTotalSupply, + uint256 targetCeloTotalSupply, uint256 communityTargetRewards, uint256 carbonFundTargetRewards ) @@ -296,7 +303,7 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 _totalL2LinearSecondsAvailable ); // Pay out half of all block rewards linearly. - uint256 totalLinearRewards = (GOLD_SUPPLY_CAP - GENESIS_GOLD_SUPPLY) / 2; //(200 million) includes validator rewards. + uint256 totalLinearRewards = (CELO_SUPPLY_CAP - GENESIS_CELO_SUPPLY) / 2; //(200 million) includes validator rewards. FixidityLib.Fraction memory l2LinearRewards = FixidityLib.newFixed( totalLinearRewards - _mintedOnL1 @@ -321,10 +328,10 @@ contract MintGoldSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2 .divide(totalL2LinearSecondsAvailableFraction) .fromFixed(); - targetGoldTotalSupply = + targetCeloTotalSupply = communityTargetRewards + carbonFundTargetRewards + - GENESIS_GOLD_SUPPLY + + GENESIS_CELO_SUPPLY + _mintedOnL1; } } diff --git a/packages/protocol/contracts-0.8/common/interfaces/IMintGoldScheduleInitializer.sol b/packages/protocol/contracts-0.8/common/interfaces/ICeloDistributionScheduleInitializer.sol similarity index 69% rename from packages/protocol/contracts-0.8/common/interfaces/IMintGoldScheduleInitializer.sol rename to packages/protocol/contracts-0.8/common/interfaces/ICeloDistributionScheduleInitializer.sol index 37d71ed70c0..1877c73b17d 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IMintGoldScheduleInitializer.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/ICeloDistributionScheduleInitializer.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.5.13 <0.9.0; -interface IMintGoldScheduleInitializer { +interface ICeloDistributionScheduleInitializer { function initialize() external; } diff --git a/packages/protocol/contracts-0.8/common/interfaces/IGoldToken.sol b/packages/protocol/contracts-0.8/common/interfaces/IGoldToken.sol index aa2f579a7f3..846667e0d7a 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IGoldToken.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/IGoldToken.sol @@ -21,10 +21,12 @@ interface IGoldToken is IERC20 { function setRegistry(address registryAddress) external; /** - * @notice Used set the address of the MintGoldSchedule contract. - * @param goldTokenMintingScheduleAddress The address of the MintGoldSchedule contract. + * @notice Used set the address of the CeloDistributionSchedule contract. + * @param celoTokenDistributionScheduleAddress The address of the CeloDistributionSchedule contract. */ - function setGoldTokenMintingScheduleAddress(address goldTokenMintingScheduleAddress) external; + function setCeloTokenDistributionScheduleAddress( + address celoTokenDistributionScheduleAddress + ) external; /** * @dev Mints a new token. @@ -32,4 +34,10 @@ interface IGoldToken is IERC20 { * @param value The amount of token to be minted. */ function mint(address to, uint256 value) external returns (bool); + + /** + * @notice Increases the variable for total amount of CELO in existence. + * @param amount The amount to increase counter by. + */ + function increaseSupply(uint256 amount) external; } diff --git a/packages/protocol/contracts/common/GoldToken.sol b/packages/protocol/contracts/common/GoldToken.sol index f17862f439c..3bcbaf36cc6 100644 --- a/packages/protocol/contracts/common/GoldToken.sol +++ b/packages/protocol/contracts/common/GoldToken.sol @@ -9,7 +9,7 @@ import "./CalledByVm.sol"; import "./Initializable.sol"; import "./interfaces/ICeloToken.sol"; import "./interfaces/ICeloVersionedContract.sol"; -import "./interfaces/IMintGoldSchedule.sol"; +import "./interfaces/ICeloDistributionSchedule.sol"; import "../../contracts-0.8/common/IsL2Check.sol"; contract GoldToken is @@ -37,7 +37,7 @@ contract GoldToken is // Burn address is 0xdEaD because truffle is having buggy behaviour with the zero address address constant BURN_ADDRESS = address(0x000000000000000000000000000000000000dEaD); - IMintGoldSchedule public goldTokenMintingSchedule; + ICeloDistributionSchedule public celoTokenDistributionSchedule; event Transfer(address indexed from, address indexed to, uint256 value); @@ -45,11 +45,14 @@ contract GoldToken is event Approval(address indexed owner, address indexed spender, uint256 value); - event SetGoldTokenMintingScheduleAddress(address indexed newScheduleAddress); + event SetCeloTokenDistributionScheduleAddress(address indexed newScheduleAddress); modifier onlySchedule() { if (isL2()) { - require(msg.sender == address(goldTokenMintingSchedule), "Only MintGoldSchedule can call."); + require( + msg.sender == address(celoTokenDistributionSchedule), + "Only CeloDistributionSchedule can call." + ); } else { require(msg.sender == address(0), "Only VM can call."); } @@ -73,20 +76,20 @@ contract GoldToken is } /** - * @notice Used set the address of the MintGoldSchedule contract. - * @param goldTokenMintingScheduleAddress The address of the MintGoldSchedule contract. + * @notice Used set the address of the CeloDistributionSchedule contract. + * @param celoTokenDistributionScheduleAddress The address of the CeloDistributionSchedule contract. */ - function setGoldTokenMintingScheduleAddress( - address goldTokenMintingScheduleAddress + function setCeloTokenDistributionScheduleAddress( + address celoTokenDistributionScheduleAddress ) external onlyOwner { require( - goldTokenMintingScheduleAddress != address(0) || - goldTokenMintingScheduleAddress != address(goldTokenMintingSchedule), + celoTokenDistributionScheduleAddress != address(0) || + celoTokenDistributionScheduleAddress != address(celoTokenDistributionSchedule), "Invalid address." ); - goldTokenMintingSchedule = IMintGoldSchedule(goldTokenMintingScheduleAddress); + celoTokenDistributionSchedule = ICeloDistributionSchedule(celoTokenDistributionScheduleAddress); - emit SetGoldTokenMintingScheduleAddress(goldTokenMintingScheduleAddress); + emit SetCeloTokenDistributionScheduleAddress(celoTokenDistributionScheduleAddress); } /** @@ -198,8 +201,9 @@ contract GoldToken is * @notice Mints new CELO and gives it to 'to'. * @param to The account for which to mint tokens. * @param value The amount of CELO to mint. + * @dev This function will be deprecated in L2. */ - function mint(address to, uint256 value) external onlySchedule returns (bool) { + function mint(address to, uint256 value) external onlyL1 onlyVm returns (bool) { if (value == 0) { return true; } @@ -221,7 +225,7 @@ contract GoldToken is * @dev This function will be deprecated in L2. The onlyway to increase * the supply is with the mint function. */ - function increaseSupply(uint256 amount) external onlyL1 onlyVm { + function increaseSupply(uint256 amount) external onlySchedule { totalSupply_ = totalSupply_.add(amount); } diff --git a/packages/protocol/contracts/common/interfaces/IMintGoldSchedule.sol b/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol similarity index 94% rename from packages/protocol/contracts/common/interfaces/IMintGoldSchedule.sol rename to packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol index 64f1eb2cae6..fc50e49b4d4 100644 --- a/packages/protocol/contracts/common/interfaces/IMintGoldSchedule.sol +++ b/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.5.13 <0.9.0; -interface IMintGoldSchedule { +interface ICeloDistributionSchedule { /** * @notice Mints CELO to the beneficiaries according to the predefined schedule. */ diff --git a/packages/protocol/contracts/common/proxies/MintGoldScheduleProxy.sol b/packages/protocol/contracts/common/proxies/CeloDistributionScheduleProxy.sol similarity index 65% rename from packages/protocol/contracts/common/proxies/MintGoldScheduleProxy.sol rename to packages/protocol/contracts/common/proxies/CeloDistributionScheduleProxy.sol index d9be7c6df68..5fd995d64bf 100644 --- a/packages/protocol/contracts/common/proxies/MintGoldScheduleProxy.sol +++ b/packages/protocol/contracts/common/proxies/CeloDistributionScheduleProxy.sol @@ -3,4 +3,4 @@ pragma solidity ^0.5.13; import "../Proxy.sol"; /* solhint-disable-next-line no-empty-blocks */ -contract MintGoldScheduleProxy is Proxy {} +contract CeloDistributionScheduleProxy is Proxy {} diff --git a/packages/protocol/lib/registry-utils.ts b/packages/protocol/lib/registry-utils.ts index 9b58e47cecd..2e2b49d7269 100644 --- a/packages/protocol/lib/registry-utils.ts +++ b/packages/protocol/lib/registry-utils.ts @@ -35,7 +35,7 @@ export enum CeloContractName { GovernanceApproverMultiSig = 'GovernanceApproverMultiSig', GrandaMento = 'GrandaMento', LockedGold = 'LockedGold', - MintGoldSchedule = 'MintGoldSchedule', + CeloDistributionSchedule = 'CeloDistributionSchedule', OdisPayments = 'OdisPayments', Random = 'Random', Reserve = 'Reserve', diff --git a/packages/protocol/migrations_sol/Migration.s.sol b/packages/protocol/migrations_sol/Migration.s.sol index d7c4ab31209..d22b4a7bcf3 100644 --- a/packages/protocol/migrations_sol/Migration.s.sol +++ b/packages/protocol/migrations_sol/Migration.s.sol @@ -41,7 +41,7 @@ import "@celo-contracts/identity/interfaces/IOdisPaymentsInitializer.sol"; import "@celo-contracts/identity/interfaces/IFederatedAttestationsInitializer.sol"; import "@celo-contracts/stability/interfaces/ISortedOracles.sol"; import "@celo-contracts-8/common/interfaces/IGasPriceMinimumInitializer.sol"; -import "@celo-contracts-8/common/interfaces/IMintGoldScheduleInitializer.sol"; +import "@celo-contracts-8/common/interfaces/ICeloDistributionScheduleInitializer.sol"; import "@migrations-sol/HelperInterFaces.sol"; import "@openzeppelin/contracts8/utils/math/Math.sol"; @@ -235,7 +235,7 @@ contract Migration is Script, UsingRegistry, Constants { migrateUniswapFeeHandlerSeller(); migrateFeeHandler(json); migrateOdisPayments(); - migrateMintGoldSchedule(); + migrateCeloDistributionSchedule(); migrateGovernance(json); vm.stopBroadcast(); @@ -907,10 +907,10 @@ contract Migration is Script, UsingRegistry, Constants { ); } - function migrateMintGoldSchedule() public { + function migrateCeloDistributionSchedule() public { deployProxiedContract( - "MintGoldSchedule", - abi.encodeWithSelector(IMintGoldScheduleInitializer.initialize.selector) + "CeloDistributionSchedule", + abi.encodeWithSelector(ICeloDistributionScheduleInitializer.initialize.selector) ); } diff --git a/packages/protocol/migrations_sol/constants.sol b/packages/protocol/migrations_sol/constants.sol index dcdec2c2d3a..c79790f3295 100644 --- a/packages/protocol/migrations_sol/constants.sol +++ b/packages/protocol/migrations_sol/constants.sol @@ -19,7 +19,7 @@ contract Constants { "Governance", "GovernanceSlasher", "LockedGold", - "MintGoldSchedule", + "CeloDistributionSchedule", "OdisPayments", "Random", "Registry", diff --git a/packages/protocol/migrations_ts/28_mintGoldSchedule.ts b/packages/protocol/migrations_ts/28_celoDistributionSchedule.ts similarity index 64% rename from packages/protocol/migrations_ts/28_mintGoldSchedule.ts rename to packages/protocol/migrations_ts/28_celoDistributionSchedule.ts index 8dd1f3820dc..95850ef59c6 100644 --- a/packages/protocol/migrations_ts/28_mintGoldSchedule.ts +++ b/packages/protocol/migrations_ts/28_celoDistributionSchedule.ts @@ -1,16 +1,16 @@ import { CeloContractName } from '@celo/protocol/lib/registry-utils' import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils' -import { MintGoldScheduleInstance } from 'types/08' +import { CeloDistributionScheduleInstance } from 'types/08' import { SOLIDITY_08_PACKAGE } from '../contractPackages' const initializeArgs = async () => { return [] } -module.exports = deploymentForCoreContract( +module.exports = deploymentForCoreContract( web3, artifacts, - CeloContractName.MintGoldSchedule, + CeloContractName.CeloDistributionSchedule, initializeArgs, undefined, SOLIDITY_08_PACKAGE diff --git a/packages/protocol/releaseData/initializationData/release12.json b/packages/protocol/releaseData/initializationData/release12.json index fada832b995..73775cc013f 100644 --- a/packages/protocol/releaseData/initializationData/release12.json +++ b/packages/protocol/releaseData/initializationData/release12.json @@ -1,4 +1,4 @@ { "FeeCurrencyDirectory": [], - "MintGoldSchedule": [] + "CeloDistributionSchedule": [] } diff --git a/packages/protocol/scripts/consts.ts b/packages/protocol/scripts/consts.ts index 2aed53633ad..35a779a556b 100644 --- a/packages/protocol/scripts/consts.ts +++ b/packages/protocol/scripts/consts.ts @@ -35,7 +35,7 @@ export const ProxyContracts = [ 'RegistryProxy', 'SortedOraclesProxy', 'UniswapFeeHandlerSellerProxy', - 'MintGoldScheduleProxy', + 'CeloDistributionScheduleProxy', ] export const CoreContracts = [ @@ -51,7 +51,7 @@ export const CoreContracts = [ 'MultiSig', 'Registry', 'Freezer', - 'MintGoldSchedule', + 'CeloDistributionSchedule', // governance 'Election', diff --git a/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol b/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol new file mode 100644 index 00000000000..3813322d094 --- /dev/null +++ b/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol @@ -0,0 +1,688 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; +pragma experimental ABIEncoderV2; + +import "celo-foundry-8/Test.sol"; +import "@celo-contracts/common/FixidityLib.sol"; +import "@celo-contracts/common/interfaces/IRegistry.sol"; +import "@celo-contracts-8/common/interfaces/IGoldToken.sol"; +import "@celo-contracts/governance/interfaces/IGovernance.sol"; +import "@celo-contracts-8/common/CeloDistributionSchedule.sol"; +import "@celo-contracts-8/common/IsL2Check.sol"; +import { Constants } from "@test-sol/constants.sol"; + +import "@test-sol/unit/governance/mock/MockGovernance.sol"; + +contract CeloDistributionScheduleTest is Test, Constants, IsL2Check { + using FixidityLib for FixidityLib.Fraction; + + IRegistry registry; + IGoldToken celoToken; + MockGovernance governance; + + CeloDistributionSchedule mintCeloSchedule; + + address owner = address(this); + + address registryAddress; + address celoTokenAddress = actor("celoTokenAddress"); + + address mintCeloOwner = actor("mintCeloOwner"); + address communityRewardFund = actor("communityRewardFund"); + address carbonOffsettingPartner = actor("carbonOffsettingPartner"); + + address newPartner = actor("newPartner"); + address randomAddress = actor("randomAddress"); + + address constant l1RegistryAddress = 0x000000000000000000000000000000000000ce10; + + uint256 constant DAILY_DISTRIBUTION_AMOUNT = 6748256563599655349558; // 6,748 Celo + uint256 constant L1_MINTED_CELO_SUPPLY = 692702432463315819704447326; // as of May 15 2024 + + uint256 constant CELO_SUPPLY_CAP = 1000000000 ether; // 1 billion Celo + uint256 constant GENESIS_CELO_SUPPLY = 600000000 ether; // 600 million Celo + + uint256 constant FIFTEEN_YEAR_LINEAR_REWARD = (CELO_SUPPLY_CAP - GENESIS_CELO_SUPPLY) / 2; // 200 million Celo + + uint256 constant FIFTEEN_YEAR_CELO_SUPPLY = GENESIS_CELO_SUPPLY + FIFTEEN_YEAR_LINEAR_REWARD; // 800 million Celo (includes GENESIS_CELO_SUPPLY) + + uint256 constant MAX_L2_DISTRIBUTION = FIFTEEN_YEAR_CELO_SUPPLY - L1_MINTED_CELO_SUPPLY; // 107.2 million Celo + + uint256 constant L2_INITIAL_STASH_BALANCE = FIFTEEN_YEAR_LINEAR_REWARD + MAX_L2_DISTRIBUTION; // leftover from L1 target supply plus the 2nd 15 year term. + + uint256 constant MAX_L2_COMMUNITY_DISTRIBUTION = MAX_L2_DISTRIBUTION / 4; // 26.8 million Celo + uint256 constant MAX_L2_CARBON_FUND_DISTRIBUTION = MAX_L2_DISTRIBUTION / 1000; // 107,297 Celo + + uint256 constant L2_FIFTEEN_YEAR_CELO_SUPPLY = + L1_MINTED_CELO_SUPPLY + MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION; + + uint256 constant l2StartTime = 1715808537; // Arbitary later date (May 15 2024) + uint256 constant communityRewardFraction = FIXED1 / 4; // 25% + uint256 constant carbonOffsettingFraction = FIXED1 / 1000; // 0.1% + uint256 constant newCommunityRewardFraction = FIXED1 / 2; // 50% + uint256 constant newCarbonOffsettingFraction = FIXED1 / 500; // 0.2% + + event CommunityRewardFractionSet(uint256 fraction); + event CarbonOffsettingFundSet(address indexed partner, uint256 fraction); + + function setUp() public virtual { + setUpL1(); + + // Setup L2 after minting L1 supply. + registryAddress = proxyAdminAddress; + deployCodeTo("Registry.sol", abi.encode(false), registryAddress); + registry = IRegistry(registryAddress); + + registry.setAddressFor("GoldToken", address(celoToken)); + registry.setAddressFor("Governance", address(governance)); + + celoToken.setRegistry(registryAddress); + } + + function setUpL1() public { + registryAddress = l1RegistryAddress; + + deployCodeTo("Registry.sol", abi.encode(false), registryAddress); + registry = IRegistry(registryAddress); + + deployCodeTo("GoldToken.sol", abi.encode(false), celoTokenAddress); + celoToken = IGoldToken(celoTokenAddress); + + // Using a mock contract, as foundry does not allow for library linking when using deployCodeTo + governance = new MockGovernance(); + + registry.setAddressFor("GoldToken", address(celoToken)); + + registry.setAddressFor("Governance", address(governance)); + + vm.deal(address(0), CELO_SUPPLY_CAP); + assertEq(celoToken.totalSupply(), 0, "starting total supply not zero."); + // Mint L1 supply + vm.prank(address(0)); + celoToken.mint(randomAddress, L1_MINTED_CELO_SUPPLY); + assertEq(celoToken.totalSupply(), L1_MINTED_CELO_SUPPLY, "total supply incorrect."); + } + + function newMintCelo() internal returns (CeloDistributionSchedule) { + vm.warp(block.timestamp + l2StartTime); + vm.prank(mintCeloOwner); + mintCeloSchedule = new CeloDistributionSchedule(true); + + vm.deal(address(mintCeloSchedule), L2_INITIAL_STASH_BALANCE); + + celoToken.setCeloTokenDistributionScheduleAddress(address(mintCeloSchedule)); + + vm.prank(mintCeloOwner); + mintCeloSchedule.initialize(); + + vm.prank(mintCeloOwner); + + mintCeloSchedule.activate( + l2StartTime, + communityRewardFraction, + carbonOffsettingPartner, + carbonOffsettingFraction, + registryAddress + ); + } +} + +contract CeloDistributionScheduleTest_initialize is CeloDistributionScheduleTest { + function setUp() public override { + super.setUp(); + vm.warp(block.timestamp + l2StartTime); + + vm.prank(mintCeloOwner); + mintCeloSchedule = new CeloDistributionSchedule(true); + + celoToken.setCeloTokenDistributionScheduleAddress(address(mintCeloSchedule)); + + vm.prank(mintCeloOwner); + mintCeloSchedule.initialize(); + } + + function test_ShouldSetAOwnerToMintCeloScheduleInstance() public { + assertEq(mintCeloSchedule.owner(), mintCeloOwner); + } + + function test_ShouldNotSetBeneficiariesToMintCeloScheduleInstance() public { + assertEq(mintCeloSchedule.communityRewardFund(), address(0)); + assertEq(mintCeloSchedule.carbonOffsettingPartner(), address(0)); + } + + function test_ShouldHaveZeroTotalDistributedByScheduleOnInit() public { + assertEq(mintCeloSchedule.totalDistributedBySchedule(), 0); + } + + function test_ShouldNotSetTheL2StartTime() public { + assertEq(mintCeloSchedule.l2StartTime(), 0); + } +} + +contract CeloDistributionScheduleTest_activate_L1 is CeloDistributionScheduleTest { + function setUp() public override { + super.setUpL1(); + + mintCeloSchedule = new CeloDistributionSchedule(true); + mintCeloSchedule.initialize(); + } + + function test_Reverts_WhenCalledOnL1() public { + vm.warp(block.timestamp + l2StartTime); + vm.expectRevert("This method is not supported in L1."); + mintCeloSchedule.activate( + l2StartTime, + communityRewardFraction, + carbonOffsettingPartner, + carbonOffsettingFraction, + registryAddress + ); + } +} + +contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { + function test_ShouldHaveZeroTotalDistributedByScheduleOnInit() public { + newMintCelo(); + assertEq(mintCeloSchedule.totalDistributedBySchedule(), 0); + } + function test_ShouldUpdateDependencies() public { + newMintCelo(); + assertEq(mintCeloSchedule.l2StartTime(), l2StartTime); + assertEq(mintCeloSchedule.totalSupplyAtL2Start(), L1_MINTED_CELO_SUPPLY); + assertEq(mintCeloSchedule.communityRewardFund(), address(governance)); + assertEq(mintCeloSchedule.carbonOffsettingPartner(), carbonOffsettingPartner); + assertEq(mintCeloSchedule.getCarbonOffsettingFraction(), carbonOffsettingFraction); + assertEq(mintCeloSchedule.getCommunityRewardFraction(), communityRewardFraction); + } + + function test_Reverts_WhenRegistryIsTheNullAddress() public { + vm.warp(block.timestamp + l2StartTime); + mintCeloSchedule = new CeloDistributionSchedule(true); + mintCeloSchedule.initialize(); + vm.deal(address(mintCeloSchedule), L2_INITIAL_STASH_BALANCE); + vm.expectRevert("The registry address cannot be the zero address"); + mintCeloSchedule.activate( + l2StartTime, + communityRewardFraction, + carbonOffsettingPartner, + carbonOffsettingFraction, + address(0) + ); + } + + function test_Reverts_WhenCommunityFractionIsZero() public { + vm.warp(block.timestamp + l2StartTime); + mintCeloSchedule = new CeloDistributionSchedule(true); + mintCeloSchedule.initialize(); + vm.deal(address(mintCeloSchedule), L2_INITIAL_STASH_BALANCE); + vm.expectRevert( + "Value must be different from existing community reward fraction and less than 1." + ); + mintCeloSchedule.activate( + l2StartTime, + 0, + carbonOffsettingPartner, + carbonOffsettingFraction, + registryAddress + ); + } + + function test_Reverts_WhenCarbonOffsettingPartnerIsNullAddress() public { + vm.warp(block.timestamp + l2StartTime); + mintCeloSchedule = new CeloDistributionSchedule(true); + mintCeloSchedule.initialize(); + vm.deal(address(mintCeloSchedule), L2_INITIAL_STASH_BALANCE); + + vm.expectRevert("Partner cannot be the zero address."); + mintCeloSchedule.activate( + l2StartTime, + communityRewardFraction, + address(0), + carbonOffsettingFraction, + registryAddress + ); + } + + function test_Reverts_WhenRegistryNotUpdated() public { + vm.warp(block.timestamp + l2StartTime); + registry.setAddressFor("Governance", address(0)); + mintCeloSchedule = new CeloDistributionSchedule(true); + vm.deal(address(mintCeloSchedule), L2_INITIAL_STASH_BALANCE); + mintCeloSchedule.initialize(); + + vm.expectRevert("identifier has no registry entry"); + mintCeloSchedule.activate( + l2StartTime, + communityRewardFraction, + carbonOffsettingPartner, + carbonOffsettingFraction, + registryAddress + ); + } + + function test_Reverts_WhenCalledTwice() public { + newMintCelo(); + vm.expectRevert("Contract has already been activated."); + + vm.prank(mintCeloOwner); + + mintCeloSchedule.activate( + l2StartTime, + communityRewardFraction, + carbonOffsettingPartner, + carbonOffsettingFraction, + registryAddress + ); + } + + function test_Reverts_WhenTheContractDoesNotHaveBalance() public { + vm.warp(block.timestamp + l2StartTime); + vm.prank(mintCeloOwner); + mintCeloSchedule = new CeloDistributionSchedule(true); + + vm.deal(address(mintCeloSchedule), L2_INITIAL_STASH_BALANCE); + + celoToken.setCeloTokenDistributionScheduleAddress(address(mintCeloSchedule)); + + vm.prank(mintCeloOwner); + mintCeloSchedule.initialize(); + + vm.deal(address(mintCeloSchedule), 0); + + vm.expectRevert("Contract does not have CELO balance."); + vm.prank(mintCeloOwner); + mintCeloSchedule.activate( + l2StartTime, + communityRewardFraction, + carbonOffsettingPartner, + carbonOffsettingFraction, + registryAddress + ); + } +} + +contract CeloDistributionScheduleTest_setCommunityRewardFraction is CeloDistributionScheduleTest { + function setUp() public override { + super.setUp(); + newMintCelo(); + } + function test_ShouldSetNewFraction() public { + vm.prank(mintCeloOwner); + mintCeloSchedule.setCommunityRewardFraction(newCommunityRewardFraction); + assertEq(mintCeloSchedule.getCommunityRewardFraction(), newCommunityRewardFraction); + } + function test_Emits_CommunityRewardFractionSetEvent() public { + vm.expectEmit(true, true, true, true); + emit CommunityRewardFractionSet(newCommunityRewardFraction); + vm.prank(mintCeloOwner); + mintCeloSchedule.setCommunityRewardFraction(newCommunityRewardFraction); + } + function test_Reverts_WhenCalledByOtherThanOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(randomAddress); + mintCeloSchedule.setCommunityRewardFraction(newCommunityRewardFraction); + } + function test_Reverts_WhenFractionIsTheSame() public { + vm.expectRevert( + "Value must be different from existing community reward fraction and less than 1." + ); + vm.prank(mintCeloOwner); + mintCeloSchedule.setCommunityRewardFraction(communityRewardFraction); + } + function test_Reverts_WhenSumOfFractionsGtOne() public { + vm.expectRevert("Sum of partner fractions must be less than or equal to 1."); + vm.prank(mintCeloOwner); + mintCeloSchedule.setCommunityRewardFraction((FIXED1 - 1)); + } + function test_Reverts_WhenDependenciesNotSet() public { + mintCeloSchedule = new CeloDistributionSchedule(true); + + celoToken.setCeloTokenDistributionScheduleAddress(address(mintCeloSchedule)); + + vm.prank(mintCeloOwner); + mintCeloSchedule.initialize(); + + vm.expectRevert("Distribution schedule has not been activated."); + vm.prank(mintCeloOwner); + mintCeloSchedule.setCommunityRewardFraction(communityRewardFraction); + } + function test_Reverts_WhenFractionChangesAfter15Years() public { + vm.warp(block.timestamp + (15 * YEAR + 4 * DAY)); + + assertEq(mintCeloSchedule.totalDistributedBySchedule(), 0, "Incorrect mintableAmount"); + + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + + vm.warp(block.timestamp + (15 * YEAR) + (4 * DAY)); + + vm.expectRevert( + "Can only update fraction once block reward calculation for years 15-30 has been implemented." + ); + + vm.prank(mintCeloOwner); + mintCeloSchedule.setCommunityRewardFraction(((FIXED1 / 4) * 3)); + } +} + +contract CeloDistributionScheduleTest_setCarbonOffsettingFund is CeloDistributionScheduleTest { + function setUp() public override { + super.setUp(); + newMintCelo(); + } + + function test_ShouldSetNewPartner() public { + vm.prank(mintCeloOwner); + mintCeloSchedule.setCarbonOffsettingFund(newPartner, carbonOffsettingFraction); + assertEq(mintCeloSchedule.carbonOffsettingPartner(), newPartner); + } + function test_ShouldSetNewFraction() public { + vm.prank(mintCeloOwner); + mintCeloSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, newCarbonOffsettingFraction); + assertEq(mintCeloSchedule.getCarbonOffsettingFraction(), newCarbonOffsettingFraction); + } + + function test_Emits_CarbonOffsettingFundSetEvent() public { + vm.expectEmit(true, true, true, true); + emit CarbonOffsettingFundSet(newPartner, carbonOffsettingFraction); + vm.prank(mintCeloOwner); + mintCeloSchedule.setCarbonOffsettingFund(newPartner, carbonOffsettingFraction); + } + + function test_Reverts_WhenCalledByOtherThanOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(randomAddress); + mintCeloSchedule.setCarbonOffsettingFund(newPartner, carbonOffsettingFraction); + } + + function test_Reverts_WhenPartnerAndFractionAreTheSame() public { + vm.expectRevert("Partner and value must be different from existing carbon offsetting fund."); + vm.prank(mintCeloOwner); + mintCeloSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, carbonOffsettingFraction); + } + + function test_Reverts_WhenSumOfFractionsGtOne() public { + vm.expectRevert("Sum of partner fractions must be less than or equal to 1."); + vm.prank(mintCeloOwner); + mintCeloSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, (FIXED1 - 1)); + } + + function test_Reverts_WhenDependenciesNotSet() public { + mintCeloSchedule = new CeloDistributionSchedule(true); + + celoToken.setCeloTokenDistributionScheduleAddress(address(mintCeloSchedule)); + + vm.prank(mintCeloOwner); + mintCeloSchedule.initialize(); + + vm.expectRevert("Distribution schedule has not been activated."); + vm.prank(mintCeloOwner); + mintCeloSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, carbonOffsettingFraction); + } + + function test_Reverts_WhenFractionChangesAfter15Years() public { + vm.warp(block.timestamp + (15 * YEAR + 4 * DAY)); + + assertEq(mintCeloSchedule.totalDistributedBySchedule(), 0, "Incorrect mintableAmount"); + + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + + vm.warp(block.timestamp + (15 * YEAR) + (4 * DAY)); + + vm.expectRevert( + "Can only update fraction once block reward calculation for years 15-30 has been implemented." + ); + + vm.prank(mintCeloOwner); + mintCeloSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, ((FIXED1 / 4) * 3)); + } +} + +contract CeloDistributionScheduleTest_distributeAccordingToSchedule_L1 is + CeloDistributionScheduleTest +{ + uint256 initialMintCeloAmount; + + function setUp() public override { + super.setUpL1(); + + mintCeloSchedule = new CeloDistributionSchedule(true); + mintCeloSchedule.initialize(); + } + + function test_Reverts_WhenMintingOnL1() public { + vm.warp(block.timestamp + 3 * MONTH + 1 * DAY); + + vm.expectRevert("This method is not supported in L1."); + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + } +} + +contract CeloDistributionScheduleTest_distributeAccordingToSchedule is + CeloDistributionScheduleTest +{ + uint256 initialMintCeloAmount; + uint256 mintPerPeriod; + + function setUp() public override { + super.setUp(); + + newMintCelo(); + } + + function test_Reverts_WhenDependenciesAreNotSet() public { + mintCeloSchedule = new CeloDistributionSchedule(true); + + vm.prank(mintCeloOwner); + mintCeloSchedule.initialize(); + + vm.expectRevert("Distribution schedule has not been activated."); + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + } + + function test_ShouldAllowMintingAsSoon1SecondAfterSettingDependencies() public { + uint256 communityFundBalanceBefore = celoToken.balanceOf(address(governance)); + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + uint256 communityFundBalanceAfter = celoToken.balanceOf(address(governance)); + assertGt(communityFundBalanceAfter, communityFundBalanceBefore); + } + + function test_Reverts_WhenMintableAmountIsZero() public { + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + + vm.expectRevert("Distributable amount must be greater than zero."); + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + } + + function test_ShouldAllowToMint25Percent2years9MonthsPostL2Launch() public { + vm.warp(block.timestamp + 2 * YEAR + 267 * DAY + 63868); // 25% time since L2 + + uint256 expectedDistributedAmount = (L2_FIFTEEN_YEAR_CELO_SUPPLY - L1_MINTED_CELO_SUPPLY) / 4; + + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + + assertApproxEqRel( + mintCeloSchedule.totalDistributedBySchedule(), + expectedDistributedAmount, + 1e10 + ); + } + + function test_ShouldAllowToMint50Percent5AndHalfYearsPostL2Launch() public { + vm.warp(block.timestamp + (5 * YEAR) + (170 * DAY) + 41338); + + uint256 expectedDistributedAmount = (L2_FIFTEEN_YEAR_CELO_SUPPLY - L1_MINTED_CELO_SUPPLY) / 2; + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + + assertApproxEqRel( + mintCeloSchedule.totalDistributedBySchedule(), + expectedDistributedAmount, + 1e10 + ); + } + + function test_ShouldAllowToMint75Percent11YearsAnd3MonthsPostL2Launch() public { + vm.warp(block.timestamp + 8 * YEAR + 73 * DAY + 18807); + + uint256 expectedDistributedAmount = ((L2_FIFTEEN_YEAR_CELO_SUPPLY - L1_MINTED_CELO_SUPPLY) / + 4) * 3; + + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + + assertApproxEqRel( + mintCeloSchedule.totalDistributedBySchedule(), + expectedDistributedAmount, + 1e10 + ); + } + + function test_ShouldAllowToMint100Percent11YearsPostL2Launch() public { + uint256 communityFundBalanceBefore = celoToken.balanceOf(address(governance)); + uint256 carbonOffsettingPartnerBalanceBefore = celoToken.balanceOf(carbonOffsettingPartner); + vm.warp(block.timestamp + (11 * YEAR)); + + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + + assertApproxEqRel( + mintCeloSchedule.totalDistributedBySchedule(), + MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, + 1e10 + ); + + uint256 communityFundBalanceAfter = celoToken.balanceOf(address(governance)); + uint256 carbonOffsettingPartnerBalanceAfter = celoToken.balanceOf(carbonOffsettingPartner); + + assertApproxEqRel( + communityFundBalanceAfter - communityFundBalanceBefore, + MAX_L2_COMMUNITY_DISTRIBUTION, + 1e10 + ); + + assertApproxEqRel( + carbonOffsettingPartnerBalanceAfter - carbonOffsettingPartnerBalanceBefore, + MAX_L2_CARBON_FUND_DISTRIBUTION, + 1e10 + ); + } + + function test_ShouldMintUpToLinearSuppplyAfter15Years() public { + vm.warp(block.timestamp + (15 * YEAR) + (4 * DAY)); + + assertEq(mintCeloSchedule.totalDistributedBySchedule(), 0, "Incorrect mintableAmount"); + + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + + assertApproxEqRel( + mintCeloSchedule.totalDistributedBySchedule(), + MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, + 1e10 + ); + } + + function test_Reverts_WhenMintingSecondTimeAfter15Years() public { + vm.warp(block.timestamp + (15 * YEAR) + (1 * DAY)); + + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + + assertApproxEqRel( + mintCeloSchedule.totalDistributedBySchedule(), + MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, + 1e10 + ); + + vm.expectRevert("Block reward calculation for years 15-30 unimplemented"); + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + } + + function test_Reverts_WhenTheContractBalanceIsLowerExpected() public { + vm.deal(address(mintCeloSchedule), 0); + vm.prank(address(mintCeloSchedule)); + + vm.expectRevert("Contract balance is insufficient."); + mintCeloSchedule.distributeAccordingToSchedule(); + } + + function test_ShouldTransferbalanceFromThisContract() public { + uint256 initialStashBalance = celoToken.balanceOf(address(mintCeloSchedule)); + + vm.warp(block.timestamp + (15 * YEAR)); + + mintCeloSchedule.distributeAccordingToSchedule(); + + uint256 finalStashBalance = celoToken.balanceOf(address(mintCeloSchedule)); + + assertLt(finalStashBalance, initialStashBalance); + + assertApproxEqRel( + celoToken.balanceOf(address(mintCeloSchedule)), + L2_INITIAL_STASH_BALANCE - (MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION), + 1e10 + ); + } +} + +contract CeloDistributionScheduleTest_getDistributableAmount is CeloDistributionScheduleTest { + uint256 initialMintCeloAmount; + + function setUp() public override { + super.setUp(); + + newMintCelo(); + } + + function test_ShouldReturnFullAmountAvailableForThisReleasePeriod() public { + vm.warp(block.timestamp + 1 * DAY); + assertApproxEqRel(mintCeloSchedule.getDistributableAmount(), DAILY_DISTRIBUTION_AMOUNT, 1e10); + } + + function test_ShouldReturnOnlyAmountNotYetDistributed() public { + vm.warp(block.timestamp + 1 * DAY); + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + + vm.warp(block.timestamp + 1 * DAY + 1); + assertApproxEqRel(mintCeloSchedule.getDistributableAmount(), DAILY_DISTRIBUTION_AMOUNT, 1e10); + } + + function test_ShouldReturnOnlyUpToMaxL2DistributionBeforeItIsDistributed() public { + vm.warp(block.timestamp + 16 * YEAR); + assertApproxEqRel( + mintCeloSchedule.getDistributableAmount(), + MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, + 1e10 + ); + } + + function test_Reverts_When15YearsHavePassedAndAllLinearScheduleHaseBeenReleased() public { + vm.warp(block.timestamp + 15 * YEAR); + + vm.prank(randomAddress); + mintCeloSchedule.distributeAccordingToSchedule(); + vm.expectRevert("Block reward calculation for years 15-30 unimplemented"); + mintCeloSchedule.getDistributableAmount(); + } + + function test_Reverts_WhenDependenciesNotSet() public { + mintCeloSchedule = new CeloDistributionSchedule(true); + + vm.prank(mintCeloOwner); + mintCeloSchedule.initialize(); + + vm.expectRevert("Distribution schedule has not been activated."); + + mintCeloSchedule.getDistributableAmount(); + } +} diff --git a/packages/protocol/test-sol/unit/common/GoldToken.t.sol b/packages/protocol/test-sol/unit/common/GoldToken.t.sol index c422eef35c9..8aa8f819661 100644 --- a/packages/protocol/test-sol/unit/common/GoldToken.t.sol +++ b/packages/protocol/test-sol/unit/common/GoldToken.t.sol @@ -6,17 +6,17 @@ import "@celo-contracts/common/GoldToken.sol"; import "@test-sol/unit/common/GoldTokenMock.sol"; contract GoldTokenTest is Test, IsL2Check { - GoldToken goldToken; + GoldToken celoToken; - uint256 constant ONE_GOLDTOKEN = 1000000000000000000; + uint256 constant ONE_CELOTOKEN = 1000000000000000000; address receiver; address sender; - address goldTokenOwner; - address goldTokenMintingSchedule; + address celoTokenOwner; + address celoTokenDistributionSchedule; event Transfer(address indexed from, address indexed to, uint256 value); event TransferComment(string comment); - event SetGoldTokenMintingScheduleAddress(address indexed newScheduleAddress); + event SetCeloTokenDistributionScheduleAddress(address indexed newScheduleAddress); modifier _whenL2() { deployCodeTo("Registry.sol", abi.encode(false), proxyAdminAddress); @@ -24,15 +24,15 @@ contract GoldTokenTest is Test, IsL2Check { } function setUp() public { - goldTokenOwner = actor("goldTokenOwner"); - goldTokenMintingSchedule = actor("goldTokenMintingSchedule"); - vm.prank(goldTokenOwner); - goldToken = new GoldToken(true); - deployCodeTo("MintGoldSchedule.sol", abi.encode(false), goldTokenMintingSchedule); + celoTokenOwner = actor("celoTokenOwner"); + celoTokenDistributionSchedule = actor("celoTokenDistributionSchedule"); + vm.prank(celoTokenOwner); + celoToken = new GoldToken(true); + deployCodeTo("CeloDistributionSchedule.sol", abi.encode(false), celoTokenDistributionSchedule); receiver = actor("receiver"); sender = actor("sender"); - vm.deal(receiver, ONE_GOLDTOKEN); - vm.deal(sender, ONE_GOLDTOKEN); + vm.deal(receiver, ONE_CELOTOKEN); + vm.deal(sender, ONE_CELOTOKEN); } } @@ -42,47 +42,47 @@ contract GoldTokenTest_general is GoldTokenTest { } function test_name() public { - assertEq(goldToken.name(), "Celo native asset"); + assertEq(celoToken.name(), "Celo native asset"); } function test_symbol() public { - assertEq(goldToken.symbol(), "CELO"); + assertEq(celoToken.symbol(), "CELO"); } function test_decimals() public { - assertEq(uint256(goldToken.decimals()), 18); + assertEq(uint256(celoToken.decimals()), 18); } function test_balanceOf() public { - assertEq(goldToken.balanceOf(receiver), receiver.balance); + assertEq(celoToken.balanceOf(receiver), receiver.balance); } function test_approve() public { vm.prank(sender); - goldToken.approve(receiver, ONE_GOLDTOKEN); - assertEq(goldToken.allowance(sender, receiver), ONE_GOLDTOKEN); + celoToken.approve(receiver, ONE_CELOTOKEN); + assertEq(celoToken.allowance(sender, receiver), ONE_CELOTOKEN); } function test_increaseAllowance() public { vm.prank(sender); - goldToken.increaseAllowance(receiver, ONE_GOLDTOKEN); + celoToken.increaseAllowance(receiver, ONE_CELOTOKEN); vm.prank(sender); - goldToken.increaseAllowance(receiver, ONE_GOLDTOKEN); - assertEq(goldToken.allowance(sender, receiver), ONE_GOLDTOKEN * 2); + celoToken.increaseAllowance(receiver, ONE_CELOTOKEN); + assertEq(celoToken.allowance(sender, receiver), ONE_CELOTOKEN * 2); } function test_decreaseAllowance() public { vm.prank(sender); - goldToken.approve(receiver, ONE_GOLDTOKEN * 2); + celoToken.approve(receiver, ONE_CELOTOKEN * 2); vm.prank(sender); - goldToken.decreaseAllowance(receiver, ONE_GOLDTOKEN); - assertEq(goldToken.allowance(sender, receiver), ONE_GOLDTOKEN); + celoToken.decreaseAllowance(receiver, ONE_CELOTOKEN); + assertEq(celoToken.allowance(sender, receiver), ONE_CELOTOKEN); } function test_allowance() public { vm.prank(sender); - goldToken.approve(receiver, ONE_GOLDTOKEN); - assertEq(goldToken.allowance(sender, receiver), ONE_GOLDTOKEN); + celoToken.approve(receiver, ONE_CELOTOKEN); + assertEq(celoToken.allowance(sender, receiver), ONE_CELOTOKEN); } } @@ -92,32 +92,32 @@ contract GoldTokenTest_transfer is GoldTokenTest { } function test_ShouldTransferBalanceFromOneUserToAnother() public { - uint256 startBalanceFrom = goldToken.balanceOf(sender); - uint256 startBalanceTo = goldToken.balanceOf(receiver); + uint256 startBalanceFrom = celoToken.balanceOf(sender); + uint256 startBalanceTo = celoToken.balanceOf(receiver); vm.prank(sender); - goldToken.transfer(receiver, ONE_GOLDTOKEN); - assertEq(sender.balance, startBalanceFrom - ONE_GOLDTOKEN); - assertEq(receiver.balance, startBalanceTo + ONE_GOLDTOKEN); + celoToken.transfer(receiver, ONE_CELOTOKEN); + assertEq(sender.balance, startBalanceFrom - ONE_CELOTOKEN); + assertEq(receiver.balance, startBalanceTo + ONE_CELOTOKEN); } function test_ShouldTransferBalanceWithAComment() public { string memory comment = "tacos at lunch"; - uint256 startBalanceFrom = goldToken.balanceOf(sender); - uint256 startBalanceTo = goldToken.balanceOf(receiver); + uint256 startBalanceFrom = celoToken.balanceOf(sender); + uint256 startBalanceTo = celoToken.balanceOf(receiver); vm.prank(sender); vm.expectEmit(true, true, true, true); - emit Transfer(sender, receiver, ONE_GOLDTOKEN); + emit Transfer(sender, receiver, ONE_CELOTOKEN); vm.expectEmit(true, true, true, true); emit TransferComment(comment); - goldToken.transferWithComment(receiver, ONE_GOLDTOKEN, comment); - assertEq(sender.balance, startBalanceFrom - ONE_GOLDTOKEN); - assertEq(receiver.balance, startBalanceTo + ONE_GOLDTOKEN); + celoToken.transferWithComment(receiver, ONE_CELOTOKEN, comment); + assertEq(sender.balance, startBalanceFrom - ONE_CELOTOKEN); + assertEq(receiver.balance, startBalanceTo + ONE_CELOTOKEN); } function test_ShouldNotAllowToTransferToNullAddress() public { vm.prank(sender); vm.expectRevert(); - goldToken.transfer(address(0), ONE_GOLDTOKEN); + celoToken.transfer(address(0), ONE_CELOTOKEN); } } @@ -125,36 +125,36 @@ contract GoldTokenTest_transferFrom is GoldTokenTest { function setUp() public { super.setUp(); vm.prank(sender); - goldToken.approve(receiver, ONE_GOLDTOKEN); + celoToken.approve(receiver, ONE_CELOTOKEN); } function test_ShouldTransferBalanceFromOneUserToAnother() public { - uint256 startBalanceFrom = goldToken.balanceOf(sender); - uint256 startBalanceTo = goldToken.balanceOf(receiver); + uint256 startBalanceFrom = celoToken.balanceOf(sender); + uint256 startBalanceTo = celoToken.balanceOf(receiver); vm.prank(receiver); - goldToken.transferFrom(sender, receiver, ONE_GOLDTOKEN); - assertEq(sender.balance, startBalanceFrom - ONE_GOLDTOKEN); - assertEq(receiver.balance, startBalanceTo + ONE_GOLDTOKEN); + celoToken.transferFrom(sender, receiver, ONE_CELOTOKEN); + assertEq(sender.balance, startBalanceFrom - ONE_CELOTOKEN); + assertEq(receiver.balance, startBalanceTo + ONE_CELOTOKEN); } function test_ShouldNotAllowToTransferToNullAddress() public { vm.prank(receiver); vm.expectRevert(); - goldToken.transferFrom(sender, address(0), ONE_GOLDTOKEN); + celoToken.transferFrom(sender, address(0), ONE_CELOTOKEN); } function test_ShouldNotAllowTransferMoreThanSenderHas() public { - uint256 value = sender.balance + ONE_GOLDTOKEN * 4; + uint256 value = sender.balance + ONE_CELOTOKEN * 4; vm.prank(receiver); vm.expectRevert(); - goldToken.transferFrom(sender, receiver, value); + celoToken.transferFrom(sender, receiver, value); } function test_ShouldNotAllowTransferringMoreThanTheSpenderIsAllowed() public { vm.prank(receiver); vm.expectRevert(); - goldToken.transferFrom(sender, receiver, ONE_GOLDTOKEN + 1); + celoToken.transferFrom(sender, receiver, ONE_CELOTOKEN + 1); } } @@ -164,195 +164,169 @@ contract GoldTokenTest_burn is GoldTokenTest { function setUp() public { super.setUp(); - startBurn = goldToken.getBurnedAmount(); + startBurn = celoToken.getBurnedAmount(); } function test_burn_address_starts_with_zero_balance() public { - assertEq(goldToken.balanceOf(burnAddress), 0); + assertEq(celoToken.balanceOf(burnAddress), 0); } function test_burn_starts_as_start_burn_amount() public { - assertEq(goldToken.getBurnedAmount(), startBurn); + assertEq(celoToken.getBurnedAmount(), startBurn); } function test_burn_amount_eq_the_balance_of_the_burn_address() public { - assertEq(goldToken.getBurnedAmount(), goldToken.balanceOf(burnAddress)); + assertEq(celoToken.getBurnedAmount(), celoToken.balanceOf(burnAddress)); } function test_returns_right_burn_amount() public { - goldToken.burn(ONE_GOLDTOKEN); - assertEq(goldToken.getBurnedAmount(), ONE_GOLDTOKEN + startBurn); + celoToken.burn(ONE_CELOTOKEN); + assertEq(celoToken.getBurnedAmount(), ONE_CELOTOKEN + startBurn); } } contract GoldTokenTest_mint is GoldTokenTest { function test_Reverts_whenCalledByOtherThanVm() public { - vm.prank(goldTokenOwner); - vm.expectRevert("Only VM can call."); - goldToken.mint(receiver, ONE_GOLDTOKEN); + vm.prank(celoTokenOwner); + vm.expectRevert("Only VM can call"); + celoToken.mint(receiver, ONE_CELOTOKEN); - vm.prank(goldTokenMintingSchedule); - vm.expectRevert("Only VM can call."); - goldToken.mint(receiver, ONE_GOLDTOKEN); + vm.prank(celoTokenDistributionSchedule); + vm.expectRevert("Only VM can call"); + celoToken.mint(receiver, ONE_CELOTOKEN); } - function test_Should_increaseGoldTokenTotalSupplyWhencalledByVm() public { - uint256 goldTokenSupplyBefore = goldToken.totalSupply(); + function test_Should_increaseCeloTokenTotalSupplyWhencalledByVm() public { + uint256 celoTokenSupplyBefore = celoToken.totalSupply(); vm.prank(address(0)); - goldToken.mint(receiver, ONE_GOLDTOKEN); - uint256 goldTokenSupplyAfter = goldToken.totalSupply(); - assertGt(goldTokenSupplyAfter, goldTokenSupplyBefore); + celoToken.mint(receiver, ONE_CELOTOKEN); + uint256 celoTokenSupplyAfter = celoToken.totalSupply(); + assertGt(celoTokenSupplyAfter, celoTokenSupplyBefore); } function test_Emits_TransferEvent() public { vm.prank(address(0)); vm.expectEmit(true, true, true, true); - emit Transfer(address(0), receiver, ONE_GOLDTOKEN); - goldToken.mint(receiver, ONE_GOLDTOKEN); - } -} - -contract GoldTokenTest_mint_l2 is GoldTokenTest { - function setUp() public _whenL2 { - super.setUp(); - vm.prank(goldTokenOwner); - goldToken.setGoldTokenMintingScheduleAddress(goldTokenMintingSchedule); + emit Transfer(address(0), receiver, ONE_CELOTOKEN); + celoToken.mint(receiver, ONE_CELOTOKEN); } - function test_Reverts_whenCalledByOtherThanMintingSchedule() public { + function test_Reverts_whenL2() public _whenL2 { + vm.expectRevert("This method is no longer supported in L2."); + vm.prank(celoTokenDistributionSchedule); + celoToken.mint(receiver, ONE_CELOTOKEN); + vm.expectRevert("This method is no longer supported in L2."); vm.prank(address(0)); - vm.expectRevert("Only MintGoldSchedule can call."); - goldToken.mint(receiver, ONE_GOLDTOKEN); - - vm.prank(address(9)); - vm.expectRevert("Only MintGoldSchedule can call."); - goldToken.mint(receiver, ONE_GOLDTOKEN); - - vm.prank(goldTokenOwner); - vm.expectRevert("Only MintGoldSchedule can call."); - goldToken.mint(receiver, ONE_GOLDTOKEN); - } - - function test_Should_increaseGoldTokenTotalSupply() public { - uint256 goldTokenSupplyBefore = goldToken.totalSupply(); - vm.prank(goldTokenMintingSchedule); - goldToken.mint(receiver, ONE_GOLDTOKEN); - uint256 goldTokenSupplyAfter = goldToken.totalSupply(); - assertGt(goldTokenSupplyAfter, goldTokenSupplyBefore); - } - - function test_Should_increaseGoldTokenBalanceWhenMintedByGoldTokenMintingSchedule() public { - uint256 originalBalance = goldToken.balanceOf(receiver); - vm.prank(goldTokenMintingSchedule); - goldToken.mint(receiver, ONE_GOLDTOKEN); - uint256 balanceAfterMint = goldToken.balanceOf(receiver); - assertGt(balanceAfterMint, originalBalance); - } - - function test_Emits_TransferEvent() public { - vm.prank(goldTokenMintingSchedule); - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), receiver, ONE_GOLDTOKEN); - goldToken.mint(receiver, ONE_GOLDTOKEN); + celoToken.mint(receiver, ONE_CELOTOKEN); } } -contract GoldTokenTest_setGoldTokenMintingScheduleAddress is GoldTokenTest { +contract GoldTokenTest_setCeloTokenDistributionScheduleAddress is GoldTokenTest { function test_Reverts_whenCalledByOtherThanL2Governance() public _whenL2 { vm.expectRevert("Ownable: caller is not the owner"); vm.prank(address(0)); - goldToken.setGoldTokenMintingScheduleAddress(goldTokenMintingSchedule); + celoToken.setCeloTokenDistributionScheduleAddress(celoTokenDistributionSchedule); } function test_ShouldSucceedWhenCalledByOwner() public { - vm.prank(goldTokenOwner); - goldToken.setGoldTokenMintingScheduleAddress(goldTokenMintingSchedule); + vm.prank(celoTokenOwner); + celoToken.setCeloTokenDistributionScheduleAddress(celoTokenDistributionSchedule); - assertEq(address(goldToken.goldTokenMintingSchedule()), goldTokenMintingSchedule); + assertEq(address(celoToken.celoTokenDistributionSchedule()), celoTokenDistributionSchedule); } + function test_ShouldSucceedWhenCalledByL2Governance() public _whenL2 { - vm.prank(goldTokenOwner); - goldToken.setGoldTokenMintingScheduleAddress(goldTokenMintingSchedule); + vm.prank(celoTokenOwner); + celoToken.setCeloTokenDistributionScheduleAddress(celoTokenDistributionSchedule); - assertEq(address(goldToken.goldTokenMintingSchedule()), goldTokenMintingSchedule); + assertEq(address(celoToken.celoTokenDistributionSchedule()), celoTokenDistributionSchedule); } - function test_Emits_SetGoldTokenMintingScheduleAddressEvent() public { + + function test_Emits_SetCeloTokenDistributionScheduleAddressEvent() public { vm.expectEmit(true, true, true, true); - emit SetGoldTokenMintingScheduleAddress(goldTokenMintingSchedule); - vm.prank(goldTokenOwner); - goldToken.setGoldTokenMintingScheduleAddress(goldTokenMintingSchedule); + emit SetCeloTokenDistributionScheduleAddress(celoTokenDistributionSchedule); + vm.prank(celoTokenOwner); + celoToken.setCeloTokenDistributionScheduleAddress(celoTokenDistributionSchedule); } } contract GoldTokenTest_increaseSupply is GoldTokenTest { function test_ShouldIncreaseTotalSupply() public { - uint256 goldTokenSupplyBefore = goldToken.totalSupply(); + uint256 celoTokenSupplyBefore = celoToken.totalSupply(); vm.prank(address(0)); - goldToken.increaseSupply(ONE_GOLDTOKEN); - uint256 goldTokenSupplyAfter = goldToken.totalSupply(); - assertGt(goldTokenSupplyAfter, goldTokenSupplyBefore); + celoToken.increaseSupply(ONE_CELOTOKEN); + uint256 celoTokenSupplyAfter = celoToken.totalSupply(); + assertGt(celoTokenSupplyAfter, celoTokenSupplyBefore); } function test_Reverts_WhenCalledByOtherThanVm() public { - vm.prank(goldTokenOwner); - vm.expectRevert("Only VM can call"); - goldToken.increaseSupply(ONE_GOLDTOKEN); - vm.prank(goldTokenMintingSchedule); - vm.expectRevert("Only VM can call"); - goldToken.increaseSupply(ONE_GOLDTOKEN); + vm.prank(celoTokenOwner); + vm.expectRevert("Only VM can call."); + celoToken.increaseSupply(ONE_CELOTOKEN); + vm.prank(celoTokenDistributionSchedule); + vm.expectRevert("Only VM can call."); + celoToken.increaseSupply(ONE_CELOTOKEN); } } -contract GoldTokenTest_increaseSupply_l2 is GoldTokenTest { +contract GoldTokenTest_increaseSupply_L2 is GoldTokenTest { function setUp() public _whenL2 { super.setUp(); - vm.prank(goldTokenOwner); - goldToken.setGoldTokenMintingScheduleAddress(goldTokenMintingSchedule); + vm.prank(celoTokenOwner); + celoToken.setCeloTokenDistributionScheduleAddress(celoTokenDistributionSchedule); + } + + function test_ShouldIncreaseTotalSupply() public { + uint256 celoTokenSupplyBefore = celoToken.totalSupply(); + vm.prank(address(celoTokenDistributionSchedule)); + celoToken.increaseSupply(ONE_CELOTOKEN); + uint256 celoTokenSupplyAfter = celoToken.totalSupply(); + assertGt(celoTokenSupplyAfter, celoTokenSupplyBefore); } - function test_Reverts_WhenCalledByAnyone() public { + function test_Reverts_WhenCalledByOtherThanSchedule() public { vm.prank(address(0)); - vm.expectRevert("This method is no longer supported in L2."); - goldToken.increaseSupply(ONE_GOLDTOKEN); - vm.prank(goldTokenMintingSchedule); - vm.expectRevert("This method is no longer supported in L2."); - goldToken.increaseSupply(ONE_GOLDTOKEN); + vm.expectRevert("Only CeloDistributionSchedule can call."); + celoToken.increaseSupply(ONE_CELOTOKEN); + vm.prank(celoTokenOwner); + vm.expectRevert("Only CeloDistributionSchedule can call."); + celoToken.increaseSupply(ONE_CELOTOKEN); } function test_ShouldNotIncreaseTotalSupply() public { - uint256 goldTokenSupplyBefore = goldToken.totalSupply(); - vm.expectRevert("This method is no longer supported in L2."); - vm.prank(goldTokenOwner); - goldToken.increaseSupply(ONE_GOLDTOKEN); + uint256 celoTokenSupplyBefore = celoToken.totalSupply(); + vm.expectRevert("Only CeloDistributionSchedule can call."); + vm.prank(celoTokenOwner); + celoToken.increaseSupply(ONE_CELOTOKEN); - uint256 goldTokenSupplyAfter = goldToken.totalSupply(); - assertEq(goldTokenSupplyAfter, goldTokenSupplyBefore); + uint256 celoTokenSupplyAfter = celoToken.totalSupply(); + assertEq(celoTokenSupplyAfter, celoTokenSupplyBefore); } } -contract GoldTokenMockTest is Test { - GoldTokenMock mockGoldToken; - uint256 ONE_GOLDTOKEN = 1000000000000000000; +contract CeloTokenMockTest is Test { + GoldTokenMock mockCeloToken; + uint256 ONE_CELOTOKEN = 1000000000000000000; address burnAddress = address(0x000000000000000000000000000000000000dEaD); function setUp() public { - mockGoldToken = new GoldTokenMock(); - mockGoldToken.setTotalSupply(ONE_GOLDTOKEN * 1000); + mockCeloToken = new GoldTokenMock(); + mockCeloToken.setTotalSupply(ONE_CELOTOKEN * 1000); } } -contract GoldTokenMock_circulatingSupply is GoldTokenMockTest { +contract CeloTokenMock_circulatingSupply is CeloTokenMockTest { function setUp() public { super.setUp(); } function test_ShouldMatchCirculationSupply_WhenNoBurn() public { - assertEq(mockGoldToken.circulatingSupply(), mockGoldToken.totalSupply()); + assertEq(mockCeloToken.circulatingSupply(), mockCeloToken.totalSupply()); } function test_ShouldDecreaseCirculatingSupply_WhenThereWasBurn() public { - mockGoldToken.setBalanceOf(burnAddress, ONE_GOLDTOKEN); - assertEq(mockGoldToken.circulatingSupply(), ONE_GOLDTOKEN * 999); - assertEq(mockGoldToken.circulatingSupply(), mockGoldToken.totalSupply() - ONE_GOLDTOKEN); + mockCeloToken.setBalanceOf(burnAddress, ONE_CELOTOKEN); + assertEq(mockCeloToken.circulatingSupply(), ONE_CELOTOKEN * 999); + assertEq(mockCeloToken.circulatingSupply(), mockCeloToken.totalSupply() - ONE_CELOTOKEN); } } diff --git a/packages/protocol/test-sol/unit/common/MintGoldSchedule.t.sol b/packages/protocol/test-sol/unit/common/MintGoldSchedule.t.sol deleted file mode 100644 index 53fe8155d6e..00000000000 --- a/packages/protocol/test-sol/unit/common/MintGoldSchedule.t.sol +++ /dev/null @@ -1,613 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.7 <0.8.20; -pragma experimental ABIEncoderV2; - -import "celo-foundry-8/Test.sol"; -import "@celo-contracts/common/FixidityLib.sol"; -import "@celo-contracts/common/interfaces/IRegistry.sol"; -import "@celo-contracts-8/common/interfaces/IGoldToken.sol"; -import "@celo-contracts/governance/interfaces/IGovernance.sol"; -import "@celo-contracts-8/common/MintGoldSchedule.sol"; -import "@celo-contracts-8/common/IsL2Check.sol"; -import { Constants } from "@test-sol/constants.sol"; - -import "@test-sol/unit/governance/mock/MockGovernance.sol"; - -contract MintGoldScheduleTest is Test, Constants, IsL2Check { - using FixidityLib for FixidityLib.Fraction; - - IRegistry registry; - IGoldToken goldToken; - MockGovernance governance; - - MintGoldSchedule mintGoldSchedule; - - address owner = address(this); - - address registryAddress; - address goldTokenAddress = actor("goldTokenAddress"); - - address mintGoldOwner = actor("mintGoldOwner"); - address communityRewardFund = actor("communityRewardFund"); - address carbonOffsettingPartner = actor("carbonOffsettingPartner"); - - address newPartner = actor("newPartner"); - address randomAddress = actor("randomAddress"); - - address constant l1RegistryAddress = 0x000000000000000000000000000000000000ce10; - - // uint256 constant DAILY_MINT_AMOUNT_UPPER = 6749 ether; // 6,749 Gold - uint256 constant DAILY_MINT_AMOUNT_LOWER = 6748256563599655349558; // 6,748 Gold - uint256 constant L1_MINTED_GOLD_SUPPLY = 692702432463315819704447326; // as of May 15 2024 - - uint256 constant GOLD_SUPPLY_CAP = 1000000000 ether; // 1 billion Gold - uint256 constant GENESIS_GOLD_SUPPLY = 600000000 ether; // 600 million Gold - - uint256 constant FIFTEEN_YEAR_LINEAR_REWARD = (GOLD_SUPPLY_CAP - GENESIS_GOLD_SUPPLY) / 2; // 200 million Gold - uint256 constant FIFTEEN_YEAR_GOLD_SUPPLY = GENESIS_GOLD_SUPPLY + FIFTEEN_YEAR_LINEAR_REWARD; // 800 million Gold (includes GENESIS_GOLD_SUPPLY) - - uint256 constant MAX_L2_DISTRIBUTION = FIFTEEN_YEAR_GOLD_SUPPLY - L1_MINTED_GOLD_SUPPLY; // 107.2 million Gold - uint256 constant MAX_L2_COMMUNITY_DISTRIBUTION = MAX_L2_DISTRIBUTION / 4; // 26.8 million Gold - uint256 constant MAX_L2_CARBON_FUND_DISTRIBUTION = MAX_L2_DISTRIBUTION / 1000; // 107,297 Gold - - uint256 constant L2_FIFTEEN_YEAR_GOLD_SUPPLY = - L1_MINTED_GOLD_SUPPLY + MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION; - - uint256 constant l2StartTime = 1715808537; // Arbitary later date (May 15 2024) - uint256 constant communityRewardFraction = FIXED1 / 4; // 25% - uint256 constant carbonOffsettingFraction = FIXED1 / 1000; // 0.1% - uint256 constant newCommunityRewardFraction = FIXED1 / 2; // 50% - uint256 constant newCarbonOffsettingFraction = FIXED1 / 500; // 0.2% - - event CommunityRewardFractionSet(uint256 fraction); - event CarbonOffsettingFundSet(address indexed partner, uint256 fraction); - - function setUp() public virtual { - setUpL1(); - - // Setup L2 after minting L1 supply. - registryAddress = proxyAdminAddress; - deployCodeTo("Registry.sol", abi.encode(false), registryAddress); - registry = IRegistry(registryAddress); - - registry.setAddressFor("GoldToken", address(goldToken)); - registry.setAddressFor("Governance", address(governance)); - - goldToken.setRegistry(registryAddress); - } - - function setUpL1() public { - registryAddress = l1RegistryAddress; - - deployCodeTo("Registry.sol", abi.encode(false), registryAddress); - registry = IRegistry(registryAddress); - - deployCodeTo("GoldToken.sol", abi.encode(false), goldTokenAddress); - goldToken = IGoldToken(goldTokenAddress); - - // Using a mock contract, as foundry does not allow for library linking when using deployCodeTo - governance = new MockGovernance(); - - registry.setAddressFor("GoldToken", address(goldToken)); - - registry.setAddressFor("Governance", address(governance)); - - vm.deal(address(0), GOLD_SUPPLY_CAP); - assertEq(goldToken.totalSupply(), 0, "starting total supply not zero."); - // Mint L1 supply - vm.prank(address(0)); - goldToken.mint(randomAddress, L1_MINTED_GOLD_SUPPLY); - assertEq(goldToken.totalSupply(), L1_MINTED_GOLD_SUPPLY, "total supply incorrect."); - } - - function newMintGold() internal returns (MintGoldSchedule) { - vm.warp(block.timestamp + l2StartTime); - vm.prank(mintGoldOwner); - mintGoldSchedule = new MintGoldSchedule(true); - - goldToken.setGoldTokenMintingScheduleAddress(address(mintGoldSchedule)); - - vm.prank(mintGoldOwner); - mintGoldSchedule.initialize(); - - vm.prank(mintGoldOwner); - - mintGoldSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction, - registryAddress - ); - } -} - -contract MintGoldScheduleTest_initialize is MintGoldScheduleTest { - function setUp() public override { - super.setUp(); - vm.warp(block.timestamp + l2StartTime); - - vm.prank(mintGoldOwner); - mintGoldSchedule = new MintGoldSchedule(true); - goldToken.setGoldTokenMintingScheduleAddress(address(mintGoldSchedule)); - - vm.prank(mintGoldOwner); - mintGoldSchedule.initialize(); - } - - function test_ShouldSetAOwnerToMintGoldScheduleInstance() public { - assertEq(mintGoldSchedule.owner(), mintGoldOwner); - } - - function test_ShouldNotSetBeneficiariesToMintGoldScheduleInstance() public { - assertEq(mintGoldSchedule.communityRewardFund(), address(0)); - assertEq(mintGoldSchedule.carbonOffsettingPartner(), address(0)); - } - - function test_ShouldHaveZeroTotalMintedByScheduleOnInit() public { - assertEq(mintGoldSchedule.totalMintedBySchedule(), 0); - } - - function test_ShouldNotSetTheL2StartTime() public { - assertEq(mintGoldSchedule.l2StartTime(), 0); - } -} - -contract MintGoldScheduleTest_setDependencies_L1 is MintGoldScheduleTest { - function setUp() public override { - super.setUpL1(); - - mintGoldSchedule = new MintGoldSchedule(true); - mintGoldSchedule.initialize(); - } - - function test_Reverts_WhenCalledOnL1() public { - vm.warp(block.timestamp + l2StartTime); - vm.expectRevert("This method is not supported in L1."); - mintGoldSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction, - registryAddress - ); - } -} -contract MintGoldScheduleTest_setDependencies is MintGoldScheduleTest { - function test_ShouldHaveZeroTotalMintedByScheduleOnInit() public { - newMintGold(); - assertEq(mintGoldSchedule.totalMintedBySchedule(), 0); - } - function test_ShouldUpdateDependencies() public { - newMintGold(); - assertEq(mintGoldSchedule.l2StartTime(), l2StartTime); - assertEq(mintGoldSchedule.totalSupplyAtL2Start(), L1_MINTED_GOLD_SUPPLY); - assertEq(mintGoldSchedule.communityRewardFund(), address(governance)); - assertEq(mintGoldSchedule.carbonOffsettingPartner(), carbonOffsettingPartner); - assertEq(mintGoldSchedule.getCarbonOffsettingFraction(), carbonOffsettingFraction); - assertEq(mintGoldSchedule.getCommunityRewardFraction(), communityRewardFraction); - } - - function test_Reverts_WhenRegistryIsTheNullAddress() public { - vm.warp(block.timestamp + l2StartTime); - mintGoldSchedule = new MintGoldSchedule(true); - mintGoldSchedule.initialize(); - - vm.expectRevert("The registry address cannot be the zero address"); - mintGoldSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction, - address(0) - ); - } - - function test_Reverts_WhenCommunityFractionIsZero() public { - vm.warp(block.timestamp + l2StartTime); - mintGoldSchedule = new MintGoldSchedule(true); - mintGoldSchedule.initialize(); - - vm.expectRevert( - "Value must be different from existing community reward fraction and less than 1." - ); - mintGoldSchedule.activate( - l2StartTime, - 0, - carbonOffsettingPartner, - carbonOffsettingFraction, - registryAddress - ); - } - - function test_Reverts_WhenCarbonOffsettingPartnerIsNullAddress() public { - vm.warp(block.timestamp + l2StartTime); - mintGoldSchedule = new MintGoldSchedule(true); - mintGoldSchedule.initialize(); - - vm.expectRevert("Partner cannot be the zero address."); - mintGoldSchedule.activate( - l2StartTime, - communityRewardFraction, - address(0), - carbonOffsettingFraction, - registryAddress - ); - } - - function test_Reverts_WhenRegistryNotUpdated() public { - vm.warp(block.timestamp + l2StartTime); - registry.setAddressFor("Governance", address(0)); - mintGoldSchedule = new MintGoldSchedule(true); - - mintGoldSchedule.initialize(); - - vm.expectRevert("identifier has no registry entry"); - mintGoldSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction, - registryAddress - ); - } - - function test_Reverts_WhenCalledTwice() public { - newMintGold(); - vm.expectRevert("Contract has already been activated."); - - vm.prank(mintGoldOwner); - - mintGoldSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction, - registryAddress - ); - } -} - -contract MintGoldScheduleTest_setCommunityRewardFraction is MintGoldScheduleTest { - function setUp() public override { - super.setUp(); - newMintGold(); - } - function test_ShouldSetNewFraction() public { - vm.prank(mintGoldOwner); - mintGoldSchedule.setCommunityRewardFraction(newCommunityRewardFraction); - assertEq(mintGoldSchedule.getCommunityRewardFraction(), newCommunityRewardFraction); - } - function test_Emits_CommunityRewardFractionSetEvent() public { - vm.expectEmit(true, true, true, true); - emit CommunityRewardFractionSet(newCommunityRewardFraction); - vm.prank(mintGoldOwner); - mintGoldSchedule.setCommunityRewardFraction(newCommunityRewardFraction); - } - function test_Reverts_WhenCalledByOtherThanOwner() public { - vm.expectRevert("Ownable: caller is not the owner"); - vm.prank(randomAddress); - mintGoldSchedule.setCommunityRewardFraction(newCommunityRewardFraction); - } - function test_Reverts_WhenFractionIsTheSame() public { - vm.expectRevert( - "Value must be different from existing community reward fraction and less than 1." - ); - vm.prank(mintGoldOwner); - mintGoldSchedule.setCommunityRewardFraction(communityRewardFraction); - } - function test_Reverts_WhenSumOfFractionsGtOne() public { - vm.expectRevert("Sum of partner fractions must be less than or equal to 1."); - vm.prank(mintGoldOwner); - mintGoldSchedule.setCommunityRewardFraction((FIXED1 - 1)); - } - function test_Reverts_WhenDependenciesNotSet() public { - mintGoldSchedule = new MintGoldSchedule(true); - - goldToken.setGoldTokenMintingScheduleAddress(address(mintGoldSchedule)); - - vm.prank(mintGoldOwner); - mintGoldSchedule.initialize(); - - vm.expectRevert("Minting schedule has not been activated."); - vm.prank(mintGoldOwner); - mintGoldSchedule.setCommunityRewardFraction(communityRewardFraction); - } - function test_Reverts_WhenFractionChangesAfter15Years() public { - vm.warp(block.timestamp + (15 * YEAR + 4 * DAY)); - - assertEq(mintGoldSchedule.totalMintedBySchedule(), 0, "Incorrect mintableAmount"); - - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - - vm.warp(block.timestamp + (15 * YEAR) + (4 * DAY)); - - vm.expectRevert( - "Can only update fraction once block reward calculation for years 15-30 has been implemented." - ); - - vm.prank(mintGoldOwner); - mintGoldSchedule.setCommunityRewardFraction(((FIXED1 / 4) * 3)); - } -} - -contract MintGoldScheduleTest_setCarbonOffsettingFund is MintGoldScheduleTest { - function setUp() public override { - super.setUp(); - newMintGold(); - } - - function test_ShouldSetNewPartner() public { - vm.prank(mintGoldOwner); - mintGoldSchedule.setCarbonOffsettingFund(newPartner, carbonOffsettingFraction); - assertEq(mintGoldSchedule.carbonOffsettingPartner(), newPartner); - } - function test_ShouldSetNewFraction() public { - vm.prank(mintGoldOwner); - mintGoldSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, newCarbonOffsettingFraction); - assertEq(mintGoldSchedule.getCarbonOffsettingFraction(), newCarbonOffsettingFraction); - } - - function test_Emits_CarbonOffsettingFundSetEvent() public { - vm.expectEmit(true, true, true, true); - emit CarbonOffsettingFundSet(newPartner, carbonOffsettingFraction); - vm.prank(mintGoldOwner); - mintGoldSchedule.setCarbonOffsettingFund(newPartner, carbonOffsettingFraction); - } - - function test_Reverts_WhenCalledByOtherThanOwner() public { - vm.expectRevert("Ownable: caller is not the owner"); - vm.prank(randomAddress); - mintGoldSchedule.setCarbonOffsettingFund(newPartner, carbonOffsettingFraction); - } - - function test_Reverts_WhenPartnerAndFractionAreTheSame() public { - vm.expectRevert("Partner and value must be different from existing carbon offsetting fund."); - vm.prank(mintGoldOwner); - mintGoldSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, carbonOffsettingFraction); - } - - function test_Reverts_WhenSumOfFractionsGtOne() public { - vm.expectRevert("Sum of partner fractions must be less than or equal to 1."); - vm.prank(mintGoldOwner); - mintGoldSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, (FIXED1 - 1)); - } - - function test_Reverts_WhenDependenciesNotSet() public { - mintGoldSchedule = new MintGoldSchedule(true); - - goldToken.setGoldTokenMintingScheduleAddress(address(mintGoldSchedule)); - - vm.prank(mintGoldOwner); - mintGoldSchedule.initialize(); - - vm.expectRevert("Minting schedule has not been activated."); - vm.prank(mintGoldOwner); - mintGoldSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, carbonOffsettingFraction); - } - - function test_Reverts_WhenFractionChangesAfter15Years() public { - vm.warp(block.timestamp + (15 * YEAR + 4 * DAY)); - - assertEq(mintGoldSchedule.totalMintedBySchedule(), 0, "Incorrect mintableAmount"); - - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - - vm.warp(block.timestamp + (15 * YEAR) + (4 * DAY)); - - vm.expectRevert( - "Can only update fraction once block reward calculation for years 15-30 has been implemented." - ); - - vm.prank(mintGoldOwner); - mintGoldSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, ((FIXED1 / 4) * 3)); - } -} - -contract MintGoldScheduleTest_mintAccordingToSchedule_L1 is MintGoldScheduleTest { - uint256 initialMintGoldAmount; - - function setUp() public override { - super.setUpL1(); - - mintGoldSchedule = new MintGoldSchedule(true); - mintGoldSchedule.initialize(); - } - - function test_Reverts_WhenMintingOnL1() public { - vm.warp(block.timestamp + 3 * MONTH + 1 * DAY); - - vm.expectRevert("This method is not supported in L1."); - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - } -} - -contract MintGoldScheduleTest_mintAccordingToSchedule is MintGoldScheduleTest { - uint256 initialMintGoldAmount; - uint256 mintPerPeriod; - - function setUp() public override { - super.setUp(); - - newMintGold(); - } - - function test_Reverts_WhenDependenciesAreNotSet() public { - mintGoldSchedule = new MintGoldSchedule(true); - - vm.prank(mintGoldOwner); - mintGoldSchedule.initialize(); - - vm.expectRevert("Minting schedule has not been activated."); - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - } - - function test_ShouldAllowMintingAsSoon1SecondAfterSettingDependencies() public { - uint256 communityFundBalanceBefore = goldToken.balanceOf(address(governance)); - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - uint256 communityFundBalanceAfter = goldToken.balanceOf(address(governance)); - assertGt(communityFundBalanceAfter, communityFundBalanceBefore); - } - - function test_Reverts_WhenMintableAmountIsZero() public { - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - - vm.expectRevert("Mintable amount must be greater than zero"); - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - } - - function test_ShouldAllowToMint25Percent2years9MonthsPostL2Launch() public { - vm.warp(block.timestamp + 2 * YEAR + 267 * DAY + 63868); // 25% time since L2 - - uint256 expectedMintedAmount = (L2_FIFTEEN_YEAR_GOLD_SUPPLY - L1_MINTED_GOLD_SUPPLY) / 4; - - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - - assertApproxEqRel(mintGoldSchedule.totalMintedBySchedule(), expectedMintedAmount, 1e10); - } - - function test_ShouldAllowToMint50Percent5AndHalfYearsPostL2Launch() public { - vm.warp(block.timestamp + (5 * YEAR) + (170 * DAY) + 41338); - - uint256 expectedMintedAmount = (L2_FIFTEEN_YEAR_GOLD_SUPPLY - L1_MINTED_GOLD_SUPPLY) / 2; - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - - assertApproxEqRel(mintGoldSchedule.totalMintedBySchedule(), expectedMintedAmount, 1e10); - } - - function test_ShouldAllowToMint75Percent11YearsAnd3MonthsPostL2Launch() public { - vm.warp(block.timestamp + 8 * YEAR + 73 * DAY + 18807); - - uint256 expectedMintedAmount = ((L2_FIFTEEN_YEAR_GOLD_SUPPLY - L1_MINTED_GOLD_SUPPLY) / 4) * 3; - - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - - assertApproxEqRel(mintGoldSchedule.totalMintedBySchedule(), expectedMintedAmount, 1e10); - } - - function test_ShouldAllowToMint100Percent11YearsPostL2Launch() public { - uint256 communityFundBalanceBefore = goldToken.balanceOf(address(governance)); - uint256 carbonOffsettingPartnerBalanceBefore = goldToken.balanceOf(carbonOffsettingPartner); - vm.warp(block.timestamp + (11 * YEAR)); - - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - - assertApproxEqRel( - mintGoldSchedule.totalMintedBySchedule(), - MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, - 1e10 - ); - - uint256 communityFundBalanceAfter = goldToken.balanceOf(address(governance)); - uint256 carbonOffsettingPartnerBalanceAfter = goldToken.balanceOf(carbonOffsettingPartner); - - assertApproxEqRel( - communityFundBalanceAfter - communityFundBalanceBefore, - MAX_L2_COMMUNITY_DISTRIBUTION, - 1e10 - ); - - assertApproxEqRel( - carbonOffsettingPartnerBalanceAfter - carbonOffsettingPartnerBalanceBefore, - MAX_L2_CARBON_FUND_DISTRIBUTION, - 1e10 - ); - } - - function test_ShouldMintUpToLinearSuppplyAfter15Years() public { - vm.warp(block.timestamp + (15 * YEAR) + (4 * DAY)); - - assertEq(mintGoldSchedule.totalMintedBySchedule(), 0, "Incorrect mintableAmount"); - - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - - assertApproxEqRel( - mintGoldSchedule.totalMintedBySchedule(), - MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, - 1e10 - ); - } - - function test_Reverts_WhenMintingSecondTimeAfter15Years() public { - vm.warp(block.timestamp + (15 * YEAR) + (1 * DAY)); - - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - - assertApproxEqRel( - mintGoldSchedule.totalMintedBySchedule(), - MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, - 1e10 - ); - - vm.expectRevert("Block reward calculation for years 15-30 unimplemented"); - - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - } -} - -contract MintGoldScheduleTest_getMintableAmount is MintGoldScheduleTest { - uint256 initialMintGoldAmount; - - function setUp() public override { - super.setUp(); - - newMintGold(); - } - - function test_ShouldReturnFullAmountAvailableForThisReleasePeriod() public { - vm.warp(block.timestamp + 1 * DAY); - assertApproxEqRel(mintGoldSchedule.getMintableAmount(), DAILY_MINT_AMOUNT_LOWER, 1e10); - } - - function test_ShouldReturnOnlyAmountNotYetMinted() public { - vm.warp(block.timestamp + 1 * DAY); - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - - vm.warp(block.timestamp + 1 * DAY + 1); - assertApproxEqRel(mintGoldSchedule.getMintableAmount(), DAILY_MINT_AMOUNT_LOWER, 1e10); - } - - function test_ShouldReturnOnlyUpToMaxL2DistributionBeforeItIsMinted() public { - vm.warp(block.timestamp + 16 * YEAR); - assertApproxEqRel( - mintGoldSchedule.getMintableAmount(), - MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, - 1e10 - ); - } - - function test_Reverts_When15YearsHavePassedAndAllLinearScheduleHaseBeenReleased() public { - vm.warp(block.timestamp + 15 * YEAR); - - vm.prank(randomAddress); - mintGoldSchedule.mintAccordingToSchedule(); - vm.expectRevert("Block reward calculation for years 15-30 unimplemented"); - mintGoldSchedule.getMintableAmount(); - } - - function test_Reverts_WhenDependenciesNotSet() public { - mintGoldSchedule = new MintGoldSchedule(true); - - vm.prank(mintGoldOwner); - mintGoldSchedule.initialize(); - - vm.expectRevert("Minting schedule has not been activated."); - - mintGoldSchedule.getMintableAmount(); - } -}