From a94ff83962177f011eaa99772388f4d75fb680c4 Mon Sep 17 00:00:00 2001 From: Rian Hughes <ryanhughes4500@hotmail.com> Date: Fri, 24 Nov 2023 10:38:15 +0300 Subject: [PATCH] rpcv06 implement v3 transactions (#462) * rpcv06 implement v3 transactions * update v3-txs to rpc1 * invokeV3 transaction hash * Add transaction hash functions for deploy, declare v3 * Add DeclareTxHash test - wip * wip * wip - test deployAccount * test - integration wip * test - declareTxnHashV3 works * test - invokeTxnHashV3 works * test - deployAcntTxnHashV3 works * Add erroneous test - no test data for v3 testnet * fix v3 type fields * Fix minor test * Update rpc/types_transaction.go Co-authored-by: Josh Klopfenstein <git@joshklop.com> * address comments * use mocks to test TxHash instead of requiring integration endpoint * update transactionHash test to use mocks and not testnet --------- Co-authored-by: Josh Klopfenstein <git@joshklop.com> --- account/account.go | 174 ++++++++++++--- account/account_test.go | 333 ++++++++++++++++++++-------- rpc/contract_test.go | 48 ++-- rpc/mock_test.go | 23 +- rpc/types.go | 30 ++- rpc/types_block.go | 12 +- rpc/types_broadcast_transaction.go | 21 ++ rpc/types_transaction.go | 126 ++++++++++- rpc/types_transaction_interfaces.go | 67 ++---- rpc/types_transaction_receipt.go | 5 +- utils/slices.go | 41 ++++ 11 files changed, 652 insertions(+), 228 deletions(-) create mode 100644 utils/slices.go diff --git a/account/account.go b/account/account.go index 2dc00bc2..75c289c9 100644 --- a/account/account.go +++ b/account/account.go @@ -5,6 +5,7 @@ import ( "errors" "time" + "github.com/NethermindEth/juno/core/crypto" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/starknet.go/curve" "github.com/NethermindEth/starknet.go/hash" @@ -30,7 +31,7 @@ var ( type AccountInterface interface { Sign(ctx context.Context, msg *felt.Felt) ([]*felt.Felt, error) TransactionHashInvoke(invokeTxn rpc.InvokeTxnType) (*felt.Felt, error) - TransactionHashDeployAccount(tx rpc.DeployAccountTxn, contractAddress *felt.Felt) (*felt.Felt, error) + TransactionHashDeployAccount(tx rpc.DeployAccountType, contractAddress *felt.Felt) (*felt.Felt, error) TransactionHashDeclare(tx rpc.DeclareTxnType) (*felt.Felt, error) SignInvokeTransaction(ctx context.Context, tx *rpc.InvokeTxnV1) error SignDeployAccountTransaction(ctx context.Context, tx *rpc.DeployAccountTxn, precomputeAddress *felt.Felt) error @@ -171,37 +172,65 @@ func (account *Account) SignDeclareTransaction(ctx context.Context, tx *rpc.Decl // Returns: // - *felt.Felt: the calculated transaction hash // - error: an error if any -func (account *Account) TransactionHashDeployAccount(tx rpc.DeployAccountTxn, contractAddress *felt.Felt) (*felt.Felt, error) { +func (account *Account) TransactionHashDeployAccount(tx rpc.DeployAccountType, contractAddress *felt.Felt) (*felt.Felt, error) { // https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/transactions/#deploy_account_transaction + switch txn := tx.(type) { + case rpc.DeployAccountTxn: + calldata := []*felt.Felt{txn.ClassHash, txn.ContractAddressSalt} + calldata = append(calldata, txn.ConstructorCalldata...) + calldataHash, err := hash.ComputeHashOnElementsFelt(calldata) + if err != nil { + return nil, err + } - // There is only version 1 of deployAccount txn - if tx.Version != rpc.TransactionV1 { - return nil, ErrTxnTypeUnSupported - } - calldata := []*felt.Felt{tx.ClassHash, tx.ContractAddressSalt} - calldata = append(calldata, tx.ConstructorCalldata...) - calldataHash, err := hash.ComputeHashOnElementsFelt(calldata) - if err != nil { - return nil, err - } + versionFelt, err := new(felt.Felt).SetString(string(txn.Version)) + if err != nil { + return nil, err + } - versionFelt, err := new(felt.Felt).SetString(string(tx.Version)) - if err != nil { - return nil, err - } + // https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/transactions/#deploy_account_hash_calculation + return hash.CalculateTransactionHashCommon( + PREFIX_DEPLOY_ACCOUNT, + versionFelt, + contractAddress, + &felt.Zero, + calldataHash, + txn.MaxFee, + account.ChainId, + []*felt.Felt{txn.Nonce}, + ) + case rpc.DeployAccountTxnV3: + if txn.Version == "" || txn.ResourceBounds == (rpc.ResourceBoundsMapping{}) || txn.Nonce == nil || txn.PayMasterData == nil { + return nil, ErrNotAllParametersSet + } + calldata := []*felt.Felt{txn.ClassHash, txn.ContractAddressSalt} + calldata = append(calldata, txn.ConstructorCalldata...) - // https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/transactions/#deploy_account_hash_calculation - return hash.CalculateTransactionHashCommon( - PREFIX_DEPLOY_ACCOUNT, - versionFelt, - contractAddress, - &felt.Zero, - calldataHash, - tx.MaxFee, - account.ChainId, - []*felt.Felt{tx.Nonce}, - ) + txnVersionFelt, err := new(felt.Felt).SetString(string(txn.Version)) + if err != nil { + return nil, err + } + DAUint64, err := dataAvailabilityMode(txn.FeeMode, txn.NonceDataMode) + if err != nil { + return nil, err + } + // https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/transactions/#deploy_account_hash_calculation + return crypto.PoseidonArray( + PREFIX_DEPLOY_ACCOUNT, + txnVersionFelt, + contractAddress, + tipAndResourcesHash(txn.Tip.Impl().Uint64(), txn.ResourceBounds), + crypto.PoseidonArray(txn.PayMasterData...), + account.ChainId, + txn.Nonce, + new(felt.Felt).SetUint64(DAUint64), + crypto.PoseidonArray(txn.ConstructorCalldata...), + txn.ClassHash, + txn.ContractAddressSalt, + ), nil + } + return nil, ErrTxnTypeUnSupported } // TransactionHashInvoke calculates the transaction hash for the given invoke transaction. @@ -270,10 +299,56 @@ func (account *Account) TransactionHashInvoke(tx rpc.InvokeTxnType) (*felt.Felt, account.ChainId, []*felt.Felt{txn.Nonce}, ) + case rpc.InvokeTxnV3: + // https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md#protocol-changes + if txn.Version == "" || txn.ResourceBounds == (rpc.ResourceBoundsMapping{}) || len(txn.Calldata) == 0 || txn.Nonce == nil || txn.SenderAddress == nil || txn.PayMasterData == nil || txn.AccountDeploymentData == nil { + return nil, ErrNotAllParametersSet + } + + txnVersionFelt, err := new(felt.Felt).SetString(string(txn.Version)) + if err != nil { + return nil, err + } + DAUint64, err := dataAvailabilityMode(txn.FeeMode, txn.NonceDataMode) + if err != nil { + return nil, err + } + + return crypto.PoseidonArray( + PREFIX_TRANSACTION, + txnVersionFelt, + txn.SenderAddress, + tipAndResourcesHash(txn.Tip.Impl().Uint64(), txn.ResourceBounds), + crypto.PoseidonArray(txn.PayMasterData...), + account.ChainId, + txn.Nonce, + new(felt.Felt).SetUint64(DAUint64), + crypto.PoseidonArray(txn.AccountDeploymentData...), + crypto.PoseidonArray(txn.Calldata...), + ), nil } return nil, ErrTxnTypeUnSupported } +func tipAndResourcesHash(tip uint64, resourceBounds rpc.ResourceBoundsMapping) *felt.Felt { + l1Bounds := new(felt.Felt).SetBytes(resourceBounds.L1Gas.Bytes(rpc.ResourceL1Gas)) + l2Bounds := new(felt.Felt).SetBytes(resourceBounds.L2Gas.Bytes(rpc.ResourceL2Gas)) + return crypto.PoseidonArray(new(felt.Felt).SetUint64(tip), l1Bounds, l2Bounds) +} + +func dataAvailabilityMode(feeDAMode, nonceDAMode rpc.DataAvailabilityMode) (uint64, error) { + const dataAvailabilityModeBits = 32 + fee64, err := feeDAMode.UInt64() + if err != nil { + return 0, err + } + nonce64, err := nonceDAMode.UInt64() + if err != nil { + return 0, err + } + return fee64 + nonce64<<dataAvailabilityModeBits, nil +} + // TransactionHashDeclare calculates the transaction hash for declaring a transaction type. // // Parameters: @@ -342,6 +417,35 @@ func (account *Account) TransactionHashDeclare(tx rpc.DeclareTxnType) (*felt.Fel account.ChainId, []*felt.Felt{txn.Nonce, txn.CompiledClassHash}, ) + case rpc.DeclareTxnV3: + // https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-8.md#protocol-changes + if txn.Version == "" || txn.ResourceBounds == (rpc.ResourceBoundsMapping{}) || txn.Nonce == nil || txn.SenderAddress == nil || txn.PayMasterData == nil || txn.AccountDeploymentData == nil || + txn.ClassHash == nil || txn.CompiledClassHash == nil { + return nil, ErrNotAllParametersSet + } + + txnVersionFelt, err := new(felt.Felt).SetString(string(txn.Version)) + if err != nil { + return nil, err + } + DAUint64, err := dataAvailabilityMode(txn.FeeMode, txn.NonceDataMode) + if err != nil { + return nil, err + } + + return crypto.PoseidonArray( + PREFIX_DECLARE, + txnVersionFelt, + txn.SenderAddress, + tipAndResourcesHash(txn.Tip.Impl().Uint64(), txn.ResourceBounds), + crypto.PoseidonArray(txn.PayMasterData...), + account.ChainId, + txn.Nonce, + new(felt.Felt).SetUint64(DAUint64), + crypto.PoseidonArray(txn.AccountDeploymentData...), + txn.ClassHash, + txn.CompiledClassHash, + ), nil } return nil, ErrTxnTypeUnSupported @@ -473,7 +577,7 @@ func (account *Account) BlockNumber(ctx context.Context) (uint64, error) { // - blockID: The rpc.BlockID object representing the block. // Returns: // - uint64: the number of transactions in the block -// - error: an error, if any +// - error: an error, if any func (account *Account) BlockTransactionCount(ctx context.Context, blockID rpc.BlockID) (uint64, error) { return account.provider.BlockTransactionCount(ctx, blockID) } @@ -530,12 +634,12 @@ func (account *Account) ChainID(ctx context.Context) (string, error) { // // Parameters: // - ctx: The context.Context -// - blockID: The rpc.BlockID +// - blockID: The rpc.BlockID // - classHash: The `*felt.Felt` // Returns: -// - *rpc.ClassOutput: The rpc.ClassOutput (the class output could be a DeprecatedContractClass +// - *rpc.ClassOutput: The rpc.ClassOutput (the class output could be a DeprecatedContractClass // or just a Contract class depending on the contract version) -// - error: An error if any occurred. +// - error: An error if any occurred. func (account *Account) Class(ctx context.Context, blockID rpc.BlockID, classHash *felt.Felt) (rpc.ClassOutput, error) { return account.provider.Class(ctx, blockID, classHash) } @@ -546,9 +650,9 @@ func (account *Account) Class(ctx context.Context, blockID rpc.BlockID, classHas // - blockID: The rpc.BlockID object representing the block ID. // - contractAddress: The felt.Felt object representing the contract address. // Returns: -// - *rpc.ClassOutput: The rpc.ClassOutput object (the class output could be a DeprecatedContractClass +// - *rpc.ClassOutput: The rpc.ClassOutput object (the class output could be a DeprecatedContractClass // or just a Contract class depending on the contract version) -// - error: An error if any occurred. +// - error: An error if any occurred. func (account *Account) ClassAt(ctx context.Context, blockID rpc.BlockID, contractAddress *felt.Felt) (rpc.ClassOutput, error) { return account.provider.ClassAt(ctx, blockID, contractAddress) } @@ -618,7 +722,7 @@ func (account *Account) Nonce(ctx context.Context, blockID rpc.BlockID, contract } // SimulateTransactions simulates transactions using the provided context -// Parameters: +// Parameters: // - ctx: The context.Context object // - blockID: The rpc.BlockID object for the block referencing the state or call the transactions are on // - txns: The slice of rpc.Transaction objects representing the transactions to simulate @@ -707,7 +811,7 @@ func (account *Account) TransactionReceipt(ctx context.Context, transactionHash // Parameters: // - ctx: The context.Context object for the request. // - transactionHash: The transaction hash for which the transaction trace is to be retrieved. -// Returns: +// Returns: // - rpc.TxnTrace: The rpc.TxnTrace object representing the transaction trace, and an error if any. func (account *Account) TraceTransaction(ctx context.Context, transactionHash *felt.Felt) (rpc.TxnTrace, error) { return account.provider.TraceTransaction(ctx, transactionHash) diff --git a/account/account_test.go b/account/account_test.go index 0c80c60e..6eac0e30 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -41,7 +41,8 @@ var ( // Parameters: // - m: is the test main // Returns: -// none +// +// none func TestMain(m *testing.M) { flag.StringVar(&testEnv, "env", "mock", "set the test environment") flag.Parse() @@ -60,11 +61,13 @@ func TestMain(m *testing.M) { // of the transaction hash. Each test case consists of the expected hash, a flag // indicating whether the KeyStore should be set, account address, public key, // private key, chain ID, function call, and transaction details. -// +// // Parameters: // - t: The testing.T object for running the test +// // Returns: -// none +// +// none func TestTransactionHashInvoke(t *testing.T) { mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) @@ -184,11 +187,12 @@ func TestTransactionHashInvoke(t *testing.T) { // // It tests the FmtCallData function by providing different test sets // and comparing the output with the expected call data. -// +// // Parameters: // - t: The testing.T instance for running the test // Return: -// none +// +// none func TestFmtCallData(t *testing.T) { mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) @@ -253,7 +257,8 @@ func TestFmtCallData(t *testing.T) { // Parameters: // - t: The testing.T instance for running the test // Return: -// none +// +// none func TestChainIdMOCK(t *testing.T) { mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) @@ -296,7 +301,8 @@ func TestChainIdMOCK(t *testing.T) { // Parameters: // - t: The testing.T instance for running the test // Return: -// none +// +// none func TestChainId(t *testing.T) { mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) @@ -344,7 +350,8 @@ func TestChainId(t *testing.T) { // Parameters: // - t: The testing.T instance for running the test // Returns: -// none +// +// none func TestSignMOCK(t *testing.T) { mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) @@ -404,7 +411,8 @@ func TestSignMOCK(t *testing.T) { // Parameters: // - t: The testing.T instance for running the test // Returns: -// none +// +// none func TestAddInvoke(t *testing.T) { type testSetType struct { @@ -582,9 +590,11 @@ func TestAddInvoke(t *testing.T) { // response is not nil. // // Parameters: -// - t: is the testing framework +// - t: is the testing framework +// // Returns: -// none +// +// none func TestAddDeployAccountDevnet(t *testing.T) { if testEnv != "devnet" { t.Skip("Skipping test as it requires a devnet environment") @@ -633,65 +643,6 @@ func TestAddDeployAccountDevnet(t *testing.T) { require.NotNil(t, resp, "AddDeployAccountTransaction resp not nil") } -// TestTransactionHashDeployAccountTestnet tests the TransactionHashDeployAccount function when using the testnet environment. -// -// It creates a client and provider, initializes the required addresses and keys, and sets up the transaction parameters. -// It then precomputes the address and calculates the hash of the transaction. -// Finally, it verifies that the calculated hash matches the expected hash. -// -// Parameters: -// - t: is the testing framework -// Returns: -// none -func TestTransactionHashDeployAccountTestnet(t *testing.T) { - - if testEnv != "testnet" { - t.Skip("Skipping test as it requires a testnet environment") - } - - client, err := rpc.NewClient(base) - require.NoError(t, err, "Error in rpc.NewClient") - provider := rpc.NewProvider(client) - - AccountAddress := utils.TestHexToFelt(t, "0x0088d0038623a89bf853c70ea68b1062ccf32b094d1d7e5f924cda8404dc73e1") - PubKey := utils.TestHexToFelt(t, "0x7ed3c6482e12c3ef7351214d1195ee7406d814af04a305617599ff27be43883") - PrivKey := utils.TestHexToFelt(t, "0x07514c4f0de1f800b0b0c7377ef39294ce218a7abd9a1c9b6aa574779f7cdc6a") - - ExpectedHash := utils.TestHexToFelt(t, "0x5b6b5927cd70ad7a80efdbe898244525871875c76540b239f6730118598b9cb") - ExpectedPrecomputeAddr := utils.TestHexToFelt(t, "0x88d0038623a89bf853c70ea68b1062ccf32b094d1d7e5f924cda8404dc73e1") - ks := account.NewMemKeystore() - fakePrivKeyBI, ok := new(big.Int).SetString(PrivKey.String(), 0) - require.True(t, ok) - ks.Put(PubKey.String(), fakePrivKeyBI) - - acnt, err := account.NewAccount(provider, AccountAddress, PubKey.String(), ks) - require.NoError(t, err) - - classHash := utils.TestHexToFelt(t, "0x3131fa018d520a037686ce3efddeab8f28895662f019ca3ca18a626650f7d1e") - - tx := rpc.DeployAccountTxn{ - Nonce: &felt.Zero, - MaxFee: utils.TestHexToFelt(t, "0x105ef39b2000"), - Type: rpc.TransactionType_DeployAccount, - Version: rpc.TransactionV1, - Signature: []*felt.Felt{}, - ClassHash: classHash, - ContractAddressSalt: utils.TestHexToFelt(t, "0x7ed3c6482e12c3ef7351214d1195ee7406d814af04a305617599ff27be43883"), - ConstructorCalldata: []*felt.Felt{ - utils.TestHexToFelt(t, "0x5aa23d5bb71ddaa783da7ea79d405315bafa7cf0387a74f4593578c3e9e6570"), - utils.TestHexToFelt(t, "0x2dd76e7ad84dbed81c314ffe5e7a7cacfb8f4836f01af4e913f275f89a3de1a"), - utils.TestHexToFelt(t, "0x1"), - utils.TestHexToFelt(t, "0x7ed3c6482e12c3ef7351214d1195ee7406d814af04a305617599ff27be43883"), - }, - } - precomputedAddress, err := acnt.PrecomputeAddress(&felt.Zero, tx.ContractAddressSalt, classHash, tx.ConstructorCalldata) - require.Equal(t, ExpectedPrecomputeAddr.String(), precomputedAddress.String(), "Error with calulcating PrecomputeAddress") - - hash, err := acnt.TransactionHashDeployAccount(tx, precomputedAddress) - require.NoError(t, err, "TransactionHashDeployAccount gave an Error") - require.Equal(t, hash.String(), ExpectedHash.String(), "Error with calulcating TransactionHashDeployAccount") -} - // TestTransactionHashDeclare tests the TransactionHashDeclare function. // // This function verifies that the TransactionHashDeclare function returns the @@ -709,35 +660,225 @@ func TestTransactionHashDeployAccountTestnet(t *testing.T) { // Parameters: // - t: reference to the testing.T object // Returns: -// none +// +// none func TestTransactionHashDeclare(t *testing.T) { - // https://goerli.voyager.online/tx/0x4e0519272438a3ae0d0fca776136e2bb6fcd5d3b2af47e53575c5874ccfce92 - if testEnv != "testnet" { - t.Skip("Skipping test as it requires a testnet environment") + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + mockRpcProvider := mocks.NewMockRpcProvider(mockCtrl) + mockRpcProvider.EXPECT().ChainID(context.Background()).Return("SN_GOERLI", nil) + + acnt, err := account.NewAccount(mockRpcProvider, &felt.Zero, "", account.NewMemKeystore()) + require.NoError(t, err) + + type testSetType struct { + Txn rpc.DeclareTxnType + ExpectedHash *felt.Felt + ExpectedErr error + } + testSet := map[string][]testSetType{ + "mock": {{ + // Note this is a testnet / goerli transaction + Txn: rpc.DeclareTxnV2{ + Nonce: utils.TestHexToFelt(t, "0xb"), + MaxFee: utils.TestHexToFelt(t, "0x50c8f3053db"), + Type: rpc.TransactionType_Declare, + Version: rpc.TransactionV2, + Signature: []*felt.Felt{}, + SenderAddress: utils.TestHexToFelt(t, "0x36437dffa1b0bf630f04690a3b302adbabb942deb488ea430660c895ff25acf"), + CompiledClassHash: utils.TestHexToFelt(t, "0x615a5260d3d47d79fba87898da95cb5394b181c7d5097bc8ced4ed06ac24ac5"), + ClassHash: utils.TestHexToFelt(t, "0x639cdc0c42c8c4d3d805e56294fa0e6bf5a584ad0fcd538b843cc294913b982"), + }, + ExpectedHash: utils.TestHexToFelt(t, "0x4e0519272438a3ae0d0fca776136e2bb6fcd5d3b2af47e53575c5874ccfce92"), + ExpectedErr: nil, + }, + { + // https://external.integration.starknet.io/feeder_gateway/get_transaction?transactionHash=0x41d1f5206ef58a443e7d3d1ca073171ec25fa75313394318fc83a074a6631c3 + Txn: rpc.DeclareTxnV3{ + Nonce: utils.TestHexToFelt(t, "0x1"), + Type: rpc.TransactionType_Declare, + Version: rpc.TransactionV3, + Signature: []*felt.Felt{ + utils.TestHexToFelt(t, "0x29a49dff154fede73dd7b5ca5a0beadf40b4b069f3a850cd8428e54dc809ccc"), + utils.TestHexToFelt(t, "0x429d142a17223b4f2acde0f5ecb9ad453e188b245003c86fab5c109bad58fc3")}, + SenderAddress: utils.TestHexToFelt(t, "0x2fab82e4aef1d8664874e1f194951856d48463c3e6bf9a8c68e234a629a6f50"), + CompiledClassHash: utils.TestHexToFelt(t, "0x1add56d64bebf8140f3b8a38bdf102b7874437f0c861ab4ca7526ec33b4d0f8"), + ClassHash: utils.TestHexToFelt(t, "0x5ae9d09292a50ed48c5930904c880dab56e85b825022a7d689cfc9e65e01ee7"), + ResourceBounds: rpc.ResourceBoundsMapping{ + L1Gas: rpc.ResourceBounds{ + MaxAmount: utils.TestHexToFelt(t, "0x186a0"), + MaxPricePerUnit: utils.TestHexToFelt(t, "0x2540be400"), + }, + L2Gas: rpc.ResourceBounds{ + MaxAmount: utils.TestHexToFelt(t, "0x0"), + MaxPricePerUnit: utils.TestHexToFelt(t, "0x0"), + }, + }, + Tip: utils.TestHexToFelt(t, "0x0"), + PayMasterData: []*felt.Felt{}, + AccountDeploymentData: []*felt.Felt{}, + NonceDataMode: rpc.DAModeL1, + FeeMode: rpc.DAModeL1, + }, + ExpectedHash: utils.TestHexToFelt(t, "0x41d1f5206ef58a443e7d3d1ca073171ec25fa75313394318fc83a074a6631c3"), + ExpectedErr: nil, + }, + }}[testEnv] + for _, test := range testSet { + hash, err := acnt.TransactionHashDeclare(test.Txn) + require.Equal(t, test.ExpectedErr, err) + require.Equal(t, test.ExpectedHash.String(), hash.String(), "TransactionHashDeclare not what expected") } - expectedHash := utils.TestHexToFelt(t, "0x4e0519272438a3ae0d0fca776136e2bb6fcd5d3b2af47e53575c5874ccfce92") +} - client, err := rpc.NewClient(base) - require.NoError(t, err, "Error in rpc.NewClient") - provider := rpc.NewProvider(client) +func TestTransactionHashInvokeV3(t *testing.T) { + + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + mockRpcProvider := mocks.NewMockRpcProvider(mockCtrl) + mockRpcProvider.EXPECT().ChainID(context.Background()).Return("SN_GOERLI", nil) - acnt, err := account.NewAccount(provider, &felt.Zero, "", account.NewMemKeystore()) + acnt, err := account.NewAccount(mockRpcProvider, &felt.Zero, "", account.NewMemKeystore()) require.NoError(t, err) - tx := rpc.DeclareTxnV2{ - Nonce: utils.TestHexToFelt(t, "0xb"), - MaxFee: utils.TestHexToFelt(t, "0x50c8f3053db"), - Type: rpc.TransactionType_Declare, - Version: rpc.TransactionV2, - Signature: []*felt.Felt{}, - SenderAddress: utils.TestHexToFelt(t, "0x36437dffa1b0bf630f04690a3b302adbabb942deb488ea430660c895ff25acf"), - CompiledClassHash: utils.TestHexToFelt(t, "0x615a5260d3d47d79fba87898da95cb5394b181c7d5097bc8ced4ed06ac24ac5"), - ClassHash: utils.TestHexToFelt(t, "0x639cdc0c42c8c4d3d805e56294fa0e6bf5a584ad0fcd538b843cc294913b982"), + type testSetType struct { + Txn rpc.DeclareTxnType + ExpectedHash *felt.Felt + ExpectedErr error + } + testSet := map[string][]testSetType{ + "mock": { + { + // https://external.integration.starknet.io/feeder_gateway/get_transaction?transactionHash=0x49728601e0bb2f48ce506b0cbd9c0e2a9e50d95858aa41463f46386dca489fd + Txn: rpc.InvokeTxnV3{ + Nonce: utils.TestHexToFelt(t, "0xe97"), + Type: rpc.TransactionType_Invoke, + Version: rpc.TransactionV3, + Signature: []*felt.Felt{ + utils.TestHexToFelt(t, "0x71a9b2cd8a8a6a4ca284dcddcdefc6c4fd20b92c1b201bd9836e4ce376fad16"), + utils.TestHexToFelt(t, "0x6bef4745194c9447fdc8dd3aec4fc738ab0a560b0d2c7bf62fbf58aef3abfc5")}, + ResourceBounds: rpc.ResourceBoundsMapping{ + L1Gas: rpc.ResourceBounds{ + MaxAmount: utils.TestHexToFelt(t, "0x186a0"), + MaxPricePerUnit: utils.TestHexToFelt(t, "0x5af3107a4000"), + }, + L2Gas: rpc.ResourceBounds{ + MaxAmount: utils.TestHexToFelt(t, "0x0"), + MaxPricePerUnit: utils.TestHexToFelt(t, "0x0"), + }, + }, + Tip: utils.TestHexToFelt(t, "0x0"), + PayMasterData: []*felt.Felt{}, + AccountDeploymentData: []*felt.Felt{}, + SenderAddress: utils.TestHexToFelt(t, "0x3f6f3bc663aedc5285d6013cc3ffcbc4341d86ab488b8b68d297f8258793c41"), + Calldata: utils.TestHexArrToFelt(t, []string{ + "0x2", + "0x450703c32370cf7ffff540b9352e7ee4ad583af143a361155f2b485c0c39684", + "0x27c3334165536f239cfd400ed956eabff55fc60de4fb56728b6a4f6b87db01c", + "0x0", + "0x4", + "0x4c312760dfd17a954cdd09e76aa9f149f806d88ec3e402ffaf5c4926f568a42", + "0x5df99ae77df976b4f0e5cf28c7dcfe09bd6e81aab787b19ac0c08e03d928cf", + "0x4", + "0x1", + "0x5", + "0x450703c32370cf7ffff540b9352e7ee4ad583af143a361155f2b485c0c39684", + "0x5df99ae77df976b4f0e5cf28c7dcfe09bd6e81aab787b19ac0c08e03d928cf", + "0x1", + "0x7fe4fd616c7fece1244b3616bb516562e230be8c9f29668b46ce0369d5ca829", + "0x287acddb27a2f9ba7f2612d72788dc96a5b30e401fc1e8072250940e024a587", + }), + NonceDataMode: rpc.DAModeL1, + FeeMode: rpc.DAModeL1, + }, + ExpectedHash: utils.TestHexToFelt(t, "0x49728601e0bb2f48ce506b0cbd9c0e2a9e50d95858aa41463f46386dca489fd"), + ExpectedErr: nil, + }, + }}[testEnv] + for _, test := range testSet { + hash, err := acnt.TransactionHashInvoke(test.Txn) + require.Equal(t, test.ExpectedErr, err) + require.Equal(t, test.ExpectedHash.String(), hash.String(), "TransactionHashDeclare not what expected") } +} + +func TestTransactionHashdeployAccount(t *testing.T) { + + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + mockRpcProvider := mocks.NewMockRpcProvider(mockCtrl) + mockRpcProvider.EXPECT().ChainID(context.Background()).Return("SN_GOERLI", nil) - hash, err := acnt.TransactionHashDeclare(tx) + acnt, err := account.NewAccount(mockRpcProvider, &felt.Zero, "", account.NewMemKeystore()) require.NoError(t, err) - require.Equal(t, expectedHash.String(), hash.String(), "TransactionHashDeclare not what expected") + + type testSetType struct { + Txn rpc.DeployAccountType + SenderAddress *felt.Felt + ExpectedHash *felt.Felt + ExpectedErr error + } + testSet := map[string][]testSetType{ + "mock": { + { + Txn: rpc.DeployAccountTxn{ + Nonce: &felt.Zero, + MaxFee: utils.TestHexToFelt(t, "0x105ef39b2000"), + Type: rpc.TransactionType_DeployAccount, + Version: rpc.TransactionV1, + Signature: []*felt.Felt{}, + ClassHash: utils.TestHexToFelt(t, "0x3131fa018d520a037686ce3efddeab8f28895662f019ca3ca18a626650f7d1e"), + ContractAddressSalt: utils.TestHexToFelt(t, "0x7ed3c6482e12c3ef7351214d1195ee7406d814af04a305617599ff27be43883"), + ConstructorCalldata: []*felt.Felt{ + utils.TestHexToFelt(t, "0x5aa23d5bb71ddaa783da7ea79d405315bafa7cf0387a74f4593578c3e9e6570"), + utils.TestHexToFelt(t, "0x2dd76e7ad84dbed81c314ffe5e7a7cacfb8f4836f01af4e913f275f89a3de1a"), + utils.TestHexToFelt(t, "0x1"), + utils.TestHexToFelt(t, "0x7ed3c6482e12c3ef7351214d1195ee7406d814af04a305617599ff27be43883"), + }, + }, + SenderAddress: utils.TestHexToFelt(t, "0x88d0038623a89bf853c70ea68b1062ccf32b094d1d7e5f924cda8404dc73e1"), + ExpectedHash: utils.TestHexToFelt(t, "0x5b6b5927cd70ad7a80efdbe898244525871875c76540b239f6730118598b9cb"), + ExpectedErr: nil, + }, + { + // https://external.integration.starknet.io/feeder_gateway/get_transaction?transactionHash=0x29fd7881f14380842414cdfdd8d6c0b1f2174f8916edcfeb1ede1eb26ac3ef0 + Txn: rpc.DeployAccountTxnV3{ + Nonce: utils.TestHexToFelt(t, "0x0"), + Type: rpc.TransactionType_DeployAccount, + Version: rpc.TransactionV3, + Signature: []*felt.Felt{ + utils.TestHexToFelt(t, "0x6d756e754793d828c6c1a89c13f7ec70dbd8837dfeea5028a673b80e0d6b4ec"), + utils.TestHexToFelt(t, "0x4daebba599f860daee8f6e100601d98873052e1c61530c630cc4375c6bd48e3")}, + ResourceBounds: rpc.ResourceBoundsMapping{ + L1Gas: rpc.ResourceBounds{ + MaxAmount: utils.TestHexToFelt(t, "0x186a0"), + MaxPricePerUnit: utils.TestHexToFelt(t, "0x5af3107a4000"), + }, + L2Gas: rpc.ResourceBounds{ + MaxAmount: utils.TestHexToFelt(t, "0x0"), + MaxPricePerUnit: utils.TestHexToFelt(t, "0x0"), + }, + }, + Tip: utils.TestHexToFelt(t, "0x0"), + PayMasterData: []*felt.Felt{}, + NonceDataMode: rpc.DAModeL1, + FeeMode: rpc.DAModeL1, + ClassHash: utils.TestHexToFelt(t, "0x2338634f11772ea342365abd5be9d9dc8a6f44f159ad782fdebd3db5d969738"), + ConstructorCalldata: utils.TestHexArrToFelt(t, []string{ + "0x5cd65f3d7daea6c63939d659b8473ea0c5cd81576035a4d34e52fb06840196c", + }), + ContractAddressSalt: utils.TestHexToFelt(t, "0x0"), + }, + SenderAddress: utils.TestHexToFelt(t, "0x2fab82e4aef1d8664874e1f194951856d48463c3e6bf9a8c68e234a629a6f50"), + ExpectedHash: utils.TestHexToFelt(t, "0x29fd7881f14380842414cdfdd8d6c0b1f2174f8916edcfeb1ede1eb26ac3ef0"), + ExpectedErr: nil, + }, + }}[testEnv] + for _, test := range testSet { + hash, err := acnt.TransactionHashDeployAccount(test.Txn, test.SenderAddress) + require.Equal(t, test.ExpectedErr, err) + require.Equal(t, test.ExpectedHash.String(), hash.String(), "TransactionHashDeclare not what expected") + } } // TestWaitForTransactionReceiptMOCK is a unit test for the WaitForTransactionReceipt function. @@ -751,7 +892,8 @@ func TestTransactionHashDeclare(t *testing.T) { // Parameters: // - t: The testing.T object for test assertions and logging // Returns: -// none +// +// none func TestWaitForTransactionReceiptMOCK(t *testing.T) { mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) @@ -833,7 +975,8 @@ func TestWaitForTransactionReceiptMOCK(t *testing.T) { // Parameters: // - t: The testing.T instance for running the test // Returns: -// none +// +// none func TestWaitForTransactionReceipt(t *testing.T) { if testEnv != "devnet" { t.Skip("Skipping test as it requires a devnet environment") @@ -882,9 +1025,11 @@ func TestWaitForTransactionReceipt(t *testing.T) { // It asserts that the expected hash and error values are returned for each test set. // // Parameters: -// - t: The testing.T instance for running the test +// - t: The testing.T instance for running the test +// // Returns: -// none +// +// none func TestAddDeclareTxn(t *testing.T) { // https://goerli.voyager.online/tx/0x76af2faec46130ffad1ab2f615ad16b30afcf49cfbd09f655a26e545b03a21d if testEnv != "testnet" { diff --git a/rpc/contract_test.go b/rpc/contract_test.go index 51609006..d8c7274e 100644 --- a/rpc/contract_test.go +++ b/rpc/contract_test.go @@ -18,17 +18,18 @@ import ( // - Sets the provider of the test configuration to the spy object. // - Calls the ClassAt function with the specified block tag and contract address. // - Checks the response type and performs the following actions based on the type: -// - If the response type is DeprecatedContractClass: -// - Compares the response object with the spy object and checks for a full match. -// - If the objects do not match, compares them again and logs an error if the match is still not achieved. -// - Checks if the program code exists in the response object. -// - If the response type is ContractClass: -// - Throws an error indicating that the case is not covered. +// - If the response type is DeprecatedContractClass: +// - Compares the response object with the spy object and checks for a full match. +// - If the objects do not match, compares them again and logs an error if the match is still not achieved. +// - Checks if the program code exists in the response object. +// - If the response type is ContractClass: +// - Throws an error indicating that the case is not covered. // // Parameters: // - t: the testing object for running the test cases // Returns: -// none +// +// none func TestClassAt(t *testing.T) { testConfig := beforeEach(t) @@ -96,7 +97,8 @@ func TestClassAt(t *testing.T) { // Parameters: // - t: the testing object for running the test cases // Returns: -// none +// +// none func TestClassHashAt(t *testing.T) { testConfig := beforeEach(t) @@ -162,20 +164,21 @@ func TestClassHashAt(t *testing.T) { // - Calls the Class function with the appropriate parameters. // - Handles the response based on its type: // - If the response is of type DeprecatedContractClass: -// - Compares the response with the spy object to check for any differences. -// - If there is a difference, it reports an error and prints the difference. -// - Checks if the class program starts with the expected program. -// - If not, it reports an error. +// - Compares the response with the spy object to check for any differences. +// - If there is a difference, it reports an error and prints the difference. +// - Checks if the class program starts with the expected program. +// - If not, it reports an error. // - If the response is of type ContractClass: -// - Compares the constructor entry point with the expected entry point constructor. -// - If they are not equal, it reports an error. +// - Compares the constructor entry point with the expected entry point constructor. +// - If they are not equal, it reports an error. // // The function is used for testing the behavior of the Class function in different scenarios. // // Parameters: // - t: A *testing.T object used for reporting test failures and logging // Returns: -// none +// +// none func TestClass(t *testing.T) { testConfig := beforeEach(t) @@ -249,7 +252,8 @@ func TestClass(t *testing.T) { // Parameters: // - t: The testing.T instance used for reporting test failures and logging // Returns: -// none +// +// none func TestStorageAt(t *testing.T) { testConfig := beforeEach(t) @@ -317,7 +321,8 @@ func TestStorageAt(t *testing.T) { // Parameters: // - t: the testing object for running the test cases // Returns: -// none +// +// none func TestNonce(t *testing.T) { testConfig := beforeEach(t) @@ -369,7 +374,8 @@ func TestNonce(t *testing.T) { // Parameters: // - t: the testing object for running the test cases // Returns: -// none +// +// none func TestEstimateMessageFee(t *testing.T) { testConfig := beforeEach(t) @@ -384,9 +390,9 @@ func TestEstimateMessageFee(t *testing.T) { MsgFromL1: MsgFromL1{FromAddress: "0x0", ToAddress: &felt.Zero, Selector: &felt.Zero, Payload: []*felt.Felt{&felt.Zero}}, BlockID: BlockID{Tag: "latest"}, ExpectedFeeEst: FeeEstimate{ - GasConsumed: NumAsHex("0x1"), - GasPrice: NumAsHex("0x2"), - OverallFee: NumAsHex("0x3"), + GasConsumed: new(felt.Felt).SetUint64(1), + GasPrice: new(felt.Felt).SetUint64(2), + OverallFee: new(felt.Felt).SetUint64(3), }, }, }, diff --git a/rpc/mock_test.go b/rpc/mock_test.go index ea749ecd..884e2157 100644 --- a/rpc/mock_test.go +++ b/rpc/mock_test.go @@ -582,10 +582,13 @@ func mock_starknet_estimateFee(result interface{}, method string, args ...interf return errWrongArgs } + gasCons, _ := new(felt.Felt).SetString("0x01a4") + gasPrice, _ := new(felt.Felt).SetString("0x45") + overallFee, _ := new(felt.Felt).SetString("0x7134") output := FeeEstimate{ - GasConsumed: NumAsHex("0x01a4"), - GasPrice: NumAsHex("0x45"), - OverallFee: NumAsHex("0x7134"), + GasConsumed: gasCons, + GasPrice: gasPrice, + OverallFee: overallFee, } outputContent, _ := json.Marshal(output) json.Unmarshal(outputContent, r) @@ -621,9 +624,9 @@ func mock_starknet_estimateMessageFee(result interface{}, method string, args .. } output := FeeEstimate{ - GasConsumed: NumAsHex("0x1"), - GasPrice: NumAsHex("0x2"), - OverallFee: NumAsHex("0x3"), + GasConsumed: new(felt.Felt).SetUint64(1), + GasPrice: new(felt.Felt).SetUint64(2), + OverallFee: new(felt.Felt).SetUint64(3), } outputContent, _ := json.Marshal(output) json.Unmarshal(outputContent, r) @@ -789,10 +792,10 @@ func mock_starknet_getStateUpdate(result interface{}, method string, args ...int // - args: optional arguments for the method // Returns: // - error: an error if -// - The result parameter is not of type *json.RawMessage -// - The number of arguments is not equal to 2 -// - The first argument is not of type BlockID -// - The second argument is not of type *felt.Felt +// - The result parameter is not of type *json.RawMessage +// - The number of arguments is not equal to 2 +// - The first argument is not of type BlockID +// - The second argument is not of type *felt.Felt func mock_starknet_getNonce(result interface{}, method string, args ...interface{}) error { r, ok := result.(*json.RawMessage) if !ok { diff --git a/rpc/types.go b/rpc/types.go index 99474b67..2acf5e1f 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -115,7 +115,9 @@ type SyncStatus struct { // occurred during the marshaling process. // // Parameters: -// none +// +// none +// // Returns: // - []byte: the JSON encoding of the SyncStatus struct // - error: any error that occurred during the marshaling process @@ -136,7 +138,9 @@ func (s SyncStatus) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals the JSON data into the SyncStatus struct. // // Parameters: -// -data: It takes a byte slice as input representing the JSON data to be unmarshaled. +// +// -data: It takes a byte slice as input representing the JSON data to be unmarshaled. +// // Returns: // - error: an error if the unmarshaling fails func (s *SyncStatus) UnmarshalJSON(data []byte) error { @@ -185,13 +189,13 @@ type TxDetails struct { type FeeEstimate struct { // GasConsumed the Ethereum gas cost of the transaction (see https://docs.starknet.io/docs/Fees/fee-mechanism for more info) - GasConsumed NumAsHex `json:"gas_consumed"` + GasConsumed *felt.Felt `json:"gas_consumed"` // GasPrice the gas price (in gwei) that was used in the cost estimation - GasPrice NumAsHex `json:"gas_price"` + GasPrice *felt.Felt `json:"gas_price"` // OverallFee the estimated fee for the transaction (in gwei), product of gas_consumed and gas_price - OverallFee NumAsHex `json:"overall_fee"` + OverallFee *felt.Felt `json:"overall_fee"` } type TxnExecutionStatus string @@ -229,7 +233,9 @@ func (ts *TxnExecutionStatus) UnmarshalJSON(data []byte) error { // The function returns the marshaled byte slice and a nil error. // // Parameters: -// none +// +// none +// // Returns: // - []byte: the JSON encoding of the TxnExecutionStatus // - error: the error if there was an issue marshaling @@ -240,7 +246,9 @@ func (ts TxnExecutionStatus) MarshalJSON() ([]byte, error) { // String returns the string representation of the TxnExecutionStatus. // // Parameters: -// none +// +// none +// // Returns: // - string: the string representation of the TxnExecutionStatus func (s TxnExecutionStatus) String() string { @@ -279,7 +287,9 @@ func (ts *TxnFinalityStatus) UnmarshalJSON(data []byte) error { // MarshalJSON marshals the TxnFinalityStatus into JSON. // // Parameters: -// none +// +// none +// // Returns: // - []byte: a byte slice // - error: an error if any @@ -290,7 +300,9 @@ func (ts TxnFinalityStatus) MarshalJSON() ([]byte, error) { // String returns the string representation of the TxnFinalityStatus. // // Parameters: -// none +// +// none +// // Returns: // - string: the string representation of the TxnFinalityStatus func (s TxnFinalityStatus) String() string { diff --git a/rpc/types_block.go b/rpc/types_block.go index d2578de9..78a80e04 100644 --- a/rpc/types_block.go +++ b/rpc/types_block.go @@ -31,7 +31,9 @@ type BlockID struct { // while the error indicates any error that occurred during the marshaling process. // // Parameters: -// none +// +// none +// // Returns: // - []byte: the JSON representation of the BlockID // - error: any error that occurred during the marshaling process @@ -99,7 +101,9 @@ func (bs *BlockStatus) UnmarshalJSON(data []byte) error { // MarshalJSON returns the JSON encoding of BlockStatus. // // Parameters: -// none +// +// none +// // Returns: // - []byte: a byte slice // - error: an error if any @@ -165,7 +169,7 @@ type PendingBlockHeader struct { type ResourcePrice struct { // The price of one unit of the given resource, denominated in strk - PriceInStrk NumAsHex `json:"price_in_strk,omitempty"` + PriceInStrk *felt.Felt `json:"price_in_strk,omitempty"` // The price of one unit of the given resource, denominated in wei - PriceInWei NumAsHex `json:"price_in_wei"` + PriceInWei *felt.Felt `json:"price_in_wei"` } diff --git a/rpc/types_broadcast_transaction.go b/rpc/types_broadcast_transaction.go index fcfdf50e..69264735 100644 --- a/rpc/types_broadcast_transaction.go +++ b/rpc/types_broadcast_transaction.go @@ -9,6 +9,7 @@ var ( _ BroadcastTxn = BroadcastInvokev1Txn{} _ BroadcastTxn = BroadcastDeclareV1Txn{} _ BroadcastTxn = BroadcastDeclareV2Txn{} + _ BroadcastTxn = BroadcastDeclareTxnV3{} _ BroadcastTxn = BroadcastDeployAccountTxn{} ) @@ -56,6 +57,26 @@ type BroadcastDeclareV2Txn struct { ContractClass ContractClass `json:"contract_class"` } +type BroadcastDeclareTxnV3 struct { + Type TransactionType `json:"type"` + SenderAddress *felt.Felt `json:"sender_address"` + CompiledClassHash *felt.Felt `json:"compiled_class_hash"` + Version NumAsHex `json:"version"` + Signature []*felt.Felt `json:"signature"` + Nonce *felt.Felt `json:"nonce"` + ContractClass *ContractClass `json:"contract_class"` + ResourceBounds ResourceBoundsMapping `json:"resource_bounds"` + Tip *felt.Felt `json:"tip"` + // The data needed to allow the paymaster to pay for the transaction in native tokens + PayMasterData []*felt.Felt `json:"paymaster_data"` + // The data needed to deploy the account contract from which this tx will be initiated + AccountDeploymentData *felt.Felt `json:"account_deployment_data"` + // The storage domain of the account's nonce (an account has a nonce per DA mode) + NonceDataMode DataAvailabilityMode `json:"nonce_data_availability_mode"` + // The storage domain of the account's balance from which fee will be charged + FeeMode DataAvailabilityMode `json:"fee_data_availability_mode"` +} + type BroadcastDeployAccountTxn struct { DeployAccountTxn } diff --git a/rpc/types_transaction.go b/rpc/types_transaction.go index 6a1e737e..623be52e 100644 --- a/rpc/types_transaction.go +++ b/rpc/types_transaction.go @@ -1,12 +1,14 @@ package rpc import ( + "encoding/binary" "encoding/json" "errors" "fmt" "math/big" "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/starknet.go/utils" ) // https://github.com/starkware-libs/starknet-specs/blob/a789ccc3432c57777beceaa53a34a7ae2f25fda0/api/starknet_api_openrpc.json#L1252 @@ -45,11 +47,29 @@ type InvokeTxnV1 struct { // The data expected by the account's `execute` function (in most usecases, this includes the called contract address and a function selector) Calldata []*felt.Felt `json:"calldata"` } +type InvokeTxnV3 struct { + Type TransactionType `json:"type"` + SenderAddress *felt.Felt `json:"sender_address"` + Calldata []*felt.Felt `json:"calldata"` + Version TransactionVersion `json:"version"` + Signature []*felt.Felt `json:"signature"` + Nonce *felt.Felt `json:"nonce"` + ResourceBounds ResourceBoundsMapping `json:"resource_bounds"` + Tip *felt.Felt `json:"tip"` + // The data needed to allow the paymaster to pay for the transaction in native tokens + PayMasterData []*felt.Felt `json:"paymaster_data"` + // The data needed to deploy the account contract from which this tx will be initiated + AccountDeploymentData []*felt.Felt `json:"account_deployment_data"` + // The storage domain of the account's nonce (an account has a nonce per DA mode) + NonceDataMode DataAvailabilityMode `json:"nonce_data_availability_mode"` + // The storage domain of the account's balance from which fee will be charged + FeeMode DataAvailabilityMode `json:"fee_data_availability_mode"` +} type L1HandlerTxn struct { Type TransactionType `json:"type,omitempty"` // Version of the transaction scheme - Version NumAsHex `json:"version"` + Version *felt.Felt `json:"version"` // Nonce Nonce string `json:"nonce,omitempty"` FunctionCall @@ -89,6 +109,77 @@ type DeclareTxnV2 struct { ClassHash *felt.Felt `json:"class_hash"` } +type DeclareTxnV3 struct { + Type TransactionType `json:"type"` + SenderAddress *felt.Felt `json:"sender_address"` + CompiledClassHash *felt.Felt `json:"compiled_class_hash"` + Version TransactionVersion `json:"version"` + Signature []*felt.Felt `json:"signature"` + Nonce *felt.Felt `json:"nonce"` + ClassHash *felt.Felt `json:"class_hash"` + ResourceBounds ResourceBoundsMapping `json:"resource_bounds"` + Tip *felt.Felt `json:"tip"` + // The data needed to allow the paymaster to pay for the transaction in native tokens + PayMasterData []*felt.Felt `json:"paymaster_data"` + // The data needed to deploy the account contract from which this tx will be initiated + AccountDeploymentData []*felt.Felt `json:"account_deployment_data"` + // The storage domain of the account's nonce (an account has a nonce per DA mode) + NonceDataMode DataAvailabilityMode `json:"nonce_data_availability_mode"` + // The storage domain of the account's balance from which fee will be charged + FeeMode DataAvailabilityMode `json:"fee_data_availability_mode"` +} + +type ResourceBoundsMapping struct { + // The max amount and max price per unit of L1 gas used in this tx + L1Gas ResourceBounds `json:"l1_gas"` + // The max amount and max price per unit of L2 gas used in this tx + L2Gas ResourceBounds `json:"l2_gas"` +} + +type DataAvailabilityMode string + +const ( + DAModeL1 DataAvailabilityMode = "L1" + DAModeL2 DataAvailabilityMode = "L2" +) + +func (da *DataAvailabilityMode) UInt64() (uint64, error) { + switch *da { + case DAModeL1: + return uint64(0), nil + case DAModeL2: + return uint64(1), nil + } + return 0, errors.New("Unknown DAMode") +} + +type Resource string + +const ( + ResourceL1Gas Resource = "L1_GAS" + ResourceL2Gas Resource = "L2_GAS" +) + +type ResourceBounds struct { + // The max amount of the resource that can be used in the tx + MaxAmount *felt.Felt `json:"max_amount"` + // The max price per unit of this resource for this tx + MaxPricePerUnit *felt.Felt `json:"max_price_per_unit"` +} + +func (rb ResourceBounds) Bytes(resource Resource) []byte { + const eight = 8 + maxAmountBytes := make([]byte, eight) + binary.BigEndian.PutUint64(maxAmountBytes, rb.MaxAmount.Impl().Uint64()) + maxPriceBytes := rb.MaxPricePerUnit.Bytes() + return utils.Flatten( + []byte{0}, + []byte(resource), + maxAmountBytes, + maxPriceBytes[16:], // uint128. + ) +} + // DeployTxn The structure of a deploy transaction. Note that this transaction type is deprecated and will no longer be supported in future versions type DeployTxn struct { // ClassHash The hash of the deployed contract's class @@ -117,6 +208,24 @@ type DeployAccountTxn struct { ConstructorCalldata []*felt.Felt `json:"constructor_calldata"` } +type DeployAccountTxnV3 struct { + Type TransactionType `json:"type"` + Version TransactionVersion `json:"version"` + Signature []*felt.Felt `json:"signature"` + Nonce *felt.Felt `json:"nonce"` + ContractAddressSalt *felt.Felt `json:"contract_address_salt"` + ConstructorCalldata []*felt.Felt `json:"constructor_calldata"` + ClassHash *felt.Felt `json:"class_hash"` + ResourceBounds ResourceBoundsMapping `json:"resource_bounds"` + Tip *felt.Felt `json:"tip"` + // The data needed to allow the paymaster to pay for the transaction in native tokens + PayMasterData []*felt.Felt `json:"paymaster_data"` + // The storage domain of the account's nonce (an account has a nonce per DA mode) + NonceDataMode DataAvailabilityMode `json:"nonce_data_availability_mode"` + // The storage domain of the account's balance from which fee will be charged + FeeMode DataAvailabilityMode `json:"fee_data_availability_mode"` +} + type UnknownTransaction struct{ Transaction } // UnmarshalJSON unmarshals the JSON data into an UnknownTransaction object. @@ -222,15 +331,22 @@ func remarshal(v interface{}, dst interface{}) error { type TransactionVersion string const ( - TransactionV0 TransactionVersion = "0x0" - TransactionV1 TransactionVersion = "0x1" - TransactionV2 TransactionVersion = "0x2" + TransactionV0 TransactionVersion = "0x0" + TransactionV0WithQueryBit TransactionVersion = "0x100000000000000000000000000000000" + TransactionV1 TransactionVersion = "0x1" + TransactionV1WithQueryBit TransactionVersion = "0x100000000000000000000000000000001" + TransactionV2 TransactionVersion = "0x2" + TransactionV2WithQueryBit TransactionVersion = "0x100000000000000000000000000000002" + TransactionV3 TransactionVersion = "0x3" + TransactionV3WithQueryBit TransactionVersion = "0x100000000000000000000000000000003" ) // BigInt returns a big integer corresponding to the transaction version. // // Parameters: -// none +// +// none +// // Returns: // - *big.Int: a pointer to a big.Int // - error: an error if the conversion fails diff --git a/rpc/types_transaction_interfaces.go b/rpc/types_transaction_interfaces.go index 15f695e6..1af5a6d9 100644 --- a/rpc/types_transaction_interfaces.go +++ b/rpc/types_transaction_interfaces.go @@ -4,6 +4,7 @@ type AddDeclareTxnInput interface{} var _ AddDeclareTxnInput = DeclareTxnV1{} var _ AddDeclareTxnInput = DeclareTxnV2{} +var _ AddDeclareTxnInput = DeclareTxnV3{} type Transaction interface { GetType() TransactionType @@ -11,88 +12,55 @@ type Transaction interface { var _ Transaction = InvokeTxnV0{} var _ Transaction = InvokeTxnV1{} +var _ Transaction = InvokeTxnV3{} var _ Transaction = DeclareTxnV1{} var _ Transaction = DeclareTxnV2{} +var _ Transaction = DeclareTxnV3{} var _ Transaction = DeployTxn{} var _ Transaction = DeployAccountTxn{} +var _ Transaction = DeployAccountTxnV3{} var _ Transaction = L1HandlerTxn{} -// GetType returns the transaction type of the InvokeTxnV0 struct. -// -// Parameters: -// none -// Returns: -// - TransactionType: the transaction type func (tx InvokeTxnV0) GetType() TransactionType { return tx.Type } -// GetType returns the type of the InvokeTxnV1 transaction. -// -// Parameters: -// none -// Returns: -// - TransactionType: the transaction type func (tx InvokeTxnV1) GetType() TransactionType { return tx.Type } -// GetType returns the TransactionType of the DeclareTxnV0 object. -// -// Parameters: -// none -// Returns: -// - TransactionType: the transaction type +func (tx InvokeTxnV3) GetType() TransactionType { + return tx.Type +} + func (tx DeclareTxnV0) GetType() TransactionType { return tx.Type } -// GetType returns the transaction type of DeclareTxnV1. -// -// Parameters: -// none -// Returns: -// - TransactionType: the transaction type func (tx DeclareTxnV1) GetType() TransactionType { return tx.Type } -// GetType returns the type of the transaction. -// -// Parameters: -// none -// Returns: -// - TransactionType: the transaction type func (tx DeclareTxnV2) GetType() TransactionType { return tx.Type } -// GetType returns the type of the DeployTxn. -// -// Parameters: -// none -// Returns: -// - TransactionType: the transaction type +func (tx DeclareTxnV3) GetType() TransactionType { + return tx.Type +} + func (tx DeployTxn) GetType() TransactionType { return tx.Type } -// GetType returns the transaction type of the DeployAccountTxn. -// -// Parameters: -// none -// Returns: -// - TransactionType: the transaction type func (tx DeployAccountTxn) GetType() TransactionType { return tx.Type } -// GetType returns the transaction type of the L1HandlerTxn. -// -// Parameters: -// none -// Returns: -// - TransactionType: the transaction type +func (tx DeployAccountTxnV3) GetType() TransactionType { + return tx.Type +} + func (tx L1HandlerTxn) GetType() TransactionType { return tx.Type } @@ -104,13 +72,16 @@ type InvokeTxnType interface{} var _ InvokeTxnType = InvokeTxnV0{} var _ InvokeTxnType = InvokeTxnV1{} +var _ InvokeTxnType = InvokeTxnV3{} type DeclareTxnType interface{} var _ DeclareTxnType = DeclareTxnV0{} var _ DeclareTxnType = DeclareTxnV1{} var _ DeclareTxnType = DeclareTxnV2{} +var _ DeclareTxnType = DeclareTxnV3{} type DeployAccountType interface{} var _ DeployAccountType = DeployAccountTxn{} +var _ DeployAccountType = DeployAccountTxnV3{} diff --git a/rpc/types_transaction_receipt.go b/rpc/types_transaction_receipt.go index b3bf71e5..b0f2ee7a 100644 --- a/rpc/types_transaction_receipt.go +++ b/rpc/types_transaction_receipt.go @@ -392,11 +392,12 @@ type ExecutionResources struct { BitwiseApps int `json:"bitwise_builtin_applications,omitempty"` // The number of KECCAK builtin instances KeccakApps int `json:"keccak_builtin_applications,omitempty"` - // The number of accesses to the segment arena + // The number of accesses to the segment arena SegmentArenaBuiltin int `json:"segment_arena_builtin,omitempty"` } -func (er *ExecutionResources) Validation() bool { +// Validate checks if the fields are non-zero (to match the starknet-specs) +func (er *ExecutionResources) Validate() bool { if er.Steps == 0 || er.MemoryHoles == 0 || er.RangeCheckApps == 0 || er.PedersenApps == 0 || er.PoseidonApps == 0 || er.ECOPApps == 0 || er.ECDSAApps == 0 || er.BitwiseApps == 0 || er.KeccakApps == 0 || er.SegmentArenaBuiltin == 0 { diff --git a/utils/slices.go b/utils/slices.go new file mode 100644 index 00000000..00e7cd6b --- /dev/null +++ b/utils/slices.go @@ -0,0 +1,41 @@ +package utils + +import "slices" + +func Flatten[T any](sl ...[]T) []T { + var result []T + for _, slice := range sl { + result = append(result, slice...) + } + + return result +} + +func Map[T1, T2 any](slice []T1, f func(T1) T2) []T2 { + if slice == nil { + return nil + } + + result := make([]T2, len(slice)) + for i, e := range slice { + result[i] = f(e) + } + + return result +} + +func Filter[T any](slice []T, f func(T) bool) []T { + var result []T + for _, e := range slice { + if f(e) { + result = append(result, e) + } + } + + return result +} + +// All returns true if all elements match the given predicate +func All[T any](slice []T, f func(T) bool) bool { + return slices.IndexFunc(slice, func(e T) bool { return !f(e) }) == -1 +}