Skip to content

Commit ac54404

Browse files
committed
fix: prioritize error states in waitForTransaction evaluation
1 parent 6d8c291 commit ac54404

File tree

3 files changed

+82
-11
lines changed

3 files changed

+82
-11
lines changed

__tests__/fixtures.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,8 @@ export const getTestProvider = (): ProviderInterface => {
5454
if (process.env.IS_LOCALHOST_DEVNET === 'true') {
5555
// accelerate the tests when running locally
5656
const originalWaitForTransaction = provider.waitForTransaction.bind(provider);
57-
provider.waitForTransaction = (
58-
txHash: string,
59-
{ retryInterval }: waitForTransactionOptions = {}
60-
) => {
61-
return originalWaitForTransaction(txHash, { retryInterval: retryInterval || 1000 });
57+
provider.waitForTransaction = (txHash: string, options: waitForTransactionOptions = {}) => {
58+
return originalWaitForTransaction(txHash, { retryInterval: 1000, ...options });
6259
};
6360
}
6461

__tests__/rpcProvider.test.ts

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import { getStarkKey, utils } from '@scure/starknet';
22

3-
import { Account, Contract, GetBlockResponse, RpcProvider, stark } from '../src';
3+
import {
4+
Account,
5+
CallData,
6+
Contract,
7+
GetBlockResponse,
8+
RPC,
9+
RpcProvider,
10+
TransactionExecutionStatus,
11+
stark,
12+
waitForTransactionOptions,
13+
} from '../src';
414
import { StarknetChainId } from '../src/constants';
5-
import { CallData } from '../src/utils/calldata';
615
import { felt, uint256 } from '../src/utils/calldata/cairo';
716
import { toHexString } from '../src/utils/num';
817
import {
@@ -102,6 +111,66 @@ describeIfRpc('RPCProvider', () => {
102111
});
103112
});
104113

114+
describe('waitForTransaction', () => {
115+
const receipt = {};
116+
const transactionStatusSpy = jest.spyOn(rpcProvider as any, 'getTransactionStatus');
117+
const transactionReceiptSpy = jest.spyOn(rpcProvider as any, 'getTransactionReceipt');
118+
119+
const generateOptions = (o: waitForTransactionOptions) => ({ retryInterval: 10, ...o });
120+
const generateTransactionStatus = (
121+
finality_status: RPC.SPEC.TXN_STATUS,
122+
execution_status?: RPC.SPEC.TXN_EXECUTION_STATUS
123+
): RPC.TransactionStatus => ({
124+
finality_status,
125+
execution_status,
126+
});
127+
const response = {
128+
successful: generateTransactionStatus('ACCEPTED_ON_L1', 'SUCCEEDED'),
129+
reverted: generateTransactionStatus('ACCEPTED_ON_L2', 'REVERTED'),
130+
rejected: generateTransactionStatus('REJECTED'),
131+
};
132+
133+
beforeAll(() => {
134+
transactionStatusSpy.mockResolvedValue(null);
135+
transactionReceiptSpy.mockResolvedValue(receipt);
136+
});
137+
138+
afterAll(() => {
139+
transactionStatusSpy.mockRestore();
140+
transactionReceiptSpy.mockRestore();
141+
});
142+
143+
test('successful - default', async () => {
144+
transactionStatusSpy.mockResolvedValueOnce(response.successful);
145+
await expect(rpcProvider.waitForTransaction(0)).resolves.toBe(receipt);
146+
});
147+
148+
test('reverted - default', async () => {
149+
transactionStatusSpy.mockResolvedValueOnce(response.reverted);
150+
await expect(rpcProvider.waitForTransaction(0)).resolves.toBe(receipt);
151+
});
152+
153+
test('rejected - default', async () => {
154+
transactionStatusSpy.mockResolvedValueOnce(response.rejected);
155+
await expect(rpcProvider.waitForTransaction(0)).rejects.toThrow(
156+
`${undefined}: ${RPC.ETransactionStatus.REJECTED}`
157+
);
158+
});
159+
160+
test('reverted - as error state', async () => {
161+
transactionStatusSpy.mockResolvedValueOnce(response.reverted);
162+
const options = generateOptions({ errorStates: [TransactionExecutionStatus.REVERTED] });
163+
await expect(rpcProvider.waitForTransaction(0, options)).rejects.toThrow(
164+
`${RPC.ETransactionExecutionStatus.REVERTED}: ${RPC.ETransactionStatus.ACCEPTED_ON_L2}`
165+
);
166+
});
167+
168+
test('no error state; timed-out', async () => {
169+
const options = generateOptions({ errorStates: [] });
170+
await expect(rpcProvider.waitForTransaction(0, options)).rejects.toThrow(/timed-out/);
171+
});
172+
});
173+
105174
describe('RPC methods', () => {
106175
let latestBlock: GetBlockResponse;
107176

src/provider/rpc.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,9 @@ export class RpcProvider implements ProviderInterface {
321321
const retryInterval = options?.retryInterval ?? 5000;
322322
const errorStates: any = options?.errorStates ?? [
323323
RPC.ETransactionStatus.REJECTED,
324-
RPC.ETransactionExecutionStatus.REVERTED,
324+
// TODO: commented out to preserve the long-standing behavior of "reverted" not being treated as an error by default
325+
// should decide which behavior to keep in the future
326+
// RPC.ETransactionExecutionStatus.REVERTED,
325327
];
326328
const successStates: any = options?.successStates ?? [
327329
RPC.ETransactionExecutionStatus.SUCCEEDED,
@@ -347,14 +349,17 @@ export class RpcProvider implements ProviderInterface {
347349
throw error;
348350
}
349351

350-
if (successStates.includes(executionStatus) || successStates.includes(finalityStatus)) {
351-
onchain = true;
352-
} else if (errorStates.includes(executionStatus) || errorStates.includes(finalityStatus)) {
352+
if (errorStates.includes(executionStatus) || errorStates.includes(finalityStatus)) {
353353
const message = `${executionStatus}: ${finalityStatus}`;
354354
const error = new Error(message) as Error & { response: RPC.TransactionStatus };
355355
error.response = txStatus;
356356
isErrorState = true;
357357
throw error;
358+
} else if (
359+
successStates.includes(executionStatus) ||
360+
successStates.includes(finalityStatus)
361+
) {
362+
onchain = true;
358363
}
359364
} catch (error) {
360365
if (error instanceof Error && isErrorState) {

0 commit comments

Comments
 (0)