From 8d556198a6b451cf5ef472244850f614d4183e56 Mon Sep 17 00:00:00 2001 From: 0xIryna Date: Sun, 11 May 2025 20:07:37 -0700 Subject: [PATCH 1/8] chore: upgrade common --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index 4c88b3f0..a3d5c2cb 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 4c88b3f012beda49cd29e25642c123f6a2e97616 +Subproject commit a3d5c2cb9b752250c50117bc0728cecec44860ec From aa9f90b8b0cb36b5039c24413641d4d23ae78d33 Mon Sep 17 00:00:00 2001 From: 0xIryna Date: Mon, 12 May 2025 21:20:03 -0700 Subject: [PATCH 2/8] chore: add OZ --- .gitmodules | 4 ++++ lib/openzeppelin-contracts | 1 + 2 files changed, 5 insertions(+) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index fb7b8472..e1457877 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,7 @@ [submodule "lib/common"] path = lib/common url = git@github.com:m0-foundation/common.git +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = git@github.com:OpenZeppelin/openzeppelin-contracts.git + branch = release-v5.3 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 00000000..e4f70216 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit e4f70216d759d8e6a64144a9e1f7bbeed78e7079 From c8cf27cef9add278013a95016450f7f5b9c70f1f Mon Sep 17 00:00:00 2001 From: 0xIryna Date: Tue, 13 May 2025 08:34:36 -0700 Subject: [PATCH 3/8] feat: add upgradability --- script/Deploy.s.sol | 7 ++- script/DeployBase.sol | 38 +++++++++++---- src/MToken.sol | 33 ++++++++++++- src/abstract/ContinuousIndexing.sol | 10 ++-- src/interfaces/IMToken.sol | 18 ++++++++ test/Deploy.t.sol | 49 +++++++++++++------- test/MToken.t.sol | 16 +++++-- test/Migrate.t.sol | 72 +++++++++++++++++++++++++++++ test/utils/MTokenHarness.sol | 2 +- 9 files changed, 208 insertions(+), 37 deletions(-) create mode 100644 test/Migrate.t.sol diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index d5c04c0b..58eb41e6 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -12,15 +12,18 @@ contract Deploy is Script, DeployBase { function run() external { (address deployer_, ) = deriveRememberKey(vm.envString("MNEMONIC"), 0); + address migrationAdmin_ = vm.envAddress("MIGRATION_ADMIN"); console2.log("Deployer:", deployer_); + console2.log("Migration Admin:", migrationAdmin_); vm.startBroadcast(deployer_); - address mToken_ = deploy(_REGISTRAR); + (address implementation_, address proxy_) = deploy(_REGISTRAR, migrationAdmin_); vm.stopBroadcast(); - console2.log("M Token address:", mToken_); + console2.log("M Token Implementation address:", implementation_); + console2.log("M Token Proxy address:", proxy_); } } diff --git a/script/DeployBase.sol b/script/DeployBase.sol index b902d071..5f73575a 100644 --- a/script/DeployBase.sol +++ b/script/DeployBase.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.26; +import { ERC1967Proxy } from "../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { ContractHelper } from "../lib/common/src/libs/ContractHelper.sol"; import { MToken } from "../src/MToken.sol"; @@ -9,23 +10,42 @@ import { MToken } from "../src/MToken.sol"; contract DeployBase { /** * @dev Deploys the M Token contract. - * @param registrar_ The address of the Registrar contract. - * @return mToken_ The address of the deployed M Token contract. + * @param registrar_ The address of the Registrar contract. + * @param migrationAdmin_ The address of a migration admin. + * @return implementation_ The address of the deployed M Token implementation. + * @return proxy_ The address of the deployed M Token proxy. */ - function deploy(address registrar_) public virtual returns (address mToken_) { - // M token needs `registrar_` addresses. - return address(new MToken(registrar_)); + function deploy(address registrar_, address migrationAdmin_) public virtual returns (address implementation_, address proxy_) { + implementation_ = address(new MToken(registrar_, migrationAdmin_)); + proxy_ = address(new ERC1967Proxy(implementation_, abi.encodeCall(MToken.initialize, ()))); } - function _getExpectedMToken(address deployer_, uint256 deployerNonce_) internal pure returns (address) { + function _getExpectedMTokenImplementation( + address deployer_, + uint256 deployerNonce_ + ) internal pure returns (address) { return ContractHelper.getContractFrom(deployer_, deployerNonce_); } - function getExpectedMToken(address deployer_, uint256 deployerNonce_) public pure virtual returns (address) { - return _getExpectedMToken(deployer_, deployerNonce_); + function getExpectedMTokenImplementation( + address deployer_, + uint256 deployerNonce_ + ) public pure virtual returns (address) { + return _getExpectedMTokenImplementation(deployer_, deployerNonce_); + } + + function _getExpectedMTokenProxy(address deployer_, uint256 deployerNonce_) internal pure returns (address) { + return ContractHelper.getContractFrom(deployer_, deployerNonce_ + 1); + } + + function getExpectedMTokenProxy( + address deployer_, + uint256 deployerNonce_ + ) public pure virtual returns (address) { + return _getExpectedMTokenProxy(deployer_, deployerNonce_); } function getDeployerNonceAfterMTokenDeployment(uint256 deployerNonce_) public pure virtual returns (uint256) { - return deployerNonce_ + 1; + return deployerNonce_ + 2; } } diff --git a/src/MToken.sol b/src/MToken.sol index 85d20cc2..6bdf7780 100644 --- a/src/MToken.sol +++ b/src/MToken.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.26; import { ERC20Extended } from "../lib/common/src/ERC20Extended.sol"; import { UIntMath } from "../lib/common/src/libs/UIntMath.sol"; +import { Migratable } from "../lib/common/src/Migratable.sol"; import { IERC20 } from "../lib/common/src/interfaces/IERC20.sol"; @@ -20,7 +21,7 @@ import { ContinuousIndexingMath } from "./libs/ContinuousIndexingMath.sol"; * @author M^0 Labs * @notice ERC20 M Token living on other chains. */ -contract MToken is IMToken, ContinuousIndexing, ERC20Extended { +contract MToken is IMToken, ContinuousIndexing, ERC20Extended, Migratable { /* ============ Structs ============ */ /** @@ -41,6 +42,9 @@ contract MToken is IMToken, ContinuousIndexing, ERC20Extended { /// @inheritdoc IMToken address public immutable registrar; + /// @inheritdoc IMToken + address public immutable migrationAdmin; + /// @inheritdoc IMToken uint240 public totalNonEarningSupply; @@ -62,11 +66,21 @@ contract MToken is IMToken, ContinuousIndexing, ERC20Extended { /** * @notice Constructs the M Token contract. + * @dev Sets immutable storage. * @param registrar_ The address of the Registrar contract. + * @param migrationAdmin_ The address of a migration admin. */ - constructor(address registrar_) ContinuousIndexing() ERC20Extended("M by M^0", "M", 6) { + constructor(address registrar_, address migrationAdmin_) ContinuousIndexing() ERC20Extended("M by M^0", "M", 6) { if ((registrar = registrar_) == address(0)) revert ZeroRegistrar(); if ((portal = RegistrarReader.getPortal(registrar_)) == address(0)) revert ZeroPortal(); + if ((migrationAdmin = migrationAdmin_) == address(0)) revert ZeroMigrationAdmin(); + } + + /* ============ Initializer ============ */ + + /// @inheritdoc IMToken + function initialize() external initializer { + _initialize(); } /* ============ Interactive Functions ============ */ @@ -112,6 +126,15 @@ contract MToken is IMToken, ContinuousIndexing, ERC20Extended { _stopEarning(account_); } + /** + * @dev Performs the contract migration by calling `migrator_`. + * @param migrator_ The address of a migrator contract. + */ + function migrate(address migrator_) external { + if (msg.sender != migrationAdmin) revert UnauthorizedMigration(); + _migrate(migrator_); + } + /* ============ View/Pure Functions ============ */ /// @inheritdoc IMToken @@ -438,4 +461,10 @@ contract MToken is IMToken, ContinuousIndexing, ERC20Extended { function _revertIfNotPortal() internal view { if (msg.sender != portal) revert NotPortal(); } + + /// @inheritdoc Migratable + function _getMigrator() internal pure override returns (address migrator_) { + // NOTE: in this version only the admin-controlled migration via `migrate()` function is supported + return address(0); + } } diff --git a/src/abstract/ContinuousIndexing.sol b/src/abstract/ContinuousIndexing.sol index 6498c467..17d458c4 100644 --- a/src/abstract/ContinuousIndexing.sol +++ b/src/abstract/ContinuousIndexing.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.26; +import { Initializable } from "../../lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol"; + import { IContinuousIndexing } from "../interfaces/IContinuousIndexing.sol"; import { ContinuousIndexingMath } from "../libs/ContinuousIndexingMath.sol"; @@ -10,7 +12,7 @@ import { ContinuousIndexingMath } from "../libs/ContinuousIndexingMath.sol"; * @title Abstract Continuous Indexing Contract to handle index updates in inheriting contracts. * @author M^0 Labs */ -abstract contract ContinuousIndexing is IContinuousIndexing { +abstract contract ContinuousIndexing is IContinuousIndexing, Initializable { /* ============ Variables ============ */ /// @inheritdoc IContinuousIndexing @@ -19,10 +21,10 @@ abstract contract ContinuousIndexing is IContinuousIndexing { /// @inheritdoc IContinuousIndexing uint40 public latestUpdateTimestamp; - /* ============ Constructor ============ */ + /* ============ Initializer ============ */ - /// @notice Constructs the ContinuousIndexing contract. - constructor() { + /// @notice Initializes Proxy's storage. + function _initialize() internal onlyInitializing { latestIndex = ContinuousIndexingMath.EXP_SCALED_ONE; latestUpdateTimestamp = uint40(block.timestamp); } diff --git a/src/interfaces/IMToken.sol b/src/interfaces/IMToken.sol index e06db571..eaccee32 100644 --- a/src/interfaces/IMToken.sol +++ b/src/interfaces/IMToken.sol @@ -50,12 +50,18 @@ interface IMToken is IContinuousIndexing, IERC20Extended { /// @notice Emitted when principal of total supply (earning and non-earning) will overflow a `type(uint112).max`. error OverflowsPrincipalOfTotalSupply(); + /// @notice Emitted when the migrate function is called by a account other than the migration admin. + error UnauthorizedMigration(); + /// @notice Emitted in constructor if the Portal address in the Registrar is 0x0. error ZeroPortal(); /// @notice Emitted in constructor if the Registrar address is 0x0. error ZeroRegistrar(); + /// @notice Emitted in constructor if Migration Admin is 0x0. + error ZeroMigrationAdmin(); + /* ============ Interactive Functions ============ */ /** @@ -102,6 +108,15 @@ interface IMToken is IContinuousIndexing, IERC20Extended { */ function stopEarning(address account) external; + /// @notice Initializes the Proxy's storage. + function initialize() external; + + /** + * @notice Performs an arbitrarily defined migration. + * @param migrator The address of a migrator contract. + */ + function migrate(address migrator) external; + /* ============ View/Pure Functions ============ */ /// @notice The address of the M Portal contract. @@ -110,6 +125,9 @@ interface IMToken is IContinuousIndexing, IERC20Extended { /// @notice The address of the Registrar contract. function registrar() external view returns (address); + /// @notice The account that can call the `migrate(address migrator)` function. + function migrationAdmin() external view returns (address migrationAdmin); + /** * @notice The principal of an earner M token balance. * @param account The account to get the principal balance of. diff --git a/test/Deploy.t.sol b/test/Deploy.t.sol index 88d0fc16..b4bc2852 100644 --- a/test/Deploy.t.sol +++ b/test/Deploy.t.sol @@ -2,31 +2,48 @@ pragma solidity 0.8.26; -import { Test, console2 } from "../lib/forge-std/src/Test.sol"; +import { Test } from "../lib/forge-std/src/Test.sol"; import { IMToken } from "../src/interfaces/IMToken.sol"; +import { IRegistrar } from "../src/interfaces/IRegistrar.sol"; import { DeployBase } from "../script/DeployBase.sol"; -import { MockRegistrar } from "./utils/Mocks.sol"; - contract Deploy is Test, DeployBase { - MockRegistrar internal _registrar; + address internal constant _EXPECTED_PROXY = 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b; + address internal constant _DEPLOYER = 0xF2f1ACbe0BA726fEE8d75f3E32900526874740BB; - address internal _portal = makeAddr("portal"); + address internal immutable _MIGRATION_ADMIN = makeAddr("migration admin"); + address internal immutable _REGISTRAR = makeAddr("registrar"); + address internal immutable _PORTAL = makeAddr("portal"); - function setUp() external { - _registrar = new MockRegistrar(); - _registrar.setPortal(_portal); - } + uint64 internal constant _DEPLOYER_PROXY_NONCE = 8; function test_deploy() external { - address mToken_ = deploy(address(_registrar)); - - assertEq(mToken_, getExpectedMToken(address(this), 2)); - - // MToken assertions - assertEq(IMToken(mToken_).portal(), _portal); - assertEq(IMToken(mToken_).registrar(), address(_registrar)); + vm.mockCall( + _REGISTRAR, + abi.encodeWithSelector(IRegistrar.portal.selector), + abi.encode(_PORTAL) + ); + + // Set nonce to 1 before `_DEPLOYER_PROXY_NONCE` since implementation is deployed before proxy. + vm.setNonce(_DEPLOYER, _DEPLOYER_PROXY_NONCE - 1); + + vm.startPrank(_DEPLOYER); + (address implementation_, address proxy_) = deploy(_REGISTRAR, _MIGRATION_ADMIN); + vm.stopPrank(); + + // M Token Implementation assertions + assertEq(implementation_, getExpectedMTokenImplementation(_DEPLOYER, 7)); + assertEq(IMToken(implementation_).migrationAdmin(), _MIGRATION_ADMIN); + assertEq(IMToken(implementation_).registrar(), _REGISTRAR); + assertEq(IMToken(implementation_).portal(), _PORTAL); + + // M Token Proxy assertions + assertEq(proxy_, getExpectedMTokenProxy(_DEPLOYER, 7)); + assertEq(proxy_, _EXPECTED_PROXY); + assertEq(IMToken(proxy_).migrationAdmin(), _MIGRATION_ADMIN); + assertEq(IMToken(proxy_).registrar(), _REGISTRAR); + assertEq(IMToken(proxy_).portal(), _PORTAL); } } diff --git a/test/MToken.t.sol b/test/MToken.t.sol index 5b4d681f..892e0aaa 100644 --- a/test/MToken.t.sol +++ b/test/MToken.t.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.26; import { IERC20Extended } from "../lib/common/src/interfaces/IERC20Extended.sol"; import { UIntMath } from "../lib/common/src/libs/UIntMath.sol"; +import { ERC1967Proxy } from "../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { IContinuousIndexing } from "../src/interfaces/IContinuousIndexing.sol"; import { IMToken } from "../src/interfaces/IMToken.sol"; @@ -26,6 +27,7 @@ contract MTokenTests is TestUtils { address internal _portal = makeAddr("portal"); + address internal _migrationAdmin = makeAddr("migrationAdmin"); address[] internal _accounts = [_alice, _bob, _charlie, _david]; uint256 internal _start = vm.getBlockTimestamp(); @@ -33,13 +35,16 @@ contract MTokenTests is TestUtils { uint128 internal _expectedCurrentIndex; MockRegistrar internal _registrar; + + MTokenHarness internal _implementation; MTokenHarness internal _mToken; function setUp() external { _registrar = new MockRegistrar(); _registrar.setPortal(_portal); - _mToken = new MTokenHarness(address(_registrar)); + _implementation = new MTokenHarness(address(_registrar), _migrationAdmin); + _mToken = MTokenHarness(address(new ERC1967Proxy(address(_implementation), abi.encodeCall(IMToken.initialize, ())))); _mToken.setLatestIndex(_expectedCurrentIndex = 1_100000068703); } @@ -55,14 +60,19 @@ contract MTokenTests is TestUtils { /* ============ constructor ============ */ function test_constructor_zeroRegistrar() external { vm.expectRevert(IMToken.ZeroRegistrar.selector); - new MTokenHarness(address(0)); + new MTokenHarness(address(0), _migrationAdmin); + } + + function test_constructor_zeroMigrationAdmin() external { + vm.expectRevert(IMToken.ZeroMigrationAdmin.selector); + new MTokenHarness(address(_registrar), address(0)); } function test_constructor_zeroPortal() external { _registrar.setPortal(address(0)); vm.expectRevert(IMToken.ZeroPortal.selector); - new MTokenHarness(address(_registrar)); + new MTokenHarness(address(_registrar), _migrationAdmin); } /* ============ mint ============ */ diff --git a/test/Migrate.t.sol b/test/Migrate.t.sol new file mode 100644 index 00000000..9b2929a1 --- /dev/null +++ b/test/Migrate.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.26; + +import { Test } from "../lib/forge-std/src/Test.sol"; +import { ERC1967Proxy } from "../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +import { IMToken } from "../src/interfaces/IMToken.sol"; +import { IRegistrar } from "../src/interfaces/IRegistrar.sol"; + +import { MToken } from "../src/MToken.sol"; + +contract MTokenV2 { + function foo() external pure returns (uint256) { + return 1; + } +} + +contract MTokenMigratorV1 { + bytes32 private constant _IMPLEMENTATION_SLOT = + bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc); + + address public immutable implementationV2; + + constructor(address implementationV2_) { + implementationV2 = implementationV2_; + } + + fallback() external virtual { + bytes32 slot_ = _IMPLEMENTATION_SLOT; + address implementationV2_ = implementationV2; + + assembly { + sstore(slot_, implementationV2_) + } + } +} + +contract MigrationTests is Test { + bytes32 internal constant _MIGRATOR_V1_PREFIX = "m_migrator_v1"; + + address internal _migrationAdmin = makeAddr("migrationAdmin"); + address internal _registrar = makeAddr("registrar"); + address internal _portal = makeAddr("portal"); + + MToken internal _implementation; + MToken internal _mToken; + + function setUp() external { + vm.mockCall( + _registrar, + abi.encodeWithSelector(IRegistrar.portal.selector), + abi.encode(_portal) + ); + + _implementation = new MToken(_registrar, _migrationAdmin); + _mToken = MToken(address(new ERC1967Proxy(address(_implementation), abi.encodeCall(IMToken.initialize, ())))); + } + + function test_migration() external { + MTokenV2 implementationV2_ = new MTokenV2(); + address migrator_ = address(new MTokenMigratorV1(address(implementationV2_))); + + vm.expectRevert(); + MTokenV2(address(_mToken)).foo(); + + vm.prank(_migrationAdmin); + _mToken.migrate(migrator_); + + assertEq(MTokenV2(address(_mToken)).foo(), 1); + } +} diff --git a/test/utils/MTokenHarness.sol b/test/utils/MTokenHarness.sol index 9d470255..f59e1de6 100644 --- a/test/utils/MTokenHarness.sol +++ b/test/utils/MTokenHarness.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.26; import { MToken } from "../../src/MToken.sol"; contract MTokenHarness is MToken { - constructor(address registrar_) MToken(registrar_) {} + constructor(address registrar_, address migrationAdmin_) MToken(registrar_, migrationAdmin_) {} function setLatestIndex(uint256 index_) external { latestIndex = uint128(index_); From 31426f4b39253ffe823f31e8776580ddf151e22d Mon Sep 17 00:00:00 2001 From: 0xIryna Date: Tue, 13 May 2025 08:41:18 -0700 Subject: [PATCH 4/8] feat: disableInitializers in Implementation constructor --- src/MToken.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MToken.sol b/src/MToken.sol index 6bdf7780..a2ca6f54 100644 --- a/src/MToken.sol +++ b/src/MToken.sol @@ -71,6 +71,8 @@ contract MToken is IMToken, ContinuousIndexing, ERC20Extended, Migratable { * @param migrationAdmin_ The address of a migration admin. */ constructor(address registrar_, address migrationAdmin_) ContinuousIndexing() ERC20Extended("M by M^0", "M", 6) { + _disableInitializers(); + if ((registrar = registrar_) == address(0)) revert ZeroRegistrar(); if ((portal = RegistrarReader.getPortal(registrar_)) == address(0)) revert ZeroPortal(); if ((migrationAdmin = migrationAdmin_) == address(0)) revert ZeroMigrationAdmin(); From 02af9de28172333e9952878b64637eb35acfa7f9 Mon Sep 17 00:00:00 2001 From: 0xIryna Date: Tue, 20 May 2025 18:21:57 -0700 Subject: [PATCH 5/8] chore: pass portal as an argument --- src/MToken.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/MToken.sol b/src/MToken.sol index a2ca6f54..97df9e09 100644 --- a/src/MToken.sol +++ b/src/MToken.sol @@ -67,14 +67,15 @@ contract MToken is IMToken, ContinuousIndexing, ERC20Extended, Migratable { /** * @notice Constructs the M Token contract. * @dev Sets immutable storage. - * @param registrar_ The address of the Registrar contract. + * @param registrar_ The address of the Registrar contract. + * @param portal_ The address of the Portal contract. * @param migrationAdmin_ The address of a migration admin. */ - constructor(address registrar_, address migrationAdmin_) ContinuousIndexing() ERC20Extended("M by M^0", "M", 6) { + constructor(address registrar_, address portal_, address migrationAdmin_) ContinuousIndexing() ERC20Extended("M by M^0", "M", 6) { _disableInitializers(); if ((registrar = registrar_) == address(0)) revert ZeroRegistrar(); - if ((portal = RegistrarReader.getPortal(registrar_)) == address(0)) revert ZeroPortal(); + if ((portal = portal_) == address(0)) revert ZeroPortal(); if ((migrationAdmin = migrationAdmin_) == address(0)) revert ZeroMigrationAdmin(); } From 6b253f7395a43110b81adcc43bd590d48d4c0f3a Mon Sep 17 00:00:00 2001 From: 0xIryna Date: Tue, 7 Oct 2025 10:45:44 -0700 Subject: [PATCH 6/8] chore: MToken name update M^0 -> M0 --- src/MToken.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MToken.sol b/src/MToken.sol index 97df9e09..3f107592 100644 --- a/src/MToken.sol +++ b/src/MToken.sol @@ -71,7 +71,7 @@ contract MToken is IMToken, ContinuousIndexing, ERC20Extended, Migratable { * @param portal_ The address of the Portal contract. * @param migrationAdmin_ The address of a migration admin. */ - constructor(address registrar_, address portal_, address migrationAdmin_) ContinuousIndexing() ERC20Extended("M by M^0", "M", 6) { + constructor(address registrar_, address portal_, address migrationAdmin_) ContinuousIndexing() ERC20Extended("M by M0", "M", 6) { _disableInitializers(); if ((registrar = registrar_) == address(0)) revert ZeroRegistrar(); From da5ddc0da4d255d3062670417c0641b2b0163363 Mon Sep 17 00:00:00 2001 From: 0xIryna Date: Tue, 7 Oct 2025 10:49:22 -0700 Subject: [PATCH 7/8] chore: upgrade --- lib/common | 2 +- lib/forge-std | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/common b/lib/common index a3d5c2cb..66657d98 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit a3d5c2cb9b752250c50117bc0728cecec44860ec +Subproject commit 66657d984bb77ba83863dd4607704594a6c610bc diff --git a/lib/forge-std b/lib/forge-std index 8f24d6b0..ff47d405 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa +Subproject commit ff47d4052a6018d9e5419e5cf013b16ff8006aae From 2ea2235d67ca5a6feeb596c0fa4e8f99de050b94 Mon Sep 17 00:00:00 2001 From: 0xIryna Date: Tue, 7 Oct 2025 10:57:11 -0700 Subject: [PATCH 8/8] fix: pass Portal as a parameter in tests --- script/Deploy.s.sol | 3 ++- script/DeployBase.sol | 5 +++-- test/Deploy.t.sol | 8 +------- test/MToken.t.sol | 10 ++++------ test/Migrate.t.sol | 8 +------- test/utils/MTokenHarness.sol | 2 +- 6 files changed, 12 insertions(+), 24 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 58eb41e6..c4560bc4 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -9,6 +9,7 @@ import { DeployBase } from "./DeployBase.sol"; contract Deploy is Script, DeployBase { // NOTE: Ensure this is the correct Registrar testnet/mainnet address. address internal constant _REGISTRAR = 0x0000000000000000000000000000000000000000; + address internal constant _PORTAL = 0x0000000000000000000000000000000000000000; function run() external { (address deployer_, ) = deriveRememberKey(vm.envString("MNEMONIC"), 0); @@ -19,7 +20,7 @@ contract Deploy is Script, DeployBase { vm.startBroadcast(deployer_); - (address implementation_, address proxy_) = deploy(_REGISTRAR, migrationAdmin_); + (address implementation_, address proxy_) = deploy(_REGISTRAR, _PORTAL, migrationAdmin_); vm.stopBroadcast(); diff --git a/script/DeployBase.sol b/script/DeployBase.sol index 5f73575a..10cd4d23 100644 --- a/script/DeployBase.sol +++ b/script/DeployBase.sol @@ -11,12 +11,13 @@ contract DeployBase { /** * @dev Deploys the M Token contract. * @param registrar_ The address of the Registrar contract. + * @param portal_ The address of the Portal contract. * @param migrationAdmin_ The address of a migration admin. * @return implementation_ The address of the deployed M Token implementation. * @return proxy_ The address of the deployed M Token proxy. */ - function deploy(address registrar_, address migrationAdmin_) public virtual returns (address implementation_, address proxy_) { - implementation_ = address(new MToken(registrar_, migrationAdmin_)); + function deploy(address registrar_, address portal_, address migrationAdmin_) public virtual returns (address implementation_, address proxy_) { + implementation_ = address(new MToken(registrar_, portal_, migrationAdmin_)); proxy_ = address(new ERC1967Proxy(implementation_, abi.encodeCall(MToken.initialize, ()))); } diff --git a/test/Deploy.t.sol b/test/Deploy.t.sol index b4bc2852..785fbcc5 100644 --- a/test/Deploy.t.sol +++ b/test/Deploy.t.sol @@ -20,17 +20,11 @@ contract Deploy is Test, DeployBase { uint64 internal constant _DEPLOYER_PROXY_NONCE = 8; function test_deploy() external { - vm.mockCall( - _REGISTRAR, - abi.encodeWithSelector(IRegistrar.portal.selector), - abi.encode(_PORTAL) - ); - // Set nonce to 1 before `_DEPLOYER_PROXY_NONCE` since implementation is deployed before proxy. vm.setNonce(_DEPLOYER, _DEPLOYER_PROXY_NONCE - 1); vm.startPrank(_DEPLOYER); - (address implementation_, address proxy_) = deploy(_REGISTRAR, _MIGRATION_ADMIN); + (address implementation_, address proxy_) = deploy(_REGISTRAR, _PORTAL, _MIGRATION_ADMIN); vm.stopPrank(); // M Token Implementation assertions diff --git a/test/MToken.t.sol b/test/MToken.t.sol index 892e0aaa..2a610a76 100644 --- a/test/MToken.t.sol +++ b/test/MToken.t.sol @@ -43,7 +43,7 @@ contract MTokenTests is TestUtils { _registrar = new MockRegistrar(); _registrar.setPortal(_portal); - _implementation = new MTokenHarness(address(_registrar), _migrationAdmin); + _implementation = new MTokenHarness(address(_registrar), _portal, _migrationAdmin); _mToken = MTokenHarness(address(new ERC1967Proxy(address(_implementation), abi.encodeCall(IMToken.initialize, ())))); _mToken.setLatestIndex(_expectedCurrentIndex = 1_100000068703); @@ -60,19 +60,17 @@ contract MTokenTests is TestUtils { /* ============ constructor ============ */ function test_constructor_zeroRegistrar() external { vm.expectRevert(IMToken.ZeroRegistrar.selector); - new MTokenHarness(address(0), _migrationAdmin); + new MTokenHarness(address(0), _portal, _migrationAdmin); } function test_constructor_zeroMigrationAdmin() external { vm.expectRevert(IMToken.ZeroMigrationAdmin.selector); - new MTokenHarness(address(_registrar), address(0)); + new MTokenHarness(address(_registrar), _portal, address(0)); } function test_constructor_zeroPortal() external { - _registrar.setPortal(address(0)); - vm.expectRevert(IMToken.ZeroPortal.selector); - new MTokenHarness(address(_registrar), _migrationAdmin); + new MTokenHarness(address(_registrar), address(0), _migrationAdmin); } /* ============ mint ============ */ diff --git a/test/Migrate.t.sol b/test/Migrate.t.sol index 9b2929a1..4d339d4e 100644 --- a/test/Migrate.t.sol +++ b/test/Migrate.t.sol @@ -47,13 +47,7 @@ contract MigrationTests is Test { MToken internal _mToken; function setUp() external { - vm.mockCall( - _registrar, - abi.encodeWithSelector(IRegistrar.portal.selector), - abi.encode(_portal) - ); - - _implementation = new MToken(_registrar, _migrationAdmin); + _implementation = new MToken(_registrar, _portal, _migrationAdmin); _mToken = MToken(address(new ERC1967Proxy(address(_implementation), abi.encodeCall(IMToken.initialize, ())))); } diff --git a/test/utils/MTokenHarness.sol b/test/utils/MTokenHarness.sol index f59e1de6..99b40905 100644 --- a/test/utils/MTokenHarness.sol +++ b/test/utils/MTokenHarness.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.26; import { MToken } from "../../src/MToken.sol"; contract MTokenHarness is MToken { - constructor(address registrar_, address migrationAdmin_) MToken(registrar_, migrationAdmin_) {} + constructor(address registrar_, address portal_, address migrationAdmin_) MToken(registrar_, portal_, migrationAdmin_) {} function setLatestIndex(uint256 index_) external { latestIndex = uint128(index_);