@@ -6,7 +6,7 @@ const MAIN_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000001";
66const SENDER_ADDRESS = "0x0000000000000000000000000000000000000002" ;
77const RECIPIENT_ADDRESS = "0x0000000000000000000000000000000000000003" ;
88
9- const ADD_TRANSACTION_ABI_V5 = [
9+ const ADD_TRANSACTION_ABI_V6 = [
1010 {
1111 type : "function" ,
1212 name : "addTransaction" ,
@@ -17,12 +17,13 @@ const ADD_TRANSACTION_ABI_V5 = [
1717 { name : "_numOfInitialValidators" , type : "uint256" } ,
1818 { name : "_maxRotations" , type : "uint256" } ,
1919 { name : "_txData" , type : "bytes" } ,
20+ { name : "_validUntil" , type : "uint256" } ,
2021 ] ,
2122 outputs : [ ] ,
2223 } ,
2324] as const ;
2425
25- const ADD_TRANSACTION_ABI_V6 = [
26+ const ADD_TRANSACTION_ABI_V7 = [
2627 {
2728 type : "function" ,
2829 name : "addTransaction" ,
@@ -33,24 +34,53 @@ const ADD_TRANSACTION_ABI_V6 = [
3334 { name : "_numOfInitialValidators" , type : "uint256" } ,
3435 { name : "_maxRotations" , type : "uint256" } ,
3536 { name : "_txData" , type : "bytes" } ,
37+ {
38+ name : "_feesDistribution" ,
39+ type : "tuple" ,
40+ components : [
41+ { name : "leaderTimeoutFee" , type : "uint256" } ,
42+ { name : "validatorsTimeoutFee" , type : "uint256" } ,
43+ { name : "appealRounds" , type : "uint256" } ,
44+ { name : "rollupStorageFee" , type : "uint256" } ,
45+ { name : "rollupGenVMFee" , type : "uint256" } ,
46+ { name : "totalMessageFees" , type : "uint256" } ,
47+ { name : "rotations" , type : "uint256[]" } ,
48+ ] ,
49+ } ,
3650 { name : "_validUntil" , type : "uint256" } ,
3751 ] ,
3852 outputs : [ ] ,
3953 } ,
4054] as const ;
4155
42- const selectorForV5 = encodeFunctionData ( {
43- abi : ADD_TRANSACTION_ABI_V5 as any ,
44- functionName : "addTransaction" ,
45- args : [ SENDER_ADDRESS , RECIPIENT_ADDRESS , 5 , 3 , "0x" ] ,
46- } ) . slice ( 0 , 10 ) ;
47-
4856const selectorForV6 = encodeFunctionData ( {
4957 abi : ADD_TRANSACTION_ABI_V6 as any ,
5058 functionName : "addTransaction" ,
5159 args : [ SENDER_ADDRESS , RECIPIENT_ADDRESS , 5 , 3 , "0x" , 0n ] ,
5260} ) . slice ( 0 , 10 ) ;
5361
62+ const selectorForV7 = encodeFunctionData ( {
63+ abi : ADD_TRANSACTION_ABI_V7 as any ,
64+ functionName : "addTransaction" ,
65+ args : [
66+ SENDER_ADDRESS ,
67+ RECIPIENT_ADDRESS ,
68+ 5 ,
69+ 3 ,
70+ "0x" ,
71+ {
72+ leaderTimeoutFee : 0n ,
73+ validatorsTimeoutFee : 0n ,
74+ appealRounds : 0n ,
75+ rollupStorageFee : 0n ,
76+ rollupGenVMFee : 0n ,
77+ totalMessageFees : 0n ,
78+ rotations : [ ] ,
79+ } ,
80+ 0n ,
81+ ] ,
82+ } ) . slice ( 0 , 10 ) ;
83+
5484const setupWriteContractHarness = ( {
5585 initialAbi,
5686 signTransactionMock,
@@ -94,23 +124,6 @@ const setupWriteContractHarness = ({
94124} ;
95125
96126describe ( "contractActions addTransaction ABI compatibility" , ( ) => {
97- it ( "encodes addTransaction with 5 args when ABI has 5 inputs" , async ( ) => {
98- const { actions, estimateTransactionGas} = setupWriteContractHarness ( {
99- initialAbi : ADD_TRANSACTION_ABI_V5 ,
100- } ) ;
101-
102- await expect (
103- actions . writeContract ( {
104- address : RECIPIENT_ADDRESS ,
105- functionName : "ping" ,
106- value : 0n ,
107- } ) ,
108- ) . rejects . toThrow ( "stop_after_encoding" ) ;
109-
110- const encodedData = estimateTransactionGas . mock . calls [ 0 ] [ 0 ] . data as `0x${string } `;
111- expect ( encodedData . slice ( 0 , 10 ) ) . toBe ( selectorForV5 ) ;
112- } ) ;
113-
114127 it ( "encodes addTransaction with 6 args when ABI has 6 inputs" , async ( ) => {
115128 const { actions, estimateTransactionGas} = setupWriteContractHarness ( {
116129 initialAbi : ADD_TRANSACTION_ABI_V6 ,
@@ -128,14 +141,9 @@ describe("contractActions addTransaction ABI compatibility", () => {
128141 expect ( encodedData . slice ( 0 , 10 ) ) . toBe ( selectorForV6 ) ;
129142 } ) ;
130143
131- it ( "retries with v6 signature when v5 signature fails with ABI mismatch" , async ( ) => {
132- const signTransaction = vi
133- . fn ( )
134- . mockRejectedValueOnce ( new Error ( "Invalid pointer in tuple at location 128 in payload" ) )
135- . mockRejectedValueOnce ( new Error ( "stop_after_retry" ) ) ;
144+ it ( "encodes addTransaction with 7 args when ABI has WithFees variant" , async ( ) => {
136145 const { actions, estimateTransactionGas} = setupWriteContractHarness ( {
137- initialAbi : ADD_TRANSACTION_ABI_V5 ,
138- signTransactionMock : signTransaction ,
146+ initialAbi : ADD_TRANSACTION_ABI_V7 ,
139147 } ) ;
140148
141149 await expect (
@@ -144,52 +152,20 @@ describe("contractActions addTransaction ABI compatibility", () => {
144152 functionName : "ping" ,
145153 value : 0n ,
146154 } ) ,
147- ) . rejects . toThrow ( "stop_after_retry " ) ;
155+ ) . rejects . toThrow ( "stop_after_encoding " ) ;
148156
149- expect ( signTransaction ) . toHaveBeenCalledTimes ( 2 ) ;
150- const firstEncodedData = signTransaction . mock . calls [ 0 ] [ 0 ] . data as `0x${string } `;
151- const secondEncodedData = signTransaction . mock . calls [ 1 ] [ 0 ] . data as `0x${string } `;
152- expect ( firstEncodedData . slice ( 0 , 10 ) ) . toBe ( selectorForV5 ) ;
153- expect ( secondEncodedData . slice ( 0 , 10 ) ) . toBe ( selectorForV6 ) ;
154- expect ( estimateTransactionGas ) . toHaveBeenCalledTimes ( 2 ) ;
157+ const encodedData = estimateTransactionGas . mock . calls [ 0 ] [ 0 ] . data as `0x${string } `;
158+ expect ( encodedData . slice ( 0 , 10 ) ) . toBe ( selectorForV7 ) ;
155159 } ) ;
156160
157- it ( "retries when ABI mismatch details are on error.details (viem InternalRpcError shape)" , async ( ) => {
158- const signTransaction = vi
159- . fn ( )
160- . mockRejectedValueOnce ( {
161- shortMessage : "An internal error was received." ,
162- details : "Invalid pointer in tuple at location 128 in payload" ,
163- } )
164- . mockRejectedValueOnce ( new Error ( "stop_after_retry" ) ) ;
165- const { actions} = setupWriteContractHarness ( {
166- initialAbi : ADD_TRANSACTION_ABI_V5 ,
167- signTransactionMock : signTransaction as any ,
161+ it ( "uses refreshed ABI from initializeConsensusSmartContract before write encoding" , async ( ) => {
162+ const { actions, estimateTransactionGas, client} = setupWriteContractHarness ( {
163+ initialAbi : ADD_TRANSACTION_ABI_V6 ,
168164 } ) ;
169165
170- await expect (
171- actions . writeContract ( {
172- address : RECIPIENT_ADDRESS ,
173- functionName : "ping" ,
174- value : 0n ,
175- } ) ,
176- ) . rejects . toThrow ( "stop_after_retry" ) ;
177-
178- expect ( signTransaction ) . toHaveBeenCalledTimes ( 2 ) ;
179- const firstEncodedData = signTransaction . mock . calls [ 0 ] [ 0 ] . data as `0x${string } `;
180- const secondEncodedData = signTransaction . mock . calls [ 1 ] [ 0 ] . data as `0x${string } `;
181- expect ( firstEncodedData . slice ( 0 , 10 ) ) . toBe ( selectorForV5 ) ;
182- expect ( secondEncodedData . slice ( 0 , 10 ) ) . toBe ( selectorForV6 ) ;
183- } ) ;
184-
185- it ( "retries with v5 signature when v6 signature fails with ABI mismatch" , async ( ) => {
186- const signTransaction = vi
187- . fn ( )
188- . mockRejectedValueOnce ( new Error ( "Invalid pointer in tuple at location 128 in payload" ) )
189- . mockRejectedValueOnce ( new Error ( "stop_after_retry" ) ) ;
190- const { actions, estimateTransactionGas} = setupWriteContractHarness ( {
191- initialAbi : ADD_TRANSACTION_ABI_V6 ,
192- signTransactionMock : signTransaction ,
166+ // Simulate initializeConsensusSmartContract updating the ABI
167+ client . initializeConsensusSmartContract . mockImplementation ( async ( ) => {
168+ client . chain . consensusMainContract . abi = [ ...ADD_TRANSACTION_ABI_V7 ] ;
193169 } ) ;
194170
195171 await expect (
@@ -198,14 +174,11 @@ describe("contractActions addTransaction ABI compatibility", () => {
198174 functionName : "ping" ,
199175 value : 0n ,
200176 } ) ,
201- ) . rejects . toThrow ( "stop_after_retry " ) ;
177+ ) . rejects . toThrow ( "stop_after_encoding " ) ;
202178
203- expect ( signTransaction ) . toHaveBeenCalledTimes ( 2 ) ;
204- const firstEncodedData = signTransaction . mock . calls [ 0 ] [ 0 ] . data as `0x${string } `;
205- const secondEncodedData = signTransaction . mock . calls [ 1 ] [ 0 ] . data as `0x${string } `;
206- expect ( firstEncodedData . slice ( 0 , 10 ) ) . toBe ( selectorForV6 ) ;
207- expect ( secondEncodedData . slice ( 0 , 10 ) ) . toBe ( selectorForV5 ) ;
208- expect ( estimateTransactionGas ) . toHaveBeenCalledTimes ( 2 ) ;
179+ const encodedData = estimateTransactionGas . mock . calls [ 0 ] [ 0 ] . data as `0x${string } `;
180+ expect ( encodedData . slice ( 0 , 10 ) ) . toBe ( selectorForV7 ) ;
181+ expect ( client . initializeConsensusSmartContract ) . toHaveBeenCalledTimes ( 1 ) ;
209182 } ) ;
210183
211184 it ( "uses direct eth_sendTransaction for non-local accounts without prepareTransactionRequest" , async ( ) => {
@@ -257,78 +230,37 @@ describe("contractActions addTransaction ABI compatibility", () => {
257230 expect ( sendTxCall ) . toBeDefined ( ) ;
258231
259232 const sendTxParams = sendTxCall ?. [ 0 ] ?. params ?. [ 0 ] ;
233+ // Gas is 21000 * 1.5 (50% buffer) = 31500 = 0x7b0c
260234 expect ( sendTxParams ) . toMatchObject ( {
261235 from : SENDER_ADDRESS ,
262236 to : MAIN_CONTRACT_ADDRESS ,
263237 value : "0x0" ,
264- gas : "0x5208 " ,
238+ gas : "0x7b0c " ,
265239 nonce : "0x0" ,
266240 type : "0x0" ,
267241 chainId : "0xeec7" ,
268242 gasPrice : "0x1" ,
269243 } ) ;
270244 } ) ;
271245
272- it ( "retries alternate ABI for injected-wallet errors with nested invalid pointer details" , async ( ) => {
273- const sentPayloads : `0x${string } `[ ] = [ ] ;
274- const request = vi . fn ( ) . mockImplementation ( async ( { method, params} : { method : string ; params ?: any [ ] } ) => {
275- if ( method === "eth_gasPrice" ) {
276- return "0x1" ;
277- }
278-
279- if ( method === "eth_sendTransaction" ) {
280- const payload = params ?. [ 0 ] ;
281- sentPayloads . push ( payload ?. data ) ;
282-
283- if ( sentPayloads . length === 1 ) {
284- throw {
285- code : - 32603 ,
286- message : "Internal JSON-RPC error." ,
287- data : {
288- originalError : {
289- message : "Invalid pointer in tuple at location 128 in payload" ,
290- } ,
291- } ,
292- } ;
293- }
294-
295- return "0x1234" ;
296- }
297-
298- throw new Error ( `Unexpected RPC method: ${ method } ` ) ;
246+ it ( "applies 50% gas buffer to estimated gas" , async ( ) => {
247+ const signTransaction = vi . fn ( ) . mockRejectedValue ( new Error ( "stop_after_gas" ) ) ;
248+ const { actions, estimateTransactionGas} = setupWriteContractHarness ( {
249+ initialAbi : ADD_TRANSACTION_ABI_V6 ,
250+ signTransactionMock : signTransaction ,
299251 } ) ;
300252
301- const client = {
302- chain : {
303- id : 61_127 ,
304- defaultNumberOfInitialValidators : 5 ,
305- defaultConsensusMaxRotations : 3 ,
306- consensusMainContract : {
307- address : MAIN_CONTRACT_ADDRESS ,
308- abi : [ ...ADD_TRANSACTION_ABI_V5 ] ,
309- bytecode : "0x" ,
310- } ,
311- } ,
312- account : {
313- address : SENDER_ADDRESS ,
314- type : "json-rpc" ,
315- } ,
316- initializeConsensusSmartContract : vi . fn ( ) . mockResolvedValue ( undefined ) ,
317- getCurrentNonce : vi . fn ( ) . mockResolvedValue ( 0n ) ,
318- estimateTransactionGas : vi . fn ( ) . mockResolvedValue ( 21_000n ) ,
319- request,
320- } ;
321-
322- const actions = contractActions ( client as any , { } as any ) ;
323- const txHash = await actions . writeContract ( {
324- address : RECIPIENT_ADDRESS ,
325- functionName : "ping" ,
326- value : 0n ,
327- } ) ;
253+ await expect (
254+ actions . writeContract ( {
255+ address : RECIPIENT_ADDRESS ,
256+ functionName : "ping" ,
257+ value : 0n ,
258+ } ) ,
259+ ) . rejects . toThrow ( "stop_after_gas" ) ;
328260
329- expect ( txHash ) . toBe ( "0x1234" ) ;
330- expect ( sentPayloads ) . toHaveLength ( 2 ) ;
331- expect ( sentPayloads [ 0 ] . slice ( 0 , 10 ) ) . toBe ( selectorForV5 ) ;
332- expect ( sentPayloads [ 1 ] . slice ( 0 , 10 ) ) . toBe ( selectorForV6 ) ;
261+ expect ( estimateTransactionGas ) . toHaveBeenCalledTimes ( 1 ) ;
262+ // The gas value passed to signTransaction should be 21000 * 1.5 = 31500
263+ const txRequest = signTransaction . mock . calls [ 0 ] [ 0 ] ;
264+ expect ( txRequest . gas ) . toBe ( 31_500n ) ;
333265 } ) ;
334266} ) ;
0 commit comments