Skip to content

Commit 05256df

Browse files
committed
feat: migrationAdmin 2-step transfer
1 parent ba8ef1a commit 05256df

15 files changed

+419
-97
lines changed

script/DeployBase.sol

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,19 @@ contract DeployBase {
4343
// Wrapped M Token Implementation constructor needs `earnerManagerProxy_`.
4444
// Wrapped M Token Proxy constructor needs `wrappedMTokenImplementation_`.
4545

46-
earnerManagerImplementation_ = address(new EarnerManager(registrar_, earnerManagerMigrationAdmin_));
46+
earnerManagerImplementation_ = address(new EarnerManager(registrar_));
4747

4848
earnerManagerProxy_ = address(new Proxy(earnerManagerImplementation_));
4949

50+
EarnerManager(earnerManagerProxy_).initialize(earnerManagerMigrationAdmin_);
51+
5052
wrappedMTokenImplementation_ = address(
51-
new WrappedMToken(mToken_, registrar_, earnerManagerProxy_, excessDestination_, wrappedMMigrationAdmin_)
53+
new WrappedMToken(mToken_, registrar_, earnerManagerProxy_, excessDestination_)
5254
);
5355

5456
wrappedMTokenProxy_ = address(new Proxy(wrappedMTokenImplementation_));
57+
58+
WrappedMToken(wrappedMTokenProxy_).initialize(wrappedMMigrationAdmin_);
5559
}
5660

5761
/**
@@ -88,15 +92,19 @@ contract DeployBase {
8892
// Wrapped M Token Implementation constructor needs `earnerManagerProxy_`.
8993
// Migrator needs `wrappedMTokenImplementation_` addresses.
9094

91-
earnerManagerImplementation_ = address(new EarnerManager(registrar_, earnerManagerMigrationAdmin_));
95+
earnerManagerImplementation_ = address(new EarnerManager(registrar_));
9296

9397
earnerManagerProxy_ = address(new Proxy(earnerManagerImplementation_));
9498

99+
EarnerManager(earnerManagerProxy_).initialize(earnerManagerMigrationAdmin_);
100+
95101
wrappedMTokenImplementation_ = address(
96-
new WrappedMToken(mToken_, registrar_, earnerManagerProxy_, excessDestination_, wrappedMMigrationAdmin_)
102+
new WrappedMToken(mToken_, registrar_, earnerManagerProxy_, excessDestination_)
97103
);
98104

99-
wrappedMTokenMigrator_ = address(new WrappedMTokenMigratorV1(wrappedMTokenImplementation_, earners_));
105+
wrappedMTokenMigrator_ = address(
106+
new WrappedMTokenMigratorV1(wrappedMTokenImplementation_, earners_, wrappedMMigrationAdmin_)
107+
);
100108
}
101109

102110
/**

src/EarnerManager.sol

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,29 +39,49 @@ contract EarnerManager is IEarnerManager, Migratable {
3939
/// @inheritdoc IEarnerManager
4040
address public immutable registrar;
4141

42+
/// @dev Used to check if the contract is the implementation or a proxy.
43+
address internal immutable _self = address(this);
44+
45+
/// @inheritdoc IEarnerManager
46+
address public migrationAdmin;
47+
4248
/// @inheritdoc IEarnerManager
43-
address public immutable migrationAdmin;
49+
address public pendingMigrationAdmin;
4450

4551
/// @dev Mapping of account to earner details.
4652
mapping(address account => EarnerDetails earnerDetails) internal _earnerDetails;
4753

4854
/* ============ Modifiers ============ */
4955

56+
modifier onlyMigrationAdmin() {
57+
_revertIfNotMigrationAdmin();
58+
_;
59+
}
60+
5061
modifier onlyAdmin() {
5162
_revertIfNotAdmin();
5263
_;
5364
}
5465

55-
/* ============ Constructor ============ */
66+
/* ============ Constructor and Initializer ============ */
5667

5768
/**
5869
* @dev Constructs the contract.
59-
* @param registrar_ The address of a Registrar contract.
60-
* @param migrationAdmin_ The address of a migration admin.
70+
* @param registrar_ The address of a Registrar contract.
6171
*/
62-
constructor(address registrar_, address migrationAdmin_) {
72+
constructor(address registrar_) {
6373
if ((registrar = registrar_) == address(0)) revert ZeroRegistrar();
64-
if ((migrationAdmin = migrationAdmin_) == address(0)) revert ZeroMigrationAdmin();
74+
}
75+
76+
/**
77+
* @dev Used by a proxy of this implementation to initialize its state.
78+
* @param migrationAdmin_ The address of a migration admin.
79+
*/
80+
function initialize(address migrationAdmin_) external {
81+
if (address(this) == _self) revert NotProxy();
82+
if (migrationAdmin != address(0)) revert AlreadyInitialized();
83+
84+
_setMigrationAdmin(migrationAdmin_);
6585
}
6686

6787
/* ============ Interactive Functions ============ */
@@ -95,12 +115,24 @@ contract EarnerManager is IEarnerManager, Migratable {
95115
/* ============ Temporary Admin Migration ============ */
96116

97117
/// @inheritdoc IEarnerManager
98-
function migrate(address migrator_) external {
99-
if (msg.sender != migrationAdmin) revert UnauthorizedMigration();
100-
118+
function migrate(address migrator_) external onlyMigrationAdmin {
101119
_migrate(migrator_);
102120
}
103121

122+
/// @inheritdoc IEarnerManager
123+
function setPendingMigrationAdmin(address migrationAdmin_) external onlyMigrationAdmin {
124+
emit PendingMigrationAdminSet(pendingMigrationAdmin = migrationAdmin_);
125+
}
126+
127+
/// @inheritdoc IEarnerManager
128+
function acceptMigrationAdmin() external {
129+
if (msg.sender != pendingMigrationAdmin) revert NotPendingMigrationAdmin();
130+
131+
_setMigrationAdmin(msg.sender);
132+
133+
delete pendingMigrationAdmin;
134+
}
135+
104136
/* ============ View/Pure Functions ============ */
105137

106138
/// @inheritdoc IEarnerManager
@@ -228,10 +260,13 @@ contract EarnerManager is IEarnerManager, Migratable {
228260
}
229261

230262
/**
231-
* @dev Reverts if the caller is not an admin.
263+
* @dev Sets the migration admin to `migrationAdmin_`.
264+
* @param migrationAdmin_ The address of the account to set as the migration admin.
232265
*/
233-
function _revertIfNotAdmin() internal view {
234-
if (!isAdmin(msg.sender)) revert NotAdmin();
266+
function _setMigrationAdmin(address migrationAdmin_) internal {
267+
if ((migrationAdmin = migrationAdmin_) == address(0)) revert ZeroMigrationAdmin();
268+
269+
emit MigrationAdminSet(migrationAdmin_);
235270
}
236271

237272
/* ============ Internal View/Pure Functions ============ */
@@ -255,4 +290,16 @@ contract EarnerManager is IEarnerManager, Migratable {
255290
function _isValidAdmin(address admin_) internal view returns (bool isValidAdmin_) {
256291
return (admin_ != address(0)) && isAdmin(admin_);
257292
}
293+
294+
/**
295+
* @dev Reverts if the caller is not an admin.
296+
*/
297+
function _revertIfNotAdmin() internal view {
298+
if (!isAdmin(msg.sender)) revert NotAdmin();
299+
}
300+
301+
/// @dev Reverts if the caller is not the migration admin.
302+
function _revertIfNotMigrationAdmin() internal view {
303+
if (msg.sender != migrationAdmin) revert NotMigrationAdmin();
304+
}
258305
}

src/WrappedMToken.sol

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,6 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended {
7171
/// @inheritdoc IWrappedMToken
7272
address public immutable earnerManager;
7373

74-
/// @inheritdoc IWrappedMToken
75-
address public immutable migrationAdmin;
76-
7774
/// @inheritdoc IWrappedMToken
7875
address public immutable mToken;
7976

@@ -83,6 +80,9 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended {
8380
/// @inheritdoc IWrappedMToken
8481
address public immutable excessDestination;
8582

83+
/// @dev Used to check if the contract is the implementation or a proxy.
84+
address internal immutable _self = address(this);
85+
8686
/// @inheritdoc IWrappedMToken
8787
uint112 public totalEarningPrincipal;
8888

@@ -100,29 +100,49 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended {
100100

101101
mapping(address account => address claimRecipient) internal _claimRecipients;
102102

103-
/* ============ Constructor ============ */
103+
/// @inheritdoc IWrappedMToken
104+
address public migrationAdmin;
105+
106+
/// @inheritdoc IWrappedMToken
107+
address public pendingMigrationAdmin;
108+
109+
/* ============ Modifiers ============ */
110+
111+
modifier onlyMigrationAdmin() {
112+
_revertIfNotMigrationAdmin();
113+
_;
114+
}
115+
116+
/* ============ Constructor and Initializer ============ */
104117

105118
/**
106119
* @dev Constructs the contract given an M Token address and migration admin.
107-
* Note that a proxy will not need to initialize since there are no mutable storage values affected.
108120
* @param mToken_ The address of an M Token.
109121
* @param registrar_ The address of a Registrar.
110122
* @param earnerManager_ The address of an Earner Manager.
111123
* @param excessDestination_ The address of an excess destination.
112-
* @param migrationAdmin_ The address of a migration admin.
113124
*/
114125
constructor(
115126
address mToken_,
116127
address registrar_,
117128
address earnerManager_,
118-
address excessDestination_,
119-
address migrationAdmin_
129+
address excessDestination_
120130
) ERC20Extended("M (Wrapped) by M^0", "wM", 6) {
121131
if ((mToken = mToken_) == address(0)) revert ZeroMToken();
122132
if ((registrar = registrar_) == address(0)) revert ZeroRegistrar();
123133
if ((earnerManager = earnerManager_) == address(0)) revert ZeroEarnerManager();
124134
if ((excessDestination = excessDestination_) == address(0)) revert ZeroExcessDestination();
125-
if ((migrationAdmin = migrationAdmin_) == address(0)) revert ZeroMigrationAdmin();
135+
}
136+
137+
/**
138+
* @dev Used by a proxy of this implementation to initialize its state.
139+
* @param migrationAdmin_ The address of a migration admin.
140+
*/
141+
function initialize(address migrationAdmin_) external {
142+
if (address(this) == _self) revert NotProxy();
143+
if (migrationAdmin != address(0)) revert AlreadyInitialized();
144+
145+
_setMigrationAdmin(migrationAdmin_);
126146
}
127147

128148
/* ============ Interactive Functions ============ */
@@ -266,12 +286,24 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended {
266286
/* ============ Temporary Admin Migration ============ */
267287

268288
/// @inheritdoc IWrappedMToken
269-
function migrate(address migrator_) external {
270-
if (msg.sender != migrationAdmin) revert UnauthorizedMigration();
271-
289+
function migrate(address migrator_) external onlyMigrationAdmin {
272290
_migrate(migrator_);
273291
}
274292

293+
/// @inheritdoc IWrappedMToken
294+
function setPendingMigrationAdmin(address migrationAdmin_) external onlyMigrationAdmin {
295+
emit PendingMigrationAdminSet(pendingMigrationAdmin = migrationAdmin_);
296+
}
297+
298+
/// @inheritdoc IWrappedMToken
299+
function acceptMigrationAdmin() external {
300+
if (msg.sender != pendingMigrationAdmin) revert NotPendingMigrationAdmin();
301+
302+
_setMigrationAdmin(msg.sender);
303+
304+
delete pendingMigrationAdmin;
305+
}
306+
275307
/* ============ View/Pure Functions ============ */
276308

277309
/// @inheritdoc IWrappedMToken
@@ -781,6 +813,16 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended {
781813
emit StoppedEarning(account_);
782814
}
783815

816+
/**
817+
* @dev Sets the migration admin to `migrationAdmin_`.
818+
* @param migrationAdmin_ The address of the account to set as the migration admin.
819+
*/
820+
function _setMigrationAdmin(address migrationAdmin_) internal {
821+
if ((migrationAdmin = migrationAdmin_) == address(0)) revert ZeroMigrationAdmin();
822+
823+
emit MigrationAdminSet(migrationAdmin_);
824+
}
825+
784826
/* ============ Internal View/Pure Functions ============ */
785827

786828
/// @dev Returns the current index of the M Token.
@@ -878,6 +920,11 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended {
878920
if (account_ == address(0)) revert InvalidRecipient(account_);
879921
}
880922

923+
/// @dev Reverts if the caller is not the migration admin.
924+
function _revertIfNotMigrationAdmin() internal view {
925+
if (msg.sender != migrationAdmin) revert NotMigrationAdmin();
926+
}
927+
881928
/**
882929
* @dev Reads the uint128 value at some index of an array of uint128 values whose storage pointer is given,
883930
* assuming the index is valid, without wasting gas checking for out-of-bounds errors.

src/WrappedMTokenMigratorV1.sol

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,21 @@ contract WrappedMTokenMigratorV1 {
4848

4949
address public immutable listOfEarnerToMigrate;
5050

51-
constructor(address newImplementation_, address[] memory earners_) {
52-
newImplementation = newImplementation_;
51+
address public immutable migrationAdmin;
52+
53+
constructor(address newImplementation_, address[] memory earners_, address migrationAdmin_) {
54+
if ((newImplementation = newImplementation_) == address(0)) revert();
5355

5456
listOfEarnerToMigrate = address(new ListOfEarnersToMigrate(earners_));
57+
58+
if ((migrationAdmin = migrationAdmin_) == address(0)) revert();
5559
}
5660

5761
fallback() external virtual {
5862
_migrateEarners();
5963

64+
_setMigrationAdmin(migrationAdmin);
65+
6066
address newImplementation_ = newImplementation;
6167

6268
assembly {
@@ -98,4 +104,14 @@ contract WrappedMTokenMigratorV1 {
98104
accounts_.slot := 6 // `_accounts` is slot 6 in v1.
99105
}
100106
}
107+
108+
/**
109+
* @dev Sets the `migrationAdmin` slot to `migrationAdmin_`.
110+
* @param migrationAdmin_ The address of the account to set the `migrationAdmin_` to.
111+
*/
112+
function _setMigrationAdmin(address migrationAdmin_) internal {
113+
assembly {
114+
sstore(9, migrationAdmin_) // `migrationAdmin` is slot 9 in v2.
115+
}
116+
}
101117
}

0 commit comments

Comments
 (0)