From a807adf4838c17d529e10a802ff5f9ecbc857a32 Mon Sep 17 00:00:00 2001 From: Michael De Luca Date: Tue, 2 Jul 2024 14:09:06 -0400 Subject: [PATCH] feat: Admin Migration --- src/Migratable.sol | 27 ++++++++++++++++++--------- src/WrappedMToken.sol | 13 ++++++++++++- src/interfaces/IMigratable.sol | 6 +++++- src/interfaces/IWrappedMToken.sol | 10 ++++++++++ test/Test.t.sol | 17 ++++++++++++++++- test/WrappedMToken.t.sol | 11 +++++++++-- test/utils/WrappedMTokenHarness.sol | 2 +- 7 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/Migratable.sol b/src/Migratable.sol index 88fe516..4207d21 100644 --- a/src/Migratable.sol +++ b/src/Migratable.sol @@ -14,15 +14,7 @@ abstract contract Migratable is IMigratable { /* ============ Interactive Functions ============ */ function migrate() external { - address migrator_ = _getMigrator(); - - if (migrator_ == address(0)) revert ZeroMigrator(); - - address oldImplementation_ = implementation(); - - migrator_.delegatecall(""); - - emit Migrate(migrator_, oldImplementation_, implementation()); + _migrate(_getMigrator()); } /* ============ View/Pure Functions ============ */ @@ -35,6 +27,23 @@ abstract contract Migratable is IMigratable { } } + /* ============ Internal Interactive Functions ============ */ + + function _migrate(address migrator_) internal { + if (migrator_ == address(0)) revert ZeroMigrator(); + + if (migrator_.code.length == 0) revert InvalidMigrator(); + + address oldImplementation_ = implementation(); + + migrator_.delegatecall(""); + + address newImplementation_ = implementation(); + + emit Migrated(migrator_, oldImplementation_, newImplementation_); + emit Upgraded(newImplementation_); + } + /* ============ Internal View/Pure Functions ============ */ function _getMigrator() internal view virtual returns (address migrator_); diff --git a/src/WrappedMToken.sol b/src/WrappedMToken.sol index 4e500ad..767362a 100644 --- a/src/WrappedMToken.sol +++ b/src/WrappedMToken.sol @@ -26,6 +26,7 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { bytes32 internal constant _CLAIM_OVERRIDE_RECIPIENT_PREFIX = "wm_claim_override_recipient"; bytes32 internal constant _MIGRATOR_V1_PREFIX = "wm_migrator_v1"; + address public immutable migrationAdmin; address public immutable mToken; address public immutable registrar; address public immutable vault; @@ -39,11 +40,13 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { /* ============ Constructor ============ */ - constructor(address mToken_) ERC20Extended("WrappedM by M^0", "wM", 6) { + constructor(address mToken_, address migrationAdmin_) ERC20Extended("WrappedM by M^0", "wM", 6) { if ((mToken = mToken_) == address(0)) revert ZeroMToken(); registrar = IMTokenLike(mToken_).ttgRegistrar(); vault = IRegistrarLike(registrar).vault(); + + if ((migrationAdmin = migrationAdmin_) == address(0)) revert ZeroMigrationAdmin(); } /* ============ Interactive Functions ============ */ @@ -110,6 +113,14 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { } } + /* ============ Temporary Admin Migration ============ */ + + function migrate(address migrator_) external { + if (msg.sender != migrationAdmin) revert UnauthorizedMigration(); + + _migrate(migrator_); + } + /* ============ View/Pure Functions ============ */ function accruedYieldOf(address account_) external view returns (uint240 yield_) { diff --git a/src/interfaces/IMigratable.sol b/src/interfaces/IMigratable.sol index acde4a3..5174524 100644 --- a/src/interfaces/IMigratable.sol +++ b/src/interfaces/IMigratable.sol @@ -3,7 +3,11 @@ pragma solidity 0.8.23; interface IMigratable { - event Migrate(address indexed migrator, address indexed oldImplementation, address indexed newImplementation); + event Migrated(address indexed migrator, address indexed oldImplementation, address indexed newImplementation); + + event Upgraded(address indexed implementation); + + error InvalidMigrator(); error ZeroMigrator(); diff --git a/src/interfaces/IWrappedMToken.sol b/src/interfaces/IWrappedMToken.sol index 2be2631..690d667 100644 --- a/src/interfaces/IWrappedMToken.sol +++ b/src/interfaces/IWrappedMToken.sol @@ -41,9 +41,15 @@ interface IWrappedMToken is IMigratable, IERC20Extended { /// @notice Emitted when calling `startEarning` for an account not approved as earner by TTG. error NotApprovedEarner(); + /// @notice Emitted when the non-governance migrate function is called by a account other than the migration admin. + error UnauthorizedMigration(); + /// @notice Emitted in constructor if M Token is 0x0. error ZeroMToken(); + /// @notice Emitted in constructor if Migration Admin is 0x0. + error ZeroMigrationAdmin(); + /* ============ Interactive Functions ============ */ function wrap(address recipient, uint256 amount) external; @@ -58,6 +64,10 @@ interface IWrappedMToken is IMigratable, IERC20Extended { function stopEarningFor(address account) external; + /* ============ Temporary Admin Migration ============ */ + + function migrate(address migrator_) external; + /* ============ View/Pure Functions ============ */ function accruedYieldOf(address account) external view returns (uint240 yield); diff --git a/test/Test.t.sol b/test/Test.t.sol index 166ecf7..7dd672c 100644 --- a/test/Test.t.sol +++ b/test/Test.t.sol @@ -48,6 +48,8 @@ contract Tests is Test { address internal _carol = makeAddr("carol"); address internal _dave = makeAddr("dave"); + address internal _migrationAdmin = makeAddr("migrationAdmin"); + address internal _vault = makeAddr("vault"); MockM internal _mToken; @@ -63,7 +65,7 @@ contract Tests is Test { _mToken.setCurrentIndex(_EXP_SCALED_ONE); _mToken.setTtgRegistrar(address(_registrar)); - _implementation = new WrappedMToken(address(_mToken)); + _implementation = new WrappedMToken(address(_mToken), _migrationAdmin); _wrappedMToken = IWrappedMToken(address(new Proxy(address(_implementation)))); } @@ -412,4 +414,17 @@ contract Tests is Test { assertEq(WrappedMTokenV2(address(_wrappedMToken)).foo(), 1); } + + function test_migration_fromAdmin() external { + WrappedMTokenV2 implementationV2_ = new WrappedMTokenV2(); + address migrator_ = address(new WrappedMTokenMigratorV1(address(implementationV2_))); + + vm.expectRevert(); + WrappedMTokenV2(address(_wrappedMToken)).foo(); + + vm.prank(_migrationAdmin); + _wrappedMToken.migrate(migrator_); + + assertEq(WrappedMTokenV2(address(_wrappedMToken)).foo(), 1); + } } diff --git a/test/WrappedMToken.t.sol b/test/WrappedMToken.t.sol index 56eec8f..bd4ef07 100644 --- a/test/WrappedMToken.t.sol +++ b/test/WrappedMToken.t.sol @@ -30,6 +30,8 @@ contract WrappedMTokenTests is Test { address internal _charlie = makeAddr("charlie"); address internal _david = makeAddr("david"); + address internal _migrationAdmin = makeAddr("migrationAdmin"); + address[] internal _accounts = [_alice, _bob, _charlie, _david]; address internal _vault = makeAddr("vault"); @@ -49,7 +51,7 @@ contract WrappedMTokenTests is Test { _mToken.setCurrentIndex(_EXP_SCALED_ONE); _mToken.setTtgRegistrar(address(_registrar)); - _implementation = new WrappedMTokenHarness(address(_mToken)); + _implementation = new WrappedMTokenHarness(address(_mToken), _migrationAdmin); _wrappedMToken = WrappedMTokenHarness(address(new Proxy(address(_implementation)))); @@ -67,7 +69,12 @@ contract WrappedMTokenTests is Test { function test_constructor_zeroMToken() external { vm.expectRevert(IWrappedMToken.ZeroMToken.selector); - new WrappedMTokenHarness(address(0)); + new WrappedMTokenHarness(address(0), address(0)); + } + + function test_constructor_zeroMigrationAdmin() external { + vm.expectRevert(IWrappedMToken.ZeroMigrationAdmin.selector); + new WrappedMTokenHarness(address(_mToken), address(0)); } /* ============ wrap ============ */ diff --git a/test/utils/WrappedMTokenHarness.sol b/test/utils/WrappedMTokenHarness.sol index a77d861..13051a0 100644 --- a/test/utils/WrappedMTokenHarness.sol +++ b/test/utils/WrappedMTokenHarness.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.23; import { WrappedMToken } from "../../src/WrappedMToken.sol"; contract WrappedMTokenHarness is WrappedMToken { - constructor(address mToken_) WrappedMToken(mToken_) {} + constructor(address mToken_, address migrationAdmin_) WrappedMToken(mToken_, migrationAdmin_) {} function setIsEarningOf(address account_, bool isEarning_) external { (, uint128 index_, , uint240 balance_) = _getBalanceInfo(account_);