From 89715da3fdb4b497cc5771eb83a88460007740b6 Mon Sep 17 00:00:00 2001 From: Abraham Makovetsky Date: Tue, 21 Nov 2023 17:02:36 +0200 Subject: [PATCH 1/4] fix(Calldata.compile): do not split long `entrypoint` names before calling `getSelectorFromName` Since `getSelectorFromName` uses keccak on its input, we don't need to split the given text value, as there are practically no limits on the input it can handle --- src/utils/calldata/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/calldata/index.ts b/src/utils/calldata/index.ts index 09971fdd9..4e962e41b 100644 --- a/src/utils/calldata/index.ts +++ b/src/utils/calldata/index.ts @@ -164,8 +164,8 @@ export class CallData { const oe = Array.isArray(o) ? [o.length.toString(), ...o] : o; return Object.entries(oe).flatMap(([k, v]) => { let value = v; - if (isLongText(value)) value = splitLongString(value); if (k === 'entrypoint') value = getSelectorFromName(value); + else if (isLongText(value)) value = splitLongString(value); const kk = Array.isArray(oe) && k === '0' ? '$$len' : k; if (isBigInt(value)) return [[`${prefix}${kk}`, felt(value)]]; if (Object(value) === value) { From 6d8c291bce130d7b00ae6d81aff071c4986f04af Mon Sep 17 00:00:00 2001 From: Petar Penovic Date: Thu, 7 Dec 2023 10:17:14 +0100 Subject: [PATCH 2/4] fix: apply bound for contract address from hash calculation --- __tests__/utils/address.test.ts | 6 ++++++ __tests__/utils/utils.test.ts | 35 ++++++++++++++++++++++----------- src/constants.ts | 5 ++++- src/utils/address.ts | 4 ++-- src/utils/hash.ts | 4 +++- 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/__tests__/utils/address.test.ts b/__tests__/utils/address.test.ts index 69687fdab..b3c346f37 100644 --- a/__tests__/utils/address.test.ts +++ b/__tests__/utils/address.test.ts @@ -1,3 +1,4 @@ +import { constants, num } from '../../src'; import { addAddressPadding, getChecksumAddress, @@ -17,6 +18,11 @@ describe('validateAndParseAddress', () => { return expect(validateAndParseAddress(addr)).toEqual(`${addAddressPadding(addr)}`); }); + + test('should fail for out of bound address', () => { + const addr = num.toHex(constants.ADDR_BOUND + 1n); + expect(() => validateAndParseAddress(addr)).toThrow(/^Message not signable/); + }); }); describe('address checksums', () => { diff --git a/__tests__/utils/utils.test.ts b/__tests__/utils/utils.test.ts index e41e8375b..f0019a405 100644 --- a/__tests__/utils/utils.test.ts +++ b/__tests__/utils/utils.test.ts @@ -1,3 +1,5 @@ +import * as starkCurve from '@scure/starknet'; + import { constants, ec, hash, num, stark } from '../../src'; import { Block } from '../../src/provider/utils'; @@ -78,18 +80,15 @@ describe('estimatedFeeToMaxFee()', () => { }); describe('calculateContractAddressFromHash()', () => { - // This test just show how to use calculateContractAddressFromHash for new devs - + const ethAddress = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7'; + const daiAddress = '0x03e85bfbb8e2a42b7bead9e88e9a1b19dbccf661471061807292120462396ec9'; + const factoryAddress = '0x249827618A01858A72B7D04339C47195A324D20D6037033DFE2829F98AFF4FC'; + const classHash = '0x55187E68C60664A947048E0C9E5322F9BF55F7D435ECDCF17ED75724E77368F'; + // Any type of salt can be used. It depends on the dApp what kind of salt it wants to use. + const salt = ec.starkCurve.pedersen(ethAddress, daiAddress); + + // This test just shows how to use calculateContractAddressFromHash for new devs test('calculated contract address should match the snapshot', () => { - const ethAddress = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7'; - - const daiAddress = '0x03e85bfbb8e2a42b7bead9e88e9a1b19dbccf661471061807292120462396ec9'; - const factoryAddress = '0x249827618A01858A72B7D04339C47195A324D20D6037033DFE2829F98AFF4FC'; - const classHash = '0x55187E68C60664A947048E0C9E5322F9BF55F7D435ECDCF17ED75724E77368F'; - - // Any type of salt can be used. It depends on the dApp what kind of salt it wants to use. - const salt = ec.starkCurve.pedersen(ethAddress, daiAddress); - const res = hash.calculateContractAddressFromHash( salt, classHash, @@ -101,6 +100,20 @@ describe('calculateContractAddressFromHash()', () => { `"0x36dc8dcb3440596472ddde11facacc45d0cd250df764ae7c3d1a360c853c324"` ); }); + + test('output should be bound', () => { + const starkCurveSpy = jest.spyOn(starkCurve, 'pedersen'); + starkCurveSpy.mockReturnValue(num.toHex(constants.ADDR_BOUND + 1n)); + const res = hash.calculateContractAddressFromHash( + salt, + classHash, + [ethAddress, daiAddress, factoryAddress], + factoryAddress + ); + expect(starkCurveSpy).toHaveBeenCalled(); + expect(BigInt(res)).toBeLessThan(constants.ADDR_BOUND); + starkCurveSpy.mockRestore(); + }); }); describe('new Block()', () => { diff --git a/src/constants.ts b/src/constants.ts index 60a9303b1..d90b97a73 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -14,9 +14,12 @@ export const BN_FEE_TRANSACTION_VERSION_2 = 2n ** 128n + BN_TRANSACTION_VERSION_ export const ZERO = 0n; export const MASK_250 = 2n ** 250n - 1n; // 2 ** 250 - 1 -export const MASK_251 = 2n ** 251n; export const API_VERSION = ZERO; +// based on: https://github.com/starkware-libs/cairo-lang/blob/v0.12.3/src/starkware/starknet/common/storage.cairo#L3 +export const MAX_STORAGE_ITEM_SIZE = 256n; +export const ADDR_BOUND = 2n ** 251n - MAX_STORAGE_ITEM_SIZE; + export enum BaseUrl { SN_MAIN = 'https://alpha-mainnet.starknet.io', SN_GOERLI = 'https://alpha4.starknet.io', diff --git a/src/utils/address.ts b/src/utils/address.ts index 310bbfae0..e0647df86 100644 --- a/src/utils/address.ts +++ b/src/utils/address.ts @@ -1,7 +1,7 @@ /* eslint-disable no-bitwise */ import { hexToBytes } from '@noble/curves/abstract/utils'; -import { MASK_251, ZERO } from '../constants'; +import { ADDR_BOUND, ZERO } from '../constants'; import { BigNumberish } from '../types'; import { addHexPrefix, removeHexPrefix } from './encode'; import { keccakBn } from './hash'; @@ -12,7 +12,7 @@ export function addAddressPadding(address: BigNumberish): string { } export function validateAndParseAddress(address: BigNumberish): string { - assertInRange(address, ZERO, MASK_251, 'Starknet Address'); + assertInRange(address, ZERO, ADDR_BOUND - 1n, 'Starknet Address'); const result = addAddressPadding(address); diff --git a/src/utils/hash.ts b/src/utils/hash.ts index 205a37599..3c8268d2c 100644 --- a/src/utils/hash.ts +++ b/src/utils/hash.ts @@ -3,6 +3,7 @@ import { poseidonHashMany } from '@scure/starknet'; import { + ADDR_BOUND, API_VERSION, BN_FEE_TRANSACTION_VERSION_1, BN_FEE_TRANSACTION_VERSION_2, @@ -205,13 +206,14 @@ export function calculateContractAddressFromHash( const CONTRACT_ADDRESS_PREFIX = felt('0x535441524b4e45545f434f4e54524143545f41444452455353'); // Equivalent to 'STARKNET_CONTRACT_ADDRESS' - return computeHashOnElements([ + const hash = computeHashOnElements([ CONTRACT_ADDRESS_PREFIX, deployerAddress, salt, classHash, constructorCalldataHash, ]); + return toHex(BigInt(hash) % ADDR_BOUND); } function nullSkipReplacer(key: string, value: any) { From ac544045e2079b68042d850a09b203fc5536c0d0 Mon Sep 17 00:00:00 2001 From: Petar Penovic Date: Tue, 5 Dec 2023 17:12:24 +0100 Subject: [PATCH 3/4] fix: prioritize error states in waitForTransaction evaluation --- __tests__/fixtures.ts | 7 +--- __tests__/rpcProvider.test.ts | 73 ++++++++++++++++++++++++++++++++++- src/provider/rpc.ts | 13 +++++-- 3 files changed, 82 insertions(+), 11 deletions(-) diff --git a/__tests__/fixtures.ts b/__tests__/fixtures.ts index 6e33c9828..cbe809536 100644 --- a/__tests__/fixtures.ts +++ b/__tests__/fixtures.ts @@ -54,11 +54,8 @@ export const getTestProvider = (): ProviderInterface => { if (process.env.IS_LOCALHOST_DEVNET === 'true') { // accelerate the tests when running locally const originalWaitForTransaction = provider.waitForTransaction.bind(provider); - provider.waitForTransaction = ( - txHash: string, - { retryInterval }: waitForTransactionOptions = {} - ) => { - return originalWaitForTransaction(txHash, { retryInterval: retryInterval || 1000 }); + provider.waitForTransaction = (txHash: string, options: waitForTransactionOptions = {}) => { + return originalWaitForTransaction(txHash, { retryInterval: 1000, ...options }); }; } diff --git a/__tests__/rpcProvider.test.ts b/__tests__/rpcProvider.test.ts index 072c23f09..6346fc61c 100644 --- a/__tests__/rpcProvider.test.ts +++ b/__tests__/rpcProvider.test.ts @@ -1,8 +1,17 @@ import { getStarkKey, utils } from '@scure/starknet'; -import { Account, Contract, GetBlockResponse, RpcProvider, stark } from '../src'; +import { + Account, + CallData, + Contract, + GetBlockResponse, + RPC, + RpcProvider, + TransactionExecutionStatus, + stark, + waitForTransactionOptions, +} from '../src'; import { StarknetChainId } from '../src/constants'; -import { CallData } from '../src/utils/calldata'; import { felt, uint256 } from '../src/utils/calldata/cairo'; import { toHexString } from '../src/utils/num'; import { @@ -102,6 +111,66 @@ describeIfRpc('RPCProvider', () => { }); }); + describe('waitForTransaction', () => { + const receipt = {}; + const transactionStatusSpy = jest.spyOn(rpcProvider as any, 'getTransactionStatus'); + const transactionReceiptSpy = jest.spyOn(rpcProvider as any, 'getTransactionReceipt'); + + const generateOptions = (o: waitForTransactionOptions) => ({ retryInterval: 10, ...o }); + const generateTransactionStatus = ( + finality_status: RPC.SPEC.TXN_STATUS, + execution_status?: RPC.SPEC.TXN_EXECUTION_STATUS + ): RPC.TransactionStatus => ({ + finality_status, + execution_status, + }); + const response = { + successful: generateTransactionStatus('ACCEPTED_ON_L1', 'SUCCEEDED'), + reverted: generateTransactionStatus('ACCEPTED_ON_L2', 'REVERTED'), + rejected: generateTransactionStatus('REJECTED'), + }; + + beforeAll(() => { + transactionStatusSpy.mockResolvedValue(null); + transactionReceiptSpy.mockResolvedValue(receipt); + }); + + afterAll(() => { + transactionStatusSpy.mockRestore(); + transactionReceiptSpy.mockRestore(); + }); + + test('successful - default', async () => { + transactionStatusSpy.mockResolvedValueOnce(response.successful); + await expect(rpcProvider.waitForTransaction(0)).resolves.toBe(receipt); + }); + + test('reverted - default', async () => { + transactionStatusSpy.mockResolvedValueOnce(response.reverted); + await expect(rpcProvider.waitForTransaction(0)).resolves.toBe(receipt); + }); + + test('rejected - default', async () => { + transactionStatusSpy.mockResolvedValueOnce(response.rejected); + await expect(rpcProvider.waitForTransaction(0)).rejects.toThrow( + `${undefined}: ${RPC.ETransactionStatus.REJECTED}` + ); + }); + + test('reverted - as error state', async () => { + transactionStatusSpy.mockResolvedValueOnce(response.reverted); + const options = generateOptions({ errorStates: [TransactionExecutionStatus.REVERTED] }); + await expect(rpcProvider.waitForTransaction(0, options)).rejects.toThrow( + `${RPC.ETransactionExecutionStatus.REVERTED}: ${RPC.ETransactionStatus.ACCEPTED_ON_L2}` + ); + }); + + test('no error state; timed-out', async () => { + const options = generateOptions({ errorStates: [] }); + await expect(rpcProvider.waitForTransaction(0, options)).rejects.toThrow(/timed-out/); + }); + }); + describe('RPC methods', () => { let latestBlock: GetBlockResponse; diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts index 721585920..92b7dfd37 100644 --- a/src/provider/rpc.ts +++ b/src/provider/rpc.ts @@ -321,7 +321,9 @@ export class RpcProvider implements ProviderInterface { const retryInterval = options?.retryInterval ?? 5000; const errorStates: any = options?.errorStates ?? [ RPC.ETransactionStatus.REJECTED, - RPC.ETransactionExecutionStatus.REVERTED, + // TODO: commented out to preserve the long-standing behavior of "reverted" not being treated as an error by default + // should decide which behavior to keep in the future + // RPC.ETransactionExecutionStatus.REVERTED, ]; const successStates: any = options?.successStates ?? [ RPC.ETransactionExecutionStatus.SUCCEEDED, @@ -347,14 +349,17 @@ export class RpcProvider implements ProviderInterface { throw error; } - if (successStates.includes(executionStatus) || successStates.includes(finalityStatus)) { - onchain = true; - } else if (errorStates.includes(executionStatus) || errorStates.includes(finalityStatus)) { + if (errorStates.includes(executionStatus) || errorStates.includes(finalityStatus)) { const message = `${executionStatus}: ${finalityStatus}`; const error = new Error(message) as Error & { response: RPC.TransactionStatus }; error.response = txStatus; isErrorState = true; throw error; + } else if ( + successStates.includes(executionStatus) || + successStates.includes(finalityStatus) + ) { + onchain = true; } } catch (error) { if (error instanceof Error && isErrorState) { From 88639e1dbaf6ca27264bc6655a0b36fc9a24ed88 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 10 Dec 2023 05:41:16 +0000 Subject: [PATCH 4/4] chore(release): 5.24.5 [skip ci] ## [5.24.5](https://github.com/starknet-io/starknet.js/compare/v5.24.4...v5.24.5) (2023-12-10) ### Bug Fixes * apply bound for contract address from hash calculation ([6d8c291](https://github.com/starknet-io/starknet.js/commit/6d8c291bce130d7b00ae6d81aff071c4986f04af)) * **Calldata.compile:** do not split long `entrypoint` names before calling `getSelectorFromName` ([89715da](https://github.com/starknet-io/starknet.js/commit/89715da3fdb4b497cc5771eb83a88460007740b6)) * prioritize error states in waitForTransaction evaluation ([ac54404](https://github.com/starknet-io/starknet.js/commit/ac544045e2079b68042d850a09b203fc5536c0d0)) --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5890a615..9cc21f87b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [5.24.5](https://github.com/starknet-io/starknet.js/compare/v5.24.4...v5.24.5) (2023-12-10) + +### Bug Fixes + +- apply bound for contract address from hash calculation ([6d8c291](https://github.com/starknet-io/starknet.js/commit/6d8c291bce130d7b00ae6d81aff071c4986f04af)) +- **Calldata.compile:** do not split long `entrypoint` names before calling `getSelectorFromName` ([89715da](https://github.com/starknet-io/starknet.js/commit/89715da3fdb4b497cc5771eb83a88460007740b6)) +- prioritize error states in waitForTransaction evaluation ([ac54404](https://github.com/starknet-io/starknet.js/commit/ac544045e2079b68042d850a09b203fc5536c0d0)) + ## [5.24.4](https://github.com/starknet-io/starknet.js/compare/v5.24.3...v5.24.4) (2023-12-06) ### Bug Fixes diff --git a/package-lock.json b/package-lock.json index 3b4899725..f868833e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "starknet", - "version": "5.24.4", + "version": "5.24.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "starknet", - "version": "5.24.4", + "version": "5.24.5", "license": "MIT", "dependencies": { "@noble/curves": "~1.2.0", diff --git a/package.json b/package.json index 03a3def70..7c1e23fdd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "starknet", - "version": "5.24.4", + "version": "5.24.5", "description": "JavaScript library for Starknet", "main": "dist/index.js", "module": "dist/index.mjs",