Skip to content

Commit

Permalink
Support EVMExtraArgsV2 and OZ's AccessControl in CCIPLocalSimulator (#26
Browse files Browse the repository at this point in the history
)

* chore: Adjust tests to reflect recent changes in TokenPool contracts

* chore: Extract some variables from memory to storage to avoid stack too deep error

* feat: Add supportNewTokenViaAccessControlDefaultAdmin function to CCIPLocalSimulator; Support EVMExtraArgsV2 in MockCCIPRouter

* chore: Prepare for 0.2.3 release
  • Loading branch information
andrejrakic authored Nov 30, 2024
1 parent cd3bfb8 commit 7d8b2f8
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 35 deletions.
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.3] - 30 November 2024

### Dependencies

| Package | Version |
| ------------------------- | ------------ |
| @chainlink/contracts-ccip | 1.5.1-beta.0 |
| @chainlink/contracts | 1.1.1 |

- [x] Chainlink CCIP
- [x] Chainlink CCIP v1.5
- [x] Chainlink Data Feeds
- [ ] Chainlink Automation
- [ ] Chainlink VRF 2
- [ ] Chainlink VRF 2.5

### Added

- Added `supportNewTokenViaAccessControlDefaultAdmin` function to
`CCIPLocalSimulator.sol`
- Bumped `@chainlink/contracts-ccip` to `1.5.1-beta.0` to reflect new changes in
the CCIP `TokenPool.sol` smart contract (check
[CCIPv1_5BurnMintPoolFork.t.sol](./test/e2e/ccip/CCIPv1_5ForkBurnMintPoolFork.t.sol)
and
[CCIPv1_5LockReleasePoolFork.t.sol](./test/e2e/ccip/CCIPv1_5ForkLockReleasePoolFork.t.sol)
tests) and to support `EVMExtraArgsV2` in `MockCCIPRouter.sol`

## [0.2.2] - 15 October 2024

### Dependencies
Expand Down Expand Up @@ -271,3 +298,4 @@ and this project adheres to
[0.2.2-beta.1]:
https://github.com/smartcontractkit/chainlink-local/releases/tag/v0.2.2-beta.1
[0.2.2]: https://github.com/smartcontractkit/chainlink-local/releases/tag/v0.2.2
[0.2.3]: https://github.com/smartcontractkit/chainlink-local/releases/tag/v0.2.3
23 changes: 23 additions & 0 deletions api_reference/solidity/ccip/CCIPLocalSimulator.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ The list of supported token addresses
error CCIPLocalSimulator__MsgSenderIsNotTokenOwner()
```

### CCIPLocalSimulator\_\_RequiredRoleNotFound

```solidity
error CCIPLocalSimulator__RequiredRoleNotFound(address account, bytes32 role, address token)
```

### constructor

```solidity
Expand Down Expand Up @@ -110,6 +116,23 @@ function. Reverts if the caller is not the token CCIPAdmin.
| ------------ | ------- | ------------------------------------------------------------------ |
| tokenAddress | address | - The address of the token to add to the list of supported tokens. |

### supportNewTokenViaAccessControlDefaultAdmin

```solidity
function supportNewTokenViaAccessControlDefaultAdmin(address tokenAddress) external
```

Allows user to support any new token, besides CCIP BnM and CCIP LnM, for
cross-chain transfers. The caller must have the DEFAULT_ADMIN_ROLE as defined by
the contract itself. Reverts if the caller is not the admin of the token using
OZ's AccessControl DEFAULT_ADMIN_ROLE.

#### Parameters

| Name | Type | Description |
| ------------ | ------- | ------------------------------------------------------------------ |
| tokenAddress | address | - The address of the token to add to the list of supported tokens. |

### isChainSupported

```solidity
Expand Down
2 changes: 1 addition & 1 deletion lib/ccip
Submodule ccip updated 1679 files
38 changes: 31 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@chainlink/local",
"description": "Chainlink Local Simulator",
"license": "MIT",
"version": "0.2.2",
"version": "0.2.3",
"files": [
"src/**/*.sol",
"!src/test/**/*",
Expand Down Expand Up @@ -43,6 +43,6 @@
},
"dependencies": {
"@chainlink/contracts": "^1.1.1",
"@chainlink/contracts-ccip": "^1.5.0"
"@chainlink/contracts-ccip": "^1.5.1-beta.0"
}
}
}
18 changes: 18 additions & 0 deletions src/ccip/CCIPLocalSimulator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {SafeERC20} from
"@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
import {IOwner} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IOwner.sol";
import {IGetCCIPAdmin} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IGetCCIPAdmin.sol";
import {AccessControl} from
"@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v5.0.2/contracts/access/AccessControl.sol";

/// @title CCIPLocalSimulator
/// @notice This contract simulates local CCIP (Cross-Chain Interoperability Protocol) operations for testing and development purposes.
Expand Down Expand Up @@ -41,6 +43,7 @@ contract CCIPLocalSimulator {
address[] internal s_supportedTokens;

error CCIPLocalSimulator__MsgSenderIsNotTokenOwner();
error CCIPLocalSimulator__RequiredRoleNotFound(address account, bytes32 role, address token);

/**
* @notice Constructor to initialize the contract and pre-deployed token instances
Expand Down Expand Up @@ -84,6 +87,21 @@ contract CCIPLocalSimulator {
s_supportedTokens.push(tokenAddress);
}

/**
* @notice Allows user to support any new token, besides CCIP BnM and CCIP LnM, for cross-chain transfers.
* The caller must have the DEFAULT_ADMIN_ROLE as defined by the contract itself.
* Reverts if the caller is not the admin of the token using OZ's AccessControl DEFAULT_ADMIN_ROLE.
*
* @param tokenAddress - The address of the token to add to the list of supported tokens.
*/
function supportNewTokenViaAccessControlDefaultAdmin(address tokenAddress) external {
bytes32 defaultAdminRole = AccessControl(tokenAddress).DEFAULT_ADMIN_ROLE();
if (!AccessControl(tokenAddress).hasRole(defaultAdminRole, msg.sender)) {
revert CCIPLocalSimulator__RequiredRoleNotFound(msg.sender, defaultAdminRole, tokenAddress);
}
s_supportedTokens.push(tokenAddress);
}

/**
* @notice Checks whether the provided `chainSelector` is supported by the simulator.
*
Expand Down
28 changes: 18 additions & 10 deletions test/e2e/ccip/CCIPv1_5ForkBurnMintPoolFork.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ contract CCIPv1_5BurnMintPoolFork is Test {
CCIPLocalSimulatorFork public ccipLocalSimulatorFork;
MockERC20BurnAndMintToken public mockERC20TokenEthSepolia;
MockERC20BurnAndMintToken public mockERC20TokenBaseSepolia;
BurnMintTokenPool public burnMintTokenPoolEthSepolia;
BurnMintTokenPool public burnMintTokenPoolBaseSepolia;

Register.NetworkDetails ethSepoliaNetworkDetails;
Register.NetworkDetails baseSepoliaNetworkDetails;

uint256 ethSepoliaFork;
Expand Down Expand Up @@ -98,13 +101,14 @@ contract CCIPv1_5BurnMintPoolFork is Test {
function test_forkSupportNewCCIPToken() public {
// Step 3) Deploy BurnMintTokenPool on Ethereum Sepolia
vm.selectFork(ethSepoliaFork);
Register.NetworkDetails memory ethSepoliaNetworkDetails =
ccipLocalSimulatorFork.getNetworkDetails(block.chainid);
ethSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid);
address[] memory allowlist = new address[](0);
uint8 localTokenDecimals = 18;

vm.startPrank(alice);
BurnMintTokenPool burnMintTokenPoolEthSepolia = new BurnMintTokenPool(
burnMintTokenPoolEthSepolia = new BurnMintTokenPool(
IBurnMintERC20(address(mockERC20TokenEthSepolia)),
localTokenDecimals,
allowlist,
ethSepoliaNetworkDetails.rmnProxyAddress,
ethSepoliaNetworkDetails.routerAddress
Expand All @@ -116,8 +120,9 @@ contract CCIPv1_5BurnMintPoolFork is Test {
baseSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid);

vm.startPrank(alice);
BurnMintTokenPool burnMintTokenPoolBaseSepolia = new BurnMintTokenPool(
burnMintTokenPoolBaseSepolia = new BurnMintTokenPool(
IBurnMintERC20(address(mockERC20TokenBaseSepolia)),
localTokenDecimals,
allowlist,
baseSepoliaNetworkDetails.rmnProxyAddress,
baseSepoliaNetworkDetails.routerAddress
Expand Down Expand Up @@ -203,31 +208,34 @@ contract CCIPv1_5BurnMintPoolFork is Test {

vm.startPrank(alice);
TokenPool.ChainUpdate[] memory chains = new TokenPool.ChainUpdate[](1);
bytes[] memory remotePoolAddressesEthSepolia = new bytes[](1);
remotePoolAddressesEthSepolia[0] = abi.encode(address(burnMintTokenPoolEthSepolia));
chains[0] = TokenPool.ChainUpdate({
remoteChainSelector: baseSepoliaNetworkDetails.chainSelector,
allowed: true,
remotePoolAddress: abi.encode(address(burnMintTokenPoolBaseSepolia)),
remotePoolAddresses: remotePoolAddressesEthSepolia,
remoteTokenAddress: abi.encode(address(mockERC20TokenBaseSepolia)),
outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100_000, rate: 167}),
inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100_000, rate: 167})
});
burnMintTokenPoolEthSepolia.applyChainUpdates(chains);
uint64[] memory remoteChainSelectorsToRemove = new uint64[](0);
burnMintTokenPoolEthSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains);
vm.stopPrank();

// Step 14) Configure Token Pool on Base Sepolia
vm.selectFork(baseSepoliaFork);

vm.startPrank(alice);
chains = new TokenPool.ChainUpdate[](1);
bytes[] memory remotePoolAddressesBaseSepolia = new bytes[](1);
remotePoolAddressesBaseSepolia[0] = abi.encode(address(burnMintTokenPoolEthSepolia));
chains[0] = TokenPool.ChainUpdate({
remoteChainSelector: ethSepoliaNetworkDetails.chainSelector,
allowed: true,
remotePoolAddress: abi.encode(address(burnMintTokenPoolEthSepolia)),
remotePoolAddresses: remotePoolAddressesBaseSepolia,
remoteTokenAddress: abi.encode(address(mockERC20TokenEthSepolia)),
outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100_000, rate: 167}),
inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100_000, rate: 167})
});
burnMintTokenPoolBaseSepolia.applyChainUpdates(chains);
burnMintTokenPoolBaseSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains);
vm.stopPrank();

// Step 15) Mint tokens on Ethereum Sepolia and transfer them to Base Sepolia
Expand Down
33 changes: 21 additions & 12 deletions test/e2e/ccip/CCIPv1_5LockReleasePoolFork.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ contract CCIPv1_5LockReleasePoolFork is Test {
CCIPLocalSimulatorFork public ccipLocalSimulatorFork;
MockERC20TokenOwner public mockERC20TokenEthSepolia;
MockERC20TokenOwner public mockERC20TokenBaseSepolia;
LockReleaseTokenPool public lockReleaseTokenPoolEthSepolia;
LockReleaseTokenPool public lockReleaseTokenPoolBaseSepolia;

Register.NetworkDetails ethSepoliaNetworkDetails;
Register.NetworkDetails baseSepoliaNetworkDetails;

uint256 ethSepoliaFork;
uint256 baseSepoliaFork;
Expand Down Expand Up @@ -62,13 +67,14 @@ contract CCIPv1_5LockReleasePoolFork is Test {
function test_forkSupportNewCCIPToken() public {
// Step 3) Deploy LockReleaseTokenPool on Ethereum Sepolia
vm.selectFork(ethSepoliaFork);
Register.NetworkDetails memory ethSepoliaNetworkDetails =
ccipLocalSimulatorFork.getNetworkDetails(block.chainid);
ethSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid);
address[] memory allowlist = new address[](0);
uint8 localTokenDecimals = 18;

vm.startPrank(alice);
LockReleaseTokenPool lockReleaseTokenPoolEthSepolia = new LockReleaseTokenPool(
lockReleaseTokenPoolEthSepolia = new LockReleaseTokenPool(
IERC20(address(mockERC20TokenEthSepolia)),
localTokenDecimals,
allowlist,
ethSepoliaNetworkDetails.rmnProxyAddress,
true, // acceptLiquidity
Expand All @@ -78,12 +84,12 @@ contract CCIPv1_5LockReleasePoolFork is Test {

// Step 4) Deploy LockReleaseTokenPool on Base Sepolia
vm.selectFork(baseSepoliaFork);
Register.NetworkDetails memory baseSepoliaNetworkDetails =
ccipLocalSimulatorFork.getNetworkDetails(block.chainid);
baseSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid);

vm.startPrank(alice);
LockReleaseTokenPool lockReleaseTokenPoolBaseSepolia = new LockReleaseTokenPool(
lockReleaseTokenPoolBaseSepolia = new LockReleaseTokenPool(
IERC20(address(mockERC20TokenBaseSepolia)),
localTokenDecimals,
allowlist,
baseSepoliaNetworkDetails.rmnProxyAddress,
true, // acceptLiquidity
Expand Down Expand Up @@ -174,31 +180,34 @@ contract CCIPv1_5LockReleasePoolFork is Test {

vm.startPrank(alice);
TokenPool.ChainUpdate[] memory chains = new TokenPool.ChainUpdate[](1);
bytes[] memory remotePoolAddressesEthSepolia = new bytes[](1);
remotePoolAddressesEthSepolia[0] = abi.encode(address(lockReleaseTokenPoolBaseSepolia));
chains[0] = TokenPool.ChainUpdate({
remoteChainSelector: baseSepoliaNetworkDetails.chainSelector,
allowed: true,
remotePoolAddress: abi.encode(address(lockReleaseTokenPoolBaseSepolia)),
remotePoolAddresses: remotePoolAddressesEthSepolia,
remoteTokenAddress: abi.encode(address(mockERC20TokenBaseSepolia)),
outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: liquidityAmount, rate: 167}),
inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: liquidityAmount, rate: 167})
});
lockReleaseTokenPoolEthSepolia.applyChainUpdates(chains);
uint64[] memory remoteChainSelectorsToRemove = new uint64[](0);
lockReleaseTokenPoolEthSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains);
vm.stopPrank();

// Step 14) Configure Token Pool on Base Sepolia
vm.selectFork(baseSepoliaFork);

vm.startPrank(alice);
chains = new TokenPool.ChainUpdate[](1);
bytes[] memory remotePoolAddressesBaseSepolia = new bytes[](1);
remotePoolAddressesBaseSepolia[0] = abi.encode(address(lockReleaseTokenPoolEthSepolia));
chains[0] = TokenPool.ChainUpdate({
remoteChainSelector: ethSepoliaNetworkDetails.chainSelector,
allowed: true,
remotePoolAddress: abi.encode(address(lockReleaseTokenPoolEthSepolia)),
remotePoolAddresses: remotePoolAddressesBaseSepolia,
remoteTokenAddress: abi.encode(address(mockERC20TokenEthSepolia)),
outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: liquidityAmount, rate: 167}),
inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: liquidityAmount, rate: 167})
});
lockReleaseTokenPoolBaseSepolia.applyChainUpdates(chains);
lockReleaseTokenPoolBaseSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains);
vm.stopPrank();

// Step 15) Transfer tokens from Ethereum Sepolia to Base Sepolia
Expand Down
Loading

0 comments on commit 7d8b2f8

Please sign in to comment.