Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CCIP-4130 CCIP-Compatible Generic BurnMintERC20 #15123

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions contracts/.changeset/hot-pandas-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@chainlink/contracts': minor
---

Add a new contract, BurnMintERC20, which is basically just our ERC677 implementation without the transferAndCall function. #internal


PR issue: CCIP-4130

Solidity Review issue: CCIP-3966
216 changes: 108 additions & 108 deletions contracts/gas-snapshots/ccip.gas-snapshot

Large diffs are not rendered by default.

30 changes: 28 additions & 2 deletions contracts/gas-snapshots/shared.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@ AuthorizedCallers_applyAuthorizedCallerUpdates:test_SkipRemove_Success() (gas: 3
AuthorizedCallers_applyAuthorizedCallerUpdates:test_ZeroAddressNotAllowed_Revert() (gas: 64413)
AuthorizedCallers_constructor:test_ZeroAddressNotAllowed_Revert() (gas: 64390)
AuthorizedCallers_constructor:test_constructor_Success() (gas: 674931)
BurnMintERC20approve:test_Approve_Success() (gas: 55564)
BurnMintERC20approve:test_InvalidAddress_Reverts() (gas: 10663)
BurnMintERC20burn:test_BasicBurn_Success() (gas: 135713)
BurnMintERC20burn:test_BurnFromZeroAddress_Reverts() (gas: 43655)
BurnMintERC20burn:test_ExceedsBalance_Reverts() (gas: 21711)
BurnMintERC20burn:test_SenderNotBurner_Reverts() (gas: 19837)
BurnMintERC20burnFrom:test_BurnFrom_Success() (gas: 57874)
BurnMintERC20burnFrom:test_ExceedsBalance_Reverts() (gas: 35788)
BurnMintERC20burnFrom:test_InsufficientAllowance_Reverts() (gas: 21720)
BurnMintERC20burnFrom:test_SenderNotBurner_Reverts() (gas: 19804)
BurnMintERC20burnFromAlias:test_BurnFrom_Success() (gas: 57918)
BurnMintERC20burnFromAlias:test_ExceedsBalance_Reverts() (gas: 35821)
BurnMintERC20burnFromAlias:test_InsufficientAllowance_Reverts() (gas: 21762)
BurnMintERC20burnFromAlias:test_SenderNotBurner_Reverts() (gas: 19846)
BurnMintERC20constructor:test_Constructor_Success() (gas: 1762798)
BurnMintERC20decreaseApproval:test_DecreaseApproval_Success() (gas: 31192)
BurnMintERC20getCCIPAdmin:test_getCCIPAdmin_Success() (gas: 10525)
BurnMintERC20getCCIPAdmin:test_setCCIPAdmin_Success() (gas: 21612)
BurnMintERC20grantMintAndBurnRoles:test_GrantMintAndBurnRoles_Success() (gas: 79321)
BurnMintERC20increaseApproval:test_IncreaseApproval_Success() (gas: 44163)
BurnMintERC20mint:test_BasicMint_Success() (gas: 57263)
BurnMintERC20mint:test_MaxSupplyExceeded_Reverts() (gas: 50037)
BurnMintERC20mint:test_SenderNotMinter_Reverts() (gas: 11479)
BurnMintERC20supportsInterface:test_SupportsInterface_Success() (gas: 11217)
BurnMintERC20transfer:test_InvalidAddress_Reverts() (gas: 10617)
BurnMintERC20transfer:test_Transfer_Success() (gas: 42343)
BurnMintERC677_approve:testApproveSuccess() (gas: 55512)
BurnMintERC677_approve:testInvalidAddressReverts() (gas: 10663)
BurnMintERC677_burn:testBasicBurnSuccess() (gas: 172100)
Expand Down Expand Up @@ -39,10 +65,10 @@ CallWithExactGas__callWithExactGas:test_CallWithExactGasSafeReturnDataExactGas()
CallWithExactGas__callWithExactGas:test_NoContractReverts() (gas: 11559)
CallWithExactGas__callWithExactGas:test_NoGasForCallExactCheckReverts() (gas: 15788)
CallWithExactGas__callWithExactGas:test_NotEnoughGasForCallReverts() (gas: 16241)
CallWithExactGas__callWithExactGas:test_callWithExactGasSuccess(bytes,bytes4) (runs: 257, μ: 15744, ~: 15697)
CallWithExactGas__callWithExactGas:test_callWithExactGasSuccess(bytes,bytes4) (runs: 256, μ: 15744, ~: 15697)
CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractExactGasSuccess() (gas: 20116)
CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractReceiverErrorSuccess() (gas: 66439)
CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractSuccess(bytes,bytes4) (runs: 257, μ: 16254, ~: 16207)
CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_CallWithExactGasEvenIfTargetIsNoContractSuccess(bytes,bytes4) (runs: 256, μ: 16254, ~: 16207)
CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NoContractSuccess() (gas: 12962)
CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NoGasForCallExactCheckReturnFalseSuccess() (gas: 13005)
CallWithExactGas__callWithExactGasEvenIfTargetIsNoContract:test_NotEnoughGasForCallReturnsFalseSuccess() (gas: 13317)
Expand Down
1 change: 1 addition & 0 deletions contracts/scripts/native_solc_compile_all_ccip
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ compileContract ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol
compileContract ccip/capability/CCIPHome.sol
compileContract ccip/NonceManager.sol
compileContract shared/token/ERC677/BurnMintERC677.sol
compileContract shared/token/ERC20/BurnMintERC20.sol


# Pools
Expand Down
1 change: 1 addition & 0 deletions contracts/scripts/native_solc_compile_all_shared
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ compileContract () {

compileContract shared/token/ERC677/BurnMintERC677.sol
compileContract shared/token/ERC677/LinkToken.sol
compileContract shared/token/ERC20/BurnMintERC20.sol
compileContract shared/mocks/WERC20Mock.sol
compileContract vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol
compileContract shared/test/helpers/ChainReaderTester.sol
10 changes: 5 additions & 5 deletions contracts/src/v0.8/ccip/test/TokenSetup.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.24;

import {BurnMintERC677} from "../../shared/token/ERC677/BurnMintERC677.sol";
import {BurnMintERC20} from "../../shared/token/ERC20/BurnMintERC20.sol";
import {BurnMintTokenPool} from "../pools/BurnMintTokenPool.sol";
import {LockReleaseTokenPool} from "../pools/LockReleaseTokenPool.sol";
import {TokenPool} from "../pools/TokenPool.sol";
Expand All @@ -26,14 +26,14 @@ contract TokenSetup is RouterSetup {
mapping(address sourceToken => address destToken) internal s_destTokenBySourceToken;

function _deploySourceToken(string memory tokenName, uint256 dealAmount, uint8 decimals) internal returns (address) {
BurnMintERC677 token = new BurnMintERC677(tokenName, tokenName, decimals, 0);
BurnMintERC20 token = new BurnMintERC20(tokenName, tokenName, decimals, 0, 0);
s_sourceTokens.push(address(token));
deal(address(token), OWNER, dealAmount);
return address(token);
}

function _deployDestToken(string memory tokenName, uint256 dealAmount) internal returns (address) {
BurnMintERC677 token = new BurnMintERC677(tokenName, tokenName, 18, 0);
BurnMintERC20 token = new BurnMintERC20(tokenName, tokenName, 18, 0, 0);
s_destTokens.push(address(token));
deal(address(token), OWNER, dealAmount);
return address(token);
Expand Down Expand Up @@ -63,8 +63,8 @@ contract TokenSetup is RouterSetup {
}

BurnMintTokenPool pool =
new MaybeRevertingBurnMintTokenPool(BurnMintERC677(token), new address[](0), address(s_mockRMN), router);
BurnMintERC677(token).grantMintAndBurnRoles(address(pool));
new MaybeRevertingBurnMintTokenPool(BurnMintERC20(token), new address[](0), address(s_mockRMN), router);
BurnMintERC20(token).grantMintAndBurnRoles(address(pool));

if (isSourcePool) {
s_sourcePoolByToken[address(token)] = address(pool);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pragma solidity ^0.8.0;

import {IMessageTransmitterWithRelay} from "./interfaces/IMessageTransmitterWithRelay.sol";

import {BurnMintERC677} from "../../../shared/token/ERC677/BurnMintERC677.sol";
import {BurnMintERC20} from "../../../shared/token/ERC20/BurnMintERC20.sol";

// solhint-disable
contract MockE2EUSDCTransmitter is IMessageTransmitterWithRelay {
Expand All @@ -28,7 +28,7 @@ contract MockE2EUSDCTransmitter is IMessageTransmitterWithRelay {
// Next available nonce from this source domain
uint64 public nextAvailableNonce;

BurnMintERC677 internal immutable i_token;
BurnMintERC20 internal immutable i_token;

/**
* @notice Emitted when a new message is dispatched
Expand All @@ -41,7 +41,7 @@ contract MockE2EUSDCTransmitter is IMessageTransmitterWithRelay {
i_localDomain = _localDomain;
s_shouldSucceed = true;

i_token = BurnMintERC677(token);
i_token = BurnMintERC20(token);
}

/// @param message The original message on the source chain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.24;
import {IMessageInterceptor} from "../../../interfaces/IMessageInterceptor.sol";
import {IRouter} from "../../../interfaces/IRouter.sol";

import {BurnMintERC677} from "../../../../shared/token/ERC677/BurnMintERC677.sol";
import {BurnMintERC20} from "../../../../shared/token/ERC20/BurnMintERC20.sol";
import {FeeQuoter} from "../../../FeeQuoter.sol";
import {Client} from "../../../libraries/Client.sol";
import {Internal} from "../../../libraries/Internal.sol";
Expand Down Expand Up @@ -416,9 +416,9 @@ contract OnRamp_forwardFromRouter is OnRampSetup {
vm.startPrank(OWNER);

MaybeRevertingBurnMintTokenPool newPool = new MaybeRevertingBurnMintTokenPool(
BurnMintERC677(sourceETH), new address[](0), address(s_mockRMNRemote), address(s_sourceRouter)
BurnMintERC20(sourceETH), new address[](0), address(s_mockRMNRemote), address(s_sourceRouter)
);
BurnMintERC677(sourceETH).grantMintAndBurnRoles(address(newPool));
BurnMintERC20(sourceETH).grantMintAndBurnRoles(address(newPool));
deal(address(sourceETH), address(newPool), type(uint256).max);

// Add TokenPool to OnRamp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/

contract BurnFromMintTokenPool_lockOrBurn is BurnFromMintTokenPoolSetup {
function test_setup_Success() public view {
assertEq(address(s_burnMintERC677), address(s_pool.getToken()));
assertEq(address(s_burnMintERC20), address(s_pool.getToken()));
assertEq(address(s_mockRMN), s_pool.getRmnProxy());
assertEq(false, s_pool.getAllowListEnabled());
assertEq(type(uint256).max, s_burnMintERC677.allowance(address(s_pool), address(s_pool)));
assertEq(type(uint256).max, s_burnMintERC20.allowance(address(s_pool), address(s_pool)));
assertEq("BurnFromMintTokenPool 1.5.0", s_pool.typeAndVersion());
}

function test_PoolBurn_Success() public {
uint256 burnAmount = 20_000e18;

deal(address(s_burnMintERC677), address(s_pool), burnAmount);
assertEq(s_burnMintERC677.balanceOf(address(s_pool)), burnAmount);
deal(address(s_burnMintERC20), address(s_pool), burnAmount);
assertEq(s_burnMintERC20.balanceOf(address(s_pool)), burnAmount);

vm.startPrank(s_burnMintOnRamp);

Expand All @@ -35,25 +35,25 @@ contract BurnFromMintTokenPool_lockOrBurn is BurnFromMintTokenPoolSetup {
emit TokenPool.Burned(address(s_burnMintOnRamp), burnAmount);

bytes4 expectedSignature = bytes4(keccak256("burnFrom(address,uint256)"));
vm.expectCall(address(s_burnMintERC677), abi.encodeWithSelector(expectedSignature, address(s_pool), burnAmount));
vm.expectCall(address(s_burnMintERC20), abi.encodeWithSelector(expectedSignature, address(s_pool), burnAmount));

s_pool.lockOrBurn(
Pool.LockOrBurnInV1({
originalSender: OWNER,
receiver: bytes(""),
amount: burnAmount,
remoteChainSelector: DEST_CHAIN_SELECTOR,
localToken: address(s_burnMintERC677)
localToken: address(s_burnMintERC20)
})
);

assertEq(s_burnMintERC677.balanceOf(address(s_pool)), 0);
assertEq(s_burnMintERC20.balanceOf(address(s_pool)), 0);
}

// Should not burn tokens if cursed.
function test_PoolBurnRevertNotHealthy_Revert() public {
s_mockRMN.setGlobalCursed(true);
uint256 before = s_burnMintERC677.balanceOf(address(s_pool));
uint256 before = s_burnMintERC20.balanceOf(address(s_pool));
vm.startPrank(s_burnMintOnRamp);

vm.expectRevert(TokenPool.CursedByRMN.selector);
Expand All @@ -63,11 +63,11 @@ contract BurnFromMintTokenPool_lockOrBurn is BurnFromMintTokenPoolSetup {
receiver: bytes(""),
amount: 1e5,
remoteChainSelector: DEST_CHAIN_SELECTOR,
localToken: address(s_burnMintERC677)
localToken: address(s_burnMintERC20)
})
);

assertEq(s_burnMintERC677.balanceOf(address(s_pool)), before);
assertEq(s_burnMintERC20.balanceOf(address(s_pool)), before);
}

function test_ChainNotAllowed_Revert() public {
Expand All @@ -78,7 +78,7 @@ contract BurnFromMintTokenPool_lockOrBurn is BurnFromMintTokenPoolSetup {
originalSender: bytes(""),
receiver: OWNER,
amount: 1,
localToken: address(s_burnMintERC677),
localToken: address(s_burnMintERC20),
remoteChainSelector: wrongChainSelector,
sourcePoolAddress: _generateSourceTokenData().sourcePoolAddress,
sourcePoolData: _generateSourceTokenData().extraData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ contract BurnFromMintTokenPoolSetup is BurnMintSetup {
function setUp() public virtual override {
BurnMintSetup.setUp();

s_pool = new BurnFromMintTokenPool(s_burnMintERC677, new address[](0), address(s_mockRMN), address(s_sourceRouter));
s_burnMintERC677.grantMintAndBurnRoles(address(s_pool));
s_pool = new BurnFromMintTokenPool(s_burnMintERC20, new address[](0), address(s_mockRMN), address(s_sourceRouter));
s_burnMintERC20.grantMintAndBurnRoles(address(s_pool));

_applyChainUpdates(address(s_pool));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.24;

import {BurnMintERC677} from "../../../../shared/token/ERC677/BurnMintERC677.sol";
import {BurnMintERC20} from "../../../../shared/token/ERC20/BurnMintERC20.sol";
import {Router} from "../../../Router.sol";
import {BurnMintTokenPool} from "../../../pools/BurnMintTokenPool.sol";
import {TokenPool} from "../../../pools/TokenPool.sol";
import {RouterSetup} from "../../router/RouterSetup.t.sol";

contract BurnMintSetup is RouterSetup {
BurnMintERC677 internal s_burnMintERC677;
BurnMintERC20 internal s_burnMintERC20;
address internal s_burnMintOffRamp = makeAddr("burn_mint_offRamp");
address internal s_burnMintOnRamp = makeAddr("burn_mint_onRamp");

Expand All @@ -18,7 +18,7 @@ contract BurnMintSetup is RouterSetup {
function setUp() public virtual override {
RouterSetup.setUp();

s_burnMintERC677 = new BurnMintERC677("Chainlink Token", "LINK", 18, 0);
s_burnMintERC20 = new BurnMintERC20("Chainlink Token", "LINK", 18, 0, 0);
}

function _applyChainUpdates(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ contract BurnMintTokenPoolSetup is BurnMintSetup {
function setUp() public virtual override {
BurnMintSetup.setUp();

s_pool = new BurnMintTokenPool(s_burnMintERC677, new address[](0), address(s_mockRMN), address(s_sourceRouter));
s_burnMintERC677.grantMintAndBurnRoles(address(s_pool));
s_pool = new BurnMintTokenPool(s_burnMintERC20, new address[](0), address(s_mockRMN), address(s_sourceRouter));
s_burnMintERC20.grantMintAndBurnRoles(address(s_pool));

_applyChainUpdates(address(s_pool));
}
}

contract BurnMintTokenPool_lockOrBurn is BurnMintTokenPoolSetup {
function test_Setup_Success() public view {
assertEq(address(s_burnMintERC677), address(s_pool.getToken()));
assertEq(address(s_burnMintERC20), address(s_pool.getToken()));
assertEq(address(s_mockRMN), s_pool.getRmnProxy());
assertEq(false, s_pool.getAllowListEnabled());
assertEq("BurnMintTokenPool 1.5.0", s_pool.typeAndVersion());
Expand All @@ -33,8 +33,8 @@ contract BurnMintTokenPool_lockOrBurn is BurnMintTokenPoolSetup {
function test_PoolBurn_Success() public {
uint256 burnAmount = 20_000e18;

deal(address(s_burnMintERC677), address(s_pool), burnAmount);
assertEq(s_burnMintERC677.balanceOf(address(s_pool)), burnAmount);
deal(address(s_burnMintERC20), address(s_pool), burnAmount);
assertEq(s_burnMintERC20.balanceOf(address(s_pool)), burnAmount);

vm.startPrank(s_burnMintOnRamp);

Expand All @@ -48,25 +48,25 @@ contract BurnMintTokenPool_lockOrBurn is BurnMintTokenPoolSetup {
emit TokenPool.Burned(address(s_burnMintOnRamp), burnAmount);

bytes4 expectedSignature = bytes4(keccak256("burn(uint256)"));
vm.expectCall(address(s_burnMintERC677), abi.encodeWithSelector(expectedSignature, burnAmount));
vm.expectCall(address(s_burnMintERC20), abi.encodeWithSelector(expectedSignature, burnAmount));

s_pool.lockOrBurn(
Pool.LockOrBurnInV1({
originalSender: OWNER,
receiver: bytes(""),
amount: burnAmount,
remoteChainSelector: DEST_CHAIN_SELECTOR,
localToken: address(s_burnMintERC677)
localToken: address(s_burnMintERC20)
})
);

assertEq(s_burnMintERC677.balanceOf(address(s_pool)), 0);
assertEq(s_burnMintERC20.balanceOf(address(s_pool)), 0);
}

// Should not burn tokens if cursed.
function test_PoolBurnRevertNotHealthy_Revert() public {
s_mockRMN.setGlobalCursed(true);
uint256 before = s_burnMintERC677.balanceOf(address(s_pool));
uint256 before = s_burnMintERC20.balanceOf(address(s_pool));
vm.startPrank(s_burnMintOnRamp);

vm.expectRevert(TokenPool.CursedByRMN.selector);
Expand All @@ -76,11 +76,11 @@ contract BurnMintTokenPool_lockOrBurn is BurnMintTokenPoolSetup {
receiver: bytes(""),
amount: 1e5,
remoteChainSelector: DEST_CHAIN_SELECTOR,
localToken: address(s_burnMintERC677)
localToken: address(s_burnMintERC20)
})
);

assertEq(s_burnMintERC677.balanceOf(address(s_pool)), before);
assertEq(s_burnMintERC20.balanceOf(address(s_pool)), before);
}

function test_ChainNotAllowed_Revert() public {
Expand All @@ -93,7 +93,7 @@ contract BurnMintTokenPool_lockOrBurn is BurnMintTokenPoolSetup {
receiver: bytes(""),
amount: 1,
remoteChainSelector: wrongChainSelector,
localToken: address(s_burnMintERC677)
localToken: address(s_burnMintERC20)
})
);
}
Expand Down
Loading
Loading