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); - } }