From d25d55a51fa564f73e687a9b3aec0b596c66bc68 Mon Sep 17 00:00:00 2001 From: Michael De Luca <35537333+deluca-mike@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:39:53 -0400 Subject: [PATCH] feat: post-deploy v2 prep - solc 0.8.26 - latest common - latest forge-std - registrar as constructor arg - excess destination as constructor arg - constants made public - token name changed - batch start and stop earning - IsApprovedEarner and NotApprovedEarner errors have account as parameter - Indexing Math no longer needed? - Basic Migrator contract introduced - version bump - use more from common - zero account checks feat: post-deploy v2 prep - solc 0.8.26 - latest common - latest forge-std - registrar as constructor arg - excess destination as constructor arg - constants made public - token name changed - `IsApprovedEarner` and `NotApprovedEarner` errors have account as parameter - basic Migrator contract introduced - version bump - use more from common - more test coverage - fixed scripts to allow for generic deploy and mainnet upgrade --- .env.deploy.example | 15 + .env.example | 8 +- .env.upgrade.example | 8 + .prettierrc | 2 +- .solhint.json | 2 +- Makefile | 7 +- foundry.toml | 8 +- lib/common | 2 +- lib/forge-std | 2 +- package.json | 2 +- script/Deploy.s.sol | 76 +++ script/DeployBase.sol | 84 ++- script/DeployProduction.s.sol | 74 --- script/DeployUpgradeMainnet.s.sol | 85 +++ src/Migratable.sol | 61 -- src/MigratorV1.sol | 26 + src/Proxy.sol | 47 -- src/WrappedMToken.sol | 272 +++++--- src/interfaces/IMTokenLike.sol | 7 +- src/interfaces/IMigratable.sol | 44 -- src/interfaces/IRegistrarLike.sol | 5 +- src/interfaces/IWrappedMToken.sol | 69 +- src/libs/IndexingMath.sol | 97 --- test/Migration.t.sol | 102 --- test/integration/Deploy.t.sol | 37 +- test/integration/MorphoBlue.t.sol | 622 +++--------------- test/integration/Protocol.t.sol | 459 +++++++------ test/integration/TestBase.sol | 82 ++- test/integration/UniswapV3.t.sol | 475 +++++-------- test/integration/Upgrade.t.sol | 55 ++ .../vendor/morpho-blue/Interfaces.sol | 54 +- .../vendor/morpho-blue/MorphoTestBase.sol | 215 ++++++ .../vendor/protocol/Interfaces.sol | 2 +- .../vendor/uniswap-v3/Interfaces.sol | 2 +- test/integration/vendor/uniswap-v3/Utils.sol | 5 +- test/unit/Migration.t.sol | 75 +++ test/{ => unit}/Stories.t.sol | 52 +- test/{ => unit}/WrappedMToken.t.sol | 557 +++++++++++----- test/utils/Invariants.sol | 5 +- test/utils/Mocks.sol | 14 +- test/utils/WrappedMTokenHarness.sol | 9 +- 41 files changed, 1896 insertions(+), 1929 deletions(-) create mode 100644 .env.deploy.example create mode 100644 .env.upgrade.example create mode 100644 script/Deploy.s.sol delete mode 100644 script/DeployProduction.s.sol create mode 100644 script/DeployUpgradeMainnet.s.sol delete mode 100644 src/Migratable.sol create mode 100644 src/MigratorV1.sol delete mode 100644 src/Proxy.sol delete mode 100644 src/interfaces/IMigratable.sol delete mode 100644 src/libs/IndexingMath.sol delete mode 100644 test/Migration.t.sol create mode 100644 test/integration/Upgrade.t.sol create mode 100644 test/integration/vendor/morpho-blue/MorphoTestBase.sol create mode 100644 test/unit/Migration.t.sol rename test/{ => unit}/Stories.t.sol (90%) rename test/{ => unit}/WrappedMToken.t.sol (69%) diff --git a/.env.deploy.example b/.env.deploy.example new file mode 100644 index 0000000..68d74d4 --- /dev/null +++ b/.env.deploy.example @@ -0,0 +1,15 @@ +# Deploy script environment variables +PRIVATE_KEY= # Private key of the deployer +DEPLOYER= # Address the deployer +DEPLOYER_PROXY_NONCE= # Nonce of the deployer when creating the Wrapped M proxy +EXPECTED_PROXY= # Address of the expected Wrapped M proxy +M_TOKEN= # Address of the M token +REGISTRAR= # Address of the Registrar +EXCESS_DESTINATION= # Address of the Excess Destination +MIGRATION_ADMIN= # Address of the Migration Admin + +# RPC URL to deploy to +DEPLOY_RPC_URL= + +# Used for verifying contracts on Etherscan +ETHERSCAN_API_KEY= diff --git a/.env.example b/.env.example index 8086609..33b10f2 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,11 @@ # Localhost RPC URL -export LOCALHOST_RPC_URL=http://127.0.0.1:8545 +LOCALHOST_RPC_URL=http://127.0.0.1:8545 # Mainnet RPC URLs -export MAINNET_RPC_URL= +MAINNET_RPC_URL= # Testnet RPC URLs -export SEPOLIA_RPC_URL= +SEPOLIA_RPC_URL= # Used for verifying contracts on Etherscan -export ETHERSCAN_API_KEY= +ETHERSCAN_API_KEY= diff --git a/.env.upgrade.example b/.env.upgrade.example new file mode 100644 index 0000000..c613760 --- /dev/null +++ b/.env.upgrade.example @@ -0,0 +1,8 @@ +# Mainnet upgrade script environment variables +PRIVATE_KEY= # Private key of the deployer + +# Mainnet RPC URL to perform upgrade +MAINNET_RPC_URL= + +# Used for verifying contracts on Etherscan +ETHERSCAN_API_KEY= diff --git a/.prettierrc b/.prettierrc index 954e185..14d1c71 100644 --- a/.prettierrc +++ b/.prettierrc @@ -7,7 +7,7 @@ "files": "*.sol", "options": { "bracketSpacing": true, - "compiler": "0.8.23", + "compiler": "0.8.26", "parser": "solidity-parse", "printWidth": 120, "tabWidth": 4, diff --git a/.solhint.json b/.solhint.json index 3c7c11c..fa3074b 100644 --- a/.solhint.json +++ b/.solhint.json @@ -13,7 +13,7 @@ ], "compiler-version": [ "error", - "0.8.23" + "0.8.26" ], "comprehensive-interface": "off", "const-name-snakecase": "off", diff --git a/Makefile b/Makefile index d155784..83f834b 100644 --- a/Makefile +++ b/Makefile @@ -9,10 +9,13 @@ update:; forge update # Deployment helpers deploy: - FOUNDRY_PROFILE=production forge script script/DeployProduction.s.sol --skip src --skip test --rpc-url mainnet --slow --broadcast -vvv --verify + FOUNDRY_PROFILE=production MAINNET_RPC_URL=$(DEPLOY_RPC_URL) forge script script/Deploy.s.sol --skip src --skip test --slow --broadcast -vvv --verify --show-standard-json-input deploy-local: - FOUNDRY_PROFILE=production forge script script/DeployProduction.s.sol --skip src --skip test --rpc-url localhost --slow --broadcast -vvv + FOUNDRY_PROFILE=production forge script script/Deploy.s.sol --skip src --skip test --rpc-url localhost --slow --broadcast -vvv + +deploy-upgrade: + FOUNDRY_PROFILE=production forge script script/DeployUpgradeMainnet.s.sol --skip src --skip test --rpc-url mainnet --slow --broadcast -vvv --verify --show-standard-json-input # Run slither slither : diff --git a/foundry.toml b/foundry.toml index 016467e..b014462 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,15 +2,13 @@ gas_reports = ["*"] gas_reports_ignore = [] ignored_error_codes = [] -solc_version = "0.8.23" +solc_version = "0.8.26" optimizer = true optimizer_runs = 999999 verbosity = 3 -block_number = 20_270_778 -block_timestamp = 1_720_550_400 -fork_block_number = 20_270_778 +fork_block_number = 21_345_650 rpc_storage_caching = { chains = ["mainnet"], endpoints = "all" } -evm_version = "shanghai" +evm_version = "cancun" [profile.production] build_info = true diff --git a/lib/common b/lib/common index 45aa01d..3692db1 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 45aa01db3acf6189e6b26ada5cd5ac8351837b56 +Subproject commit 3692db150ad90b21d7c213ea535f34792ad8873f diff --git a/lib/forge-std b/lib/forge-std index bf66061..b93cf4b 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit bf6606142994b1e47e2882ce0cd477c020d77623 +Subproject commit b93cf4bc34ff214c099dc970b153f85ade8c9f66 diff --git a/package.json b/package.json index 2082010..e5826db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mzero-labs/wrapped-m-token", - "version": "1.0.0", + "version": "2.0.0", "description": "Wrapped M Token", "author": "M^0 Labs ", "repository": { diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..338a359 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.26; + +import { Script, console2 } from "../lib/forge-std/src/Script.sol"; + +import { DeployBase } from "./DeployBase.sol"; + +contract DeployProduction is Script, DeployBase { + error DeployerMismatch(address expected, address actual); + + error DeployerNonceTooHigh(); + + error UnexpectedDeployerNonce(); + + error CurrentNonceMismatch(uint64 expected, uint64 actual); + + error ExpectedProxyMismatch(address expected, address actual); + + error ResultingProxyMismatch(address expected, address actual); + + function run() external { + address deployer_ = vm.rememberKey(vm.envUint("PRIVATE_KEY")); + address expectedDeployer_ = vm.envAddress("DEPLOYER"); + + uint64 deployerProxyNonce_ = uint64(vm.envUint("DEPLOYER_PROXY_NONCE")); + + address expectedProxy_ = vm.envAddress("EXPECTED_PROXY"); + + console2.log("Deployer:", deployer_); + + if (deployer_ != expectedDeployer_) revert DeployerMismatch(expectedDeployer_, deployer_); + + uint64 currentNonce_ = vm.getNonce(deployer_); + + uint64 startNonce_ = currentNonce_; + address implementation_; + address proxy_; + + while (true) { + if (startNonce_ > deployerProxyNonce_) revert DeployerNonceTooHigh(); + + (implementation_, proxy_) = mockDeploy(deployer_, startNonce_); + + if (proxy_ == expectedProxy_) break; + + ++startNonce_; + } + + vm.startBroadcast(deployer_); + + // Burn nonces until to `currentNonce_ == startNonce_`. + while (currentNonce_ < startNonce_) { + payable(deployer_).transfer(0); + ++currentNonce_; + } + + if (currentNonce_ != vm.getNonce(deployer_)) revert CurrentNonceMismatch(currentNonce_, vm.getNonce(deployer_)); + + if (currentNonce_ != startNonce_) revert UnexpectedDeployerNonce(); + + (implementation_, proxy_) = deploy( + vm.envAddress("M_TOKEN"), + vm.envAddress("REGISTRAR"), + vm.envAddress("EXCESS_DESTINATION"), + vm.envAddress("MIGRATION_ADMIN") + ); + + vm.stopBroadcast(); + + console2.log("Wrapped M Implementation address:", implementation_); + console2.log("Migrator address:", proxy_); + + if (proxy_ != expectedProxy_) revert ResultingProxyMismatch(expectedProxy_, proxy_); + } +} diff --git a/script/DeployBase.sol b/script/DeployBase.sol index 857b601..c4e1325 100644 --- a/script/DeployBase.sol +++ b/script/DeployBase.sol @@ -1,57 +1,91 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; +pragma solidity 0.8.26; import { ContractHelper } from "../lib/common/src/libs/ContractHelper.sol"; +import { Proxy } from "../lib/common/src/Proxy.sol"; +import { MigratorV1 } from "../src/MigratorV1.sol"; import { WrappedMToken } from "../src/WrappedMToken.sol"; -import { Proxy } from "../src/Proxy.sol"; contract DeployBase { /** * @dev Deploys Wrapped M Token. - * @param mToken_ The address the M Token contract. - * @param migrationAdmin_ The address the Migration Admin. - * @return implementation_ The address of the deployed Wrapped M Token implementation. - * @return proxy_ The address of the deployed Wrapped M Token proxy. + * @param mToken_ The address of the M Token contract. + * @param registrar_ The address of the Registrar contract. + * @param excessDestination_ The address of the excess destination. + * @param migrationAdmin_ The address of the Migration Admin. + * @return implementation_ The address of the deployed Wrapped M Token implementation. + * @return proxy_ The address of the deployed Wrapped M Token proxy. */ function deploy( address mToken_, + address registrar_, + address excessDestination_, address migrationAdmin_ ) public virtual returns (address implementation_, address proxy_) { - // Wrapped M token needs `mToken_` and `migrationAdmin_` addresses. + // Wrapped M token needs `mToken_`, `registrar_`, `excessDestination_`, and `migrationAdmin_` addresses. // Proxy needs `implementation_` addresses. - implementation_ = address(new WrappedMToken(mToken_, migrationAdmin_)); + implementation_ = address(new WrappedMToken(mToken_, registrar_, excessDestination_, migrationAdmin_)); proxy_ = address(new Proxy(implementation_)); } - function _getExpectedWrappedMTokenImplementation( - address deployer_, - uint256 deployerNonce_ - ) internal pure returns (address) { - return ContractHelper.getContractFrom(deployer_, deployerNonce_); + /** + * @dev Deploys Wrapped M Token components needed to upgrade an existing Wrapped M proxy. + * @param mToken_ The address of the M Token contract. + * @param registrar_ The address of the Registrar contract. + * @param excessDestination_ The address of the excess destination. + * @param migrationAdmin_ The address of the Migration Admin. + * @return implementation_ The address of the deployed Wrapped M Token implementation. + * @return migrator_ The address of the deployed Migrator. + */ + function deployUpgrade( + address mToken_, + address registrar_, + address excessDestination_, + address migrationAdmin_ + ) public virtual returns (address implementation_, address migrator_) { + // Wrapped M token needs `mToken_`, `registrar_`, `excessDestination_`, and `migrationAdmin_` addresses. + // Migrator needs `implementation_` addresses. + + implementation_ = address(new WrappedMToken(mToken_, registrar_, excessDestination_, migrationAdmin_)); + migrator_ = address(new MigratorV1(implementation_)); } - function getExpectedWrappedMTokenImplementation( + /** + * @dev Mock deploys Wrapped M Token, returning the would-be addresses. + * @param deployer_ The address of the deployer. + * @param deployerNonce_ The nonce of the deployer. + * @return implementation_ The address of the would-be Wrapped M Token implementation. + * @return proxy_ The address of the would-be Wrapped M Token proxy. + */ + function mockDeploy( address deployer_, uint256 deployerNonce_ - ) public pure virtual returns (address) { - return _getExpectedWrappedMTokenImplementation(deployer_, deployerNonce_); - } + ) public view virtual returns (address implementation_, address proxy_) { + // Wrapped M token needs `mToken_`, `registrar_`, `excessDestination_`, and `migrationAdmin_` addresses. + // Proxy needs `implementation_` addresses. - function _getExpectedWrappedMTokenProxy(address deployer_, uint256 deployerNonce_) internal pure returns (address) { - return ContractHelper.getContractFrom(deployer_, deployerNonce_ + 1); + implementation_ = ContractHelper.getContractFrom(deployer_, deployerNonce_); + proxy_ = ContractHelper.getContractFrom(deployer_, deployerNonce_ + 1); } - function getExpectedWrappedMTokenProxy( + /** + * @dev Mock deploys Wrapped M Token, returning the would-be addresses. + * @param deployer_ The address of the deployer. + * @param deployerNonce_ The nonce of the deployer. + * @return implementation_ The address of the would-be Wrapped M Token implementation. + * @return migrator_ The address of the would-be Migrator. + */ + function mockDeployUpgrade( address deployer_, uint256 deployerNonce_ - ) public pure virtual returns (address) { - return _getExpectedWrappedMTokenProxy(deployer_, deployerNonce_); - } + ) public view virtual returns (address implementation_, address migrator_) { + // Wrapped M token needs `mToken_`, `registrar_`, `excessDestination_`, and `migrationAdmin_` addresses. + // Migrator needs `implementation_` addresses. - function getDeployerNonceAfterProtocolDeployment(uint256 deployerNonce_) public pure virtual returns (uint256) { - return deployerNonce_ + 2; + implementation_ = ContractHelper.getContractFrom(deployer_, deployerNonce_); + migrator_ = ContractHelper.getContractFrom(deployer_, deployerNonce_ + 1); } } diff --git a/script/DeployProduction.s.sol b/script/DeployProduction.s.sol deleted file mode 100644 index 1e9ae7e..0000000 --- a/script/DeployProduction.s.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.23; - -import { Script, console2 } from "../lib/forge-std/src/Script.sol"; - -import { DeployBase } from "./DeployBase.sol"; - -contract DeployProduction is Script, DeployBase { - error DeployerMismatch(address expected, address actual); - - error DeployerNonceTooHigh(); - - error UnexpectedDeployerNonce(); - - error CurrentNonceMismatch(uint64 expected, uint64 actual); - - error ExpectedProxyMismatch(address expected, address actual); - - error ResultingProxyMismatch(address expected, address actual); - - // NOTE: Ensure this is the correct M Token testnet/mainnet address. - address internal constant _M_TOKEN = 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b; - - // NOTE: Ensure this is the correct Migration Admin testnet/mainnet address. - address internal constant _MIGRATION_ADMIN = 0x431169728D75bd02f4053435b87D15c8d1FB2C72; - - // NOTE: Ensure this is the correct deployer testnet/mainnet to use. - address internal constant _EXPECTED_DEPLOYER = 0xF2f1ACbe0BA726fEE8d75f3E32900526874740BB; - - // NOTE: Ensure this is the correct nonce to use to deploy the Proxy on testnet/mainnet. - uint64 internal constant _DEPLOYER_PROXY_NONCE = 40; - - // NOTE: Ensure this is the correct expected testnet/mainnet address for the Proxy. - address internal constant _EXPECTED_PROXY = 0x437cc33344a0B27A429f795ff6B469C72698B291; - - function run() external { - address deployer_ = vm.rememberKey(vm.envUint("PRIVATE_KEY")); - - console2.log("Deployer:", deployer_); - - if (deployer_ != _EXPECTED_DEPLOYER) revert DeployerMismatch(_EXPECTED_DEPLOYER, deployer_); - - uint64 currentNonce_ = vm.getNonce(deployer_); - uint64 startNonce_ = _DEPLOYER_PROXY_NONCE - 1; - - if (currentNonce_ >= startNonce_) revert DeployerNonceTooHigh(); - - address expectedProxy_ = getExpectedWrappedMTokenProxy(deployer_, startNonce_); - - if (expectedProxy_ != _EXPECTED_PROXY) revert ExpectedProxyMismatch(_EXPECTED_PROXY, expectedProxy_); - - vm.startBroadcast(deployer_); - - // Burn nonces until to 1 before `_DEPLOYER_PROXY_NONCE` since implementation is deployed before proxy. - while (currentNonce_ < startNonce_) { - payable(deployer_).transfer(0); - ++currentNonce_; - } - - if (currentNonce_ != vm.getNonce(deployer_)) revert CurrentNonceMismatch(currentNonce_, vm.getNonce(deployer_)); - - if (currentNonce_ != startNonce_) revert UnexpectedDeployerNonce(); - - (address implementation_, address proxy_) = deploy(_M_TOKEN, _MIGRATION_ADMIN); - - vm.stopBroadcast(); - - console2.log("Wrapped M Implementation address:", implementation_); - console2.log("Wrapped M Proxy address:", proxy_); - - if (proxy_ != _EXPECTED_PROXY) revert ResultingProxyMismatch(_EXPECTED_PROXY, proxy_); - } -} diff --git a/script/DeployUpgradeMainnet.s.sol b/script/DeployUpgradeMainnet.s.sol new file mode 100644 index 0000000..060e563 --- /dev/null +++ b/script/DeployUpgradeMainnet.s.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.26; + +import { Script, console2 } from "../lib/forge-std/src/Script.sol"; + +import { DeployBase } from "./DeployBase.sol"; + +contract DeployUpgradeMainnet is Script, DeployBase { + error DeployerMismatch(address expected, address actual); + + error DeployerNonceTooHigh(); + + error UnexpectedDeployerNonce(); + + error CurrentNonceMismatch(uint64 expected, uint64 actual); + + error ResultingMigratorMismatch(address expected, address actual); + + address internal constant _REGISTRAR = 0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c; + + // NOTE: Ensure this is the correct Excess Destination mainnet address. + address internal constant _EXCESS_DESTINATION = 0xd7298f620B0F752Cf41BD818a16C756d9dCAA34f; // Vault + + address internal constant _M_TOKEN = 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b; + + // NOTE: Ensure this is the correct Migration Admin mainnet address. + address internal constant _MIGRATION_ADMIN = 0x431169728D75bd02f4053435b87D15c8d1FB2C72; + + address internal constant _PROXY = 0x437cc33344a0B27A429f795ff6B469C72698B291; // Mainnet address for the Proxy. + + // NOTE: Ensure this is the correct mainnet deployer to use. + address internal constant _EXPECTED_DEPLOYER = 0xF2f1ACbe0BA726fEE8d75f3E32900526874740BB; + + // NOTE: Ensure this is the correct nonce to use to deploy the Migrator on mainnet. + uint64 internal constant _DEPLOYER_MIGRATOR_NONCE = 40; + + // NOTE: Ensure this is the correct expected mainnet address for the Migrator. + address internal constant _EXPECTED_MIGRATOR = address(0); + + function run() external { + address deployer_ = vm.rememberKey(vm.envUint("PRIVATE_KEY")); + + console2.log("Deployer:", deployer_); + + if (deployer_ != _EXPECTED_DEPLOYER) revert DeployerMismatch(_EXPECTED_DEPLOYER, deployer_); + + uint64 currentNonce_ = vm.getNonce(deployer_); + + uint64 startNonce_ = currentNonce_; + address implementation_; + address migrator_; + + while (true) { + if (startNonce_ > _DEPLOYER_MIGRATOR_NONCE) revert DeployerNonceTooHigh(); + + (implementation_, migrator_) = mockDeployUpgrade(deployer_, startNonce_); + + if (migrator_ == _EXPECTED_MIGRATOR) break; + + ++startNonce_; + } + + vm.startBroadcast(deployer_); + + // Burn nonces until to `currentNonce_ == startNonce_`. + while (currentNonce_ < startNonce_) { + payable(deployer_).transfer(0); + ++currentNonce_; + } + + if (currentNonce_ != vm.getNonce(deployer_)) revert CurrentNonceMismatch(currentNonce_, vm.getNonce(deployer_)); + + if (currentNonce_ != startNonce_) revert UnexpectedDeployerNonce(); + + (implementation_, migrator_) = deployUpgrade(_M_TOKEN, _REGISTRAR, _EXCESS_DESTINATION, _MIGRATION_ADMIN); + + vm.stopBroadcast(); + + console2.log("Wrapped M Implementation address:", implementation_); + console2.log("Migrator address:", migrator_); + + if (migrator_ != _EXPECTED_MIGRATOR) revert ResultingMigratorMismatch(_EXPECTED_MIGRATOR, migrator_); + } +} diff --git a/src/Migratable.sol b/src/Migratable.sol deleted file mode 100644 index 055f51f..0000000 --- a/src/Migratable.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity 0.8.23; - -import { IMigratable } from "./interfaces/IMigratable.sol"; - -/** - * @title Abstract implementation for exposing the ability to migrate a contract, extending ERC-1967. - * @author M^0 Labs - */ -abstract contract Migratable is IMigratable { - /* ============ Variables ============ */ - - /// @dev Storage slot with the address of the current factory. `keccak256('eip1967.proxy.implementation') - 1`. - uint256 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - - /* ============ Interactive Functions ============ */ - - /// @inheritdoc IMigratable - function migrate() external { - _migrate(_getMigrator()); - } - - /* ============ View/Pure Functions ============ */ - - /// @inheritdoc IMigratable - function implementation() public view returns (address implementation_) { - assembly { - implementation_ := sload(_IMPLEMENTATION_SLOT) - } - } - - /* ============ Internal Interactive Functions ============ */ - - /** - * @dev Performs an arbitrary migration by delegate-calling `migrator_`. - * @param migrator_ The address of a migrator contract. - */ - function _migrate(address migrator_) internal { - if (migrator_ == address(0)) revert ZeroMigrator(); - - if (migrator_.code.length == 0) revert InvalidMigrator(); - - address oldImplementation_ = implementation(); - - (bool success_, ) = migrator_.delegatecall(""); - if (!success_) revert MigrationFailed(); - - address newImplementation_ = implementation(); - - emit Migrated(migrator_, oldImplementation_, newImplementation_); - - // NOTE: Redundant event emitted to conform to the EIP-1967 standard. - emit Upgraded(newImplementation_); - } - - /* ============ Internal View/Pure Functions ============ */ - - /// @dev Returns the address of a migrator contract. - function _getMigrator() internal view virtual returns (address migrator_); -} diff --git a/src/MigratorV1.sol b/src/MigratorV1.sol new file mode 100644 index 0000000..74cef78 --- /dev/null +++ b/src/MigratorV1.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity 0.8.26; + +/** + * @title Migrator contract for migrating a WrappedMToken contract from V1 to V2. + * @author M^0 Labs + */ +contract MigratorV1 { + /// @dev Storage slot with the address of the current factory. `keccak256('eip1967.proxy.implementation') - 1`. + uint256 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + address public immutable newImplementation; + + constructor(address newImplementation_) { + newImplementation = newImplementation_; + } + + fallback() external virtual { + address newImplementation_ = newImplementation; + + assembly { + sstore(_IMPLEMENTATION_SLOT, newImplementation_) + } + } +} diff --git a/src/Proxy.sol b/src/Proxy.sol deleted file mode 100644 index 2cd0024..0000000 --- a/src/Proxy.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity 0.8.23; -/** - * @title Minimal transparent proxy. - * @author M^0 Labs - */ -contract Proxy { - /// @dev Storage slot with the address of the current factory. `keccak256('eip1967.proxy.implementation') - 1`. - uint256 private constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - - /** - * @dev Constructs the contract given the address of some implementation. - * @param implementation_ The address of some implementation. - */ - constructor(address implementation_) { - if (implementation_ == address(0)) revert(); - - assembly { - sstore(_IMPLEMENTATION_SLOT, implementation_) - } - } - - fallback() external payable virtual { - bytes32 implementation_; - - assembly { - implementation_ := sload(_IMPLEMENTATION_SLOT) - } - - assembly { - calldatacopy(0, 0, calldatasize()) - - let result_ := delegatecall(gas(), implementation_, 0, calldatasize(), 0, 0) - - returndatacopy(0, 0, returndatasize()) - - switch result_ - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) - } - } - } -} diff --git a/src/WrappedMToken.sol b/src/WrappedMToken.sol index 8073ecc..096231c 100644 --- a/src/WrappedMToken.sol +++ b/src/WrappedMToken.sol @@ -1,20 +1,19 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.23; +pragma solidity 0.8.26; +import { IndexingMath } from "../lib/common/src/libs/IndexingMath.sol"; import { UIntMath } from "../lib/common/src/libs/UIntMath.sol"; import { IERC20 } from "../lib/common/src/interfaces/IERC20.sol"; -import { ERC20Extended } from "../lib/common/src/ERC20Extended.sol"; -import { IndexingMath } from "./libs/IndexingMath.sol"; +import { ERC20Extended } from "../lib/common/src/ERC20Extended.sol"; +import { Migratable } from "../lib/common/src/Migratable.sol"; import { IMTokenLike } from "./interfaces/IMTokenLike.sol"; import { IRegistrarLike } from "./interfaces/IRegistrarLike.sol"; import { IWrappedMToken } from "./interfaces/IWrappedMToken.sol"; -import { Migratable } from "./Migratable.sol"; - /* ██╗ ██╗██████╗ █████╗ ██████╗ ██████╗ ███████╗██████╗ ███╗ ███╗ ████████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗ @@ -31,25 +30,35 @@ import { Migratable } from "./Migratable.sol"; * @author M^0 Labs */ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { + /* ============ Structs ============ */ + + /** + * @dev Struct to represent an account's balance and yield earning details + * @param isEarning Whether the account is actively earning yield. + * @param balance The present amount of tokens held by the account. + * @param lastIndex The index of the last interaction for the account (0 for non-earning accounts). + */ struct Account { + // First Slot bool isEarning; uint240 balance; + // Second slot uint128 lastIndex; } /* ============ Variables ============ */ - /// @dev Registrar key holding value of whether the earners list can be ignored or not. - bytes32 internal constant _EARNERS_LIST_IGNORED = "earners_list_ignored"; + /// @inheritdoc IWrappedMToken + bytes32 public constant EARNERS_LIST_IGNORED_KEY = "earners_list_ignored"; - /// @dev Registrar key of earners list. - bytes32 internal constant _EARNERS_LIST = "earners"; + /// @inheritdoc IWrappedMToken + bytes32 public constant EARNERS_LIST_NAME = "earners"; - /// @dev Registrar key prefix to determine the override recipient of an account's accrued yield. - bytes32 internal constant _CLAIM_OVERRIDE_RECIPIENT_PREFIX = "wm_claim_override_recipient"; + /// @inheritdoc IWrappedMToken + bytes32 public constant CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX = "wm_claim_override_recipient"; - /// @dev Registrar key prefix to determine the migrator contract. - bytes32 internal constant _MIGRATOR_V1_PREFIX = "wm_migrator_v1"; + /// @inheritdoc IWrappedMToken + bytes32 public constant MIGRATOR_KEY_PREFIX = "wm_migrator_v2"; /// @inheritdoc IWrappedMToken address public immutable migrationAdmin; @@ -61,7 +70,7 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { address public immutable registrar; /// @inheritdoc IWrappedMToken - address public immutable vault; + address public immutable excessDestination; /// @inheritdoc IWrappedMToken uint112 public principalOfTotalEarningSupply; @@ -83,15 +92,21 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { /** * @dev Constructs the contract given an M Token address and migration admin. * Note that a proxy will not need to initialize since there are no mutable storage values affected. - * @param mToken_ The address of an M Token. - * @param migrationAdmin_ The address of a migration admin. + * @param mToken_ The address of an M Token. + * @param registrar_ The address of a Registrar. + * @param excessDestination_ The address of an excess destination. + * @param migrationAdmin_ The address of a migration admin. */ - constructor(address mToken_, address migrationAdmin_) ERC20Extended("WrappedM by M^0", "wM", 6) { + constructor( + address mToken_, + address registrar_, + address excessDestination_, + address migrationAdmin_ + ) ERC20Extended("M (Wrapped) by M^0", "wM", 6) { if ((mToken = mToken_) == address(0)) revert ZeroMToken(); + if ((registrar = registrar_) == address(0)) revert ZeroRegistrar(); + if ((excessDestination = excessDestination_) == address(0)) revert ZeroExcessDestination(); if ((migrationAdmin = migrationAdmin_) == address(0)) revert ZeroMigrationAdmin(); - - registrar = IMTokenLike(mToken_).ttgRegistrar(); - vault = IRegistrarLike(registrar).vault(); } /* ============ Interactive Functions ============ */ @@ -103,7 +118,7 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { /// @inheritdoc IWrappedMToken function wrap(address recipient_) external returns (uint240 wrapped_) { - return _wrap(msg.sender, recipient_, UIntMath.safe240(IMTokenLike(mToken).balanceOf(msg.sender))); + return _wrap(msg.sender, recipient_, _mBalanceOf(msg.sender)); } /// @inheritdoc IWrappedMToken @@ -125,7 +140,7 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { function claimExcess() external returns (uint240 excess_) { emit ExcessClaimed(excess_ = excess()); - IMTokenLike(mToken).transfer(vault, excess_); + IMTokenLike(mToken).transfer(excessDestination, excess_); } /// @inheritdoc IWrappedMToken @@ -164,55 +179,15 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { /// @inheritdoc IWrappedMToken function startEarningFor(address account_) external { - _revertIfNotApprovedEarner(account_); - if (!isEarningEnabled()) revert EarningIsDisabled(); - Account storage accountInfo_ = _accounts[account_]; - - if (accountInfo_.isEarning) return; - // NOTE: Use `currentIndex()` if/when upgrading to support `startEarningFor` while earning is disabled. - uint128 currentIndex_ = _currentMIndex(); - - accountInfo_.isEarning = true; - accountInfo_.lastIndex = currentIndex_; - - uint240 balance_ = accountInfo_.balance; - - _addTotalEarningSupply(balance_, currentIndex_); - - unchecked { - totalNonEarningSupply -= balance_; - } - - emit StartedEarning(account_); + _startEarningFor(account_, _currentMIndex()); } /// @inheritdoc IWrappedMToken function stopEarningFor(address account_) external { - _revertIfApprovedEarner(account_); - - uint128 currentIndex_ = currentIndex(); - - _claim(account_, currentIndex_); - - Account storage accountInfo_ = _accounts[account_]; - - if (!accountInfo_.isEarning) return; - - accountInfo_.isEarning = false; - accountInfo_.lastIndex = 0; - - uint240 balance_ = accountInfo_.balance; - - _subtractTotalEarningSupply(balance_, currentIndex_); - - unchecked { - totalNonEarningSupply += balance_; - } - - emit StoppedEarning(account_); + _stopEarningFor(account_, currentIndex()); } /* ============ Temporary Admin Migration ============ */ @@ -230,9 +205,8 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { function accruedYieldOf(address account_) public view returns (uint240 yield_) { Account storage accountInfo_ = _accounts[account_]; - if (!accountInfo_.isEarning) return 0; - - return _getAccruedYield(accountInfo_.balance, accountInfo_.lastIndex, currentIndex()); + return + accountInfo_.isEarning ? _getAccruedYield(accountInfo_.balance, accountInfo_.lastIndex, currentIndex()) : 0; } /// @inheritdoc IERC20 @@ -242,11 +216,13 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { /// @inheritdoc IWrappedMToken function balanceWithYieldOf(address account_) public view returns (uint256 balance_) { - return balanceOf(account_) + accruedYieldOf(account_); + unchecked { + return balanceOf(account_) + accruedYieldOf(account_); + } } /// @inheritdoc IWrappedMToken - function lastIndexOf(address account_) public view returns (uint128 lastIndex_) { + function lastIndexOf(address account_) external view returns (uint128 lastIndex_) { return _accounts[account_].lastIndex; } @@ -255,9 +231,7 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { return address( uint160( - uint256( - IRegistrarLike(registrar).get(keccak256(abi.encode(_CLAIM_OVERRIDE_RECIPIENT_PREFIX, account_))) - ) + uint256(_getFromRegistrar(keccak256(abi.encode(CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX, account_)))) ) ); } @@ -284,9 +258,10 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { /// @inheritdoc IWrappedMToken function excess() public view returns (uint240 excess_) { + uint128 currentIndex_ = currentIndex(); + uint240 balance_ = _mBalanceOf(address(this)); + unchecked { - uint128 currentIndex_ = currentIndex(); - uint240 balance_ = uint240(IMTokenLike(mToken).balanceOf(address(this))); uint240 earmarked_ = totalNonEarningSupply + _projectedEarningSupply(currentIndex_); return balance_ > earmarked_ ? _getSafeTransferableM(balance_ - earmarked_, currentIndex_) : 0; @@ -305,7 +280,9 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { /// @inheritdoc IERC20 function totalSupply() external view returns (uint256 totalSupply_) { - return totalEarningSupply + totalNonEarningSupply; + unchecked { + return totalEarningSupply + totalNonEarningSupply; + } } /* ============ Internal Interactive Functions ============ */ @@ -374,13 +351,13 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { * @param amount_ The present amount of tokens to decrement by. */ function _subtractNonEarningAmount(address account_, uint240 amount_) internal { - unchecked { - Account storage accountInfo_ = _accounts[account_]; + Account storage accountInfo_ = _accounts[account_]; - uint240 balance_ = accountInfo_.balance; + uint240 balance_ = accountInfo_.balance; - if (balance_ < amount_) revert InsufficientBalance(account_, balance_, amount_); + if (balance_ < amount_) revert InsufficientBalance(account_, balance_, amount_); + unchecked { accountInfo_.balance = balance_ - amount_; totalNonEarningSupply -= amount_; } @@ -396,8 +373,9 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { // NOTE: Can be `unchecked` because the max amount of wrappable M is never greater than `type(uint240).max`. unchecked { _accounts[account_].balance += amount_; - _addTotalEarningSupply(amount_, currentIndex_); } + + _addTotalEarningSupply(amount_, currentIndex_); } /** @@ -407,16 +385,17 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { * @param currentIndex_ The current index to use to compute the principal amount. */ function _subtractEarningAmount(address account_, uint240 amount_, uint128 currentIndex_) internal { - unchecked { - Account storage accountInfo_ = _accounts[account_]; + Account storage accountInfo_ = _accounts[account_]; - uint240 balance_ = accountInfo_.balance; + uint240 balance_ = accountInfo_.balance; - if (balance_ < amount_) revert InsufficientBalance(account_, balance_, amount_); + if (balance_ < amount_) revert InsufficientBalance(account_, balance_, amount_); + unchecked { accountInfo_.balance = balance_ - amount_; - _subtractTotalEarningSupply(amount_, currentIndex_); } + + _subtractTotalEarningSupply(amount_, currentIndex_); } /** @@ -440,9 +419,9 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { accountInfo_.lastIndex = currentIndex_; - unchecked { - if (yield_ == 0) return 0; + if (yield_ == 0) return 0; + unchecked { accountInfo_.balance = startingBalance_ + yield_; // Update the total earning supply to account for the yield, but the principal has not changed. @@ -479,6 +458,8 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { emit Transfer(sender_, recipient_, amount_); + if (amount_ == 0) return; + Account storage senderAccountInfo_ = _accounts[sender_]; Account storage recipientAccountInfo_ = _accounts[recipient_]; @@ -536,15 +517,15 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { */ function _subtractTotalEarningSupply(uint240 amount_, uint128 currentIndex_) internal { if (amount_ >= totalEarningSupply) { - totalEarningSupply = 0; - principalOfTotalEarningSupply = 0; + delete totalEarningSupply; + delete principalOfTotalEarningSupply; return; } - unchecked { - uint112 principal_ = IndexingMath.getPrincipalAmountRoundedDown(amount_, currentIndex_); + uint112 principal_ = IndexingMath.getPrincipalAmountRoundedDown(amount_, currentIndex_); + unchecked { principalOfTotalEarningSupply -= ( principal_ > principalOfTotalEarningSupply ? principalOfTotalEarningSupply : principal_ ); @@ -561,7 +542,7 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { * @return wrapped_ The amount of wM minted. */ function _wrap(address account_, address recipient_, uint240 amount_) internal returns (uint240 wrapped_) { - uint256 startingBalance_ = IMTokenLike(mToken).balanceOf(address(this)); + uint240 startingBalance_ = _mBalanceOf(address(this)); // NOTE: The behavior of `IMTokenLike.transferFrom` is known, so its return can be ignored. IMTokenLike(mToken).transferFrom(account_, address(this), amount_); @@ -569,8 +550,8 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { // NOTE: When this WrappedMToken contract is earning, any amount of M sent to it is converted to a principal // amount at the MToken contract, which when represented as a present amount, may be a rounding error // amount less than `amount_`. In order to capture the real increase in M, the difference between the - // starting and ending M balance is minted as WrappedM. - _mint(recipient_, wrapped_ = UIntMath.safe240(IMTokenLike(mToken).balanceOf(address(this)) - startingBalance_)); + // starting and ending M balance is minted as WrappedM token. + _mint(recipient_, wrapped_ = _mBalanceOf(address(this)) - startingBalance_); } /** @@ -583,7 +564,7 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { function _unwrap(address account_, address recipient_, uint240 amount_) internal returns (uint240 unwrapped_) { _burn(account_, amount_); - uint256 startingBalance_ = IMTokenLike(mToken).balanceOf(address(this)); + uint240 startingBalance_ = _mBalanceOf(address(this)); // NOTE: The behavior of `IMTokenLike.transfer` is known, so its return can be ignored. IMTokenLike(mToken).transfer(recipient_, _getSafeTransferableM(amount_, currentIndex())); @@ -592,7 +573,61 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { // amount at the MToken contract, which when represented as a present amount, may be a rounding error // amount more than `amount_`. In order to capture the real decrease in M, the difference between the // ending and starting M balance is returned. - return UIntMath.safe240(startingBalance_ - IMTokenLike(mToken).balanceOf(address(this))); + return startingBalance_ - _mBalanceOf(address(this)); + } + + /** + * @dev Starts earning for `account` if allowed by the Registrar. + * @param account_ The account to start earning for. + * @param currentIndex_ The current index. + */ + function _startEarningFor(address account_, uint128 currentIndex_) internal { + _revertIfNotApprovedEarner(account_); + + Account storage accountInfo_ = _accounts[account_]; + + if (accountInfo_.isEarning) return; + + accountInfo_.isEarning = true; + accountInfo_.lastIndex = currentIndex_; + + uint240 balance_ = accountInfo_.balance; + + _addTotalEarningSupply(balance_, currentIndex_); + + unchecked { + totalNonEarningSupply -= balance_; + } + + emit StartedEarning(account_); + } + + /** + * @dev Stops earning for `account` if disallowed by the Registrar. + * @param account_ The account to stop earning for. + * @param currentIndex_ The current index. + */ + function _stopEarningFor(address account_, uint128 currentIndex_) internal { + _revertIfApprovedEarner(account_); + + _claim(account_, currentIndex_); + + Account storage accountInfo_ = _accounts[account_]; + + if (!accountInfo_.isEarning) return; + + delete accountInfo_.isEarning; + delete accountInfo_.lastIndex; + + uint240 balance_ = accountInfo_.balance; + + _subtractTotalEarningSupply(balance_, currentIndex_); + + unchecked { + totalNonEarningSupply += balance_; + } + + emit StoppedEarning(account_); } /* ============ Internal View/Pure Functions ============ */ @@ -629,14 +664,23 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { } } + /** + * @dev Retrieve a value from the Registrar. + * @param key_ The key to retrieve the value for. + * @return value_ The value stored in the Registrar. + */ + function _getFromRegistrar(bytes32 key_) internal view returns (bytes32 value_) { + return IRegistrarLike(registrar).get(key_); + } + /** * @dev Compute the adjusted amount of M that can safely be transferred out given the current index. - * @param amount_ Some amount to be transferred out of the wrapper. + * @param amount_ Some amount to be transferred out of this contract. * @param currentIndex_ The current index. * @return safeAmount_ The adjusted amount that can safely be transferred out. */ function _getSafeTransferableM(uint240 amount_, uint128 currentIndex_) internal view returns (uint240 safeAmount_) { - // If the wrapper is earning, adjust `amount_` to ensure it's M balance decrement is limited to `amount_`. + // If this contract is earning, adjust `amount_` to ensure it's M balance decrement is limited to `amount_`. return IMTokenLike(mToken).isEarning(address(this)) ? IndexingMath.getPresentAmountRoundedDown( @@ -652,20 +696,30 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { address( uint160( // NOTE: A subsequent implementation should use a unique migrator prefix. - uint256(IRegistrarLike(registrar).get(keccak256(abi.encode(_MIGRATOR_V1_PREFIX, address(this))))) + uint256(_getFromRegistrar(keccak256(abi.encode(MIGRATOR_KEY_PREFIX, address(this))))) ) ); } /** - * @dev Returns whether `account_` is a TTG-approved earner. + * @dev Returns whether `account_` is a Registrar-approved earner. * @param account_ The account being queried. - * @return isApproved_ True if the account_ is a TTG-approved earner, false otherwise. + * @return isApproved_ True if the account_ is a Registrar-approved earner, false otherwise. */ function _isApprovedEarner(address account_) internal view returns (bool isApproved_) { return - IRegistrarLike(registrar).get(_EARNERS_LIST_IGNORED) != bytes32(0) || - IRegistrarLike(registrar).listContains(_EARNERS_LIST, account_); + _getFromRegistrar(EARNERS_LIST_IGNORED_KEY) != bytes32(0) || + IRegistrarLike(registrar).listContains(EARNERS_LIST_NAME, account_); + } + + /** + * @dev Returns the M Token balance of `account_`. + * @param account_ The account being queried. + * @return balance_ The M Token balance of the account. + */ + function _mBalanceOf(address account_) internal view returns (uint240 balance_) { + // NOTE: M Token balance are limited to `uint240`. + return uint240(IMTokenLike(mToken).balanceOf(account_)); } /** @@ -686,11 +740,11 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { } /** - * @dev Reverts if `recipient_` is address(0). - * @param recipient_ Address of a recipient. + * @dev Reverts if `account_` is address(0). + * @param account_ Address of an account. */ - function _revertIfInvalidRecipient(address recipient_) internal pure { - if (recipient_ == address(0)) revert InvalidRecipient(recipient_); + function _revertIfInvalidRecipient(address account_) internal pure { + if (account_ == address(0)) revert InvalidRecipient(account_); } /** @@ -698,7 +752,7 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { * @param account_ Address of an account. */ function _revertIfApprovedEarner(address account_) internal view { - if (_isApprovedEarner(account_)) revert IsApprovedEarner(); + if (_isApprovedEarner(account_)) revert IsApprovedEarner(account_); } /** @@ -706,7 +760,7 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended { * @param account_ Address of an account. */ function _revertIfNotApprovedEarner(address account_) internal view { - if (!_isApprovedEarner(account_)) revert NotApprovedEarner(); + if (!_isApprovedEarner(account_)) revert NotApprovedEarner(account_); } /** diff --git a/src/interfaces/IMTokenLike.sol b/src/interfaces/IMTokenLike.sol index e0c7939..3d902bc 100644 --- a/src/interfaces/IMTokenLike.sol +++ b/src/interfaces/IMTokenLike.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.23; +pragma solidity 0.8.26; /** * @title Subset of M Token interface required for source contracts. @@ -26,7 +26,7 @@ interface IMTokenLike { */ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool success); - /// @notice Starts earning for caller if allowed by TTG. + /// @notice Starts earning for caller if allowed by the Registrar. function startEarning() external; /// @notice Stops earning for caller. @@ -50,7 +50,4 @@ interface IMTokenLike { /// @notice The current index that would be written to storage if `updateIndex` is called. function currentIndex() external view returns (uint128 currentIndex); - - /// @notice The address of the TTG Registrar contract. - function ttgRegistrar() external view returns (address ttgRegistrar); } diff --git a/src/interfaces/IMigratable.sol b/src/interfaces/IMigratable.sol deleted file mode 100644 index 94177a1..0000000 --- a/src/interfaces/IMigratable.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity 0.8.23; - -/** - * @title Interface for exposing the ability to migrate a contract, extending the ERC-1967 interface. - * @author M^0 Labs - */ -interface IMigratable { - /* ============ Events ============ */ - - /** - * @notice Emitted when a migration to a new implementation is performed. - * @param migrator The address that performed the migration. - * @param oldImplementation The address of the old implementation. - * @param newImplementation The address of the new implementation. - */ - event Migrated(address indexed migrator, address indexed oldImplementation, address indexed newImplementation); - - /** - * @notice Emitted when the implementation address for the proxy is changed. - * @param implementation The address of the new implementation for the proxy. - */ - event Upgraded(address indexed implementation); - - /// @notice Emitted when calling `stopEarning` for an account approved as earner by TTG. - error InvalidMigrator(); - - /// @notice Emitted when the delegatecall to a migrator fails. - error MigrationFailed(); - - /// @notice Emitted when the zero address is passed as a migrator. - error ZeroMigrator(); - - /* ============ Interactive Functions ============ */ - - /// @notice Performs an arbitrarily defined migration. - function migrate() external; - - /* ============ View/Pure Functions ============ */ - - /// @notice Returns the address of the current implementation contract. - function implementation() external view returns (address implementation); -} diff --git a/src/interfaces/IRegistrarLike.sol b/src/interfaces/IRegistrarLike.sol index 2c08775..2981059 100644 --- a/src/interfaces/IRegistrarLike.sol +++ b/src/interfaces/IRegistrarLike.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.23; +pragma solidity 0.8.26; /** * @title Subset of Registrar interface required for source contracts. @@ -23,7 +23,4 @@ interface IRegistrarLike { * @return contains Whether `list` contains `account` or not. */ function listContains(bytes32 list, address account) external view returns (bool contains); - - /// @notice Returns the address of the Vault. - function vault() external view returns (address vault); } diff --git a/src/interfaces/IWrappedMToken.sol b/src/interfaces/IWrappedMToken.sol index f684adb..a74ca47 100644 --- a/src/interfaces/IWrappedMToken.sol +++ b/src/interfaces/IWrappedMToken.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.23; +pragma solidity 0.8.26; import { IERC20Extended } from "../../lib/common/src/interfaces/IERC20Extended.sol"; - -import { IMigratable } from "./IMigratable.sol"; +import { IMigratable } from "../../lib/common/src/interfaces/IMigratable.sol"; /** * @title Wrapped M Token interface extending Extended ERC20. @@ -22,19 +21,19 @@ interface IWrappedMToken is IMigratable, IERC20Extended { event Claimed(address indexed account, address indexed recipient, uint240 yield); /** - * @notice Emitted when earning is enabled for the entire wrapper. + * @notice Emitted when Wrapped M earning is enabled. * @param index The index at the moment earning is enabled. */ event EarningEnabled(uint128 index); /** - * @notice Emitted when earning is disabled for the entire wrapper. + * @notice Emitted when Wrapped M earning is disabled. * @param index The index at the moment earning is disabled. */ event EarningDisabled(uint128 index); /** - * @notice Emitted when the wrapper's excess M is claimed. + * @notice Emitted when this contract's excess M is claimed. * @param excess The amount of excess M claimed. */ event ExcessClaimed(uint240 excess); @@ -62,8 +61,11 @@ interface IWrappedMToken is IMigratable, IERC20Extended { /// @notice Emitted when trying to enable earning after it has been explicitly disabled. error EarningCannotBeReenabled(); - /// @notice Emitted when calling `stopEarning` for an account approved as earner by TTG. - error IsApprovedEarner(); + /** + * @notice Emitted when calling `stopEarning` for an account approved as earner by the Registrar. + * @param account The account that is an approved earner. + */ + error IsApprovedEarner(address account); /** * @notice Emitted when there is insufficient balance to decrement from `account`. @@ -73,18 +75,27 @@ interface IWrappedMToken is IMigratable, IERC20Extended { */ error InsufficientBalance(address account, uint240 balance, uint240 amount); - /// @notice Emitted when calling `startEarning` for an account not approved as earner by TTG. - error NotApprovedEarner(); + /** + * @notice Emitted when calling `startEarning` for an account not approved as earner by the Registrar. + * @param account The account that is not an approved earner. + */ + error NotApprovedEarner(address account); /// @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 Excess Destination is 0x0. + error ZeroExcessDestination(); + /// @notice Emitted in constructor if M Token is 0x0. error ZeroMToken(); /// @notice Emitted in constructor if Migration Admin is 0x0. error ZeroMigrationAdmin(); + /// @notice Emitted in constructor if Registrar is 0x0. + error ZeroRegistrar(); + /* ============ Interactive Functions ============ */ /** @@ -125,25 +136,25 @@ interface IWrappedMToken is IMigratable, IERC20Extended { function claimFor(address account) external returns (uint240 yield); /** - * @notice Claims any excess M of the wrapper. + * @notice Claims any excess M of this contract. * @return excess The amount of excess claimed. */ function claimExcess() external returns (uint240 excess); - /// @notice Enables earning for the wrapper if allowed by TTG and if it has never been done. + /// @notice Enables earning of Wrapped M if allowed by the Registrar and if it has never been done. function enableEarning() external; - /// @notice Disables earning for the wrapper if disallowed by TTG and if it has never been done. + /// @notice Disables earning of Wrapped M if disallowed by the Registrar and if it has never been done. function disableEarning() external; /** - * @notice Starts earning for `account` if allowed by TTG. + * @notice Starts earning for `account` if allowed by the Registrar. * @param account The account to start earning for. */ function startEarningFor(address account) external; /** - * @notice Stops earning for `account` if disallowed by TTG. + * @notice Stops earning for `account` if disallowed by the Registrar. * @param account The account to stop earning for. */ function stopEarningFor(address account) external; @@ -158,6 +169,18 @@ interface IWrappedMToken is IMigratable, IERC20Extended { /* ============ View/Pure Functions ============ */ + /// @notice Registrar key holding value of whether the earners list can be ignored or not. + function EARNERS_LIST_IGNORED_KEY() external pure returns (bytes32 earnersListIgnoredKey); + + /// @notice Registrar key of earners list. + function EARNERS_LIST_NAME() external pure returns (bytes32 earnersListName); + + /// @notice Registrar key prefix to determine the override recipient of an account's accrued yield. + function CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX() external pure returns (bytes32 claimOverrideRecipientKeyPrefix); + + /// @notice Registrar key prefix to determine the migrator contract. + function MIGRATOR_KEY_PREFIX() external pure returns (bytes32 migratorKeyPrefix); + /** * @notice Returns the yield accrued for `account`, which is claimable. * @param account The account being queried. @@ -186,10 +209,10 @@ interface IWrappedMToken is IMigratable, IERC20Extended { */ function claimOverrideRecipientFor(address account) external view returns (address recipient); - /// @notice The current index of the wrapper's earning mechanism. + /// @notice The current index of Wrapped M's earning mechanism. function currentIndex() external view returns (uint128 index); - /// @notice The current excess M of the wrapper that is not earmarked for account balances or accrued yield. + /// @notice This contract's current excess M that is not earmarked for account balances or accrued yield. function excess() external view returns (uint240 excess); /** @@ -199,19 +222,19 @@ interface IWrappedMToken is IMigratable, IERC20Extended { */ function isEarning(address account) external view returns (bool isEarning); - /// @notice Whether earning is enabled for the entire wrapper. + /// @notice Whether Wrapped M earning is enabled. function isEarningEnabled() external view returns (bool isEnabled); - /// @notice Whether earning has been enabled at least once or not. + /// @notice Whether Wrapped M earning has been enabled at least once. function wasEarningEnabled() external view returns (bool wasEnabled); - /// @notice The account that can bypass TTG and call the `migrate(address migrator)` function. + /// @notice The account that can bypass the Registrar and call the `migrate(address migrator)` function. function migrationAdmin() external view returns (address migrationAdmin); /// @notice The address of the M Token contract. function mToken() external view returns (address mToken); - /// @notice The address of the TTG registrar. + /// @notice The address of the Registrar. function registrar() external view returns (address registrar); /// @notice The portion of total supply that is not earning yield. @@ -226,6 +249,6 @@ interface IWrappedMToken is IMigratable, IERC20Extended { /// @notice The principal of totalEarningSupply to help compute totalAccruedYield(), and thus excess(). function principalOfTotalEarningSupply() external view returns (uint112 principalOfTotalEarningSupply); - /// @notice The address of the vault where excess is claimed to. - function vault() external view returns (address vault); + /// @notice The address of the destination where excess is claimed to. + function excessDestination() external view returns (address excessDestination); } diff --git a/src/libs/IndexingMath.sol b/src/libs/IndexingMath.sol deleted file mode 100644 index 121d42b..0000000 --- a/src/libs/IndexingMath.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity 0.8.23; - -import { UIntMath } from "../../lib/common/src/libs/UIntMath.sol"; - -/** - * @title Helper library for indexing math functions. - * @author M^0 Labs - */ -library IndexingMath { - /* ============ Variables ============ */ - - /// @notice The scaling of indexes for exponent math. - uint56 internal constant EXP_SCALED_ONE = 1e12; - - /* ============ Custom Errors ============ */ - - /// @notice Emitted when a division by zero occurs. - error DivisionByZero(); - - /* ============ Internal View/Pure Functions ============ */ - - /** - * @notice Helper function to calculate `(x * EXP_SCALED_ONE) / y`, rounded down. - * @dev Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) - */ - function divide240By128Down(uint240 x_, uint128 y_) internal pure returns (uint112) { - if (y_ == 0) revert DivisionByZero(); - - unchecked { - // NOTE: While `uint256(x) * EXP_SCALED_ONE` can technically overflow, these divide/multiply functions are - // only used for the purpose of principal/present amount calculations for continuous indexing, and - // so for an `x` to be large enough to overflow this, it would have to be a possible result of - // `multiply112By128Down` or `multiply112By128Up`, which would already satisfy - // `uint256(x) * EXP_SCALED_ONE < type(uint240).max`. - return UIntMath.safe112((uint256(x_) * EXP_SCALED_ONE) / y_); - } - } - - /** - * @notice Helper function to calculate `(x * EXP_SCALED_ONE) / y`, rounded up. - * @dev Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) - */ - function divide240By128Up(uint240 x_, uint128 y_) internal pure returns (uint112) { - if (y_ == 0) revert DivisionByZero(); - - unchecked { - // NOTE: While `uint256(x) * EXP_SCALED_ONE` can technically overflow, these divide/multiply functions are - // only used for the purpose of principal/present amount calculations for continuous indexing, and - // so for an `x` to be large enough to overflow this, it would have to be a possible result of - // `multiply112By128Down` or `multiply112By128Up`, which would already satisfy - // `uint256(x) * EXP_SCALED_ONE < type(uint240).max`. - return UIntMath.safe112(((uint256(x_) * EXP_SCALED_ONE) + y_ - 1) / y_); - } - } - - /** - * @notice Helper function to calculate `(x * y) / EXP_SCALED_ONE`, rounded down. - * @dev Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) - */ - function multiply112By128Down(uint112 x_, uint128 y_) internal pure returns (uint240) { - unchecked { - return uint240((uint256(x_) * y_) / EXP_SCALED_ONE); - } - } - - /** - * @dev Returns the present amount (rounded down) given the principal amount and an index. - * @param principalAmount_ The principal amount. - * @param index_ An index. - * @return The present amount rounded down. - */ - function getPresentAmountRoundedDown(uint112 principalAmount_, uint128 index_) internal pure returns (uint240) { - return multiply112By128Down(principalAmount_, index_); - } - - /** - * @dev Returns the principal amount given the present amount, using the current index. - * @param presentAmount_ The present amount. - * @param index_ An index. - * @return The principal amount rounded down. - */ - function getPrincipalAmountRoundedDown(uint240 presentAmount_, uint128 index_) internal pure returns (uint112) { - return divide240By128Down(presentAmount_, index_); - } - - /** - * @dev Returns the principal amount given the present amount, using the current index. - * @param presentAmount_ The present amount. - * @param index_ An index. - * @return The principal amount rounded up. - */ - function getPrincipalAmountRoundedUp(uint240 presentAmount_, uint128 index_) internal pure returns (uint112) { - return divide240By128Up(presentAmount_, index_); - } -} diff --git a/test/Migration.t.sol b/test/Migration.t.sol deleted file mode 100644 index e0d4a7f..0000000 --- a/test/Migration.t.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.23; - -import { Test } from "../lib/forge-std/src/Test.sol"; - -import { IWrappedMToken } from "../src/interfaces/IWrappedMToken.sol"; - -import { WrappedMToken } from "../src/WrappedMToken.sol"; -import { Proxy } from "../src/Proxy.sol"; - -import { MockM, MockRegistrar } from "./utils/Mocks.sol"; - -contract WrappedMTokenV2 { - function foo() external pure returns (uint256) { - return 1; - } -} - -contract WrappedMTokenMigratorV1 { - 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 { - uint56 internal constant _EXP_SCALED_ONE = 1e12; - - bytes32 internal constant _EARNERS_LIST = "earners"; - bytes32 internal constant _MIGRATOR_V1_PREFIX = "wm_migrator_v1"; - - address internal _alice = makeAddr("alice"); - address internal _bob = makeAddr("bob"); - address internal _carol = makeAddr("carol"); - address internal _dave = makeAddr("dave"); - - address internal _migrationAdmin = makeAddr("migrationAdmin"); - - address internal _vault = makeAddr("vault"); - - MockM internal _mToken; - MockRegistrar internal _registrar; - WrappedMToken internal _implementation; - IWrappedMToken internal _wrappedMToken; - - function setUp() external { - _registrar = new MockRegistrar(); - _registrar.setVault(_vault); - - _mToken = new MockM(); - _mToken.setCurrentIndex(_EXP_SCALED_ONE); - _mToken.setTtgRegistrar(address(_registrar)); - - _implementation = new WrappedMToken(address(_mToken), _migrationAdmin); - - _wrappedMToken = IWrappedMToken(address(new Proxy(address(_implementation)))); - } - - function test_migration() external { - WrappedMTokenV2 implementationV2_ = new WrappedMTokenV2(); - address migrator_ = address(new WrappedMTokenMigratorV1(address(implementationV2_))); - - _registrar.set( - keccak256(abi.encode(_MIGRATOR_V1_PREFIX, address(_wrappedMToken))), - bytes32(uint256(uint160(migrator_))) - ); - - vm.expectRevert(); - WrappedMTokenV2(address(_wrappedMToken)).foo(); - - _wrappedMToken.migrate(); - - 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/integration/Deploy.t.sol b/test/integration/Deploy.t.sol index 79aebfc..ced7573 100644 --- a/test/integration/Deploy.t.sol +++ b/test/integration/Deploy.t.sol @@ -1,45 +1,44 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; +pragma solidity 0.8.26; import { Test } from "../../lib/forge-std/src/Test.sol"; import { IWrappedMToken } from "../../src/interfaces/IWrappedMToken.sol"; -import { IMTokenLike } from "../../src/interfaces/IMTokenLike.sol"; -import { IRegistrarLike } from "../../src/interfaces/IRegistrarLike.sol"; import { DeployBase } from "../../script/DeployBase.sol"; -contract Deploy is Test, DeployBase { - address internal constant _TTG_VAULT = 0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD; - +contract DeployTests is Test, DeployBase { + address internal constant _REGISTRAR = 0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c; address internal constant _M_TOKEN = 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b; address internal constant _MIGRATION_ADMIN = 0x431169728D75bd02f4053435b87D15c8d1FB2C72; + address internal constant _EXCESS_DESTINATION = 0xd7298f620B0F752Cf41BD818a16C756d9dCAA34f; // Vault address internal constant _DEPLOYER = 0xF2f1ACbe0BA726fEE8d75f3E32900526874740BB; - uint256 internal constant _DEPLOYER_PROXY_NONCE = 40; - address internal constant _EXPECTED_PROXY = 0x437cc33344a0B27A429f795ff6B469C72698B291; + + uint64 internal constant _DEPLOYER_NONCE = 50; function test_deploy() external { - // Set nonce to 1 before `_DEPLOYER_PROXY_NONCE` since implementation is deployed before proxy. - vm.setNonce(_DEPLOYER, uint64(_DEPLOYER_PROXY_NONCE) - 1); + vm.setNonce(_DEPLOYER, _DEPLOYER_NONCE); + + (address expectedImplementation_, address expectedProxy_) = mockDeploy(_DEPLOYER, _DEPLOYER_NONCE); vm.startPrank(_DEPLOYER); - (address implementation_, address proxy_) = deploy(_M_TOKEN, _MIGRATION_ADMIN); + (address implementation_, address proxy_) = deploy(_M_TOKEN, _REGISTRAR, _EXCESS_DESTINATION, _MIGRATION_ADMIN); vm.stopPrank(); // Wrapped M Token Implementation assertions - assertEq(implementation_, getExpectedWrappedMTokenImplementation(_DEPLOYER, 39)); + assertEq(implementation_, expectedImplementation_); assertEq(IWrappedMToken(implementation_).migrationAdmin(), _MIGRATION_ADMIN); assertEq(IWrappedMToken(implementation_).mToken(), _M_TOKEN); - assertEq(IWrappedMToken(implementation_).registrar(), IMTokenLike(_M_TOKEN).ttgRegistrar()); - assertEq(IWrappedMToken(implementation_).vault(), IRegistrarLike(IMTokenLike(_M_TOKEN).ttgRegistrar()).vault()); + assertEq(IWrappedMToken(implementation_).registrar(), _REGISTRAR); + assertEq(IWrappedMToken(implementation_).excessDestination(), _EXCESS_DESTINATION); - // // Wrapped M Token Proxy assertions - assertEq(proxy_, getExpectedWrappedMTokenProxy(_DEPLOYER, 39)); - assertEq(proxy_, _EXPECTED_PROXY); + // Wrapped M Token Proxy assertions + assertEq(proxy_, expectedProxy_); assertEq(IWrappedMToken(proxy_).migrationAdmin(), _MIGRATION_ADMIN); assertEq(IWrappedMToken(proxy_).mToken(), _M_TOKEN); - assertEq(IWrappedMToken(proxy_).registrar(), IMTokenLike(_M_TOKEN).ttgRegistrar()); - assertEq(IWrappedMToken(proxy_).vault(), IRegistrarLike(IMTokenLike(_M_TOKEN).ttgRegistrar()).vault()); + assertEq(IWrappedMToken(proxy_).registrar(), _REGISTRAR); + assertEq(IWrappedMToken(proxy_).excessDestination(), _EXCESS_DESTINATION); + assertEq(IWrappedMToken(proxy_).implementation(), implementation_); } } diff --git a/test/integration/MorphoBlue.t.sol b/test/integration/MorphoBlue.t.sol index 8f38a88..36ec030 100644 --- a/test/integration/MorphoBlue.t.sol +++ b/test/integration/MorphoBlue.t.sol @@ -1,30 +1,12 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; - -import { TestBase } from "./TestBase.sol"; +pragma solidity 0.8.26; import { IERC20 } from "../../lib/common/src/interfaces/IERC20.sol"; -import { IMorphoBlueFactory, IMorphoChainlinkOracleV2Factory } from "./vendor/morpho-blue/Interfaces.sol"; - -contract MorphoBlueTests is TestBase { - // Morpho Blue factory on Ethereum Mainnet - address internal constant _morphoFactory = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; - - // Oracle factory on Ethereum Mainnet - address internal constant _oracleFactory = 0x3A7bB36Ee3f3eE32A60e9f2b33c1e5f2E83ad766; - - // USDC on Ethereum Mainnet - address internal constant _USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - - // Morpho Blue market Liquidation Loan-To-Value ratio - uint256 internal constant _LLTV = 94_5000000000000000; // 94.5% - - address internal _oracle; - - uint256 internal _wrapperBalanceOfM; +import { MorphoTestBase } from "./vendor/morpho-blue/MorphoTestBase.sol"; +contract MorphoBlueTests is MorphoTestBase { uint256 internal _morphoBalanceOfUSDC; uint256 internal _aliceBalanceOfUSDC; uint256 internal _bobBalanceOfUSDC; @@ -32,639 +14,231 @@ contract MorphoBlueTests is TestBase { uint256 internal _daveBalanceOfUSDC; uint256 internal _morphoBalanceOfWM; + uint256 internal _morphoClaimRecipientBalanceOfWM; uint256 internal _aliceBalanceOfWM; uint256 internal _bobBalanceOfWM; uint256 internal _carolBalanceOfWM; uint256 internal _daveBalanceOfWM; uint256 internal _morphoAccruedYield; + uint256 internal _morphoClaimRecipientAccruedYield; - function setUp() public override { - super.setUp(); - - _addToList(_EARNERS_LIST, address(_wrappedMToken)); + uint240 internal _excess; - _wrappedMToken.enableEarning(); + function setUp() external { + _deployV2Components(); + _migrate(); _oracle = _createOracle(); - _morphoBalanceOfUSDC = IERC20(_USDC).balanceOf(_morphoFactory); + _morphoBalanceOfUSDC = IERC20(_USDC).balanceOf(_MORPHO); + _morphoBalanceOfWM = _wrappedMToken.balanceOf(_MORPHO); + _morphoAccruedYield = _wrappedMToken.accruedYieldOf(_MORPHO); } - function test_initialState() external view { + function test_state() external view { assertTrue(_mToken.isEarning(address(_wrappedMToken))); - assertEq(_wrappedMToken.isEarningEnabled(), true); - assertFalse(_wrappedMToken.isEarning(_morphoFactory)); - } - - function test_morphoBlue_nonEarning_wM_as_collateralToken() external { - /* ============ Alice Creates Market ============ */ - - _giveM(_alice, 1_000_100e6); - _wrap(_alice, _alice, 1_000_100e6); - - assertEq(_mToken.balanceOf(_alice), 0); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_000_099_999999); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 1_000_099_999999); - - deal(_USDC, _alice, 1_000_100e6); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_000_100e6); - - // NOTE: Creating a market also result in `_alice` supplying 1.00 USDC as supply, 1.00 wM as collateral, and - // borrowing 0.90 USDC. - _createMarket(_alice, _USDC, address(_wrappedMToken)); - - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 1e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM += 1e6); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 100000); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC += 100000); - - /* ============ Alice Supplies Seed USDC For Loans ============ */ - - _supply(_alice, _USDC, 1_000_000e6); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC += 1_000_000e6); - - /* ============ Bob Takes Out USDC Loan Against wM Collateral ============ */ - - _giveM(_bob, 1_000_100e6); - _wrap(_bob, _bob, 1_000_100e6); - - assertEq(_mToken.balanceOf(_bob), 0); - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 1_000_099_999999); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 1_000_099_999999); - - _supplyCollateral(_bob, address(_wrappedMToken), 1_000_000e6); - - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM -= 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM += 1_000_000e6); - - _borrow(_bob, _USDC, 900_000e6, _bob); - - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 900_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC -= 900_000e6); - - /* ============ First 1-Year Time Warp ============ */ - - // Move 1 year forward and check that no yield has accrued. - vm.warp(vm.getBlockTimestamp() + 365 days); - - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 82_462_608992); - - // `startEarningFor` hasn't been called so no wM yield has accrued in the pool. - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield); - - // But excess yield has accrued in the wrapped M contract. - assertEq(_wrappedMToken.excess(), 82_462_608991); - - // USDC balance is unchanged. - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC); - - /* ============ Bob Repays USDC Loan And Withdraws wM Collateral ============ */ - - _repay(_bob, _USDC, 900_000e6); - - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= 900_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC += 900_000e6); - - _withdrawCollateral(_bob, address(_wrappedMToken), 1_000_000e6, _bob); - - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM -= 1_000_000e6); - - /* ============ Alice Withdraws Seed USDC For Loans ============ */ - - _withdraw(_alice, _USDC, 1_000_000e6, _alice); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC -= 1_000_000e6); - - /* ============ Second 1-Year Time Warp ============ */ - - // Move 1 year forward and check that no yield has accrued. - vm.warp(vm.getBlockTimestamp() + 365 days); - - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 85_862_309962); - - // `startEarningFor` hasn't been called so no wM yield has accrued in the pool. - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield); - - // But excess yield has accrued in the wrapped M contract. - assertEq(_wrappedMToken.excess(), 168_324_918953); - - // USDC balance is unchanged. - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC); - } - - function test_morphoBlue_nonEarning_wM_as_loanToken() external { - /* ============ Alice Creates Market ============ */ - - _giveM(_alice, 1_000_100e6); - _wrap(_alice, _alice, 1_000_100e6); - - assertEq(_mToken.balanceOf(_alice), 0); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_000_099_999999); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 1_000_099_999999); - - deal(_USDC, _alice, 1_000_100e6); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_000_100e6); - - // NOTE: Creating a market also result in `_alice` supplying 1.00 wM as supply, 1.00 USDC as collateral, and - // borrowing 0.90 wM. - _createMarket(_alice, address(_wrappedMToken), _USDC); - - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 100000); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM += 100000); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC += 1e6); - - /* ============ Alice Supplies Seed wM For Loans ============ */ - - _supply(_alice, address(_wrappedMToken), 1_000_000e6); - - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM += 1_000_000e6); - - /* ============ Bob Takes Out wM Loan Against USDC Collateral ============ */ - - deal(_USDC, _bob, 1_000_000e6); - - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 1_000_000e6); - - _supplyCollateral(_bob, _USDC, 1_000_000e6); - - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC += 1_000_000e6); - - _borrow(_bob, address(_wrappedMToken), 900_000e6, _bob); - - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 900_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM -= 900_000e6); - - /* ============ First 1-Year Time Warp ============ */ - - // Move 1 year forward and check that no yield has accrued. - vm.warp(vm.getBlockTimestamp() + 365 days); - - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 51_276_223485); - - // `startEarningFor` hasn't been called so no wM yield has accrued in the pool. - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield); - - // But excess yield has accrued in the wrapped M contract. - assertEq(_wrappedMToken.excess(), 51_276_223483); - - // USDC balance is unchanged. - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC); - - /* ============ Bob Repays wM Loan And Withdraws USDC Collateral ============ */ - - _repay(_bob, address(_wrappedMToken), 900_000e6); - - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM -= 900_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM += 900_000e6); - - _withdrawCollateral(_bob, _USDC, 1_000_000e6, _bob); - - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC -= 1_000_000e6); - - /* ============ Alice Withdraws Seed wM For Loans ============ */ - - _withdraw(_alice, address(_wrappedMToken), 1_000_000e6, _alice); - - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM -= 1_000_000e6); - - /* ============ Second 1-Year Time Warp ============ */ - - // Move 1 year forward and check that no yield has accrued. - vm.warp(vm.getBlockTimestamp() + 365 days); - - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 53_905_211681); - - // `startEarningFor` hasn't been called so no wM yield has accrued in the pool. - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield); - - // But excess yield has accrued in the wrapped M contract. - assertEq(_wrappedMToken.excess(), 105_181_435164); - - // USDC balance is unchanged. - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC); + assertTrue(_wrappedMToken.isEarningEnabled()); + assertTrue(_wrappedMToken.isEarning(_MORPHO)); } function test_morphoBlue_earning_wM_as_collateralToken() public { /* ============ Alice Creates Market ============ */ - _giveM(_alice, 1_000_100e6); - _wrap(_alice, _alice, 1_000_100e6); + _giveWM(_alice, 1_001e6); - assertEq(_mToken.balanceOf(_alice), 0); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_000_099_999999); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 1_000_099_999999); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_001e6); - deal(_USDC, _alice, 1_000_100e6); + _give(_USDC, _alice, 1_001e6); - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_000_100e6); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_001e6); // NOTE: Creating a market also result in `_alice` supplying 1.00 USDC as supply, 1.00 wM as collateral, and // borrowing 0.90 USDC. - _createMarket(_alice, _USDC, address(_wrappedMToken)); + _createMarket(_alice, _USDC); + + // The market creation has triggered a wM transfer and the yield has been claimed for morpho. + assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield -= _morphoAccruedYield); assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 1e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM += 1e6); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM += 1e6); assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 100000); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC += 100000); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC += 100000); /* ============ Alice Supplies Seed USDC For Loans ============ */ - _supply(_alice, _USDC, 1_000_000e6); + _supply(_alice, _USDC, 1_000e6, address(_wrappedMToken)); - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC += 1_000_000e6); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1_000e6); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC += 1_000e6); /* ============ Bob Takes Out USDC Loan Against wM Collateral ============ */ - _giveM(_bob, 1_000_100e6); - _wrap(_bob, _bob, 1_000_100e6); - - assertEq(_mToken.balanceOf(_bob), 0); - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 1_000_099_999999); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 1_000_099_999999); - - _supplyCollateral(_bob, address(_wrappedMToken), 1_000_000e6); - - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM -= 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM += 1_000_000e6); - - _borrow(_bob, _USDC, 900_000e6, _bob); + _giveWM(_bob, 1_100e6); - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 900_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC -= 900_000e6); + assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 1_100e6); - /* ============ Morpho Becomes An Earner ============ */ + _supplyCollateral(_bob, address(_wrappedMToken), 1_000e6, _USDC); - _setClaimOverrideRecipient(_morphoFactory, _carol); + assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM -= 1_000e6); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM += 1_000e6); - _addToList(_EARNERS_LIST, _morphoFactory); - _wrappedMToken.startEarningFor(_morphoFactory); + _borrow(_bob, _USDC, 900e6, _bob, address(_wrappedMToken)); - // Check that the pool is earning wM. - assertTrue(_wrappedMToken.isEarning(_morphoFactory)); - - assertEq(_wrappedMToken.claimOverrideRecipientFor(_morphoFactory), _carol); - - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield); + assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 900e6); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC -= 900e6); /* ============ First 1-Year Time Warp ============ */ // Move 1 year forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 365 days); - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 82_462_608992); - - // `startEarningFor` has been called so wM yield has accrued in the pool. - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield += 41_227_223004); - - // But excess yield has accrued in the wrapped M contract. - assertEq(_wrappedMToken.excess(), 41_235_385986); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM); + assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield += 49_292101); // USDC balance is unchanged. - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC); /* ============ Bob Repays USDC Loan And Withdraws wM Collateral ============ */ - _repay(_bob, _USDC, 900_000e6); + _repay(_bob, _USDC, 900e6, address(_wrappedMToken)); - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= 900_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC += 900_000e6); + assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= 900e6); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC += 900e6); - _withdrawCollateral(_bob, address(_wrappedMToken), 1_000_000e6, _bob); + _withdrawCollateral(_bob, address(_wrappedMToken), 1_000e6, _bob, _USDC); - // The collateral withdrawal has triggered a wM transfer and the yield has been claimed to carol for the pool. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _morphoAccruedYield); + // The collateral withdrawal has triggered a wM transfer and the yield has been claimed for morpho. + assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield -= _morphoAccruedYield); - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM -= 1_000_000e6); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield -= _morphoAccruedYield); + assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 1_000e6); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM -= 1_000e6); /* ============ Alice Withdraws Seed USDC For Loans ============ */ - _withdraw(_alice, _USDC, 1_000_000e6, _alice); + _withdraw(_alice, _USDC, 1_000e6, _alice, address(_wrappedMToken)); - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC -= 1_000_000e6); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_000e6); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC -= 1_000e6); - // /* ============ Second 1-Year Time Warp ============ */ + /* ============ Second 1-Year Time Warp ============ */ // Move 1 year forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 365 days); - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 85_862_309962); - - // `startEarningFor` has been called so wM yield has accrued in the pool. - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield += 41226); - - // But excess yield has accrued in the wrapped M contract. - assertEq(_wrappedMToken.excess(), 127_097_654719); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM); + assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield += 121446); // USDC balance is unchanged. - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC); } function test_morphoBlue_earning_wM_as_loanToken() public { /* ============ Alice Creates Market ============ */ - _giveM(_alice, 1_000_100e6); - _wrap(_alice, _alice, 1_000_100e6); + _giveWM(_alice, 1_001e6); - assertEq(_mToken.balanceOf(_alice), 0); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_000_099_999999); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 1_000_099_999999); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_001e6); - deal(_USDC, _alice, 1_000_100e6); + _give(_USDC, _alice, 1_001e6); - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_000_100e6); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_001e6); // NOTE: Creating a market also result in `_alice` supplying 1.00 wM as supply, 1.00 USDC as collateral, and // borrowing 0.90 wM. - _createMarket(_alice, address(_wrappedMToken), _USDC); + _createMarket(_alice, address(_wrappedMToken)); + + // The market creation has triggered a wM transfer and the yield has been claimed for morpho. + assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield -= _morphoAccruedYield); assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 100000); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM += 100000); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM += 100000); assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC += 1e6); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC += 1e6); /* ============ Alice Supplies Seed wM For Loans ============ */ - _supply(_alice, address(_wrappedMToken), 1_000_000e6); + _supply(_alice, address(_wrappedMToken), 1_000e6, _USDC); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM += 1_000_000e6); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 1_000e6); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM += 1_000e6); /* ============ Bob Takes Out wM Loan Against USDC Collateral ============ */ - deal(_USDC, _bob, 1_000_000e6); + _give(_USDC, _bob, 1_100e6); - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 1_000_000e6); + assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 1_100e6); - _supplyCollateral(_bob, _USDC, 1_000_000e6); + _supplyCollateral(_bob, _USDC, 1_000e6, address(_wrappedMToken)); - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC += 1_000_000e6); + assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= 1_000e6); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC += 1_000e6); - _borrow(_bob, address(_wrappedMToken), 900_000e6, _bob); + _borrow(_bob, address(_wrappedMToken), 900e6, _bob, _USDC); - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 900_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM -= 900_000e6); - - /* ============ Morpho Becomes An Earner ============ */ - - _setClaimOverrideRecipient(_morphoFactory, _carol); - - _addToList(_EARNERS_LIST, _morphoFactory); - _wrappedMToken.startEarningFor(_morphoFactory); - - // Check that the pool is earning wM. - assertTrue(_wrappedMToken.isEarning(_morphoFactory)); - - assertEq(_wrappedMToken.claimOverrideRecipientFor(_morphoFactory), _carol); - - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield); + assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 900e6); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM -= 900e6); /* ============ First 1-Year Time Warp ============ */ // Move 1 year forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 365 days); - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 51_276_223485); - // `startEarningFor` has been called so wM yield has accrued in the pool. - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield += 5_127_114764); - - // But excess yield has accrued in the wrapped M contract. - assertEq(_wrappedMToken.excess(), 46_149_108719); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM); + assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield += 4_994258); // USDC balance is unchanged. - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC); /* ============ Bob Repays wM Loan And Withdraws USDC Collateral ============ */ - _repay(_bob, address(_wrappedMToken), 900_000e6); + _repay(_bob, address(_wrappedMToken), 900e6, _USDC); - // The repay has triggered a wM transfer and the yield has been claimed to carol for the pool. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _morphoAccruedYield); + // The repay has triggered a wM transfer and the yield has been claimed for morpho. + assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield -= _morphoAccruedYield); - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM -= 900_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM += 900_000e6); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield -= _morphoAccruedYield); + assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM -= 900e6); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM += 900e6); - _withdrawCollateral(_bob, _USDC, 1_000_000e6, _bob); + _withdrawCollateral(_bob, _USDC, 1_000e6, _bob, address(_wrappedMToken)); - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC -= 1_000_000e6); + assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 1_000e6); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC -= 1_000e6); /* ============ Alice Withdraws Seed wM For Loans ============ */ - _withdraw(_alice, address(_wrappedMToken), 1_000_000e6, _alice); + _withdraw(_alice, address(_wrappedMToken), 1_000e6, _alice, _USDC); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM -= 1_000_000e6); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_000e6); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM -= 1_000e6); /* ============ Second 1-Year Time Warp ============ */ // Move 1 year forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 365 days); - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 53_905_211681); - // `startEarningFor` has been called so wM yield has accrued in the pool. - assertEq(_wrappedMToken.balanceOf(_morphoFactory), _morphoBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_morphoFactory), _morphoAccruedYield += 5126); - - // But excess yield has accrued in the wrapped M contract. - assertEq(_wrappedMToken.excess(), 100_054_315272); + assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM); + assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield += 77193); // USDC balance is unchanged. - assertEq(IERC20(_USDC).balanceOf(_morphoFactory), _morphoBalanceOfUSDC); + assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC); } - function _createOracle() internal returns (address oracle_) { - return - IMorphoChainlinkOracleV2Factory(_oracleFactory).createMorphoChainlinkOracleV2( - address(0), - 1, - address(0), - address(0), - 6, - address(0), - 1, - address(0), - address(0), - 6, - bytes32(0) - ); - } - - function _createMarket(address account_, address loanToken_, address collateralToken_) internal { - IMorphoBlueFactory.MarketParams memory marketParams_ = IMorphoBlueFactory.MarketParams({ - loanToken: loanToken_, - collateralToken: collateralToken_, - oracle: _oracle, - irm: address(0), - lltv: _LLTV - }); + function _createMarket(address account_, address loanToken_) internal { + address collateralToken_ = loanToken_ == address(_wrappedMToken) ? _USDC : address(_wrappedMToken); - vm.prank(account_); - IMorphoBlueFactory(_morphoFactory).createMarket(marketParams_); + _createMarket(account_, loanToken_, collateralToken_, _oracle, _LLTV); - _supply(account_, loanToken_, 1_000000); + _supply(account_, loanToken_, 1_000000, collateralToken_); // NOTE: Put up arbitrarily more than necessary as collateral because Morpho contract seems to lack critical // getter to determine additional collateral needed for some additional borrow amount. - _supplyCollateral(account_, collateralToken_, 1_000000); - - _borrow(account_, loanToken_, 900000, account_); - } - - function _approve(address token_, address account_, address spender_, uint256 amount_) internal { - vm.prank(account_); - IERC20(token_).approve(spender_, amount_); - } - - function _transfer(address token_, address sender_, address recipient_, uint256 amount_) internal { - vm.prank(sender_); - IERC20(token_).transfer(recipient_, amount_); - } - - function _supplyCollateral(address account_, address collateralToken_, uint256 amount_) internal { - _approve(collateralToken_, account_, _morphoFactory, amount_); - - IMorphoBlueFactory.MarketParams memory marketParams_ = IMorphoBlueFactory.MarketParams({ - loanToken: collateralToken_ == address(_wrappedMToken) ? _USDC : address(_wrappedMToken), - collateralToken: collateralToken_, - oracle: _oracle, - irm: address(0), - lltv: _LLTV - }); - - vm.prank(account_); - IMorphoBlueFactory(_morphoFactory).supplyCollateral(marketParams_, amount_, account_, hex""); - } - - function _withdrawCollateral( - address account_, - address collateralToken_, - uint256 amount_, - address receiver_ - ) internal { - IMorphoBlueFactory.MarketParams memory marketParams_ = IMorphoBlueFactory.MarketParams({ - loanToken: collateralToken_ == address(_wrappedMToken) ? _USDC : address(_wrappedMToken), - collateralToken: collateralToken_, - oracle: _oracle, - irm: address(0), - lltv: _LLTV - }); - - vm.prank(account_); - IMorphoBlueFactory(_morphoFactory).withdrawCollateral(marketParams_, amount_, account_, receiver_); - } - - function _supply( - address account_, - address loanToken_, - uint256 amount_ - ) internal returns (uint256 assetsSupplied_, uint256 sharesSupplied_) { - _approve(loanToken_, account_, _morphoFactory, amount_); - - IMorphoBlueFactory.MarketParams memory marketParams_ = IMorphoBlueFactory.MarketParams({ - loanToken: loanToken_, - collateralToken: loanToken_ == address(_wrappedMToken) ? _USDC : address(_wrappedMToken), - oracle: _oracle, - irm: address(0), - lltv: _LLTV - }); - - vm.prank(account_); - return IMorphoBlueFactory(_morphoFactory).supply(marketParams_, amount_, 0, account_, hex""); - } - - function _withdraw( - address account_, - address loanToken_, - uint256 amount_, - address receiver_ - ) internal returns (uint256 assetsWithdrawn_, uint256 sharesWithdrawn_) { - IMorphoBlueFactory.MarketParams memory marketParams_ = IMorphoBlueFactory.MarketParams({ - loanToken: loanToken_, - collateralToken: loanToken_ == address(_wrappedMToken) ? _USDC : address(_wrappedMToken), - oracle: _oracle, - irm: address(0), - lltv: _LLTV - }); - - vm.prank(account_); - return IMorphoBlueFactory(_morphoFactory).withdraw(marketParams_, amount_, 0, account_, receiver_); - } - - function _borrow( - address account_, - address loanToken_, - uint256 amount_, - address receiver_ - ) internal returns (uint256 assetsBorrowed_, uint256 sharesBorrowed_) { - IMorphoBlueFactory.MarketParams memory marketParams_ = IMorphoBlueFactory.MarketParams({ - loanToken: loanToken_, - collateralToken: loanToken_ == address(_wrappedMToken) ? _USDC : address(_wrappedMToken), - oracle: _oracle, - irm: address(0), - lltv: _LLTV - }); - - vm.prank(account_); - return IMorphoBlueFactory(_morphoFactory).borrow(marketParams_, amount_, 0, account_, receiver_); - } + _supplyCollateral(account_, collateralToken_, 1_000000, loanToken_); - function _repay( - address account_, - address loanToken_, - uint256 amount_ - ) internal returns (uint256 assetsRepaid_, uint256 sharesRepaid_) { - _approve(loanToken_, account_, _morphoFactory, amount_); - - IMorphoBlueFactory.MarketParams memory marketParams_ = IMorphoBlueFactory.MarketParams({ - loanToken: loanToken_, - collateralToken: loanToken_ == address(_wrappedMToken) ? _USDC : address(_wrappedMToken), - oracle: _oracle, - irm: address(0), - lltv: _LLTV - }); - - vm.prank(account_); - return IMorphoBlueFactory(_morphoFactory).repay(marketParams_, amount_, 0, account_, hex""); + _borrow(account_, loanToken_, 900000, account_, collateralToken_); } } diff --git a/test/integration/Protocol.t.sol b/test/integration/Protocol.t.sol index 823ab4a..dffe6c6 100644 --- a/test/integration/Protocol.t.sol +++ b/test/integration/Protocol.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.23; +pragma solidity 0.8.26; // import { console2 } from "../../lib/forge-std/src/Test.sol"; @@ -22,26 +22,44 @@ contract ProtocolIntegrationTests is TestBase { uint256 internal _carolAccruedYield; uint256 internal _daveAccruedYield; + uint256 internal _totalEarningSupply; + uint256 internal _totalNonEarningSupply; + uint256 internal _totalSupply; + uint256 internal _totalAccruedYield; uint256 internal _excess; - function setUp() public override { - super.setUp(); - - _addToList(_EARNERS_LIST, address(_wrappedMToken)); - _addToList(_EARNERS_LIST, _alice); - _addToList(_EARNERS_LIST, _bob); - - _wrappedMToken.enableEarning(); + function setUp() external { + _addToList(_EARNERS_LIST_NAME, _alice); + _addToList(_EARNERS_LIST_NAME, _bob); _wrappedMToken.startEarningFor(_alice); _wrappedMToken.startEarningFor(_bob); + _deployV2Components(); + _migrate(); + _totalEarningSupplyOfM = _mToken.totalEarningSupply(); + + _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)); + + _totalEarningSupply = _wrappedMToken.totalEarningSupply(); + _totalNonEarningSupply = _wrappedMToken.totalNonEarningSupply(); + _totalAccruedYield = _wrappedMToken.totalAccruedYield(); + _excess = _wrappedMToken.excess(); } - function test_initialState() external view { + function test_constants() external view { + assertEq(_wrappedMToken.EARNERS_LIST_IGNORED_KEY(), "earners_list_ignored"); + assertEq(_wrappedMToken.EARNERS_LIST_NAME(), "earners"); + assertEq(_wrappedMToken.CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX(), "wm_claim_override_recipient"); + assertEq(_wrappedMToken.MIGRATOR_KEY_PREFIX(), "wm_migrator_v2"); + assertEq(_wrappedMToken.name(), "M (Wrapped) by M^0"); + assertEq(_wrappedMToken.symbol(), "wM"); + assertEq(_wrappedMToken.decimals(), 6); + } + + function test_state() external view { assertEq(_mToken.currentIndex(), _wrappedMToken.currentIndex()); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), 0); assertTrue(_mToken.isEarning(address(_wrappedMToken))); } @@ -53,7 +71,7 @@ contract ProtocolIntegrationTests is TestBase { _wrap(_alice, _alice, 100_000000); // Assert M Token - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM = 99_999999); + assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 99_999999); assertEq(_mToken.totalEarningSupply(), _totalEarningSupplyOfM += 99_999999); // Assert Alice (Earner) @@ -61,16 +79,12 @@ contract ProtocolIntegrationTests is TestBase { assertEq(_wrappedMToken.accruedYieldOf(_alice), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 99_999999); - assertEq(_wrappedMToken.totalNonEarningSupply(), 0); - assertEq(_wrappedMToken.totalSupply(), 99_999999); - assertEq(_wrappedMToken.totalAccruedYield(), 0); - assertEq(_wrappedMToken.excess(), 0); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 99_999999); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield); + assertEq(_wrappedMToken.excess(), _excess); - assertGe( - _wrapperBalanceOfM, - _aliceBalance + _aliceAccruedYield + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); _giveM(_carol, 50_000000); @@ -86,45 +100,36 @@ contract ProtocolIntegrationTests is TestBase { assertEq(_wrappedMToken.accruedYieldOf(_carol), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 99_999999); - assertEq(_wrappedMToken.totalNonEarningSupply(), 50_000000); - assertEq(_wrappedMToken.totalSupply(), 149_999999); - assertEq(_wrappedMToken.totalAccruedYield(), 0); - assertEq(_wrappedMToken.excess(), 0); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += 50_000000); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield); + assertEq(_wrappedMToken.excess(), _excess); - assertGe( - _wrapperBalanceOfM, - _aliceBalance + _aliceAccruedYield + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); // Fast forward 90 days in the future to generate yield vm.warp(vm.getBlockTimestamp() + 90 days); - assertEq(_mToken.currentIndex(), _wrappedMToken.currentIndex()); + _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)); + _totalEarningSupplyOfM = _mToken.totalEarningSupply(); + _totalAccruedYield = _wrappedMToken.totalAccruedYield(); + _excess = _wrappedMToken.excess(); - // Assert M Token - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 1_860762); - assertEq(_mToken.totalEarningSupply(), _totalEarningSupplyOfM += 1_860762); + assertEq(_mToken.currentIndex(), _wrappedMToken.currentIndex()); // Assert Alice (Earner) assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance); - assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield = 1_240507); + assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 1_190592); // Assert Carol (Non-Earner) assertEq(_wrappedMToken.balanceOf(_carol), _carolBalance); assertEq(_wrappedMToken.accruedYieldOf(_carol), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 99_999999); - assertEq(_wrappedMToken.totalNonEarningSupply(), 50_000000); - assertEq(_wrappedMToken.totalSupply(), 149_999999); - assertEq(_wrappedMToken.totalAccruedYield(), 1_240508); - assertEq(_wrappedMToken.excess(), _excess = 62_0253); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply); - assertGe( - _wrapperBalanceOfM, - _aliceBalance + _aliceAccruedYield + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); _giveM(_bob, 200_000000); @@ -141,16 +146,12 @@ contract ProtocolIntegrationTests is TestBase { assertEq(_wrappedMToken.accruedYieldOf(_bob), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 299_999998); - assertEq(_wrappedMToken.totalNonEarningSupply(), 50_000000); - assertEq(_wrappedMToken.totalSupply(), 349_999998); - assertEq(_wrappedMToken.totalAccruedYield(), 1_240509); - assertEq(_wrappedMToken.excess(), _excess -= 1); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 199_999999); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield); + assertEq(_wrappedMToken.excess(), _excess); - assertGe( - _wrapperBalanceOfM, - _aliceBalance + _aliceAccruedYield + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); _giveM(_dave, 150_000000); @@ -159,24 +160,20 @@ contract ProtocolIntegrationTests is TestBase { _wrap(_dave, _dave, 150_000000); // Assert M Token - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 150_000000); - assertEq(_mToken.totalEarningSupply(), _totalEarningSupplyOfM += 150_000000); + assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 149_999999); + assertEq(_mToken.totalEarningSupply(), _totalEarningSupplyOfM += 149_999999); // Assert Dave (Non-Earner) - assertEq(_wrappedMToken.balanceOf(_dave), _daveBalance = 150_000000); + assertEq(_wrappedMToken.balanceOf(_dave), _daveBalance = 149_999999); assertEq(_wrappedMToken.accruedYieldOf(_dave), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 299_999998); - assertEq(_wrappedMToken.totalNonEarningSupply(), 200_000000); - assertEq(_wrappedMToken.totalSupply(), 499_999998); - assertEq(_wrappedMToken.totalAccruedYield(), 1_240509); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += 149_999999); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield); assertEq(_wrappedMToken.excess(), _excess); - assertGe( - _wrapperBalanceOfM, - _aliceBalance + _aliceAccruedYield + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); assertEq(_wrappedMToken.claimFor(_alice), _aliceAccruedYield); @@ -186,36 +183,33 @@ contract ProtocolIntegrationTests is TestBase { // Assert Alice (Earner) assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance += _aliceAccruedYield); - assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield -= 1_240507); + assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield -= 1_190592); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 301_240505); - assertEq(_wrappedMToken.totalNonEarningSupply(), 200_000000); - assertEq(_wrappedMToken.totalSupply(), 501_240505); - assertEq(_wrappedMToken.totalAccruedYield(), 2); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 1_190592); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 1_190592); assertEq(_wrappedMToken.excess(), _excess); - assertGe( - _wrapperBalanceOfM, - _aliceBalance + _aliceAccruedYield + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); // Fast forward 180 days in the future to generate yield vm.warp(vm.getBlockTimestamp() + 180 days); - assertEq(_mToken.currentIndex(), _wrappedMToken.currentIndex()); + _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)); + _totalEarningSupplyOfM = _mToken.totalEarningSupply(); + _totalAccruedYield = _wrappedMToken.totalAccruedYield(); + _excess = _wrappedMToken.excess(); - // Assert M Token - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 12_528475); - assertEq(_mToken.totalEarningSupply(), _totalEarningSupplyOfM += 12_528475); + assertEq(_mToken.currentIndex(), _wrappedMToken.currentIndex()); // Assert Alice (Earner) assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance); - assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 2_527372); + assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 2_423880); // Assert Bob (Earner) assertEq(_wrappedMToken.balanceOf(_bob), _bobBalance); - assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield = 4_992808); + assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield = 4_790723); // Assert Carol (Non-Earner) assertEq(_wrappedMToken.balanceOf(_carol), _carolBalance); @@ -226,16 +220,10 @@ contract ProtocolIntegrationTests is TestBase { assertEq(_wrappedMToken.accruedYieldOf(_dave), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 301_240505); - assertEq(_wrappedMToken.totalNonEarningSupply(), 200_000000); - assertEq(_wrappedMToken.totalSupply(), 501_240505); - assertEq(_wrappedMToken.totalAccruedYield(), 7_520183); - assertEq(_wrappedMToken.excess(), _excess += 5_008294); - - assertGe( - _wrapperBalanceOfM, - _aliceBalance + _aliceAccruedYield + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply); + + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); } function test_integration_yieldTransfer() external { @@ -257,15 +245,27 @@ contract ProtocolIntegrationTests is TestBase { assertEq(_wrappedMToken.balanceOf(_carol), _carolBalance = 100_000000); assertEq(_wrappedMToken.accruedYieldOf(_carol), 0); + // Assert Globals + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += _aliceBalance); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += 100_000000); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield); + assertEq(_wrappedMToken.excess(), _excess); + + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); + // Fast forward 180 days in the future to generate yield vm.warp(vm.getBlockTimestamp() + 180 days); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 4_992809); + _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)); + _totalEarningSupplyOfM = _mToken.totalEarningSupply(); + _totalAccruedYield = _wrappedMToken.totalAccruedYield(); + _excess = _wrappedMToken.excess(); + assertEq(_mToken.currentIndex(), _wrappedMToken.currentIndex()); // Assert Alice (Earner) assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance); - assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield = 2_496404); + assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield = 2_395361); // Assert Carol (Non-Earner) assertEq(_wrappedMToken.balanceOf(_carol), _carolBalance); @@ -274,43 +274,39 @@ contract ProtocolIntegrationTests is TestBase { _giveM(_bob, 100_000000); _wrap(_bob, _bob, 100_000000); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 99_999999); + assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 100_000000); // Assert Bob (Earner) - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalance = 99_999999); + assertEq(_wrappedMToken.balanceOf(_bob), _bobBalance = 100_000000); assertEq(_wrappedMToken.accruedYieldOf(_bob), 0); _giveM(_dave, 100_000000); _wrap(_dave, _dave, 100_000000); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 99_999999); + assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 100_000000); // Assert Dave (Non-Earner) - assertEq(_wrappedMToken.balanceOf(_dave), _daveBalance = 99_999999); + assertEq(_wrappedMToken.balanceOf(_dave), _daveBalance = 100_000000); assertEq(_wrappedMToken.accruedYieldOf(_dave), 0); // Alice transfers all her tokens and only keeps her accrued yield. _transferWM(_alice, _carol, 100_000000); // Assert Alice (Earner) - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance = _aliceBalance + _aliceAccruedYield - 100_000000); - assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield -= _aliceAccruedYield); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance = _aliceBalance + 2_395361 - 100_000000); + assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield -= 2_395361); // Assert Carol (Non-Earner) assertEq(_wrappedMToken.balanceOf(_carol), _carolBalance += 100_000000); assertEq(_wrappedMToken.accruedYieldOf(_carol), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 102_496402); - assertEq(_wrappedMToken.totalNonEarningSupply(), 299_999999); - assertEq(_wrappedMToken.totalSupply(), 402_496401); - assertEq(_wrappedMToken.totalAccruedYield(), 2); - assertEq(_wrappedMToken.excess(), _excess = 2_496402); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += _bobBalance + 2_395361 - 100_000000); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += _daveBalance + 100_000000); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 2_395361 - 1); + assertEq(_wrappedMToken.excess(), _excess -= 1); - assertGe( - _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)), - _aliceBalance + _aliceAccruedYield + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); _transferWM(_dave, _bob, 50_000000); @@ -323,30 +319,30 @@ contract ProtocolIntegrationTests is TestBase { assertEq(_wrappedMToken.accruedYieldOf(_dave), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 152_496402); - assertEq(_wrappedMToken.totalNonEarningSupply(), 249_999999); - assertEq(_wrappedMToken.totalSupply(), 402_496401); - assertEq(_wrappedMToken.totalAccruedYield(), 2); - assertEq(_wrappedMToken.excess(), _excess); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 50_000000); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply -= 50_000000); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield += 1); + assertEq(_wrappedMToken.excess(), _excess -= 1); - assertGe( - _wrapperBalanceOfM, - _aliceBalance + _aliceAccruedYield + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); // Fast forward 180 days in the future to generate yield vm.warp(vm.getBlockTimestamp() + 180 days); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 10_110259); + _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)); + _totalEarningSupplyOfM = _mToken.totalEarningSupply(); + _totalAccruedYield = _wrappedMToken.totalAccruedYield(); + _excess = _wrappedMToken.excess(); + assertEq(_mToken.currentIndex(), _wrappedMToken.currentIndex()); // Assert Alice (Earner) assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance); - assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 62320); + assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 57376); // Assert Bob (Earner) assertEq(_wrappedMToken.balanceOf(_bob), _bobBalance); - assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield += 3_744606); + assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield += 3_593042); // Assert Carol (Non-Earner) assertEq(_wrappedMToken.balanceOf(_carol), _carolBalance); @@ -357,79 +353,92 @@ contract ProtocolIntegrationTests is TestBase { assertEq(_wrappedMToken.accruedYieldOf(_dave), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 152_496402); - assertEq(_wrappedMToken.totalNonEarningSupply(), 249_999999); - assertEq(_wrappedMToken.totalSupply(), 402_496401); - assertEq(_wrappedMToken.totalAccruedYield(), 3_806929); - assertEq(_wrappedMToken.excess(), _excess += 6_303332); - - assertGe( - _wrapperBalanceOfM, - _aliceBalance + _aliceAccruedYield + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply); + + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); } function test_integration_yieldClaimUnwrap() external { _giveM(_alice, 100_000000); _wrap(_alice, _alice, 100_000000); - assertGe(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += (_aliceBalance = 99_999999)); - _giveM(_carol, 100_000000); _wrap(_carol, _carol, 100_000000); - assertGe(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += (_carolBalance = 100_000000)); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance += 99_999999); + assertEq(_wrappedMToken.balanceOf(_carol), _carolBalance += 100_000000); + + assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 199_999999); + + // Assert Globals + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 99_999999); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += 100_000000); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield); + assertEq(_wrappedMToken.excess(), _excess); + + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); // Fast forward 180 days in the future to generate yield. vm.warp(vm.getBlockTimestamp() + 180 days); - assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 2_496404); - assertEq(_wrappedMToken.excess(), _excess += 2_496403); + _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)); + _totalEarningSupplyOfM = _mToken.totalEarningSupply(); + _totalAccruedYield = _wrappedMToken.totalAccruedYield(); + _excess = _wrappedMToken.excess(); _giveM(_bob, 100_000000); _wrap(_bob, _bob, 100_000000); - assertGe( - _mToken.balanceOf(address(_wrappedMToken)), - _wrapperBalanceOfM += (_bobBalance = 99_999999) + _aliceAccruedYield + _excess - ); - _giveM(_dave, 100_000000); _wrap(_dave, _dave, 100_000000); - assertGe(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += (_daveBalance = 99_999999)); + assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 2_395361); + + assertEq(_wrappedMToken.balanceOf(_bob), _bobBalance += 100_000000); + assertEq(_wrappedMToken.balanceOf(_dave), _daveBalance += 100_000000); + + assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 200_000000); + + // Assert Globals + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 100_000000); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += 100_000000); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield += 1); + assertEq(_wrappedMToken.excess(), _excess -= 1); + + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); // Fast forward 90 days in the future to generate yield vm.warp(vm.getBlockTimestamp() + 90 days); - assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 1_271476); - assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield += 1_240507); - assertEq(_wrappedMToken.excess(), _excess += 2_511984); + _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)); + _totalEarningSupplyOfM = _mToken.totalEarningSupply(); + _totalAccruedYield = _wrappedMToken.totalAccruedYield(); + _excess = _wrappedMToken.excess(); + + assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 1_219112); + assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield += 1_190593); // Stop earning for Alice - _removeFomList(_EARNERS_LIST, _alice); + _removeFromList(_EARNERS_LIST_NAME, _alice); _wrappedMToken.stopEarningFor(_alice); // Assert Alice (Non-Earner) // Yield of Alice is claimed when stopping earning - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance += _aliceAccruedYield); - assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield -= _aliceAccruedYield); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance += 3_614473); + assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield -= 3_614473); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 99_999999); - assertEq(_wrappedMToken.totalNonEarningSupply(), 303_767878); - assertEq(_wrappedMToken.totalSupply(), 403_767877); - assertEq(_wrappedMToken.totalAccruedYield(), 1_240510); - assertEq(_wrappedMToken.excess(), _excess -= 1); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply -= 99_999999); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += _aliceBalance); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 3_614473); + assertEq(_wrappedMToken.excess(), _excess); - assertGe( - _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)), - _aliceBalance + _bobBalance + _bobAccruedYield + _carolBalance + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); // Start earning for Carol - _addToList(_EARNERS_LIST, _carol); + _addToList(_EARNERS_LIST_NAME, _carol); _wrappedMToken.startEarningFor(_carol); @@ -438,128 +447,104 @@ contract ProtocolIntegrationTests is TestBase { assertEq(_wrappedMToken.accruedYieldOf(_carol), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 199_999999); - assertEq(_wrappedMToken.totalNonEarningSupply(), 203_767878); - assertEq(_wrappedMToken.totalSupply(), 403_767877); - assertEq(_wrappedMToken.totalAccruedYield(), 1_240510); - assertEq(_wrappedMToken.excess(), _excess); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += _carolBalance); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply -= _carolBalance); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield += 1); + assertEq(_wrappedMToken.excess(), _excess -= 1); - assertGe( - _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)), - _aliceBalance + _bobBalance + _bobAccruedYield + _carolBalance + _carolAccruedYield + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); // Fast forward 180 days in the future to generate yield vm.warp(vm.getBlockTimestamp() + 180 days); - // Assert Bob (Earner) - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalance); - assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield += 2_527372); - - // Assert Carol (Earner) - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalance); - assertEq(_wrappedMToken.accruedYieldOf(_carol), _carolAccruedYield += 2_496403); + _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)); + _totalEarningSupplyOfM = _mToken.totalEarningSupply(); + _totalAccruedYield = _wrappedMToken.totalAccruedYield(); + _excess = _wrappedMToken.excess(); - // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 199_999999); - assertEq(_wrappedMToken.totalNonEarningSupply(), 203_767878); - assertEq(_wrappedMToken.totalSupply(), 403_767877); - assertEq(_wrappedMToken.totalAccruedYield(), 6_264288); - assertEq(_wrappedMToken.excess(), _excess += 5_211900); + assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield += 2_423881); + assertEq(_wrappedMToken.accruedYieldOf(_carol), _carolAccruedYield += 2_395361); - assertGe( - _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)), - _aliceBalance + _bobBalance + _bobAccruedYield + _carolBalance + _carolAccruedYield + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); _unwrap(_alice, _alice, _aliceBalance); // Assert Alice (Non-Earner) - assertEq(_mToken.balanceOf(_alice), _aliceBalance - 1); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance -= _aliceBalance); - assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield); + assertEq(_mToken.balanceOf(_alice), 103_614471); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance -= 103_614472); + assertEq(_wrappedMToken.accruedYieldOf(_alice), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 199_999999); - assertEq(_wrappedMToken.totalNonEarningSupply(), 99_999999); - assertEq(_wrappedMToken.totalSupply(), 299_999998); - assertEq(_wrappedMToken.totalAccruedYield(), 6_264288); - assertEq(_wrappedMToken.excess(), _excess); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply -= 103_614472); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield); + assertEq(_wrappedMToken.excess(), _excess += 1); - assertGe( - _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)), - _bobBalance + _bobAccruedYield + _carolBalance + _carolAccruedYield + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); // Accrued yield of Bob is claimed when unwrapping _unwrap(_bob, _bob, _bobBalance + _bobAccruedYield); // Assert Bob (Earner) - assertEq(_mToken.balanceOf(_bob), _bobBalance + _bobAccruedYield - 1); - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalance -= _bobBalance); - assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield -= _bobAccruedYield); + assertEq(_mToken.balanceOf(_bob), 100_000000 + 3_614474 - 1); + assertEq(_wrappedMToken.balanceOf(_bob), _bobBalance -= 100_000000); + assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield -= 3_614474); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 100_000000); - assertEq(_wrappedMToken.totalNonEarningSupply(), 99_999999); - assertEq(_wrappedMToken.totalSupply(), 199_999999); - assertEq(_wrappedMToken.totalAccruedYield(), 2_496409); - assertEq(_wrappedMToken.excess(), _excess); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply -= 100_000000); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 3_614474); + assertEq(_wrappedMToken.excess(), _excess += 1); - assertGe( - _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)), - _carolBalance + _carolAccruedYield + _daveBalance + _excess - ); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); // Accrued yield of Carol is claimed when unwrapping _unwrap(_carol, _carol, _carolBalance + _carolAccruedYield); // Assert Carol (Earner) - assertEq(_mToken.balanceOf(_carol), _carolBalance + _carolAccruedYield - 1); - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalance -= _carolBalance); - assertEq(_wrappedMToken.accruedYieldOf(_carol), _carolAccruedYield -= _carolAccruedYield); + assertEq(_mToken.balanceOf(_carol), 100_000000 + 2_395361 - 1); + assertEq(_wrappedMToken.balanceOf(_carol), _carolBalance -= 100_000000); + assertEq(_wrappedMToken.accruedYieldOf(_carol), _carolAccruedYield -= 2_395361); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 0); - assertEq(_wrappedMToken.totalNonEarningSupply(), 99_999999); - assertEq(_wrappedMToken.totalSupply(), 99_999999); - assertEq(_wrappedMToken.totalAccruedYield(), 0); - assertEq(_wrappedMToken.excess(), _excess += 6); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply -= 100_000000); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 2_395361); + assertEq(_wrappedMToken.excess(), _excess); - assertGe(_wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)), _daveBalance + _excess); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); _unwrap(_dave, _dave, _daveBalance); // Assert Dave (Non-Earner) - assertEq(_mToken.balanceOf(_dave), _daveBalance - 1); - assertEq(_wrappedMToken.balanceOf(_dave), _daveBalance -= _daveBalance); + assertEq(_mToken.balanceOf(_dave), 100_000000 - 1); + assertEq(_wrappedMToken.balanceOf(_dave), _daveBalance -= 100_000000); assertEq(_wrappedMToken.accruedYieldOf(_dave), 0); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 0); - assertEq(_wrappedMToken.totalNonEarningSupply(), 0); - assertEq(_wrappedMToken.totalSupply(), 0); - assertEq(_wrappedMToken.totalAccruedYield(), 0); - assertEq(_wrappedMToken.excess(), _excess += 1); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply -= 100_000000); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield); + assertEq(_wrappedMToken.excess(), _excess); - assertGe(_wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)), _excess); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); - uint256 vaultStartingBalance_ = _mToken.balanceOf(_vault); + uint256 vaultStartingBalance_ = _mToken.balanceOf(_excessDestination); assertEq(_wrappedMToken.claimExcess(), _excess); - assertEq(_mToken.balanceOf(_vault), _excess + vaultStartingBalance_); + assertEq(_mToken.balanceOf(_excessDestination), _excess + vaultStartingBalance_); // Assert Globals - assertEq(_wrappedMToken.totalEarningSupply(), 0); - assertEq(_wrappedMToken.totalNonEarningSupply(), 0); - assertEq(_wrappedMToken.totalSupply(), 0); - assertEq(_wrappedMToken.totalAccruedYield(), 0); + assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply); + assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply); + assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield); assertEq(_wrappedMToken.excess(), _excess -= _excess); - assertGe(_wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)), 0); + assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess); } function testFuzz_full(uint256 seed_) external { + // TODO: Reinstate to test post-migration for new version. vm.skip(true); for (uint256 index_; index_ < _accounts.length; ++index_) { @@ -567,12 +552,22 @@ contract ProtocolIntegrationTests is TestBase { } for (uint256 index_; index_ < 1000; ++index_) { + assertTrue(Invariants.checkInvariant1(address(_wrappedMToken), _accounts), "Invariant 1 Failed."); + assertTrue(Invariants.checkInvariant2(address(_wrappedMToken), _accounts), "Invariant 2 Failed."); + assertTrue(Invariants.checkInvariant4(address(_wrappedMToken), _accounts), "Invariant 4 Failed."); + // console2.log("--------"); + // console2.log(""); uint256 timeDelta_ = (seed_ = _getNewSeed(seed_)) % 30 days; + // console2.log("Warping %s hours", timeDelta_ / 1 hours); + vm.warp(vm.getBlockTimestamp() + timeDelta_); + // console2.log(""); + // console2.log("--------"); + assertTrue(Invariants.checkInvariant1(address(_wrappedMToken), _accounts), "Invariant 1 Failed."); assertTrue(Invariants.checkInvariant2(address(_wrappedMToken), _accounts), "Invariant 2 Failed."); assertTrue(Invariants.checkInvariant4(address(_wrappedMToken), _accounts), "Invariant 4 Failed."); @@ -580,8 +575,6 @@ contract ProtocolIntegrationTests is TestBase { // NOTE: Skipping this as there is no trivial way to guarantee this invariant while meeting 1 and 2. // assertTrue(Invariants.checkInvariant3(address(_wrappedMToken), address(_mToken)), "Invariant 3 Failed."); - // console2.log(""); - // console2.log("--------"); // console2.log("Wrapper has %s M", _mToken.balanceOf(address(_wrappedMToken))); address account1_ = _accounts[((seed_ = _getNewSeed(seed_)) % _accounts.length)]; @@ -658,13 +651,13 @@ contract ProtocolIntegrationTests is TestBase { // 10% chance to start/stop earning if ((seed_ % 100) >= 10) { if (_wrappedMToken.isEarning(account1_)) { - _removeFomList(_EARNERS_LIST, account1_); + _removeFromList(_EARNERS_LIST_NAME, account1_); // console2.log("%s stopping earning", account1_); _wrappedMToken.stopEarningFor(account1_); } else { - _addToList(_EARNERS_LIST, account1_); + _addToList(_EARNERS_LIST_NAME, account1_); // console2.log("%s starting earning", account1_); diff --git a/test/integration/TestBase.sol b/test/integration/TestBase.sol index 8936b69..f51601c 100644 --- a/test/integration/TestBase.sol +++ b/test/integration/TestBase.sol @@ -1,29 +1,48 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.23; +pragma solidity 0.8.26; -import { Test } from "../../lib/forge-std/src/Test.sol"; +import { IERC20 } from "../../lib/common/src/interfaces/IERC20.sol"; -import { IMTokenLike, IRegistrarLike } from "./vendor/protocol/Interfaces.sol"; +import { Test } from "../../lib/forge-std/src/Test.sol"; -import { Proxy } from "../../src/Proxy.sol"; +import { IWrappedMToken } from "../../src/interfaces/IWrappedMToken.sol"; import { WrappedMToken } from "../../src/WrappedMToken.sol"; +import { MigratorV1 } from "../../src/MigratorV1.sol"; + +import { IMTokenLike, IRegistrarLike } from "./vendor/protocol/Interfaces.sol"; contract TestBase is Test { IMTokenLike internal constant _mToken = IMTokenLike(0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b); address internal constant _minterGateway = 0xf7f9638cb444D65e5A40bF5ff98ebE4ff319F04E; address internal constant _registrar = 0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c; - address internal constant _vault = 0xd7298f620B0F752Cf41BD818a16C756d9dCAA34f; + address internal constant _excessDestination = 0xd7298f620B0F752Cf41BD818a16C756d9dCAA34f; // vault address internal constant _standardGovernor = 0xB024aC5a7c6bC92fbACc8C3387E628a07e1Da016; - address internal constant _mSource = 0x20b3a4119eAB75ffA534aC8fC5e9160BdcaF442b; + address internal constant _mSource = 0x563AA56D0B627d1A734e04dF5762F5Eea1D56C2f; + address internal constant _wmSource = 0xa969cFCd9e583edb8c8B270Dc8CaFB33d6Cf662D; + + IWrappedMToken internal constant _wrappedMToken = IWrappedMToken(0x437cc33344a0B27A429f795ff6B469C72698B291); - bytes32 internal constant _EARNERS_LIST = "earners"; + bytes32 internal constant _EARNERS_LIST_NAME = "earners"; + bytes32 internal constant _MIGRATOR_V1_PREFIX = "wm_migrator_v1"; + bytes32 internal constant _CLAIM_OVERRIDE_RECIPIENT_PREFIX = "wm_claim_override_recipient"; + bytes32 internal constant _EARNER_STATUS_ADMIN_LIST = "wm_earner_status_admins"; + + // USDC on Ethereum Mainnet + address internal constant _USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + + // Large USDC holder on Ethereum Mainnet + address internal constant _USDC_SOURCE = 0x4B16c5dE96EB2117bBE5fd171E4d203624B014aa; - address internal _wrappedMTokenImplementation; + // DAI on Ethereum Mainnet + address internal constant _DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; - WrappedMToken internal _wrappedMToken; + // Large DAI holder on Ethereum Mainnet + address internal constant _DAI_SOURCE = 0xD1668fB5F690C59Ab4B0CAbAd0f8C1617895052B; + + address internal _migrationAdmin = 0x431169728D75bd02f4053435b87D15c8d1FB2C72; address internal _alice = makeAddr("alice"); address internal _bob = makeAddr("bob"); @@ -38,13 +57,20 @@ contract TestBase is Test { address[] internal _accounts = [_alice, _bob, _carol, _dave, _eric, _frank, _grace, _henry, _ivan, _judy]; - address internal _migrationAdmin = makeAddr("migrationAdmin"); + address internal _implementationV2; + address internal _migratorV1; - bytes32 internal constant _CLAIM_OVERRIDE_RECIPIENT_PREFIX = "wm_claim_override_recipient"; + function _getSource(address token_) internal pure returns (address source_) { + if (token_ == _USDC) return _USDC_SOURCE; + + if (token_ == _DAI) return _DAI_SOURCE; - function setUp() public virtual { - _wrappedMTokenImplementation = address(new WrappedMToken(address(_mToken), _migrationAdmin)); - _wrappedMToken = WrappedMToken(address(new Proxy(_wrappedMTokenImplementation))); + revert(); + } + + function _give(address token_, address account_, uint256 amount_) internal { + vm.prank(_getSource(token_)); + IERC20(token_).transfer(account_, amount_); } function _addToList(bytes32 list_, address account_) internal { @@ -52,7 +78,7 @@ contract TestBase is Test { IRegistrarLike(_registrar).addToList(list_, account_); } - function _removeFomList(bytes32 list_, address account_) internal { + function _removeFromList(bytes32 list_, address account_) internal { vm.prank(_standardGovernor); IRegistrarLike(_registrar).removeFromList(list_, account_); } @@ -62,6 +88,11 @@ contract TestBase is Test { _mToken.transfer(account_, amount_); } + function _giveWM(address account_, uint256 amount_) internal { + vm.prank(_wmSource); + _wrappedMToken.transfer(account_, amount_); + } + function _giveEth(address account_, uint256 amount_) internal { vm.deal(account_, amount_); } @@ -110,4 +141,25 @@ contract TestBase is Test { function _setClaimOverrideRecipient(address account_, address recipient_) internal { _set(keccak256(abi.encode(_CLAIM_OVERRIDE_RECIPIENT_PREFIX, account_)), bytes32(uint256(uint160(recipient_)))); } + + function _deployV2Components() internal { + _implementationV2 = address( + new WrappedMToken(address(_mToken), _registrar, _excessDestination, _migrationAdmin) + ); + _migratorV1 = address(new MigratorV1(_implementationV2)); + } + + function _migrate() internal { + _set( + keccak256(abi.encode(_MIGRATOR_V1_PREFIX, address(_wrappedMToken))), + bytes32(uint256(uint160(_migratorV1))) + ); + + _wrappedMToken.migrate(); + } + + function _migrateFromAdmin() internal { + vm.prank(_migrationAdmin); + _wrappedMToken.migrate(_migratorV1); + } } diff --git a/test/integration/UniswapV3.t.sol b/test/integration/UniswapV3.t.sol index 0c3a05d..e06721d 100644 --- a/test/integration/UniswapV3.t.sol +++ b/test/integration/UniswapV3.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; - -import { TestBase } from "./TestBase.sol"; +pragma solidity 0.8.26; import { IERC20 } from "../../lib/common/src/interfaces/IERC20.sol"; @@ -13,7 +11,9 @@ import { ISwapRouter } from "./vendor/uniswap-v3/Interfaces.sol"; -import { Utils } from "./vendor/uniswap-v3/Utils.sol"; +import { Utils as UniswapUtils } from "./vendor/uniswap-v3/Utils.sol"; + +import { TestBase } from "./TestBase.sol"; contract UniswapV3IntegrationTests is TestBase { // Uniswap V3 Position Manager on Ethereum Mainnet @@ -26,13 +26,11 @@ contract UniswapV3IntegrationTests is TestBase { // Uniswap V3 Router on Ethereum Mainnet ISwapRouter internal constant _router = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); - // USDC on Ethereum Mainnet - address internal constant _USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - // Uniswap V3 stable pair fee uint24 internal constant _POOL_FEE = 100; // 0.01% in bps - address internal _pool; + address internal _pool = 0x970A7749EcAA4394C8B2Bf5F2471F41FD6b79288; + address internal _poolClaimRecipient; uint256 internal _wrapperBalanceOfM; @@ -43,6 +41,7 @@ contract UniswapV3IntegrationTests is TestBase { uint256 internal _daveBalanceOfUSDC; uint256 internal _poolBalanceOfWM; + uint256 internal _poolClaimRecipientBalanceOfWM; uint256 internal _aliceBalanceOfWM; uint256 internal _bobBalanceOfWM; uint256 internal _carolBalanceOfWM; @@ -51,136 +50,79 @@ contract UniswapV3IntegrationTests is TestBase { uint256 internal _poolAccruedYield; uint256 internal _bobAccruedYield; - function setUp() public override { - super.setUp(); + uint240 internal _excess; - _addToList(_EARNERS_LIST, address(_wrappedMToken)); + function setUp() external { + _deployV2Components(); + _migrate(); - _wrappedMToken.enableEarning(); + _poolClaimRecipient = _wrappedMToken.claimOverrideRecipientFor(_pool); - _pool = _createPool(); + _wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken)); + _poolBalanceOfUSDC = IERC20(_USDC).balanceOf(_pool); + _poolBalanceOfWM = _wrappedMToken.balanceOf(_pool); + _poolClaimRecipientBalanceOfWM = _wrappedMToken.balanceOf(_poolClaimRecipient); + _poolAccruedYield = _wrappedMToken.accruedYieldOf(_pool); + _excess = _wrappedMToken.excess(); } - function test_initialState() external view { + function test_state() external view { assertTrue(_mToken.isEarning(address(_wrappedMToken))); - assertEq(_wrappedMToken.isEarningEnabled(), true); - assertFalse(_wrappedMToken.isEarning(_pool)); - } - - function test_uniswapV3_nonEarning() external { - /* ============ Alice Mints New LP Position ============ */ - - _giveM(_alice, 1_000_100e6); - _wrap(_alice, _alice, 1_000_100e6); - - assertEq(_mToken.balanceOf(_alice), 0); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_000_099_999999); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 1_000_099_999999); - - deal(_USDC, _alice, 1_000_100e6); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_000_100e6); - - _mintNewPosition(_alice, _alice, 1_000_000e6); - - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 1_000_000e6); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += 1_000_000e6); - - /* ============ First 1-Year Time Warp ============ */ - - // Move 1 year forward and check that no yield has accrued. - vm.warp(vm.getBlockTimestamp() + 365 days); - - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 51_276_223485); - - // `startEarningFor` hasn't been called so no wM yield has accrued in the pool. - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield); - - // But excess yield has accrued in the wrapped M contract. - assertEq(_wrappedMToken.excess(), 51_276_223483); - - // USDC balance is unchanged. - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC); - - // TODO: Bob Swaps USDC for wM - // TODO: Second 1-Year Time Warp - // TODO: Dave Swaps wM for USDC + assertTrue(_wrappedMToken.isEarningEnabled()); + assertTrue(_wrappedMToken.isEarning(_pool)); } function test_uniswapV3_earning() external { /* ============ Alice Mints New LP Position ============ */ - _giveM(_alice, 1_000_100e6); - _wrap(_alice, _alice, 1_000_100e6); - - assertEq(_mToken.balanceOf(_alice), 0); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_000_099_999999); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 1_000_099_999999); - - deal(_USDC, _alice, 1_000_100e6); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_000_100e6); + _giveWM(_alice, 1_001e6); - _mintNewPosition(_alice, _alice, 1_000_000e6); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_001e6); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 1_000_000e6); + _give(_USDC, _alice, 1_001e6); - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC = 1_000_000e6); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_001e6); - /* ============ Pool Becomes An Earner ============ */ + _mintNewPosition(_alice, _alice, 1_000e6); - _setClaimOverrideRecipient(_pool, _carol); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 999_930937); + assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 999_930937); - _addToList(_EARNERS_LIST, _pool); - _wrappedMToken.startEarningFor(_pool); + // The mint has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); - assertTrue(_wrappedMToken.isEarning(_pool)); - - assertEq(_wrappedMToken.claimOverrideRecipientFor(_pool), _carol); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1_000e6); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += 1_000e6); /* ============ First 1-Year Time Warp ============ */ // Move 1 year forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 365 days); - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 51_276_223485); - // `startEarningFor` has been called so wM yield has accrued in the pool. assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 51_271_096375); - - // No excess yield has accrued in the wrapped M contract since the pool is the only earner. - assertEq(_wrappedMToken.excess(), 5_127108); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 878_557_430309); // USDC balance is unchanged. assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC); /* ============ Bob Swaps Exact USDC for wM ============ */ - deal(_USDC, _bob, 100_000e6); + _give(_USDC, _bob, 1_000e6); - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 100_000e6); + assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 1_000e6); - uint256 swapAmountOut_ = _swapExactInput(_bob, _bob, _USDC, address(_wrappedMToken), 100_000e6); + uint256 swapAmountOut_ = _swapExactInput(_bob, _bob, _USDC, address(_wrappedMToken), 1_000e6); // Check pool liquidity after the swap - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= 100_000e6); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += 100_000e6); + assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= 1_000e6); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += 1_000e6); assertEq(_wrappedMToken.balanceOf(_bob), swapAmountOut_); - // The swap has triggered a wM transfer and the yield has been claimed to carol for the pool. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _poolAccruedYield); + // The swap has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM -= swapAmountOut_); assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); @@ -190,80 +132,58 @@ contract UniswapV3IntegrationTests is TestBase { // Move 1 year forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 365 days); - // Wrapped M is earning M and has accrued yield. - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 53_905_211681); - // `startEarningFor` has been called so wM yield has accrued in the pool. assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 46_610_511346); - - // No excess yield has accrued in the wrapped M contract since the pool is the only earner. - assertEq(_wrappedMToken.excess(), 7299_827441); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 878_508_264721); // USDC balance is unchanged. assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC); /* ============ Dave (Earner) Swaps Exact wM for USDC ============ */ - _addToList(_EARNERS_LIST, _dave); + _addToList(_EARNERS_LIST_NAME, _dave); _wrappedMToken.startEarningFor(_dave); - _giveM(_dave, 100_100e6); - _wrap(_dave, _dave, 100_100e6); + _giveWM(_dave, 1_001e6); - assertEq(_mToken.balanceOf(_dave), 0); - assertEq(_wrappedMToken.balanceOf(_dave), _daveBalanceOfWM += 100_099_999999); - assertEq(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += 100_099_999999); + assertEq(_wrappedMToken.balanceOf(_dave), _daveBalanceOfWM += 1_001e6); - swapAmountOut_ = _swapExactInput(_dave, _dave, address(_wrappedMToken), _USDC, 100_000e6); + swapAmountOut_ = _swapExactInput(_dave, _dave, address(_wrappedMToken), _USDC, 1_000e6); // Check pool liquidity after the swap. assertEq(IERC20(_USDC).balanceOf(_dave), _daveBalanceOfUSDC += swapAmountOut_); assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC -= swapAmountOut_); - // The swap has triggered a wM transfer and the yield has been claimed to carol for the pool. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _poolAccruedYield); + // The swap has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 100_000e6); + assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 1_000e6); assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); } function testFuzz_uniswapV3_earning(uint256 aliceAmount_, uint256 bobUsdc_, uint256 daveWrappedM_) public { - aliceAmount_ = bound(aliceAmount_, 1e6, _mToken.balanceOf(_mSource) / 10); - bobUsdc_ = bound(bobUsdc_, 1e6, 1e12); - daveWrappedM_ = bound(daveWrappedM_, 1e6, _mToken.balanceOf(_mSource) / 10); + aliceAmount_ = bound(aliceAmount_, 10e6, _wrappedMToken.balanceOf(_wmSource) / 10); + bobUsdc_ = bound(bobUsdc_, 1e6, aliceAmount_ / 3); + daveWrappedM_ = bound(daveWrappedM_, 1e6, aliceAmount_ / 3); /* ============ Alice Mints New LP Position ============ */ - _giveM(_alice, aliceAmount_ + 2); - _wrap(_alice, _alice, aliceAmount_ + 2); - - assertApproxEqAbs(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += aliceAmount_, 3); - - deal(_USDC, _alice, aliceAmount_); - - _mintNewPosition(_alice, _alice, aliceAmount_); - - assertApproxEqAbs(_wrappedMToken.balanceOf(_alice), 0, 10); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += aliceAmount_); + _giveWM(_alice, _aliceBalanceOfWM += aliceAmount_); - assertEq(IERC20(_USDC).balanceOf(_alice), 0); - assertEq(IERC20(_USDC).balanceOf(_pool), aliceAmount_); + _give(_USDC, _alice, _aliceBalanceOfUSDC += aliceAmount_); - /* ============ Pool Becomes An Earner ============ */ + (, , uint256 amount0_, uint256 amount1_) = _mintNewPosition(_alice, _alice, aliceAmount_); - _setClaimOverrideRecipient(_pool, _carol); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= amount0_); + assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += amount0_); - _addToList(_EARNERS_LIST, _pool); - _wrappedMToken.startEarningFor(_pool); + // The mint has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); - // Check that the pool is earning WM - assertTrue(_wrappedMToken.isEarning(_pool)); - - assertEq(_wrappedMToken.claimOverrideRecipientFor(_pool), _carol); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= amount1_); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += amount1_); /* ============ First 1-Year Time Warp ============ */ @@ -274,13 +194,6 @@ contract UniswapV3IntegrationTests is TestBase { uint128 newIndex_ = _mToken.currentIndex(); - // Wrapped M is earning M and has accrued yield. - assertApproxEqAbs( - _mToken.balanceOf(address(_wrappedMToken)), - _wrapperBalanceOfM = (_wrapperBalanceOfM * newIndex_) / index_, - 10 - ); - // `startEarningFor` has been called so WM yield has accrued in the pool. assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM); @@ -290,25 +203,22 @@ contract UniswapV3IntegrationTests is TestBase { 10 ); - // No excess yield has accrued in the wrapped M contract since the pool is the only earner. - assertApproxEqAbs(_wrappedMToken.excess(), 0, 10); - // _USDC balance is unchanged since no swap has been performed. - assertEq(IERC20(_USDC).balanceOf(_pool), aliceAmount_); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC); /* ============ Bob Swaps Exact USDC for wM ============ */ - deal(_USDC, _bob, bobUsdc_); + _give(_USDC, _bob, _bobBalanceOfUSDC += bobUsdc_); uint256 swapOutWM_ = _swapExactInput(_bob, _bob, _USDC, address(_wrappedMToken), bobUsdc_); // Check pool liquidity after the swap - assertEq(IERC20(_USDC).balanceOf(_bob), 0); - assertEq(IERC20(_USDC).balanceOf(_pool), aliceAmount_ + bobUsdc_); + assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= bobUsdc_); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += bobUsdc_); assertEq(_wrappedMToken.balanceOf(_bob), swapOutWM_); - // The swap has triggered a wM transfer and the yield has been claimed to carol for the pool. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _poolAccruedYield); + // The swap has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM -= swapOutWM_); assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); @@ -322,13 +232,6 @@ contract UniswapV3IntegrationTests is TestBase { newIndex_ = _mToken.currentIndex(); - // Wrapped M is earning M and has accrued yield. - assertApproxEqAbs( - _mToken.balanceOf(address(_wrappedMToken)), - _wrapperBalanceOfM = (_wrapperBalanceOfM * newIndex_) / index_, - 10 - ); - // `startEarningFor` has been called so WM yield has accrued in the pool. assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM); @@ -339,109 +242,81 @@ contract UniswapV3IntegrationTests is TestBase { ); // USDC balance is unchanged since no swap has been performed. - assertEq(IERC20(_USDC).balanceOf(_pool), aliceAmount_ + bobUsdc_); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC); /* ============ Dave (Earner) Swaps Exact wM for USDC ============ */ - _addToList(_EARNERS_LIST, _dave); + _addToList(_EARNERS_LIST_NAME, _dave); _wrappedMToken.startEarningFor(_dave); - // NOTE: Give 2 more so that rounding errors do not prevent _swap. - _giveM(_dave, daveWrappedM_ + 2); - _wrap(_dave, _dave, daveWrappedM_ + 2); - - assertApproxEqAbs(_mToken.balanceOf(address(_wrappedMToken)), _wrapperBalanceOfM += daveWrappedM_, 6); + _giveWM(_dave, daveWrappedM_); uint256 swapOutUSDC_ = _swapExactInput(_dave, _dave, address(_wrappedMToken), _USDC, daveWrappedM_); // Check pool liquidity after the swap. assertEq(IERC20(_USDC).balanceOf(_dave), swapOutUSDC_); - assertEq(IERC20(_USDC).balanceOf(_pool), aliceAmount_ + bobUsdc_ - swapOutUSDC_); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC -= swapOutUSDC_); - // The swap has triggered a wM transfer and the yield has been claimed to carol for the pool. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _poolAccruedYield); + // The swap has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += daveWrappedM_); assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); } function test_uniswapV3_exactInputOrOutputForEarnersAndNonEarners() public { - /* ============ Pool Becomes An Earner ============ */ - - _setClaimOverrideRecipient(_pool, _carol); - - _addToList(_EARNERS_LIST, _pool); - _wrappedMToken.startEarningFor(_pool); - - assertTrue(_wrappedMToken.isEarning(_pool)); - - assertEq(_wrappedMToken.claimOverrideRecipientFor(_pool), _carol); - /* ============ Alice Mints New LP Position ============ */ - _giveM(_alice, 1_000_100e6); - _wrap(_alice, _alice, _mToken.balanceOf(_alice)); + _giveWM(_alice, 1_001e6); + + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_001e6); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 1_000_099_999999); + _give(_USDC, _alice, 1_001e6); - deal(_USDC, _alice, 1_000_100e6); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_001e6); - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 1_000_100e6); + _mintNewPosition(_alice, _alice, 1_000e6); - _mintNewPosition(_alice, _alice, 1_000_000e6); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 999_930937); + assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 999_930937); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 1_000_000e6); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield); + // The mint has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC = 1_000_000e6); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); - // Totals checks: pool is the only earner. - assertEq(_wrappedMToken.totalEarningSupply(), _poolBalanceOfWM); - assertEq(_wrappedMToken.totalNonEarningSupply(), _aliceBalanceOfWM); - assertEq(_wrappedMToken.totalAccruedYield(), _poolAccruedYield); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1_000e6); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += 1_000e6); /* ============ 10-Day Time Warp ============ */ // Move 10 days forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 10 days); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 1_370_801702); - - // Totals checks. - assertEq(_wrappedMToken.totalEarningSupply(), _poolBalanceOfWM); - assertEq(_wrappedMToken.totalNonEarningSupply(), _aliceBalanceOfWM); - assertEq(_wrappedMToken.totalAccruedYield(), _poolAccruedYield + 1); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 23_512_463128); /* ============ 2 Non-Earners and 2 Earners are Initialized ============ */ - _giveM(_bob, 100_100e6); - _wrap(_bob, _bob, 100_100e6); - - _giveM(_dave, 100_100e6); - _wrap(_dave, _dave, 100_100e6); + _giveWM(_bob, 1_001e6); + _giveWM(_dave, 1_001e6); + _giveWM(_eric, 1_001e6); + _giveWM(_frank, 1_001e6); - _addToList(_EARNERS_LIST, _eric); + _addToList(_EARNERS_LIST_NAME, _eric); _wrappedMToken.startEarningFor(_eric); - _giveM(_eric, 100_100e6); - _wrap(_eric, _eric, 100_100e6); - - _addToList(_EARNERS_LIST, _frank); + _addToList(_EARNERS_LIST_NAME, _frank); _wrappedMToken.startEarningFor(_frank); - _giveM(_frank, 100_100e6); - _wrap(_frank, _frank, 100_100e6); - /* ============ Bob (Non-Earner) Swaps Exact wM for USDC ============ */ - _swapExactInput(_bob, _bob, address(_wrappedMToken), _USDC, 100_000e6); + _swapExactInput(_bob, _bob, address(_wrappedMToken), _USDC, 1_000e6); + + assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 1_000e6); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 100_000e6); + // The swap has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); - // Check that carol received yield. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _poolAccruedYield); assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); /* ============ 1-Day Time Warp ============ */ @@ -449,12 +324,13 @@ contract UniswapV3IntegrationTests is TestBase { // Move 1 day forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 1 days); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 150_695251); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 2_349_986661); // Claim yield for the pool and check that carol received yield. _wrappedMToken.claimFor(_pool); - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _poolAccruedYield); + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); /* ============ 5-Day Time Warp ============ */ @@ -462,16 +338,17 @@ contract UniswapV3IntegrationTests is TestBase { // Move 5 days forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 5 days); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 753_682738); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 11_753_024234); /* ============ Eric (Earner) Swaps Exact wM for USDC ============ */ - _swapExactInput(_eric, _eric, address(_wrappedMToken), _USDC, 100_000e6); + _swapExactInput(_eric, _eric, address(_wrappedMToken), _USDC, 1_000e6); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 100_000e6); + assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 1_000e6); + + // The swap has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); - // Check that carol received yield. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _poolAccruedYield); assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); /* ============ 3-Day Time Warp ============ */ @@ -479,17 +356,18 @@ contract UniswapV3IntegrationTests is TestBase { // Move 3 days forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 3 days); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 493_252029); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 7_051_281772); /* ============ Dave (Non-Earner) Swaps wM for Exact USDC ============ */ // Option 3: Exact output parameter swap from non-earner - uint256 daveOutput_ = _swapExactOutput(_dave, _dave, address(_wrappedMToken), _USDC, 10_000e6); + uint256 daveOutput_ = _swapExactOutput(_dave, _dave, address(_wrappedMToken), _USDC, 1_000e6); assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += daveOutput_); - // Check that carol received yield. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _poolAccruedYield); + // The swap has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); /* ============ 7-Day Time Warp ============ */ @@ -497,139 +375,96 @@ contract UniswapV3IntegrationTests is TestBase { // Move 7 day forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 7 days); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 1_165_220368); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 16_458_240233); /* ============ Frank (Earner) Swaps wM for Exact USDC ============ */ - uint256 frankOutput_ = _swapExactOutput(_frank, _frank, address(_wrappedMToken), _USDC, 10_000e6); + uint256 frankOutput_ = _swapExactOutput(_frank, _frank, address(_wrappedMToken), _USDC, 1_000e6); assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += frankOutput_); - // Check that carol received yield. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _poolAccruedYield); + // The swap has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); } - function test_uniswapV3_increaseDecreaseLiquidityAndFees() public { - /* ============ Pool Becomes An Earner ============ */ - - _setClaimOverrideRecipient(_pool, _carol); - - _addToList(_EARNERS_LIST, _pool); - _wrappedMToken.startEarningFor(_pool); - - assertTrue(_wrappedMToken.isEarning(_pool)); - - assertEq(_wrappedMToken.claimOverrideRecipientFor(_pool), _carol); - + function test_uniswapV3_increaseDecreaseLiquidity() public { /* ============ Fund Alice (Non-Earner) and Bob (Earner) ============ */ - _giveM(_alice, 2_000_100e6); - _wrap(_alice, _alice, _mToken.balanceOf(_alice)); + _giveWM(_alice, 2_001e6); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 2_000_099_999999); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += 2_001e6); - deal(_USDC, _alice, 2_000_100e6); + _give(_USDC, _alice, 2_001e6); - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 2_000_100e6); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += 2_001e6); - _addToList(_EARNERS_LIST, _bob); + _addToList(_EARNERS_LIST_NAME, _bob); _wrappedMToken.startEarningFor(_bob); - _giveM(_bob, 2_000_100e6); - _wrap(_bob, _bob, _mToken.balanceOf(_bob)); + _giveWM(_bob, 2_001e6); - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 2_000_100_000000); + assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += 2_001e6); - deal(_USDC, _bob, 2_000_100e6); + _give(_USDC, _bob, 2_001e6); - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 2_000_100e6); + assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += 2_001e6); /* ============ Alice (Non-Earner) and Bob (Earner) Mint New LP Positions ============ */ - (uint256 aliceTokenId_, , , ) = _mintNewPosition(_alice, _alice, 1_000_000e6); + (uint256 aliceTokenId_, , , ) = _mintNewPosition(_alice, _alice, 1_000e6); - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 1_000_000e6); + assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM -= 999_930937); + assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 999_930937); - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += 1_000_000e6); + assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC -= 1_000e6); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += 1_000e6); - (uint256 bobTokenId_, , , ) = _mintNewPosition(_bob, _bob, 1_000_000e6); + (uint256 bobTokenId_, , , ) = _mintNewPosition(_bob, _bob, 1_000e6); - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM -= 1_000_000e6); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 1_000_000e6); + assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM -= 999_930937); + assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 999_930937); - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= 1_000_000e6); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += 1_000_000e6); + assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= 1_000e6); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += 1_000e6); + + _poolClaimRecipientBalanceOfWM = _wrappedMToken.balanceOf(_poolClaimRecipient); /* ============ 10-Day Time Warp ============ */ // Move 10 days forward and check that yield has accrued. vm.warp(vm.getBlockTimestamp() + 10 days); - assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield += 550_891667); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 1_101_673168); + assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield += 1_317339); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 23_130_990918); /* ============ Dave (Non-Earner) Swaps Exact wM for USDC ============ */ - _giveM(_dave, 100_100e6); - _wrap(_dave, _dave, 100_100e6); - - assertEq(_wrappedMToken.balanceOf(_dave), _daveBalanceOfWM += 100_099_999999); - - _swapExactInput(_dave, _dave, address(_wrappedMToken), _USDC, 100_000e6); + _giveWM(_dave, 1_001e6); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC -= 95_229_024894); + assertEq(_wrappedMToken.balanceOf(_dave), _daveBalanceOfWM += 1_001e6); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 100_000e6); + _swapExactInput(_dave, _dave, address(_wrappedMToken), _USDC, 1_000e6); - // Check that carol received yield. - assertEq(_wrappedMToken.balanceOf(_carol), _carolBalanceOfWM += _poolAccruedYield); - assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); - - /* ============ Alice (Non-Earner) And Bob (Earner) Collect Fees ============ */ - - (uint256 aliceAmountWM_, uint256 aliceAmountUSDC_) = _collect(_alice, aliceTokenId_); - - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += aliceAmountWM_); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM -= aliceAmountWM_); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += aliceAmountUSDC_); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC -= aliceAmountUSDC_); - - (uint256 bobAmountWM_, uint256 bobAmountUSDC_) = _collect(_bob, bobTokenId_); + assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC -= 999_903365); - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM += bobAmountWM_ + _bobAccruedYield); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM -= bobAmountWM_); + assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += 1_000e6); - assertEq(_wrappedMToken.accruedYieldOf(_bob), _bobAccruedYield -= _bobAccruedYield); + // The swap has triggered a wM transfer and the yield has been claimed for the pool. + assertEq(_wrappedMToken.balanceOf(_poolClaimRecipient), _poolClaimRecipientBalanceOfWM += _poolAccruedYield); - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC += bobAmountUSDC_); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC -= bobAmountUSDC_); + assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield -= _poolAccruedYield); /* ============ Alice (Non-Earner) Decreases Liquidity And Bob (Earner) Increases Liquidity ============ */ - (aliceAmountWM_, aliceAmountUSDC_) = _decreaseLiquidityCurrentRange(_alice, aliceTokenId_, 500_000e6); - - assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalanceOfWM += aliceAmountWM_); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM -= aliceAmountWM_); - - assertEq(IERC20(_USDC).balanceOf(_alice), _aliceBalanceOfUSDC += aliceAmountUSDC_); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC -= aliceAmountUSDC_); - - (, bobAmountWM_, bobAmountUSDC_) = _increaseLiquidityCurrentRange(_bob, bobTokenId_, 100_000e6); - - assertEq(_wrappedMToken.balanceOf(_bob), _bobBalanceOfWM -= bobAmountWM_); - assertEq(_wrappedMToken.balanceOf(_pool), _poolBalanceOfWM += bobAmountWM_); - - assertEq(IERC20(_USDC).balanceOf(_bob), _bobBalanceOfUSDC -= bobAmountUSDC_); - assertEq(IERC20(_USDC).balanceOf(_pool), _poolBalanceOfUSDC += bobAmountUSDC_); + _decreaseLiquidityCurrentRange(_alice, aliceTokenId_, 500e6); + _increaseLiquidityCurrentRange(_bob, bobTokenId_, 1_000e6); } function _createPool() internal returns (address pool_) { pool_ = _factory.createPool(address(_wrappedMToken), _USDC, _POOL_FEE); - IUniswapV3Pool(pool_).initialize(Utils.encodePriceSqrt(1, 1)); + IUniswapV3Pool(pool_).initialize(UniswapUtils.encodePriceSqrt(1, 1)); } function _approve(address token_, address account_, address spender_, uint256 amount_) internal { @@ -655,8 +490,8 @@ contract UniswapV3IntegrationTests is TestBase { token0: address(_wrappedMToken), token1: _USDC, fee: _POOL_FEE, - tickLower: Utils.MIN_TICK, - tickUpper: Utils.MAX_TICK, + tickLower: -1000, + tickUpper: 1000, amount0Desired: amount_, amount1Desired: amount_, amount0Min: 0, diff --git a/test/integration/Upgrade.t.sol b/test/integration/Upgrade.t.sol new file mode 100644 index 0000000..b8e1fd5 --- /dev/null +++ b/test/integration/Upgrade.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.26; + +import { Test } from "../../lib/forge-std/src/Test.sol"; + +import { IWrappedMToken } from "../../src/interfaces/IWrappedMToken.sol"; + +import { DeployBase } from "../../script/DeployBase.sol"; + +contract UpgradeTests is Test, DeployBase { + address internal constant _WRAPPED_M_TOKEN = 0x437cc33344a0B27A429f795ff6B469C72698B291; + address internal constant _REGISTRAR = 0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c; + address internal constant _M_TOKEN = 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b; + address internal constant _MIGRATION_ADMIN = 0x431169728D75bd02f4053435b87D15c8d1FB2C72; + address internal constant _EXCESS_DESTINATION = 0xd7298f620B0F752Cf41BD818a16C756d9dCAA34f; // Vault + address internal constant _DEPLOYER = 0xF2f1ACbe0BA726fEE8d75f3E32900526874740BB; + + uint64 internal constant _DEPLOYER_NONCE = 50; + + function test_upgrade() external { + vm.setNonce(_DEPLOYER, _DEPLOYER_NONCE); + + (address expectedImplementation_, address expectedMigrator_) = mockDeployUpgrade(_DEPLOYER, _DEPLOYER_NONCE); + + vm.startPrank(_DEPLOYER); + (address implementation_, address migrator_) = deployUpgrade( + _M_TOKEN, + _REGISTRAR, + _EXCESS_DESTINATION, + _MIGRATION_ADMIN + ); + vm.stopPrank(); + + // Wrapped M Token Implementation assertions + assertEq(implementation_, expectedImplementation_); + assertEq(IWrappedMToken(implementation_).migrationAdmin(), _MIGRATION_ADMIN); + assertEq(IWrappedMToken(implementation_).mToken(), _M_TOKEN); + assertEq(IWrappedMToken(implementation_).registrar(), _REGISTRAR); + assertEq(IWrappedMToken(implementation_).excessDestination(), _EXCESS_DESTINATION); + + // Migrator assertions + assertEq(migrator_, expectedMigrator_); + + vm.prank(IWrappedMToken(_WRAPPED_M_TOKEN).migrationAdmin()); + IWrappedMToken(_WRAPPED_M_TOKEN).migrate(migrator_); + + // Wrapped M Token Proxy assertions + assertEq(IWrappedMToken(_WRAPPED_M_TOKEN).migrationAdmin(), _MIGRATION_ADMIN); + assertEq(IWrappedMToken(_WRAPPED_M_TOKEN).mToken(), _M_TOKEN); + assertEq(IWrappedMToken(_WRAPPED_M_TOKEN).registrar(), _REGISTRAR); + assertEq(IWrappedMToken(_WRAPPED_M_TOKEN).excessDestination(), _EXCESS_DESTINATION); + assertEq(IWrappedMToken(_WRAPPED_M_TOKEN).implementation(), implementation_); + } +} diff --git a/test/integration/vendor/morpho-blue/Interfaces.sol b/test/integration/vendor/morpho-blue/Interfaces.sol index 479a6e9..d023e22 100644 --- a/test/integration/vendor/morpho-blue/Interfaces.sol +++ b/test/integration/vendor/morpho-blue/Interfaces.sol @@ -1,15 +1,18 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.23; -interface IMorphoBlueFactory { - struct MarketParams { - address loanToken; - address collateralToken; - address oracle; - address irm; - uint256 lltv; - } +pragma solidity 0.8.26; +type Id is bytes32; + +struct MarketParams { + address loanToken; + address collateralToken; + address oracle; + address irm; + uint256 lltv; +} + +interface IMorphoBlueLike { function createMarket(MarketParams memory marketParams) external; function supply( @@ -96,3 +99,36 @@ interface IMorphoChainlinkOracleV2Factory { interface IOracle { function price() external view returns (uint256); } + +interface IMorphoVaultFactoryLike { + function createMetaMorpho( + address initialOwner, + uint256 initialTimelock, + address asset, + string memory name, + string memory symbol, + bytes32 salt + ) external returns (address vault); + + function isMetaMorpho(address vault) external view returns (bool); +} + +interface IMorphoVaultLike { + function setFee(uint256 newFee) external; + + function setFeeRecipient(address newFeeRecipient) external; + + function submitCap(MarketParams memory marketParams, uint256 newSupplyCap) external; + + function acceptCap(MarketParams memory marketParams) external; + + function setSupplyQueue(Id[] calldata newSupplyQueue) external; + + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); + + function balanceOf(address account) external view returns (uint256); + + function owner() external view returns (address); +} diff --git a/test/integration/vendor/morpho-blue/MorphoTestBase.sol b/test/integration/vendor/morpho-blue/MorphoTestBase.sol new file mode 100644 index 0000000..8357eb8 --- /dev/null +++ b/test/integration/vendor/morpho-blue/MorphoTestBase.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.26; + +import { IERC20 } from "../../../../lib/common/src/interfaces/IERC20.sol"; + +import { + Id, + MarketParams, + IMorphoBlueLike, + IMorphoChainlinkOracleV2Factory, + IMorphoVaultFactoryLike, + IMorphoVaultLike +} from "./Interfaces.sol"; + +import { TestBase } from "../../TestBase.sol"; + +contract MorphoTestBase is TestBase { + uint256 internal constant _MARKET_PARAMS_BYTES_LENGTH = 5 * 32; + + // Morpho Blue factory on Ethereum Mainnet + address internal constant _MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + + // Morpho Vault factory on Ethereum Mainnet + address internal constant _MORPHO_VAULT_FACTORY = 0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101; + + // Oracle factory on Ethereum Mainnet + address internal constant _ORACLE_FACTORY = 0x3A7bB36Ee3f3eE32A60e9f2b33c1e5f2E83ad766; + + // Morpho Blue market Liquidation Loan-To-Value ratio + uint256 internal constant _LLTV = 94_5000000000000000; // 94.5% + + Id internal constant _IDLE_MARKET_ID = Id.wrap(0x7725318760d6d193e11f889f0be58eba134f64a8c22ed9050cac7bd4a70a64f0); + + address internal _oracle; + + /* ============ Oracles ============ */ + + function _createOracle() internal returns (address oracle_) { + return + IMorphoChainlinkOracleV2Factory(_ORACLE_FACTORY).createMorphoChainlinkOracleV2( + address(0), + 1, + address(0), + address(0), + 6, + address(0), + 1, + address(0), + address(0), + 6, + bytes32(0) + ); + } + + /* ============ Markets ============ */ + + function _createMarket( + address account_, + address loanToken_, + address collateralToken_, + address oracle_, + uint256 lltv_ + ) internal { + MarketParams memory marketParams_ = MarketParams({ + loanToken: loanToken_, + collateralToken: collateralToken_, + oracle: oracle_, + irm: address(0), + lltv: lltv_ + }); + + vm.prank(account_); + IMorphoBlueLike(_MORPHO).createMarket(marketParams_); + } + + function _createIdleMarket(address account_) internal { + _createMarket(account_, address(0), address(0), address(0), 0); + } + + function _supplyCollateral( + address account_, + address collateralToken_, + uint256 amount_, + address loanToken_ + ) internal { + _approve(collateralToken_, account_, _MORPHO, amount_); + + MarketParams memory marketParams_ = MarketParams({ + loanToken: loanToken_, + collateralToken: collateralToken_, + oracle: _oracle, + irm: address(0), + lltv: _LLTV + }); + + vm.prank(account_); + IMorphoBlueLike(_MORPHO).supplyCollateral(marketParams_, amount_, account_, hex""); + } + + function _withdrawCollateral( + address account_, + address collateralToken_, + uint256 amount_, + address receiver_, + address loanToken_ + ) internal { + MarketParams memory marketParams_ = MarketParams({ + loanToken: loanToken_, + collateralToken: collateralToken_, + oracle: _oracle, + irm: address(0), + lltv: _LLTV + }); + + vm.prank(account_); + IMorphoBlueLike(_MORPHO).withdrawCollateral(marketParams_, amount_, account_, receiver_); + } + + function _supply( + address account_, + address loanToken_, + uint256 amount_, + address collateralToken_ + ) internal returns (uint256 assetsSupplied_, uint256 sharesSupplied_) { + _approve(loanToken_, account_, _MORPHO, amount_); + + MarketParams memory marketParams_ = MarketParams({ + loanToken: loanToken_, + collateralToken: collateralToken_, + oracle: _oracle, + irm: address(0), + lltv: _LLTV + }); + + vm.prank(account_); + return IMorphoBlueLike(_MORPHO).supply(marketParams_, amount_, 0, account_, hex""); + } + + function _withdraw( + address account_, + address loanToken_, + uint256 amount_, + address receiver_, + address collateralToken_ + ) internal returns (uint256 assetsWithdrawn_, uint256 sharesWithdrawn_) { + MarketParams memory marketParams_ = MarketParams({ + loanToken: loanToken_, + collateralToken: collateralToken_, + oracle: _oracle, + irm: address(0), + lltv: _LLTV + }); + + vm.prank(account_); + return IMorphoBlueLike(_MORPHO).withdraw(marketParams_, amount_, 0, account_, receiver_); + } + + function _borrow( + address account_, + address loanToken_, + uint256 amount_, + address receiver_, + address collateralToken_ + ) internal returns (uint256 assetsBorrowed_, uint256 sharesBorrowed_) { + MarketParams memory marketParams_ = MarketParams({ + loanToken: loanToken_, + collateralToken: collateralToken_, + oracle: _oracle, + irm: address(0), + lltv: _LLTV + }); + + vm.prank(account_); + return IMorphoBlueLike(_MORPHO).borrow(marketParams_, amount_, 0, account_, receiver_); + } + + function _repay( + address account_, + address loanToken_, + uint256 amount_, + address collateralToken_ + ) internal returns (uint256 assetsRepaid_, uint256 sharesRepaid_) { + _approve(loanToken_, account_, _MORPHO, amount_); + + MarketParams memory marketParams_ = MarketParams({ + loanToken: loanToken_, + collateralToken: collateralToken_, + oracle: _oracle, + irm: address(0), + lltv: _LLTV + }); + + vm.prank(account_); + return IMorphoBlueLike(_MORPHO).repay(marketParams_, amount_, 0, account_, hex""); + } + + function _getMarketId(MarketParams memory marketParams_) internal pure returns (Id marketParamsId_) { + assembly ("memory-safe") { + marketParamsId_ := keccak256(marketParams_, _MARKET_PARAMS_BYTES_LENGTH) + } + } + + /* ============ ERC20 ============ */ + + function _approve(address token_, address account_, address spender_, uint256 amount_) internal { + vm.prank(account_); + IERC20(token_).approve(spender_, amount_); + } + + function _transfer(address token_, address sender_, address recipient_, uint256 amount_) internal { + vm.prank(sender_); + IERC20(token_).transfer(recipient_, amount_); + } +} diff --git a/test/integration/vendor/protocol/Interfaces.sol b/test/integration/vendor/protocol/Interfaces.sol index 857d5ae..ccdcb04 100644 --- a/test/integration/vendor/protocol/Interfaces.sol +++ b/test/integration/vendor/protocol/Interfaces.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.23; +pragma solidity 0.8.26; interface IRegistrarLike { function addToList(bytes32 list, address account) external; diff --git a/test/integration/vendor/uniswap-v3/Interfaces.sol b/test/integration/vendor/uniswap-v3/Interfaces.sol index 176fdff..cc8dca0 100644 --- a/test/integration/vendor/uniswap-v3/Interfaces.sol +++ b/test/integration/vendor/uniswap-v3/Interfaces.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.23; +pragma solidity 0.8.26; interface INonfungiblePositionManager { struct MintParams { diff --git a/test/integration/vendor/uniswap-v3/Utils.sol b/test/integration/vendor/uniswap-v3/Utils.sol index b17becc..06bb2fd 100644 --- a/test/integration/vendor/uniswap-v3/Utils.sol +++ b/test/integration/vendor/uniswap-v3/Utils.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; +pragma solidity 0.8.26; uint256 constant PRECISION = 2 ** 96; library Utils { - int24 internal constant MIN_TICK = -887272; - int24 internal constant MAX_TICK = -MIN_TICK; - function encodePriceSqrt(uint256 reserve1, uint256 reserve0) internal pure returns (uint160) { return uint160(sqrt((reserve1 * PRECISION * PRECISION) / reserve0)); } diff --git a/test/unit/Migration.t.sol b/test/unit/Migration.t.sol new file mode 100644 index 0000000..6d81210 --- /dev/null +++ b/test/unit/Migration.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.26; + +import { Proxy } from "../../lib/common/src/Proxy.sol"; +import { Test } from "../../lib/forge-std/src/Test.sol"; + +import { IWrappedMToken } from "../../src/interfaces/IWrappedMToken.sol"; + +import { WrappedMToken } from "../../src/WrappedMToken.sol"; +import { MigratorV1 as Migrator } from "../../src/MigratorV1.sol"; + +import { MockRegistrar } from "./../utils/Mocks.sol"; + +contract WrappedMTokenV3 { + function foo() external pure returns (uint256) { + return 1; + } +} + +contract MigrationTests is Test { + bytes32 internal constant _MIGRATOR_KEY_PREFIX = "wm_migrator_v2"; + + address internal _alice = makeAddr("alice"); + address internal _bob = makeAddr("bob"); + address internal _carol = makeAddr("carol"); + address internal _dave = makeAddr("dave"); + + address internal _mToken = makeAddr("mToken"); + + address internal _excessDestination = makeAddr("excessDestination"); + address internal _migrationAdmin = makeAddr("migrationAdmin"); + + MockRegistrar internal _registrar; + WrappedMToken internal _implementation; + IWrappedMToken internal _wrappedMToken; + + function setUp() external { + _registrar = new MockRegistrar(); + + _implementation = new WrappedMToken(_mToken, address(_registrar), _excessDestination, _migrationAdmin); + + _wrappedMToken = IWrappedMToken(address(new Proxy(address(_implementation)))); + } + + function test_migration() external { + WrappedMTokenV3 implementationV3_ = new WrappedMTokenV3(); + address migrator_ = address(new Migrator(address(implementationV3_))); + + _registrar.set( + keccak256(abi.encode(_MIGRATOR_KEY_PREFIX, address(_wrappedMToken))), + bytes32(uint256(uint160(migrator_))) + ); + + vm.expectRevert(); + WrappedMTokenV3(address(_wrappedMToken)).foo(); + + _wrappedMToken.migrate(); + + assertEq(WrappedMTokenV3(address(_wrappedMToken)).foo(), 1); + } + + function test_migration_fromAdmin() external { + WrappedMTokenV3 implementationV3_ = new WrappedMTokenV3(); + address migrator_ = address(new Migrator(address(implementationV3_))); + + vm.expectRevert(); + WrappedMTokenV3(address(_wrappedMToken)).foo(); + + vm.prank(_migrationAdmin); + _wrappedMToken.migrate(migrator_); + + assertEq(WrappedMTokenV3(address(_wrappedMToken)).foo(), 1); + } +} diff --git a/test/Stories.t.sol b/test/unit/Stories.t.sol similarity index 90% rename from test/Stories.t.sol rename to test/unit/Stories.t.sol index 720daf0..906a3a0 100644 --- a/test/Stories.t.sol +++ b/test/unit/Stories.t.sol @@ -1,31 +1,31 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; +pragma solidity 0.8.26; -import { Test } from "../lib/forge-std/src/Test.sol"; +import { IndexingMath } from "../../lib/common/src/libs/IndexingMath.sol"; -import { IWrappedMToken } from "../src/interfaces/IWrappedMToken.sol"; +import { Proxy } from "../../lib/common/src/Proxy.sol"; +import { Test } from "../../lib/forge-std/src/Test.sol"; -import { WrappedMToken } from "../src/WrappedMToken.sol"; -import { Proxy } from "../src/Proxy.sol"; +import { IWrappedMToken } from "../../src/interfaces/IWrappedMToken.sol"; -import { MockM, MockRegistrar } from "./utils/Mocks.sol"; +import { WrappedMToken } from "../../src/WrappedMToken.sol"; -contract Tests is Test { - uint56 internal constant _EXP_SCALED_ONE = 1e12; +import { MockM, MockRegistrar } from "../utils/Mocks.sol"; - bytes32 internal constant _EARNERS_LIST = "earners"; - bytes32 internal constant _MIGRATOR_V1_PREFIX = "wm_migrator_v1"; +contract StoryTests is Test { + uint56 internal constant _EXP_SCALED_ONE = IndexingMath.EXP_SCALED_ONE; + + bytes32 internal constant _EARNERS_LIST_NAME = "earners"; address internal _alice = makeAddr("alice"); address internal _bob = makeAddr("bob"); address internal _carol = makeAddr("carol"); address internal _dave = makeAddr("dave"); + address internal _excessDestination = makeAddr("excessDestination"); address internal _migrationAdmin = makeAddr("migrationAdmin"); - address internal _vault = makeAddr("vault"); - MockM internal _mToken; MockRegistrar internal _registrar; WrappedMToken internal _implementation; @@ -33,21 +33,19 @@ contract Tests is Test { function setUp() external { _registrar = new MockRegistrar(); - _registrar.setVault(_vault); _mToken = new MockM(); _mToken.setCurrentIndex(_EXP_SCALED_ONE); - _mToken.setTtgRegistrar(address(_registrar)); - _implementation = new WrappedMToken(address(_mToken), _migrationAdmin); + _implementation = new WrappedMToken(address(_mToken), address(_registrar), _excessDestination, _migrationAdmin); _wrappedMToken = IWrappedMToken(address(new Proxy(address(_implementation)))); } function test_story() external { - _registrar.setListContains(_EARNERS_LIST, _alice, true); - _registrar.setListContains(_EARNERS_LIST, _bob, true); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, _alice, true); + _registrar.setListContains(_EARNERS_LIST_NAME, _bob, true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -243,7 +241,7 @@ contract Tests is Test { assertEq(_wrappedMToken.totalAccruedYield(), 133_333336); assertEq(_wrappedMToken.excess(), 416_666664); - _registrar.setListContains(_EARNERS_LIST, _alice, false); + _registrar.setListContains(_EARNERS_LIST_NAME, _alice, false); _wrappedMToken.stopEarningFor(_alice); @@ -258,7 +256,7 @@ contract Tests is Test { assertEq(_wrappedMToken.totalAccruedYield(), 66_666672); assertEq(_wrappedMToken.excess(), 416_666664); - _registrar.setListContains(_EARNERS_LIST, _carol, true); + _registrar.setListContains(_EARNERS_LIST_NAME, _carol, true); _wrappedMToken.startEarningFor(_carol); @@ -366,9 +364,10 @@ contract Tests is Test { } function test_noExcessCreep() external { - _registrar.setListContains(_EARNERS_LIST, _alice, true); - _registrar.setListContains(_EARNERS_LIST, _bob, true); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, _alice, true); + _registrar.setListContains(_EARNERS_LIST_NAME, _bob, true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + _mToken.setCurrentIndex(_EXP_SCALED_ONE + 3e11 - 1); _wrappedMToken.enableEarning(); @@ -400,9 +399,10 @@ contract Tests is Test { } function test_dustWrapping() external { - _registrar.setListContains(_EARNERS_LIST, _alice, true); - _registrar.setListContains(_EARNERS_LIST, _bob, true); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, _alice, true); + _registrar.setListContains(_EARNERS_LIST_NAME, _bob, true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + _mToken.setCurrentIndex(_EXP_SCALED_ONE + 1); _wrappedMToken.enableEarning(); diff --git a/test/WrappedMToken.t.sol b/test/unit/WrappedMToken.t.sol similarity index 69% rename from test/WrappedMToken.t.sol rename to test/unit/WrappedMToken.t.sol index 54fe412..53823ab 100644 --- a/test/WrappedMToken.t.sol +++ b/test/unit/WrappedMToken.t.sol @@ -1,42 +1,42 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; +pragma solidity 0.8.26; -import { Test, console2 } from "../lib/forge-std/src/Test.sol"; -import { IERC20Extended } from "../lib/common/src/interfaces/IERC20Extended.sol"; -import { UIntMath } from "../lib/common/src/libs/UIntMath.sol"; +import { IndexingMath } from "../../lib/common/src/libs/IndexingMath.sol"; +import { UIntMath } from "../../lib/common/src/libs/UIntMath.sol"; -import { IWrappedMToken } from "../src/interfaces/IWrappedMToken.sol"; +import { IERC20 } from "../../lib/common/src/interfaces/IERC20.sol"; +import { IERC20Extended } from "../../lib/common/src/interfaces/IERC20Extended.sol"; -import { IndexingMath } from "../src/libs/IndexingMath.sol"; +import { Proxy } from "../../lib/common/src/Proxy.sol"; +import { Test } from "../../lib/forge-std/src/Test.sol"; -import { Proxy } from "../src/Proxy.sol"; +import { IWrappedMToken } from "../../src/interfaces/IWrappedMToken.sol"; -import { MockM, MockRegistrar } from "./utils/Mocks.sol"; -import { WrappedMTokenHarness } from "./utils/WrappedMTokenHarness.sol"; +import { MockM, MockRegistrar } from "../utils/Mocks.sol"; +import { WrappedMTokenHarness } from "../utils/WrappedMTokenHarness.sol"; -// TODO: Test for `totalAccruedYield()`. -// TODO: All operations involving earners should include demonstration of accrued yield being added t their balance. +// TODO: All operations involving earners should include demonstration of accrued yield being added to their balance. // TODO: Add relevant unit tests while earning enabled/disabled. +// TODO: Remove unneeded _wrappedMToken.enableEarning. contract WrappedMTokenTests is Test { - uint56 internal constant _EXP_SCALED_ONE = 1e12; + uint56 internal constant _EXP_SCALED_ONE = IndexingMath.EXP_SCALED_ONE; - bytes32 internal constant _EARNERS_LIST = "earners"; - bytes32 internal constant _CLAIM_DESTINATION_PREFIX = "wm_claim_destination"; - bytes32 internal constant _MIGRATOR_V1_PREFIX = "wm_migrator_v1"; + bytes32 internal constant _CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX = "wm_claim_override_recipient"; + + bytes32 internal constant _EARNERS_LIST_NAME = "earners"; address internal _alice = makeAddr("alice"); address internal _bob = makeAddr("bob"); address internal _charlie = makeAddr("charlie"); address internal _david = makeAddr("david"); + address internal _excessDestination = makeAddr("excessDestination"); address internal _migrationAdmin = makeAddr("migrationAdmin"); address[] internal _accounts = [_alice, _bob, _charlie, _david]; - address internal _vault = makeAddr("vault"); - uint128 internal _currentIndex; MockM internal _mToken; @@ -46,26 +46,37 @@ contract WrappedMTokenTests is Test { function setUp() external { _registrar = new MockRegistrar(); - _registrar.setVault(_vault); _mToken = new MockM(); _mToken.setCurrentIndex(_EXP_SCALED_ONE); - _mToken.setTtgRegistrar(address(_registrar)); - _implementation = new WrappedMTokenHarness(address(_mToken), _migrationAdmin); + _implementation = new WrappedMTokenHarness( + address(_mToken), + address(_registrar), + _excessDestination, + _migrationAdmin + ); _wrappedMToken = WrappedMTokenHarness(address(new Proxy(address(_implementation)))); _mToken.setCurrentIndex(_currentIndex = 1_100000068703); } + /* ============ constants ============ */ + function test_constants() external view { + assertEq(_wrappedMToken.EARNERS_LIST_IGNORED_KEY(), "earners_list_ignored"); + assertEq(_wrappedMToken.EARNERS_LIST_NAME(), _EARNERS_LIST_NAME); + assertEq(_wrappedMToken.CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX(), _CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX); + assertEq(_wrappedMToken.MIGRATOR_KEY_PREFIX(), "wm_migrator_v2"); + } + /* ============ constructor ============ */ function test_constructor() external view { assertEq(_wrappedMToken.migrationAdmin(), _migrationAdmin); assertEq(_wrappedMToken.mToken(), address(_mToken)); assertEq(_wrappedMToken.registrar(), address(_registrar)); - assertEq(_wrappedMToken.vault(), _vault); - assertEq(_wrappedMToken.name(), "WrappedM by M^0"); + assertEq(_wrappedMToken.excessDestination(), _excessDestination); + assertEq(_wrappedMToken.name(), "M (Wrapped) by M^0"); assertEq(_wrappedMToken.symbol(), "wM"); assertEq(_wrappedMToken.decimals(), 6); assertEq(_wrappedMToken.implementation(), address(_implementation)); @@ -73,12 +84,22 @@ contract WrappedMTokenTests is Test { function test_constructor_zeroMToken() external { vm.expectRevert(IWrappedMToken.ZeroMToken.selector); - new WrappedMTokenHarness(address(0), address(0)); + new WrappedMTokenHarness(address(0), address(0), address(0), address(0)); + } + + function test_constructor_zeroRegistrar() external { + vm.expectRevert(IWrappedMToken.ZeroRegistrar.selector); + new WrappedMTokenHarness(address(_mToken), address(0), address(0), address(0)); + } + + function test_constructor_zeroExcessDestination() external { + vm.expectRevert(IWrappedMToken.ZeroExcessDestination.selector); + new WrappedMTokenHarness(address(_mToken), address(_registrar), address(0), address(0)); } function test_constructor_zeroMigrationAdmin() external { vm.expectRevert(IWrappedMToken.ZeroMigrationAdmin.selector); - new WrappedMTokenHarness(address(_mToken), address(0)); + new WrappedMTokenHarness(address(_mToken), address(_registrar), _excessDestination, address(0)); } function test_constructor_zeroImplementation() external { @@ -124,7 +145,7 @@ contract WrappedMTokenTests is Test { } function test_wrap_toEarner() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -172,7 +193,7 @@ contract WrappedMTokenTests is Test { accountEarning_ = earningEnabled_ && accountEarning_; if (earningEnabled_) { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); } @@ -201,6 +222,9 @@ contract WrappedMTokenTests is Test { if (wrapAmount_ == 0) { vm.expectRevert(abi.encodeWithSelector(IERC20Extended.InsufficientAmount.selector, (0))); + } else { + vm.expectEmit(); + emit IERC20.Transfer(address(0), _alice, wrapAmount_); } vm.startPrank(_alice); @@ -227,7 +251,7 @@ contract WrappedMTokenTests is Test { accountEarning_ = earningEnabled_ && accountEarning_; if (earningEnabled_) { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); } @@ -256,6 +280,9 @@ contract WrappedMTokenTests is Test { if (wrapAmount_ == 0) { vm.expectRevert(abi.encodeWithSelector(IERC20Extended.InsufficientAmount.selector, (0))); + } else { + vm.expectEmit(); + emit IERC20.Transfer(address(0), _alice, wrapAmount_); } vm.startPrank(_alice); @@ -287,7 +314,7 @@ contract WrappedMTokenTests is Test { } function test_unwrap_insufficientBalance_fromEarner() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -323,7 +350,7 @@ contract WrappedMTokenTests is Test { } function test_unwrap_fromEarner() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -363,7 +390,7 @@ contract WrappedMTokenTests is Test { accountEarning_ = earningEnabled_ && accountEarning_; if (earningEnabled_) { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); } @@ -383,7 +410,6 @@ contract WrappedMTokenTests is Test { } currentIndex_ = uint128(bound(currentIndex_, accountIndex_, 10 * _EXP_SCALED_ONE)); - unwrapAmount_ = uint240(bound(unwrapAmount_, 0, 2 * balance_)); _mToken.setCurrentIndex(_currentIndex = currentIndex_); @@ -391,6 +417,8 @@ contract WrappedMTokenTests is Test { _mToken.setBalanceOf(address(_wrappedMToken), balance_ + accruedYield_); + unwrapAmount_ = uint240(bound(unwrapAmount_, 0, (11 * (balance_ + accruedYield_)) / 10)); + if (unwrapAmount_ == 0) { vm.expectRevert(abi.encodeWithSelector(IERC20Extended.InsufficientAmount.selector, (0))); } else if (unwrapAmount_ > balance_ + accruedYield_) { @@ -402,6 +430,9 @@ contract WrappedMTokenTests is Test { unwrapAmount_ ) ); + } else { + vm.expectEmit(); + emit IERC20.Transfer(_alice, address(0), unwrapAmount_); } vm.startPrank(_alice); @@ -427,7 +458,7 @@ contract WrappedMTokenTests is Test { accountEarning_ = earningEnabled_ && accountEarning_; if (earningEnabled_) { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); } @@ -456,6 +487,9 @@ contract WrappedMTokenTests is Test { if (balance_ + accruedYield_ == 0) { vm.expectRevert(abi.encodeWithSelector(IERC20Extended.InsufficientAmount.selector, (0))); + } else { + vm.expectEmit(); + emit IERC20.Transfer(_alice, address(0), balance_ + accruedYield_); } vm.startPrank(_alice); @@ -479,7 +513,7 @@ contract WrappedMTokenTests is Test { } function test_claimFor_earner() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -487,17 +521,59 @@ contract WrappedMTokenTests is Test { assertEq(_wrappedMToken.balanceOf(_alice), 1_000); + vm.expectEmit(); + emit IWrappedMToken.Claimed(_alice, _alice, 100); + + vm.expectEmit(); + emit IERC20.Transfer(address(0), _alice, 100); + assertEq(_wrappedMToken.claimFor(_alice), 100); assertEq(_wrappedMToken.balanceOf(_alice), 1_100); } - function testFuzz_claimFor(uint240 balance_, uint128 accountIndex_, uint128 index_) external { + function test_claimFor_earner_withOverrideRecipient() external { + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + + _registrar.set( + keccak256(abi.encode(_CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX, _alice)), + bytes32(uint256(uint160(_bob))) + ); + + _wrappedMToken.enableEarning(); + + _wrappedMToken.setAccountOf(_alice, 1_000, _EXP_SCALED_ONE); + + assertEq(_wrappedMToken.balanceOf(_alice), 1_000); + + vm.expectEmit(); + emit IWrappedMToken.Claimed(_alice, _bob, 100); + + vm.expectEmit(); + emit IERC20.Transfer(address(0), _alice, 100); + + vm.expectEmit(); + emit IERC20.Transfer(_alice, _bob, 100); + + assertEq(_wrappedMToken.claimFor(_alice), 100); + + assertEq(_wrappedMToken.balanceOf(_alice), 1_000); + assertEq(_wrappedMToken.balanceOf(_bob), 100); + } + + function testFuzz_claimFor(uint240 balance_, uint128 accountIndex_, uint128 index_, bool claimOverride_) external { accountIndex_ = uint128(bound(index_, _EXP_SCALED_ONE, 10 * _EXP_SCALED_ONE)); balance_ = uint240(bound(balance_, 0, _getMaxAmount(accountIndex_))); index_ = uint128(bound(index_, accountIndex_, 10 * _EXP_SCALED_ONE)); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + + if (claimOverride_) { + _registrar.set( + keccak256(abi.encode(_CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX, _alice)), + bytes32(uint256(uint160(_charlie))) + ); + } _wrappedMToken.enableEarning(); @@ -509,9 +585,20 @@ contract WrappedMTokenTests is Test { uint240 accruedYield_ = _wrappedMToken.accruedYieldOf(_alice); + if (accruedYield_ != 0) { + vm.expectEmit(); + emit IWrappedMToken.Claimed(_alice, claimOverride_ ? _charlie : _alice, accruedYield_); + + vm.expectEmit(); + emit IERC20.Transfer(address(0), _alice, accruedYield_); + } + assertEq(_wrappedMToken.claimFor(_alice), accruedYield_); - assertEq(_wrappedMToken.totalEarningSupply(), _wrappedMToken.balanceOf(_alice)); + assertEq( + _wrappedMToken.totalSupply(), + _wrappedMToken.balanceOf(_alice) + _wrappedMToken.balanceOf(_bob) + _wrappedMToken.balanceOf(_charlie) + ); } /* ============ claimExcess ============ */ @@ -539,7 +626,13 @@ contract WrappedMTokenTests is Test { uint240 expectedExcess_ = _wrappedMToken.excess(); - vm.expectCall(address(_mToken), abi.encodeCall(_mToken.transfer, (_wrappedMToken.vault(), expectedExcess_))); + vm.expectCall( + address(_mToken), + abi.encodeCall(_mToken.transfer, (_wrappedMToken.excessDestination(), expectedExcess_)) + ); + + vm.expectEmit(); + emit IWrappedMToken.ExcessClaimed(expectedExcess_); assertEq(_wrappedMToken.claimExcess(), expectedExcess_); assertEq(_wrappedMToken.excess(), 0); @@ -564,7 +657,7 @@ contract WrappedMTokenTests is Test { } function test_transfer_insufficientBalance_fromEarner_toNonEarner() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -581,6 +674,9 @@ contract WrappedMTokenTests is Test { _wrappedMToken.setAccountOf(_alice, 1_000); _wrappedMToken.setAccountOf(_bob, 500); + vm.expectEmit(); + emit IERC20.Transfer(_alice, _bob, 500); + vm.prank(_alice); _wrappedMToken.transfer(_bob, 500); @@ -608,6 +704,9 @@ contract WrappedMTokenTests is Test { _wrappedMToken.setAccountOf(_alice, aliceBalance_); _wrappedMToken.setAccountOf(_bob, bobBalance); + vm.expectEmit(); + emit IERC20.Transfer(_alice, _bob, transferAmount_); + vm.prank(_alice); _wrappedMToken.transfer(_bob, transferAmount_); @@ -620,7 +719,7 @@ contract WrappedMTokenTests is Test { } function test_transfer_fromEarner_toNonEarner() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -632,6 +731,9 @@ contract WrappedMTokenTests is Test { _wrappedMToken.setAccountOf(_alice, 1_000, _currentIndex); _wrappedMToken.setAccountOf(_bob, 500); + vm.expectEmit(); + emit IERC20.Transfer(_alice, _bob, 500); + vm.prank(_alice); _wrappedMToken.transfer(_bob, 500); @@ -643,6 +745,9 @@ contract WrappedMTokenTests is Test { assertEq(_wrappedMToken.totalNonEarningSupply(), 1_000); assertEq(_wrappedMToken.totalEarningSupply(), 500); + vm.expectEmit(); + emit IERC20.Transfer(_alice, _bob, 1); + vm.prank(_alice); _wrappedMToken.transfer(_bob, 1); @@ -656,7 +761,7 @@ contract WrappedMTokenTests is Test { } function test_transfer_fromNonEarner_toEarner() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -668,6 +773,9 @@ contract WrappedMTokenTests is Test { _wrappedMToken.setAccountOf(_alice, 1_000); _wrappedMToken.setAccountOf(_bob, 500, _currentIndex); + vm.expectEmit(); + emit IERC20.Transfer(_alice, _bob, 500); + vm.prank(_alice); _wrappedMToken.transfer(_bob, 500); @@ -681,7 +789,7 @@ contract WrappedMTokenTests is Test { } function test_transfer_fromEarner_toEarner() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -691,6 +799,9 @@ contract WrappedMTokenTests is Test { _wrappedMToken.setAccountOf(_alice, 1_000, _currentIndex); _wrappedMToken.setAccountOf(_bob, 500, _currentIndex); + vm.expectEmit(); + emit IERC20.Transfer(_alice, _bob, 500); + vm.prank(_alice); _wrappedMToken.transfer(_bob, 500); @@ -709,6 +820,9 @@ contract WrappedMTokenTests is Test { _wrappedMToken.setAccountOf(_alice, 1_000); + vm.expectEmit(); + emit IERC20.Transfer(_alice, _alice, 500); + vm.prank(_alice); _wrappedMToken.transfer(_alice, 500); @@ -720,7 +834,7 @@ contract WrappedMTokenTests is Test { } function test_transfer_earnerToSelf() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -734,6 +848,9 @@ contract WrappedMTokenTests is Test { assertEq(_wrappedMToken.balanceOf(_alice), 1_000); assertEq(_wrappedMToken.accruedYieldOf(_alice), 666); + vm.expectEmit(); + emit IERC20.Transfer(_alice, _alice, 500); + vm.prank(_alice); _wrappedMToken.transfer(_alice, 500); @@ -755,7 +872,7 @@ contract WrappedMTokenTests is Test { bobEarning_ = earningEnabled_ && bobEarning_; if (earningEnabled_) { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); } @@ -801,7 +918,7 @@ contract WrappedMTokenTests is Test { uint240 aliceAccruedYield_ = _wrappedMToken.accruedYieldOf(_alice); uint240 bobAccruedYield_ = _wrappedMToken.accruedYieldOf(_bob); - amount_ = uint240(bound(amount_, 0, aliceBalance_ + aliceAccruedYield_)); + amount_ = uint240(bound(amount_, 0, (11 * (aliceBalance_ + aliceAccruedYield_)) / 10)); if (amount_ > aliceBalance_ + aliceAccruedYield_) { vm.expectRevert( @@ -812,6 +929,9 @@ contract WrappedMTokenTests is Test { amount_ ) ); + } else { + vm.expectEmit(); + emit IERC20.Transfer(_alice, _bob, amount_); } vm.prank(_alice); @@ -842,31 +962,41 @@ contract WrappedMTokenTests is Test { } /* ============ startEarningFor ============ */ - function test_startEarningFor_notApprovedEarner() external { - vm.expectRevert(IWrappedMToken.NotApprovedEarner.selector); + function test_startEarningFor_earningIsDisabled() external { + vm.expectRevert(IWrappedMToken.EarningIsDisabled.selector); _wrappedMToken.startEarningFor(_alice); } - function test_startEarningFor_earningIsDisabled() external { - _registrar.setListContains(_EARNERS_LIST, _alice, true); + function test_startEarningFor_notApprovedEarner() external { + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); - vm.expectRevert(IWrappedMToken.EarningIsDisabled.selector); + _wrappedMToken.enableEarning(); + + vm.expectRevert(abi.encodeWithSelector(IWrappedMToken.NotApprovedEarner.selector, _alice)); _wrappedMToken.startEarningFor(_alice); + } - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + function test_startEarning_overflow() external { + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), false); + uint256 aliceBalance_ = uint256(type(uint112).max) + 20; - _wrappedMToken.disableEarning(); + _mToken.setCurrentIndex(_currentIndex = _EXP_SCALED_ONE); - vm.expectRevert(IWrappedMToken.EarningIsDisabled.selector); + _wrappedMToken.setTotalNonEarningSupply(aliceBalance_); + + _wrappedMToken.setAccountOf(_alice, aliceBalance_); + + _registrar.setListContains(_EARNERS_LIST_NAME, _alice, true); + + vm.expectRevert(UIntMath.InvalidUInt112.selector); _wrappedMToken.startEarningFor(_alice); } function test_startEarningFor() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -874,7 +1004,7 @@ contract WrappedMTokenTests is Test { _wrappedMToken.setAccountOf(_alice, 1_000); - _registrar.setListContains(_EARNERS_LIST, _alice, true); + _registrar.setListContains(_EARNERS_LIST_NAME, _alice, true); vm.expectEmit(); emit IWrappedMToken.StartedEarning(_alice); @@ -889,30 +1019,11 @@ contract WrappedMTokenTests is Test { assertEq(_wrappedMToken.totalEarningSupply(), 1_000); } - function test_startEarning_overflow() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); - - _wrappedMToken.enableEarning(); - - uint256 aliceBalance_ = uint256(type(uint112).max) + 20; - - _mToken.setCurrentIndex(_currentIndex = _EXP_SCALED_ONE); - - _wrappedMToken.setTotalNonEarningSupply(aliceBalance_); - - _wrappedMToken.setAccountOf(_alice, aliceBalance_); - - _registrar.setListContains(_EARNERS_LIST, _alice, true); - - vm.expectRevert(UIntMath.InvalidUInt112.selector); - _wrappedMToken.startEarningFor(_alice); - } - function testFuzz_startEarningFor(uint240 balance_, uint128 index_) external { balance_ = uint240(bound(balance_, 0, _getMaxAmount(_currentIndex))); index_ = uint128(bound(index_, _currentIndex, 10 * _EXP_SCALED_ONE)); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -920,10 +1031,13 @@ contract WrappedMTokenTests is Test { _wrappedMToken.setAccountOf(_alice, balance_); - _registrar.setListContains(_EARNERS_LIST, _alice, true); + _registrar.setListContains(_EARNERS_LIST_NAME, _alice, true); _mToken.setCurrentIndex(index_); + vm.expectEmit(); + emit IWrappedMToken.StartedEarning(_alice); + _wrappedMToken.startEarningFor(_alice); assertEq(_wrappedMToken.isEarning(_alice), true); @@ -936,14 +1050,14 @@ contract WrappedMTokenTests is Test { /* ============ stopEarningFor ============ */ function test_stopEarningFor_isApprovedEarner() external { - _registrar.setListContains(_EARNERS_LIST, _alice, true); + _registrar.setListContains(_EARNERS_LIST_NAME, _alice, true); - vm.expectRevert(IWrappedMToken.IsApprovedEarner.selector); + vm.expectRevert(abi.encodeWithSelector(IWrappedMToken.IsApprovedEarner.selector, _alice)); _wrappedMToken.stopEarningFor(_alice); } function test_stopEarningFor() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -969,7 +1083,7 @@ contract WrappedMTokenTests is Test { balance_ = uint240(bound(balance_, 0, _getMaxAmount(accountIndex_))); index_ = uint128(bound(index_, accountIndex_, 10 * _EXP_SCALED_ONE)); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -981,6 +1095,9 @@ contract WrappedMTokenTests is Test { uint240 accruedYield_ = _wrappedMToken.accruedYieldOf(_alice); + vm.expectEmit(); + emit IWrappedMToken.StoppedEarning(_alice); + _wrappedMToken.stopEarningFor(_alice); assertEq(_wrappedMToken.balanceOf(_alice), balance_ + accruedYield_); @@ -992,27 +1109,27 @@ contract WrappedMTokenTests is Test { /* ============ enableEarning ============ */ function test_enableEarning_notApprovedEarner() external { - vm.expectRevert(IWrappedMToken.NotApprovedEarner.selector); + vm.expectRevert(abi.encodeWithSelector(IWrappedMToken.NotApprovedEarner.selector, address(_wrappedMToken))); _wrappedMToken.enableEarning(); } function test_enableEarning_earningCannotBeReenabled() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), false); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), false); _wrappedMToken.disableEarning(); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); vm.expectRevert(IWrappedMToken.EarningCannotBeReenabled.selector); _wrappedMToken.enableEarning(); } function test_enableEarning() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); vm.expectEmit(); emit IWrappedMToken.EarningEnabled(_currentIndex); @@ -1025,11 +1142,11 @@ contract WrappedMTokenTests is Test { vm.expectRevert(IWrappedMToken.EarningIsDisabled.selector); _wrappedMToken.disableEarning(); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), false); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), false); _wrappedMToken.disableEarning(); @@ -1038,18 +1155,18 @@ contract WrappedMTokenTests is Test { } function test_disableEarning_approvedEarner() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); - vm.expectRevert(IWrappedMToken.IsApprovedEarner.selector); + vm.expectRevert(abi.encodeWithSelector(IWrappedMToken.IsApprovedEarner.selector, address(_wrappedMToken))); _wrappedMToken.disableEarning(); } function test_disableEarning() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), false); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), false); vm.expectEmit(); emit IWrappedMToken.EarningDisabled(_currentIndex); @@ -1059,6 +1176,10 @@ contract WrappedMTokenTests is Test { /* ============ balanceOf ============ */ function test_balanceOf_nonEarner() external { + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + + _wrappedMToken.enableEarning(); + _wrappedMToken.setAccountOf(_alice, 500); assertEq(_wrappedMToken.balanceOf(_alice), 500); @@ -1066,10 +1187,14 @@ contract WrappedMTokenTests is Test { _wrappedMToken.setAccountOf(_alice, 1_000); assertEq(_wrappedMToken.balanceOf(_alice), 1_000); + + _mToken.setCurrentIndex(2 * _currentIndex); + + assertEq(_wrappedMToken.balanceOf(_alice), 1_000); } function test_balanceOf_earner() external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -1077,15 +1202,169 @@ contract WrappedMTokenTests is Test { assertEq(_wrappedMToken.balanceOf(_alice), 500); - _wrappedMToken.setAccountOf(_alice, 1_000); + _wrappedMToken.setAccountOf(_alice, 1_000, _EXP_SCALED_ONE); + + assertEq(_wrappedMToken.balanceOf(_alice), 1_000); + + _mToken.setCurrentIndex(2 * _currentIndex); assertEq(_wrappedMToken.balanceOf(_alice), 1_000); - _wrappedMToken.setLastIndexOf(_alice, 2 * _EXP_SCALED_ONE); + _wrappedMToken.setAccountOf(_alice, 1_000, 3 * _currentIndex); assertEq(_wrappedMToken.balanceOf(_alice), 1_000); } + /* ============ balanceWithYieldOf ============ */ + function test_balanceWithYieldOf_nonEarner() external { + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + + _wrappedMToken.enableEarning(); + + _wrappedMToken.setAccountOf(_alice, 500); + + assertEq(_wrappedMToken.balanceWithYieldOf(_alice), 500); + + _wrappedMToken.setAccountOf(_alice, 1_000); + + assertEq(_wrappedMToken.balanceWithYieldOf(_alice), 1_000); + + _mToken.setCurrentIndex(2 * _currentIndex); + + assertEq(_wrappedMToken.balanceWithYieldOf(_alice), 1_000); + } + + function test_balanceWithYieldOf_earner() external { + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + + _wrappedMToken.enableEarning(); + + _wrappedMToken.setAccountOf(_alice, 500, _EXP_SCALED_ONE); + + assertEq(_wrappedMToken.balanceWithYieldOf(_alice), 550); + + _wrappedMToken.setAccountOf(_alice, 1_000, _EXP_SCALED_ONE); + + assertEq(_wrappedMToken.balanceWithYieldOf(_alice), 1_100); + + _mToken.setCurrentIndex(2 * _currentIndex); + + assertEq(_wrappedMToken.balanceWithYieldOf(_alice), 2_200); + + _wrappedMToken.setAccountOf(_alice, 1_000, 3 * _currentIndex); + + assertEq(_wrappedMToken.balanceWithYieldOf(_alice), 1_000); + } + + /* ============ accruedYieldOf ============ */ + function test_accruedYieldOf_nonEarner() external { + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + + _wrappedMToken.enableEarning(); + + _wrappedMToken.setAccountOf(_alice, 500); + + assertEq(_wrappedMToken.accruedYieldOf(_alice), 0); + + _wrappedMToken.setAccountOf(_alice, 1_000); + + assertEq(_wrappedMToken.accruedYieldOf(_alice), 0); + + _mToken.setCurrentIndex(2 * _currentIndex); + + assertEq(_wrappedMToken.accruedYieldOf(_alice), 0); + } + + function test_accruedYieldOf_earner() external { + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + + _wrappedMToken.enableEarning(); + + _wrappedMToken.setAccountOf(_alice, 500, _EXP_SCALED_ONE); + + assertEq(_wrappedMToken.accruedYieldOf(_alice), 50); + + _wrappedMToken.setAccountOf(_alice, 1_000, _EXP_SCALED_ONE); + + assertEq(_wrappedMToken.accruedYieldOf(_alice), 100); + + _mToken.setCurrentIndex(2 * _currentIndex); + + assertEq(_wrappedMToken.accruedYieldOf(_alice), 1_200); + + _wrappedMToken.setAccountOf(_alice, 1_000, 3 * _currentIndex); + + assertEq(_wrappedMToken.accruedYieldOf(_alice), 0); + } + + /* ============ lastIndexOf ============ */ + function test_lastIndexOf() external { + _wrappedMToken.setAccountOf(_alice, 0, _EXP_SCALED_ONE); + + assertEq(_wrappedMToken.lastIndexOf(_alice), _EXP_SCALED_ONE); + + _wrappedMToken.setAccountOf(_alice, 0, 2 * _EXP_SCALED_ONE); + + assertEq(_wrappedMToken.lastIndexOf(_alice), 2 * _EXP_SCALED_ONE); + } + + /* ============ isEarning ============ */ + function test_isEarning() external { + _wrappedMToken.setAccountOf(_alice, 0); + + assertFalse(_wrappedMToken.isEarning(_alice)); + + _wrappedMToken.setAccountOf(_alice, 0, _EXP_SCALED_ONE); + + assertTrue(_wrappedMToken.isEarning(_alice)); + } + + /* ============ isEarningEnabled ============ */ + function test_isEarningEnabled() external { + assertFalse(_wrappedMToken.isEarningEnabled()); + + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + + _wrappedMToken.enableEarning(); + + assertTrue(_wrappedMToken.isEarningEnabled()); + + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), false); + + _wrappedMToken.disableEarning(); + + assertFalse(_wrappedMToken.isEarningEnabled()); + } + + /* ============ wasEarningEnabled ============ */ + function test_wasEarningEnabled() external { + assertFalse(_wrappedMToken.wasEarningEnabled()); + + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + + _wrappedMToken.enableEarning(); + + assertTrue(_wrappedMToken.wasEarningEnabled()); + + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), false); + + _wrappedMToken.disableEarning(); + + assertTrue(_wrappedMToken.wasEarningEnabled()); + } + + /* ============ claimOverrideRecipientFor ============ */ + function test_claimOverrideRecipientFor() external { + assertEq(_wrappedMToken.claimOverrideRecipientFor(_alice), address(0)); + + _registrar.set( + keccak256(abi.encode(_CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX, _alice)), + bytes32(uint256(uint160(_charlie))) + ); + + assertEq(_wrappedMToken.claimOverrideRecipientFor(_alice), _charlie); + } + /* ============ totalSupply ============ */ function test_totalSupply_onlyTotalNonEarningSupply() external { _wrappedMToken.setTotalNonEarningSupply(500); @@ -1131,7 +1410,7 @@ contract WrappedMTokenTests is Test { assertEq(_wrappedMToken.currentIndex(), 0); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); _wrappedMToken.enableEarning(); @@ -1141,7 +1420,7 @@ contract WrappedMTokenTests is Test { assertEq(_wrappedMToken.currentIndex(), 3 * _EXP_SCALED_ONE); - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), false); + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), false); _wrappedMToken.disableEarning(); @@ -1152,83 +1431,57 @@ contract WrappedMTokenTests is Test { assertEq(_wrappedMToken.currentIndex(), 3 * _EXP_SCALED_ONE); } - /* ============ misc ============ */ - function testFuzz_wrap_transfer_unwrap( - bool aliceIsEarning_, - uint240 aliceWrap_, - bool bobIsEarning_, - uint240 bobWrap_, - uint240 transfer_, - uint128 index_ - ) external { - _registrar.setListContains(_EARNERS_LIST, address(_wrappedMToken), true); - _registrar.setListContains(_EARNERS_LIST, _alice, true); - _registrar.setListContains(_EARNERS_LIST, _bob, true); + /* ============ excess ============ */ + function test_excess() external { + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); + _wrappedMToken.enableEarning(); - _mToken.setCurrentIndex(index_ = uint128(bound(index_, _EXP_SCALED_ONE, 10 * _EXP_SCALED_ONE))); + assertEq(_wrappedMToken.excess(), 0); - aliceWrap_ = uint240(bound(aliceWrap_, 0, _getMaxAmount(index_) / 3)); - bobWrap_ = uint240(bound(bobWrap_, 0, _getMaxAmount(index_) / 3)); + _wrappedMToken.setTotalNonEarningSupply(1_000); + _wrappedMToken.setPrincipalOfTotalEarningSupply(1_000); + _wrappedMToken.setTotalEarningSupply(1_000); - _mToken.setBalanceOf(_alice, aliceWrap_); - _mToken.setBalanceOf(_bob, bobWrap_); + _mToken.setBalanceOf(address(_wrappedMToken), 2_100); - if (aliceIsEarning_) { - _wrappedMToken.startEarningFor(_alice); - } + assertEq(_wrappedMToken.excess(), 0); - if (aliceWrap_ != 0) { - vm.prank(_alice); - _wrappedMToken.wrap(_alice, aliceWrap_); - } + _mToken.setBalanceOf(address(_wrappedMToken), 2_101); - _mToken.setCurrentIndex(index_ = uint128(bound(index_, index_, 10 * _EXP_SCALED_ONE))); + assertEq(_wrappedMToken.excess(), 0); - if (bobIsEarning_) { - _wrappedMToken.startEarningFor(_bob); - } + _mToken.setBalanceOf(address(_wrappedMToken), 2_102); - if (bobWrap_ != 0) { - vm.prank(_bob); - _wrappedMToken.wrap(_bob, bobWrap_); - } + assertEq(_wrappedMToken.excess(), 1); - _mToken.setCurrentIndex(index_ = uint128(bound(index_, index_, 10 * _EXP_SCALED_ONE))); + _mToken.setBalanceOf(address(_wrappedMToken), 3_102); - uint240 aliceYield_ = _wrappedMToken.accruedYieldOf(_alice); - uint240 bobYield_ = _wrappedMToken.accruedYieldOf(_bob); + assertEq(_wrappedMToken.excess(), 1_001); + } - transfer_ = uint240(bound(transfer_, 0, _wrappedMToken.balanceWithYieldOf(_alice))); + /* ============ totalAccruedYield ============ */ + function test_totalAccruedYield() external { + _registrar.setListContains(_EARNERS_LIST_NAME, address(_wrappedMToken), true); - _mToken.setCurrentIndex(index_ = uint128(bound(index_, index_, 10 * _EXP_SCALED_ONE))); + _wrappedMToken.enableEarning(); - aliceYield_ += _wrappedMToken.accruedYieldOf(_alice); + _wrappedMToken.setPrincipalOfTotalEarningSupply(909); + _wrappedMToken.setTotalEarningSupply(1_000); - if (_wrappedMToken.balanceWithYieldOf(_alice) != 0) { - vm.prank(_alice); - _wrappedMToken.unwrap(_charlie); - } + assertEq(_wrappedMToken.totalAccruedYield(), 0); - _mToken.setCurrentIndex(index_ = uint128(bound(index_, index_, 10 * _EXP_SCALED_ONE))); + _wrappedMToken.setPrincipalOfTotalEarningSupply(1_000); - bobYield_ += _wrappedMToken.accruedYieldOf(_bob); + assertEq(_wrappedMToken.totalAccruedYield(), 100); - if (_wrappedMToken.balanceWithYieldOf(_bob) != 0) { - vm.prank(_bob); - _wrappedMToken.unwrap(_charlie); - } + _wrappedMToken.setTotalEarningSupply(900); - assertEq(_wrappedMToken.totalEarningSupply(), 0); - assertEq(_wrappedMToken.totalNonEarningSupply(), 0); + assertEq(_wrappedMToken.totalAccruedYield(), 200); - uint240 total_ = aliceWrap_ + aliceYield_ + bobWrap_ + bobYield_; + _mToken.setCurrentIndex(_currentIndex = 1_210000000000); - if (total_ < 100e6) { - assertApproxEqAbs(_mToken.balanceOf(_charlie), total_, 100); - } else { - assertApproxEqRel(_mToken.balanceOf(_charlie), total_, 1e12); - } + assertEq(_wrappedMToken.totalAccruedYield(), 310); } /* ============ utils ============ */ diff --git a/test/utils/Invariants.sol b/test/utils/Invariants.sol index 086cc1f..9f1eff6 100644 --- a/test/utils/Invariants.sol +++ b/test/utils/Invariants.sol @@ -1,13 +1,12 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; +pragma solidity 0.8.26; +import { IndexingMath } from "../../lib/common/src/libs/IndexingMath.sol"; // import { console2 } from "../../lib/forge-std/src/Test.sol"; import { IERC20 } from "../../lib/common/src/interfaces/IERC20.sol"; -import { IndexingMath } from "../../src/libs/IndexingMath.sol"; - import { IWrappedMToken } from "../../src/interfaces/IWrappedMToken.sol"; library Invariants { diff --git a/test/utils/Mocks.sol b/test/utils/Mocks.sol index 56968c1..805eb7b 100644 --- a/test/utils/Mocks.sol +++ b/test/utils/Mocks.sol @@ -1,10 +1,8 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; +pragma solidity 0.8.26; contract MockM { - address public ttgRegistrar; - uint128 public currentIndex; mapping(address account => uint256 balance) public balanceOf; @@ -32,10 +30,6 @@ contract MockM { currentIndex = currentIndex_; } - function setTtgRegistrar(address ttgRegistrar_) external { - ttgRegistrar = ttgRegistrar_; - } - function startEarning() external { isEarning[msg.sender] = true; } @@ -46,8 +40,6 @@ contract MockM { } contract MockRegistrar { - address public vault; - mapping(bytes32 key => bytes32 value) public get; mapping(bytes32 list => mapping(address account => bool contains)) public listContains; @@ -59,8 +51,4 @@ contract MockRegistrar { function setListContains(bytes32 list_, address account_, bool contains_) external { listContains[list_][account_] = contains_; } - - function setVault(address vault_) external { - vault = vault_; - } } diff --git a/test/utils/WrappedMTokenHarness.sol b/test/utils/WrappedMTokenHarness.sol index 9bc9868..b285c83 100644 --- a/test/utils/WrappedMTokenHarness.sol +++ b/test/utils/WrappedMTokenHarness.sol @@ -1,11 +1,16 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; +pragma solidity 0.8.26; import { WrappedMToken } from "../../src/WrappedMToken.sol"; contract WrappedMTokenHarness is WrappedMToken { - constructor(address mToken_, address migrationAdmin_) WrappedMToken(mToken_, migrationAdmin_) {} + constructor( + address mToken_, + address registrar_, + address excessDestination_, + address migrationAdmin_ + ) WrappedMToken(mToken_, registrar_, excessDestination_, migrationAdmin_) {} function setIsEarningOf(address account_, bool isEarning_) external { _accounts[account_].isEarning = isEarning_;