Skip to content

Commit

Permalink
fix(MToken): handle allowance in burn
Browse files Browse the repository at this point in the history
  • Loading branch information
PierrickGT committed Aug 28, 2024
1 parent 524d98a commit 16ae64d
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 5 deletions.
2 changes: 1 addition & 1 deletion script/DeployBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity 0.8.26;

import { ContractHelper } from "../lib/common/src/ContractHelper.sol";
import { ContractHelper } from "../lib/common/src/libs/ContractHelper.sol";

import { MToken } from "../src/MToken.sol";

Expand Down
12 changes: 12 additions & 0 deletions src/MToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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 @@ -78,6 +79,17 @@ contract MToken is IMToken, ContinuousIndexing, ERC20Extended {

/// @inheritdoc IMToken
function burn(address account_, uint256 amount_) external onlyPortal {
uint256 spenderAllowance_ = allowance[account_][msg.sender];

if (spenderAllowance_ != type(uint256).max) {
if (spenderAllowance_ < amount_)
revert IERC20Extended.InsufficientAllowance(msg.sender, spenderAllowance_, amount_);

unchecked {
_setAllowance(account_, msg.sender, spenderAllowance_ - amount_);
}
}

_burn(account_, amount_);
}

Expand Down
9 changes: 5 additions & 4 deletions src/interfaces/IMToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,18 @@ interface IMToken is IContinuousIndexing, IERC20Extended {

/**
* @notice Updates the index and mints tokens.
* @dev MUST only be callable by a trusted bridge.
* @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.
* @param index The index to update to.
*/
function mint(address account, uint256 amount, uint128 index) external;

/**
* @notice Burns tokens.
* @dev MUST only be callable by a trusted bridge.
* @param account The address of account to burn from.
* @notice Burns `amount` of M tokens from `account`.
* @dev MUST only be callable by the Spoke Portal.
* @dev MUST revert if `account` has not approved the Spoke Portal to burn their M tokens.
* @param account The address of the account to burn from.
* @param amount The amount of M Token to burn.
*/
function burn(address account, uint256 amount) external;
Expand Down
43 changes: 43 additions & 0 deletions test/MToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ contract MTokenTests is TestUtils {
function test_burn_insufficientBalance_fromNonEarner() external {
_mToken.setInternalBalanceOf(_alice, 999);

vm.prank(_alice);
_mToken.approve(_portal, type(uint256).max);

vm.expectRevert(abi.encodeWithSelector(IMToken.InsufficientBalance.selector, _alice, 999, 1_000));
vm.prank(_portal);
_mToken.burn(_alice, 1_000);
Expand All @@ -225,16 +228,47 @@ contract MTokenTests is TestUtils {
_mToken.setIsEarning(_alice, true);
_mToken.setInternalBalanceOf(_alice, 908);

vm.prank(_alice);
_mToken.approve(_portal, type(uint256).max);

vm.expectRevert(abi.encodeWithSelector(IMToken.InsufficientBalance.selector, _alice, 908, 910));
vm.prank(_portal);
_mToken.burn(_alice, 1_000);
}

function test_burn_insufficientAllowance() external {
uint256 amount_ = 1_000;

vm.expectRevert(abi.encodeWithSelector(IERC20Extended.InsufficientAllowance.selector, _portal, 0, amount_));

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

function test_burn_updateAllowance() external {
uint256 amount_ = 1_000;

_mToken.setInternalBalanceOf(_alice, amount_);

vm.prank(_alice);
_mToken.approve(_portal, amount_);

assertEq(_mToken.allowance(_alice, _portal), amount_);

vm.prank(_portal);
_mToken.burn(_alice, amount_);

assertEq(_mToken.allowance(_alice, _portal), 0);
}

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

_mToken.setInternalBalanceOf(_alice, 1_000);

vm.prank(_alice);
_mToken.approve(_portal, type(uint256).max);

vm.prank(_portal);
_mToken.burn(_alice, 500);

Expand All @@ -261,6 +295,9 @@ contract MTokenTests is TestUtils {
_mToken.setTotalNonEarningSupply(supply_);
_mToken.setInternalBalanceOf(_alice, supply_);

vm.prank(_alice);
_mToken.approve(_portal, type(uint256).max);

vm.prank(_portal);
_mToken.burn(_alice, supply_ / 2);

Expand All @@ -286,6 +323,9 @@ contract MTokenTests is TestUtils {
_mToken.setIsEarning(_alice, true);
_mToken.setInternalBalanceOf(_alice, 909);

vm.prank(_alice);
_mToken.approve(_portal, type(uint256).max);

vm.prank(_portal);
_mToken.burn(_alice, 1);

Expand Down Expand Up @@ -320,6 +360,9 @@ contract MTokenTests is TestUtils {
uint256 burnAmount_ = _mToken.balanceOf(_alice) / 2;
vm.assume(burnAmount_ != 0);

vm.prank(_alice);
_mToken.approve(_portal, type(uint256).max);

vm.prank(_portal);
_mToken.burn(_alice, burnAmount_);

Expand Down

0 comments on commit 16ae64d

Please sign in to comment.