Skip to content

Commit 5e041e6

Browse files
authored
feeQuoter: Add unit test coverage for different extraArgs types (#340)
* feeQuoter: Add unit test coverage for different extraArgs types * yarn fmt
1 parent 1e640d0 commit 5e041e6

File tree

6 files changed

+252
-36
lines changed

6 files changed

+252
-36
lines changed

contracts/contracts/ccip/onramp/contract.tolk

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -311,18 +311,12 @@ fun applyAllowlistUpdates(mutate st: OnRamp_Storage, updates: cell) {
311311
}
312312
}
313313

314-
// calculateMetadataHash/calculateMessageHash
315-
316314
get fun destChainSelectors(): tuple? {
317315
val st = lazy OnRamp_Storage.load();
318316
var d = st.destChainConfigs;
319317
return keysLispList(d)!;
320318
}
321319

322-
fun withdrawFeeTokens() {
323-
324-
}
325-
326320
get fun typeAndVersion(): (slice, slice) {
327321
return ("com.chainlink.ton.ccip.OnRamp", CONTRACT_VERSION);
328322
}

contracts/tests/ccip/feequoter/FeeQuoter.getValidatedFee.spec.ts

Lines changed: 191 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import { Blockchain } from '@ton/sandbox'
55

66
import { FeeQuoterSetup, FeeQuoterFeeSetup, Token } from './FeeQuoterSetup'
77
import * as feeQuoter from '../../../wrappers/ccip/FeeQuoter'
8+
import { ExtraArgs } from '../../../wrappers/ccip/Router'
89
import * as sendExec from '../../../wrappers/ccip/CCIPSendExecutor'
910
import * as rt from '../../../wrappers/ccip/Router'
1011
import { asSnakeBytes, asSnakeData, ZERO_ADDRESS } from '../../../src/utils'
1112
import { skip } from 'node:test'
1213
import { verifyBodyMessage } from '../CCIPRouter.spec'
14+
import { create } from 'domain'
1315

1416
describe('FeeQuoter GetValidatedFee', () => {
1517
let setup: FeeQuoterFeeSetup
@@ -42,7 +44,7 @@ describe('FeeQuoter GetValidatedFee', () => {
4244
premiumMultiplierWeiPerEth
4345
const calldataLen = BigInt(message.data.beginParse().remainingBits / 8)
4446
const dataAvailabilityFeeUSD = await setup.bind.feeQuoter.getDataAvailabilityCost(
45-
FeeQuoterSetup.DEST_CHAIN_SELECTOR,
47+
FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
4648
FeeQuoterSetup.USD_PER_DATA_AVAILABILITY_GAS,
4749
calldataLen,
4850
BigInt(message.tokenAmounts.length),
@@ -57,7 +59,7 @@ describe('FeeQuoter GetValidatedFee', () => {
5759

5860
it('should handle zero data availability multiplier', async () => {
5961
const destChainConfig = await setup.bind.feeQuoter.getDestChainConfig(
60-
FeeQuoterSetup.DEST_CHAIN_SELECTOR,
62+
FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
6163
)
6264
// Update dest chain config to set data availability multiplier to 0
6365
{
@@ -67,7 +69,7 @@ describe('FeeQuoter GetValidatedFee', () => {
6769
value: toNano('1'),
6870
updates: [
6971
{
70-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
72+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
7173
config: {
7274
...destChainConfig,
7375
destDataAvailabilityMultiplierBps: 0,
@@ -110,7 +112,7 @@ describe('FeeQuoter GetValidatedFee', () => {
110112

111113
for (const token of testTokens) {
112114
const message: rt.CCIPSend = {
113-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
115+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
114116
receiver: FeeQuoterSetup.DEST_ADDRESS,
115117
data: asSnakeBytes(Buffer.alloc(customDataSize)),
116118
tokenAmounts: [],
@@ -150,7 +152,7 @@ describe('FeeQuoter GetValidatedFee', () => {
150152
premiumMultiplierWeiPerEth
151153

152154
const dataAvailabilityFeeUSD = await setup.bind.feeQuoter.getDataAvailabilityCost(
153-
FeeQuoterSetup.DEST_CHAIN_SELECTOR,
155+
FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
154156
FeeQuoterSetup.USD_PER_DATA_AVAILABILITY_GAS,
155157
calldataLen,
156158
BigInt(message.tokenAmounts.length),
@@ -166,7 +168,7 @@ describe('FeeQuoter GetValidatedFee', () => {
166168

167169
it('should allow out of order execution when not enforced', async () => {
168170
const message: rt.CCIPSend = {
169-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
171+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
170172
receiver: FeeQuoterSetup.DEST_ADDRESS,
171173
data: beginCell().endCell(),
172174
tokenAmounts: [],
@@ -188,7 +190,7 @@ describe('FeeQuoter GetValidatedFee', () => {
188190

189191
it('should allow fail when allow out of order execution is false', async () => {
190192
const message: rt.CCIPSend = {
191-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
193+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
192194
receiver: FeeQuoterSetup.DEST_ADDRESS,
193195
data: beginCell().endCell(),
194196
tokenAmounts: [],
@@ -209,7 +211,7 @@ describe('FeeQuoter GetValidatedFee', () => {
209211
})
210212

211213
it('should revert when destination chain not enabled', async () => {
212-
const invalidChainSelector = FeeQuoterSetup.DEST_CHAIN_SELECTOR + 1n
214+
const invalidChainSelector = FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM + 1n
213215
const message: rt.CCIPSend = {
214216
destChainSelector: invalidChainSelector,
215217
receiver: FeeQuoterSetup.DEST_ADDRESS,
@@ -260,7 +262,7 @@ describe('FeeQuoter GetValidatedFee', () => {
260262

261263
it('should revert when message too large', async () => {
262264
const message: rt.CCIPSend = {
263-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
265+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
264266
receiver: FeeQuoterSetup.DEST_ADDRESS,
265267
data: asSnakeBytes(Buffer.alloc(FeeQuoterSetup.MAX_DATA_SIZE + 1)),
266268
tokenAmounts: [],
@@ -310,7 +312,7 @@ describe('FeeQuoter GetValidatedFee', () => {
310312
const tooManyTokens = [FeeQuoterSetup.SOURCE_FEE_TOKEN] // We don't support token transfers in TON yet
311313

312314
const message: rt.CCIPSend = {
313-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
315+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
314316
receiver: FeeQuoterSetup.DEST_ADDRESS,
315317
data: beginCell().endCell(),
316318
tokenAmounts: tooManyTokens.map((token) => ({
@@ -361,7 +363,7 @@ describe('FeeQuoter GetValidatedFee', () => {
361363

362364
it('should revert when gas limit too high', async () => {
363365
const message: rt.CCIPSend = {
364-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
366+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
365367
receiver: FeeQuoterSetup.DEST_ADDRESS,
366368
data: beginCell().endCell(),
367369
tokenAmounts: [],
@@ -411,7 +413,7 @@ describe('FeeQuoter GetValidatedFee', () => {
411413
const notAFeeToken = FeeQuoterSetup.CUSTOM_TOKEN.token
412414

413415
const message: rt.CCIPSend = {
414-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
416+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
415417
receiver: FeeQuoterSetup.DEST_ADDRESS,
416418
data: beginCell().endCell(),
417419
tokenAmounts: [],
@@ -491,7 +493,7 @@ describe('FeeQuoter GetValidatedFee', () => {
491493
overrides.dataAvailabilityGasPrice !== undefined
492494
? [
493495
{
494-
chainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
496+
chainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
495497
executionGasPrice: overrides.executionGasPrice ?? FeeQuoterSetup.USD_PER_GAS,
496498
dataAvailabilityGasPrice:
497499
overrides.dataAvailabilityGasPrice ??
@@ -548,7 +550,7 @@ describe('FeeQuoter GetValidatedFee', () => {
548550
value: toNano('1'),
549551
updates: [
550552
{
551-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
553+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
552554
config: {
553555
...FeeQuoterSetup.destChainConfig,
554556
...destConfigOverrides,
@@ -605,7 +607,7 @@ describe('FeeQuoter GetValidatedFee', () => {
605607
const gasLimit = overrides.gasLimit ?? BigInt(FeeQuoterSetup.MAX_GAS_LIMIT)
606608

607609
const message: rt.CCIPSend = {
608-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
610+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
609611
receiver: FeeQuoterSetup.DEST_ADDRESS,
610612
data: asSnakeBytes(Buffer.alloc(dataSize)),
611613
tokenAmounts: [],
@@ -635,7 +637,7 @@ describe('FeeQuoter GetValidatedFee', () => {
635637
const gasLimit = overrides.gasLimit ?? BigInt(FeeQuoterSetup.MAX_GAS_LIMIT)
636638

637639
const message: rt.CCIPSend = {
638-
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
640+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
639641
receiver: FeeQuoterSetup.DEST_ADDRESS,
640642
data: asSnakeBytes(Buffer.alloc(dataSize)),
641643
tokenAmounts: [],
@@ -815,4 +817,177 @@ describe('FeeQuoter GetValidatedFee', () => {
815817
)
816818
})
817819
})
820+
821+
// extraArgs validation
822+
const validEVMExtraArgs: ExtraArgs = {
823+
kind: 'generic-v2',
824+
gasLimit: BigInt(FeeQuoterSetup.GAS_LIMIT),
825+
allowOutOfOrderExecution: true,
826+
}
827+
describe('EVMExtraArgs', () => {
828+
it('valid extra args', async () => {
829+
const message: rt.CCIPSend = {
830+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
831+
receiver: FeeQuoterSetup.DEST_ADDRESS,
832+
data: beginCell().endCell(),
833+
tokenAmounts: [],
834+
feeToken: FeeQuoterSetup.NATIVE_TON.token,
835+
extraArgs: rt.builder.data.extraArgs.encode(validEVMExtraArgs).endCell(),
836+
}
837+
838+
const result = await setup.getValidatedFee(message, beginCell().endCell())
839+
expect(result.fee).toBeGreaterThan(0n)
840+
})
841+
842+
// NOTE: GasLimitTooHigh already tested above as "should revert when gas limit too high"
843+
})
844+
845+
describe('SVMExtraArgs', () => {
846+
const validSVMExtraArgs: ExtraArgs = {
847+
kind: 'svm-v1',
848+
computeUnits: BigInt(FeeQuoterSetup.GAS_LIMIT),
849+
accountIsWritableBitMap: 0n,
850+
allowOutOfOrderExecution: true,
851+
tokenReceiver: Buffer.alloc(32),
852+
accounts: [Buffer.alloc(32)],
853+
}
854+
855+
it('valid extra args', async () => {
856+
const message: rt.CCIPSend = {
857+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_SVM,
858+
receiver: FeeQuoterSetup.DEST_ADDRESS,
859+
data: beginCell().endCell(),
860+
tokenAmounts: [],
861+
feeToken: FeeQuoterSetup.NATIVE_TON.token,
862+
extraArgs: rt.builder.data.extraArgs.encode(validSVMExtraArgs).endCell(),
863+
}
864+
865+
const result = await setup.getValidatedFee(message, beginCell().endCell())
866+
expect(result.fee).toBeGreaterThan(0n)
867+
})
868+
869+
it('reverts with empty extra args', async () => {
870+
const message: rt.CCIPSend = {
871+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_SVM,
872+
receiver: FeeQuoterSetup.DEST_ADDRESS,
873+
data: beginCell().endCell(),
874+
tokenAmounts: [],
875+
feeToken: FeeQuoterSetup.NATIVE_TON.token,
876+
extraArgs: beginCell().endCell(),
877+
}
878+
const result = await setup.assertGetFeeValidationError(
879+
message,
880+
feeQuoter.FeeQuoterError.InvalidExtraArgsData,
881+
)
882+
})
883+
884+
it('reverts with invalid tag', async () => {
885+
const message: rt.CCIPSend = {
886+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_SVM,
887+
receiver: FeeQuoterSetup.DEST_ADDRESS,
888+
data: beginCell().endCell(),
889+
tokenAmounts: [],
890+
feeToken: FeeQuoterSetup.NATIVE_TON.token,
891+
extraArgs: rt.builder.data.extraArgs.encode(validEVMExtraArgs).endCell(),
892+
}
893+
const result = await setup.assertGetFeeValidationError(
894+
message,
895+
feeQuoter.FeeQuoterError.InvalidExtraArgsData,
896+
)
897+
})
898+
899+
it('reverts if out of order execution is false', async () => {
900+
const message: rt.CCIPSend = {
901+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_SVM,
902+
receiver: FeeQuoterSetup.DEST_ADDRESS,
903+
data: beginCell().endCell(),
904+
tokenAmounts: [],
905+
feeToken: FeeQuoterSetup.NATIVE_TON.token,
906+
extraArgs: rt.builder.data.extraArgs
907+
.encode({
908+
...validSVMExtraArgs,
909+
allowOutOfOrderExecution: false,
910+
})
911+
.endCell(),
912+
}
913+
const result = await setup.assertGetFeeValidationError(
914+
message,
915+
feeQuoter.FeeQuoterError.ExtraArgOutOfOrderExecutionMustBeTrue,
916+
)
917+
})
918+
})
919+
920+
describe('SuiExtraArgs', () => {
921+
const validSVMExtraArgs: ExtraArgs = {
922+
kind: 'sui-v1',
923+
gasLimit: BigInt(FeeQuoterSetup.GAS_LIMIT),
924+
allowOutOfOrderExecution: true,
925+
tokenReceiver: Buffer.alloc(32),
926+
receiverObjectIds: [Buffer.alloc(32)],
927+
}
928+
929+
it('valid extra args', async () => {
930+
const message: rt.CCIPSend = {
931+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_SUI,
932+
receiver: FeeQuoterSetup.DEST_ADDRESS,
933+
data: beginCell().endCell(),
934+
tokenAmounts: [],
935+
feeToken: FeeQuoterSetup.NATIVE_TON.token,
936+
extraArgs: rt.builder.data.extraArgs.encode(validSVMExtraArgs).endCell(),
937+
}
938+
939+
const result = await setup.getValidatedFee(message, beginCell().endCell())
940+
expect(result.fee).toBeGreaterThan(0n)
941+
})
942+
943+
it('reverts with empty extra args', async () => {
944+
const message: rt.CCIPSend = {
945+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_SVM,
946+
receiver: FeeQuoterSetup.DEST_ADDRESS,
947+
data: beginCell().endCell(),
948+
tokenAmounts: [],
949+
feeToken: FeeQuoterSetup.NATIVE_TON.token,
950+
extraArgs: beginCell().endCell(),
951+
}
952+
const result = await setup.assertGetFeeValidationError(
953+
message,
954+
feeQuoter.FeeQuoterError.InvalidExtraArgsData,
955+
)
956+
})
957+
958+
it('reverts with invalid tag', async () => {
959+
const message: rt.CCIPSend = {
960+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_SUI,
961+
receiver: FeeQuoterSetup.DEST_ADDRESS,
962+
data: beginCell().endCell(),
963+
tokenAmounts: [],
964+
feeToken: FeeQuoterSetup.NATIVE_TON.token,
965+
extraArgs: rt.builder.data.extraArgs.encode(validEVMExtraArgs).endCell(),
966+
}
967+
const result = await setup.assertGetFeeValidationError(
968+
message,
969+
feeQuoter.FeeQuoterError.InvalidExtraArgsData,
970+
)
971+
})
972+
973+
it('reverts if out of order execution is false', async () => {
974+
const message: rt.CCIPSend = {
975+
destChainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_SUI,
976+
receiver: FeeQuoterSetup.DEST_ADDRESS,
977+
data: beginCell().endCell(),
978+
tokenAmounts: [],
979+
feeToken: FeeQuoterSetup.NATIVE_TON.token,
980+
extraArgs: rt.builder.data.extraArgs
981+
.encode({
982+
...validSVMExtraArgs,
983+
allowOutOfOrderExecution: false,
984+
})
985+
.endCell(),
986+
}
987+
const result = await setup.assertGetFeeValidationError(
988+
message,
989+
feeQuoter.FeeQuoterError.ExtraArgOutOfOrderExecutionMustBeTrue,
990+
)
991+
})
992+
})
818993
})

contracts/tests/ccip/feequoter/FeeQuoter.updatePrices.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ describe('FeeQuoter UpdatePrices', () => {
117117

118118
it('should update only gas price', async () => {
119119
const gasPriceUpdate: feeQuoter.GasPriceUpdate = {
120-
chainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
120+
chainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
121121
executionGasPrice: 2000000000000000000000n, // 2000e18
122122
dataAvailabilityGasPrice: 1000000000000000000n, // 1e18
123123
}
@@ -140,7 +140,7 @@ describe('FeeQuoter UpdatePrices', () => {
140140

141141
// Verify the gas price was updated
142142
const gasPrice = await setup.bind.feeQuoter.getDestinationChainGasPrice(
143-
FeeQuoterSetup.DEST_CHAIN_SELECTOR,
143+
FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
144144
)
145145
expect(gasPrice.value.executionGasPrice).toEqual(gasPriceUpdate.executionGasPrice)
146146
expect(gasPrice.value.dataAvailabilityGasPrice).toEqual(gasPriceUpdate.dataAvailabilityGasPrice)
@@ -155,7 +155,7 @@ describe('FeeQuoter UpdatePrices', () => {
155155

156156
const gasPriceUpdates: feeQuoter.GasPriceUpdate[] = [
157157
{
158-
chainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR,
158+
chainSelector: FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
159159
executionGasPrice: 2000000n, // 2e6
160160
dataAvailabilityGasPrice: 1000000n, // 1e6
161161
},
@@ -196,7 +196,7 @@ describe('FeeQuoter UpdatePrices', () => {
196196
// Note: For gas prices, we can only test the first one since the contract
197197
// only supports one destination chain config in our simplified setup
198198
const gasPrice = await setup.bind.feeQuoter.getDestinationChainGasPrice(
199-
FeeQuoterSetup.DEST_CHAIN_SELECTOR,
199+
FeeQuoterSetup.DEST_CHAIN_SELECTOR_EVM,
200200
)
201201
expect(gasPrice.value.executionGasPrice).toEqual(gasPriceUpdates[0].executionGasPrice)
202202
expect(gasPrice.value.dataAvailabilityGasPrice).toEqual(

0 commit comments

Comments
 (0)