Skip to content

Commit c701cb2

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

File tree

14 files changed

+275
-42
lines changed

14 files changed

+275
-42
lines changed

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"

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: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
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::Pause
8+
// ├── when caller is not owner
9+
// │ └── it should revert
10+
// └── when caller is owner
11+
// └── it should pause the contract
12+
13+
// EnsoCCIPReceiver::Unpause
14+
// ├── when caller is not owner
15+
// │ └── it should revert
16+
// └── when caller is owner
17+
// └── it should unpause the contract
18+
19+
// EnsoCCIPReceiver::TransferOwnership
20+
// ├── when caller is not owner
21+
// │ └── it should revert
22+
// └── when caller is owner
23+
// └── it should start ownership transfer
24+
25+
// EnsoCCIPReceiver::RenounceOwnership
26+
// ├── when caller is not owner
27+
// │ └── it should revert
28+
// └── when caller is owner
29+
// └── 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.20;
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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity ^0.8.20;
3+
4+
import { EnsoCCIPReceiver_Unit_Concrete_Test } from "./EnsoCCIPReceiver.t.sol";
5+
import { Ownable } from "openzeppelin-contracts/access/Ownable.sol";
6+
7+
contract EnsoCCIPReceiver_Pause_Unit_Concrete_Test is EnsoCCIPReceiver_Unit_Concrete_Test {
8+
function test_RevertWhen_CallerIsNotOwner() external {
9+
// Act & Assert
10+
// it should revert
11+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, s_account1));
12+
vm.prank(s_account1);
13+
s_ensoCcipReceiver.pause();
14+
}
15+
16+
function test_WhenCallerIsOwner() external {
17+
// Act
18+
vm.prank(s_owner);
19+
s_ensoCcipReceiver.pause();
20+
21+
// Assert
22+
// it should pause the contract
23+
assertTrue(s_ensoCcipReceiver.paused());
24+
}
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity ^0.8.20;
3+
4+
import { EnsoCCIPReceiver_Unit_Concrete_Test } from "./EnsoCCIPReceiver.t.sol";
5+
import { Ownable } from "openzeppelin-contracts/access/Ownable.sol";
6+
7+
contract EnsoCCIPReceiver_RenounceOwnership_Unit_Concrete_Test is EnsoCCIPReceiver_Unit_Concrete_Test {
8+
function test_RevertWhen_CallerIsNotOwner() external {
9+
// Act & Assert
10+
// it should revert
11+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, s_account2));
12+
vm.prank(s_account2);
13+
s_ensoCcipReceiver.renounceOwnership();
14+
}
15+
16+
function test_WhenCallerIsOwner() external {
17+
// Act
18+
vm.prank(s_owner);
19+
s_ensoCcipReceiver.renounceOwnership();
20+
21+
// Assert
22+
// it should transfer ownership to zero address
23+
assertEq(s_ensoCcipReceiver.owner(), address(0));
24+
}
25+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity ^0.8.20;
3+
4+
import { EnsoCCIPReceiver_Unit_Concrete_Test } from "./EnsoCCIPReceiver.t.sol";
5+
import { Ownable } from "openzeppelin-contracts/access/Ownable.sol";
6+
7+
contract EnsoCCIPReceiver_TransferOwnership_Unit_Concrete_Test is EnsoCCIPReceiver_Unit_Concrete_Test {
8+
function test_RevertWhen_CallerIsNotOwner() external {
9+
// Act & Assert
10+
// it should revert
11+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, s_account2));
12+
vm.prank(s_account2);
13+
s_ensoCcipReceiver.transferOwnership(s_account2);
14+
}
15+
16+
function test_WhenCallerIsOwner() external {
17+
// Act
18+
vm.prank(s_owner);
19+
s_ensoCcipReceiver.transferOwnership(s_account2);
20+
21+
// Assert
22+
// it should start ownership transfer
23+
assertEq(s_ensoCcipReceiver.owner(), s_owner);
24+
assertEq(s_ensoCcipReceiver.pendingOwner(), s_account2);
25+
}
26+
}

0 commit comments

Comments
 (0)