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

Send pol to stakemanager #59

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 18 additions & 8 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,27 +70,29 @@ contract DefaultEmissionManager is Ownable2StepUpgradeable, IDefaultEmissionMana
uint256 amountToMint = newSupply - currentSupply;
if (amountToMint == 0) return; // no minting required

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

emit TokenMint(amountToMint, msg.sender);

IPolygonEcosystemToken _token = token;

_token.mint(address(this), amountToMint);

_token.safeTransfer(treasury, treasuryAmt);
// backconvert POL to MATIC before sending to StakeManager
migration.unmigrateTo(stakeManager, stakeManagerAmt);

_token.safeTransfer(stakeManager, stakeManagerAmt);
}

/// @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;
Expand Down
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
42 changes: 19 additions & 23 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 @@ -125,7 +127,6 @@ contract DefaultEmissionManagerTest is Test {
emissionManager.mint();
// timeElapsed is zero, so no minting
assertEq(polygon.balanceOf(stakeManager), 0);
assertEq(matic.balanceOf(stakeManager), 0);
assertEq(polygon.balanceOf(treasury), 0);
}

Expand All @@ -144,16 +145,15 @@ contract DefaultEmissionManagerTest is Test {

assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA);
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), totalAmtMintedOneThird);
uint256 totalAmtMintedTwoFifth = totalAmtMinted * 2 / 5;
assertEq(polygon.balanceOf(stakeManager), totalAmtMinted - totalAmtMintedTwoFifth);
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 @@ -164,13 +164,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(matic.balanceOf(stakeManager), stakeManagerBalance);
assertEq(polygon.balanceOf(stakeManager), 0);
assertEq(polygon.balanceOf(stakeManager), stakeManagerBalance);
assertEq(polygon.balanceOf(treasury), balance);

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

Expand All @@ -180,13 +178,12 @@ 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(matic.balanceOf(stakeManager), stakeManagerBalance);
assertEq(polygon.balanceOf(stakeManager), 0);
assertEq(polygon.balanceOf(stakeManager), stakeManagerBalance);
assertEq(polygon.balanceOf(treasury), balance);
}

Expand All @@ -195,10 +192,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 @@ -208,13 +205,12 @@ 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(matic.balanceOf(stakeManager), stakeManagerBalance);
assertEq(polygon.balanceOf(stakeManager), 0);
assertEq(polygon.balanceOf(stakeManager), stakeManagerBalance);
assertEq(polygon.balanceOf(treasury), balance);
}
}
Expand All @@ -224,6 +220,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);
}
}
79 changes: 79 additions & 0 deletions test/upgrade/DefaultEmissionManager.1.2.0.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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_BOARD = 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_BOARD);

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.upgrade(ITransparentUpgradeableProxy(address(emProxy)), address(newEmImpl));

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

// reinitialize to reset startTimestamp and start supply
emProxy.reinitialize();

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
Loading