From ae1d21062d71fbe1636ffdaf97d1a12138cefc78 Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 1 Sep 2023 17:56:06 +0200 Subject: [PATCH 01/14] improve documentation --- src/DefaultInflationManager.sol | 4 ++-- src/Polygon.sol | 2 +- src/PolygonMigration.sol | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DefaultInflationManager.sol b/src/DefaultInflationManager.sol index 1a265a9..be42c12 100644 --- a/src/DefaultInflationManager.sol +++ b/src/DefaultInflationManager.sol @@ -10,7 +10,7 @@ import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/Safe import {PowUtil} from "./lib/PowUtil.sol"; /// @title Default Inflation Manager -/// @author QEDK (https://polygon.technology) +/// @author Polygon Labs (@DhairyaSethi, @gretzke, @qedk) /// @notice A default inflation manager implementation for the Polygon ERC20 token contract on Ethereum L1 /// @dev The contract allows for a 1% mint *each* per year (compounded every second) to the stakeManager and treasury contracts /// @custom:security-contact security@polygon.technology @@ -61,7 +61,7 @@ contract DefaultInflationManager is assert(START_SUPPLY == token.totalSupply()); token.safeApprove(migration_, type(uint256).max); - + // initial ownership setup bypassing 2 step ownership transfer process _transferOwnership(owner_); } diff --git a/src/Polygon.sol b/src/Polygon.sol index 8a081f6..18e6e1d 100644 --- a/src/Polygon.sol +++ b/src/Polygon.sol @@ -6,7 +6,7 @@ import {IPolygon} from "./interfaces/IPolygon.sol"; import {IDefaultInflationManager} from "./interfaces/IDefaultInflationManager.sol"; /// @title Polygon ERC20 token -/// @author QEDK (https://polygon.technology) +/// @author Polygon Labs (@DhairyaSethi, @gretzke, @qedk) /// @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 inflation based /// on hub and treasury requirements diff --git a/src/PolygonMigration.sol b/src/PolygonMigration.sol index bed2e74..9a3b190 100644 --- a/src/PolygonMigration.sol +++ b/src/PolygonMigration.sol @@ -8,7 +8,7 @@ import {Ownable2Step} from "openzeppelin-contracts/contracts/access/Ownable2Step import {IPolygonMigration} from "./interfaces/IPolygonMigration.sol"; /// @title Polygon Migration -/// @author QEDK (https://polygon.technology) +/// @author Polygon Labs (@DhairyaSethi, @gretzke, @qedk) /// @notice This is the migration contract for Matic <-> Polygon ERC20 token on Ethereum L1 /// @dev The contract allows for a 1-to-1 conversion from $MATIC into $POL and vice-versa /// @custom:security-contact security@polygon.technology From 3b9abc29707a0184de5c9d205e7db695722035e2 Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 1 Sep 2023 18:01:24 +0200 Subject: [PATCH 02/14] reset gap back to 50 as it hasn't been deployed yet --- src/DefaultInflationManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DefaultInflationManager.sol b/src/DefaultInflationManager.sol index be42c12..ba5d27a 100644 --- a/src/DefaultInflationManager.sol +++ b/src/DefaultInflationManager.sol @@ -108,5 +108,5 @@ contract DefaultInflationManager is * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[49] private __gap; + uint256[50] private __gap; } From b9c3cfe1d87748fd1631d3be4d1f1f21a8337bac Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 1 Sep 2023 18:06:28 +0200 Subject: [PATCH 03/14] Make unmigration lock a boolean for simplicity and future maintenance --- src/PolygonMigration.sol | 14 ++++++-------- src/interfaces/IPolygonMigration.sol | 2 +- test/PolygonMigration.t.sol | 10 +++------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/PolygonMigration.sol b/src/PolygonMigration.sol index 9a3b190..4ab42c2 100644 --- a/src/PolygonMigration.sol +++ b/src/PolygonMigration.sol @@ -19,10 +19,10 @@ contract PolygonMigration is Ownable2Step, IPolygonMigration { IERC20 public polygon; IERC20 public immutable matic; uint256 public releaseTimestamp; - uint256 public unmigrationLock; + bool public unmigrationLocked; modifier ifUnmigrationUnlocked() { - if (unmigrationLock != 0) revert UnmigrationLocked(); + if (unmigrationLocked) revert UnmigrationLocked(); _; } @@ -109,12 +109,10 @@ contract PolygonMigration is Ownable2Step, IPolygonMigration { /// @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 unmigrationLock_ New unmigration lock status - function updateUnmigrationLock( - uint256 unmigrationLock_ - ) external onlyOwner { - unmigrationLock = unmigrationLock_; - emit UnmigrationLockUpdated(unmigrationLock_); + /// @param unmigrationLocked_ New unmigration lock status + function updateUnmigrationLock(bool unmigrationLocked_) external onlyOwner { + unmigrationLocked = unmigrationLocked_; + emit UnmigrationLockUpdated(unmigrationLocked_); } /// @notice Allows governance to release the remaining POL tokens after the migration period has elapsed diff --git a/src/interfaces/IPolygonMigration.sol b/src/interfaces/IPolygonMigration.sol index 0b4ee88..546a381 100644 --- a/src/interfaces/IPolygonMigration.sol +++ b/src/interfaces/IPolygonMigration.sol @@ -10,7 +10,7 @@ interface IPolygonMigration { event Migrated(address indexed account, uint256 amount); event Unmigrated(address indexed account, uint256 amount); - event UnmigrationLockUpdated(uint256 lock); + event UnmigrationLockUpdated(bool lock); event ReleaseTimestampUpdated(uint256 timestamp); event Released(uint256 polAmount, uint256 maticAmount); diff --git a/test/PolygonMigration.t.sol b/test/PolygonMigration.t.sol index 2897674..177aac6 100644 --- a/test/PolygonMigration.t.sol +++ b/test/PolygonMigration.t.sol @@ -113,16 +113,12 @@ contract PolygonMigrationTest is Test { assertEq(matic.balanceOf(user), amount2); } - function testRevert_Unmigrate( - address user, - uint256 amount, - uint256 unmigrationLock - ) external { + function testRevert_Unmigrate(address user, uint256 amount) external { + bool unmigrationLock = true; vm.assume( amount <= 10000000000 * 10 ** 18 && user != address(0) && - user != address(migration) && - unmigrationLock != 0 + user != address(migration) ); matic.mint(user, amount); vm.startPrank(user); From 59876e235fdd676ab17dd542578ef0044776d7e4 Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 1 Sep 2023 18:07:02 +0200 Subject: [PATCH 04/14] update naming convention --- src/PolygonMigration.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PolygonMigration.sol b/src/PolygonMigration.sol index 4ab42c2..e3da0d9 100644 --- a/src/PolygonMigration.sol +++ b/src/PolygonMigration.sol @@ -21,7 +21,7 @@ contract PolygonMigration is Ownable2Step, IPolygonMigration { uint256 public releaseTimestamp; bool public unmigrationLocked; - modifier ifUnmigrationUnlocked() { + modifier onlyUnmigrationUnlocked() { if (unmigrationLocked) revert UnmigrationLocked(); _; } @@ -54,7 +54,7 @@ contract PolygonMigration is Ownable2Step, IPolygonMigration { /// @notice This function allows for unmigrating from POL tokens to MATIC tokens /// @param amount Amount of POL to migrate - function unmigrate(uint256 amount) external ifUnmigrationUnlocked { + function unmigrate(uint256 amount) external onlyUnmigrationUnlocked { emit Unmigrated(msg.sender, amount); polygon.safeTransferFrom(msg.sender, address(this), amount); @@ -67,7 +67,7 @@ contract PolygonMigration is Ownable2Step, IPolygonMigration { function unmigrateTo( uint256 amount, address account - ) external ifUnmigrationUnlocked { + ) external onlyUnmigrationUnlocked { emit Unmigrated(msg.sender, amount); polygon.safeTransferFrom(msg.sender, address(this), amount); @@ -82,7 +82,7 @@ contract PolygonMigration is Ownable2Step, IPolygonMigration { uint8 v, bytes32 r, bytes32 s - ) external ifUnmigrationUnlocked { + ) external onlyUnmigrationUnlocked { emit Unmigrated(msg.sender, amount); IERC20Permit(address(polygon)).safePermit( From f10a0a45ba0851f0ffbc9887af3742753c8868af Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 1 Sep 2023 21:04:25 +0200 Subject: [PATCH 05/14] Make migration contract upgradeable, remove release functionality --- script/Deploy.s.sol | 20 +++++-- src/PolygonMigration.sol | 46 ++++++---------- src/interfaces/IPolygonMigration.sol | 4 -- test/DefaultInflationManager.t.sol | 11 +++- test/PolygonMigration.t.sol | 81 ++++++++-------------------- 5 files changed, 62 insertions(+), 100 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index a492c23..42267df 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -23,7 +23,17 @@ contract Deploy is Script { ) public { vm.startBroadcast(deployerPrivateKey); - PolygonMigration migration = new PolygonMigration(matic, governance); + address migrationImplementation = address(new PolygonMigration()); + + address migrationProxy = address( + new TransparentUpgradeableProxy( + address(migrationImplementation), + governance, + "" + ) + ); + + PolygonMigration(migrationProxy).initialize(matic); address inflationManagerImplementation = address( new DefaultInflationManager() @@ -37,21 +47,21 @@ contract Deploy is Script { ); Polygon polygonToken = new Polygon( - address(migration), + address(migrationProxy), address(inflationManagerProxy) ); DefaultInflationManager(inflationManagerProxy).initialize( address(polygonToken), - address(migration), + address(migrationProxy), stakeManager, treasury, governance ); - migration.setPolygonToken(address(polygonToken)); + PolygonMigration(migrationProxy).setPolygonToken(address(polygonToken)); - migration.transferOwnership(governance); // governance needs to accept the ownership transfer + PolygonMigration(migrationProxy).transferOwnership(governance); // governance needs to accept the ownership transfer vm.stopBroadcast(); } diff --git a/src/PolygonMigration.sol b/src/PolygonMigration.sol index e3da0d9..5d854af 100644 --- a/src/PolygonMigration.sol +++ b/src/PolygonMigration.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.21; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {IERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol"; import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; -import {Ownable2Step} from "openzeppelin-contracts/contracts/access/Ownable2Step.sol"; +import {Ownable2StepUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; import {IPolygonMigration} from "./interfaces/IPolygonMigration.sol"; /// @title Polygon Migration @@ -12,13 +12,12 @@ import {IPolygonMigration} from "./interfaces/IPolygonMigration.sol"; /// @notice This is the migration contract for Matic <-> Polygon ERC20 token on Ethereum L1 /// @dev The contract allows for a 1-to-1 conversion from $MATIC into $POL and vice-versa /// @custom:security-contact security@polygon.technology -contract PolygonMigration is Ownable2Step, IPolygonMigration { +contract PolygonMigration is Ownable2StepUpgradeable, IPolygonMigration { using SafeERC20 for IERC20; using SafeERC20 for IERC20Permit; IERC20 public polygon; - IERC20 public immutable matic; - uint256 public releaseTimestamp; + IERC20 public matic; bool public unmigrationLocked; modifier onlyUnmigrationUnlocked() { @@ -26,12 +25,15 @@ contract PolygonMigration is Ownable2Step, IPolygonMigration { _; } - constructor(address matic_, address owner_) { - if (matic_ == address(0) || owner_ == address(0)) - revert InvalidAddress(); + constructor() { + // so that the implementation contract cannot be initialized + _disableInitializers(); + } + function initialize(address matic_) external initializer { + __Ownable_init(); + if (matic_ == address(0)) revert InvalidAddress(); matic = IERC20(matic_); - releaseTimestamp = block.timestamp + (365 days * 4); // 4 years } /// @notice This function allows owner/governance to set POL token address *only once* @@ -98,15 +100,6 @@ contract PolygonMigration is Ownable2Step, IPolygonMigration { matic.safeTransfer(msg.sender, amount); } - /// @notice Allows governance to update the release timestamp if required - /// @dev The function does not do any validation since governance can correct the timestamp if required - /// @param timestamp_ New release timestamp - function updateReleaseTimestamp(uint256 timestamp_) external onlyOwner { - if (timestamp_ < block.timestamp) revert InvalidTimestamp(); - releaseTimestamp = timestamp_; - emit ReleaseTimestampUpdated(timestamp_); - } - /// @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 @@ -115,17 +108,10 @@ contract PolygonMigration is Ownable2Step, IPolygonMigration { emit UnmigrationLockUpdated(unmigrationLocked_); } - /// @notice Allows governance to release the remaining POL tokens after the migration period has elapsed - /// @dev In case any MATIC was sent out of process, it will be sent to the dead address - function release() external onlyOwner { - if (block.timestamp < releaseTimestamp) revert MigrationNotOver(); - uint256 polAmount = polygon.balanceOf(address(this)); - uint256 maticAmount = matic.balanceOf(address(this)); - polygon.safeTransfer(msg.sender, polAmount); - matic.safeTransfer( - 0x000000000000000000000000000000000000dEaD, - maticAmount - ); - emit Released(polAmount, maticAmount); - } + /** + * @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[50] private __gap; } diff --git a/src/interfaces/IPolygonMigration.sol b/src/interfaces/IPolygonMigration.sol index 546a381..c14fe0e 100644 --- a/src/interfaces/IPolygonMigration.sol +++ b/src/interfaces/IPolygonMigration.sol @@ -4,15 +4,11 @@ pragma solidity 0.8.21; interface IPolygonMigration { error UnmigrationLocked(); error InvalidAddressOrAlreadySet(); - error InvalidTimestamp(); - error MigrationNotOver(); error InvalidAddress(); event Migrated(address indexed account, uint256 amount); event Unmigrated(address indexed account, uint256 amount); event UnmigrationLockUpdated(bool lock); - event ReleaseTimestampUpdated(uint256 timestamp); - event Released(uint256 polAmount, uint256 maticAmount); function migrate(uint256 amount) external; diff --git a/test/DefaultInflationManager.t.sol b/test/DefaultInflationManager.t.sol index d7c251f..9af6355 100644 --- a/test/DefaultInflationManager.t.sol +++ b/test/DefaultInflationManager.t.sol @@ -42,7 +42,16 @@ contract DefaultInflationManagerTest is Test { ) ); matic = new ERC20PresetMinterPauser("Matic Token", "MATIC"); - migration = new PolygonMigration(address(matic), governance); + migration = PolygonMigration( + address( + new TransparentUpgradeableProxy( + address(new PolygonMigration()), + msg.sender, + "" + ) + ) + ); + migration.initialize(address(matic)); polygon = new Polygon(address(migration), address(inflationManager)); migration.setPolygonToken(address(polygon)); // deployer sets token migration.transferOwnership(governance); diff --git a/test/PolygonMigration.t.sol b/test/PolygonMigration.t.sol index 177aac6..ad52603 100644 --- a/test/PolygonMigration.t.sol +++ b/test/PolygonMigration.t.sol @@ -5,6 +5,7 @@ import {Polygon} from "src/Polygon.sol"; import {PolygonMigration} from "src/PolygonMigration.sol"; import {IPolygonMigration} from "src/interfaces/IPolygonMigration.sol"; import {ERC20PresetMinterPauser} from "openzeppelin-contracts/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; +import {TransparentUpgradeableProxy} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {SigUtils} from "test/SigUtils.t.sol"; import {Test} from "forge-std/Test.sol"; @@ -24,7 +25,16 @@ contract PolygonMigrationTest is Test { inflationManager = makeAddr("inflationManager"); governance = makeAddr("governance"); matic = new ERC20PresetMinterPauser("Matic Token", "MATIC"); - migration = new PolygonMigration(address(matic), governance); + migration = PolygonMigration( + address( + new TransparentUpgradeableProxy( + address(new PolygonMigration()), + msg.sender, + "" + ) + ) + ); + migration.initialize(address(matic)); polygon = new Polygon(address(migration), address(inflationManager)); sigUtils = new SigUtils(polygon.DOMAIN_SEPARATOR()); @@ -37,21 +47,21 @@ contract PolygonMigrationTest is Test { function test_Deployment() external { assertEq(address(migration.polygon()), address(polygon)); assertEq(address(migration.matic()), address(matic)); - assertEq( - migration.releaseTimestamp(), - block.timestamp + (365 days * 4) - ); assertEq(migration.owner(), governance); } function test_InvalidDeployment() external { - PolygonMigration temp; - vm.expectRevert(IPolygonMigration.InvalidAddress.selector); - temp = new PolygonMigration(address(matic), address(0)); - vm.expectRevert(IPolygonMigration.InvalidAddress.selector); - temp = new PolygonMigration(address(0), governance); + PolygonMigration temp = PolygonMigration( + address( + new TransparentUpgradeableProxy( + address(new PolygonMigration()), + msg.sender, + "" + ) + ) + ); vm.expectRevert(IPolygonMigration.InvalidAddress.selector); - temp = new PolygonMigration(address(0), address(0)); + temp.initialize(address(0)); } function test_Migrate(address user, uint256 amount) external { @@ -210,53 +220,4 @@ contract PolygonMigrationTest is Test { assertEq(matic.balanceOf(address(migration)), amount - amount2); assertEq(matic.balanceOf(user), amount2); } - - function testRevert_UpdateReleaseTimestampOnlyGovernance( - address user, - uint256 timestamp - ) external { - vm.assume(timestamp >= block.timestamp && user != governance); - vm.expectRevert("Ownable: caller is not the owner"); - migration.updateReleaseTimestamp(timestamp); - } - - function testRevert_UpdateReleaseTimestampTooEarly( - uint256 timestamp - ) external { - vm.assume(timestamp < block.timestamp); - vm.startPrank(governance); - vm.expectRevert(IPolygonMigration.InvalidTimestamp.selector); - migration.updateReleaseTimestamp(timestamp); - } - - function test_UpdateReleaseTimestamp(uint256 timestamp) external { - vm.assume(timestamp >= block.timestamp); - vm.startPrank(governance); - migration.updateReleaseTimestamp(timestamp); - - assertEq(migration.releaseTimestamp(), timestamp); - } - - function testRevert_ReleaseOnlyGovernance() external { - vm.expectRevert("Ownable: caller is not the owner"); - migration.release(); - } - - function testRevert_ReleaseTooEarly(uint256 timestamp) external { - vm.assume(timestamp < migration.releaseTimestamp()); - vm.startPrank(governance); - vm.expectRevert(IPolygonMigration.MigrationNotOver.selector); - migration.release(); - } - - function test_Release(uint256 timestamp) external { - vm.assume(timestamp >= migration.releaseTimestamp()); - vm.warp(timestamp); - uint256 balance = polygon.balanceOf(address(migration)); - vm.startPrank(governance); - migration.release(); - - assertEq(polygon.balanceOf(address(migration)), 0); - assertEq(polygon.balanceOf(governance), balance); - } } From 67355776a2227421a5fc048070c6ab346a9e44f3 Mon Sep 17 00:00:00 2001 From: gretzke Date: Fri, 1 Sep 2023 21:30:01 +0200 Subject: [PATCH 06/14] adjust supply cap --- src/Polygon.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Polygon.sol b/src/Polygon.sol index 18e6e1d..b86d716 100644 --- a/src/Polygon.sol +++ b/src/Polygon.sol @@ -13,7 +13,7 @@ import {IDefaultInflationManager} from "./interfaces/IDefaultInflationManager.so /// @custom:security-contact security@polygon.technology contract Polygon is ERC20Permit, IPolygon { address public immutable inflationManager; - uint256 public constant mintPerSecondCap = 0.00000001e18; // 0.00000001% of POL Supply per second, in 18 deciamls + uint256 public constant mintPerSecondCap = 0.0000000420e18; // 0.0000042% of POL Supply per second, in 18 decimals uint256 public lastMint; constructor( From 68e90a9faf6559efc241a54695c2bf3bc573e8f3 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Sat, 2 Sep 2023 01:14:43 +0530 Subject: [PATCH 07/14] fix: test --- test/Polygon.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Polygon.t.sol b/test/Polygon.t.sol index 1353e92..e3a9540 100644 --- a/test/Polygon.t.sol +++ b/test/Polygon.t.sol @@ -14,6 +14,7 @@ contract PolygonTest is Test { address public treasury; address public stakeManager; DefaultInflationManager public inflationManager; + uint256 public constant mintPerSecondCap = 0.0000000420e18; // 0.0000042% of POL Supply per second, in 18 decimals function setUp() external { migration = makeAddr("migration"); @@ -97,7 +98,7 @@ contract PolygonTest is Test { ); skip(++delay); // avoid delay == 0 - uint256 maxMint = (0.00000001e18 * delay * polygon.totalSupply()) / + uint256 maxMint = (mintPerSecondCap * delay * polygon.totalSupply()) / 1e18; if (amount > maxMint) vm.expectRevert( From b81d8bb4bec6767c925eb630164a14b8229a4af3 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Sat, 2 Sep 2023 01:24:11 +0530 Subject: [PATCH 08/14] fix: migrationContract fuzz inputs --- test/PolygonMigration.t.sol | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/PolygonMigration.t.sol b/test/PolygonMigration.t.sol index ad52603..8b9b0d7 100644 --- a/test/PolygonMigration.t.sol +++ b/test/PolygonMigration.t.sol @@ -68,7 +68,8 @@ contract PolygonMigrationTest is Test { vm.assume( amount <= 10000000000 * 10 ** 18 && user != address(0) && - user != address(migration) + user != address(migration) && + user != governance ); matic.mint(user, amount); vm.startPrank(user); @@ -104,7 +105,8 @@ contract PolygonMigrationTest is Test { amount <= 10000000000 * 10 ** 18 && amount2 <= amount && user != address(0) && - user != address(migration) + user != address(migration) && + user != governance ); matic.mint(user, amount); vm.startPrank(user); @@ -128,7 +130,8 @@ contract PolygonMigrationTest is Test { vm.assume( amount <= 10000000000 * 10 ** 18 && user != address(0) && - user != address(migration) + user != address(migration) && + user != governance ); matic.mint(user, amount); vm.startPrank(user); @@ -159,6 +162,7 @@ contract PolygonMigrationTest is Test { amount2 <= amount && user != address(0) && user != address(migration) && + user != governance && user != migrateTo && migrateTo != address(0) && migrateTo != address(migration) @@ -192,6 +196,7 @@ contract PolygonMigrationTest is Test { privKey < 115792089237316195423570985008687907852837564279074904382605163141518161494337 && (user = vm.addr(privKey)) != address(migration) && + user != governance && amount <= 10000000000 * 10 ** 18 && amount2 <= amount ); From 3a820fdc6c87f62606457d852f145b339f0eb9c5 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Sat, 2 Sep 2023 01:38:08 +0530 Subject: [PATCH 09/14] chore: initialize during proxy deployment --- script/Deploy.s.sol | 12 +++++------- test/PolygonMigration.t.sol | 6 ++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 42267df..0125a02 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -27,14 +27,12 @@ contract Deploy is Script { address migrationProxy = address( new TransparentUpgradeableProxy( - address(migrationImplementation), + migrationImplementation, governance, - "" + abi.encodeCall(PolygonMigration.initialize, matic) ) ); - PolygonMigration(migrationProxy).initialize(matic); - address inflationManagerImplementation = address( new DefaultInflationManager() ); @@ -47,13 +45,13 @@ contract Deploy is Script { ); Polygon polygonToken = new Polygon( - address(migrationProxy), - address(inflationManagerProxy) + migrationProxy, + inflationManagerProxy ); DefaultInflationManager(inflationManagerProxy).initialize( address(polygonToken), - address(migrationProxy), + migrationProxy, stakeManager, treasury, governance diff --git a/test/PolygonMigration.t.sol b/test/PolygonMigration.t.sol index 8b9b0d7..bbb5fa9 100644 --- a/test/PolygonMigration.t.sol +++ b/test/PolygonMigration.t.sol @@ -30,11 +30,10 @@ contract PolygonMigrationTest is Test { new TransparentUpgradeableProxy( address(new PolygonMigration()), msg.sender, - "" + abi.encodeCall(PolygonMigration.initialize, address(matic)) ) ) ); - migration.initialize(address(matic)); polygon = new Polygon(address(migration), address(inflationManager)); sigUtils = new SigUtils(polygon.DOMAIN_SEPARATOR()); @@ -62,6 +61,9 @@ contract PolygonMigrationTest is Test { ); vm.expectRevert(IPolygonMigration.InvalidAddress.selector); temp.initialize(address(0)); + temp.initialize(address(matic)); + vm.expectRevert("Initializable: contract is already initialized"); + temp.initialize(address(1)); } function test_Migrate(address user, uint256 amount) external { From 9878ed762e4269c6c337c9929dbec706dd4189bc Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Sat, 2 Sep 2023 01:41:56 +0530 Subject: [PATCH 10/14] gasopt: cache lastMint --- src/Polygon.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Polygon.sol b/src/Polygon.sol index b86d716..21134c7 100644 --- a/src/Polygon.sol +++ b/src/Polygon.sol @@ -33,11 +33,12 @@ contract Polygon is ERC20Permit, IPolygon { /// @param amount Amount to mint function mint(address to, uint256 amount) external { if (msg.sender != inflationManager) revert OnlyInflationManager(); - if (lastMint == 0) - lastMint = IDefaultInflationManager(inflationManager) + uint256 lastMintCache = lastMint; + if (lastMintCache == 0) + lastMintCache = IDefaultInflationManager(inflationManager) .startTimestamp(); - uint256 timeElapsedSinceLastMint = block.timestamp - lastMint; + uint256 timeElapsedSinceLastMint = block.timestamp - lastMintCache; uint256 maxMint = (timeElapsedSinceLastMint * mintPerSecondCap * totalSupply()) / 1e18; From 84b49b60fcdcd4f65582af744cad5994f2253883 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Sat, 2 Sep 2023 01:51:20 +0530 Subject: [PATCH 11/14] chore: switch unmigrateTo param order; --- src/DefaultInflationManager.sol | 2 +- src/PolygonMigration.sol | 4 ++-- src/interfaces/IPolygonMigration.sol | 2 +- test/PolygonMigration.t.sol | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/DefaultInflationManager.sol b/src/DefaultInflationManager.sol index ba5d27a..6a7237f 100644 --- a/src/DefaultInflationManager.sol +++ b/src/DefaultInflationManager.sol @@ -82,7 +82,7 @@ contract DefaultInflationManager is token.mint(address(this), amountToMint); token.transfer(treasury, treasuryAmt); // backconvert POL to MATIC before sending to StakeManager - migration.unmigrateTo(stakeManagerAmt, stakeManager); + migration.unmigrateTo(stakeManager, stakeManagerAmt); emit TokenMint(amountToMint, msg.sender); } diff --git a/src/PolygonMigration.sol b/src/PolygonMigration.sol index 5d854af..41c735f 100644 --- a/src/PolygonMigration.sol +++ b/src/PolygonMigration.sol @@ -67,8 +67,8 @@ contract PolygonMigration is Ownable2StepUpgradeable, IPolygonMigration { /// @param amount Amount of POL to migrate /// @param account Address to receive MATIC tokens function unmigrateTo( - uint256 amount, - address account + address account, + uint256 amount ) external onlyUnmigrationUnlocked { emit Unmigrated(msg.sender, amount); diff --git a/src/interfaces/IPolygonMigration.sol b/src/interfaces/IPolygonMigration.sol index c14fe0e..57db513 100644 --- a/src/interfaces/IPolygonMigration.sol +++ b/src/interfaces/IPolygonMigration.sol @@ -14,7 +14,7 @@ interface IPolygonMigration { function unmigrate(uint256 amount) external; - function unmigrateTo(uint256 amount, address account) external; + function unmigrateTo(address account, uint256 amount) external; function unmigrateWithPermit( uint256 amount, diff --git a/test/PolygonMigration.t.sol b/test/PolygonMigration.t.sol index bbb5fa9..dc9c95a 100644 --- a/test/PolygonMigration.t.sol +++ b/test/PolygonMigration.t.sol @@ -179,7 +179,7 @@ contract PolygonMigrationTest is Test { assertEq(polygon.balanceOf(user), amount); polygon.approve(address(migration), amount2); - migration.unmigrateTo(amount2, migrateTo); + migration.unmigrateTo(migrateTo, amount2); assertEq(polygon.balanceOf(user), amount - amount2); assertEq(matic.balanceOf(address(migration)), amount - amount2); From 59b606189f12a0b589e5fb2f1f590e98c20d9c1e Mon Sep 17 00:00:00 2001 From: gretzke Date: Sat, 2 Sep 2023 00:00:55 +0200 Subject: [PATCH 12/14] update compounding interval from seconds to years --- src/DefaultInflationManager.sol | 16 ++++++++-------- test/DefaultInflationManager.t.sol | 17 ++++++----------- test/util/calc.js | 12 ++++++------ 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/DefaultInflationManager.sol b/src/DefaultInflationManager.sol index ba5d27a..33e7e98 100644 --- a/src/DefaultInflationManager.sol +++ b/src/DefaultInflationManager.sol @@ -12,7 +12,7 @@ import {PowUtil} from "./lib/PowUtil.sol"; /// @title Default Inflation Manager /// @author Polygon Labs (@DhairyaSethi, @gretzke, @qedk) /// @notice A default inflation manager implementation for the Polygon ERC20 token contract on Ethereum L1 -/// @dev The contract allows for a 1% mint *each* per year (compounded every second) to the stakeManager and treasury contracts +/// @dev The contract allows for a 1% mint *each* per year (compounded every year) to the stakeManager and treasury contracts /// @custom:security-contact security@polygon.technology contract DefaultInflationManager is Initializable, @@ -21,8 +21,8 @@ contract DefaultInflationManager is { using SafeERC20 for IPolygon; - // log2(2%pa continuously compounded inflation per second) in 18 decimals(Wad), see _inflatedSupplyAfter - uint256 public constant INTEREST_PER_SECOND_LOG2 = 0.000000000914951192e18; + // log2(2%pa continuously compounded inflation per year) in 18 decimals, see _inflatedSupplyAfter + uint256 public constant INTEREST_PER_YEAR_LOG2 = 0.028569152196770894e18; uint256 public constant START_SUPPLY = 10_000_000_000e18; IPolygon public token; @@ -89,16 +89,16 @@ contract DefaultInflationManager is /// @notice Returns total supply from compounded inflation after timeElapsed from startTimestamp (deployment) /// @param timeElapsed The time elapsed since startTimestamp - /// @dev interestRatePerSecond = 1.000000000634195839; 2% per year in seconds with 18 decimals - /// approximate the compounded interest rate per second using x^y = 2^(log2(x)*y) - /// where x is the interest rate per second and y is the number of seconds elapsed since deployment - /// log2(interestRatePerSecond) = 0.000000000914951192 with 18 decimals, as the interest rate does not change, hard code the value + /// @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 inflation after timeElapsed function _inflatedSupplyAfter( uint256 timeElapsed ) private pure returns (uint256 supply) { uint256 supplyFactor = PowUtil.exp2( - INTEREST_PER_SECOND_LOG2 * timeElapsed + (INTEREST_PER_YEAR_LOG2 * timeElapsed) / 365 days ); supply = (supplyFactor * START_SUPPLY) / 1e18; } diff --git a/test/DefaultInflationManager.t.sol b/test/DefaultInflationManager.t.sol index 9af6355..45369ba 100644 --- a/test/DefaultInflationManager.t.sol +++ b/test/DefaultInflationManager.t.sol @@ -20,10 +20,8 @@ contract DefaultInflationManagerTest is Test { DefaultInflationManager public inflationManager; DefaultInflationManager public inflationManagerImplementation; - uint256 private constant _INTEREST_PER_SECOND_LOG2 = - 0.000000000914951192e18; - // precision accurary due to log2 approximation is upto the first 9 digits - uint256 private constant _MAX_PRECISION_DELTA = 1e21; + // precision accuracy due to log2 approximation is up to the first 5 digits + uint256 private constant _MAX_PRECISION_DELTA = 1e13; string[] internal inputs = new string[](4); @@ -157,14 +155,13 @@ contract DefaultInflationManagerTest is Test { function test_MintDelay(uint128 delay) external { vm.assume(delay <= 10 * 365 days); - uint256 elapsedTime = block.timestamp + delay; uint256 initialTotalSupply = polygon.totalSupply(); skip(delay); inflationManager.mint(); - inputs[2] = vm.toString(elapsedTime); + inputs[2] = vm.toString(delay); inputs[3] = vm.toString(initialTotalSupply); uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256)); @@ -187,13 +184,12 @@ contract DefaultInflationManagerTest is Test { function test_MintDelayTwice(uint128 delay) external { vm.assume(delay <= 5 * 365 days && delay > 0); - uint256 elapsedTime = block.timestamp + delay; uint256 initialTotalSupply = polygon.totalSupply(); skip(delay); inflationManager.mint(); - inputs[2] = vm.toString(elapsedTime); + inputs[2] = vm.toString(delay); inputs[3] = vm.toString(initialTotalSupply); uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256)); @@ -211,7 +207,7 @@ contract DefaultInflationManagerTest is Test { skip(delay); inflationManager.mint(); - inputs[2] = vm.toString(elapsedTime + delay); + inputs[2] = vm.toString(delay * 2); inputs[3] = vm.toString(initialTotalSupply); newSupply = abi.decode(vm.ffi(inputs), (uint256)); @@ -234,13 +230,12 @@ contract DefaultInflationManagerTest is Test { uint256 balance; for (uint256 cycle; cycle < cycles; cycle++) { - uint256 elapsedTime = block.timestamp + delay; uint256 initialTotalSupply = polygon.totalSupply(); skip(delay); inflationManager.mint(); - inputs[2] = vm.toString(elapsedTime); + inputs[2] = vm.toString(delay * (cycle + 1)); inputs[3] = vm.toString(initialTotalSupply); uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256)); diff --git a/test/util/calc.js b/test/util/calc.js index 386038a..2bec51f 100644 --- a/test/util/calc.js +++ b/test/util/calc.js @@ -1,12 +1,12 @@ -const interestRatePerSecond = 1.000000000634195839 -const startSupply = 10_000_000_000e18 +const interestRatePerYear = 1.02; +const startSupply = 10_000_000_000e18; function main() { - const [timeElapsedInSeconds] = process.argv.slice(2) + const [timeElapsedInSeconds] = process.argv.slice(2); - const supplyFactor = Math.pow(interestRatePerSecond, timeElapsedInSeconds) - const newSupply = BigInt(startSupply * supplyFactor) + const supplyFactor = Math.pow(interestRatePerYear, timeElapsedInSeconds / (365 * 24 * 60 * 60)); + const newSupply = BigInt(startSupply * supplyFactor); - console.log('0x' + newSupply.toString(16).padStart(64, '0')) // abi.encode(toMint) + console.log("0x" + newSupply.toString(16).padStart(64, "0")); // abi.encode(toMint) } main(); From ebe85ff0177232619ae4b1727847bb2ee63ed329 Mon Sep 17 00:00:00 2001 From: gretzke Date: Sat, 2 Sep 2023 00:08:29 +0200 Subject: [PATCH 13/14] set up prettier --- .github/workflows/test.yml | 72 +++++++++---------- .prettierignore | 5 ++ .prettierrc | 7 ++ README.md | 2 +- script/Deploy.s.sol | 22 ++---- src/DefaultInflationManager.sol | 14 +--- src/Polygon.sol | 16 ++--- src/PolygonMigration.sol | 18 +---- src/interfaces/IPolygonMigration.sol | 8 +-- test/DefaultInflationManager.t.sol | 104 ++++++--------------------- test/Polygon.t.sol | 52 +++----------- test/PolygonMigration.t.sol | 40 ++--------- test/SigUtils.t.sol | 14 +++- test/util/calc.js | 8 +-- 14 files changed, 115 insertions(+), 267 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eda48cf..5ea46b3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,43 +1,43 @@ name: test on: - push: - branches: [main, dev] - pull_request: - branches: [main, dev, feat/stakeManager] + push: + branches: [main, dev] + pull_request: + branches: [main, dev, feat/stakeManager] env: - FOUNDRY_PROFILE: ci + FOUNDRY_PROFILE: ci jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Get node.js - uses: actions/setup-node@v3 - with: - node-version: 18.x - - - name: Run Forge build - run: | - forge --version - forge build --sizes - id: build - - - name: Run Forge tests - # ! revert back to FOUNDRY_PROFILE=intense forge test -vvv - run: forge test -vvv - id: test + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Get node.js + uses: actions/setup-node@v3 + with: + node-version: 18.x + + - name: Run Forge build + run: | + forge --version + forge build --sizes + id: build + + - name: Run Forge tests + # ! revert back to FOUNDRY_PROFILE=intense forge test -vvv + run: forge test -vvv + id: test diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ae8848a --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +.prettierrc +foundry.toml +out +lib/ +cache/ \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..315f7cf --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 120, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": false +} diff --git a/README.md b/README.md index 6c55880..2e28a56 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# indicia \ No newline at end of file +# indicia diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 0125a02..b3ae428 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -15,12 +15,7 @@ contract Deploy is Script { deployerPrivateKey = vm.envUint("PRIVATE_KEY"); } - function run( - address matic, - address governance, - address treasury, - address stakeManager - ) public { + function run(address matic, address governance, address treasury, address stakeManager) public { vm.startBroadcast(deployerPrivateKey); address migrationImplementation = address(new PolygonMigration()); @@ -33,21 +28,12 @@ contract Deploy is Script { ) ); - address inflationManagerImplementation = address( - new DefaultInflationManager() - ); + address inflationManagerImplementation = address(new DefaultInflationManager()); address inflationManagerProxy = address( - new TransparentUpgradeableProxy( - address(inflationManagerImplementation), - governance, - "" - ) + new TransparentUpgradeableProxy(address(inflationManagerImplementation), governance, "") ); - Polygon polygonToken = new Polygon( - migrationProxy, - inflationManagerProxy - ); + Polygon polygonToken = new Polygon(migrationProxy, inflationManagerProxy); DefaultInflationManager(inflationManagerProxy).initialize( address(polygonToken), diff --git a/src/DefaultInflationManager.sol b/src/DefaultInflationManager.sol index e9c9d7d..61f662b 100644 --- a/src/DefaultInflationManager.sol +++ b/src/DefaultInflationManager.sol @@ -14,11 +14,7 @@ import {PowUtil} from "./lib/PowUtil.sol"; /// @notice A default inflation 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 /// @custom:security-contact security@polygon.technology -contract DefaultInflationManager is - Initializable, - Ownable2StepUpgradeable, - IDefaultInflationManager -{ +contract DefaultInflationManager is Initializable, Ownable2StepUpgradeable, IDefaultInflationManager { using SafeERC20 for IPolygon; // log2(2%pa continuously compounded inflation per year) in 18 decimals, see _inflatedSupplyAfter @@ -94,12 +90,8 @@ contract DefaultInflationManager is /// 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 inflation after timeElapsed - function _inflatedSupplyAfter( - uint256 timeElapsed - ) private pure returns (uint256 supply) { - uint256 supplyFactor = PowUtil.exp2( - (INTEREST_PER_YEAR_LOG2 * timeElapsed) / 365 days - ); + function _inflatedSupplyAfter(uint256 timeElapsed) private pure returns (uint256 supply) { + uint256 supplyFactor = PowUtil.exp2((INTEREST_PER_YEAR_LOG2 * timeElapsed) / 365 days); supply = (supplyFactor * START_SUPPLY) / 1e18; } diff --git a/src/Polygon.sol b/src/Polygon.sol index 21134c7..44a83e4 100644 --- a/src/Polygon.sol +++ b/src/Polygon.sol @@ -16,12 +16,8 @@ contract Polygon is ERC20Permit, IPolygon { uint256 public constant mintPerSecondCap = 0.0000000420e18; // 0.0000042% of POL Supply per second, in 18 decimals uint256 public lastMint; - constructor( - address migration_, - address inflationManager_ - ) ERC20("Polygon", "POL") ERC20Permit("Polygon") { - if (migration_ == address(0) || inflationManager_ == address(0)) - revert InvalidAddress(); + constructor(address migration_, address inflationManager_) ERC20("Polygon", "POL") ERC20Permit("Polygon") { + if (migration_ == address(0) || inflationManager_ == address(0)) revert InvalidAddress(); inflationManager = inflationManager_; _mint(migration_, 10_000_000_000e18); @@ -34,14 +30,10 @@ contract Polygon is ERC20Permit, IPolygon { function mint(address to, uint256 amount) external { if (msg.sender != inflationManager) revert OnlyInflationManager(); uint256 lastMintCache = lastMint; - if (lastMintCache == 0) - lastMintCache = IDefaultInflationManager(inflationManager) - .startTimestamp(); + if (lastMintCache == 0) lastMintCache = IDefaultInflationManager(inflationManager).startTimestamp(); uint256 timeElapsedSinceLastMint = block.timestamp - lastMintCache; - uint256 maxMint = (timeElapsedSinceLastMint * - mintPerSecondCap * - totalSupply()) / 1e18; + uint256 maxMint = (timeElapsedSinceLastMint * mintPerSecondCap * totalSupply()) / 1e18; if (amount > maxMint) revert MaxMintExceeded(maxMint, amount); lastMint = block.timestamp; diff --git a/src/PolygonMigration.sol b/src/PolygonMigration.sol index 41c735f..87043ed 100644 --- a/src/PolygonMigration.sol +++ b/src/PolygonMigration.sol @@ -39,8 +39,7 @@ contract PolygonMigration is Ownable2StepUpgradeable, IPolygonMigration { /// @notice This function allows owner/governance to set POL token address *only once* /// @param polygon_ Address of deployed POL token function setPolygonToken(address polygon_) external onlyOwner { - if (polygon_ == address(0) || address(polygon) != address(0)) - revert InvalidAddressOrAlreadySet(); + if (polygon_ == address(0) || address(polygon) != address(0)) revert InvalidAddressOrAlreadySet(); polygon = IERC20(polygon_); } @@ -66,10 +65,7 @@ contract PolygonMigration is Ownable2StepUpgradeable, IPolygonMigration { /// @notice This function allows for unmigrating POL tokens (from msg.sender) to MATIC tokens (to account) /// @param amount Amount of POL to migrate /// @param account Address to receive MATIC tokens - function unmigrateTo( - address account, - uint256 amount - ) external onlyUnmigrationUnlocked { + function unmigrateTo(address account, uint256 amount) external onlyUnmigrationUnlocked { emit Unmigrated(msg.sender, amount); polygon.safeTransferFrom(msg.sender, address(this), amount); @@ -87,15 +83,7 @@ contract PolygonMigration is Ownable2StepUpgradeable, IPolygonMigration { ) external onlyUnmigrationUnlocked { emit Unmigrated(msg.sender, amount); - IERC20Permit(address(polygon)).safePermit( - msg.sender, - address(this), - amount, - deadline, - v, - r, - s - ); + IERC20Permit(address(polygon)).safePermit(msg.sender, address(this), amount, deadline, v, r, s); polygon.safeTransferFrom(msg.sender, address(this), amount); matic.safeTransfer(msg.sender, amount); } diff --git a/src/interfaces/IPolygonMigration.sol b/src/interfaces/IPolygonMigration.sol index 57db513..4e4c253 100644 --- a/src/interfaces/IPolygonMigration.sol +++ b/src/interfaces/IPolygonMigration.sol @@ -16,11 +16,5 @@ interface IPolygonMigration { function unmigrateTo(address account, uint256 amount) external; - function unmigrateWithPermit( - uint256 amount, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; + function unmigrateWithPermit(uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; } diff --git a/test/DefaultInflationManager.t.sol b/test/DefaultInflationManager.t.sol index 45369ba..23c4915 100644 --- a/test/DefaultInflationManager.t.sol +++ b/test/DefaultInflationManager.t.sol @@ -31,23 +31,11 @@ contract DefaultInflationManagerTest is Test { governance = makeAddr("governance"); inflationManagerImplementation = new DefaultInflationManager(); inflationManager = DefaultInflationManager( - address( - new TransparentUpgradeableProxy( - address(inflationManagerImplementation), - msg.sender, - "" - ) - ) + address(new TransparentUpgradeableProxy(address(inflationManagerImplementation), msg.sender, "")) ); matic = new ERC20PresetMinterPauser("Matic Token", "MATIC"); migration = PolygonMigration( - address( - new TransparentUpgradeableProxy( - address(new PolygonMigration()), - msg.sender, - "" - ) - ) + address(new TransparentUpgradeableProxy(address(new PolygonMigration()), msg.sender, "")) ); migration.initialize(address(matic)); polygon = new Polygon(address(migration), address(inflationManager)); @@ -55,13 +43,7 @@ contract DefaultInflationManagerTest is Test { migration.transferOwnership(governance); vm.prank(governance); migration.acceptOwnership(); - inflationManager.initialize( - address(polygon), - address(migration), - stakeManager, - treasury, - governance - ); + inflationManager.initialize(address(polygon), address(migration), stakeManager, treasury, governance); // POL being inflationary, 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 @@ -75,13 +57,7 @@ contract DefaultInflationManagerTest is Test { function testRevert_Initialize() external { vm.expectRevert("Initializable: contract is already initialized"); - inflationManager.initialize( - address(0), - address(0), - address(0), - address(0), - address(0) - ); + inflationManager.initialize(address(0), address(0), address(0), address(0), address(0)); } function test_Deployment() external { @@ -89,10 +65,7 @@ contract DefaultInflationManagerTest is Test { assertEq(inflationManager.stakeManager(), stakeManager); assertEq(inflationManager.treasury(), treasury); assertEq(inflationManager.owner(), governance); - assertEq( - polygon.allowance(address(inflationManager), address(migration)), - type(uint256).max - ); + assertEq(polygon.allowance(address(inflationManager), address(migration)), type(uint256).max); assertEq(inflationManager.START_SUPPLY(), 10_000_000_000e18); assertEq(polygon.totalSupply(), 10_000_000_000e18); } @@ -108,32 +81,21 @@ contract DefaultInflationManagerTest is Test { params[seed % params.length] = address(0); // any one is zero addr address proxy = address( - new TransparentUpgradeableProxy( - address(new DefaultInflationManager()), - msg.sender, - "" - ) + new TransparentUpgradeableProxy(address(new DefaultInflationManager()), msg.sender, "") ); vm.expectRevert(InvalidAddress.selector); - DefaultInflationManager(proxy).initialize( - params[0], - params[1], - params[2], - params[3], - params[4] - ); + DefaultInflationManager(proxy).initialize(params[0], params[1], params[2], params[3], params[4]); } function test_ImplementationCannotBeInitialized() external { vm.expectRevert("Initializable: contract is already initialized"); - DefaultInflationManager(address(inflationManagerImplementation)) - .initialize( - address(0), - address(0), - address(0), - address(0), - address(0) - ); + DefaultInflationManager(address(inflationManagerImplementation)).initialize( + address(0), + address(0), + address(0), + address(0), + address(0) + ); vm.expectRevert("Initializable: contract is already initialized"); DefaultInflationManager(address(inflationManager)).initialize( address(0), @@ -165,20 +127,10 @@ contract DefaultInflationManagerTest is Test { inputs[3] = vm.toString(initialTotalSupply); uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256)); - assertApproxEqAbs( - newSupply, - polygon.totalSupply(), - _MAX_PRECISION_DELTA - ); - assertEq( - matic.balanceOf(stakeManager), - (polygon.totalSupply() - initialTotalSupply) / 2 - ); + assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA); + assertEq(matic.balanceOf(stakeManager), (polygon.totalSupply() - initialTotalSupply) / 2); assertEq(polygon.balanceOf(stakeManager), 0); - assertEq( - polygon.balanceOf(treasury), - (polygon.totalSupply() - initialTotalSupply) / 2 - ); + assertEq(polygon.balanceOf(treasury), (polygon.totalSupply() - initialTotalSupply) / 2); } function test_MintDelayTwice(uint128 delay) external { @@ -193,11 +145,7 @@ contract DefaultInflationManagerTest is Test { inputs[3] = vm.toString(initialTotalSupply); uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256)); - assertApproxEqAbs( - newSupply, - polygon.totalSupply(), - _MAX_PRECISION_DELTA - ); + assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA); uint256 balance = (polygon.totalSupply() - initialTotalSupply) / 2; assertEq(matic.balanceOf(stakeManager), balance); assertEq(polygon.balanceOf(stakeManager), 0); @@ -211,11 +159,7 @@ contract DefaultInflationManagerTest is Test { inputs[3] = vm.toString(initialTotalSupply); newSupply = abi.decode(vm.ffi(inputs), (uint256)); - assertApproxEqAbs( - newSupply, - polygon.totalSupply(), - _MAX_PRECISION_DELTA - ); + assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA); balance += (polygon.totalSupply() - initialTotalSupply) / 2; assertEq(matic.balanceOf(stakeManager), balance); assertEq(polygon.balanceOf(stakeManager), 0); @@ -223,9 +167,7 @@ contract DefaultInflationManagerTest is Test { } function test_MintDelayAfterNCycles(uint128 delay, uint8 cycles) external { - vm.assume( - delay * uint256(cycles) <= 10 * 365 days && delay > 0 && cycles < 30 - ); + vm.assume(delay * uint256(cycles) <= 10 * 365 days && delay > 0 && cycles < 30); uint256 balance; @@ -239,11 +181,7 @@ contract DefaultInflationManagerTest is Test { inputs[3] = vm.toString(initialTotalSupply); uint256 newSupply = abi.decode(vm.ffi(inputs), (uint256)); - assertApproxEqAbs( - newSupply, - polygon.totalSupply(), - _MAX_PRECISION_DELTA - ); + assertApproxEqAbs(newSupply, polygon.totalSupply(), _MAX_PRECISION_DELTA); balance += (polygon.totalSupply() - initialTotalSupply) / 2; assertEq(matic.balanceOf(stakeManager), balance); assertEq(polygon.balanceOf(stakeManager), 0); diff --git a/test/Polygon.t.sol b/test/Polygon.t.sol index e3a9540..8ed78ba 100644 --- a/test/Polygon.t.sol +++ b/test/Polygon.t.sol @@ -22,22 +22,10 @@ contract PolygonTest is Test { stakeManager = makeAddr("stakeManager"); matic = makeAddr("matic"); inflationManager = DefaultInflationManager( - address( - new TransparentUpgradeableProxy( - address(new DefaultInflationManager()), - msg.sender, - "" - ) - ) + address(new TransparentUpgradeableProxy(address(new DefaultInflationManager()), msg.sender, "")) ); polygon = new Polygon(migration, address(inflationManager)); - inflationManager.initialize( - address(polygon), - migration, - stakeManager, - treasury, - msg.sender - ); + inflationManager.initialize(address(polygon), migration, stakeManager, treasury, msg.sender); } function test_Deployment() external { @@ -61,11 +49,7 @@ contract PolygonTest is Test { token = new Polygon(address(0), address(0)); } - function testRevert_Mint( - address user, - address to, - uint256 amount - ) external { + function testRevert_Mint(address user, address to, uint256 amount) external { vm.assume(user != address(inflationManager)); vm.startPrank(user); vm.expectRevert(IPolygon.OnlyInflationManager.selector); @@ -74,40 +58,20 @@ contract PolygonTest is Test { function test_Mint(address to, uint256 amount) external { skip(1e8); // delay needed for a max mint of 10B - vm.assume( - to != address(0) && - amount <= 10000000000 * 10 ** 18 && - to != migration - ); + vm.assume(to != address(0) && amount <= 10000000000 * 10 ** 18 && to != migration); vm.prank(address(inflationManager)); polygon.mint(to, amount); assertEq(polygon.balanceOf(to), amount); } - function test_MintMaxExceeded( - address to, - uint256 amount, - uint256 delay - ) external { - vm.assume( - to != address(0) && - amount <= 10000000000 * 10 ** 18 && - to != migration && - delay < 10 * 365 days - ); + function test_MintMaxExceeded(address to, uint256 amount, uint256 delay) external { + vm.assume(to != address(0) && amount <= 10000000000 * 10 ** 18 && to != migration && delay < 10 * 365 days); skip(++delay); // avoid delay == 0 - uint256 maxMint = (mintPerSecondCap * delay * polygon.totalSupply()) / - 1e18; + uint256 maxMint = (mintPerSecondCap * delay * polygon.totalSupply()) / 1e18; if (amount > maxMint) - vm.expectRevert( - abi.encodeWithSelector( - IPolygon.MaxMintExceeded.selector, - maxMint, - amount - ) - ); + vm.expectRevert(abi.encodeWithSelector(IPolygon.MaxMintExceeded.selector, maxMint, amount)); vm.prank(address(inflationManager)); polygon.mint(to, amount); diff --git a/test/PolygonMigration.t.sol b/test/PolygonMigration.t.sol index dc9c95a..e5cb72b 100644 --- a/test/PolygonMigration.t.sol +++ b/test/PolygonMigration.t.sol @@ -51,13 +51,7 @@ contract PolygonMigrationTest is Test { function test_InvalidDeployment() external { PolygonMigration temp = PolygonMigration( - address( - new TransparentUpgradeableProxy( - address(new PolygonMigration()), - msg.sender, - "" - ) - ) + address(new TransparentUpgradeableProxy(address(new PolygonMigration()), msg.sender, "")) ); vm.expectRevert(IPolygonMigration.InvalidAddress.selector); temp.initialize(address(0)); @@ -68,10 +62,7 @@ contract PolygonMigrationTest is Test { function test_Migrate(address user, uint256 amount) external { vm.assume( - amount <= 10000000000 * 10 ** 18 && - user != address(0) && - user != address(migration) && - user != governance + amount <= 10000000000 * 10 ** 18 && user != address(0) && user != address(migration) && user != governance ); matic.mint(user, amount); vm.startPrank(user); @@ -98,11 +89,7 @@ contract PolygonMigrationTest is Test { vm.stopPrank(); } - function test_Unmigrate( - address user, - uint256 amount, - uint256 amount2 - ) external { + function test_Unmigrate(address user, uint256 amount, uint256 amount2) external { vm.assume( amount <= 10000000000 * 10 ** 18 && amount2 <= amount && @@ -130,10 +117,7 @@ contract PolygonMigrationTest is Test { function testRevert_Unmigrate(address user, uint256 amount) external { bool unmigrationLock = true; vm.assume( - amount <= 10000000000 * 10 ** 18 && - user != address(0) && - user != address(migration) && - user != governance + amount <= 10000000000 * 10 ** 18 && user != address(0) && user != address(migration) && user != governance ); matic.mint(user, amount); vm.startPrank(user); @@ -153,12 +137,7 @@ contract PolygonMigrationTest is Test { vm.stopPrank(); } - function test_UnmigrateTo( - address user, - address migrateTo, - uint256 amount, - uint256 amount2 - ) external { + function test_UnmigrateTo(address user, address migrateTo, uint256 amount, uint256 amount2) external { vm.assume( amount <= 10000000000 * 10 ** 18 && amount2 <= amount && @@ -187,16 +166,11 @@ contract PolygonMigrationTest is Test { assertEq(matic.balanceOf(migrateTo), amount2); } - function test_UnmigrateWithPermit( - uint256 privKey, - uint256 amount, - uint256 amount2 - ) external { + function test_UnmigrateWithPermit(uint256 privKey, uint256 amount, uint256 amount2) external { address user; vm.assume( privKey != 0 && - privKey < - 115792089237316195423570985008687907852837564279074904382605163141518161494337 && + privKey < 115792089237316195423570985008687907852837564279074904382605163141518161494337 && (user = vm.addr(privKey)) != address(migration) && user != governance && amount <= 10000000000 * 10 ** 18 && diff --git a/test/SigUtils.t.sol b/test/SigUtils.t.sol index a97165b..e93934f 100644 --- a/test/SigUtils.t.sol +++ b/test/SigUtils.t.sol @@ -21,9 +21,17 @@ contract SigUtils { // computes the hash of a permit function getStructHash(Permit memory _permit) internal pure returns (bytes32) { - return keccak256( - abi.encode(PERMIT_TYPEHASH, _permit.owner, _permit.spender, _permit.value, _permit.nonce, _permit.deadline) - ); + return + keccak256( + abi.encode( + PERMIT_TYPEHASH, + _permit.owner, + _permit.spender, + _permit.value, + _permit.nonce, + _permit.deadline + ) + ); } // computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer diff --git a/test/util/calc.js b/test/util/calc.js index 2bec51f..b5c2f19 100644 --- a/test/util/calc.js +++ b/test/util/calc.js @@ -1,12 +1,12 @@ const interestRatePerYear = 1.02; const startSupply = 10_000_000_000e18; function main() { - const [timeElapsedInSeconds] = process.argv.slice(2); + const [timeElapsedInSeconds] = process.argv.slice(2); - const supplyFactor = Math.pow(interestRatePerYear, timeElapsedInSeconds / (365 * 24 * 60 * 60)); - const newSupply = BigInt(startSupply * supplyFactor); + const supplyFactor = Math.pow(interestRatePerYear, timeElapsedInSeconds / (365 * 24 * 60 * 60)); + const newSupply = BigInt(startSupply * supplyFactor); - console.log("0x" + newSupply.toString(16).padStart(64, "0")); // abi.encode(toMint) + console.log("0x" + newSupply.toString(16).padStart(64, "0")); // abi.encode(toMint) } main(); From 4e8eb8af04a6d4b4f65e9c10688a8812fff4e658 Mon Sep 17 00:00:00 2001 From: gretzke Date: Sat, 2 Sep 2023 00:22:40 +0200 Subject: [PATCH 14/14] fix some slither stuff --- slither.config.json | 3 +++ src/DefaultInflationManager.sol | 6 +++--- src/Polygon.sol | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 slither.config.json diff --git a/slither.config.json b/slither.config.json new file mode 100644 index 0000000..3583960 --- /dev/null +++ b/slither.config.json @@ -0,0 +1,3 @@ +{ + "filter_paths": "(lib/|test/|script/)" +} diff --git a/src/DefaultInflationManager.sol b/src/DefaultInflationManager.sol index 61f662b..a4a3d39 100644 --- a/src/DefaultInflationManager.sol +++ b/src/DefaultInflationManager.sol @@ -75,12 +75,12 @@ contract DefaultInflationManager is Initializable, Ownable2StepUpgradeable, IDef uint256 treasuryAmt = amountToMint / 2; uint256 stakeManagerAmt = amountToMint - treasuryAmt; + emit TokenMint(amountToMint, msg.sender); + token.mint(address(this), amountToMint); - token.transfer(treasury, treasuryAmt); + token.safeTransfer(treasury, treasuryAmt); // backconvert POL to MATIC before sending to StakeManager migration.unmigrateTo(stakeManager, stakeManagerAmt); - - emit TokenMint(amountToMint, msg.sender); } /// @notice Returns total supply from compounded inflation after timeElapsed from startTimestamp (deployment) diff --git a/src/Polygon.sol b/src/Polygon.sol index 44a83e4..bedcfa4 100644 --- a/src/Polygon.sol +++ b/src/Polygon.sol @@ -12,8 +12,8 @@ import {IDefaultInflationManager} from "./interfaces/IDefaultInflationManager.so /// on hub and treasury requirements /// @custom:security-contact security@polygon.technology contract Polygon is ERC20Permit, IPolygon { + uint256 public constant MINT_PER_SECOND_CAP = 0.0000000420e18; // 0.0000042% of POL Supply per second, in 18 decimals address public immutable inflationManager; - uint256 public constant mintPerSecondCap = 0.0000000420e18; // 0.0000042% of POL Supply per second, in 18 decimals uint256 public lastMint; constructor(address migration_, address inflationManager_) ERC20("Polygon", "POL") ERC20Permit("Polygon") { @@ -33,7 +33,7 @@ contract Polygon is ERC20Permit, IPolygon { if (lastMintCache == 0) lastMintCache = IDefaultInflationManager(inflationManager).startTimestamp(); uint256 timeElapsedSinceLastMint = block.timestamp - lastMintCache; - uint256 maxMint = (timeElapsedSinceLastMint * mintPerSecondCap * totalSupply()) / 1e18; + uint256 maxMint = (timeElapsedSinceLastMint * MINT_PER_SECOND_CAP * totalSupply()) / 1e18; if (amount > maxMint) revert MaxMintExceeded(maxMint, amount); lastMint = block.timestamp;