Skip to content

Commit

Permalink
Implement formatCallDAta for Cairo2 (#399)
Browse files Browse the repository at this point in the history
* Implement formatCallDAta for Cairo2

* Add another test for increase_value()

* add another test

* remove prints and fix typo

---------

Co-authored-by: Carmen Cabrera <[email protected]>
  • Loading branch information
rianhughes and cicr99 committed Oct 10, 2023
1 parent 1de1a44 commit f1a671a
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 68 deletions.
78 changes: 54 additions & 24 deletions account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,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)
Expand Down Expand Up @@ -317,12 +316,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)
}
Expand Down Expand Up @@ -420,26 +413,63 @@ 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 2 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
}
170 changes: 126 additions & 44 deletions account/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,22 @@ 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
}
testSet := map[string][]testSetType{
"devnet": {},
"mock": {
{
CairoVersion: 0,
ChainID: "SN_GOERLI",
FnCall: rpc.FunctionCall{
ContractAddress: utils.TestHexToFelt(t, "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"),
EntryPointSelector: utils.GetSelectorFromNameFelt("transfer"),
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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"),
Expand All @@ -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)

Expand All @@ -396,15 +475,18 @@ 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)
if err != nil {
require.Equal(t, err.Error(), test.ExpectedError.Error())
require.Nil(t, resp)
}

}
}

Expand Down

0 comments on commit f1a671a

Please sign in to comment.