diff --git a/executors/multiversx/scCallsExecutor_test.go b/executors/multiversx/scCallsExecutor_test.go index c2b80423..f92d3de9 100644 --- a/executors/multiversx/scCallsExecutor_test.go +++ b/executors/multiversx/scCallsExecutor_test.go @@ -43,7 +43,7 @@ func createTestProxySCCompleteCallData(token string) parsers.ProxySCCompleteCall Type: 1, Function: "callMe", GasLimit: 5000000, - Arguments: []interface{}{"arg1", "arg2"}, + Arguments: []string{"arg1", "arg2"}, }), From: common.Address{}, Token: token, diff --git a/integrationTests/relayers/slowTests/common.go b/integrationTests/relayers/slowTests/common.go index ca13c9cc..157f3c50 100644 --- a/integrationTests/relayers/slowTests/common.go +++ b/integrationTests/relayers/slowTests/common.go @@ -9,6 +9,11 @@ import ( "github.com/multiversx/mx-bridge-eth-go/integrationTests/relayers/slowTests/framework" "github.com/multiversx/mx-bridge-eth-go/parsers" "github.com/multiversx/mx-bridge-eth-go/testsCommon" + logger "github.com/multiversx/mx-chain-logger-go" +) + +var ( + log = logger.GetOrCreate("integrationTests/relayers/slowTests") ) // GenerateTestUSDCToken will generate a test USDC token @@ -93,7 +98,7 @@ func GenerateTestMEMEToken() framework.TestTokenParams { } } -func createScCallData(function string, gasLimit uint64, args ...interface{}) []byte { +func createScCallData(function string, gasLimit uint64, args ...string) []byte { codec := testsCommon.TestMultiversXCodec{} callData := parsers.CallData{ Type: bridgeCore.DataPresentProtocolMarker, diff --git a/integrationTests/relayers/slowTests/ethToMultiversXWithChainSimulator_test.go b/integrationTests/relayers/slowTests/ethToMultiversXWithChainSimulator_test.go index 7c4cf477..d7b0471f 100644 --- a/integrationTests/relayers/slowTests/ethToMultiversXWithChainSimulator_test.go +++ b/integrationTests/relayers/slowTests/ethToMultiversXWithChainSimulator_test.go @@ -7,10 +7,12 @@ package slowTests import ( "context" + "encoding/binary" "errors" "fmt" "math/big" "os" + "strings" "testing" "time" @@ -18,6 +20,8 @@ import ( "github.com/multiversx/mx-bridge-eth-go/integrationTests/mock" "github.com/multiversx/mx-bridge-eth-go/integrationTests/relayers/slowTests/framework" logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-sdk-go/data" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -25,12 +29,8 @@ const ( timeout = time.Minute * 15 ) -var ( - log = logger.GetOrCreate("integrationTests/relayers/slowTests") -) - func TestRelayersShouldExecuteTransfers(t *testing.T) { - testRelayersWithChainSimulatorAndTokens( + _ = testRelayersWithChainSimulatorAndTokens( t, make(chan error), GenerateTestUSDCToken(), @@ -38,6 +38,28 @@ func TestRelayersShouldExecuteTransfers(t *testing.T) { ) } +func TestRelayersShouldExecuteTransfersWithSCCallsWithParameters(t *testing.T) { + dummyAddress := strings.Repeat("2", 32) + dummyUint64 := string([]byte{37}) + + callData := createScCallData("callPayableWithParams", 50000000, dummyUint64, dummyAddress) + + usdcToken := GenerateTestUSDCToken() + usdcToken.TestOperations[2].MvxSCCallData = callData + + memeToken := GenerateTestMEMEToken() + memeToken.TestOperations[2].MvxSCCallData = callData + + testSetup := testRelayersWithChainSimulatorAndTokens( + t, + make(chan error), + usdcToken, + memeToken, + ) + + testCallPayableWithParamsWasCalled(testSetup, 37, usdcToken.AbstractTokenIdentifier, memeToken.AbstractTokenIdentifier) +} + func TestRelayerShouldExecuteTransfersAndNotCatchErrors(t *testing.T) { errorString := "ERROR" mockLogObserver := mock.NewMockLogObserver(errorString) @@ -63,14 +85,14 @@ func TestRelayerShouldExecuteTransfersAndNotCatchErrors(t *testing.T) { } }() - testRelayersWithChainSimulatorAndTokens( + _ = testRelayersWithChainSimulatorAndTokens( t, stopChan, GenerateTestMEMEToken(), ) } -func testRelayersWithChainSimulatorAndTokens(tb testing.TB, manualStopChan chan error, tokens ...framework.TestTokenParams) { +func testRelayersWithChainSimulatorAndTokens(tb testing.TB, manualStopChan chan error, tokens ...framework.TestTokenParams) *framework.TestSetup { startsFromEthFlow, startsFromMvXFlow := createFlowsBasedOnToken(tb, tokens...) setupFunc := func(tb testing.TB, setup *framework.TestSetup) { @@ -100,7 +122,7 @@ func testRelayersWithChainSimulatorAndTokens(tb testing.TB, manualStopChan chan return false } - testRelayersWithChainSimulator(tb, + return testRelayersWithChainSimulator(tb, setupFunc, processFunc, manualStopChan, @@ -138,7 +160,7 @@ func testRelayersWithChainSimulator(tb testing.TB, setupFunc func(tb testing.TB, setup *framework.TestSetup), processLoopFunc func(tb testing.TB, setup *framework.TestSetup) bool, stopChan chan error, -) { +) *framework.TestSetup { defer func() { r := recover() if r != nil { @@ -159,17 +181,17 @@ func testRelayersWithChainSimulator(tb testing.TB, select { case <-interrupt: require.Fail(tb, "signal interrupted") - return + return testSetup case <-time.After(timeout): require.Fail(tb, "time out") - return + return testSetup case err := <-stopChan: require.Nil(tb, err) - return + return testSetup default: testDone := processLoopFunc(tb, testSetup) if testDone { - return + return testSetup } } } @@ -317,7 +339,7 @@ func testRelayersShouldNotExecuteTransfers( } }() - testRelayersWithChainSimulator(tb, setupFunc, processFunc, stopChan) + _ = testRelayersWithChainSimulator(tb, setupFunc, processFunc, stopChan) } func testEthContractsShouldError(tb testing.TB, testToken framework.TestTokenParams) { @@ -341,9 +363,43 @@ func testEthContractsShouldError(tb testing.TB, testToken framework.TestTokenPar return true } - testRelayersWithChainSimulator(tb, + _ = testRelayersWithChainSimulator(tb, setupFunc, processFunc, make(chan error), ) } + +func testCallPayableWithParamsWasCalled(testSetup *framework.TestSetup, value uint64, tokens ...string) { + if len(tokens) == 0 { + return + } + + vmRequest := &data.VmValueRequest{ + Address: testSetup.MultiversxHandler.TestCallerAddress.Bech32(), + FuncName: "getCalledDataParams", + } + + vmResponse, err := testSetup.ChainSimulator.Proxy().ExecuteVMQuery(context.Background(), vmRequest) + require.Nil(testSetup, err) + + returnedData := vmResponse.Data.ReturnData + require.Equal(testSetup, len(tokens), len(returnedData)) + + for i, token := range tokens { + buff := returnedData[i] + parsedValue, parsedToken := processCalledDataParams(buff) + assert.Equal(testSetup, value, parsedValue) + assert.Contains(testSetup, parsedToken, token) + } +} + +func processCalledDataParams(buff []byte) (uint64, string) { + valBuff := buff[:8] + value := binary.BigEndian.Uint64(valBuff) + + buff = buff[8+32+4:] // trim the nonce, address and length of the token + token := string(buff) + + return value, token +} diff --git a/integrationTests/relayers/slowTests/framework/chainSimulatorWrapper.go b/integrationTests/relayers/slowTests/framework/chainSimulatorWrapper.go index d70664a9..f651091a 100644 --- a/integrationTests/relayers/slowTests/framework/chainSimulatorWrapper.go +++ b/integrationTests/relayers/slowTests/framework/chainSimulatorWrapper.go @@ -188,20 +188,6 @@ func (instance *chainSimulatorWrapper) GenerateBlocksUntilTxProcessed(ctx contex } } -func (instance *chainSimulatorWrapper) getTxInfoWithResultsIfTxProcessingFinished(ctx context.Context, hash string) (transaction.TxStatus, *data.TransactionOnNetwork) { - txStatus, err := instance.proxyInstance.ProcessTransactionStatus(ctx, hash) - require.Nil(instance, err) - - if txStatus != transaction.TxStatusSuccess { - return txStatus, nil - } - - txResult, errGet := instance.proxyInstance.GetTransactionInfoWithResults(ctx, hash) - require.Nil(instance, errGet) - - return txStatus, &txResult.Data.Transaction -} - // ScCall will make the provided sc call func (instance *chainSimulatorWrapper) ScCall(ctx context.Context, senderSK []byte, contract *MvxAddress, value string, gasLimit uint64, function string, parameters []string) (string, *data.TransactionOnNetwork) { params := []string{function} diff --git a/integrationTests/relayers/slowTests/refundWithChainSimulator_test.go b/integrationTests/relayers/slowTests/refundWithChainSimulator_test.go index 1245a8e6..a90c2ad9 100644 --- a/integrationTests/relayers/slowTests/refundWithChainSimulator_test.go +++ b/integrationTests/relayers/slowTests/refundWithChainSimulator_test.go @@ -7,6 +7,7 @@ package slowTests import ( "math/big" + "strings" "testing" bridgeCore "github.com/multiversx/mx-bridge-eth-go/core" @@ -142,6 +143,90 @@ func TestRelayersShouldExecuteTransfersWithRefund(t *testing.T) { memeToken.TestOperations[2].MvxSCCallData = callData memeToken.TestOperations[2].MvxFaultySCCall = true + testRelayersWithChainSimulatorAndTokensAndRefund( + t, + make(chan error), + usdcToken, + memeToken, + ) + }) + t.Run("no arguments should refund", func(t *testing.T) { + callData := createScCallData("callPayableWithParams", 50000000) + usdcToken := GenerateTestUSDCToken() + usdcToken.TestOperations[2].MvxSCCallData = callData + usdcToken.TestOperations[2].MvxFaultySCCall = true + usdcToken.EthTestAddrExtraBalance = big.NewInt(-5000 + 2500 - 50 - 7000 + 300 - 50 - 1000 + 950) // -(eth->mvx) + (mvx->eth) - fees + revert after bad SC call + usdcToken.ESDTSafeExtraBalance = big.NewInt(150) // extra is just for the fees for the 2 transfers mvx->eth and the failed eth->mvx that needed refund + + memeToken := GenerateTestMEMEToken() + memeToken.TestOperations[2].MvxSCCallData = callData + memeToken.TestOperations[2].MvxFaultySCCall = true + + testRelayersWithChainSimulatorAndTokensAndRefund( + t, + make(chan error), + usdcToken, + memeToken, + ) + }) + t.Run("wrong number of arguments should refund", func(t *testing.T) { + callData := createScCallData("callPayableWithParams", 50000000, string([]byte{37})) + usdcToken := GenerateTestUSDCToken() + usdcToken.TestOperations[2].MvxSCCallData = callData + usdcToken.TestOperations[2].MvxFaultySCCall = true + usdcToken.EthTestAddrExtraBalance = big.NewInt(-5000 + 2500 - 50 - 7000 + 300 - 50 - 1000 + 950) // -(eth->mvx) + (mvx->eth) - fees + revert after bad SC call + usdcToken.ESDTSafeExtraBalance = big.NewInt(150) // extra is just for the fees for the 2 transfers mvx->eth and the failed eth->mvx that needed refund + + memeToken := GenerateTestMEMEToken() + memeToken.TestOperations[2].MvxSCCallData = callData + memeToken.TestOperations[2].MvxFaultySCCall = true + + testRelayersWithChainSimulatorAndTokensAndRefund( + t, + make(chan error), + usdcToken, + memeToken, + ) + }) + t.Run("not an uint64 argument should refund", func(t *testing.T) { + malformedUint64String := string([]byte{37, 36, 35, 34, 33, 32, 31, 32, 33}) // 9 bytes instead of 8 + dummyAddress := strings.Repeat("2", 32) + + callData := createScCallData("callPayableWithParams", 50000000, malformedUint64String, dummyAddress) + usdcToken := GenerateTestUSDCToken() + usdcToken.TestOperations[2].MvxSCCallData = callData + usdcToken.TestOperations[2].MvxFaultySCCall = true + usdcToken.EthTestAddrExtraBalance = big.NewInt(-5000 + 2500 - 50 - 7000 + 300 - 50 - 1000 + 950) // -(eth->mvx) + (mvx->eth) - fees + revert after bad SC call + usdcToken.ESDTSafeExtraBalance = big.NewInt(150) // extra is just for the fees for the 2 transfers mvx->eth and the failed eth->mvx that needed refund + + memeToken := GenerateTestMEMEToken() + memeToken.TestOperations[2].MvxSCCallData = callData + memeToken.TestOperations[2].MvxFaultySCCall = true + + testRelayersWithChainSimulatorAndTokensAndRefund( + t, + make(chan error), + usdcToken, + memeToken, + ) + }) + t.Run("wrong arguments encoding should refund", func(t *testing.T) { + callData := createScCallData("callPayableWithParams", 50000000) + // the last byte is the data missing marker, we will replace that + callData[len(callData)-1] = bridgeCore.DataPresentProtocolMarker + // add garbage data + callData = append(callData, []byte{5, 4, 55}...) + + usdcToken := GenerateTestUSDCToken() + usdcToken.TestOperations[2].MvxSCCallData = callData + usdcToken.TestOperations[2].MvxFaultySCCall = true + usdcToken.EthTestAddrExtraBalance = big.NewInt(-5000 + 2500 - 50 - 7000 + 300 - 50 - 1000 + 950) // -(eth->mvx) + (mvx->eth) - fees + revert after bad SC call + usdcToken.ESDTSafeExtraBalance = big.NewInt(150) // extra is just for the fees for the 2 transfers mvx->eth and the failed eth->mvx that needed refund + + memeToken := GenerateTestMEMEToken() + memeToken.TestOperations[2].MvxSCCallData = callData + memeToken.TestOperations[2].MvxFaultySCCall = true + testRelayersWithChainSimulatorAndTokensAndRefund( t, make(chan error), @@ -181,7 +266,7 @@ func testRelayersWithChainSimulatorAndTokensAndRefund(tb testing.TB, manualStopC return false } - testRelayersWithChainSimulator(tb, + _ = testRelayersWithChainSimulator(tb, setupFunc, processFunc, manualStopChan, diff --git a/integrationTests/relayers/slowTests/testdata/contracts/mvx/test-caller.abi.json b/integrationTests/relayers/slowTests/testdata/contracts/mvx/test-caller.abi.json index b09cd36c..e7c1a897 100644 --- a/integrationTests/relayers/slowTests/testdata/contracts/mvx/test-caller.abi.json +++ b/integrationTests/relayers/slowTests/testdata/contracts/mvx/test-caller.abi.json @@ -40,9 +40,56 @@ "mutability": "mutable", "inputs": [], "outputs": [] + }, + { + "name": "callPayableWithParams", + "mutability": "readonly", + "payableInTokens": [ + "*" + ], + "inputs": [ + { + "name": "size", + "type": "u64" + }, + { + "name": "address", + "type": "Address" + } + ], + "outputs": [] + }, + { + "name": "getCalledDataParams", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ] } ], "esdtAttributes": [], "hasCallback": false, - "types": {} + "types": { + "CalledData": { + "type": "struct", + "fields": [ + { + "name": "size", + "type": "u64" + }, + { + "name": "address", + "type": "Address" + }, + { + "name": "token_identifier", + "type": "TokenIdentifier" + } + ] + } + } } diff --git a/integrationTests/relayers/slowTests/testdata/contracts/mvx/test-caller.wasm b/integrationTests/relayers/slowTests/testdata/contracts/mvx/test-caller.wasm index e1b5a082..8419ee51 100755 Binary files a/integrationTests/relayers/slowTests/testdata/contracts/mvx/test-caller.wasm and b/integrationTests/relayers/slowTests/testdata/contracts/mvx/test-caller.wasm differ diff --git a/parsers/types.go b/parsers/types.go index 6950f666..1b8d9326 100644 --- a/parsers/types.go +++ b/parsers/types.go @@ -14,7 +14,7 @@ type CallData struct { Type byte Function string GasLimit uint64 - Arguments []interface{} + Arguments []string } // ProxySCCompleteCallData defines the struct holding Proxy SC complete call data diff --git a/testsCommon/testMultiversxCodec.go b/testsCommon/testMultiversxCodec.go index ca37687f..588f91fe 100644 --- a/testsCommon/testMultiversxCodec.go +++ b/testsCommon/testMultiversxCodec.go @@ -44,7 +44,6 @@ func (codec *TestMultiversXCodec) EncodeCallDataStrict(callData parsers.CallData result = append(result, callData.Function...) // append the function as string binary.BigEndian.PutUint64(buff64Bits, callData.GasLimit) - result = append(result, buff64Bits...) // append the gas limit as 8 bytes if len(callData.Arguments) == 0 { @@ -54,21 +53,27 @@ func (codec *TestMultiversXCodec) EncodeCallDataStrict(callData parsers.CallData return result } - binary.BigEndian.PutUint32(buff32Bits, uint32(len(callData.Arguments))) + result = append(result, bridgeCore.DataPresentProtocolMarker) + encodedArgs := codec.encodeArgs(callData.Arguments) + result = append(result, encodedArgs...) + + return result +} + +func (codec *TestMultiversXCodec) encodeArgs(args []string) []byte { + buff32Bits := make([]byte, 4) + + initialAlloc := 1024 * 1024 // 1MB initial buffer + result := make([]byte, 0, initialAlloc) + + binary.BigEndian.PutUint32(buff32Bits, uint32(len(args))) result = append(result, buff32Bits...) // append the number of arguments - for i, arg := range callData.Arguments { - switch v := arg.(type) { - case uint64: - binary.BigEndian.PutUint64(buff64Bits, v) - case string: - lenArg := len(v) - binary.BigEndian.PutUint32(buff32Bits, uint32(lenArg)) - result = append(result, buff32Bits...) // append the length of the current argument - result = append(result, v...) // append the argument as string - default: - panic(fmt.Sprintf("unsupported argument on position %d, type %T, value %+v", i, arg, arg)) - } + for _, arg := range args { + lenArg := len(arg) + binary.BigEndian.PutUint32(buff32Bits, uint32(lenArg)) + result = append(result, buff32Bits...) // append the length of the current argument + result = append(result, arg...) // append the argument as string } return result @@ -114,7 +119,7 @@ func decodeCallData(buff []byte, marker byte) parsers.CallData { panic(err) } - arguments, err := extractArgumentsAsStrings(buff) + arguments, err := extractArguments(buff) if err != nil { panic(err) } @@ -127,14 +132,14 @@ func decodeCallData(buff []byte, marker byte) parsers.CallData { } } -func extractArgumentsAsStrings(buff []byte) ([]interface{}, error) { +func extractArguments(buff []byte) ([]string, error) { if len(buff) == 0 { panic("empty buffer") } if len(buff) == 1 && buff[0] == bridgeCore.MissingDataProtocolMarker { // no arguments provided - return make([]interface{}, 0), nil + return make([]string, 0), nil } buff, numArgumentsLength, err := parsers.ExtractUint32(buff) @@ -142,7 +147,7 @@ func extractArgumentsAsStrings(buff []byte) ([]interface{}, error) { panic(err) } - arguments := make([]interface{}, 0) + arguments := make([]string, 0) for i := 0; i < numArgumentsLength; i++ { var argument string buff, argument, err = parsers.ExtractString(buff)