Skip to content

Commit 436ac9e

Browse files
committed
Add AdvancedPoolHooksExtractor
1 parent 082c8eb commit 436ac9e

File tree

4 files changed

+331
-0
lines changed

4 files changed

+331
-0
lines changed

chains/evm/.gas-snapshot

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
AdvancedPoolHooksExtractor_extract:test_extract_PostflightCheck() (gas: 63063)
2+
AdvancedPoolHooksExtractor_extract:test_extract_PreflightCheck() (gas: 49351)
13
AdvancedPoolHooks_applyAllowListUpdates:test_applyAllowListUpdates() (gas: 184344)
24
AdvancedPoolHooks_applyAllowListUpdates:test_applyAllowListUpdates_SkipsZero() (gas: 24043)
35
AdvancedPoolHooks_applyAuthorizedCallerUpdates:test_applyAuthorizedCallerUpdates_AddAndRemove() (gas: 57884)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.24;
3+
4+
import {IAdvancedPoolHooks} from "../../interfaces/IAdvancedPoolHooks.sol";
5+
import {IExtractor} from "@chainlink/ace/policy-management/interfaces/IExtractor.sol";
6+
import {IPolicyEngine} from "@chainlink/ace/policy-management/interfaces/IPolicyEngine.sol";
7+
8+
import {Pool} from "../../libraries/Pool.sol";
9+
10+
/// @notice Extracts named parameters from AdvancedPoolHooks preflightCheck and postflightCheck calldata
11+
/// for ACE policy evaluation.
12+
contract AdvancedPoolHooksExtractor is IExtractor {
13+
string public constant override typeAndVersion = "AdvancedPoolHooksExtractor 2.0.0-dev";
14+
15+
/// @notice Parameter key for the sender address initiating the transfer.
16+
bytes32 public constant PARAM_FROM = keccak256("from");
17+
/// @notice Parameter key for the recipient. Raw bytes in preflight, abi.encoded in postflight.
18+
bytes32 public constant PARAM_TO = keccak256("to");
19+
/// @notice Parameter key for the transfer amount specified during ccipSend.
20+
bytes32 public constant PARAM_AMOUNT = keccak256("amount");
21+
/// @notice Parameter key for the transfer amount after fee deduction. Only present in preflight.
22+
bytes32 public constant PARAM_AMOUNT_POST_FEE = keccak256("amount_post_fee");
23+
/// @notice Parameter key for the remote chain selector.
24+
bytes32 public constant PARAM_REMOTE_CHAIN_SELECTOR = keccak256("remote_chain_selector");
25+
/// @notice Parameter key for the local token address.
26+
bytes32 public constant PARAM_TOKEN = keccak256("token");
27+
/// @notice Parameter key for the requested number of block confirmations.
28+
bytes32 public constant PARAM_BLOCK_CONFIRMATION_REQUESTED = keccak256("block_confirmation_requested");
29+
/// @notice Parameter key for the source pool address. Only present in postflight.
30+
bytes32 public constant PARAM_SOURCE_POOL_ADDRESS = keccak256("source_pool_address");
31+
/// @notice Parameter key for the source pool data. Only present in postflight.
32+
bytes32 public constant PARAM_SOURCE_POOL_DATA = keccak256("source_pool_data");
33+
/// @notice Parameter key for the source-denominated transfer amount. Only present in postflight.
34+
bytes32 public constant PARAM_SOURCE_DENOMINATED_AMOUNT = keccak256("source_denominated_amount");
35+
36+
/// @inheritdoc IExtractor
37+
function extract(
38+
IPolicyEngine.Payload calldata payload
39+
) public pure virtual returns (IPolicyEngine.Parameter[] memory) {
40+
if (payload.selector == IAdvancedPoolHooks.preflightCheck.selector) {
41+
return _extractPreflightCheck(payload);
42+
}
43+
44+
if (payload.selector == IAdvancedPoolHooks.postflightCheck.selector) {
45+
return _extractPostflightCheck(payload);
46+
}
47+
48+
revert IPolicyEngine.UnsupportedSelector(payload.selector);
49+
}
50+
51+
/// @dev Decodes preflightCheck arguments: (LockOrBurnInV1, uint16, bytes, uint256).
52+
function _extractPreflightCheck(
53+
IPolicyEngine.Payload calldata payload
54+
) internal pure returns (IPolicyEngine.Parameter[] memory) {
55+
// tokenArgs is skipped as it is treated as context in the payload.
56+
(Pool.LockOrBurnInV1 memory lockOrBurnIn, uint16 blockConfirmationRequested,, uint256 amountPostFee) =
57+
abi.decode(payload.data, (Pool.LockOrBurnInV1, uint16, bytes, uint256));
58+
59+
IPolicyEngine.Parameter[] memory result = new IPolicyEngine.Parameter[](7);
60+
result[0] = IPolicyEngine.Parameter(PARAM_FROM, abi.encode(lockOrBurnIn.originalSender));
61+
result[1] = IPolicyEngine.Parameter(PARAM_TO, lockOrBurnIn.receiver);
62+
result[2] = IPolicyEngine.Parameter(PARAM_AMOUNT, abi.encode(lockOrBurnIn.amount));
63+
result[3] = IPolicyEngine.Parameter(PARAM_AMOUNT_POST_FEE, abi.encode(amountPostFee));
64+
result[4] = IPolicyEngine.Parameter(PARAM_REMOTE_CHAIN_SELECTOR, abi.encode(lockOrBurnIn.remoteChainSelector));
65+
result[5] = IPolicyEngine.Parameter(PARAM_TOKEN, abi.encode(lockOrBurnIn.localToken));
66+
result[6] = IPolicyEngine.Parameter(PARAM_BLOCK_CONFIRMATION_REQUESTED, abi.encode(blockConfirmationRequested));
67+
68+
return result;
69+
}
70+
71+
/// @dev Decodes postflightCheck arguments: (ReleaseOrMintInV1, uint256, uint16).
72+
function _extractPostflightCheck(
73+
IPolicyEngine.Payload calldata payload
74+
) internal pure returns (IPolicyEngine.Parameter[] memory) {
75+
(Pool.ReleaseOrMintInV1 memory releaseOrMintIn, uint256 localAmount, uint16 blockConfirmationRequested) =
76+
abi.decode(payload.data, (Pool.ReleaseOrMintInV1, uint256, uint16));
77+
78+
// offchainTokenData is skipped as it is treated as context in the payload.
79+
// Note offchainTokenData is no longer used in v2+ TokenPools.
80+
IPolicyEngine.Parameter[] memory result = new IPolicyEngine.Parameter[](9);
81+
result[0] = IPolicyEngine.Parameter(PARAM_FROM, releaseOrMintIn.originalSender);
82+
result[1] = IPolicyEngine.Parameter(PARAM_TO, abi.encode(releaseOrMintIn.receiver));
83+
result[2] = IPolicyEngine.Parameter(PARAM_AMOUNT, abi.encode(localAmount));
84+
result[3] = IPolicyEngine.Parameter(PARAM_REMOTE_CHAIN_SELECTOR, abi.encode(releaseOrMintIn.remoteChainSelector));
85+
result[4] = IPolicyEngine.Parameter(PARAM_TOKEN, abi.encode(releaseOrMintIn.localToken));
86+
result[5] = IPolicyEngine.Parameter(PARAM_BLOCK_CONFIRMATION_REQUESTED, abi.encode(blockConfirmationRequested));
87+
result[6] = IPolicyEngine.Parameter(PARAM_SOURCE_POOL_ADDRESS, releaseOrMintIn.sourcePoolAddress);
88+
result[7] = IPolicyEngine.Parameter(PARAM_SOURCE_POOL_DATA, releaseOrMintIn.sourcePoolData);
89+
result[8] =
90+
IPolicyEngine.Parameter(PARAM_SOURCE_DENOMINATED_AMOUNT, abi.encode(releaseOrMintIn.sourceDenominatedAmount));
91+
92+
return result;
93+
}
94+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.24;
3+
4+
import {IAdvancedPoolHooks} from "../../../interfaces/IAdvancedPoolHooks.sol";
5+
import {IPolicyEngine} from "@chainlink/ace/policy-management/interfaces/IPolicyEngine.sol";
6+
7+
import {Pool} from "../../../libraries/Pool.sol";
8+
import {AdvancedPoolHooksExtractorSetup} from "./AdvancedPoolHooksExtractorSetup.t.sol";
9+
10+
contract AdvancedPoolHooksExtractor_extract is AdvancedPoolHooksExtractorSetup {
11+
function test_extract_PreflightCheck() public view {
12+
IPolicyEngine.Payload memory payload = _createPreflightPayload();
13+
14+
IPolicyEngine.Parameter[] memory params = s_extractor.extract(payload);
15+
16+
assertEq(7, params.length);
17+
18+
assertEq(s_extractor.PARAM_FROM(), params[0].name);
19+
assertEq(s_originalSender, abi.decode(params[0].value, (address)));
20+
21+
assertEq(s_extractor.PARAM_TO(), params[1].name);
22+
assertEq(abi.encode(s_receiver), params[1].value);
23+
24+
assertEq(s_extractor.PARAM_AMOUNT(), params[2].name);
25+
assertEq(AMOUNT, abi.decode(params[2].value, (uint256)));
26+
27+
assertEq(s_extractor.PARAM_AMOUNT_POST_FEE(), params[3].name);
28+
assertEq(AMOUNT_POST_FEE, abi.decode(params[3].value, (uint256)));
29+
30+
assertEq(s_extractor.PARAM_REMOTE_CHAIN_SELECTOR(), params[4].name);
31+
assertEq(REMOTE_CHAIN_SELECTOR, abi.decode(params[4].value, (uint64)));
32+
33+
assertEq(s_extractor.PARAM_TOKEN(), params[5].name);
34+
assertEq(s_localToken, abi.decode(params[5].value, (address)));
35+
36+
assertEq(s_extractor.PARAM_BLOCK_CONFIRMATION_REQUESTED(), params[6].name);
37+
assertEq(BLOCK_CONFIRMATION_REQUESTED, abi.decode(params[6].value, (uint16)));
38+
}
39+
40+
function test_extract_PostflightCheck() public view {
41+
IPolicyEngine.Payload memory payload = _createPostflightPayload();
42+
43+
IPolicyEngine.Parameter[] memory params = s_extractor.extract(payload);
44+
45+
assertEq(9, params.length);
46+
47+
assertEq(s_extractor.PARAM_FROM(), params[0].name);
48+
assertEq(abi.encode(s_originalSender), params[0].value);
49+
50+
assertEq(s_extractor.PARAM_TO(), params[1].name);
51+
assertEq(s_receiver, abi.decode(params[1].value, (address)));
52+
53+
assertEq(s_extractor.PARAM_AMOUNT(), params[2].name);
54+
assertEq(AMOUNT, abi.decode(params[2].value, (uint256)));
55+
56+
assertEq(s_extractor.PARAM_REMOTE_CHAIN_SELECTOR(), params[3].name);
57+
assertEq(REMOTE_CHAIN_SELECTOR, abi.decode(params[3].value, (uint64)));
58+
59+
assertEq(s_extractor.PARAM_TOKEN(), params[4].name);
60+
assertEq(s_localToken, abi.decode(params[4].value, (address)));
61+
62+
assertEq(s_extractor.PARAM_BLOCK_CONFIRMATION_REQUESTED(), params[5].name);
63+
assertEq(BLOCK_CONFIRMATION_REQUESTED, abi.decode(params[5].value, (uint16)));
64+
65+
assertEq(s_extractor.PARAM_SOURCE_POOL_ADDRESS(), params[6].name);
66+
assertEq(abi.encode(s_sourcePool), params[6].value);
67+
68+
assertEq(s_extractor.PARAM_SOURCE_POOL_DATA(), params[7].name);
69+
assertEq(abi.encode("pool data"), params[7].value);
70+
71+
assertEq(s_extractor.PARAM_SOURCE_DENOMINATED_AMOUNT(), params[8].name);
72+
assertEq(SOURCE_DENOMINATED_AMOUNT, abi.decode(params[8].value, (uint256)));
73+
}
74+
75+
function testFuzz_extract_PreflightCheck(
76+
address originalSender,
77+
address receiver,
78+
address localToken,
79+
uint256 amount,
80+
uint256 amountPostFee,
81+
uint64 remoteChainSelector,
82+
uint16 blockConfirmationRequested
83+
) public view {
84+
Pool.LockOrBurnInV1 memory lockOrBurnIn = Pool.LockOrBurnInV1({
85+
receiver: abi.encode(receiver),
86+
remoteChainSelector: remoteChainSelector,
87+
originalSender: originalSender,
88+
amount: amount,
89+
localToken: localToken
90+
});
91+
92+
bytes memory tokenArgs = "";
93+
94+
IPolicyEngine.Payload memory payload = IPolicyEngine.Payload({
95+
selector: IAdvancedPoolHooks.preflightCheck.selector,
96+
sender: s_sender,
97+
data: abi.encode(lockOrBurnIn, blockConfirmationRequested, tokenArgs, amountPostFee),
98+
context: tokenArgs
99+
});
100+
101+
IPolicyEngine.Parameter[] memory params = s_extractor.extract(payload);
102+
103+
assertEq(7, params.length);
104+
assertEq(originalSender, abi.decode(params[0].value, (address)));
105+
assertEq(abi.encode(receiver), params[1].value);
106+
assertEq(amount, abi.decode(params[2].value, (uint256)));
107+
assertEq(amountPostFee, abi.decode(params[3].value, (uint256)));
108+
assertEq(remoteChainSelector, abi.decode(params[4].value, (uint64)));
109+
assertEq(localToken, abi.decode(params[5].value, (address)));
110+
assertEq(blockConfirmationRequested, abi.decode(params[6].value, (uint16)));
111+
}
112+
113+
function testFuzz_extract_PostflightCheck(
114+
address originalSender,
115+
address receiver,
116+
address localToken,
117+
address sourcePool,
118+
uint256 localAmount,
119+
uint256 sourceDenominatedAmount,
120+
uint64 remoteChainSelector,
121+
uint16 blockConfirmationRequested
122+
) public view {
123+
Pool.ReleaseOrMintInV1 memory releaseOrMintIn = Pool.ReleaseOrMintInV1({
124+
originalSender: abi.encode(originalSender),
125+
remoteChainSelector: remoteChainSelector,
126+
receiver: receiver,
127+
sourceDenominatedAmount: sourceDenominatedAmount,
128+
localToken: localToken,
129+
sourcePoolAddress: abi.encode(sourcePool),
130+
sourcePoolData: abi.encode("pool data"),
131+
offchainTokenData: ""
132+
});
133+
134+
IPolicyEngine.Payload memory payload = IPolicyEngine.Payload({
135+
selector: IAdvancedPoolHooks.postflightCheck.selector,
136+
sender: s_sender,
137+
data: abi.encode(releaseOrMintIn, localAmount, blockConfirmationRequested),
138+
context: ""
139+
});
140+
141+
IPolicyEngine.Parameter[] memory params = s_extractor.extract(payload);
142+
143+
assertEq(9, params.length);
144+
assertEq(abi.encode(originalSender), params[0].value);
145+
assertEq(receiver, abi.decode(params[1].value, (address)));
146+
assertEq(localAmount, abi.decode(params[2].value, (uint256)));
147+
assertEq(remoteChainSelector, abi.decode(params[3].value, (uint64)));
148+
assertEq(localToken, abi.decode(params[4].value, (address)));
149+
assertEq(blockConfirmationRequested, abi.decode(params[5].value, (uint16)));
150+
assertEq(abi.encode(sourcePool), params[6].value);
151+
assertEq(abi.encode("pool data"), params[7].value);
152+
assertEq(sourceDenominatedAmount, abi.decode(params[8].value, (uint256)));
153+
}
154+
155+
// Reverts
156+
157+
function test_extract_RevertWhen_UnsupportedSelector() public {
158+
bytes4 unsupportedSelector = bytes4(keccak256("unsupported()"));
159+
160+
IPolicyEngine.Payload memory payload =
161+
IPolicyEngine.Payload({selector: unsupportedSelector, sender: s_sender, data: "", context: ""});
162+
163+
vm.expectRevert(abi.encodeWithSelector(IPolicyEngine.UnsupportedSelector.selector, unsupportedSelector));
164+
s_extractor.extract(payload);
165+
}
166+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.24;
3+
4+
import {IAdvancedPoolHooks} from "../../../interfaces/IAdvancedPoolHooks.sol";
5+
import {IPolicyEngine} from "@chainlink/ace/policy-management/interfaces/IPolicyEngine.sol";
6+
7+
import {Pool} from "../../../libraries/Pool.sol";
8+
import {AdvancedPoolHooksExtractor} from "../../../pools/extractors/AdvancedPoolHooksExtractor.sol";
9+
10+
import {Test} from "forge-std/Test.sol";
11+
12+
contract AdvancedPoolHooksExtractorSetup is Test {
13+
AdvancedPoolHooksExtractor internal s_extractor;
14+
15+
address internal s_sender = makeAddr("sender");
16+
address internal s_originalSender = makeAddr("originalSender");
17+
address internal s_receiver = makeAddr("receiver");
18+
address internal s_localToken = makeAddr("localToken");
19+
address internal s_sourcePool = makeAddr("sourcePool");
20+
21+
uint64 internal constant REMOTE_CHAIN_SELECTOR = 123;
22+
uint256 internal constant AMOUNT = 100e18;
23+
uint256 internal constant AMOUNT_POST_FEE = 99e18;
24+
uint16 internal constant BLOCK_CONFIRMATION_REQUESTED = 5;
25+
uint256 internal constant SOURCE_DENOMINATED_AMOUNT = 200e18;
26+
27+
function setUp() public virtual {
28+
s_extractor = new AdvancedPoolHooksExtractor();
29+
}
30+
31+
function _createPreflightPayload() internal view returns (IPolicyEngine.Payload memory) {
32+
Pool.LockOrBurnInV1 memory lockOrBurnIn = Pool.LockOrBurnInV1({
33+
receiver: abi.encode(s_receiver),
34+
remoteChainSelector: REMOTE_CHAIN_SELECTOR,
35+
originalSender: s_originalSender,
36+
amount: AMOUNT,
37+
localToken: s_localToken
38+
});
39+
40+
bytes memory tokenArgs = abi.encode("token args");
41+
42+
return IPolicyEngine.Payload({
43+
selector: IAdvancedPoolHooks.preflightCheck.selector,
44+
sender: s_sender,
45+
data: abi.encode(lockOrBurnIn, BLOCK_CONFIRMATION_REQUESTED, tokenArgs, AMOUNT_POST_FEE),
46+
context: tokenArgs
47+
});
48+
}
49+
50+
function _createPostflightPayload() internal view returns (IPolicyEngine.Payload memory) {
51+
Pool.ReleaseOrMintInV1 memory releaseOrMintIn = Pool.ReleaseOrMintInV1({
52+
originalSender: abi.encode(s_originalSender),
53+
remoteChainSelector: REMOTE_CHAIN_SELECTOR,
54+
receiver: s_receiver,
55+
sourceDenominatedAmount: SOURCE_DENOMINATED_AMOUNT,
56+
localToken: s_localToken,
57+
sourcePoolAddress: abi.encode(s_sourcePool),
58+
sourcePoolData: abi.encode("pool data"),
59+
offchainTokenData: abi.encode("offchain data")
60+
});
61+
62+
return IPolicyEngine.Payload({
63+
selector: IAdvancedPoolHooks.postflightCheck.selector,
64+
sender: s_sender,
65+
data: abi.encode(releaseOrMintIn, AMOUNT, BLOCK_CONFIRMATION_REQUESTED),
66+
context: releaseOrMintIn.offchainTokenData
67+
});
68+
}
69+
}

0 commit comments

Comments
 (0)