Skip to content

Commit 58fd4bc

Browse files
fix: update tests for dynamic chain ABI encoding and gas buffer
Remove obsolete V5/V6 dual ABI fallback tests (replaced by dynamic chain ABI approach), add WithFees (7-input) ABI test, fix gas expectations for 50% buffer, and remove dead V5/V6 constants. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 5ac2be5 commit 58fd4bc

2 files changed

Lines changed: 71 additions & 189 deletions

File tree

src/contracts/actions.ts

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -405,56 +405,6 @@ const validateAccount = (Account?: Account): Account => {
405405
return Account;
406406
};
407407

408-
const ADD_TRANSACTION_ABI_V5 = [
409-
{
410-
type: "function",
411-
name: "addTransaction",
412-
stateMutability: "nonpayable",
413-
inputs: [
414-
{name: "_sender", type: "address"},
415-
{name: "_recipient", type: "address"},
416-
{name: "_numOfInitialValidators", type: "uint256"},
417-
{name: "_maxRotations", type: "uint256"},
418-
{name: "_txData", type: "bytes"},
419-
],
420-
outputs: [],
421-
},
422-
] as const;
423-
424-
const ADD_TRANSACTION_ABI_V6 = [
425-
{
426-
type: "function",
427-
name: "addTransaction",
428-
stateMutability: "nonpayable",
429-
inputs: [
430-
{name: "_sender", type: "address"},
431-
{name: "_recipient", type: "address"},
432-
{name: "_numOfInitialValidators", type: "uint256"},
433-
{name: "_maxRotations", type: "uint256"},
434-
{name: "_txData", type: "bytes"},
435-
{name: "_validUntil", type: "uint256"},
436-
],
437-
outputs: [],
438-
},
439-
] as const;
440-
441-
const getAddTransactionInputCount = (abi: readonly unknown[] | undefined): number => {
442-
if (!abi || !Array.isArray(abi)) {
443-
return 0;
444-
}
445-
446-
const addTransactionFunction = abi.find(item => {
447-
if (!item || typeof item !== "object") {
448-
return false;
449-
}
450-
451-
const candidate = item as {type?: string; name?: string};
452-
return candidate.type === "function" && candidate.name === "addTransaction";
453-
}) as {inputs?: readonly unknown[]} | undefined;
454-
455-
return Array.isArray(addTransactionFunction?.inputs) ? addTransactionFunction.inputs.length : 0;
456-
};
457-
458408
const _encodeAddTransactionData = ({
459409
client,
460410
senderAccount,

tests/contracts-actions.test.ts

Lines changed: 71 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const MAIN_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000001";
66
const SENDER_ADDRESS = "0x0000000000000000000000000000000000000002";
77
const 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-
4856
const 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+
5484
const setupWriteContractHarness = ({
5585
initialAbi,
5686
signTransactionMock,
@@ -94,23 +124,6 @@ const setupWriteContractHarness = ({
94124
};
95125

96126
describe("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

Comments
 (0)