diff --git a/.bin/devnet.sh b/.bin/devnet.sh old mode 100755 new mode 100644 index bfdf8d3f..3618d7cf --- a/.bin/devnet.sh +++ b/.bin/devnet.sh @@ -23,5 +23,4 @@ while true; do done echo "could not check devnet is_alive; fail!!!" -exit 1 - +exit 1 \ No newline at end of file diff --git a/.github/workflows/test_account.yml b/.github/workflows/test_account.yml index a5b07f63..62b3f9e2 100644 --- a/.github/workflows/test_account.yml +++ b/.github/workflows/test_account.yml @@ -34,7 +34,7 @@ jobs: run: cd account && go test -timeout 600s -v -env devnet . env: TESTNET_ACCOUNT_PRIVATE_KEY: ${{ secrets.TESTNET_ACCOUNT_PRIVATE_KEY }} - INTEGRATION_BASE: "http://0.0.0.0:5050" + INTEGRATION_BASE: "http://localhost:5050" # Test Account on mock - name: Test Account with mocks diff --git a/account/account.go b/account/account.go index ea3643b9..6852ae6b 100644 --- a/account/account.go +++ b/account/account.go @@ -2,7 +2,9 @@ package account import ( "context" + "encoding/json" "errors" + "fmt" "time" "github.com/NethermindEth/juno/core/felt" @@ -29,7 +31,6 @@ var ( //go:generate mockgen -destination=../mocks/mock_account.go -package=mocks -source=account.go AccountInterface type AccountInterface interface { Sign(ctx context.Context, msg *felt.Felt) ([]*felt.Felt, error) - BuildInvokeTx(ctx context.Context, invokeTx *rpc.InvokeTxnV1, fnCall *[]rpc.FunctionCall) error TransactionHashInvoke(invokeTxn rpc.InvokeTxnType) (*felt.Felt, error) TransactionHashDeployAccount(tx rpc.DeployAccountTxn, contractAddress *felt.Felt) (*felt.Felt, error) TransactionHashDeclare(tx rpc.DeclareTxnType) (*felt.Felt, error) @@ -317,12 +318,6 @@ func (account *Account) WaitForTransactionReceipt(ctx context.Context, transacti } } -// BuildInvokeTx formats the calldata and signs the transaction -func (account *Account) BuildInvokeTx(ctx context.Context, invokeTx *rpc.InvokeTxnV1, fnCall *[]rpc.FunctionCall) error { - invokeTx.Calldata = FmtCalldata(*fnCall) - return account.SignInvokeTransaction(ctx, invokeTx) -} - func (account *Account) AddInvokeTransaction(ctx context.Context, invokeTx rpc.InvokeTxnV1) (*rpc.AddInvokeTransactionResponse, error) { return account.provider.AddInvokeTransaction(ctx, invokeTx) } @@ -420,26 +415,68 @@ func (account *Account) TransactionByHash(ctx context.Context, hash *felt.Felt) return account.provider.TransactionByHash(ctx, hash) } +func (account *Account) FmtCalldata(fnCalls []rpc.FunctionCall, cairoVersion int) ([]*felt.Felt, error) { + switch cairoVersion { + case 0: + return FmtCalldataCairo0(fnCalls), nil + case 2: + return FmtCalldataCairo2(fnCalls), nil + default: + return nil, errors.New("Cairo version not supported") + } +} + /* -Formats the multicall transactions in a format which can be signed and verified by the network and OpenZeppelin account contracts +Formats the call data for Cairo0 contracts */ -func FmtCalldata(fnCalls []rpc.FunctionCall) []*felt.Felt { - callArray := []*felt.Felt{} - callData := []*felt.Felt{new(felt.Felt).SetUint64(uint64(len(fnCalls)))} - - for _, tx := range fnCalls { - callData = append(callData, tx.ContractAddress, tx.EntryPointSelector) +func FmtCalldataCairo0(fnCalls []rpc.FunctionCall) []*felt.Felt { + execCallData := []*felt.Felt{} + execCallData = append(execCallData, new(felt.Felt).SetUint64(uint64(len(fnCalls)))) + + // Legacy : Cairo 0 + concatCallData := []*felt.Felt{} + for _, fnCall := range fnCalls { + execCallData = append( + execCallData, + fnCall.ContractAddress, + fnCall.EntryPointSelector, + new(felt.Felt).SetUint64(uint64(len(concatCallData))), + new(felt.Felt).SetUint64(uint64(len(fnCall.Calldata))+1), + ) + concatCallData = append(concatCallData, fnCall.Calldata...) + } + execCallData = append(execCallData, new(felt.Felt).SetUint64(uint64(len(concatCallData))+1)) + execCallData = append(execCallData, concatCallData...) + execCallData = append(execCallData, new(felt.Felt).SetUint64(0)) - if len(tx.Calldata) == 0 { - callData = append(callData, &felt.Zero, &felt.Zero) - continue - } + return execCallData +} - callData = append(callData, new(felt.Felt).SetUint64(uint64(len(callArray))), new(felt.Felt).SetUint64(uint64(len(tx.Calldata))+1)) - callArray = append(callArray, tx.Calldata...) +/* +Formats the call data for Cairo 1 contracs +*/ +func FmtCalldataCairo2(fnCalls []rpc.FunctionCall) []*felt.Felt { + execCallData := []*felt.Felt{} + execCallData = append(execCallData, new(felt.Felt).SetUint64(uint64(len(fnCalls)))) + + concatCallData := []*felt.Felt{} + for _, fnCall := range fnCalls { + execCallData = append( + execCallData, + fnCall.ContractAddress, + fnCall.EntryPointSelector, + new(felt.Felt).SetUint64(uint64(len(concatCallData))), + new(felt.Felt).SetUint64(uint64(len(fnCall.Calldata))), + ) + concatCallData = append(concatCallData, fnCall.Calldata...) } - callData = append(callData, new(felt.Felt).SetUint64(uint64(len(callArray)+1))) - callData = append(callData, callArray...) - callData = append(callData, new(felt.Felt).SetUint64(0)) - return callData + execCallData = append(execCallData, new(felt.Felt).SetUint64(uint64(len(concatCallData)))) + execCallData = append(execCallData, concatCallData...) + + return execCallData +} + +func PrintTxAsJson(tx any) { + qwe, err := json.MarshalIndent(tx, "", "") + fmt.Println(string(qwe), err) } diff --git a/account/account_test.go b/account/account_test.go index 9367ce43..6b5ac233 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -158,7 +158,13 @@ func TestTransactionHashInvoke(t *testing.T) { } func TestFmtCallData(t *testing.T) { + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + mockRpcProvider := mocks.NewMockRpcProvider(mockCtrl) + type testSetType struct { + CairoVersion int + ChainID string FnCall rpc.FunctionCall ExpectedCallData []*felt.Felt } @@ -166,6 +172,8 @@ func TestFmtCallData(t *testing.T) { "devnet": {}, "mock": { { + CairoVersion: 0, + ChainID: "SN_GOERLI", FnCall: rpc.FunctionCall{ ContractAddress: utils.TestHexToFelt(t, "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), EntryPointSelector: utils.GetSelectorFromNameFelt("transfer"), @@ -191,7 +199,12 @@ func TestFmtCallData(t *testing.T) { }[testEnv] for _, test := range testSet { - fmtCallData := account.FmtCalldata([]rpc.FunctionCall{test.FnCall}) + mockRpcProvider.EXPECT().ChainID(context.Background()).Return(test.ChainID, nil) + acnt, err := account.NewAccount(mockRpcProvider, &felt.Zero, "pubkey", account.NewMemKeystore()) + require.NoError(t, err) + + fmtCallData, err := acnt.FmtCalldata([]rpc.FunctionCall{test.FnCall}, test.CairoVersion) + require.NoError(t, err) require.Equal(t, fmtCallData, test.ExpectedCallData) } } @@ -315,51 +328,52 @@ func TestSignMOCK(t *testing.T) { func TestAddInvoke(t *testing.T) { type testSetType struct { - ExpectedHash *felt.Felt - ExpectedError *rpc.RPCError - SetKS bool - AccountAddress *felt.Felt - PubKey *felt.Felt - PrivKey *felt.Felt - InvokeTx rpc.InvokeTxnV1 - FnCall rpc.FunctionCall - TxDetails rpc.TxDetails + ExpectedError *rpc.RPCError + CairoContractVersion int + SetKS bool + AccountAddress *felt.Felt + PubKey *felt.Felt + PrivKey *felt.Felt + InvokeTx rpc.InvokeTxnV1 + FnCall rpc.FunctionCall + TxDetails rpc.TxDetails } testSet := map[string][]testSetType{ "mock": {}, "devnet": {}, - "testnet": {{ - // https://goerli.voyager.online/tx/0x73cf79c4bfa0c7a41f473c07e1be5ac25faa7c2fdf9edcbd12c1438f40f13d8#overview - ExpectedHash: utils.TestHexToFelt(t, "0x73cf79c4bfa0c7a41f473c07e1be5ac25faa7c2fdf9edcbd12c1438f40f13d8"), - ExpectedError: rpc.ErrDuplicateTx, - AccountAddress: utils.TestHexToFelt(t, "0x043784df59268c02b716e20bf77797bd96c68c2f100b2a634e448c35e3ad363e"), - SetKS: true, - PubKey: utils.TestHexToFelt(t, "0x049f060d2dffd3bf6f2c103b710baf519530df44529045f92c3903097e8d861f"), - PrivKey: utils.TestHexToFelt(t, "0x043b7fe9d91942c98cd5fd37579bd99ec74f879c4c79d886633eecae9dad35fa"), - InvokeTx: rpc.InvokeTxnV1{ - Nonce: new(felt.Felt).SetUint64(2), - MaxFee: utils.TestHexToFelt(t, "0x574fbde6000"), - Version: rpc.TransactionV1, - Type: rpc.TransactionType_Invoke, - SenderAddress: utils.TestHexToFelt(t, "0x043784df59268c02b716e20bf77797bd96c68c2f100b2a634e448c35e3ad363e"), - }, - FnCall: rpc.FunctionCall{ - ContractAddress: utils.TestHexToFelt(t, "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), - EntryPointSelector: utils.GetSelectorFromNameFelt("transfer"), - Calldata: []*felt.Felt{ - utils.TestHexToFelt(t, "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), - utils.TestHexToFelt(t, "0x1"), + "testnet": { + { + // https://goerli.voyager.online/tx/0x73cf79c4bfa0c7a41f473c07e1be5ac25faa7c2fdf9edcbd12c1438f40f13d8#overview + ExpectedError: rpc.ErrDuplicateTx, + CairoContractVersion: 0, + AccountAddress: utils.TestHexToFelt(t, "0x043784df59268c02b716e20bf77797bd96c68c2f100b2a634e448c35e3ad363e"), + SetKS: true, + PubKey: utils.TestHexToFelt(t, "0x049f060d2dffd3bf6f2c103b710baf519530df44529045f92c3903097e8d861f"), + PrivKey: utils.TestHexToFelt(t, "0x043b7fe9d91942c98cd5fd37579bd99ec74f879c4c79d886633eecae9dad35fa"), + InvokeTx: rpc.InvokeTxnV1{ + Nonce: new(felt.Felt).SetUint64(2), + MaxFee: utils.TestHexToFelt(t, "0x574fbde6000"), + Version: rpc.TransactionV1, + Type: rpc.TransactionType_Invoke, + SenderAddress: utils.TestHexToFelt(t, "0x043784df59268c02b716e20bf77797bd96c68c2f100b2a634e448c35e3ad363e"), + }, + FnCall: rpc.FunctionCall{ + ContractAddress: utils.TestHexToFelt(t, "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), + EntryPointSelector: utils.GetSelectorFromNameFelt("transfer"), + Calldata: []*felt.Felt{ + utils.TestHexToFelt(t, "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"), + utils.TestHexToFelt(t, "0x1"), + }, }, }, - }, { // https://goerli.voyager.online/tx/0x171537c58b16db45aeec3d3f493617cd3dd571561b856c115dc425b85212c86#overview - ExpectedHash: utils.TestHexToFelt(t, "0x171537c58b16db45aeec3d3f493617cd3dd571561b856c115dc425b85212c86"), - ExpectedError: rpc.ErrDuplicateTx, - AccountAddress: utils.TestHexToFelt(t, "0x043784df59268c02b716e20bf77797bd96c68c2f100b2a634e448c35e3ad363e"), - SetKS: true, - PubKey: utils.TestHexToFelt(t, "0x049f060d2dffd3bf6f2c103b710baf519530df44529045f92c3903097e8d861f"), - PrivKey: utils.TestHexToFelt(t, "0x043b7fe9d91942c98cd5fd37579bd99ec74f879c4c79d886633eecae9dad35fa"), + ExpectedError: rpc.ErrDuplicateTx, + CairoContractVersion: 0, + AccountAddress: utils.TestHexToFelt(t, "0x043784df59268c02b716e20bf77797bd96c68c2f100b2a634e448c35e3ad363e"), + SetKS: true, + PubKey: utils.TestHexToFelt(t, "0x049f060d2dffd3bf6f2c103b710baf519530df44529045f92c3903097e8d861f"), + PrivKey: utils.TestHexToFelt(t, "0x043b7fe9d91942c98cd5fd37579bd99ec74f879c4c79d886633eecae9dad35fa"), InvokeTx: rpc.InvokeTxnV1{ Nonce: new(felt.Felt).SetUint64(6), MaxFee: utils.TestHexToFelt(t, "0x9184e72a000"), @@ -376,12 +390,77 @@ func TestAddInvoke(t *testing.T) { }, }, }, + { + // https://goerli.voyager.online/tx/0x1bc0f8c04584735ea9e4485f927c25a6e025bda3117beb508cd1bb5e41f08d9 + ExpectedError: rpc.ErrDuplicateTx, + CairoContractVersion: 2, + AccountAddress: utils.TestHexToFelt(t, "0x0088d0038623a89bf853c70ea68b1062ccf32b094d1d7e5f924cda8404dc73e1"), + SetKS: true, + PubKey: utils.TestHexToFelt(t, "0x7ed3c6482e12c3ef7351214d1195ee7406d814af04a305617599ff27be43883"), + PrivKey: utils.TestHexToFelt(t, "0x07514c4f0de1f800b0b0c7377ef39294ce218a7abd9a1c9b6aa574779f7cdc6a"), + InvokeTx: rpc.InvokeTxnV1{ + Nonce: new(felt.Felt).SetUint64(6), + MaxFee: utils.TestHexToFelt(t, "0x9184e72a000"), + Version: rpc.TransactionV1, + Type: rpc.TransactionType_Invoke, + SenderAddress: utils.TestHexToFelt(t, "0x0088d0038623a89bf853c70ea68b1062ccf32b094d1d7e5f924cda8404dc73e1"), + }, + FnCall: rpc.FunctionCall{ + ContractAddress: utils.TestHexToFelt(t, "0x05044dfb70b9475663e3ddddb11bbbeccc71614b8db86fc3dc0c16b2b9d3151d"), + EntryPointSelector: utils.GetSelectorFromNameFelt("increase_value_8"), + Calldata: []*felt.Felt{ + utils.TestHexToFelt(t, "0x1234"), + }, + }, + }, + { + // https://goerli.voyager.online/tx/0xe8cdb03ddc6b65c2c268eb8084bef41ef63009c10a38f8d1e167652a721588 + ExpectedError: rpc.ErrDuplicateTx, + CairoContractVersion: 2, + AccountAddress: utils.TestHexToFelt(t, "0x0088d0038623a89bf853c70ea68b1062ccf32b094d1d7e5f924cda8404dc73e1"), + SetKS: true, + PubKey: utils.TestHexToFelt(t, "0x7ed3c6482e12c3ef7351214d1195ee7406d814af04a305617599ff27be43883"), + PrivKey: utils.TestHexToFelt(t, "0x07514c4f0de1f800b0b0c7377ef39294ce218a7abd9a1c9b6aa574779f7cdc6a"), + InvokeTx: rpc.InvokeTxnV1{ + Nonce: new(felt.Felt).SetUint64(7), + MaxFee: utils.TestHexToFelt(t, "0x9184e72a000"), + Version: rpc.TransactionV1, + Type: rpc.TransactionType_Invoke, + SenderAddress: utils.TestHexToFelt(t, "0x0088d0038623a89bf853c70ea68b1062ccf32b094d1d7e5f924cda8404dc73e1"), + }, + FnCall: rpc.FunctionCall{ + ContractAddress: utils.TestHexToFelt(t, "0x05044dfb70b9475663e3ddddb11bbbeccc71614b8db86fc3dc0c16b2b9d3151d"), + EntryPointSelector: utils.GetSelectorFromNameFelt("increase_value"), + Calldata: []*felt.Felt{}, + }, + }, + { + // https://goerli.voyager.online/tx/0xdcec9fdd48440243fa8fdb8bf87cc40d5ef91181d5a4a0304140df5701c238 + ExpectedError: rpc.ErrDuplicateTx, + CairoContractVersion: 2, + AccountAddress: utils.TestHexToFelt(t, "0x0088d0038623a89bf853c70ea68b1062ccf32b094d1d7e5f924cda8404dc73e1"), + SetKS: true, + PubKey: utils.TestHexToFelt(t, "0x7ed3c6482e12c3ef7351214d1195ee7406d814af04a305617599ff27be43883"), + PrivKey: utils.TestHexToFelt(t, "0x07514c4f0de1f800b0b0c7377ef39294ce218a7abd9a1c9b6aa574779f7cdc6a"), + InvokeTx: rpc.InvokeTxnV1{ + Nonce: new(felt.Felt).SetUint64(18), + MaxFee: utils.TestHexToFelt(t, "0x9184e72a000"), + Version: rpc.TransactionV1, + Type: rpc.TransactionType_Invoke, + SenderAddress: utils.TestHexToFelt(t, "0x0088d0038623a89bf853c70ea68b1062ccf32b094d1d7e5f924cda8404dc73e1"), + }, + FnCall: rpc.FunctionCall{ + ContractAddress: utils.TestHexToFelt(t, "0x05044dfb70b9475663e3ddddb11bbbeccc71614b8db86fc3dc0c16b2b9d3151d"), + EntryPointSelector: utils.GetSelectorFromNameFelt("increase_value_8"), + Calldata: []*felt.Felt{utils.TestHexToFelt(t, "0xaC25b2B9F4ca06179fA0D2522F47Bc86A9DF9314")}, + }, + }, }, "mainnet": {}, }[testEnv] for _, test := range testSet { - client, err := rpc.NewClient(base + "/rpc") + client, err := rpc.NewClient(base) require.NoError(t, err, "Error in rpc.NewClient") provider := rpc.NewProvider(client) @@ -396,15 +475,19 @@ func TestAddInvoke(t *testing.T) { acnt, err := account.NewAccount(provider, test.AccountAddress, test.PubKey.String(), ks) require.NoError(t, err) - require.NoError(t, acnt.BuildInvokeTx(context.Background(), &test.InvokeTx, &[]rpc.FunctionCall{test.FnCall}), "Error building Invoke") + test.InvokeTx.Calldata, err = acnt.FmtCalldata([]rpc.FunctionCall{test.FnCall}, test.CairoContractVersion) + require.NoError(t, err) - txHash, err := acnt.TransactionHashInvoke(test.InvokeTx) + err = acnt.SignInvokeTransaction(context.Background(), &test.InvokeTx) require.NoError(t, err) - require.Equal(t, txHash.String(), test.ExpectedHash.String()) resp, err := acnt.AddInvokeTransaction(context.Background(), test.InvokeTx) - require.Equal(t, err.Error(), test.ExpectedError.Error()) - require.Nil(t, resp) + fmt.Println(resp, err) + if err != nil { + require.Equal(t, err.Error(), test.ExpectedError.Error()) + require.Nil(t, resp) + } + } } @@ -416,12 +499,17 @@ func TestAddDeployAccountDevnet(t *testing.T) { require.NoError(t, err, "Error in rpc.NewClient") provider := rpc.NewProvider(client) - acnts, err := newDevnet(t, base) + devnet, acnts, err := newDevnet(t, base) require.NoError(t, err, "Error setting up Devnet") fakeUser := acnts[0] fakeUserAddr := utils.TestHexToFelt(t, fakeUser.Address) fakeUserPub := utils.TestHexToFelt(t, fakeUser.PublicKey) + // mint some FeeToken + mintResp, err := devnet.Mint(fakeUserAddr, new(big.Int).SetInt64(10000000000000)) + fmt.Println(mintResp) + require.NoError(t, err, "Error devnet.Mint") + // Set up ks ks := account.NewMemKeystore() fakePrivKeyBI, ok := new(big.Int).SetString(fakeUser.PrivateKey, 0) @@ -436,7 +524,7 @@ func TestAddDeployAccountDevnet(t *testing.T) { tx := rpc.DeployAccountTxn{ Nonce: &felt.Zero, // Contract accounts start with nonce zero. - MaxFee: new(felt.Felt).SetUint64(4724395326064), + MaxFee: new(felt.Felt).SetUint64(472439532606), Type: rpc.TransactionType_DeployAccount, Version: rpc.TransactionV1, Signature: []*felt.Felt{}, @@ -444,11 +532,14 @@ func TestAddDeployAccountDevnet(t *testing.T) { ContractAddressSalt: fakeUserPub, ConstructorCalldata: []*felt.Felt{fakeUserPub}, } + qwe, err := json.MarshalIndent(tx, "", "") + fmt.Println(string(qwe)) precomputedAddress, err := acnt.PrecomputeAddress(&felt.Zero, fakeUserPub, classHash, tx.ConstructorCalldata) require.NoError(t, acnt.SignDeployAccountTransaction(context.Background(), &tx, precomputedAddress)) resp, err := acnt.AddDeployAccountTransaction(context.Background(), tx) + fmt.Println(resp, err) require.NoError(t, err, "AddDeployAccountTransaction gave an Error") require.NotNil(t, resp, "AddDeployAccountTransaction resp not nil") } @@ -707,8 +798,8 @@ func TestAddDeclareTxn(t *testing.T) { } } -func newDevnet(t *testing.T, url string) ([]devnet.TestAccount, error) { +func newDevnet(t *testing.T, url string) (*devnet.DevNet, []devnet.TestAccount, error) { devnet := devnet.NewDevNet(url) acnts, err := devnet.Accounts() - return acnts, err + return devnet, acnts, err }