Skip to content

Commit

Permalink
change staking emission from 1 to 2 percent
Browse files Browse the repository at this point in the history
feat: add ecosystemFund
feat: mint to ecosystem fund
upd: emissionRate to 1.03
upd: emissionManager version -> 1.1.0
wip: update emission manager tests
fix tests
docs
fix distribution
adjust m1ntCap
move natspec to interfaces
  • Loading branch information
DhairyaSethi authored and gretzke committed Oct 18, 2023
1 parent 1f2d13f commit acffed0
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 103 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/coverage
lcov.info
.DS_Store
.vscode
broadcast/
.env

Expand Down
31 changes: 8 additions & 23 deletions src/DefaultEmissionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/Safe
import {PowUtil} from "./lib/PowUtil.sol";

/// @title Default Emission Manager
/// @author Polygon Labs (@DhairyaSethi, @gretzke, @qedk)
/// @author Polygon Labs (@DhairyaSethi, @gretzke, @qedk, @simonDos)
/// @notice A default emission manager implementation for the Polygon ERC20 token contract on Ethereum L1
/// @dev The contract allows for a 1% mint *each* per year (compounded every year) to the stakeManager and treasury contracts
/// @dev The contract allows for a 3% mint per year (compounded). 2% staking layer and 1% treasury
/// @custom:security-contact [email protected]
contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionManager {
using SafeERC20 for IPolygonEcosystemToken;

// log2(2%pa continuously compounded emission per year) in 18 decimals, see _inflatedSupplyAfter
uint256 public constant INTEREST_PER_YEAR_LOG2 = 0.028569152196770894e18;
uint256 public constant INTEREST_PER_YEAR_LOG2 = 0.04264433740849372e18;
uint256 public constant START_SUPPLY = 10_000_000_000e18;
address private immutable DEPLOYER;

Expand Down Expand Up @@ -54,9 +53,7 @@ contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionMana
_transferOwnership(owner_);
}

/// @notice Allows anyone to mint tokens to the stakeManager and treasury contracts based on current emission rates
/// @dev Minting is done based on totalSupply diffs between the currentTotalSupply (maintained on POL, which includes any
/// previous mints) and the newSupply (calculated based on the time elapsed since deployment)
/// @inheritdoc IDefaultEmissionManager
function mint() external {
uint256 currentSupply = token.totalSupply(); // totalSupply after the last mint
uint256 newSupply = inflatedSupplyAfter(
Expand All @@ -65,7 +62,7 @@ contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionMana
uint256 amountToMint = newSupply - currentSupply;
if (amountToMint == 0) return; // no minting required

uint256 treasuryAmt = amountToMint / 2;
uint256 treasuryAmt = amountToMint / 3;
uint256 stakeManagerAmt = amountToMint - treasuryAmt;

emit TokenMint(amountToMint, msg.sender);
Expand All @@ -77,28 +74,16 @@ contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionMana
migration.unmigrateTo(stakeManager, stakeManagerAmt);
}

/// @notice Returns total supply from compounded emission after timeElapsed from startTimestamp (deployment)
/// @param timeElapsed The time elapsed since startTimestamp
/// @dev interestRatePerYear = 1.02; 2% per year
/// approximate the compounded interest rate using x^y = 2^(log2(x)*y)
/// where x is the interest rate per year and y is the number of seconds elapsed since deployment divided by 365 days in seconds
/// log2(interestRatePerYear) = 0.028569152196770894 with 18 decimals, as the interest rate does not change, hard code the value
/// @return supply total supply from compounded emission after timeElapsed
/// @inheritdoc IDefaultEmissionManager
function inflatedSupplyAfter(uint256 timeElapsed) public pure returns (uint256 supply) {
uint256 supplyFactor = PowUtil.exp2((INTEREST_PER_YEAR_LOG2 * timeElapsed) / 365 days);
supply = (supplyFactor * START_SUPPLY) / 1e18;
}

/// @notice Returns the implementation version
/// @return Version string
/// @inheritdoc IDefaultEmissionManager
function getVersion() external pure returns (string memory) {
return "1.0.0";
return "1.1.0";
}

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[48] private __gap;
}
24 changes: 8 additions & 16 deletions src/PolygonEcosystemToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {AccessControlEnumerable} from "openzeppelin-contracts/contracts/access/A
import {IPolygonEcosystemToken} from "./interfaces/IPolygonEcosystemToken.sol";

/// @title Polygon ERC20 token
/// @author Polygon Labs (@DhairyaSethi, @gretzke, @qedk)
/// @author Polygon Labs (@DhairyaSethi, @gretzke, @qedk, @simonDos)
/// @notice This is the Polygon ERC20 token contract on Ethereum L1
/// @dev The contract allows for a 1-to-1 representation between $POL and $MATIC and allows for additional emission based on hub and treasury requirements
/// @custom:security-contact [email protected]
Expand All @@ -15,7 +15,7 @@ contract PolygonEcosystemToken is ERC20Permit, AccessControlEnumerable, IPolygon
bytes32 public constant CAP_MANAGER_ROLE = keccak256("CAP_MANAGER_ROLE");
bytes32 public constant PERMIT2_REVOKER_ROLE = keccak256("PERMIT2_REVOKER_ROLE");
address public constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
uint256 public mintPerSecondCap = 10e18; // 10 POL tokens per second
uint256 public mintPerSecondCap = 13.37e18;
uint256 public lastMint;
bool public permit2Enabled;

Expand All @@ -41,10 +41,7 @@ contract PolygonEcosystemToken is ERC20Permit, AccessControlEnumerable, IPolygon
_updatePermit2Allowance(true);
}

/// @notice Mint token entrypoint for the emission manager contract
/// @dev The function only validates the sender, the emission manager is responsible for correctness
/// @param to Address to mint to
/// @param amount Amount to mint
/// @inheritdoc IPolygonEcosystemToken
function mint(address to, uint256 amount) external onlyRole(EMISSION_ROLE) {
uint256 timeElapsedSinceLastMint = block.timestamp - lastMint;
uint256 maxMint = timeElapsedSinceLastMint * mintPerSecondCap;
Expand All @@ -54,31 +51,26 @@ contract PolygonEcosystemToken is ERC20Permit, AccessControlEnumerable, IPolygon
_mint(to, amount);
}

/// @notice Update the limit of tokens that can be minted per second
/// @param newCap the amount of tokens in 18 decimals as an absolute value
/// @inheritdoc IPolygonEcosystemToken
function updateMintCap(uint256 newCap) external onlyRole(CAP_MANAGER_ROLE) {
emit MintCapUpdated(mintPerSecondCap, newCap);
mintPerSecondCap = newCap;
}

/// @notice Manages the default max approval to the permit2 contract
/// @param enabled If true, the permit2 contract has full approval by default, if false, it has no approval by default
/// @inheritdoc IPolygonEcosystemToken
function updatePermit2Allowance(bool enabled) external onlyRole(PERMIT2_REVOKER_ROLE) {
_updatePermit2Allowance(enabled);
}

/// @notice The permit2 contract has full approval by default. If the approval is revoked, it can still be manually approved.
/// @dev The permit2 contract has full approval by default. If the approval is revoked, it can still be manually approved.
function allowance(address owner, address spender) public view override(ERC20, IERC20) returns (uint256) {
if (spender == PERMIT2 && permit2Enabled) return type(uint256).max;
return super.allowance(owner, spender);
}

/// @notice Returns the implementation version
/// @dev This is to support our dev pipeline, and is present despite
/// this contract not being behind a proxy
/// @return Version string
/// @inheritdoc IPolygonEcosystemToken
function getVersion() external pure returns (string memory) {
return "1.0.0";
return "1.1.0";
}

function _updatePermit2Allowance(bool enabled) private {
Expand Down
41 changes: 7 additions & 34 deletions src/PolygonMigration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,47 +43,31 @@ contract PolygonMigration is Ownable2StepUpgradeable, IPolygonMigration {
polygon = IERC20(polygon_);
}

/// @notice This function allows for migrating MATIC tokens to POL tokens
/// @dev The function does not do any validation since the migration is a one-way process
/// @param amount Amount of MATIC to migrate
/// @inheritdoc IPolygonMigration
function migrate(uint256 amount) external {
emit Migrated(msg.sender, amount);

matic.safeTransferFrom(msg.sender, address(this), amount);
polygon.safeTransfer(msg.sender, amount);
}

/// @notice This function allows for unmigrating from POL tokens to MATIC tokens
/// @dev The function can only be called when unmigration is unlocked (lock updatable by governance)
/// @dev The function does not do any further validation, also note the unmigration is a reversible process
/// @param amount Amount of POL to migrate
/// @inheritdoc IPolygonMigration
function unmigrate(uint256 amount) external onlyUnmigrationUnlocked {
emit Unmigrated(msg.sender, msg.sender, amount);

polygon.safeTransferFrom(msg.sender, address(this), amount);
matic.safeTransfer(msg.sender, amount);
}

/// @notice This function allows for unmigrating POL tokens (from msg.sender) to MATIC tokens (to account)
/// @dev The function can only be called when unmigration is unlocked (lock updatable by governance)
/// @dev The function does not do any further validation, also note the unmigration is a reversible process
/// @param recipient Address to receive MATIC tokens
/// @param amount Amount of POL to migrate
/// @inheritdoc IPolygonMigration
function unmigrateTo(address recipient, uint256 amount) external onlyUnmigrationUnlocked {
emit Unmigrated(msg.sender, recipient, amount);

polygon.safeTransferFrom(msg.sender, address(this), amount);
matic.safeTransfer(recipient, amount);
}

/// @notice This function allows for unmigrating from POL tokens to MATIC tokens using an EIP-2612 permit
/// @dev The function can only be called when unmigration is unlocked (lock updatable by governance)
/// @dev The function does not do any further validation, also note the unmigration is a reversible process
/// @param amount Amount of POL to migrate
/// @param deadline Deadline for the permit
/// @param v v value of the permit signature
/// @param r r value of the permit signature
/// @param s s value of the permit signature
/// @inheritdoc IPolygonMigration
function unmigrateWithPermit(
uint256 amount,
uint256 deadline,
Expand All @@ -98,32 +82,21 @@ contract PolygonMigration is Ownable2StepUpgradeable, IPolygonMigration {
matic.safeTransfer(msg.sender, amount);
}

/// @notice Allows governance to lock or unlock the unmigration process
/// @dev The function does not do any validation since governance can update the unmigration process if required
/// @param unmigrationLocked_ New unmigration lock status
/// @inheritdoc IPolygonMigration
function updateUnmigrationLock(bool unmigrationLocked_) external onlyOwner {
emit UnmigrationLockUpdated(unmigrationLocked_);
unmigrationLocked = unmigrationLocked_;
}

/// @notice Returns the implementation version
/// @return Version string
/// @inheritdoc IPolygonMigration
function getVersion() external pure returns (string memory) {
return "1.0.0";
}

/// @notice Allows governance to burn `amount` of POL tokens
/// @dev This functions burns POL by sending to dead address
/// @dev does not change totalSupply in the internal accounting of POL
/// @param amount Amount of POL to burn
/// @inheritdoc IPolygonMigration
function burn(uint256 amount) external onlyOwner {
polygon.safeTransfer(0x000000000000000000000000000000000000dEaD, amount);
}

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
35 changes: 31 additions & 4 deletions src/interfaces/IDefaultEmissionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,45 @@ pragma solidity 0.8.21;

import {IPolygonEcosystemToken} from "./IPolygonEcosystemToken.sol";

/// @title Default Emission Manager
/// @author Polygon Labs (@DhairyaSethi, @gretzke, @qedk, @simonDos)
/// @notice A default emission manager implementation for the Polygon ERC20 token contract on Ethereum L1
/// @dev The contract allows for a 3% mint per year (compounded). 2% staking layer and 1% treasury
/// @custom:security-contact [email protected]
interface IDefaultEmissionManager {
/// @notice emitted when new tokens are minted
/// @param amount the amount of tokens minted
/// @param caller the caller of the mint function
event TokenMint(uint256 amount, address caller);

/// @notice thrown when a zero address is supplied during deployment
error InvalidAddress();

event TokenMint(uint256 amount, address caller);
/// @notice allows anyone to mint tokens to the stakeManager and treasury contracts based on current emission rates
/// @dev minting is done based on totalSupply diffs between the currentTotalSupply (maintained on POL, which includes any previous mints) and the newSupply (calculated based on the time elapsed since deployment)
function mint() external;

function getVersion() external pure returns (string memory version);
/// @return log2(3%pa continuously compounded emission per year) in 18 decimals, see _inflatedSupplyAfter
function INTEREST_PER_YEAR_LOG2() external view returns (uint256);

/// @return the start supply of the POL token in 18 decimals
function START_SUPPLY() external view returns (uint256);

/// @return polygonEcosystemToken address of the POL token
function token() external view returns (IPolygonEcosystemToken polygonEcosystemToken);

/// @return timestamp timestamp of initialisation of the contract, when emission starts
function startTimestamp() external view returns (uint256 timestamp);

function mint() external;

/// @notice returns total supply from compounded emission after timeElapsed from startTimestamp (deployment)
/// @param timeElapsedInSeconds the time elapsed since startTimestamp
/// @return inflatedSupply supply total supply from compounded emission after timeElapsed
/// @dev interestRatePerYear = 1.03; 3% per year
/// approximate the compounded interest rate using x^y = 2^(log2(x)*y)
/// where x is the interest rate per year and y is the number of seconds elapsed since deployment divided by 365 days in seconds
/// log2(interestRatePerYear) = 0.04264433740849372 with 18 decimals, as the interest rate does not change, hard code the value
function inflatedSupplyAfter(uint256 timeElapsedInSeconds) external pure returns (uint256 inflatedSupply);

/// @return version the implementation version
function getVersion() external pure returns (string memory version);
}
57 changes: 50 additions & 7 deletions src/interfaces/IPolygonEcosystemToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,67 @@ import {IERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/extensi
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IAccessControlEnumerable} from "openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol";

/// @title Polygon ERC20 token
/// @author Polygon Labs (@DhairyaSethi, @gretzke, @qedk, @simonDos)
/// @notice This is the Polygon ERC20 token contract on Ethereum L1
/// @dev The contract allows for a 1-to-1 representation between $POL and $MATIC and allows for additional emission based on hub and treasury requirements
/// @custom:security-contact [email protected]
interface IPolygonEcosystemToken is IERC20, IERC20Permit, IAccessControlEnumerable {
/// @notice emitted when the mint cap is updated
/// @param oldCap the old mint cap
/// @param newCap the new mint cap
event MintCapUpdated(uint256 oldCap, uint256 newCap);

/// @notice emitted when the permit2 integration is enabled/disabled
/// @param enabled whether the permit2 integration is enabled or not
event Permit2AllowanceUpdated(bool enabled);

/// @notice thrown when a zero address is supplied during deployment
error InvalidAddress();

/// @notice thrown when the mint cap is exceeded
/// @param maxMint the maximum amount of tokens that can be minted
/// @param mintRequested the amount of tokens that were requested to be minted
error MaxMintExceeded(uint256 maxMint, uint256 mintRequested);

function mintPerSecondCap() external view returns (uint256 currentMintPerSecondCap);
/// @notice mint token entrypoint for the emission manager contract
/// @param to address to mint to
/// @param amount amount to mint
/// @dev The function only validates the sender, the emission manager is responsible for correctness
function mint(address to, uint256 amount) external;

function getVersion() external pure returns (string memory version);
/// @notice update the limit of tokens that can be minted per second
/// @param newCap the amount of tokens in 18 decimals as an absolute value
function updateMintCap(uint256 newCap) external;

function lastMint() external view returns (uint256 lastMintTimestamp);
/// @notice manages the default max approval to the permit2 contract
/// @param enabled If true, the permit2 contract has full approval by default, if false, it has no approval by default
function updatePermit2Allowance(bool enabled) external;

function permit2Enabled() external view returns (bool isPermit2Enabled);
/// @return the role that allows minting of tokens
function EMISSION_ROLE() external view returns (bytes32);

function mint(address to, uint256 amount) external;
/// @return the role that allows updating the mint cap
function CAP_MANAGER_ROLE() external view returns (bytes32);

function updateMintCap(uint256 newCap) external;
/// @return the role that allows revoking the permit2 approval
function PERMIT2_REVOKER_ROLE() external view returns (bytes32);

function updatePermit2Allowance(bool enabled) external;
/// @return the address of the permit2 contract
function PERMIT2() external view returns (address);

/// @return currentMintPerSecondCap the current amount of tokens that can be minted per second
/// @dev 13.37 POL tokens per second. will limit emission in ~23 years
function mintPerSecondCap() external view returns (uint256 currentMintPerSecondCap);

/// @return lastMintTimestamp the timestamp of the last mint
function lastMint() external view returns (uint256 lastMintTimestamp);

/// @return isPermit2Enabled whether the permit2 default approval is currently active
function permit2Enabled() external view returns (bool isPermit2Enabled);

/// @notice returns the version of the contract
/// @return version version string
/// @dev this is to support our dev pipeline, and is present despite this contract not being behind a proxy
function getVersion() external pure returns (string memory version);
}
Loading

0 comments on commit acffed0

Please sign in to comment.