Skip to content

Commit

Permalink
feat: principal-based account
Browse files Browse the repository at this point in the history
- principal-based earner account, instead of last index
- auto and manual migration for existing earners
  • Loading branch information
deluca-mike committed Jan 17, 2025
1 parent b682915 commit 3d3bde5
Show file tree
Hide file tree
Showing 9 changed files with 376 additions and 266 deletions.
221 changes: 140 additions & 81 deletions src/WrappedMToken.sol

Large diffs are not rendered by default.

29 changes: 22 additions & 7 deletions src/interfaces/IWrappedMToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ interface IWrappedMToken is IMigratable, IERC20Extended {

/* ============ Custom Errors ============ */

/// @notice Emitted when performing an operation that is not allowed on an un-migrated account.
error AccountNotMigrated();

/// @notice Emitted when performing an operation that is not allowed when earning is disabled.
error EarningIsDisabled();

Expand Down Expand Up @@ -156,6 +159,18 @@ interface IWrappedMToken is IMigratable, IERC20Extended {
*/
function stopEarningFor(address account) external;

/**
* @notice Migrates the account struct for `account` from v1 to v2.
* @param account The account to migrate.
*/
function migrateAccount(address account) external;

/**
* @notice Migrates the account structs for `accounts` from v1 to v2.
* @param accounts The accounts to migrate.
*/
function migrateAccounts(address[] calldata accounts) external;

/* ============ Temporary Admin Migration ============ */

/**
Expand Down Expand Up @@ -193,11 +208,11 @@ interface IWrappedMToken is IMigratable, IERC20Extended {
function balanceWithYieldOf(address account) external view returns (uint256 balance);

/**
* @notice Returns the last index of `account`.
* @param account The address of some account.
* @return lastIndex The last index of `account`, 0 if the account is not earning.
* @notice Returns the earning principal of `account`.
* @param account The address of some account.
* @return earningPrincipal The earning principal of `account`.
*/
function lastIndexOf(address account) external view returns (uint128 lastIndex);
function earningPrincipalOf(address account) external view returns (uint112 earningPrincipal);

/**
* @notice Returns the recipient to override as the destination for an account's claim of yield.
Expand All @@ -221,7 +236,7 @@ interface IWrappedMToken is IMigratable, IERC20Extended {
/**
* @notice Returns whether `account` is a wM earner.
* @param account The account being queried.
* @return isEarning true if the account has started earning.
* @return isEarning Whether the account is a wM earner.
*/
function isEarning(address account) external view returns (bool isEarning);

Expand All @@ -246,8 +261,8 @@ interface IWrappedMToken is IMigratable, IERC20Extended {
/// @notice The portion of total supply that is earning yield.
function totalEarningSupply() external view returns (uint240 totalSupply);

/// @notice The principal of totalEarningSupply to help compute totalAccruedYield(), and thus excess().
function principalOfTotalEarningSupply() external view returns (uint112 principalOfTotalEarningSupply);
/// @notice The total earning principal to help compute totalAccruedYield(), and thus excess().
function totalEarningPrincipal() external view returns (uint112 totalEarningPrincipal);

/// @notice The address of the destination where excess is claimed to.
function excessDestination() external view returns (address excessDestination);
Expand Down
8 changes: 5 additions & 3 deletions test/integration/MorphoBlue.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ contract MorphoBlueTests is MorphoTestBase {
_deployV2Components();
_migrate();

_wrappedMToken.migrateAccount(_MORPHO);

_oracle = _createOracle();

_morphoBalanceOfUSDC = IERC20(_USDC).balanceOf(_MORPHO);
Expand Down Expand Up @@ -95,7 +97,7 @@ contract MorphoBlueTests is MorphoTestBase {
vm.warp(vm.getBlockTimestamp() + 365 days);

assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM);
assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield += 49_292101);
assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield += 49_292100);

// USDC balance is unchanged.
assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC);
Expand Down Expand Up @@ -188,7 +190,7 @@ contract MorphoBlueTests is MorphoTestBase {

// `startEarningFor` has been called so wM yield has accrued in the pool.
assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM);
assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield += 4_994258);
assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield += 4_994256);

// USDC balance is unchanged.
assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC);
Expand Down Expand Up @@ -222,7 +224,7 @@ contract MorphoBlueTests is MorphoTestBase {

// `startEarningFor` has been called so wM yield has accrued in the pool.
assertEq(_wrappedMToken.balanceOf(_MORPHO), _morphoBalanceOfWM);
assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield += 77193);
assertEq(_wrappedMToken.accruedYieldOf(_MORPHO), _morphoAccruedYield += 77192);

// USDC balance is unchanged.
assertEq(IERC20(_USDC).balanceOf(_MORPHO), _morphoBalanceOfUSDC);
Expand Down
42 changes: 21 additions & 21 deletions test/integration/Protocol.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ contract ProtocolIntegrationTests is TestBase {
// Assert Globals
assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 99_999999);
assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield);
assertEq(_wrappedMToken.excess(), _excess);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 1);
assertEq(_wrappedMToken.excess(), _excess += 1);

assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess);

Expand Down Expand Up @@ -148,8 +148,8 @@ contract ProtocolIntegrationTests is TestBase {
// Assert Globals
assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 199_999999);
assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield);
assertEq(_wrappedMToken.excess(), _excess);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 1);
assertEq(_wrappedMToken.excess(), _excess += 1);

assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess);

Expand Down Expand Up @@ -205,7 +205,7 @@ contract ProtocolIntegrationTests is TestBase {

// Assert Alice (Earner)
assertEq(_wrappedMToken.balanceOf(_alice), _aliceBalance);
assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 2_423880);
assertEq(_wrappedMToken.accruedYieldOf(_alice), _aliceAccruedYield += 2_423881);

// Assert Bob (Earner)
assertEq(_wrappedMToken.balanceOf(_bob), _bobBalance);
Expand Down Expand Up @@ -248,8 +248,8 @@ contract ProtocolIntegrationTests is TestBase {
// Assert Globals
assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += _aliceBalance);
assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += 100_000000);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield);
assertEq(_wrappedMToken.excess(), _excess);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 1);
assertEq(_wrappedMToken.excess(), _excess += 1);

assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess);

Expand Down Expand Up @@ -303,8 +303,8 @@ contract ProtocolIntegrationTests is TestBase {
// Assert Globals
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);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 2_395361 + 1);
assertEq(_wrappedMToken.excess(), _excess += 2);

assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess);

Expand All @@ -321,8 +321,8 @@ contract ProtocolIntegrationTests is TestBase {
// Assert Globals
assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 50_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply -= 50_000000);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield += 1);
assertEq(_wrappedMToken.excess(), _excess -= 1);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield);
assertEq(_wrappedMToken.excess(), _excess);

assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess);

Expand Down Expand Up @@ -374,8 +374,8 @@ contract ProtocolIntegrationTests is TestBase {
// Assert Globals
assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 99_999999);
assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += 100_000000);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield);
assertEq(_wrappedMToken.excess(), _excess);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 1);
assertEq(_wrappedMToken.excess(), _excess += 1);

assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess);

Expand Down Expand Up @@ -403,8 +403,8 @@ contract ProtocolIntegrationTests is TestBase {
// Assert Globals
assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += 100_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += 100_000000);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield += 1);
assertEq(_wrappedMToken.excess(), _excess -= 1);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield);
assertEq(_wrappedMToken.excess(), _excess);

assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess);

Expand Down Expand Up @@ -432,8 +432,8 @@ contract ProtocolIntegrationTests is TestBase {
// Assert Globals
assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply -= 99_999999);
assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply += _aliceBalance);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 3_614473);
assertEq(_wrappedMToken.excess(), _excess);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 3_614473 + 1);
assertEq(_wrappedMToken.excess(), _excess += 1);

assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess);

Expand All @@ -449,8 +449,8 @@ contract ProtocolIntegrationTests is TestBase {
// Assert Globals
assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply += _carolBalance);
assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply -= _carolBalance);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield += 1);
assertEq(_wrappedMToken.excess(), _excess -= 1);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield);
assertEq(_wrappedMToken.excess(), _excess);

assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess);

Expand Down Expand Up @@ -509,8 +509,8 @@ contract ProtocolIntegrationTests is TestBase {
// Assert Globals
assertEq(_wrappedMToken.totalEarningSupply(), _totalEarningSupply -= 100_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), _totalNonEarningSupply);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 2_395361);
assertEq(_wrappedMToken.excess(), _excess);
assertEq(_wrappedMToken.totalAccruedYield(), _totalAccruedYield -= 2_395361 + 1);
assertEq(_wrappedMToken.excess(), _excess += 1);

assertGe(_wrapperBalanceOfM, _totalEarningSupply + _totalNonEarningSupply + _totalAccruedYield + _excess);

Expand Down
4 changes: 3 additions & 1 deletion test/integration/UniswapV3.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ contract UniswapV3IntegrationTests is TestBase {
_deployV2Components();
_migrate();

_wrappedMToken.migrateAccount(_pool);

_poolClaimRecipient = _wrappedMToken.claimOverrideRecipientFor(_pool);

_wrapperBalanceOfM = _mToken.balanceOf(address(_wrappedMToken));
Expand Down Expand Up @@ -338,7 +340,7 @@ 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 += 11_753_024234);
assertEq(_wrappedMToken.accruedYieldOf(_pool), _poolAccruedYield += 11_753_024235);

/* ============ Eric (Earner) Swaps Exact wM for USDC ============ */

Expand Down
32 changes: 16 additions & 16 deletions test/unit/Stories.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ contract StoryTests is Test {
assertEq(_wrappedMToken.totalEarningSupply(), 300_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), 300_000000);
assertEq(_wrappedMToken.totalSupply(), 600_000000);
assertEq(_wrappedMToken.totalAccruedYield(), 50_000001);
assertEq(_wrappedMToken.excess(), 249_999999);
assertEq(_wrappedMToken.totalAccruedYield(), 49_999998);
assertEq(_wrappedMToken.excess(), 250_000002);

vm.prank(_dave);
_wrappedMToken.transfer(_bob, 50_000000);
Expand All @@ -212,8 +212,8 @@ contract StoryTests is Test {
assertEq(_wrappedMToken.totalEarningSupply(), 400_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), 250_000000);
assertEq(_wrappedMToken.totalSupply(), 650_000000);
assertEq(_wrappedMToken.totalAccruedYield(), 2);
assertEq(_wrappedMToken.excess(), 249_999996);
assertEq(_wrappedMToken.totalAccruedYield(), 0);
assertEq(_wrappedMToken.excess(), 250_000002);

_mToken.setCurrentIndex(4 * _EXP_SCALED_ONE);
_mToken.setBalanceOf(address(_wrappedMToken), 1_200_000000); // was 900 @ 3.0, so 1200 @ 4.0
Expand All @@ -238,8 +238,8 @@ contract StoryTests is Test {
assertEq(_wrappedMToken.totalEarningSupply(), 400_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), 250_000000);
assertEq(_wrappedMToken.totalSupply(), 650_000000);
assertEq(_wrappedMToken.totalAccruedYield(), 133_333336);
assertEq(_wrappedMToken.excess(), 416_666664);
assertEq(_wrappedMToken.totalAccruedYield(), 133_333328);
assertEq(_wrappedMToken.excess(), 416_666672);

_registrar.setListContains(_EARNERS_LIST_NAME, _alice, false);

Expand All @@ -253,8 +253,8 @@ contract StoryTests is Test {
assertEq(_wrappedMToken.totalEarningSupply(), 200_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), 516_666664);
assertEq(_wrappedMToken.totalSupply(), 716_666664);
assertEq(_wrappedMToken.totalAccruedYield(), 66_666672);
assertEq(_wrappedMToken.excess(), 416_666664);
assertEq(_wrappedMToken.totalAccruedYield(), 66_666664);
assertEq(_wrappedMToken.excess(), 416_666672);

_registrar.setListContains(_EARNERS_LIST_NAME, _carol, true);

Expand All @@ -268,8 +268,8 @@ contract StoryTests is Test {
assertEq(_wrappedMToken.totalEarningSupply(), 400_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), 316_666664);
assertEq(_wrappedMToken.totalSupply(), 716_666664);
assertEq(_wrappedMToken.totalAccruedYield(), 66_666672);
assertEq(_wrappedMToken.excess(), 416_666664);
assertEq(_wrappedMToken.totalAccruedYield(), 66_666664);
assertEq(_wrappedMToken.excess(), 416_666672);

_mToken.setCurrentIndex(5 * _EXP_SCALED_ONE);
_mToken.setBalanceOf(address(_wrappedMToken), 1_500_000000); // was 1200 @ 4.0, so 1500 @ 5.0
Expand All @@ -294,8 +294,8 @@ contract StoryTests is Test {
assertEq(_wrappedMToken.totalEarningSupply(), 400_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), 316_666664);
assertEq(_wrappedMToken.totalSupply(), 716_666664);
assertEq(_wrappedMToken.totalAccruedYield(), 183_333340);
assertEq(_wrappedMToken.excess(), 599_999995);
assertEq(_wrappedMToken.totalAccruedYield(), 183_333330);
assertEq(_wrappedMToken.excess(), 600_000005);

vm.prank(_alice);
_wrappedMToken.unwrap(_alice, 266_666664);
Expand All @@ -308,8 +308,8 @@ contract StoryTests is Test {
assertEq(_wrappedMToken.totalEarningSupply(), 400_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), 50_000000);
assertEq(_wrappedMToken.totalSupply(), 450_000000);
assertEq(_wrappedMToken.totalAccruedYield(), 183_333340);
assertEq(_wrappedMToken.excess(), 600_000000);
assertEq(_wrappedMToken.totalAccruedYield(), 183_333330);
assertEq(_wrappedMToken.excess(), 600_000010);

vm.prank(_bob);
_wrappedMToken.unwrap(_bob, 333_333330);
Expand All @@ -322,8 +322,8 @@ contract StoryTests is Test {
assertEq(_wrappedMToken.totalEarningSupply(), 200_000000);
assertEq(_wrappedMToken.totalNonEarningSupply(), 50_000000);
assertEq(_wrappedMToken.totalSupply(), 250_000000);
assertEq(_wrappedMToken.totalAccruedYield(), 50_000010);
assertEq(_wrappedMToken.excess(), 600_000000);
assertEq(_wrappedMToken.totalAccruedYield(), 50_000000);
assertEq(_wrappedMToken.excess(), 600_000010);

vm.prank(_carol);
_wrappedMToken.unwrap(_carol, 250_000000);
Expand Down
Loading

0 comments on commit 3d3bde5

Please sign in to comment.