Skip to content

Commit

Permalink
Merge branch 'dev' into send-pol
Browse files Browse the repository at this point in the history
  • Loading branch information
simonDos authored Jun 14, 2024
2 parents 8b0a0b6 + 01bc269 commit 6bd4bc6
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 25 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ jobs:
run: |
forge --version
forge build --sizes
env:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
id: build

- name: Run Forge tests
# ! revert back to FOUNDRY_PROFILE=intense forge test -vvv
run: forge test -vvv
env:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
id: test
23 changes: 16 additions & 7 deletions src/DefaultEmissionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import {PowUtil} from "./lib/PowUtil.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
/// @dev The contract allows for a 2.5% mint per year (compounded). 1.5% staking layer and 1% treasury
/// @custom:security-contact [email protected]
contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionManager {
using SafeERC20 for IPolygonEcosystemToken;

uint256 public constant INTEREST_PER_YEAR_LOG2 = 0.04264433740849372e18;
uint256 public constant INTEREST_PER_YEAR_LOG2 = 0.03562390973072122e18; // log2(1.025)
uint256 public constant START_SUPPLY = 10_000_000_000e18;
address private immutable DEPLOYER;

Expand All @@ -27,6 +27,9 @@ contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionMana
IPolygonEcosystemToken public token;
uint256 public startTimestamp;

// NEW STORAGE 1.2.0
uint256 public START_SUPPLY_1_2_0;

constructor(address migration_, address stakeManager_, address treasury_) {
if (migration_ == address(0) || stakeManager_ == address(0) || treasury_ == address(0)) revert InvalidAddress();
DEPLOYER = msg.sender;
Expand All @@ -38,6 +41,11 @@ contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionMana
_disableInitializers();
}

function reinitialize() external reinitializer(2) {
START_SUPPLY_1_2_0 = token.totalSupply();
startTimestamp = block.timestamp;
}

function initialize(address token_, address owner_) external initializer {
// prevent front-running since we can't initialize on proxy deployment
if (DEPLOYER != msg.sender) revert();
Expand All @@ -62,7 +70,8 @@ contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionMana
uint256 amountToMint = newSupply - currentSupply;
if (amountToMint == 0) return; // no minting required

uint256 treasuryAmt = amountToMint / 3;
// 2/5 of 2.5% is 1% going to the treasury
uint256 treasuryAmt = amountToMint * 2 / 5;
uint256 stakeManagerAmt = amountToMint - treasuryAmt;

emit TokenMint(amountToMint, msg.sender);
Expand All @@ -77,15 +86,15 @@ contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionMana
}

/// @inheritdoc IDefaultEmissionManager
function inflatedSupplyAfter(uint256 timeElapsed) public pure returns (uint256 supply) {
function inflatedSupplyAfter(uint256 timeElapsed) public view returns (uint256 supply) {
uint256 supplyFactor = PowUtil.exp2((INTEREST_PER_YEAR_LOG2 * timeElapsed) / 365 days);
supply = (supplyFactor * START_SUPPLY) / 1e18;
supply = (supplyFactor * START_SUPPLY_1_2_0) / 1e18;
}

/// @inheritdoc IDefaultEmissionManager
function version() external pure returns (string memory) {
return "1.1.0";
return "1.2.0";
}

uint256[48] private __gap;
uint256[47] private __gap;
}
2 changes: 1 addition & 1 deletion src/interfaces/IDefaultEmissionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface IDefaultEmissionManager {
/// 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);
function inflatedSupplyAfter(uint256 timeElapsedInSeconds) external view returns (uint256 inflatedSupply);

/// @notice returns the version of the contract
/// @return version version string
Expand Down
33 changes: 19 additions & 14 deletions test/DefaultEmissionManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ contract DefaultEmissionManagerTest is Test {
vm.prank(governance);
migration.acceptOwnership();
emissionManager.initialize(address(polygon), governance);
emissionManager.reinitialize();

// POL being emissionary, while MATIC having a constant supply,
// the requirement of unmigrating POL to MATIC for StakeManager on each mint
// is satisfied by a one-time transfer of MATIC to the migration contract
Expand Down Expand Up @@ -143,14 +145,18 @@ contract DefaultEmissionManagerTest is Test {

assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA);
uint256 totalAmtMinted = polygon.totalSupply() - initialTotalSupply;
uint256 totalAmtMintedOneThird = totalAmtMinted / 3;
assertEq(polygon.balanceOf(stakeManager), totalAmtMinted - totalAmtMintedOneThird);
assertEq(polygon.balanceOf(treasury), totalAmtMintedOneThird);

uint256 totalAmtMintedTwoFifth = totalAmtMinted * 2 / 5;
assertEq(matic.balanceOf(stakeManager), totalAmtMinted - totalAmtMintedTwoFifth);
assertEq(matic.balanceOf(treasury), 0);
assertEq(polygon.balanceOf(stakeManager), 0);
assertEq(polygon.balanceOf(treasury), totalAmtMintedTwoFifth);
}

function test_MintDelayTwice(uint128 delay) external {
vm.assume(delay <= 5 * 365 days && delay > 0);

// now that we actually pass this to calc.js, we only need to set it once.
uint256 initialTotalSupply = polygon.totalSupply();

skip(delay);
Expand All @@ -161,12 +167,11 @@ contract DefaultEmissionManagerTest is Test {
uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256));

assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA);
uint256 balance = (polygon.totalSupply() - initialTotalSupply) / 3;
uint256 balance = (polygon.totalSupply() - initialTotalSupply) * 2 / 5;
uint256 stakeManagerBalance = (polygon.totalSupply() - initialTotalSupply) - balance;
assertEq(polygon.balanceOf(stakeManager), stakeManagerBalance);
assertEq(polygon.balanceOf(treasury), balance);

initialTotalSupply = polygon.totalSupply(); // for the new run
skip(delay);
emissionManager.mint();

Expand All @@ -176,10 +181,10 @@ contract DefaultEmissionManagerTest is Test {

assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA);
uint256 totalAmtMinted = polygon.totalSupply() - initialTotalSupply;
uint256 totalAmtMintedOneThird = totalAmtMinted / 3;
uint256 totalAmtMintedTwoFifth = totalAmtMinted * 2 / 5;

balance += totalAmtMintedOneThird;
stakeManagerBalance += totalAmtMinted - totalAmtMintedOneThird;
balance = totalAmtMintedTwoFifth;
stakeManagerBalance = totalAmtMinted - totalAmtMintedTwoFifth;

assertEq(polygon.balanceOf(stakeManager), stakeManagerBalance);
assertEq(polygon.balanceOf(treasury), balance);
Expand All @@ -190,10 +195,10 @@ contract DefaultEmissionManagerTest is Test {

uint256 balance;
uint256 stakeManagerBalance;
// now that we actually pass this to calc.js, we only need to set it once.
uint256 initialTotalSupply = polygon.totalSupply();

for (uint256 cycle; cycle < cycles; cycle++) {
uint256 initialTotalSupply = polygon.totalSupply();

skip(delay);
emissionManager.mint();

Expand All @@ -203,10 +208,10 @@ contract DefaultEmissionManagerTest is Test {

assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA);
uint256 totalAmtMinted = polygon.totalSupply() - initialTotalSupply;
uint256 totalAmtMintedOneThird = totalAmtMinted / 3;
uint256 totalAmtMintedTwoFifth = totalAmtMinted * 2 / 5;

balance += totalAmtMintedOneThird;
stakeManagerBalance += totalAmtMinted - totalAmtMintedOneThird;
balance = totalAmtMintedTwoFifth;
stakeManagerBalance = totalAmtMinted - totalAmtMintedTwoFifth;

assertEq(polygon.balanceOf(stakeManager), stakeManagerBalance);
assertEq(polygon.balanceOf(treasury), balance);
Expand All @@ -218,6 +223,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), 1e20);
assertApproxEqAbs(newSupply, emissionManager.inflatedSupplyAfter(delay), 1e20);
}
}
80 changes: 80 additions & 0 deletions test/upgrade/DefaultEmissionManager.1.2.0.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {PolygonEcosystemToken} from "src/PolygonEcosystemToken.sol";
import {DefaultEmissionManager} from "src/DefaultEmissionManager.sol";
import {PolygonMigration} from "src/PolygonMigration.sol";
import {ERC20PresetMinterPauser} from "openzeppelin-contracts/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";
import {
ProxyAdmin,
TransparentUpgradeableProxy,
ITransparentUpgradeableProxy
} from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol";
import {Test} from "forge-std/Test.sol";

// this test forks mainnet and tests the upgradeability of DefaultEmissionManagerProxy

contract DefaultEmissionManagerTest is Test {
uint256 mainnetFork;

address POLYGON_PROTOCOL_COUNCIL = 0x37D085ca4a24f6b29214204E8A8666f12cf19516;
address EM_PROXY = 0xbC9f74b3b14f460a6c47dCdDFd17411cBc7b6c53;
address COMMUNITY_TREASURY = 0x2ff25495d77f380d5F65B95F103181aE8b1cf898;
address EM_PROXY_ADMIN = 0xEBea33f2c92D03556b417F4F572B2FbbE62C39c3;
PolygonEcosystemToken pol = PolygonEcosystemToken(0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6);

uint256 NEW_INTEREST_PER_YEAR_LOG2 = 0.03562390973072122e18; // log2(1.025)

string[] internal inputs = new string[](5);

function setUp() public {
string memory MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL");
mainnetFork = vm.createFork(MAINNET_RPC_URL);
}

function testUpgrade() external {
vm.selectFork(mainnetFork);

address newTreasury = makeAddr("newTreasury");

DefaultEmissionManager emProxy = DefaultEmissionManager(EM_PROXY);

assertEq(emProxy.treasury(), COMMUNITY_TREASURY);

address migration = address(emProxy.migration());
address stakeManager = emProxy.stakeManager();

DefaultEmissionManager newEmImpl = new DefaultEmissionManager(migration, stakeManager, newTreasury);

ProxyAdmin admin = ProxyAdmin(EM_PROXY_ADMIN);

vm.prank(POLYGON_PROTOCOL_COUNCIL);

admin.upgradeAndCall(
ITransparentUpgradeableProxy(address(emProxy)),
address(newEmImpl),
abi.encodeWithSelector(DefaultEmissionManager.reinitialize.selector)
);

// initialize can still not be called
vm.expectRevert("Initializable: contract is already initialized");
emProxy.initialize(makeAddr("token"), msg.sender);

assertEq(pol.totalSupply(), emProxy.START_SUPPLY_1_2_0());
assertEq(block.timestamp, emProxy.startTimestamp());

// emission is now 2.5%
inputs[0] = "node";
inputs[1] = "test/util/calc.js";
inputs[2] = vm.toString(uint256(365 days));
inputs[3] = vm.toString(pol.totalSupply());
// vm.ffi executes the js script which contains the new emission rate
uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256));
assertApproxEqAbs(newSupply, emProxy.inflatedSupplyAfter(365 days), 1e20);

// treasury has been updated
assertEq(emProxy.treasury(), newTreasury);
// emission has been updated
assertEq(emProxy.INTEREST_PER_YEAR_LOG2(), NEW_INTEREST_PER_YEAR_LOG2);
}
}
7 changes: 4 additions & 3 deletions test/util/calc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const interestRatePerYear = 1.03;
const startSupply = 10_000_000_000e18;
const emissionRatePerYear = 1.025;

function main() {
const [timeElapsedInSeconds] = process.argv.slice(2);
const [startSupply] = process.argv.slice(3);

const supplyFactor = Math.pow(interestRatePerYear, timeElapsedInSeconds / (365 * 24 * 60 * 60));
const supplyFactor = Math.pow(emissionRatePerYear, timeElapsedInSeconds / (365 * 24 * 60 * 60));
const newSupply = BigInt(startSupply * supplyFactor);

console.log("0x" + newSupply.toString(16).padStart(64, "0")); // abi.encode(toMint)
Expand Down

0 comments on commit 6bd4bc6

Please sign in to comment.