Skip to content

Commit 3975f88

Browse files
committed
feat: added EnsoCCIPReceiver tests
1 parent 3fed934 commit 3975f88

File tree

17 files changed

+412
-52
lines changed

17 files changed

+412
-52
lines changed

.bash/deploy.sh

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,22 @@ fi
2424
if [[ $network_upper == "POLYGON" ]]; then
2525
params+=(--gas-estimate-multiplier 300)
2626
fi
27-
if [ $broadcast == "broadcast" ]; then
27+
if [ "$broadcast" == "broadcast" ]; then
2828
params+=(--broadcast)
2929
if [ -n "$verifier" ]; then
3030
params+=(--verify)
3131
params+=(--verifier "${verifier}")
32-
if [ $verifier == "etherscan" ]; then
33-
params+=(--etherscan-api-key ${!blockscan_key})
34-
elif [ $verifier == "routescan" ]; then
32+
if [ "$verifier" == "etherscan" ]; then
33+
params+=(--etherscan-api-key "${!blockscan_key}")
34+
elif [ "$verifier" == "routescan" ]; then
3535
params+=(--verifier-url "https://api.routescan.io/v2/network/mainnet/evm/80094/etherscan")
3636
params+=(--etherscan-api-key "verifyContract")
37-
elif [ $verifier == "blockscout" ]; then
38-
if [ $network_upper == "INK"]; then
37+
elif [ "$verifier" == "blockscout" ]; then
38+
if [ "$network_upper" == "INK" ]; then
3939
params+=(--verifier-url "https://explorer.inkonchain.com/api")
40-
elif [ $network_upper == "PLUME"]; then
40+
elif [ "$network_upper" == "PLUME" ]; then
4141
params+=(--verifier-url "https://explorer.plume.org/api")
42-
elif [ $network_upper == "KATANA"]; then
42+
elif [ "$network_upper" == "KATANA" ]; then
4343
params+=(--verifier-url "https://explorer.katanarpc.com/api")
4444
else
4545
params+=(--verifier-url "https://${network}.blockscout.com/api")
@@ -49,5 +49,7 @@ if [ $broadcast == "broadcast" ]; then
4949
params+=(-vvvv)
5050
fi
5151

52-
set -x
53-
forge script script/${script} --private-key $PRIVATE_KEY --rpc-url ${!rpc} "${params[@]}"
52+
{ set +x; } 2>/dev/null
53+
54+
PRIVATE_KEY="$PRIVATE_KEY" \
55+
forge script "script/${script}" --rpc-url "${!rpc}" "${params[@]}"

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"format": "prettier --check . && forge fmt --check",
1010
"format:fix": "prettier --write . && forge fmt",
1111
"lint:fix": "prettier --write . && ./.bash/forge-lint.sh",
12+
"test:enso_ccip:unit": "forge test --match-path 'test/unit/concrete/{bridge/ensoCCIPReceiver,libraries/ccipMessageDecoder,fuzz/libraries/ccipMessageDecoder}/*.t.sol'",
1213
"test:enso_checkout:fork": "forge test --match-path 'test/fork/enso-checkout/*.t.sol'",
1314
"test:enso_checkout:unit": "forge test --match-path 'test/unit/concrete/{delegate/ensoReceiver,factory/erc4337CloneFactory,paymaster/signaturePaymaster}/*.t.sol'",
1415
"test:enso_checkout:mutation": "node scripts/runEnsoCheckoutMutationTests.mjs"
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.28;
3+
4+
import "../src/bridge/EnsoCCIPReceiver.sol";
5+
import "../src/libraries/DataTypes.sol";
6+
import "forge-std/Script.sol";
7+
8+
contract EnsoCCIPReceiverDeployer is Script {
9+
error EnsoRouterIsNotSet();
10+
error CCIPRouterIsNotSet();
11+
error OwnerIsNotSet();
12+
error UnsupportedChainId(uint256 chainId);
13+
14+
function run() public returns (address ensoCcipReceiver, address owner, address ccipRouter, address ensoRouter) {
15+
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
16+
17+
uint256 chainId = block.chainid;
18+
19+
// TODO: set owner address
20+
if (chainId == ChainId.ETHEREUM) {
21+
ccipRouter = 0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D;
22+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
23+
} else if (chainId == ChainId.OPTIMISM) {
24+
ccipRouter = 0x3206695CaE29952f4b0c22a169725a865bc8Ce0f;
25+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
26+
} else if (chainId == ChainId.BINANCE) {
27+
ccipRouter = 0x34B03Cb9086d7D758AC55af71584F81A598759FE;
28+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
29+
} else if (chainId == ChainId.GNOSIS) {
30+
ccipRouter = 0x4aAD6071085df840abD9Baf1697d5D5992bDadce;
31+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
32+
} else if (chainId == ChainId.UNICHAIN) {
33+
ccipRouter = 0x68891f5F96695ECd7dEdBE2289D1b73426ae7864;
34+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
35+
} else if (chainId == ChainId.POLYGON) {
36+
ccipRouter = 0x849c5ED5a80F5B408Dd4969b78c2C8fdf0565Bfe;
37+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
38+
} else if (chainId == ChainId.SONIC) {
39+
ccipRouter = 0xB4e1Ff7882474BB93042be9AD5E1fA387949B860;
40+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
41+
} else if (chainId == ChainId.ZKSYNC) {
42+
ccipRouter = 0x748Fd769d81F5D94752bf8B0875E9301d0ba71bB;
43+
ensoRouter = 0x1BD8CefD703CF6b8fF886AD2E32653C32bc62b5C; // NOTE: different router for zksync
44+
} else if (chainId == ChainId.WORLD) {
45+
ccipRouter = 0x5fd9E4986187c56826A3064954Cfa2Cf250cfA0f;
46+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
47+
} else if (chainId == ChainId.HYPER) {
48+
ccipRouter = 0x13b3332b66389B1467CA6eBd6fa79775CCeF65ec;
49+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
50+
} else if (chainId == ChainId.BASE) {
51+
ccipRouter = 0x881e3A65B4d4a04dD529061dd0071cf975F58bCD;
52+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
53+
} else if (chainId == ChainId.PLASMA) {
54+
ccipRouter = 0xcDca5D374e46A6DDDab50bD2D9acB8c796eC35C3;
55+
ensoRouter = 0xCfBAa9Cfce952Ca4F4069874fF1Df8c05e37a3c7; // NOTE: different router for plasma
56+
} else if (chainId == ChainId.ARBITRUM) {
57+
ccipRouter = 0x141fa059441E0ca23ce184B6A78bafD2A517DdE8;
58+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
59+
} else if (chainId == ChainId.AVALANCHE) {
60+
ccipRouter = 0xF4c7E640EdA248ef95972845a62bdC74237805dB;
61+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
62+
} else if (chainId == ChainId.INK) {
63+
ccipRouter = 0xca7c90A52B44E301AC01Cb5EB99b2fD99339433A;
64+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
65+
} else if (chainId == ChainId.LINEA) {
66+
ccipRouter = 0x549FEB73F2348F6cD99b9fc8c69252034897f06C;
67+
ensoRouter = 0xA146d46823f3F594B785200102Be5385CAfCE9B5; // NOTE: different router for linea
68+
} else if (chainId == ChainId.BERACHAIN) {
69+
ccipRouter = 0x71a275704c283486fBa26dad3dd0DB78804426eF;
70+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
71+
} else if (chainId == ChainId.PLUME) {
72+
ccipRouter = 0x5C4f4622AD0EC4a47e04840db7E9EcA8354109af;
73+
ensoRouter = 0x3067BDBa0e6628497d527bEF511c22DA8b32cA3F; // NOTE: different router for plume
74+
} else if (chainId == ChainId.KATANA) {
75+
ccipRouter = 0x7c19b79D2a054114Ab36ad758A36e92376e267DA;
76+
ensoRouter = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
77+
} else {
78+
revert UnsupportedChainId(chainId);
79+
}
80+
81+
if (owner == address(0)) {
82+
revert OwnerIsNotSet();
83+
}
84+
if (ccipRouter == address(0)) {
85+
revert CCIPRouterIsNotSet();
86+
}
87+
if (ensoRouter == address(0)) {
88+
revert EnsoRouterIsNotSet();
89+
}
90+
91+
vm.startBroadcast(deployerPrivateKey);
92+
93+
ensoCcipReceiver = address(new EnsoCCIPReceiver{ salt: "EnsoCCIPReceiver" }(owner, ccipRouter, ensoRouter));
94+
95+
vm.stopBroadcast();
96+
}
97+
}

src/libraries/CCIPMessageDecoder.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ library CCIPMessageDecoder {
7070
assembly ("memory-safe") {
7171
let src := add(add(base, off), 32) // start of tail payload
7272
let dst := add(shortcutData, 32) // start of new bytes payload
73-
// Copy in 32-byte chunks up to padded boundary
73+
// Copy in 32-byte chunks up to padded boundary
7474
for { let i := 0 } lt(i, padded) { i := add(i, 32) } {
7575
mstore(add(dst, i), mload(add(src, i)))
7676
}

test/EnsoRouter.t.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,8 @@ contract EnsoRouterTest is Test, ERC721Holder, ERC1155Holder {
356356

357357
Token memory tokenIn = Token(TokenType.ERC721, abi.encode(address(nft), TOKENID));
358358
Token memory tokenOut = Token(TokenType.ERC721, abi.encode(address(nftVault), 1)); // token out is checking for
359-
// balance, which
360-
// should increase by 1
359+
// balance, which
360+
// should increase by 1
361361

362362
router.safeRouteSingle(tokenIn, tokenOut, address(this), data);
363363
assertEq(1, nftVault.balanceOf(address(this)));

test/fork/enso-checkout/Checkout_SmartWallet_EntryPointV7_Fork_Test.t.sol

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,21 +110,21 @@ contract Checkout_SmartWallet_EntryPointV7_Fork_Test is Test, TokenBalanceHelper
110110
uint256 safeThreshold = 2;
111111
address safeTo = address(0); // Contract address for optional delegate call.
112112
bytes memory safeData = ""; // Data payload for optional delegate call.
113-
// address safeFallbackHandler = address(0); // Handler for fallback calls to this contract
113+
// address safeFallbackHandler = address(0); // Handler for fallback calls to this contract
114114
address safePaymentToken = address(0); // Token that should be used for the payment (0 is ETH)
115115
uint256 safePayment = 0; // Value that should be paid
116116
address payable safePaymentReceiver = payable(address(0)); // Address that should receive the payment (or 0 if
117-
// tx.origin)
118-
// s_safe.setup(
119-
// safeOwners,
120-
// safeThreshold,
121-
// safeTo,
122-
// safeData,
123-
// safeFallbackHandler,
124-
// safePaymentToken,
125-
// safePayment,
126-
// safePaymentReceiver
127-
// );
117+
// tx.origin)
118+
// s_safe.setup(
119+
// safeOwners,
120+
// safeThreshold,
121+
// safeTo,
122+
// safeData,
123+
// safeFallbackHandler,
124+
// safePaymentToken,
125+
// safePayment,
126+
// safePaymentReceiver
127+
// );
128128
bytes memory safeSetupData = abi.encodeCall(
129129
Safe.setup,
130130
(

test/mocks/MockEnsoRouter.sol

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity ^0.8.20;
3+
4+
import { Token, TokenType } from "../../../../src/interfaces/IEnsoRouter.sol";
5+
import { IERC1155 } from "openzeppelin-contracts/token/ERC1155/IERC1155.sol";
6+
import { IERC20, SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
7+
import { IERC721 } from "openzeppelin-contracts/token/ERC721/IERC721.sol";
8+
9+
contract MockEnsoShortcuts {
10+
receive() external payable { }
11+
}
12+
13+
contract MockEnsoRouter {
14+
using SafeERC20 for IERC20;
15+
16+
address public immutable shortcuts;
17+
bool private s_success;
18+
bytes private s_response;
19+
20+
error WrongMsgValue(uint256 value, uint256 expectedAmount);
21+
error UnsupportedTokenType(TokenType tokenType);
22+
23+
constructor() {
24+
shortcuts = address(new MockEnsoShortcuts());
25+
}
26+
27+
function routeSingle(Token calldata tokenIn, bytes calldata) public payable returns (bytes memory response) {
28+
bool isNativeAsset = _transfer(tokenIn);
29+
if (!isNativeAsset && msg.value != 0) {
30+
revert WrongMsgValue(msg.value, 0);
31+
}
32+
response = s_response;
33+
34+
if (!s_success) {
35+
assembly {
36+
revert(add(response, 32), mload(response))
37+
}
38+
}
39+
if (isNativeAsset) {
40+
shortcuts.call{ value: msg.value }("");
41+
}
42+
}
43+
44+
function setRouteSingleResponse(bool _success, bytes memory _response) external {
45+
s_success = _success;
46+
s_response = _response;
47+
}
48+
49+
function _transfer(Token calldata token) internal returns (bool isNativeAsset) {
50+
TokenType tokenType = token.tokenType;
51+
52+
if (tokenType == TokenType.ERC20) {
53+
(IERC20 erc20, uint256 amount) = abi.decode(token.data, (IERC20, uint256));
54+
erc20.safeTransferFrom(msg.sender, shortcuts, amount);
55+
} else if (tokenType == TokenType.Native) {
56+
// no need to get amount, it will come from msg.value
57+
isNativeAsset = true;
58+
} else if (tokenType == TokenType.ERC721) {
59+
(IERC721 erc721, uint256 tokenId) = abi.decode(token.data, (IERC721, uint256));
60+
erc721.safeTransferFrom(msg.sender, shortcuts, tokenId);
61+
} else if (tokenType == TokenType.ERC1155) {
62+
(IERC1155 erc1155, uint256 tokenId, uint256 amount) = abi.decode(token.data, (IERC1155, uint256, uint256));
63+
erc1155.safeTransferFrom(msg.sender, shortcuts, tokenId, amount, "0x");
64+
} else {
65+
revert UnsupportedTokenType(tokenType);
66+
}
67+
}
68+
}
Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,35 @@
1-
EnsoCCIPReceiver::decodeMessageData
2-
├── when msg sender is not self
3-
│ └── it should revert
4-
└── when msg sender is self
5-
├── when data is malformed
6-
│ └── it should panic
7-
└── when data is not malformed
8-
└── it should return payload decoded
1+
// EnsoCCIPReceiver::AcceptOwnership
2+
// ├── when caller is not pending owner
3+
// │ └── it should revert
4+
// └── when caller is pending owner
5+
// └── it should transfer ownership
6+
7+
EnsoCCIPReceiver::Constructor
8+
└── when deployed
9+
├── it should set owner
10+
├── it should set ccipRouter
11+
└── it should set ensoRouter
12+
13+
// EnsoCCIPReceiver::Pause
14+
// ├── when caller is not owner
15+
// │ └── it should revert
16+
// └── when caller is owner
17+
// └── it should pause the contract
18+
19+
// EnsoCCIPReceiver::Unpause
20+
// ├── when caller is not owner
21+
// │ └── it should revert
22+
// └── when caller is owner
23+
// └── it should unpause the contract
24+
25+
// EnsoCCIPReceiver::TransferOwnership
26+
// ├── when caller is not owner
27+
// │ └── it should revert
28+
// └── when caller is owner
29+
// └── it should start ownership transfer
30+
31+
// EnsoCCIPReceiver::RenounceOwnership
32+
// ├── when caller is not owner
33+
// │ └── it should revert
34+
// └── when caller is owner
35+
// └── it should transfer ownership to zero address
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity ^0.8.28;
3+
4+
import { EnsoCCIPReceiver_Unit_Concrete_Test } from "./EnsoCCIPReceiver.t.sol";
5+
import { Ownable } from "openzeppelin-contracts/access/Ownable.sol";
6+
7+
contract EnsoCCIPReceiver_AcceptOwnership_Unit_Concrete_Test is EnsoCCIPReceiver_Unit_Concrete_Test {
8+
function setUp() public virtual override {
9+
super.setUp();
10+
11+
vm.prank(s_owner);
12+
s_ensoCcipReceiver.transferOwnership(s_account2);
13+
}
14+
15+
function test_RevertWhen_CallerIsNotPendingOwner() external {
16+
// Act & Assert
17+
// it should revert
18+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, s_account1));
19+
vm.prank(s_account1);
20+
s_ensoCcipReceiver.acceptOwnership();
21+
}
22+
23+
function test_WhenCallerIsPendingOwner() external {
24+
// Act
25+
vm.prank(s_account2);
26+
s_ensoCcipReceiver.acceptOwnership();
27+
28+
// Assert
29+
// it should transfer ownership
30+
assertEq(s_ensoCcipReceiver.owner(), s_account2);
31+
}
32+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity ^0.8.28;
3+
4+
import { EnsoCCIPReceiver } from "../../../../../src/bridge/EnsoCCIPReceiver.sol";
5+
import { EnsoCCIPReceiver_Unit_Concrete_Test } from "./EnsoCCIPReceiver.t.sol";
6+
7+
contract EnsoCCIPReceiver_Constructor_Unit_Concrete_Test is EnsoCCIPReceiver_Unit_Concrete_Test {
8+
function test_WhenDeployed() external {
9+
// Act & Assert
10+
vm.prank(s_deployer);
11+
EnsoCCIPReceiver ensoCcipReceiver = new EnsoCCIPReceiver(s_owner, address(s_ccipRouter), address(s_ensoRouter));
12+
13+
// it should set owner
14+
assertTrue(ensoCcipReceiver.owner() == s_owner);
15+
16+
// it should set ccipRouter
17+
assertTrue(ensoCcipReceiver.getRouter() == address(s_ccipRouter));
18+
19+
// it should set ensoRouter
20+
assertTrue(ensoCcipReceiver.getEnsoRouter() == address(s_ensoRouter));
21+
}
22+
}

0 commit comments

Comments
 (0)