Skip to content

Commit

Permalink
Merge pull request #194 from m0-foundation/refactor/remove-index-check
Browse files Browse the repository at this point in the history
refactor: revert if index is decreasing in MToken updateIndex
  • Loading branch information
0xIryna authored Oct 28, 2024
2 parents 2e42f85 + e67af42 commit 9661cce
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 32 deletions.
14 changes: 9 additions & 5 deletions src/MToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { ERC20Extended } from "../lib/common/src/ERC20Extended.sol";
import { UIntMath } from "../lib/common/src/libs/UIntMath.sol";

import { IERC20 } from "../lib/common/src/interfaces/IERC20.sol";
import { IERC20Extended } from "../lib/common/src/interfaces/IERC20Extended.sol";

import { RegistrarReader } from "./libs/RegistrarReader.sol";

Expand Down Expand Up @@ -73,7 +72,12 @@ contract MToken is IMToken, ContinuousIndexing, ERC20Extended {

/// @inheritdoc IMToken
function mint(address account_, uint256 amount_, uint128 index_) external onlyPortal {
super.updateIndex(index_);
_updateIndex(index_);
_mint(account_, amount_);
}

/// @inheritdoc IMToken
function mint(address account_, uint256 amount_) external onlyPortal {
_mint(account_, amount_);
}

Expand All @@ -82,9 +86,9 @@ contract MToken is IMToken, ContinuousIndexing, ERC20Extended {
_burn(msg.sender, amount_);
}

/// @inheritdoc IContinuousIndexing
function updateIndex(uint128 index_) public override(IContinuousIndexing, ContinuousIndexing) onlyPortal {
super.updateIndex(index_);
/// @inheritdoc IMToken
function updateIndex(uint128 index_) external onlyPortal {
_updateIndex(index_);
}

/// @inheritdoc IMToken
Expand Down
18 changes: 10 additions & 8 deletions src/abstract/ContinuousIndexing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,25 @@ abstract contract ContinuousIndexing is IContinuousIndexing {
latestUpdateTimestamp = uint40(block.timestamp);
}

/* ============ Interactive Functions ============ */
/* ============ View/Pure Functions ============ */

/// @inheritdoc IContinuousIndexing
function updateIndex(uint128 index_) public virtual {
if (index_ <= latestIndex) return;
function currentIndex() public view virtual returns (uint128);

/* ============ Internal Interactive Functions ============ */

/**
* @notice Updates the latest index and latest accrual time.
* @param index_ The new index to compute present amounts from principal amounts.
*/
function _updateIndex(uint128 index_) internal virtual {
if (index_ < latestIndex) revert DecreasingIndex(index_, latestIndex);
latestIndex = index_;
latestUpdateTimestamp = uint40(block.timestamp);

emit IndexUpdated(index_);
}

/* ============ View/Pure Functions ============ */

/// @inheritdoc IContinuousIndexing
function currentIndex() public view virtual returns (uint128);

/* ============ Internal View/Pure Functions ============ */

/**
Expand Down
9 changes: 5 additions & 4 deletions src/interfaces/IContinuousIndexing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ interface IContinuousIndexing {
*/
event IndexUpdated(uint128 indexed index);

/* ============ Interactive Functions ============ */
/* ============ Custom Error ============ */

/**
* @notice Updates the latest index and latest accrual time in storage.
* @param index The new index to compute present amounts from principal amounts.
* @notice Emitted during index update when the new index is less than the current one.
* @param index The new index.
* @param currentIndex The current index.
*/
function updateIndex(uint128 index) external;
error DecreasingIndex(uint128 index, uint128 currentIndex);

/* ============ View/Pure Functions ============ */

Expand Down
15 changes: 15 additions & 0 deletions src/interfaces/IMToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,28 @@ interface IMToken is IContinuousIndexing, IERC20Extended {
*/
function mint(address account, uint256 amount, uint128 index) external;

/**
* @notice Mints tokens.
* @dev MUST only be callable by the Spoke Portal.
* @param account The address of account to mint to.
* @param amount The amount of M Token to mint.
*/
function mint(address account, uint256 amount) external;

/**
* @notice Burns `amount` of M tokens from `msg.sender`.
* @dev MUST only be callable by the Spoke Portal.
* @param amount The amount of M Token to burn.
*/
function burn(uint256 amount) external;

/**
* @notice Updates the latest index and latest accrual time in storage.
* @dev MUST only be callable by the Spoke Portal.
* @param index The new index to compute present amounts from principal amounts.
*/
function updateIndex(uint128 index) external;

/// @notice Starts earning for caller if allowed by TTG.
function startEarning() external;

Expand Down
64 changes: 49 additions & 15 deletions test/MToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity 0.8.26;
import { IERC20Extended } from "../lib/common/src/interfaces/IERC20Extended.sol";
import { UIntMath } from "../lib/common/src/libs/UIntMath.sol";

import { IContinuousIndexing } from "../src/interfaces/IContinuousIndexing.sol";
import { IMToken } from "../src/interfaces/IMToken.sol";

import { ContinuousIndexingMath } from "../src/libs/ContinuousIndexingMath.sol";
Expand Down Expand Up @@ -67,26 +68,49 @@ contract MTokenTests is TestUtils {
/* ============ mint ============ */
function test_mint_notPortal() external {
vm.expectRevert(IMToken.NotPortal.selector);
_mToken.mint(_alice, 0, 0);
_mToken.mint(_alice, 0);
}

function test_mint_insufficientAmount() external {
vm.expectRevert(abi.encodeWithSelector(IERC20Extended.InsufficientAmount.selector, 0));

vm.prank(_portal);
_mToken.mint(_alice, 0, 0);
_mToken.mint(_alice, 0);
}

function test_mint_invalidRecipient() external {
vm.expectRevert(abi.encodeWithSelector(IERC20Extended.InvalidRecipient.selector, address(0)));

vm.prank(_portal);
_mToken.mint(address(0), 1_000, 0);
_mToken.mint(address(0), 1_000);
}

function test_mint_mintAndUpdateIndex_decreasingIndex() external {
uint256 amount_ = 1_000;
uint128 index_ = 1;

vm.expectRevert(
abi.encodeWithSelector(IContinuousIndexing.DecreasingIndex.selector, index_, _mToken.currentIndex())
);

vm.prank(_portal);
_mToken.mint(_alice, amount_, index_);
}

function test_mint_mintAndUpdateIndex() external {
uint256 amount_ = 1_000;
uint128 index_ = 2e12;

vm.prank(_portal);
_mToken.mint(_alice, amount_, index_);

assertEq(_mToken.internalBalanceOf(_alice), amount_);
assertEq(_mToken.currentIndex(), index_);
}

function test_mint_toNonEarner() external {
vm.prank(_portal);
_mToken.mint(_alice, 1_000, 0);
_mToken.mint(_alice, 1_000);

assertEq(_mToken.internalBalanceOf(_alice), 1_000);
assertEq(_mToken.totalNonEarningSupply(), 1_000);
Expand All @@ -99,7 +123,7 @@ contract MTokenTests is TestUtils {
amount_ = bound(amount_, 1, type(uint112).max);

vm.prank(_portal);
_mToken.mint(_alice, amount_, 0);
_mToken.mint(_alice, amount_);

assertEq(_mToken.internalBalanceOf(_alice), amount_);
assertEq(_mToken.totalNonEarningSupply(), amount_);
Expand All @@ -113,18 +137,18 @@ contract MTokenTests is TestUtils {
_mToken.setIsEarning(_alice, true);

vm.prank(_portal);
_mToken.mint(_alice, type(uint112).max - 1, 0);
_mToken.mint(_alice, type(uint112).max - 1);

vm.prank(_portal);
vm.expectRevert(IMToken.OverflowsPrincipalOfTotalSupply.selector);
_mToken.mint(_bob, 2, 0);
_mToken.mint(_bob, 2);
}

function test_mint_toEarner() external {
_mToken.setIsEarning(_alice, true);

vm.prank(_portal);
_mToken.mint(_alice, 999, 0);
_mToken.mint(_alice, 999);

assertEq(_mToken.internalBalanceOf(_alice), 908);
assertEq(_mToken.totalNonEarningSupply(), 0);
Expand All @@ -133,7 +157,7 @@ contract MTokenTests is TestUtils {
assertEq(_mToken.latestUpdateTimestamp(), _start);

vm.prank(_portal);
_mToken.mint(_alice, 1, 0);
_mToken.mint(_alice, 1);

// No change due to principal round down on mint.
assertEq(_mToken.internalBalanceOf(_alice), 908);
Expand All @@ -143,7 +167,7 @@ contract MTokenTests is TestUtils {
assertEq(_mToken.latestUpdateTimestamp(), _start);

vm.prank(_portal);
_mToken.mint(_alice, 2, 0);
_mToken.mint(_alice, 2);

assertEq(_mToken.internalBalanceOf(_alice), 909);
assertEq(_mToken.totalNonEarningSupply(), 0);
Expand All @@ -158,7 +182,7 @@ contract MTokenTests is TestUtils {
_mToken.setIsEarning(_alice, true);

vm.prank(_portal);
_mToken.mint(_alice, amount_, 0);
_mToken.mint(_alice, amount_);

uint256 expectedPrincipalBalance_ = _getPrincipalAmountRoundedDown(uint240(amount_), _expectedCurrentIndex);

Expand All @@ -169,7 +193,7 @@ contract MTokenTests is TestUtils {
assertEq(_mToken.latestUpdateTimestamp(), _start);

vm.prank(_portal);
_mToken.mint(_alice, 1, 0);
_mToken.mint(_alice, 1);

expectedPrincipalBalance_ += _getPrincipalAmountRoundedDown(uint240(1), _expectedCurrentIndex);

Expand All @@ -181,7 +205,7 @@ contract MTokenTests is TestUtils {
assertEq(_mToken.latestUpdateTimestamp(), _start);

vm.prank(_portal);
_mToken.mint(_alice, 2, 0);
_mToken.mint(_alice, 2);

expectedPrincipalBalance_ += _getPrincipalAmountRoundedDown(uint240(2), _expectedCurrentIndex);

Expand All @@ -198,7 +222,7 @@ contract MTokenTests is TestUtils {

vm.expectRevert(IMToken.OverflowsPrincipalOfTotalSupply.selector);
vm.prank(_portal);
_mToken.mint(_alice, type(uint112).max, 0);
_mToken.mint(_alice, type(uint112).max);
}

/* ============ burn ============ */
Expand Down Expand Up @@ -232,7 +256,7 @@ contract MTokenTests is TestUtils {

function test_burn_fromNonEarner() external {
_mToken.setTotalNonEarningSupply(1_000);

_mToken.setInternalBalanceOf(_portal, 1_000);

vm.prank(_portal);
Expand Down Expand Up @@ -833,6 +857,16 @@ contract MTokenTests is TestUtils {
_mToken.updateIndex(0);
}

function test_updateIndex_decreasingIndex() external {
uint128 index_ = 0;
vm.expectRevert(
abi.encodeWithSelector(IContinuousIndexing.DecreasingIndex.selector, index_, _mToken.currentIndex())
);

vm.prank(_portal);
_mToken.updateIndex(index_);
}

function test_updateIndex() external {
uint256 expectedLatestUpdateTimestamp_ = _start;

Expand Down

0 comments on commit 9661cce

Please sign in to comment.