diff --git a/.gitignore b/.gitignore index 88d8366..72e88a7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /coverage lcov.info .DS_Store +.vscode .env broadcast/*/31337 diff --git a/src/DefaultEmissionManager.sol b/src/DefaultEmissionManager.sol index a93f7a9..c28b555 100644 --- a/src/DefaultEmissionManager.sol +++ b/src/DefaultEmissionManager.sol @@ -9,15 +9,15 @@ 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% stakeManager(Hub) and 1% treasury /// @custom:security-contact security@polygon.technology 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; + // log2(3%pa continuously compounded emission per year) in 18 decimals, see _inflatedSupplyAfter + uint256 public constant INTEREST_PER_YEAR_LOG2 = 0.04264433740849372e18; uint256 public constant START_SUPPLY = 10_000_000_000e18; address private immutable DEPLOYER; @@ -65,7 +65,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); @@ -79,10 +79,10 @@ contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionMana /// @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 + /// @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.028569152196770894 with 18 decimals, as the interest rate does not change, hard code the value + /// log2(interestRatePerYear) = 0.04264433740849372 with 18 decimals, as the interest rate does not change, hard code the value /// @return supply total supply from compounded emission after timeElapsed function inflatedSupplyAfter(uint256 timeElapsed) public pure returns (uint256 supply) { uint256 supplyFactor = PowUtil.exp2((INTEREST_PER_YEAR_LOG2 * timeElapsed) / 365 days); @@ -92,7 +92,7 @@ contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionMana /// @notice Returns the implementation version /// @return Version string function getVersion() external pure returns (string memory) { - return "1.0.0"; + return "1.1.0"; } /** diff --git a/src/PolygonEcosystemToken.sol b/src/PolygonEcosystemToken.sol index 8a31dad..d06b996 100644 --- a/src/PolygonEcosystemToken.sol +++ b/src/PolygonEcosystemToken.sol @@ -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 security@polygon.technology @@ -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; // 13.37 POL tokens per second. will limit emission in ~23 years uint256 public lastMint; bool public permit2Enabled; @@ -78,7 +78,7 @@ contract PolygonEcosystemToken is ERC20Permit, AccessControlEnumerable, IPolygon /// this contract not being behind a proxy /// @return Version string function getVersion() external pure returns (string memory) { - return "1.0.0"; + return "1.1.0"; } function _updatePermit2Allowance(bool enabled) private { diff --git a/test/DefaultEmissionManager.t.sol b/test/DefaultEmissionManager.t.sol index 6523b36..7e07ee7 100644 --- a/test/DefaultEmissionManager.t.sol +++ b/test/DefaultEmissionManager.t.sol @@ -143,9 +143,12 @@ contract DefaultEmissionManagerTest is Test { uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256)); assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA); - assertEq(matic.balanceOf(stakeManager), (polygon.totalSupply() - initialTotalSupply) / 2); + uint256 totalAmtMinted = polygon.totalSupply() - initialTotalSupply; + uint256 totalAmtMintedOneThird = totalAmtMinted / 3; + assertEq(matic.balanceOf(stakeManager), totalAmtMinted - totalAmtMintedOneThird); + assertEq(matic.balanceOf(treasury), 0); assertEq(polygon.balanceOf(stakeManager), 0); - assertEq(polygon.balanceOf(treasury), (polygon.totalSupply() - initialTotalSupply) / 2); + assertEq(polygon.balanceOf(treasury), totalAmtMintedOneThird); } function test_MintDelayTwice(uint128 delay) external { @@ -161,8 +164,9 @@ contract DefaultEmissionManagerTest is Test { uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256)); assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA); - uint256 balance = (polygon.totalSupply() - initialTotalSupply) / 2; - assertEq(matic.balanceOf(stakeManager), balance); + uint256 balance = (polygon.totalSupply() - initialTotalSupply) / 3; + uint256 stakeManagerBalance = (polygon.totalSupply() - initialTotalSupply) - balance; + assertEq(matic.balanceOf(stakeManager), stakeManagerBalance); assertEq(polygon.balanceOf(stakeManager), 0); assertEq(polygon.balanceOf(treasury), balance); @@ -175,8 +179,13 @@ contract DefaultEmissionManagerTest is Test { newSupply = abi.decode(vm.ffi(inputs), (uint256)); assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA); - balance += (polygon.totalSupply() - initialTotalSupply) / 2; - assertEq(matic.balanceOf(stakeManager), balance); + uint256 totalAmtMinted = polygon.totalSupply() - initialTotalSupply; + uint256 totalAmtMintedOneThird = totalAmtMinted / 3; + + balance += totalAmtMintedOneThird; + stakeManagerBalance += totalAmtMinted - totalAmtMintedOneThird; + + assertEq(matic.balanceOf(stakeManager), stakeManagerBalance); assertEq(polygon.balanceOf(stakeManager), 0); assertEq(polygon.balanceOf(treasury), balance); } @@ -185,6 +194,7 @@ contract DefaultEmissionManagerTest is Test { vm.assume(delay * uint256(cycles) <= 10 * 365 days && delay > 0 && cycles < 30); uint256 balance; + uint256 stakeManagerBalance; for (uint256 cycle; cycle < cycles; cycle++) { uint256 initialTotalSupply = polygon.totalSupply(); @@ -197,8 +207,13 @@ contract DefaultEmissionManagerTest is Test { uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256)); assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA); - balance += (polygon.totalSupply() - initialTotalSupply) / 2; - assertEq(matic.balanceOf(stakeManager), balance); + uint256 totalAmtMinted = polygon.totalSupply() - initialTotalSupply; + uint256 totalAmtMintedOneThird = totalAmtMinted / 3; + + balance += totalAmtMintedOneThird; + stakeManagerBalance += totalAmtMinted - totalAmtMintedOneThird; + + assertEq(matic.balanceOf(stakeManager), stakeManagerBalance); assertEq(polygon.balanceOf(stakeManager), 0); assertEq(polygon.balanceOf(treasury), balance); } @@ -209,6 +224,6 @@ contract DefaultEmissionManagerTest is Test { inputs[2] = vm.toString(delay); inputs[3] = vm.toString(polygon.totalSupply()); uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256)); - assertApproxEqAbs(newSupply, emissionManager.inflatedSupplyAfter(block.timestamp + delay), 1e19); + assertApproxEqAbs(newSupply, emissionManager.inflatedSupplyAfter(block.timestamp + delay), 1e20); } } diff --git a/test/PolygonEcosystemToken.t.sol b/test/PolygonEcosystemToken.t.sol index 9e1096f..4d0044a 100644 --- a/test/PolygonEcosystemToken.t.sol +++ b/test/PolygonEcosystemToken.t.sol @@ -18,7 +18,7 @@ contract PolygonTest is Test { address public governance; address public permit2revoker; DefaultEmissionManager public emissionManager; - uint256 public mintPerSecondCap = 10e18; // 10 POL tokens per second + uint256 public mintPerSecondCap = 13.37e18; // 13.37 POL tokens per second function setUp() external { migration = makeAddr("migration"); diff --git a/test/util/calc.js b/test/util/calc.js index b5c2f19..7b96dbd 100644 --- a/test/util/calc.js +++ b/test/util/calc.js @@ -1,4 +1,4 @@ -const interestRatePerYear = 1.02; +const interestRatePerYear = 1.03; const startSupply = 10_000_000_000e18; function main() { const [timeElapsedInSeconds] = process.argv.slice(2);