Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Soloseng/celo-minting-schedule #10995

Merged
merged 68 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
09f05bf
fix validator test
soloseng Apr 25, 2024
8e785af
update L2 check to include pragma 0.5
soloseng Apr 25, 2024
347e34c
update LockedGold for L2
soloseng Apr 26, 2024
265a7bd
upadated parsing and yarn
soloseng Apr 30, 2024
e637fb3
++ only L2 modifier
soloseng Apr 30, 2024
1cf8ee3
using IGoldToken interface that includes mint function
soloseng Apr 30, 2024
6473dfe
GoldToken is L2 Safe
soloseng May 1, 2024
3890dcc
working first draft of minting schedule
soloseng May 8, 2024
1575f7b
revert lockedGold changes
soloseng May 10, 2024
db67e34
fixed time discrepency
soloseng May 10, 2024
91bb198
removed variable in `mintAccordingToSchedule`
soloseng May 10, 2024
29d2c58
Using target gold total supply to calculate minting schedule.
soloseng May 13, 2024
afd78f1
using IERC20 instead of IGoldToken
soloseng May 14, 2024
3518dd0
lint
soloseng May 14, 2024
7b0b719
force trigger test on current branch
soloseng May 14, 2024
72b1f6e
fix test
soloseng May 14, 2024
6935a35
cleanup
soloseng May 14, 2024
30c21ff
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng May 14, 2024
747dc8d
mint to multiple receiver based on L2 schedule
soloseng May 16, 2024
595bdde
cleanup
soloseng May 16, 2024
d0ce225
updated interface
soloseng May 16, 2024
a76304c
Only initialize on L2
soloseng May 16, 2024
208dbfd
removed foundry version restriction
soloseng May 16, 2024
0d37a33
removed branch from push condition
soloseng May 16, 2024
6a7502f
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng May 17, 2024
06cde2c
removed extra ownership transfer step
soloseng May 17, 2024
3ad97e0
++ ts migrations
soloseng May 17, 2024
4842f17
++ proxy contract
soloseng May 17, 2024
b9d06cf
++ NATSPEC
soloseng May 17, 2024
dec5039
-- shadowed var
soloseng May 17, 2024
6133ecc
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng May 17, 2024
0113bff
manually set l2starttime
soloseng May 17, 2024
169a4b1
reorder migrations
soloseng May 17, 2024
c3a6da5
++ initialization data
soloseng May 17, 2024
d4dfec1
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng May 17, 2024
cb032cd
fix test error msg
soloseng May 17, 2024
9b0cb89
++ MintGoldSchedule to anvil migration
soloseng May 17, 2024
16ffff7
fixed requirements
soloseng May 17, 2024
8526784
++ setDependencies function
soloseng May 20, 2024
1a0f148
moved migrations to 0.8
soloseng May 21, 2024
8d585a8
adapted constant contract to include pragma 0.8
soloseng May 21, 2024
40fd24f
addapted goldToken tests to support mintGoldSchedule in 0.8
soloseng May 21, 2024
ae8a355
upgraded mintGoldSchedule to using pragma 0.8
soloseng May 21, 2024
2376367
using `assertApproxEqRel`
soloseng May 21, 2024
85ad919
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng May 21, 2024
0916c5e
bump lower end version
soloseng May 21, 2024
e9c5b19
exclude ReentrancyGuard from compatibility check
soloseng May 22, 2024
72da66a
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng May 22, 2024
3d19b55
GoldToken PR feedback
soloseng May 23, 2024
e5ac1f4
PR feedback on mintschedule
soloseng May 23, 2024
4f9e5c4
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng May 24, 2024
54d87a3
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng May 28, 2024
719d261
PR feedback
soloseng May 28, 2024
16f0f28
PR feedback
soloseng May 29, 2024
ea43941
typo fix
soloseng May 29, 2024
c88cd4f
update test epochSize to match master
soloseng May 29, 2024
43ebb95
MintGoldSchedule PR feedback
soloseng May 29, 2024
2a7da9d
moved IGoldToken to contracts-0.8, to only build using pragma ^0.8.
soloseng May 29, 2024
2c29f86
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng May 31, 2024
d931ead
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng Jun 3, 2024
0ac3330
PR feedback. mint all that is avail
soloseng Jun 4, 2024
96e1098
PR feedback
soloseng Jun 4, 2024
044c620
gas saving
soloseng Jun 4, 2024
465bbd0
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng Jun 5, 2024
4b41095
updated fle name
soloseng Jun 5, 2024
c141c4a
added to abi publishing list
soloseng Jun 5, 2024
289e596
adding interface to integration test
soloseng Jun 5, 2024
7cf3ec6
Merge branch 'master' into soloseng/celo-minting-schedule
soloseng Jun 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/protocol/contractPackages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +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'],
contracts: ['GasPriceMinimum', 'FeeCurrencyDirectory', 'MintGoldSchedule'],
proxyContracts: [
'GasPriceMinimumProxy',
'FeeCurrencyDirectoryProxy',
'MentoFeeCurrencyAdapterV1',
'MintGoldScheduleProxy',
],
truffleConfig: 'truffle-config0.8.js',
} satisfies ContractPackage
330 changes: 330 additions & 0 deletions packages/protocol/contracts-0.8/common/MintGoldSchedule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.7 <0.8.20;

import "@openzeppelin/contracts8/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts8/utils/math/Math.sol";

import "./UsingRegistry.sol";
import "../common/IsL2Check.sol";

import "../../contracts/common/FixidityLib.sol";
import "../../contracts/common/Initializable.sol";
import "../../contracts-0.8/common/interfaces/IGoldToken.sol";

/**
* @title Contract for minting new CELO token based on a schedule.
*/
contract MintGoldSchedule 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 YEARS_LINEAR = 15;
uint256 constant SECONDS_LINEAR = YEARS_LINEAR * 365 * 1 days;

bool public areDependenciesSet;
uint256 constant GENESIS_START_TIME = 1587587214; // Copied over from `EpochRewards().startTime()`.
uint256 public l2StartTime;
uint256 public totalSupplyAtL2Start;

uint256 public totalMintedBySchedule;
address public communityRewardFund;
address public carbonOffsettingPartner;

FixidityLib.Fraction private communityRewardFraction;
FixidityLib.Fraction private carbonOffsettingFraction;

event CommunityRewardFractionSet(uint256 fraction);
event CarbonOffsettingFundSet(address indexed partner, uint256 fraction);

modifier whenActivated() {
require(areDependenciesSet, "Minting schedule has not been activated.");
_;
}

/**
* @notice Sets initialized == true on implementation contracts
* @param test Set to true to skip implementation initialization
*/
constructor(bool test) public Initializable(test) {}

/**
* @notice A constructor for initialising a new instance of a MintGoldSchedule contract.
*/
function initialize() external initializer {
_transferOwnership(msg.sender);
}

/**
* @notice Sets the minting 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.
* @param _carbonOffsettingFraction The percentage of rewards going to carbon offsetting partner.
* @param registryAddress Address of the deployed contracts registry.
*/
function activate(
uint256 _l2StartTime,
uint256 _communityRewardFraction,
address _carbonOffsettingPartner,
uint256 _carbonOffsettingFraction,
address registryAddress
) external onlyOwner onlyL2 {
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.");
areDependenciesSet = true;
l2StartTime = _l2StartTime;
setRegistry(registryAddress);
soloseng marked this conversation as resolved.
Show resolved Hide resolved
communityRewardFund = address(getGovernance());
soloseng marked this conversation as resolved.
Show resolved Hide resolved
totalSupplyAtL2Start = getGoldToken().totalSupply();
setCommunityRewardFraction(_communityRewardFraction);
soloseng marked this conversation as resolved.
Show resolved Hide resolved
setCarbonOffsettingFund(_carbonOffsettingPartner, _carbonOffsettingFraction);
soloseng marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @notice Mints CELO to the community and carbon offsetting funds according to the predefined schedule.
*/
function mintAccordingToSchedule() external nonReentrant onlyL2 returns (bool) {
(
uint256 targetGoldTotalSupply,
uint256 communityRewardFundMintAmount,
uint256 carbonOffsettingPartnerMintAmount
) = getTargetGoldTotalSupply();

uint256 mintableAmount = Math.min(
getRemainingBalanceToMint(),
targetGoldTotalSupply - getGoldToken().totalSupply()
);

require(mintableAmount > 0, "Mintable amount must be greater than zero");
totalMintedBySchedule += mintableAmount;

IGoldToken goldToken = IGoldToken(address(getGoldToken()));
require(
goldToken.mint(communityRewardFund, communityRewardFundMintAmount),
"Failed to mint to community partner."
);

require(
goldToken.mint(carbonOffsettingPartner, carbonOffsettingPartnerMintAmount),
"Failed to mint to carbon offsetting partner."
);
return true;
}

/**
* @notice Returns the community reward fraction.
* @return The percentage of total reward which goes to the community funds.
*/
function getCommunityRewardFraction() external view returns (uint256) {
return communityRewardFraction.unwrap();
}

/**
* @notice Returns the carbon offsetting partner reward fraction.
* @return The percentage of total reward which goes to the carbon offsetting partner.
*/
function getCarbonOffsettingFraction() external view returns (uint256) {
return carbonOffsettingFraction.unwrap();
}

/**
* @notice Returns the storage, major, minor, and patch version of the contract.
* @return Storage version of the contract.
* @return Major version of the contract.
* @return Minor version of the contract.
* @return Patch version of the contract.
*/
function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) {
return (1, 1, 0, 0);
}

/**
* @notice Sets the community reward percentage
* @param value The percentage of the total reward to be sent to the community funds as Fixidity fraction.
* @return True upon success.
*/
function setCommunityRewardFraction(uint256 value) public onlyOwner whenActivated returns (bool) {
uint256 timeSinceL2Start = block.timestamp - l2StartTime;
soloseng marked this conversation as resolved.
Show resolved Hide resolved
uint256 totalL2LinearSecondsAvailable = SECONDS_LINEAR - (l2StartTime - GENESIS_START_TIME);
FixidityLib.Fraction memory wrappedValue = FixidityLib.wrap(value);
require(
timeSinceL2Start < totalL2LinearSecondsAvailable,
"Can only update fraction once block reward calculation for years 15-30 has been implemented."
);
require(
!wrappedValue.equals(communityRewardFraction) && wrappedValue.lt(FixidityLib.fixed1()),
"Value must be different from existing community reward fraction and less than 1."
);
communityRewardFraction = wrappedValue;
require(
FixidityLib.fixed1().gte(communityRewardFraction.add(carbonOffsettingFraction)),
"Sum of partner fractions must be less than or equal to 1."
);
emit CommunityRewardFractionSet(value);
return true;
}

/**
* @notice Sets the carbon offsetting fund.
* @param partner The address of the carbon offsetting partner.
* @param value The percentage of the total reward to be sent to the carbon offsetting partner as Fixidity fraction.
* @return True upon success.
*/
function setCarbonOffsettingFund(
address partner,
uint256 value
) public onlyOwner whenActivated returns (bool) {
require(partner != address(0), "Partner cannot be the zero address.");
uint256 timeSinceL2Start = block.timestamp - l2StartTime;
uint256 totalL2LinearSecondsAvailable = SECONDS_LINEAR - (l2StartTime - GENESIS_START_TIME);
require(
timeSinceL2Start < totalL2LinearSecondsAvailable,
"Can only update fraction once block reward calculation for years 15-30 has been implemented."
);
FixidityLib.Fraction memory wrappedValue = FixidityLib.wrap(value);
require(
partner != carbonOffsettingPartner || !wrappedValue.equals(carbonOffsettingFraction),
"Partner and value must be different from existing carbon offsetting fund."
);
require(wrappedValue.lt(FixidityLib.fixed1()), "Value must be less than 1.");
carbonOffsettingPartner = partner;
carbonOffsettingFraction = wrappedValue;
require(
FixidityLib.fixed1().gte(communityRewardFraction.add(carbonOffsettingFraction)),
"Sum of partner fractions must be less than or equal to 1."
);
emit CarbonOffsettingFundSet(partner, value);
return true;
}

/**
* @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.
*/
function getTotalMintedBySchedule() public view returns (uint256) {
return totalMintedBySchedule;
}

/**
* @return The currently mintable amount.
*/
function getMintableAmount() public view returns (uint256) {
(uint256 targetGoldTotalSupply, , ) = getTargetGoldTotalSupply();
return targetGoldTotalSupply - 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.
*/
function getTargetGoldTotalSupply()
soloseng marked this conversation as resolved.
Show resolved Hide resolved
public
view
whenActivated
returns (
uint256 targetGoldTotalSupply,
uint256 communityTargetRewards,
uint256 carbonFundTargetRewards
)
{
require(block.timestamp > GENESIS_START_TIME, "GENESIS_START_TIME has not yet been reached.");
require(block.timestamp > l2StartTime, "l2StartTime has not yet been reached.");

uint256 timeSinceL2Start = block.timestamp - l2StartTime;
uint256 totalL2LinearSecondsAvailable = SECONDS_LINEAR - (l2StartTime - GENESIS_START_TIME);
uint256 mintedOnL1 = totalSupplyAtL2Start - GENESIS_GOLD_SUPPLY;

bool isLinearDistribution = timeSinceL2Start < totalL2LinearSecondsAvailable;
if (isLinearDistribution) {
(
targetGoldTotalSupply,
communityTargetRewards,
carbonFundTargetRewards
) = _calculateTargetReward(timeSinceL2Start, totalL2LinearSecondsAvailable, mintedOnL1);

return (targetGoldTotalSupply, communityTargetRewards, carbonFundTargetRewards);
} else {
(
targetGoldTotalSupply,
communityTargetRewards,
carbonFundTargetRewards
) = _calculateTargetReward(
totalL2LinearSecondsAvailable - 1,
totalL2LinearSecondsAvailable,
mintedOnL1
);

bool hasNotYetMintedAllLinearRewards = totalMintedBySchedule +
GENESIS_GOLD_SUPPLY +
mintedOnL1 <
targetGoldTotalSupply;

if (hasNotYetMintedAllLinearRewards) {
return (targetGoldTotalSupply, communityTargetRewards, carbonFundTargetRewards);
}
revert("Block reward calculation for years 15-30 unimplemented");
return (0, 0, 0);
}
}

function _calculateTargetReward(
uint256 elapsedTime,
uint256 _totalL2LinearSecondsAvailable,
uint256 _mintedOnL1
)
internal
view
returns (
uint256 targetGoldTotalSupply,
uint256 communityTargetRewards,
uint256 carbonFundTargetRewards
)
{
FixidityLib.Fraction memory elapsedTimeFraction = FixidityLib.wrap(elapsedTime);
FixidityLib.Fraction memory totalL2LinearSecondsAvailableFraction = FixidityLib.wrap(
_totalL2LinearSecondsAvailable
);
// Pay out half of all block rewards linearly.
uint256 totalLinearRewards = (GOLD_SUPPLY_CAP - GENESIS_GOLD_SUPPLY) / 2; //(200 million) includes validator rewards.

FixidityLib.Fraction memory l2LinearRewards = FixidityLib.newFixed(
totalLinearRewards - _mintedOnL1
);

FixidityLib.Fraction memory linearRewardsToCommunity = l2LinearRewards.multiply(
communityRewardFraction
);

FixidityLib.Fraction memory linearRewardsToCarbon = l2LinearRewards.multiply(
carbonOffsettingFraction
);

communityTargetRewards = (
linearRewardsToCommunity.multiply(elapsedTimeFraction).divide(
totalL2LinearSecondsAvailableFraction
)
).fromFixed();

carbonFundTargetRewards = linearRewardsToCarbon
.multiply(elapsedTimeFraction)
.divide(totalL2LinearSecondsAvailableFraction)
.fromFixed();

targetGoldTotalSupply =
communityTargetRewards +
carbonFundTargetRewards +
GENESIS_GOLD_SUPPLY +
_mintedOnL1;
}
}
4 changes: 4 additions & 0 deletions packages/protocol/contracts-0.8/common/UsingRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "@openzeppelin/contracts8/token/ERC20/IERC20.sol";
import "../../contracts/common/interfaces/IRegistry.sol";
import "../../contracts/common/interfaces/IAccounts.sol";
import "../../contracts/common/interfaces/IFreezer.sol";
import "../../contracts/governance/interfaces/IGovernance.sol";
import "../../contracts/governance/interfaces/ILockedGold.sol";
import "../../contracts/governance/interfaces/IValidators.sol";
import "../../contracts/stability/interfaces/ISortedOracles.sol";
Expand Down Expand Up @@ -108,4 +109,7 @@ contract UsingRegistry is Ownable {
function getElection() internal view returns (IElection) {
return IElection(registry.getAddressForOrDie(ELECTION_REGISTRY_ID));
}
function getGovernance() internal view returns (IGovernance) {
return IGovernance(registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.5.13 <0.9.0;

import "@openzeppelin/contracts8/token/ERC20/IERC20.sol";

/**
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include
* the optional functions; to access them see {ERC20Detailed}.
*/
interface IGoldToken is IERC20 {
/**
* @notice Used in place of the constructor to allow the contract to be upgradable via proxy.
* @param registryAddress Address of the Registry contract.
*/
function initialize(address registryAddress) external;

/**
* @notice Updates the address pointing to a Registry contract.
* @param registryAddress The address of a registry contract for routing to other contracts.
*/
function setRegistry(address registryAddress) external;

/**
* @notice Used set the address of the MintGoldSchedule contract.
* @param goldTokenMintingScheduleAddress The address of the MintGoldSchedule contract.
*/
function setGoldTokenMintingScheduleAddress(address goldTokenMintingScheduleAddress) external;

/**
* @dev Mints a new token.
* @param to The address that will own the minted token.
* @param value The amount of token to be minted.
*/
function mint(address to, uint256 value) external returns (bool);
}
Loading
Loading