Skip to content

Commit 7d8b2f8

Browse files
authored
Support EVMExtraArgsV2 and OZ's AccessControl in CCIPLocalSimulator (#26)
* 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
1 parent cd3bfb8 commit 7d8b2f8

9 files changed

+217
-35
lines changed

CHANGELOG.md

+28
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to
77
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
## [0.2.3] - 30 November 2024
10+
11+
### Dependencies
12+
13+
| Package | Version |
14+
| ------------------------- | ------------ |
15+
| @chainlink/contracts-ccip | 1.5.1-beta.0 |
16+
| @chainlink/contracts | 1.1.1 |
17+
18+
- [x] Chainlink CCIP
19+
- [x] Chainlink CCIP v1.5
20+
- [x] Chainlink Data Feeds
21+
- [ ] Chainlink Automation
22+
- [ ] Chainlink VRF 2
23+
- [ ] Chainlink VRF 2.5
24+
25+
### Added
26+
27+
- Added `supportNewTokenViaAccessControlDefaultAdmin` function to
28+
`CCIPLocalSimulator.sol`
29+
- Bumped `@chainlink/contracts-ccip` to `1.5.1-beta.0` to reflect new changes in
30+
the CCIP `TokenPool.sol` smart contract (check
31+
[CCIPv1_5BurnMintPoolFork.t.sol](./test/e2e/ccip/CCIPv1_5ForkBurnMintPoolFork.t.sol)
32+
and
33+
[CCIPv1_5LockReleasePoolFork.t.sol](./test/e2e/ccip/CCIPv1_5ForkLockReleasePoolFork.t.sol)
34+
tests) and to support `EVMExtraArgsV2` in `MockCCIPRouter.sol`
35+
936
## [0.2.2] - 15 October 2024
1037

1138
### Dependencies
@@ -271,3 +298,4 @@ and this project adheres to
271298
[0.2.2-beta.1]:
272299
https://github.com/smartcontractkit/chainlink-local/releases/tag/v0.2.2-beta.1
273300
[0.2.2]: https://github.com/smartcontractkit/chainlink-local/releases/tag/v0.2.2
301+
[0.2.3]: https://github.com/smartcontractkit/chainlink-local/releases/tag/v0.2.3

api_reference/solidity/ccip/CCIPLocalSimulator.mdx

+23
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ The list of supported token addresses
7070
error CCIPLocalSimulator__MsgSenderIsNotTokenOwner()
7171
```
7272

73+
### CCIPLocalSimulator\_\_RequiredRoleNotFound
74+
75+
```solidity
76+
error CCIPLocalSimulator__RequiredRoleNotFound(address account, bytes32 role, address token)
77+
```
78+
7379
### constructor
7480

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

119+
### supportNewTokenViaAccessControlDefaultAdmin
120+
121+
```solidity
122+
function supportNewTokenViaAccessControlDefaultAdmin(address tokenAddress) external
123+
```
124+
125+
Allows user to support any new token, besides CCIP BnM and CCIP LnM, for
126+
cross-chain transfers. The caller must have the DEFAULT_ADMIN_ROLE as defined by
127+
the contract itself. Reverts if the caller is not the admin of the token using
128+
OZ's AccessControl DEFAULT_ADMIN_ROLE.
129+
130+
#### Parameters
131+
132+
| Name | Type | Description |
133+
| ------------ | ------- | ------------------------------------------------------------------ |
134+
| tokenAddress | address | - The address of the token to add to the list of supported tokens. |
135+
113136
### isChainSupported
114137

115138
```solidity

lib/ccip

Submodule ccip updated 1679 files

package-lock.json

+31-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@chainlink/local",
33
"description": "Chainlink Local Simulator",
44
"license": "MIT",
5-
"version": "0.2.2",
5+
"version": "0.2.3",
66
"files": [
77
"src/**/*.sol",
88
"!src/test/**/*",
@@ -43,6 +43,6 @@
4343
},
4444
"dependencies": {
4545
"@chainlink/contracts": "^1.1.1",
46-
"@chainlink/contracts-ccip": "^1.5.0"
46+
"@chainlink/contracts-ccip": "^1.5.1-beta.0"
4747
}
48-
}
48+
}

src/ccip/CCIPLocalSimulator.sol

+18
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {SafeERC20} from
1212
"@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
1313
import {IOwner} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IOwner.sol";
1414
import {IGetCCIPAdmin} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IGetCCIPAdmin.sol";
15+
import {AccessControl} from
16+
"@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v5.0.2/contracts/access/AccessControl.sol";
1517

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

4345
error CCIPLocalSimulator__MsgSenderIsNotTokenOwner();
46+
error CCIPLocalSimulator__RequiredRoleNotFound(address account, bytes32 role, address token);
4447

4548
/**
4649
* @notice Constructor to initialize the contract and pre-deployed token instances
@@ -84,6 +87,21 @@ contract CCIPLocalSimulator {
8487
s_supportedTokens.push(tokenAddress);
8588
}
8689

90+
/**
91+
* @notice Allows user to support any new token, besides CCIP BnM and CCIP LnM, for cross-chain transfers.
92+
* The caller must have the DEFAULT_ADMIN_ROLE as defined by the contract itself.
93+
* Reverts if the caller is not the admin of the token using OZ's AccessControl DEFAULT_ADMIN_ROLE.
94+
*
95+
* @param tokenAddress - The address of the token to add to the list of supported tokens.
96+
*/
97+
function supportNewTokenViaAccessControlDefaultAdmin(address tokenAddress) external {
98+
bytes32 defaultAdminRole = AccessControl(tokenAddress).DEFAULT_ADMIN_ROLE();
99+
if (!AccessControl(tokenAddress).hasRole(defaultAdminRole, msg.sender)) {
100+
revert CCIPLocalSimulator__RequiredRoleNotFound(msg.sender, defaultAdminRole, tokenAddress);
101+
}
102+
s_supportedTokens.push(tokenAddress);
103+
}
104+
87105
/**
88106
* @notice Checks whether the provided `chainSelector` is supported by the simulator.
89107
*

test/e2e/ccip/CCIPv1_5ForkBurnMintPoolFork.t.sol

+18-10
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ contract CCIPv1_5BurnMintPoolFork is Test {
6363
CCIPLocalSimulatorFork public ccipLocalSimulatorFork;
6464
MockERC20BurnAndMintToken public mockERC20TokenEthSepolia;
6565
MockERC20BurnAndMintToken public mockERC20TokenBaseSepolia;
66+
BurnMintTokenPool public burnMintTokenPoolEthSepolia;
67+
BurnMintTokenPool public burnMintTokenPoolBaseSepolia;
6668

69+
Register.NetworkDetails ethSepoliaNetworkDetails;
6770
Register.NetworkDetails baseSepoliaNetworkDetails;
6871

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

105108
vm.startPrank(alice);
106-
BurnMintTokenPool burnMintTokenPoolEthSepolia = new BurnMintTokenPool(
109+
burnMintTokenPoolEthSepolia = new BurnMintTokenPool(
107110
IBurnMintERC20(address(mockERC20TokenEthSepolia)),
111+
localTokenDecimals,
108112
allowlist,
109113
ethSepoliaNetworkDetails.rmnProxyAddress,
110114
ethSepoliaNetworkDetails.routerAddress
@@ -116,8 +120,9 @@ contract CCIPv1_5BurnMintPoolFork is Test {
116120
baseSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid);
117121

118122
vm.startPrank(alice);
119-
BurnMintTokenPool burnMintTokenPoolBaseSepolia = new BurnMintTokenPool(
123+
burnMintTokenPoolBaseSepolia = new BurnMintTokenPool(
120124
IBurnMintERC20(address(mockERC20TokenBaseSepolia)),
125+
localTokenDecimals,
121126
allowlist,
122127
baseSepoliaNetworkDetails.rmnProxyAddress,
123128
baseSepoliaNetworkDetails.routerAddress
@@ -203,31 +208,34 @@ contract CCIPv1_5BurnMintPoolFork is Test {
203208

204209
vm.startPrank(alice);
205210
TokenPool.ChainUpdate[] memory chains = new TokenPool.ChainUpdate[](1);
211+
bytes[] memory remotePoolAddressesEthSepolia = new bytes[](1);
212+
remotePoolAddressesEthSepolia[0] = abi.encode(address(burnMintTokenPoolEthSepolia));
206213
chains[0] = TokenPool.ChainUpdate({
207214
remoteChainSelector: baseSepoliaNetworkDetails.chainSelector,
208-
allowed: true,
209-
remotePoolAddress: abi.encode(address(burnMintTokenPoolBaseSepolia)),
215+
remotePoolAddresses: remotePoolAddressesEthSepolia,
210216
remoteTokenAddress: abi.encode(address(mockERC20TokenBaseSepolia)),
211217
outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100_000, rate: 167}),
212218
inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100_000, rate: 167})
213219
});
214-
burnMintTokenPoolEthSepolia.applyChainUpdates(chains);
220+
uint64[] memory remoteChainSelectorsToRemove = new uint64[](0);
221+
burnMintTokenPoolEthSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains);
215222
vm.stopPrank();
216223

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

220227
vm.startPrank(alice);
221228
chains = new TokenPool.ChainUpdate[](1);
229+
bytes[] memory remotePoolAddressesBaseSepolia = new bytes[](1);
230+
remotePoolAddressesBaseSepolia[0] = abi.encode(address(burnMintTokenPoolEthSepolia));
222231
chains[0] = TokenPool.ChainUpdate({
223232
remoteChainSelector: ethSepoliaNetworkDetails.chainSelector,
224-
allowed: true,
225-
remotePoolAddress: abi.encode(address(burnMintTokenPoolEthSepolia)),
233+
remotePoolAddresses: remotePoolAddressesBaseSepolia,
226234
remoteTokenAddress: abi.encode(address(mockERC20TokenEthSepolia)),
227235
outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100_000, rate: 167}),
228236
inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100_000, rate: 167})
229237
});
230-
burnMintTokenPoolBaseSepolia.applyChainUpdates(chains);
238+
burnMintTokenPoolBaseSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains);
231239
vm.stopPrank();
232240

233241
// Step 15) Mint tokens on Ethereum Sepolia and transfer them to Base Sepolia

test/e2e/ccip/CCIPv1_5LockReleasePoolFork.t.sol

+21-12
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ contract CCIPv1_5LockReleasePoolFork is Test {
2929
CCIPLocalSimulatorFork public ccipLocalSimulatorFork;
3030
MockERC20TokenOwner public mockERC20TokenEthSepolia;
3131
MockERC20TokenOwner public mockERC20TokenBaseSepolia;
32+
LockReleaseTokenPool public lockReleaseTokenPoolEthSepolia;
33+
LockReleaseTokenPool public lockReleaseTokenPoolBaseSepolia;
34+
35+
Register.NetworkDetails ethSepoliaNetworkDetails;
36+
Register.NetworkDetails baseSepoliaNetworkDetails;
3237

3338
uint256 ethSepoliaFork;
3439
uint256 baseSepoliaFork;
@@ -62,13 +67,14 @@ contract CCIPv1_5LockReleasePoolFork is Test {
6267
function test_forkSupportNewCCIPToken() public {
6368
// Step 3) Deploy LockReleaseTokenPool on Ethereum Sepolia
6469
vm.selectFork(ethSepoliaFork);
65-
Register.NetworkDetails memory ethSepoliaNetworkDetails =
66-
ccipLocalSimulatorFork.getNetworkDetails(block.chainid);
70+
ethSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid);
6771
address[] memory allowlist = new address[](0);
72+
uint8 localTokenDecimals = 18;
6873

6974
vm.startPrank(alice);
70-
LockReleaseTokenPool lockReleaseTokenPoolEthSepolia = new LockReleaseTokenPool(
75+
lockReleaseTokenPoolEthSepolia = new LockReleaseTokenPool(
7176
IERC20(address(mockERC20TokenEthSepolia)),
77+
localTokenDecimals,
7278
allowlist,
7379
ethSepoliaNetworkDetails.rmnProxyAddress,
7480
true, // acceptLiquidity
@@ -78,12 +84,12 @@ contract CCIPv1_5LockReleasePoolFork is Test {
7884

7985
// Step 4) Deploy LockReleaseTokenPool on Base Sepolia
8086
vm.selectFork(baseSepoliaFork);
81-
Register.NetworkDetails memory baseSepoliaNetworkDetails =
82-
ccipLocalSimulatorFork.getNetworkDetails(block.chainid);
87+
baseSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid);
8388

8489
vm.startPrank(alice);
85-
LockReleaseTokenPool lockReleaseTokenPoolBaseSepolia = new LockReleaseTokenPool(
90+
lockReleaseTokenPoolBaseSepolia = new LockReleaseTokenPool(
8691
IERC20(address(mockERC20TokenBaseSepolia)),
92+
localTokenDecimals,
8793
allowlist,
8894
baseSepoliaNetworkDetails.rmnProxyAddress,
8995
true, // acceptLiquidity
@@ -174,31 +180,34 @@ contract CCIPv1_5LockReleasePoolFork is Test {
174180

175181
vm.startPrank(alice);
176182
TokenPool.ChainUpdate[] memory chains = new TokenPool.ChainUpdate[](1);
183+
bytes[] memory remotePoolAddressesEthSepolia = new bytes[](1);
184+
remotePoolAddressesEthSepolia[0] = abi.encode(address(lockReleaseTokenPoolBaseSepolia));
177185
chains[0] = TokenPool.ChainUpdate({
178186
remoteChainSelector: baseSepoliaNetworkDetails.chainSelector,
179-
allowed: true,
180-
remotePoolAddress: abi.encode(address(lockReleaseTokenPoolBaseSepolia)),
187+
remotePoolAddresses: remotePoolAddressesEthSepolia,
181188
remoteTokenAddress: abi.encode(address(mockERC20TokenBaseSepolia)),
182189
outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: liquidityAmount, rate: 167}),
183190
inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: liquidityAmount, rate: 167})
184191
});
185-
lockReleaseTokenPoolEthSepolia.applyChainUpdates(chains);
192+
uint64[] memory remoteChainSelectorsToRemove = new uint64[](0);
193+
lockReleaseTokenPoolEthSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains);
186194
vm.stopPrank();
187195

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

191199
vm.startPrank(alice);
192200
chains = new TokenPool.ChainUpdate[](1);
201+
bytes[] memory remotePoolAddressesBaseSepolia = new bytes[](1);
202+
remotePoolAddressesBaseSepolia[0] = abi.encode(address(lockReleaseTokenPoolEthSepolia));
193203
chains[0] = TokenPool.ChainUpdate({
194204
remoteChainSelector: ethSepoliaNetworkDetails.chainSelector,
195-
allowed: true,
196-
remotePoolAddress: abi.encode(address(lockReleaseTokenPoolEthSepolia)),
205+
remotePoolAddresses: remotePoolAddressesBaseSepolia,
197206
remoteTokenAddress: abi.encode(address(mockERC20TokenEthSepolia)),
198207
outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: liquidityAmount, rate: 167}),
199208
inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: liquidityAmount, rate: 167})
200209
});
201-
lockReleaseTokenPoolBaseSepolia.applyChainUpdates(chains);
210+
lockReleaseTokenPoolBaseSepolia.applyChainUpdates(remoteChainSelectorsToRemove, chains);
202211
vm.stopPrank();
203212

204213
// Step 15) Transfer tokens from Ethereum Sepolia to Base Sepolia

0 commit comments

Comments
 (0)