Skip to content

Commit b96edae

Browse files
committed
feat: testing coverage added
1 parent d65977d commit b96edae

File tree

2 files changed

+736
-0
lines changed

2 files changed

+736
-0
lines changed

evm/test/ExecutorQuoter.t.sol

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,342 @@ contract ExecutorQuoterTest is Test {
9696
RelayInstructions.encodeGas(250000, 0)
9797
);
9898
}
99+
100+
// Error path tests
101+
102+
function test_chainInfoUpdate_invalidUpdater() public {
103+
address notUpdater = address(0xdead);
104+
vm.prank(notUpdater);
105+
vm.expectRevert(abi.encodeWithSelector(ExecutorQuoter.InvalidUpdater.selector, notUpdater, UPDATER));
106+
executorQuoter.chainInfoUpdate(chainInfoUpdates);
107+
}
108+
109+
function test_quoteUpdate_invalidUpdater() public {
110+
address notUpdater = address(0xdead);
111+
vm.prank(notUpdater);
112+
vm.expectRevert(abi.encodeWithSelector(ExecutorQuoter.InvalidUpdater.selector, notUpdater, UPDATER));
113+
executorQuoter.quoteUpdate(updates);
114+
}
115+
116+
function test_requestQuote_chainDisabled() public {
117+
uint16 disabledChain = 65000;
118+
vm.expectRevert(abi.encodeWithSelector(ExecutorQuoter.ChainDisabled.selector, disabledChain));
119+
executorQuoter.requestQuote(
120+
disabledChain,
121+
DST_ADDR,
122+
UPDATER,
123+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
124+
RelayInstructions.encodeGas(250000, 0)
125+
);
126+
}
127+
128+
function test_requestQuote_unsupportedInstruction() public {
129+
// Type 0xFF is not a valid instruction type
130+
bytes memory badInstruction = hex"ff";
131+
vm.expectRevert(abi.encodeWithSelector(ExecutorQuoter.UnsupportedInstruction.selector, 0xff));
132+
executorQuoter.requestQuote(
133+
DST_CHAIN,
134+
DST_ADDR,
135+
UPDATER,
136+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
137+
badInstruction
138+
);
139+
}
140+
141+
function test_requestQuote_moreThanOneDropOff() public {
142+
// Two Type 2 (drop-off) instructions
143+
bytes memory twoDropOffs = abi.encodePacked(
144+
RelayInstructions.encodeGasDropOffInstructions(1000, bytes32(uint256(uint160(address(this))))),
145+
RelayInstructions.encodeGasDropOffInstructions(2000, bytes32(uint256(uint160(address(this)))))
146+
);
147+
vm.expectRevert(abi.encodeWithSelector(ExecutorQuoter.MoreThanOneDropOff.selector));
148+
executorQuoter.requestQuote(
149+
DST_CHAIN,
150+
DST_ADDR,
151+
UPDATER,
152+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
153+
twoDropOffs
154+
);
155+
}
156+
157+
// Edge case tests for large values
158+
//
159+
// Note: The quote calculation uses uint128 * uint64 which fits in uint256 (2^192 < 2^256),
160+
// so overflow is not possible with the current type constraints. These tests verify
161+
// the contract handles extreme values correctly without reverting.
162+
//
163+
// Run with `forge test -vv` to see the actual quote values logged.
164+
165+
/// @notice Test that max uint128 gas limit is handled without overflow.
166+
/// uint128 * uint64 = 2^192 max, which fits in uint256.
167+
function test_requestQuote_maxGasLimit() public {
168+
uint128 maxGas = type(uint128).max;
169+
bytes memory relayInstructions = RelayInstructions.encodeGas(maxGas, 0);
170+
171+
uint256 quote = executorQuoter.requestQuote(
172+
DST_CHAIN,
173+
DST_ADDR,
174+
UPDATER,
175+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
176+
relayInstructions
177+
);
178+
179+
// Log the actual values for inspection
180+
emit log_named_uint("maxGasLimit input", maxGas);
181+
emit log_named_uint("quote result", quote);
182+
emit log_named_uint("quote in ETH (approx)", quote / 1e18);
183+
184+
assertGt(quote, maxGas, "Quote should be non-zero");
185+
}
186+
187+
/// @notice Test that max uint128 msgValue is handled without overflow.
188+
function test_requestQuote_maxMsgValue() public {
189+
uint128 maxMsgValue = type(uint128).max;
190+
bytes memory relayInstructions = RelayInstructions.encodeGas(250000, maxMsgValue);
191+
192+
uint256 quote = executorQuoter.requestQuote(
193+
DST_CHAIN,
194+
DST_ADDR,
195+
UPDATER,
196+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
197+
relayInstructions
198+
);
199+
200+
assertGt(quote, maxMsgValue, "Quote should be greater than u128.max");
201+
}
202+
203+
/// @notice Test with extreme price values - all max uint64.
204+
/// When srcPrice == dstPrice, conversion ratio is ~1, so no overflow.
205+
function test_requestQuote_extremePrices() public {
206+
ExecutorQuoter.Update[] memory extremeUpdates = new ExecutorQuoter.Update[](1);
207+
extremeUpdates[0].chainId = DST_CHAIN;
208+
extremeUpdates[0].update = packUint64(
209+
type(uint64).max, // baseFee
210+
type(uint64).max, // dstGasPrice
211+
type(uint64).max, // srcPrice
212+
type(uint64).max // dstPrice
213+
);
214+
executorQuoter.quoteUpdate(extremeUpdates);
215+
216+
bytes memory relayInstructions = RelayInstructions.encodeGas(250000, 0);
217+
218+
uint256 quote = executorQuoter.requestQuote(
219+
DST_CHAIN,
220+
DST_ADDR,
221+
UPDATER,
222+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
223+
relayInstructions
224+
);
225+
226+
emit log_named_uint("baseFee", type(uint64).max);
227+
emit log_named_uint("dstGasPrice", type(uint64).max);
228+
emit log_named_uint("srcPrice", type(uint64).max);
229+
emit log_named_uint("dstPrice", type(uint64).max);
230+
emit log_named_uint("gasLimit", 250000);
231+
emit log_named_uint("quote result", quote);
232+
emit log_named_uint("quote in ETH (approx)", quote / 1e18);
233+
234+
assertGt(quote, type(uint64).max, "Quote should be greater than a max price");
235+
}
236+
237+
/// @notice Test quote with max msgValue AND max dropoff to verify they sum correctly.
238+
function test_requestQuote_maxMsgValueAndDropoff() public {
239+
uint128 maxGas = type(uint128).max;
240+
uint128 maxMsgValue = type(uint128).max;
241+
uint128 maxDropoff = type(uint128).max;
242+
243+
// Combine gas instruction (with max gas and max msgValue) + dropoff instruction
244+
bytes memory relayInstructions = abi.encodePacked(
245+
RelayInstructions.encodeGas(maxGas, maxMsgValue),
246+
RelayInstructions.encodeGasDropOffInstructions(maxDropoff, bytes32(uint256(uint160(address(this)))))
247+
);
248+
249+
uint256 quote = executorQuoter.requestQuote(
250+
DST_CHAIN,
251+
DST_ADDR,
252+
UPDATER,
253+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
254+
relayInstructions
255+
);
256+
257+
emit log_named_uint("maxGas input", maxGas);
258+
emit log_named_uint("maxMsgValue input", maxMsgValue);
259+
emit log_named_uint("maxDropoff input", maxDropoff);
260+
emit log_named_uint("quote result", quote);
261+
emit log_named_uint("quote in ETH (approx)", quote / 1e18);
262+
emit log_named_uint("type(uint256).max", type(uint256).max);
263+
emit log_named_uint("type(uint128).max * 3", uint256(type(uint128).max) * 3);
264+
265+
// Verify quote is not capped at uint256 max (i.e., it's a real sum)
266+
assertGt(quote, uint256(type(uint128).max), "Quote should be greater than a single max uint128");
267+
}
268+
269+
/// @notice Compare individual quotes vs combined to verify addition.
270+
function test_requestQuote_verifyAddition() public {
271+
uint128 gasLimit = 250000;
272+
uint128 msgValue = 1e18; // 1 ETH worth
273+
uint128 dropoff = 2e18; // 2 ETH worth
274+
275+
// Get quote with just gas
276+
bytes memory gasOnly = RelayInstructions.encodeGas(gasLimit, 0);
277+
uint256 quoteGasOnly = executorQuoter.requestQuote(
278+
DST_CHAIN,
279+
DST_ADDR,
280+
UPDATER,
281+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
282+
gasOnly
283+
);
284+
285+
// Get quote with gas + msgValue
286+
bytes memory gasAndMsg = RelayInstructions.encodeGas(gasLimit, msgValue);
287+
uint256 quoteGasAndMsg = executorQuoter.requestQuote(
288+
DST_CHAIN,
289+
DST_ADDR,
290+
UPDATER,
291+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
292+
gasAndMsg
293+
);
294+
295+
// Get quote with gas + msgValue + dropoff
296+
bytes memory all = abi.encodePacked(
297+
RelayInstructions.encodeGas(gasLimit, msgValue),
298+
RelayInstructions.encodeGasDropOffInstructions(dropoff, bytes32(uint256(uint160(address(this)))))
299+
);
300+
uint256 quoteAll = executorQuoter.requestQuote(
301+
DST_CHAIN,
302+
DST_ADDR,
303+
UPDATER,
304+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
305+
all
306+
);
307+
308+
// Verify monotonic increase
309+
assertGt(quoteGasAndMsg, quoteGasOnly, "Adding msgValue should increase quote");
310+
assertGt(quoteAll, quoteGasAndMsg, "Adding dropoff should increase quote");
311+
}
312+
313+
/// @notice Test quote calculation with zero prices (division by zero protection).
314+
function test_requestQuote_zeroPrices() public {
315+
// Set up a quote with zero srcPrice (would cause division by zero)
316+
ExecutorQuoter.Update[] memory zeroUpdates = new ExecutorQuoter.Update[](1);
317+
zeroUpdates[0].chainId = DST_CHAIN;
318+
zeroUpdates[0].update = packUint64(
319+
27971, // baseFee
320+
100000000, // dstGasPrice
321+
0, // srcPrice = 0 (division by zero)
322+
35751300000000 // dstPrice
323+
);
324+
executorQuoter.quoteUpdate(zeroUpdates);
325+
326+
bytes memory relayInstructions = RelayInstructions.encodeGas(250000, 0);
327+
328+
// Should revert on division by zero
329+
vm.expectRevert();
330+
executorQuoter.requestQuote(
331+
DST_CHAIN,
332+
DST_ADDR,
333+
UPDATER,
334+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
335+
relayInstructions
336+
);
337+
}
338+
339+
/// @notice Test with zero gas limit - should return just base fee.
340+
function test_requestQuote_zeroGasLimit() public view {
341+
bytes memory relayInstructions = RelayInstructions.encodeGas(0, 0);
342+
343+
uint256 quote = executorQuoter.requestQuote(
344+
DST_CHAIN,
345+
DST_ADDR,
346+
UPDATER,
347+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
348+
relayInstructions
349+
);
350+
351+
// With zero gas, quote should be just the normalized base fee
352+
// baseFee = 27971 (in 10^10 decimals), normalized to 18 decimals
353+
// 27971 * 10^(18-10) = 27971 * 10^8 = 2797100000000
354+
require(quote >= 2797100000000, "Quote should include base fee");
355+
}
356+
357+
/// @notice Test normalize function with from > to (division path).
358+
/// This exercises the branch at line 107 where decimals are reduced.
359+
function test_requestQuote_normalizeFromGreaterThanTo() public {
360+
// Create a new quoter with SRC_TOKEN_DECIMALS = 8 (less than DECIMAL_RESOLUTION = 18)
361+
// This will hit the from > to branch in normalize() at lines 183 and 187
362+
ExecutorQuoter lowDecimalQuoter = new ExecutorQuoter(UPDATER, UPDATER, 8, bytes32(uint256(uint160(UPDATER))));
363+
364+
// Set up chain info
365+
ExecutorQuoter.Update[] memory chainInfo = new ExecutorQuoter.Update[](1);
366+
chainInfo[0].chainId = DST_CHAIN;
367+
chainInfo[0].update = CHAIN_INFO_UPDATE_PACKED;
368+
lowDecimalQuoter.chainInfoUpdate(chainInfo);
369+
370+
// Set up quote with non-zero values
371+
ExecutorQuoter.Update[] memory quoteUpdates = new ExecutorQuoter.Update[](1);
372+
quoteUpdates[0].chainId = DST_CHAIN;
373+
quoteUpdates[0].update = packUint64(27971, 100000000, 35751300000000, 35751300000000);
374+
lowDecimalQuoter.quoteUpdate(quoteUpdates);
375+
376+
bytes memory relayInstructions = RelayInstructions.encodeGas(250000, 1e18);
377+
378+
uint256 quote = lowDecimalQuoter.requestQuote(
379+
DST_CHAIN,
380+
DST_ADDR,
381+
UPDATER,
382+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
383+
relayInstructions
384+
);
385+
386+
// Quote should be non-zero and scaled down due to 8 decimals
387+
assertGt(quote, 0, "Quote should be non-zero");
388+
}
389+
390+
/// @notice Test normalize function with from == to (identity path).
391+
/// This exercises the branch at line 111 where no scaling is needed.
392+
function test_requestQuote_normalizeFromEqualsTo() public {
393+
// Create a quoter with SRC_TOKEN_DECIMALS = 10 (equals QUOTE_DECIMALS)
394+
// This will hit the from == to branch at line 174: normalize(baseFee, 10, 10)
395+
ExecutorQuoter equalDecimalQuoter = new ExecutorQuoter(UPDATER, UPDATER, 10, bytes32(uint256(uint160(UPDATER))));
396+
397+
// Set up chain info with gasPriceDecimals = 18, nativeDecimals = 18
398+
ExecutorQuoter.Update[] memory chainInfo = new ExecutorQuoter.Update[](1);
399+
chainInfo[0].chainId = DST_CHAIN;
400+
chainInfo[0].update = CHAIN_INFO_UPDATE_PACKED;
401+
equalDecimalQuoter.chainInfoUpdate(chainInfo);
402+
403+
// Set up quote
404+
ExecutorQuoter.Update[] memory quoteUpdates = new ExecutorQuoter.Update[](1);
405+
quoteUpdates[0].chainId = DST_CHAIN;
406+
quoteUpdates[0].update = packUint64(27971, 100000000, 35751300000000, 35751300000000);
407+
equalDecimalQuoter.quoteUpdate(quoteUpdates);
408+
409+
// Use zero gas and zero msgValue to isolate the baseFee normalization
410+
bytes memory relayInstructions = RelayInstructions.encodeGas(0, 0);
411+
412+
uint256 quote = equalDecimalQuoter.requestQuote(
413+
DST_CHAIN,
414+
DST_ADDR,
415+
UPDATER,
416+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
417+
relayInstructions
418+
);
419+
420+
// With SRC_TOKEN_DECIMALS = 10 = QUOTE_DECIMALS, baseFee should pass through unchanged
421+
// baseFee = 27971
422+
assertEq(quote, 27971, "Quote should equal baseFee when decimals match");
423+
}
424+
425+
/// @notice Test requestExecutionQuote reverts when chain is disabled.
426+
function test_requestExecutionQuote_chainDisabled() public {
427+
uint16 disabledChain = 65000;
428+
vm.expectRevert(abi.encodeWithSelector(ExecutorQuoter.ChainDisabled.selector, disabledChain));
429+
executorQuoter.requestExecutionQuote(
430+
disabledChain,
431+
DST_ADDR,
432+
UPDATER,
433+
ExecutorMessages.makeVAAv1Request(10002, bytes32(uint256(uint160(address(this)))), 1),
434+
RelayInstructions.encodeGas(250000, 0)
435+
);
436+
}
99437
}

0 commit comments

Comments
 (0)