diff --git a/clients/multiversx/mxClientDataGetter.go b/clients/multiversx/mxClientDataGetter.go index 837c8712..ea57b7d0 100644 --- a/clients/multiversx/mxClientDataGetter.go +++ b/clients/multiversx/mxClientDataGetter.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/multiversx/mx-bridge-eth-go/clients" + "github.com/multiversx/mx-bridge-eth-go/errors" "github.com/multiversx/mx-chain-core-go/core/check" logger "github.com/multiversx/mx-chain-logger-go" "github.com/multiversx/mx-sdk-go/builders" @@ -111,7 +112,7 @@ func (dataGetter *mxClientDataGetter) ExecuteQueryReturningBytes(ctx context.Con "response.ReturnCode", response.Data.ReturnCode, "response.ReturnData", fmt.Sprintf("%+v", response.Data.ReturnData)) if response.Data.ReturnCode != okCodeAfterExecution { - return nil, NewQueryResponseError( + return nil, errors.NewQueryResponseError( response.Data.ReturnCode, response.Data.ReturnMessage, request.FuncName, @@ -178,7 +179,7 @@ func (dataGetter *mxClientDataGetter) parseBool(buff []byte, funcName string, ad result, err := strconv.ParseBool(fmt.Sprintf("%d", buff[0])) if err != nil { - return false, NewQueryResponseError( + return false, errors.NewQueryResponseError( internalError, fmt.Sprintf("error converting the received bytes to bool, %s", err.Error()), funcName, @@ -206,7 +207,7 @@ func (dataGetter *mxClientDataGetter) ExecuteQueryReturningUint64(ctx context.Co num, err := parseUInt64FromByteSlice(response[0]) if err != nil { - return 0, NewQueryResponseError( + return 0, errors.NewQueryResponseError( internalError, err.Error(), request.FuncName, diff --git a/clients/multiversx/mxClientDataGetter_test.go b/clients/multiversx/mxClientDataGetter_test.go index 7659a80d..4d3ad7cd 100644 --- a/clients/multiversx/mxClientDataGetter_test.go +++ b/clients/multiversx/mxClientDataGetter_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/multiversx/mx-bridge-eth-go/clients" + bridgeErrors "github.com/multiversx/mx-bridge-eth-go/errors" "github.com/multiversx/mx-bridge-eth-go/parsers" bridgeTests "github.com/multiversx/mx-bridge-eth-go/testsCommon/bridge" "github.com/multiversx/mx-bridge-eth-go/testsCommon/interactors" @@ -198,7 +199,7 @@ func TestMXClientDataGetter_ExecuteQueryReturningBytes(t *testing.T) { dg, _ := NewMXClientDataGetter(args) - expectedErr := NewQueryResponseError(returnCode, returnMessage, calledFunction, getBech32Address(dg.multisigContractAddress), calledArgs...) + expectedErr := bridgeErrors.NewQueryResponseError(returnCode, returnMessage, calledFunction, getBech32Address(dg.multisigContractAddress), calledArgs...) dg.proxy = &interactors.ProxyStub{ ExecuteVMQueryCalled: func(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) { return &data.VmValuesResponseData{ @@ -306,7 +307,7 @@ func TestMXClientDataGetter_ExecuteQueryReturningBool(t *testing.T) { dg, _ := NewMXClientDataGetter(args) dg.proxy = createMockProxy([][]byte{[]byte("random bytes")}) - expectedError := NewQueryResponseError( + expectedError := bridgeErrors.NewQueryResponseError( internalError, `error converting the received bytes to bool, strconv.ParseBool: parsing "114": invalid syntax`, "", @@ -374,7 +375,7 @@ func TestMXClientDataGetter_ExecuteQueryReturningUint64(t *testing.T) { dg, _ := NewMXClientDataGetter(args) dg.proxy = createMockProxy([][]byte{[]byte("random bytes")}) - expectedError := NewQueryResponseError( + expectedError := bridgeErrors.NewQueryResponseError( internalError, errNotUint64Bytes.Error(), "", @@ -960,7 +961,7 @@ func TestMXClientDataGetter_GetTransactionsStatuses(t *testing.T) { result, err := dg.GetTransactionsStatuses(context.Background(), batchID) assert.Nil(t, result) - expectedErr := NewQueryResponseError(internalError, `error converting the received bytes to bool, strconv.ParseBool: parsing "56": invalid syntax`, + expectedErr := bridgeErrors.NewQueryResponseError(internalError, `error converting the received bytes to bool, strconv.ParseBool: parsing "56": invalid syntax`, "getStatusesAfterExecution", "erd1qqqqqqqqqqqqqpgqzyuaqg3dl7rqlkudrsnm5ek0j3a97qevd8sszj0glf") assert.Equal(t, expectedErr, err) }) diff --git a/clients/multiversx/queryResponseError.go b/errors/queryResponseError.go similarity index 97% rename from clients/multiversx/queryResponseError.go rename to errors/queryResponseError.go index 7a794d43..70df2472 100644 --- a/clients/multiversx/queryResponseError.go +++ b/errors/queryResponseError.go @@ -1,4 +1,4 @@ -package multiversx +package errors import "fmt" diff --git a/clients/multiversx/queryResponseError_test.go b/errors/queryResponseError_test.go similarity index 95% rename from clients/multiversx/queryResponseError_test.go rename to errors/queryResponseError_test.go index 434926a2..faf261f4 100644 --- a/clients/multiversx/queryResponseError_test.go +++ b/errors/queryResponseError_test.go @@ -1,4 +1,4 @@ -package multiversx +package errors import ( "testing" diff --git a/executors/multiversx/errors.go b/executors/multiversx/errors.go new file mode 100644 index 00000000..6ee6f0d6 --- /dev/null +++ b/executors/multiversx/errors.go @@ -0,0 +1,14 @@ +package multiversx + +import "errors" + +var ( + errInvalidNumberOfResponseLines = errors.New("invalid number of responses") + errNilProxy = errors.New("nil proxy") + errNilCodec = errors.New("nil codec") + errNilFilter = errors.New("nil filter") + errNilLogger = errors.New("nil logger") + errNilNonceTxHandler = errors.New("nil nonce transaction handler") + errNilPrivateKey = errors.New("nil private key") + errNilSingleSigner = errors.New("nil single signer") +) diff --git a/executors/multiversx/interface.go b/executors/multiversx/interface.go new file mode 100644 index 00000000..26b2a7de --- /dev/null +++ b/executors/multiversx/interface.go @@ -0,0 +1,49 @@ +package multiversx + +import ( + "context" + + "github.com/multiversx/mx-bridge-eth-go/parsers" + "github.com/multiversx/mx-chain-core-go/data/api" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-sdk-go/core" + "github.com/multiversx/mx-sdk-go/data" +) + +// Proxy defines the behavior of a proxy able to serve MultiversX blockchain requests +type Proxy interface { + GetNetworkConfig(ctx context.Context) (*data.NetworkConfig, error) + SendTransaction(ctx context.Context, tx *transaction.FrontendTransaction) (string, error) + SendTransactions(ctx context.Context, txs []*transaction.FrontendTransaction) ([]string, error) + ExecuteVMQuery(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) + GetAccount(ctx context.Context, address core.AddressHandler) (*data.Account, error) + GetNetworkStatus(ctx context.Context, shardID uint32) (*data.NetworkStatus, error) + GetShardOfAddress(ctx context.Context, bech32Address string) (uint32, error) + GetESDTTokenData(ctx context.Context, address core.AddressHandler, tokenIdentifier string, queryOptions api.AccountQueryOptions) (*data.ESDTFungibleTokenData, error) + GetTransactionInfoWithResults(ctx context.Context, hash string) (*data.TransactionInfo, error) + ProcessTransactionStatus(ctx context.Context, hexTxHash string) (transaction.TxStatus, error) + IsInterfaceNil() bool +} + +// NonceTransactionsHandler represents the interface able to handle the current nonce and the transactions resend mechanism +type NonceTransactionsHandler interface { + ApplyNonceAndGasPrice(ctx context.Context, address core.AddressHandler, tx *transaction.FrontendTransaction) error + SendTransaction(ctx context.Context, tx *transaction.FrontendTransaction) (string, error) + Close() error + IsInterfaceNil() bool +} + +// ScCallsExecuteFilter defines the operations supported by a filter that allows selective executions of batches +type ScCallsExecuteFilter interface { + ShouldExecute(callData parsers.ProxySCCompleteCallData) bool + IsInterfaceNil() bool +} + +// Codec defines the operations implemented by a MultiversX codec +type Codec interface { + EncodeCallData(callData parsers.CallData) []byte + EncodeProxySCCompleteCallData(completeData parsers.ProxySCCompleteCallData) ([]byte, error) + DecodeCallData(buff []byte) (parsers.CallData, error) + DecodeProxySCCompleteCallData(buff []byte) (parsers.ProxySCCompleteCallData, error) + IsInterfaceNil() bool +} diff --git a/executors/multiversx/scCallsExecutor.go b/executors/multiversx/scCallsExecutor.go new file mode 100644 index 00000000..79c8bdbd --- /dev/null +++ b/executors/multiversx/scCallsExecutor.go @@ -0,0 +1,266 @@ +package multiversx + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + + "github.com/multiversx/mx-bridge-eth-go/errors" + "github.com/multiversx/mx-bridge-eth-go/parsers" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data/transaction" + crypto "github.com/multiversx/mx-chain-crypto-go" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-sdk-go/builders" + "github.com/multiversx/mx-sdk-go/core" + "github.com/multiversx/mx-sdk-go/data" +) + +const ( + getPendingTransactionsFunction = "getPendingTransactions" + okCodeAfterExecution = "ok" + scProxyCallFunction = "execute" +) + +// ArgsScCallExecutor represents the DTO struct for creating a new instance of type scCallExecutor +type ArgsScCallExecutor struct { + ScProxyBech32Address string + Proxy Proxy + Codec Codec + Filter ScCallsExecuteFilter + Log logger.Logger + ExtraGasToExecute uint64 + NonceTxHandler NonceTransactionsHandler + PrivateKey crypto.PrivateKey + SingleSigner crypto.SingleSigner +} + +type scCallExecutor struct { + scProxyBech32Address string + proxy Proxy + codec Codec + filter ScCallsExecuteFilter + log logger.Logger + extraGasToExecute uint64 + nonceTxHandler NonceTransactionsHandler + privateKey crypto.PrivateKey + singleSigner crypto.SingleSigner + senderAddress core.AddressHandler +} + +// NewScCallExecutor creates a new instance of type scCallExecutor +func NewScCallExecutor(args ArgsScCallExecutor) (*scCallExecutor, error) { + err := checkArgs(args) + if err != nil { + return nil, err + } + + publicKey := args.PrivateKey.GeneratePublic() + publicKeyBytes, err := publicKey.ToByteArray() + if err != nil { + return nil, err + } + senderAddress := data.NewAddressFromBytes(publicKeyBytes) + + return &scCallExecutor{ + scProxyBech32Address: args.ScProxyBech32Address, + proxy: args.Proxy, + codec: args.Codec, + filter: args.Filter, + log: args.Log, + extraGasToExecute: args.ExtraGasToExecute, + nonceTxHandler: args.NonceTxHandler, + privateKey: args.PrivateKey, + singleSigner: args.SingleSigner, + senderAddress: senderAddress, + }, nil +} + +func checkArgs(args ArgsScCallExecutor) error { + if check.IfNil(args.Proxy) { + return errNilProxy + } + if check.IfNil(args.Codec) { + return errNilCodec + } + if check.IfNil(args.Filter) { + return errNilFilter + } + if check.IfNil(args.Log) { + return errNilLogger + } + if check.IfNil(args.NonceTxHandler) { + return errNilNonceTxHandler + } + if check.IfNil(args.PrivateKey) { + return errNilPrivateKey + } + if check.IfNil(args.SingleSigner) { + return errNilSingleSigner + } + + return nil +} + +// Execute will execute one step: get all pending operations, call the filter and send execution transactions +func (executor *scCallExecutor) Execute(ctx context.Context) error { + pendingOperations, err := executor.getPendingOperations(ctx) + if err != nil { + return err + } + + filteredPendingOperations := executor.filterOperations(pendingOperations) + + return executor.executeOperations(ctx, filteredPendingOperations) +} + +func (executor *scCallExecutor) getPendingOperations(ctx context.Context) (map[uint64]parsers.ProxySCCompleteCallData, error) { + request := &data.VmValueRequest{ + Address: executor.scProxyBech32Address, + FuncName: getPendingTransactionsFunction, + } + + response, err := executor.proxy.ExecuteVMQuery(ctx, request) + if err != nil { + executor.log.Error("got error on VMQuery", "FuncName", request.FuncName, + "Args", request.Args, "SC address", request.Address, "Caller", request.CallerAddr, "error", err) + return nil, err + } + if response.Data.ReturnCode != okCodeAfterExecution { + return nil, errors.NewQueryResponseError( + response.Data.ReturnCode, + response.Data.ReturnMessage, + request.FuncName, + request.Address, + request.Args..., + ) + } + + return executor.parseResponse(response) +} + +func (executor *scCallExecutor) parseResponse(response *data.VmValuesResponseData) (map[uint64]parsers.ProxySCCompleteCallData, error) { + numResponseLines := len(response.Data.ReturnData) + if numResponseLines%2 != 0 { + return nil, fmt.Errorf("%w: expected an even number, got %d", errInvalidNumberOfResponseLines, numResponseLines) + } + + result := make(map[uint64]parsers.ProxySCCompleteCallData, numResponseLines/2) + + for i := 0; i < numResponseLines; i += 2 { + pendingOperationID := big.NewInt(0).SetBytes(response.Data.ReturnData[i]) + callData, err := executor.codec.DecodeProxySCCompleteCallData(response.Data.ReturnData[i+1]) + if err != nil { + return nil, fmt.Errorf("%w for ReturnData at index %d", err, i+1) + } + + result[pendingOperationID.Uint64()] = callData + } + + return result, nil +} + +func (executor *scCallExecutor) filterOperations(pendingOperations map[uint64]parsers.ProxySCCompleteCallData) map[uint64]parsers.ProxySCCompleteCallData { + result := make(map[uint64]parsers.ProxySCCompleteCallData) + for id, callData := range pendingOperations { + if executor.filter.ShouldExecute(callData) { + result[id] = callData + } + } + + return result +} + +func (executor *scCallExecutor) executeOperations(ctx context.Context, pendingOperations map[uint64]parsers.ProxySCCompleteCallData) error { + networkConfig, err := executor.proxy.GetNetworkConfig(ctx) + if err != nil { + return fmt.Errorf("%w while fetching network configs", err) + } + + for id, callData := range pendingOperations { + err = executor.executeOperation(id, callData, networkConfig) + if err != nil { + return fmt.Errorf("%w for call data: %s", err, callData) + } + } + + return nil +} + +func (executor *scCallExecutor) executeOperation( + id uint64, + callData parsers.ProxySCCompleteCallData, + networkConfig *data.NetworkConfig, +) error { + txBuilder := builders.NewTxDataBuilder() + txBuilder.Function(scProxyCallFunction).ArgInt64(int64(id)) + + dataBytes, err := txBuilder.ToDataBytes() + if err != nil { + return err + } + + bech32Address, err := executor.senderAddress.AddressAsBech32String() + if err != nil { + return err + } + + tx := &transaction.FrontendTransaction{ + ChainID: networkConfig.ChainID, + Version: networkConfig.MinTransactionVersion, + GasLimit: callData.GasLimit + executor.extraGasToExecute, + Data: dataBytes, + Sender: bech32Address, + Receiver: executor.scProxyBech32Address, + Value: "0", + } + + err = executor.nonceTxHandler.ApplyNonceAndGasPrice(context.Background(), executor.senderAddress, tx) + if err != nil { + return err + } + + err = executor.signTransactionWithPrivateKey(tx) + if err != nil { + return err + } + + hash, err := executor.nonceTxHandler.SendTransaction(context.Background(), tx) + if err != nil { + return err + } + + executor.log.Info("sent transaction from executor", + "hash", hash, + "tx ID", id, + "call data", callData.String(), + "extra gas", executor.extraGasToExecute, + "sender", bech32Address) + + return nil +} + +// signTransactionWithPrivateKey signs a transaction with the client's private key +func (executor *scCallExecutor) signTransactionWithPrivateKey(tx *transaction.FrontendTransaction) error { + tx.Signature = "" + bytes, err := json.Marshal(&tx) + if err != nil { + return err + } + + signature, err := executor.singleSigner.Sign(executor.privateKey, bytes) + if err != nil { + return err + } + + tx.Signature = hex.EncodeToString(signature) + + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (executor *scCallExecutor) IsInterfaceNil() bool { + return executor == nil +} diff --git a/executors/multiversx/scCallsExecutor_test.go b/executors/multiversx/scCallsExecutor_test.go new file mode 100644 index 00000000..7c1c8864 --- /dev/null +++ b/executors/multiversx/scCallsExecutor_test.go @@ -0,0 +1,499 @@ +package multiversx + +import ( + "context" + "encoding/hex" + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/multiversx/mx-bridge-eth-go/parsers" + "github.com/multiversx/mx-bridge-eth-go/testsCommon" + testCrypto "github.com/multiversx/mx-bridge-eth-go/testsCommon/crypto" + "github.com/multiversx/mx-bridge-eth-go/testsCommon/interactors" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-core-go/data/vm" + crypto "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-sdk-go/core" + "github.com/multiversx/mx-sdk-go/data" + "github.com/stretchr/testify/assert" +) + +func createMockArgsScCallExecutor() ArgsScCallExecutor { + return ArgsScCallExecutor{ + ScProxyBech32Address: "erd1qqqqqqqqqqqqqpgqk839entmk46ykukvhpn90g6knskju3dtanaq20f66e", + Proxy: &interactors.ProxyStub{}, + Codec: &testsCommon.MultiversxCodecStub{}, + Filter: &testsCommon.ScCallsExecuteFilterStub{}, + Log: &testsCommon.LoggerStub{}, + ExtraGasToExecute: 100, + NonceTxHandler: &testsCommon.TxNonceHandlerV2Stub{}, + PrivateKey: testCrypto.NewPrivateKeyMock(), + SingleSigner: &testCrypto.SingleSignerStub{}, + } +} + +func createTestProxySCCompleteCallData(token string) parsers.ProxySCCompleteCallData { + callData := parsers.ProxySCCompleteCallData{ + CallData: parsers.CallData{ + Type: 1, + Function: "callMe", + GasLimit: 5000000, + Arguments: []string{"arg1", "arg2"}, + }, + From: common.Address{}, + Token: token, + Amount: big.NewInt(37), + Nonce: 1, + } + callData.To, _ = data.NewAddressFromBech32String("erd1qqqqqqqqqqqqqpgqnf2w270lhxhlj57jvthxw4tqsunrwnq0anaqm4d4fn") + + return callData +} + +func TestNewScCallExecutor(t *testing.T) { + t.Parallel() + + t.Run("nil proxy should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + args.Proxy = nil + + executor, err := NewScCallExecutor(args) + assert.Nil(t, executor) + assert.Equal(t, errNilProxy, err) + }) + t.Run("nil codec should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + args.Codec = nil + + executor, err := NewScCallExecutor(args) + assert.Nil(t, executor) + assert.Equal(t, errNilCodec, err) + }) + t.Run("nil filter should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + args.Filter = nil + + executor, err := NewScCallExecutor(args) + assert.Nil(t, executor) + assert.Equal(t, errNilFilter, err) + }) + t.Run("nil logger should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + args.Log = nil + + executor, err := NewScCallExecutor(args) + assert.Nil(t, executor) + assert.Equal(t, errNilLogger, err) + }) + t.Run("nil nonce tx handler should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + args.NonceTxHandler = nil + + executor, err := NewScCallExecutor(args) + assert.Nil(t, executor) + assert.Equal(t, errNilNonceTxHandler, err) + }) + t.Run("nil private key should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + args.PrivateKey = nil + + executor, err := NewScCallExecutor(args) + assert.Nil(t, executor) + assert.Equal(t, errNilPrivateKey, err) + }) + t.Run("nil single signer should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + args.SingleSigner = nil + + executor, err := NewScCallExecutor(args) + assert.Nil(t, executor) + assert.Equal(t, errNilSingleSigner, err) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + + executor, err := NewScCallExecutor(args) + assert.NotNil(t, executor) + assert.Nil(t, err) + }) +} + +func TestScCallExecutor_IsInterfaceNil(t *testing.T) { + t.Parallel() + + var instance *scCallExecutor + assert.True(t, instance.IsInterfaceNil()) + + instance = &scCallExecutor{} + assert.False(t, instance.IsInterfaceNil()) +} + +func TestScCallExecutor_Execute(t *testing.T) { + t.Parallel() + + runError := errors.New("run error") + expectedError := errors.New("expected error") + + argsForErrors := createMockArgsScCallExecutor() + argsForErrors.NonceTxHandler = &testsCommon.TxNonceHandlerV2Stub{ + ApplyNonceAndGasPriceCalled: func(ctx context.Context, address core.AddressHandler, tx *transaction.FrontendTransaction) error { + assert.Fail(t, "should have not called ApplyNonceAndGasPriceCalled") + return runError + }, + SendTransactionCalled: func(ctx context.Context, tx *transaction.FrontendTransaction) (string, error) { + assert.Fail(t, "should have not called SendTransactionCalled") + return "", runError + }, + } + + t.Run("get pending errors, should error", func(t *testing.T) { + t.Parallel() + + args := argsForErrors // value copy + args.Proxy = &interactors.ProxyStub{ + ExecuteVMQueryCalled: func(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) { + return nil, expectedError + }, + } + + executor, _ := NewScCallExecutor(args) + err := executor.Execute(context.Background()) + assert.Equal(t, expectedError, err) + }) + t.Run("get pending returns a not ok status, should error", func(t *testing.T) { + t.Parallel() + + args := argsForErrors // value copy + args.Proxy = &interactors.ProxyStub{ + ExecuteVMQueryCalled: func(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) { + return &data.VmValuesResponseData{ + Data: &vm.VMOutputApi{ + ReturnCode: "NOT OK", + }, + }, nil + }, + } + + executor, _ := NewScCallExecutor(args) + err := executor.Execute(context.Background()) + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "got response code 'NOT OK'") + }) + t.Run("get pending returns an odd number of lines, should error", func(t *testing.T) { + t.Parallel() + + args := argsForErrors // value copy + args.Proxy = &interactors.ProxyStub{ + ExecuteVMQueryCalled: func(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) { + return &data.VmValuesResponseData{ + Data: &vm.VMOutputApi{ + ReturnCode: okCodeAfterExecution, + ReturnData: [][]byte{ + {0x01}, + }, + }, + }, nil + }, + } + + executor, _ := NewScCallExecutor(args) + err := executor.Execute(context.Background()) + assert.ErrorIs(t, err, errInvalidNumberOfResponseLines) + assert.Contains(t, err.Error(), "expected an even number, got 1") + }) + t.Run("decoder errors, should error", func(t *testing.T) { + t.Parallel() + + args := argsForErrors // value copy + args.Proxy = &interactors.ProxyStub{ + ExecuteVMQueryCalled: func(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) { + return &data.VmValuesResponseData{ + Data: &vm.VMOutputApi{ + ReturnCode: okCodeAfterExecution, + ReturnData: [][]byte{ + {0x01}, + {0x03, 0x04}, + }, + }, + }, nil + }, + } + args.Codec = &testsCommon.MultiversxCodecStub{ + DecodeProxySCCompleteCallDataCalled: func(buff []byte) (parsers.ProxySCCompleteCallData, error) { + assert.Equal(t, []byte{0x03, 0x04}, buff) + + return parsers.ProxySCCompleteCallData{}, expectedError + }, + } + + executor, _ := NewScCallExecutor(args) + err := executor.Execute(context.Background()) + assert.ErrorIs(t, err, expectedError) + }) + t.Run("get network configs errors, should error", func(t *testing.T) { + t.Parallel() + + args := argsForErrors // value copy + args.Proxy = &interactors.ProxyStub{ + ExecuteVMQueryCalled: func(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) { + return &data.VmValuesResponseData{ + Data: &vm.VMOutputApi{ + ReturnCode: okCodeAfterExecution, + ReturnData: [][]byte{ + {0x01}, + {0x03, 0x04}, + }, + }, + }, nil + }, + GetNetworkConfigCalled: func(ctx context.Context) (*data.NetworkConfig, error) { + return nil, expectedError + }, + } + args.Codec = &testsCommon.MultiversxCodecStub{ + DecodeProxySCCompleteCallDataCalled: func(buff []byte) (parsers.ProxySCCompleteCallData, error) { + assert.Equal(t, []byte{0x03, 0x04}, buff) + + return parsers.ProxySCCompleteCallData{}, nil + }, + } + + executor, _ := NewScCallExecutor(args) + err := executor.Execute(context.Background()) + assert.ErrorIs(t, err, expectedError) + }) + t.Run("ApplyNonceAndGasPrice errors, should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + args.NonceTxHandler = &testsCommon.TxNonceHandlerV2Stub{ + ApplyNonceAndGasPriceCalled: func(ctx context.Context, address core.AddressHandler, tx *transaction.FrontendTransaction) error { + return expectedError + }, + SendTransactionCalled: func(ctx context.Context, tx *transaction.FrontendTransaction) (string, error) { + assert.Fail(t, "should have not called SendTransactionCalled") + return "", runError + }, + } + args.Proxy = &interactors.ProxyStub{ + ExecuteVMQueryCalled: func(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) { + return &data.VmValuesResponseData{ + Data: &vm.VMOutputApi{ + ReturnCode: okCodeAfterExecution, + ReturnData: [][]byte{ + {0x01}, + {0x03, 0x04}, + }, + }, + }, nil + }, + GetNetworkConfigCalled: func(ctx context.Context) (*data.NetworkConfig, error) { + return &data.NetworkConfig{}, nil + }, + } + args.Codec = &testsCommon.MultiversxCodecStub{ + DecodeProxySCCompleteCallDataCalled: func(buff []byte) (parsers.ProxySCCompleteCallData, error) { + assert.Equal(t, []byte{0x03, 0x04}, buff) + + return parsers.ProxySCCompleteCallData{}, nil + }, + } + + executor, _ := NewScCallExecutor(args) + err := executor.Execute(context.Background()) + assert.ErrorIs(t, err, expectedError) + }) + t.Run("Sign errors, should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + args.NonceTxHandler = &testsCommon.TxNonceHandlerV2Stub{ + ApplyNonceAndGasPriceCalled: func(ctx context.Context, address core.AddressHandler, tx *transaction.FrontendTransaction) error { + return nil + }, + SendTransactionCalled: func(ctx context.Context, tx *transaction.FrontendTransaction) (string, error) { + assert.Fail(t, "should have not called SendTransactionCalled") + return "", runError + }, + } + args.Proxy = &interactors.ProxyStub{ + ExecuteVMQueryCalled: func(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) { + return &data.VmValuesResponseData{ + Data: &vm.VMOutputApi{ + ReturnCode: okCodeAfterExecution, + ReturnData: [][]byte{ + {0x01}, + {0x03, 0x04}, + }, + }, + }, nil + }, + GetNetworkConfigCalled: func(ctx context.Context) (*data.NetworkConfig, error) { + return &data.NetworkConfig{}, nil + }, + } + args.Codec = &testsCommon.MultiversxCodecStub{ + DecodeProxySCCompleteCallDataCalled: func(buff []byte) (parsers.ProxySCCompleteCallData, error) { + assert.Equal(t, []byte{0x03, 0x04}, buff) + + return parsers.ProxySCCompleteCallData{}, nil + }, + } + args.SingleSigner = &testCrypto.SingleSignerStub{ + SignCalled: func(private crypto.PrivateKey, msg []byte) ([]byte, error) { + return nil, expectedError + }, + } + + executor, _ := NewScCallExecutor(args) + err := executor.Execute(context.Background()) + assert.ErrorIs(t, err, expectedError) + }) + t.Run("SendTransaction errors, should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + args.NonceTxHandler = &testsCommon.TxNonceHandlerV2Stub{ + ApplyNonceAndGasPriceCalled: func(ctx context.Context, address core.AddressHandler, tx *transaction.FrontendTransaction) error { + return nil + }, + SendTransactionCalled: func(ctx context.Context, tx *transaction.FrontendTransaction) (string, error) { + return "", expectedError + }, + } + args.Proxy = &interactors.ProxyStub{ + ExecuteVMQueryCalled: func(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) { + return &data.VmValuesResponseData{ + Data: &vm.VMOutputApi{ + ReturnCode: okCodeAfterExecution, + ReturnData: [][]byte{ + {0x01}, + {0x03, 0x04}, + }, + }, + }, nil + }, + GetNetworkConfigCalled: func(ctx context.Context) (*data.NetworkConfig, error) { + return &data.NetworkConfig{}, nil + }, + } + args.Codec = &testsCommon.MultiversxCodecStub{ + DecodeProxySCCompleteCallDataCalled: func(buff []byte) (parsers.ProxySCCompleteCallData, error) { + assert.Equal(t, []byte{0x03, 0x04}, buff) + + return parsers.ProxySCCompleteCallData{}, nil + }, + } + + executor, _ := NewScCallExecutor(args) + err := executor.Execute(context.Background()) + assert.ErrorIs(t, err, expectedError) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgsScCallExecutor() + + nonceCounter := uint64(100) + sendWasCalled := false + + args.Proxy = &interactors.ProxyStub{ + ExecuteVMQueryCalled: func(ctx context.Context, vmRequest *data.VmValueRequest) (*data.VmValuesResponseData, error) { + assert.Equal(t, args.ScProxyBech32Address, vmRequest.Address) + assert.Equal(t, getPendingTransactionsFunction, vmRequest.FuncName) + + return &data.VmValuesResponseData{ + Data: &vm.VMOutputApi{ + ReturnCode: okCodeAfterExecution, + ReturnData: [][]byte{ + {0x01}, + []byte("ProxySCCompleteCallData 1"), + {0x02}, + []byte("ProxySCCompleteCallData 2"), + }, + }, + }, nil + }, + GetNetworkConfigCalled: func(ctx context.Context) (*data.NetworkConfig, error) { + return &data.NetworkConfig{ + ChainID: "TEST", + MinTransactionVersion: 111, + }, nil + }, + } + args.Codec = &testsCommon.MultiversxCodecStub{ + DecodeProxySCCompleteCallDataCalled: func(buff []byte) (parsers.ProxySCCompleteCallData, error) { + if string(buff) == "ProxySCCompleteCallData 1" { + return createTestProxySCCompleteCallData("tkn1"), nil + } + if string(buff) == "ProxySCCompleteCallData 2" { + return createTestProxySCCompleteCallData("tkn2"), nil + } + + return parsers.ProxySCCompleteCallData{}, errors.New("wrong buffer") + }, + } + args.Filter = &testsCommon.ScCallsExecuteFilterStub{ + ShouldExecuteCalled: func(callData parsers.ProxySCCompleteCallData) bool { + return callData.Token == "tkn2" + }, + } + args.NonceTxHandler = &testsCommon.TxNonceHandlerV2Stub{ + ApplyNonceAndGasPriceCalled: func(ctx context.Context, address core.AddressHandler, tx *transaction.FrontendTransaction) error { + tx.Nonce = nonceCounter + tx.GasPrice = 101010 + nonceCounter++ + return nil + }, + SendTransactionCalled: func(ctx context.Context, tx *transaction.FrontendTransaction) (string, error) { + assert.Equal(t, "TEST", tx.ChainID) + assert.Equal(t, uint32(111), tx.Version) + assert.Equal(t, args.ExtraGasToExecute+5000000, tx.GasLimit) + assert.Equal(t, nonceCounter-1, tx.Nonce) + assert.Equal(t, uint64(101010), tx.GasPrice) + assert.Equal(t, hex.EncodeToString([]byte("sig")), tx.Signature) + _, err := data.NewAddressFromBech32String(tx.Sender) + assert.Nil(t, err) + assert.Equal(t, "erd1qqqqqqqqqqqqqpgqk839entmk46ykukvhpn90g6knskju3dtanaq20f66e", tx.Receiver) + assert.Equal(t, "0", tx.Value) + + // only the second pending operation gor through the filter + expectedData := scProxyCallFunction + "@02" + assert.Equal(t, expectedData, string(tx.Data)) + + sendWasCalled = true + + return "", nil + }, + } + args.SingleSigner = &testCrypto.SingleSignerStub{ + SignCalled: func(private crypto.PrivateKey, msg []byte) ([]byte, error) { + return []byte("sig"), nil + }, + } + + executor, _ := NewScCallExecutor(args) + + err := executor.Execute(context.Background()) + assert.Nil(t, err) + assert.True(t, sendWasCalled) + }) +} diff --git a/integrationTests/relayers/slowTests/ethToMultiversXWithChainSimulator_test.go b/integrationTests/relayers/slowTests/ethToMultiversXWithChainSimulator_test.go index 7d0d9aa3..07a7140d 100644 --- a/integrationTests/relayers/slowTests/ethToMultiversXWithChainSimulator_test.go +++ b/integrationTests/relayers/slowTests/ethToMultiversXWithChainSimulator_test.go @@ -79,7 +79,6 @@ const ( unpause = "unpause" unpauseEsdtSafe = "unpauseEsdtSafe" setEsdtSafeOnMultiTransfer = "setEsdtSafeOnMultiTransfer" - setMultiTransferOnEsdtSafe = "setMultiTransferOnEsdtSafe" changeOwnerAddress = "ChangeOwnerAddress" setWrappingContractAddress = "setWrappingContractAddress" setBridgeProxyContractAddress = "setBridgeProxyContractAddress" @@ -782,31 +781,35 @@ func (testSetup *simulatedSetup) executeContractsTxs() { log.Info("wrapper contract deployed", "address", testSetup.mvxWrapperAddress) - // deploy safe - testSetup.mvxSafeAddress, err = testSetup.mvxChainSimulator.DeploySC( + // deploy multi-transfer + multiTransferAddress, err := testSetup.mvxChainSimulator.DeploySC( testSetup.testContext, - safeContract, + multiTransferContract, testSetup.mvxOwnerKeys.pk, testSetup.mvxOwnerKeys.sk, - []string{getHexAddress(testSetup.t, testSetup.mvxAggregatorAddress), "01"}, + []string{}, ) require.NoError(testSetup.t, err) - require.NotEqual(testSetup.t, emptyAddress, testSetup.mvxSafeAddress) + require.NotEqual(testSetup.t, emptyAddress, multiTransferAddress) - log.Info("safe contract deployed", "address", testSetup.mvxSafeAddress) + log.Info("multi-transfer contract deployed", "address", multiTransferAddress) - // deploy multi-transfer - multiTransferAddress, err := testSetup.mvxChainSimulator.DeploySC( + // deploy safe + testSetup.mvxSafeAddress, err = testSetup.mvxChainSimulator.DeploySC( testSetup.testContext, - multiTransferContract, + safeContract, testSetup.mvxOwnerKeys.pk, testSetup.mvxOwnerKeys.sk, - []string{}, + []string{ + getHexAddress(testSetup.t, testSetup.mvxAggregatorAddress), + getHexAddress(testSetup.t, multiTransferAddress), + "01", + }, ) require.NoError(testSetup.t, err) - require.NotEqual(testSetup.t, emptyAddress, multiTransferAddress) + require.NotEqual(testSetup.t, emptyAddress, testSetup.mvxSafeAddress) - log.Info("multi-transfer contract deployed", "address", multiTransferAddress) + log.Info("safe contract deployed", "address", testSetup.mvxSafeAddress) // deploy multisig minRelayerStakeInt, _ := big.NewInt(0).SetString(minRelayerStake, 10) @@ -941,22 +944,6 @@ func (testSetup *simulatedSetup) executeContractsTxs() { log.Info("ChangeOwnerAddress for bridge proxy tx executed", "hash", hash, "status", txResult.Status) - // setMultiTransferOnEsdtSafe - hash, err = testSetup.mvxChainSimulator.ScCall( - testSetup.testContext, - testSetup.mvxOwnerKeys.pk, - testSetup.mvxOwnerKeys.sk, - testSetup.mvxMultisigAddress, - zeroValue, - setMultiTransferOnEsdtSafe, - []string{}, - ) - require.NoError(testSetup.t, err) - txResult, err = testSetup.mvxChainSimulator.GetTransactionResult(testSetup.testContext, hash) - require.NoError(testSetup.t, err) - - log.Info("setMultiTransferOnEsdtSafe tx executed", "hash", hash, "status", txResult.Status) - // setEsdtSafeOnMultiTransfer hash, err = testSetup.mvxChainSimulator.ScCall( testSetup.testContext, diff --git a/integrationTests/relayers/slowTests/testdata/contracts/mvx/bridge-proxy.abi.json b/integrationTests/relayers/slowTests/testdata/contracts/mvx/bridge-proxy.abi.json index cde2036c..44b19e5a 100644 --- a/integrationTests/relayers/slowTests/testdata/contracts/mvx/bridge-proxy.abi.json +++ b/integrationTests/relayers/slowTests/testdata/contracts/mvx/bridge-proxy.abi.json @@ -49,7 +49,7 @@ "outputs": [] }, { - "name": "executeWithAsnyc", + "name": "execute", "mutability": "mutable", "inputs": [ { @@ -74,6 +74,17 @@ } ] }, + { + "name": "getPendingTransactions", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic>", + "multi_result": true + } + ] + }, { "name": "setupMultiTransfer", "onlyOwner": true, @@ -120,17 +131,6 @@ } ] }, - { - "name": "getPendingTransactions", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "variadic", - "multi_result": true - } - ] - }, { "name": "pause", "onlyOwner": true, diff --git a/integrationTests/relayers/slowTests/testdata/contracts/mvx/bridge-proxy.wasm b/integrationTests/relayers/slowTests/testdata/contracts/mvx/bridge-proxy.wasm index d187adeb..a66cbec5 100755 Binary files a/integrationTests/relayers/slowTests/testdata/contracts/mvx/bridge-proxy.wasm and b/integrationTests/relayers/slowTests/testdata/contracts/mvx/bridge-proxy.wasm differ diff --git a/integrationTests/relayers/slowTests/testdata/contracts/mvx/esdt-safe.abi.json b/integrationTests/relayers/slowTests/testdata/contracts/mvx/esdt-safe.abi.json index 3ec619c4..74dcd470 100644 --- a/integrationTests/relayers/slowTests/testdata/contracts/mvx/esdt-safe.abi.json +++ b/integrationTests/relayers/slowTests/testdata/contracts/mvx/esdt-safe.abi.json @@ -30,6 +30,10 @@ "name": "fee_estimator_contract_address", "type": "Address" }, + { + "name": "multi_transfer_contract_address", + "type": "Address" + }, { "name": "eth_tx_gas_limit", "type": "BigUint" diff --git a/integrationTests/relayers/slowTests/testdata/contracts/mvx/esdt-safe.wasm b/integrationTests/relayers/slowTests/testdata/contracts/mvx/esdt-safe.wasm index bc88fc59..f3e6599e 100755 Binary files a/integrationTests/relayers/slowTests/testdata/contracts/mvx/esdt-safe.wasm and b/integrationTests/relayers/slowTests/testdata/contracts/mvx/esdt-safe.wasm differ diff --git a/integrationTests/relayers/slowTests/testdata/contracts/mvx/multi-transfer-esdt.wasm b/integrationTests/relayers/slowTests/testdata/contracts/mvx/multi-transfer-esdt.wasm index aefe811d..d695bb9d 100755 Binary files a/integrationTests/relayers/slowTests/testdata/contracts/mvx/multi-transfer-esdt.wasm and b/integrationTests/relayers/slowTests/testdata/contracts/mvx/multi-transfer-esdt.wasm differ diff --git a/parsers/constants.go b/parsers/constants.go index 9416e54a..5e213bd6 100644 --- a/parsers/constants.go +++ b/parsers/constants.go @@ -2,6 +2,8 @@ package parsers const uint32ArgBytes = 4 const uint64ArgBytes = 8 +const lenEthAddress = 20 +const lenMvxAddress = 32 // MissingDataProtocolMarker defines the marker for missing data (simple transfers) const MissingDataProtocolMarker byte = 0x00 diff --git a/parsers/errors.go b/parsers/errors.go index b5ebc2ed..857d6d9a 100644 --- a/parsers/errors.go +++ b/parsers/errors.go @@ -3,10 +3,15 @@ package parsers import "errors" var ( - errBufferTooShortForMarker = errors.New("buffer too short for protocol indicator") - errUnexpectedMarker = errors.New("unexpected protocol indicator") - errBufferTooShortForLength = errors.New("buffer too short while extracting the length") - errBufferTooShortForString = errors.New("buffer too short while extracting the string data") - errBufferTooShortForGasLimit = errors.New("buffer too short for gas limit") - errBufferTooShortForNumArgs = errors.New("buffer too short for numArguments length") + errBufferTooShortForMarker = errors.New("buffer too short for protocol indicator") + errUnexpectedMarker = errors.New("unexpected protocol indicator") + errBufferTooShortForLength = errors.New("buffer too short while extracting the length") + errBufferTooShortForString = errors.New("buffer too short while extracting the string data") + errBufferTooShortForUint64 = errors.New("buffer too short for uint64") + errBufferTooShortForNumArgs = errors.New("buffer too short for numArguments length") + errNilAddressHandler = errors.New("nil address handler") + errNilAmount = errors.New("nil amount") + errBufferTooShortForEthAddress = errors.New("buffer too short for Ethereum address") + errBufferTooShortForMvxAddress = errors.New("buffer too short for MultiversX address") + errBufferTooShortForBigInt = errors.New("buffer too short while extracting the big.Int value") ) diff --git a/parsers/multiversxCodec.go b/parsers/multiversxCodec.go index 921f4ffe..fc4f7149 100644 --- a/parsers/multiversxCodec.go +++ b/parsers/multiversxCodec.go @@ -3,6 +3,11 @@ package parsers import ( "encoding/binary" "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-sdk-go/data" ) // MultiversxCodec defines the codec operations to be used for MultiversX contracts @@ -42,6 +47,40 @@ func (codec *MultiversxCodec) EncodeCallData(callData CallData) []byte { return result } +// EncodeProxySCCompleteCallData will provide a valid byte slice with the encoded parameters +func (codec *MultiversxCodec) EncodeProxySCCompleteCallData(completeData ProxySCCompleteCallData) ([]byte, error) { + if check.IfNil(completeData.To) { + return nil, fmt.Errorf("%w for To field", errNilAddressHandler) + } + if completeData.Amount == nil { + return nil, errNilAmount + } + + initialAlloc := 1024 * 1024 // 1MB initial buffer + buff32Bits := make([]byte, 4) + buff64Bits := make([]byte, 8) + + result := make([]byte, 0, initialAlloc) + result = append(result, completeData.From.Bytes()...) // append To + result = append(result, completeData.To.AddressBytes()...) // append From + + binary.BigEndian.PutUint32(buff32Bits, uint32(len(completeData.Token))) + result = append(result, buff32Bits...) // append len(token) + result = append(result, completeData.Token...) // append token + + amountBytes := big.NewInt(0).Set(completeData.Amount).Bytes() + binary.BigEndian.PutUint32(buff32Bits, uint32(len(amountBytes))) + result = append(result, buff32Bits...) // append len(amount) + result = append(result, amountBytes...) // append amount + + binary.BigEndian.PutUint64(buff64Bits, completeData.Nonce) + result = append(result, buff64Bits...) // append nonce + + result = append(result, codec.EncodeCallData(completeData.CallData)...) + + return result, nil +} + // DecodeCallData will try to decode the provided bytes into a CallData struct func (codec *MultiversxCodec) DecodeCallData(buff []byte) (CallData, error) { if len(buff) == 0 { @@ -69,9 +108,9 @@ func decodeCallData(buff []byte, marker byte) (CallData, error) { return CallData{}, fmt.Errorf("%w for function", err) } - buff, gasLimit, err := extractGasLimit(buff) + buff, gasLimit, err := extractUint64(buff) if err != nil { - return CallData{}, err + return CallData{}, fmt.Errorf("%w for gas limit", err) } buff, numArgumentsLength, err := extractArgumentsLen(buff) @@ -116,10 +155,28 @@ func extractString(buff []byte) ([]byte, string, error) { return buff, endpointName, nil } -func extractGasLimit(buff []byte) ([]byte, uint64, error) { - // Check for gas limit +func extractBigInt(buff []byte) ([]byte, *big.Int, error) { + // Ensure there's enough length for the 4 bytes for length + if len(buff) < uint32ArgBytes { + return nil, nil, errBufferTooShortForLength + } + argumentLength := int(binary.BigEndian.Uint32(buff[:uint32ArgBytes])) + buff = buff[uint32ArgBytes:] // remove the len bytes + + // Check for the argument data + if len(buff) < argumentLength { + return nil, nil, errBufferTooShortForBigInt + } + + value := big.NewInt(0).SetBytes(buff[:argumentLength]) + buff = buff[argumentLength:] // remove the value bytes + + return buff, value, nil +} + +func extractUint64(buff []byte) ([]byte, uint64, error) { if len(buff) < uint64ArgBytes { // 8 bytes for gas limit - return nil, 0, errBufferTooShortForGasLimit + return nil, 0, errBufferTooShortForUint64 } gasLimit := binary.BigEndian.Uint64(buff[:uint64ArgBytes]) @@ -138,3 +195,51 @@ func extractArgumentsLen(buff []byte) ([]byte, int, error) { return buff, length, nil } + +// DecodeProxySCCompleteCallData will try to decode the provided bytes into a ProxySCCompleteCallData struct +func (codec *MultiversxCodec) DecodeProxySCCompleteCallData(buff []byte) (ProxySCCompleteCallData, error) { + result := ProxySCCompleteCallData{} + + if len(buff) < lenEthAddress { + return ProxySCCompleteCallData{}, errBufferTooShortForEthAddress + } + result.From = common.Address{} + result.From.SetBytes(buff[:lenEthAddress]) + buff = buff[lenEthAddress:] + + if len(buff) < lenMvxAddress { + return ProxySCCompleteCallData{}, errBufferTooShortForMvxAddress + } + result.To = data.NewAddressFromBytes(buff[:lenMvxAddress]) + buff = buff[lenMvxAddress:] + + buff, token, err := extractString(buff) + if err != nil { + return ProxySCCompleteCallData{}, fmt.Errorf("%w for token", err) + } + result.Token = token + + buff, amount, err := extractBigInt(buff) + if err != nil { + return ProxySCCompleteCallData{}, fmt.Errorf("%w for amount", err) + } + result.Amount = amount + + buff, nonce, err := extractUint64(buff) + if err != nil { + return ProxySCCompleteCallData{}, fmt.Errorf("%w for nonce", err) + } + result.Nonce = nonce + + result.CallData, err = codec.DecodeCallData(buff) + if err != nil { + return ProxySCCompleteCallData{}, err + } + + return result, nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (codec *MultiversxCodec) IsInterfaceNil() bool { + return codec == nil +} diff --git a/parsers/multiversxCodec_test.go b/parsers/multiversxCodec_test.go index 1a3d846e..6591b47d 100644 --- a/parsers/multiversxCodec_test.go +++ b/parsers/multiversxCodec_test.go @@ -2,9 +2,13 @@ package parsers import ( "bytes" + "encoding/hex" + "math/big" "strings" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/multiversx/mx-sdk-go/data" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -19,7 +23,37 @@ var testCallData = CallData{ }, } -func TestMultiversXCodec_EncodeDecode(t *testing.T) { +func createTestProxySCCompleteCallData() ProxySCCompleteCallData { + ethUnhexed, _ := hex.DecodeString("880ec53af800b5cd051531672ef4fc4de233bd5d") + completeCallData := ProxySCCompleteCallData{ + CallData: CallData{ + Type: DataPresentProtocolMarker, + Function: "", + GasLimit: 50000000, + Arguments: make([]string, 0), + }, + From: common.Address{}, + Token: "ETHUSDC-0ae8ee", + Amount: big.NewInt(20000), + Nonce: 1, + } + completeCallData.To, _ = data.NewAddressFromBech32String("erd1qqqqqqqqqqqqqpgqsudu3a3n9yu62k5qkgcpy4j9ywl2x2gl5smsy7t4uv") + completeCallData.From.SetBytes(ethUnhexed) + + return completeCallData +} + +func TestMultiversxCodec_IsInterfaceNil(t *testing.T) { + t.Parallel() + + var instance *MultiversxCodec + assert.True(t, instance.IsInterfaceNil()) + + instance = &MultiversxCodec{} + assert.False(t, instance.IsInterfaceNil()) +} + +func TestMultiversXCodec_EncodeDecodeCallData(t *testing.T) { t.Parallel() codec := &MultiversxCodec{} @@ -59,6 +93,37 @@ func TestMultiversXCodec_EncodeDecode(t *testing.T) { }) } +func TestMultiversXCodec_EncodeDecodeProxySCCompleteCallData(t *testing.T) { + t.Parallel() + + codec := &MultiversxCodec{} + + t.Run("with no parameters should work", func(t *testing.T) { + t.Parallel() + + localCallData := createTestProxySCCompleteCallData() + localCallData.Arguments = make([]string, 0) + + buff, err := codec.EncodeProxySCCompleteCallData(localCallData) + require.Nil(t, err) + + callData, err := codec.DecodeProxySCCompleteCallData(buff) + require.Nil(t, err) + assert.Equal(t, localCallData, callData) + }) + t.Run("with parameters should work", func(t *testing.T) { + t.Parallel() + + localCallData := createTestProxySCCompleteCallData() + buff, err := codec.EncodeProxySCCompleteCallData(localCallData) + require.Nil(t, err) + + callData, err := codec.DecodeProxySCCompleteCallData(buff) + require.Nil(t, err) + assert.Equal(t, localCallData, callData) + }) +} + func TestMultiversxCodec_DecodeCallData(t *testing.T) { t.Parallel() @@ -123,7 +188,8 @@ func TestMultiversxCodec_DecodeCallData(t *testing.T) { buff = append(buff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0) // malformed gas limit callData, err := codec.DecodeCallData(buff) - assert.ErrorIs(t, err, errBufferTooShortForGasLimit) + assert.ErrorIs(t, err, errBufferTooShortForUint64) + assert.Contains(t, err.Error(), "for gas limit") assert.Equal(t, emptyCallData, callData) }) t.Run("buffer to short for num arguments should error", func(t *testing.T) { @@ -165,3 +231,240 @@ func TestMultiversxCodec_DecodeCallData(t *testing.T) { assert.Equal(t, emptyCallData, callData) }) } + +func TestMultiversxCodec_EncodeProxySCCompleteCallData(t *testing.T) { + t.Parallel() + + codec := MultiversxCodec{} + + t.Run("nil completeData.To should error", func(t *testing.T) { + t.Parallel() + + completeCallData := ProxySCCompleteCallData{ + CallData: CallData{ + Type: DataPresentProtocolMarker, + Function: "callPayable", + GasLimit: 50000000, + Arguments: make([]string, 0), + }, + From: common.Address{}, + Token: "ETHUSDC-0ae8ee", + Amount: big.NewInt(20000), + Nonce: 1, + } + + result, err := codec.EncodeProxySCCompleteCallData(completeCallData) + assert.ErrorIs(t, err, errNilAddressHandler) + assert.Contains(t, err.Error(), "for To field") + assert.Nil(t, result) + }) + t.Run("nil completeData.Amount should error", func(t *testing.T) { + t.Parallel() + + completeCallData := ProxySCCompleteCallData{ + CallData: CallData{ + Type: DataPresentProtocolMarker, + Function: "callPayable", + GasLimit: 50000000, + Arguments: make([]string, 0), + }, + From: common.Address{}, + Token: "ETHUSDC-0ae8ee", + Nonce: 1, + } + completeCallData.To = data.NewAddressFromBytes(make([]byte, 0)) + + result, err := codec.EncodeProxySCCompleteCallData(completeCallData) + assert.ErrorIs(t, err, errNilAmount) + assert.Nil(t, result) + }) + t.Run("should work with function and no arguments", func(t *testing.T) { + t.Parallel() + + // |--------------FROM---------------------|---------------------TO----------------------------------------|-len-TK|------ETHUSDC-0ae8ee-------|-len-A-|20k|--tx-nonce=1---|M|-len-f-|--func-callPayable---|-gas-limit-50M-|-no-arg| + hexedData := "880ec53af800b5cd051531672ef4fc4de233bd5d00000000000000000500871bc8f6332939a55a80b23012564523bea3291fa4370000000e455448555344432d306165386565000000024e200000000000000001010000000b63616c6c50617961626c650000000002faf08000000000" + + ethUnhexed, err := hex.DecodeString("880ec53af800b5cd051531672ef4fc4de233bd5d") + require.Nil(t, err) + completeCallData := ProxySCCompleteCallData{ + CallData: CallData{ + Type: DataPresentProtocolMarker, + Function: "callPayable", + GasLimit: 50000000, + Arguments: make([]string, 0), + }, + From: common.Address{}, + Token: "ETHUSDC-0ae8ee", + Amount: big.NewInt(20000), + Nonce: 1, + } + completeCallData.To, err = data.NewAddressFromBech32String("erd1qqqqqqqqqqqqqpgqsudu3a3n9yu62k5qkgcpy4j9ywl2x2gl5smsy7t4uv") + require.Nil(t, err) + completeCallData.From.SetBytes(ethUnhexed) + + buff, err := hex.DecodeString(hexedData) + require.Nil(t, err) + + result, err := codec.EncodeProxySCCompleteCallData(completeCallData) + assert.Nil(t, err) + assert.Equal(t, buff, result) + }) + t.Run("should work with function and with 2 arguments", func(t *testing.T) { + t.Parallel() + + // |--------------FROM---------------------|---------------------TO----------------------------------------|-len-TK|------ETHUSDC-0ae8ee-------|-len-A-|20k|--tx-nonce=1---|M|-len-f-|--func-callPayable---|-gas-limit-50M-|-no-arg|-arg0-l|-ABC-|-arg1-l|-DEFG--| + hexedData := "880ec53af800b5cd051531672ef4fc4de233bd5d00000000000000000500871bc8f6332939a55a80b23012564523bea3291fa4370000000e455448555344432d306165386565000000024e200000000000000001010000000b63616c6c50617961626c650000000002faf08000000002000000034142430000000444454647" + + ethUnhexed, err := hex.DecodeString("880ec53af800b5cd051531672ef4fc4de233bd5d") + require.Nil(t, err) + completeCallData := ProxySCCompleteCallData{ + CallData: CallData{ + Type: DataPresentProtocolMarker, + Function: "callPayable", + GasLimit: 50000000, + Arguments: []string{ + "ABC", + "DEFG", + }, + }, + From: common.Address{}, + Token: "ETHUSDC-0ae8ee", + Amount: big.NewInt(20000), + Nonce: 1, + } + completeCallData.To, err = data.NewAddressFromBech32String("erd1qqqqqqqqqqqqqpgqsudu3a3n9yu62k5qkgcpy4j9ywl2x2gl5smsy7t4uv") + require.Nil(t, err) + completeCallData.From.SetBytes(ethUnhexed) + + buff, err := hex.DecodeString(hexedData) + require.Nil(t, err) + + result, err := codec.EncodeProxySCCompleteCallData(completeCallData) + assert.Nil(t, err) + assert.Equal(t, buff, result) + }) + t.Run("should work with no function and no arguments", func(t *testing.T) { + t.Parallel() + + // |--------------FROM---------------------|---------------------TO----------------------------------------|-len-TK|------ETHUSDC-0ae8ee-------|-len-A-|20k|--tx-nonce=1---|M|-len-f-|-gas-limit-50M-|-no-arg| + hexedData := "880ec53af800b5cd051531672ef4fc4de233bd5d00000000000000000500871bc8f6332939a55a80b23012564523bea3291fa4370000000e455448555344432d306165386565000000024e20000000000000000101000000000000000002faf08000000000" + completeCallData := createTestProxySCCompleteCallData() + buff, err := hex.DecodeString(hexedData) + require.Nil(t, err) + + result, err := codec.EncodeProxySCCompleteCallData(completeCallData) + assert.Nil(t, err) + assert.Equal(t, buff, result) + }) +} + +func TestMultiversxCodec_DecodeProxySCCompleteCallData(t *testing.T) { + t.Parallel() + + codec := MultiversxCodec{} + emptyCompleteCallData := ProxySCCompleteCallData{} + + t.Run("buffer to short for Ethereum address should error", func(t *testing.T) { + t.Parallel() + + buff := []byte{0x01} + completeCallData, err := codec.DecodeProxySCCompleteCallData(buff) + assert.ErrorIs(t, err, errBufferTooShortForEthAddress) + assert.Equal(t, emptyCompleteCallData, completeCallData) + }) + t.Run("buffer to short for MultiversX address should error", func(t *testing.T) { + t.Parallel() + + buff := bytes.Repeat([]byte{0x01}, 20) + buff = append(buff, 0x1) + completeCallData, err := codec.DecodeProxySCCompleteCallData(buff) + assert.ErrorIs(t, err, errBufferTooShortForMvxAddress) + assert.Equal(t, emptyCompleteCallData, completeCallData) + }) + t.Run("invalid token bytes should error", func(t *testing.T) { + t.Parallel() + + buff := bytes.Repeat([]byte{0x01}, 20) // Eth address + buff = append(buff, bytes.Repeat([]byte{0x01}, 32)...) // Mvx address + buff = append(buff, []byte{0x00, 0x01, 0x04}...) // invalid token + + completeCallData, err := codec.DecodeProxySCCompleteCallData(buff) + assert.ErrorIs(t, err, errBufferTooShortForLength) + assert.Contains(t, err.Error(), "for token") + assert.Equal(t, emptyCompleteCallData, completeCallData) + }) + t.Run("invalid big int size should error", func(t *testing.T) { + t.Parallel() + + buff := bytes.Repeat([]byte{0x01}, 20) // Eth address + buff = append(buff, bytes.Repeat([]byte{0x01}, 32)...) // Mvx address + buff = append(buff, []byte{0x00, 0x00, 0x00, 0x02}...) // token size + buff = append(buff, []byte{0x02, 0x03}...) // token + buff = append(buff, []byte{0x00, 0x00, 0x00}...) // invalid amount size + + completeCallData, err := codec.DecodeProxySCCompleteCallData(buff) + assert.ErrorIs(t, err, errBufferTooShortForLength) + assert.Contains(t, err.Error(), "for amount") + assert.Equal(t, emptyCompleteCallData, completeCallData) + }) + t.Run("invalid big int bytes should error", func(t *testing.T) { + t.Parallel() + + buff := bytes.Repeat([]byte{0x01}, 20) // Eth address + buff = append(buff, bytes.Repeat([]byte{0x01}, 32)...) // Mvx address + buff = append(buff, []byte{0x00, 0x00, 0x00, 0x02}...) // token size + buff = append(buff, []byte{0x02, 0x03}...) // token + buff = append(buff, []byte{0x00, 0x00, 0x00, 0x05}...) // amount size + buff = append(buff, []byte{0x00}...) // invalid amount + + completeCallData, err := codec.DecodeProxySCCompleteCallData(buff) + assert.ErrorIs(t, err, errBufferTooShortForBigInt) + assert.Contains(t, err.Error(), "for amount") + assert.Equal(t, emptyCompleteCallData, completeCallData) + }) + t.Run("invalid nonce should error", func(t *testing.T) { + t.Parallel() + + buff := bytes.Repeat([]byte{0x01}, 20) // Eth address + buff = append(buff, bytes.Repeat([]byte{0x01}, 32)...) // Mvx address + buff = append(buff, []byte{0x00, 0x00, 0x00, 0x02}...) // token size + buff = append(buff, []byte{0x02, 0x03}...) // token + buff = append(buff, []byte{0x00, 0x00, 0x00, 0x01}...) // amount size + buff = append(buff, []byte{0x01}...) // amount + buff = append(buff, []byte{0x03, 0x04}...) // invalid nonce + + completeCallData, err := codec.DecodeProxySCCompleteCallData(buff) + assert.ErrorIs(t, err, errBufferTooShortForUint64) + assert.Contains(t, err.Error(), "for nonce") + assert.Equal(t, emptyCompleteCallData, completeCallData) + }) + t.Run("invalid nonce should error", func(t *testing.T) { + t.Parallel() + + buff := bytes.Repeat([]byte{0x01}, 20) // Eth address + buff = append(buff, bytes.Repeat([]byte{0x01}, 32)...) // Mvx address + buff = append(buff, []byte{0x00, 0x00, 0x00, 0x02}...) // token size + buff = append(buff, []byte{0x02, 0x03}...) // token + buff = append(buff, []byte{0x00, 0x00, 0x00, 0x00}...) // amount size = 0 => amount = 0 + buff = append(buff, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}...) // nonce + buff = append(buff, 0x03) // invalid marker + + completeCallData, err := codec.DecodeProxySCCompleteCallData(buff) + assert.ErrorIs(t, err, errUnexpectedMarker) + assert.Contains(t, err.Error(), ": 3") + assert.Equal(t, emptyCompleteCallData, completeCallData) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + // |--------------FROM---------------------|---------------------TO----------------------------------------|-len-TK|------ETHUSDC-0ae8ee-------|-len-A-|20k|--tx-nonce=1---|M|-len-f-|-gas-limit-50M-|-no-arg| + hexedData := "880ec53af800b5cd051531672ef4fc4de233bd5d00000000000000000500871bc8f6332939a55a80b23012564523bea3291fa4370000000e455448555344432d306165386565000000024e20000000000000000101000000000000000002faf08000000000" + buff, err := hex.DecodeString(hexedData) + require.Nil(t, err) + + expectedCompleteCallData := createTestProxySCCompleteCallData() + completeCallData, err := codec.DecodeProxySCCompleteCallData(buff) + assert.Equal(t, expectedCompleteCallData, completeCallData) + assert.Nil(t, err) + }) +} diff --git a/parsers/types.go b/parsers/types.go index 36c7fd80..84ca30cd 100644 --- a/parsers/types.go +++ b/parsers/types.go @@ -1,5 +1,15 @@ package parsers +import ( + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-sdk-go/core" +) + // CallData defines the struct holding SC call data parameters type CallData struct { Type byte @@ -7,3 +17,52 @@ type CallData struct { GasLimit uint64 Arguments []string } + +// String returns the human-readable string version of the call data +func (callData CallData) String() string { + arguments := "no arguments" + if len(callData.Arguments) > 0 { + arguments = "arguments: " + strings.Join(callData.Arguments, ", ") + } + + return fmt.Sprintf("type: %d, function: %s, gas limit: %d, %s", + callData.Type, + callData.Function, + callData.GasLimit, + arguments) +} + +// ProxySCCompleteCallData defines the struct holding Proxy SC complete call data +type ProxySCCompleteCallData struct { + CallData + From common.Address + To core.AddressHandler + Token string + Amount *big.Int + Nonce uint64 +} + +// String returns the human-readable string version of the call data +func (callData ProxySCCompleteCallData) String() string { + toString := "" + var err error + if !check.IfNil(callData.To) { + toString, err = callData.To.AddressAsBech32String() + if err != nil { + toString = "" + } + } + amountString := "" + if callData.Amount != nil { + amountString = callData.Amount.String() + } + + return fmt.Sprintf("Eth address: %s, MvX address: %s, token: %s, amount: %s, nonce: %d, %s", + callData.From.String(), + toString, + callData.Token, + amountString, + callData.Nonce, + callData.CallData.String(), + ) +} diff --git a/parsers/types_test.go b/parsers/types_test.go new file mode 100644 index 00000000..2dadbbe3 --- /dev/null +++ b/parsers/types_test.go @@ -0,0 +1,102 @@ +package parsers + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/multiversx/mx-sdk-go/data" + "github.com/stretchr/testify/assert" +) + +func TestCallData_String(t *testing.T) { + t.Parallel() + + t.Run("with no arguments should work", func(t *testing.T) { + t.Parallel() + + callData := CallData{ + Type: 1, + Function: "callMe", + GasLimit: 50000, + } + expectedString := "type: 1, function: callMe, gas limit: 50000, no arguments" + + assert.Equal(t, expectedString, callData.String()) + }) + t.Run("with arguments should work", func(t *testing.T) { + t.Parallel() + + callData := CallData{ + Type: 1, + Function: "callMe", + GasLimit: 50000, + Arguments: []string{"arg1", "arg2"}, + } + expectedString := "type: 1, function: callMe, gas limit: 50000, arguments: arg1, arg2" + + assert.Equal(t, expectedString, callData.String()) + }) +} + +func TestProxySCCompleteCallData_String(t *testing.T) { + t.Parallel() + + t.Run("nil fields should work", func(t *testing.T) { + t.Parallel() + + callData := ProxySCCompleteCallData{ + CallData: CallData{ + Type: 1, + Function: "callMe", + GasLimit: 50000, + }, + From: common.Address{}, + Token: "tkn", + Nonce: 1, + } + + expectedString := "Eth address: 0x0000000000000000000000000000000000000000, MvX address: , token: tkn, amount: , nonce: 1, type: 1, function: callMe, gas limit: 50000, no arguments" + assert.Equal(t, expectedString, callData.String()) + }) + t.Run("not a Valid MvX address should work", func(t *testing.T) { + t.Parallel() + + callData := ProxySCCompleteCallData{ + CallData: CallData{ + Type: 1, + Function: "callMe", + GasLimit: 50000, + }, + From: common.Address{}, + To: data.NewAddressFromBytes([]byte{0x1, 0x2}), + Token: "tkn", + Nonce: 1, + } + + expectedString := "Eth address: 0x0000000000000000000000000000000000000000, MvX address: , token: tkn, amount: , nonce: 1, type: 1, function: callMe, gas limit: 50000, no arguments" + assert.Equal(t, expectedString, callData.String()) + }) + t.Run("with valid data should work", func(t *testing.T) { + t.Parallel() + + callData := ProxySCCompleteCallData{ + CallData: CallData{ + Type: 1, + Function: "callMe", + GasLimit: 50000, + }, + From: common.Address{}, + Token: "tkn", + Amount: big.NewInt(37), + Nonce: 1, + } + ethUnhexed, _ := hex.DecodeString("880ec53af800b5cd051531672ef4fc4de233bd5d") + callData.From.SetBytes(ethUnhexed) + callData.To, _ = data.NewAddressFromBech32String("erd1qqqqqqqqqqqqqpgqsudu3a3n9yu62k5qkgcpy4j9ywl2x2gl5smsy7t4uv") + + expectedString := "Eth address: 0x880EC53Af800b5Cd051531672EF4fc4De233bD5d, MvX address: erd1qqqqqqqqqqqqqpgqsudu3a3n9yu62k5qkgcpy4j9ywl2x2gl5smsy7t4uv, token: tkn, amount: 37, nonce: 1, type: 1, function: callMe, gas limit: 50000, no arguments" + assert.Equal(t, expectedString, callData.String()) + }) +} diff --git a/testsCommon/multiversxCodecStub.go b/testsCommon/multiversxCodecStub.go new file mode 100644 index 00000000..0d71f584 --- /dev/null +++ b/testsCommon/multiversxCodecStub.go @@ -0,0 +1,52 @@ +package testsCommon + +import "github.com/multiversx/mx-bridge-eth-go/parsers" + +// MultiversxCodecStub - +type MultiversxCodecStub struct { + EncodeCallDataCalled func(callData parsers.CallData) []byte + EncodeProxySCCompleteCallDataCalled func(completeData parsers.ProxySCCompleteCallData) ([]byte, error) + DecodeCallDataCalled func(buff []byte) (parsers.CallData, error) + DecodeProxySCCompleteCallDataCalled func(buff []byte) (parsers.ProxySCCompleteCallData, error) +} + +// EncodeCallData - +func (stub *MultiversxCodecStub) EncodeCallData(callData parsers.CallData) []byte { + if stub.EncodeCallDataCalled != nil { + return stub.EncodeCallDataCalled(callData) + } + + return make([]byte, 0) +} + +// EncodeProxySCCompleteCallData - +func (stub *MultiversxCodecStub) EncodeProxySCCompleteCallData(completeData parsers.ProxySCCompleteCallData) ([]byte, error) { + if stub.EncodeProxySCCompleteCallDataCalled != nil { + return stub.EncodeProxySCCompleteCallDataCalled(completeData) + } + + return make([]byte, 0), nil +} + +// DecodeCallData - +func (stub *MultiversxCodecStub) DecodeCallData(buff []byte) (parsers.CallData, error) { + if stub.DecodeCallDataCalled != nil { + return stub.DecodeCallDataCalled(buff) + } + + return parsers.CallData{}, nil +} + +// DecodeProxySCCompleteCallData - +func (stub *MultiversxCodecStub) DecodeProxySCCompleteCallData(buff []byte) (parsers.ProxySCCompleteCallData, error) { + if stub.DecodeProxySCCompleteCallDataCalled != nil { + return stub.DecodeProxySCCompleteCallDataCalled(buff) + } + + return parsers.ProxySCCompleteCallData{}, nil +} + +// IsInterfaceNil - +func (stub *MultiversxCodecStub) IsInterfaceNil() bool { + return stub == nil +} diff --git a/testsCommon/scCallsExecuteFilterStub.go b/testsCommon/scCallsExecuteFilterStub.go new file mode 100644 index 00000000..57f98c2f --- /dev/null +++ b/testsCommon/scCallsExecuteFilterStub.go @@ -0,0 +1,22 @@ +package testsCommon + +import "github.com/multiversx/mx-bridge-eth-go/parsers" + +// ScCallsExecuteFilterStub - +type ScCallsExecuteFilterStub struct { + ShouldExecuteCalled func(callData parsers.ProxySCCompleteCallData) bool +} + +// ShouldExecute - +func (stub *ScCallsExecuteFilterStub) ShouldExecute(callData parsers.ProxySCCompleteCallData) bool { + if stub.ShouldExecuteCalled != nil { + return stub.ShouldExecuteCalled(callData) + } + + return true +} + +// IsInterfaceNil - +func (stub *ScCallsExecuteFilterStub) IsInterfaceNil() bool { + return stub == nil +} diff --git a/testsCommon/txNonceHandlerV2Stub.go b/testsCommon/txNonceHandlerV2Stub.go new file mode 100644 index 00000000..5c8876d4 --- /dev/null +++ b/testsCommon/txNonceHandlerV2Stub.go @@ -0,0 +1,48 @@ +package testsCommon + +import ( + "context" + + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-sdk-go/core" +) + +// TxNonceHandlerV2Stub - +type TxNonceHandlerV2Stub struct { + ApplyNonceAndGasPriceCalled func(ctx context.Context, address core.AddressHandler, tx *transaction.FrontendTransaction) error + SendTransactionCalled func(ctx context.Context, tx *transaction.FrontendTransaction) (string, error) + ForceNonceReFetchCalled func(address core.AddressHandler) error + CloseCalled func() error +} + +// ApplyNonceAndGasPrice - +func (stub *TxNonceHandlerV2Stub) ApplyNonceAndGasPrice(ctx context.Context, address core.AddressHandler, tx *transaction.FrontendTransaction) error { + if stub.ApplyNonceAndGasPriceCalled != nil { + return stub.ApplyNonceAndGasPriceCalled(ctx, address, tx) + } + + return nil +} + +// SendTransaction - +func (stub *TxNonceHandlerV2Stub) SendTransaction(ctx context.Context, tx *transaction.FrontendTransaction) (string, error) { + if stub.SendTransactionCalled != nil { + return stub.SendTransactionCalled(ctx, tx) + } + + return "", nil +} + +// Close - +func (stub *TxNonceHandlerV2Stub) Close() error { + if stub.CloseCalled != nil { + return stub.CloseCalled() + } + + return nil +} + +// IsInterfaceNil - +func (stub *TxNonceHandlerV2Stub) IsInterfaceNil() bool { + return stub == nil +}