Skip to content

Commit

Permalink
CCIP-4130 CCIP-Compatible Generic BurnMintERC20 (#15123)
Browse files Browse the repository at this point in the history
* make generic BurnMintERC20 that's ccip-compatible

* formatting

* changeset, compiler version set to 8.19 for conformity, and new gethwrapper

* [Bot] Update changeset file with jira issues

* adjust compiler version

* replace ERC677 with ERC20 in tests that don't need it.

* move burnMintERC20 to access control not ownership

* forge fmt

* undo accidental changes to go file in merge

* fix compiler errors

* Update gethwrappers

* formatting

* remove more references to ERC677

* fix snapshot broken in merge

* remove automatic mint and burn role granting in BurnMintERC20 constructor

* Update gethwrappers

* optimize contract and change function names for new naming convention

* snapshot fix

* pnpm prettier formatting

* Update gethwrappers

* optimize transfer and approve function by removing unnec. error check that only applied to 677

* Update gethwrappers

* remove unused import and better interface imports

* snapshot fix

* remove failing coverage test

* add missing single quote in github action

* lets try this one more time

* remove IR minimum compilation detail

* gas snapshot fix attempt

* Revert "optimize transfer and approve function by removing unnec. error check that only applied to 677"

This reverts commit dce25c7.

* fix naming of tests

* Update gethwrappers

* fix min pragma

* add important natspec comment

* snapshot fix

* another snapshot fix attempt

---------

Co-authored-by: app-token-issuer-infra-releng[bot] <120227048+app-token-issuer-infra-releng[bot]@users.noreply.github.com>
Co-authored-by: Rens Rooimans <[email protected]>
  • Loading branch information
3 people authored Nov 21, 2024
1 parent 29eb755 commit 72da397
Show file tree
Hide file tree
Showing 44 changed files with 2,438 additions and 207 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/solidity-foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
cat <<EOF > matrix.json
[
{ "name": "automation", "setup": { "run-coverage": false, "min-coverage": 98.5, "run-gas-snapshot": false, "run-forge-fmt": false }},
{ "name": "ccip", "setup": { "run-coverage": true, "min-coverage": 98.8, "run-gas-snapshot": true, "run-forge-fmt": true }},
{ "name": "ccip", "setup": { "run-coverage": true, "min-coverage": 98.8, "extra-coverage-params": "--no-match-path='*End2End*'", "run-gas-snapshot": true, "run-forge-fmt": true }},
{ "name": "functions", "setup": { "run-coverage": false, "min-coverage": 98.5, "run-gas-snapshot": true, "run-forge-fmt": false }},
{ "name": "keystone", "setup": { "run-coverage": true, "min-coverage": 72.8, "run-gas-snapshot": false, "run-forge-fmt": false }},
{ "name": "l2ep", "setup": { "run-coverage": true, "min-coverage": 61.0, "run-gas-snapshot": true, "run-forge-fmt": false }},
Expand Down
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
226 changes: 113 additions & 113 deletions contracts/gas-snapshots/ccip.gas-snapshot

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions contracts/gas-snapshots/shared.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ 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)
BurnMintERC20_approve:test_approve() (gas: 57652)
BurnMintERC20_burn:test_BasicBurn() (gas: 153607)
BurnMintERC20_burnFrom:test_BurnFrom() (gas: 57995)
BurnMintERC20_burnFromAlias:test_burn() (gas: 58039)
BurnMintERC20_constructor:test_Constructor() (gas: 1694831)
BurnMintERC20_getCCIPAdmin:test_getCCIPAdmin() (gas: 10548)
BurnMintERC20_getCCIPAdmin:test_setCCIPAdmin() (gas: 21590)
BurnMintERC20_grantMintAndBurnRoles:test_GrantMintAndBurnRoles() (gas: 79142)
BurnMintERC20_mint:test_mint() (gas: 101849)
BurnMintERC20_supportsInterface:test_SupportsInterface() (gas: 11218)
BurnMintERC20_transfer:test_transfer() (gas: 42338)
BurnMintERC677_approve:testApproveSuccess() (gas: 55512)
BurnMintERC677_approve:testInvalidAddressReverts() (gas: 10663)
BurnMintERC677_burn:testBasicBurnSuccess() (gas: 172100)
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
6 changes: 3 additions & 3 deletions contracts/src/v0.8/ccip/test/mocks/MockE2EUSDCTransmitter.sol
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/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
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ 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));
}
Expand All @@ -36,21 +36,21 @@ contract BurnMintTokenPool_releaseOrMint is BurnMintTokenPoolSetup {
originalSender: bytes(""),
receiver: receiver,
amount: amount,
localToken: address(s_burnMintERC677),
localToken: address(s_burnMintERC20),
remoteChainSelector: DEST_CHAIN_SELECTOR,
sourcePoolAddress: abi.encode(s_remoteBurnMintPool),
sourcePoolData: "",
offchainTokenData: ""
})
);

assertEq(s_burnMintERC677.balanceOf(receiver), amount);
assertEq(s_burnMintERC20.balanceOf(receiver), amount);
}

function test_PoolMintNotHealthy_Revert() public {
// Should not mint tokens if cursed.
s_mockRMN.setGlobalCursed(true);
uint256 before = s_burnMintERC677.balanceOf(OWNER);
uint256 before = s_burnMintERC20.balanceOf(OWNER);
vm.startPrank(s_burnMintOffRamp);

vm.expectRevert(TokenPool.CursedByRMN.selector);
Expand All @@ -59,15 +59,15 @@ contract BurnMintTokenPool_releaseOrMint is BurnMintTokenPoolSetup {
originalSender: bytes(""),
receiver: OWNER,
amount: 1e5,
localToken: address(s_burnMintERC677),
localToken: address(s_burnMintERC20),
remoteChainSelector: DEST_CHAIN_SELECTOR,
sourcePoolAddress: _generateSourceTokenData().sourcePoolAddress,
sourcePoolData: _generateSourceTokenData().extraData,
offchainTokenData: ""
})
);

assertEq(s_burnMintERC677.balanceOf(OWNER), before);
assertEq(s_burnMintERC20.balanceOf(OWNER), before);
}

function test_ChainNotAllowed_Revert() public {
Expand All @@ -79,7 +79,7 @@ contract BurnMintTokenPool_releaseOrMint is BurnMintTokenPoolSetup {
originalSender: bytes(""),
receiver: OWNER,
amount: 1,
localToken: address(s_burnMintERC677),
localToken: address(s_burnMintERC20),
remoteChainSelector: wrongChainSelector,
sourcePoolAddress: _generateSourceTokenData().sourcePoolAddress,
sourcePoolData: _generateSourceTokenData().extraData,
Expand Down
Loading

0 comments on commit 72da397

Please sign in to comment.