diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c1dfecf4f..dffc9e42d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,7 +43,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -54,7 +54,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -68,4 +68,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6971fa056..c25679254 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -92,7 +92,7 @@ jobs: run: make itest - name: Upload itest logs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: itest_logs diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 64c5459f9..e3bc4c46c 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -25,7 +25,7 @@ jobs: # with '-no-fail' we let the report trigger content trigger a failure using the GitHub Security features. args: "-no-fail -fmt sarif -out gosec.sarif ./..." - name: Upload SARIF file for GitHub Advanced Security Dashboard - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: gosec.sarif @@ -54,7 +54,7 @@ jobs: fi EOF - name: Upload SARIF file for GitHub Advanced Security Dashboard - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: semgrep.sarif diff --git a/pkg/api/metamask/service.go b/pkg/api/metamask/service.go index aff97f5e3..3ac9ce272 100644 --- a/pkg/api/metamask/service.go +++ b/pkg/api/metamask/service.go @@ -172,14 +172,14 @@ func (s RPCService) Eth_EstimateGas(req estimateGasRequest) (string, error) { } } - txKind, err := proto.GuessEthereumTransactionKind(data) + txKind, err := proto.GuessEthereumTransactionKindType(data) if err != nil { return "", errors.Errorf("failed to guess ethereum tx kind, %v", err) } switch txKind { - case proto.EthereumTransferWavesKind: + case proto.EthereumTransferWavesKindType: return uint64ToHexString(proto.MinFee), nil - case proto.EthereumTransferAssetsKind: + case proto.EthereumTransferAssetsKindType: fee := proto.MinFee assetID := (*proto.AssetID)(req.To) @@ -191,7 +191,7 @@ func (s RPCService) Eth_EstimateGas(req estimateGasRequest) (string, error) { fee += proto.MinFeeScriptedAsset } return uint64ToHexString(uint64(fee)), nil - case proto.EthereumInvokeKind: + case proto.EthereumInvokeKindType: return uint64ToHexString(proto.MinFeeInvokeScript), nil default: return "", errors.Errorf("unexpected ethereum tx kind") diff --git a/pkg/proto/eth_transaction.go b/pkg/proto/eth_transaction.go index 1e3cfac03..ab956592e 100644 --- a/pkg/proto/eth_transaction.go +++ b/pkg/proto/eth_transaction.go @@ -86,17 +86,21 @@ type EthereumTxData interface { } type EthereumTransactionKind interface { + Type() EthereumTransactionKindType String() string DecodedData() *ethabi.DecodedCallData } -type EthereumTransferWavesTxKind struct { -} +type EthereumTransferWavesTxKind struct{} func NewEthereumTransferWavesTxKind() *EthereumTransferWavesTxKind { return &EthereumTransferWavesTxKind{} } +func (k *EthereumTransferWavesTxKind) Type() EthereumTransactionKindType { + return EthereumTransferWavesKindType +} + func (k *EthereumTransferWavesTxKind) DecodedData() *ethabi.DecodedCallData { return nil } @@ -115,6 +119,10 @@ func NewEthereumTransferAssetsErc20TxKind(decodedData ethabi.DecodedCallData, as return &EthereumTransferAssetsErc20TxKind{Asset: asset, decodedData: decodedData, Arguments: arguments} } +func (k *EthereumTransferAssetsErc20TxKind) Type() EthereumTransactionKindType { + return EthereumTransferAssetsKindType +} + func (k *EthereumTransferAssetsErc20TxKind) DecodedData() *ethabi.DecodedCallData { return &k.decodedData } @@ -131,6 +139,10 @@ func NewEthereumInvokeScriptTxKind(decodedData ethabi.DecodedCallData) *Ethereum return &EthereumInvokeScriptTxKind{decodedData: decodedData} } +func (k *EthereumInvokeScriptTxKind) Type() EthereumTransactionKindType { + return EthereumInvokeKindType +} + func (k *EthereumInvokeScriptTxKind) DecodedData() *ethabi.DecodedCallData { return &k.decodedData } diff --git a/pkg/proto/eth_tx_kind_resolver.go b/pkg/proto/eth_tx_kind_resolver.go index 827f0be9a..9168a678f 100644 --- a/pkg/proto/eth_tx_kind_resolver.go +++ b/pkg/proto/eth_tx_kind_resolver.go @@ -7,15 +7,17 @@ import ( "github.com/wavesplatform/gowaves/pkg/ride/ast" ) +type EthereumTransactionKindType byte + const ( - EthereumTransferWavesKind = iota + 1 - EthereumTransferAssetsKind - EthereumInvokeKind + EthereumTransferWavesKindType EthereumTransactionKindType = iota + 1 + EthereumTransferAssetsKindType + EthereumInvokeKindType ) -func GuessEthereumTransactionKind(data []byte) (int64, error) { +func GuessEthereumTransactionKindType(data []byte) (EthereumTransactionKindType, error) { if len(data) == 0 { - return EthereumTransferWavesKind, nil + return EthereumTransferWavesKindType, nil } selectorBytes := data @@ -28,10 +30,10 @@ func GuessEthereumTransactionKind(data []byte) (int64, error) { } if ethabi.IsERC20TransferSelector(selector) { - return EthereumTransferAssetsKind, nil + return EthereumTransferAssetsKindType, nil } - return EthereumInvokeKind, nil + return EthereumInvokeKindType, nil } type EthereumTransactionKindResolver interface { @@ -53,15 +55,15 @@ func NewEthereumTransactionKindResolver(resolver ethKindResolverState, scheme Sc } func (e *ethTxKindResolver) ResolveTxKind(ethTx *EthereumTransaction, isBlockRewardDistributionActivated bool) (EthereumTransactionKind, error) { - txKind, err := GuessEthereumTransactionKind(ethTx.Data()) + txKind, err := GuessEthereumTransactionKindType(ethTx.Data()) if err != nil { return nil, errors.Wrap(err, "failed to guess ethereum tx kind") } switch txKind { - case EthereumTransferWavesKind: + case EthereumTransferWavesKindType: return NewEthereumTransferWavesTxKind(), nil - case EthereumTransferAssetsKind: + case EthereumTransferAssetsKindType: db := ethabi.NewErc20MethodsMap() decodedData, err := db.ParseCallDataRide(ethTx.Data(), isBlockRewardDistributionActivated) if err != nil { @@ -81,7 +83,7 @@ func (e *ethTxKindResolver) ResolveTxKind(ethTx *EthereumTransaction, isBlockRew return nil, errors.Wrap(err, "failed to get erc20 arguments from decoded data") } return NewEthereumTransferAssetsErc20TxKind(*decodedData, *NewOptionalAssetFromDigest(assetInfo.ID), erc20Arguments), nil - case EthereumInvokeKind: + case EthereumInvokeKindType: scriptAddr, err := ethTx.WavesAddressTo(e.scheme) if err != nil { return nil, err diff --git a/pkg/ride/functions_proto.go b/pkg/ride/functions_proto.go index d7216a70c..122d86014 100644 --- a/pkg/ride/functions_proto.go +++ b/pkg/ride/functions_proto.go @@ -865,6 +865,37 @@ func transferByID(env environment, args ...rideType) (rideType, error) { } return nil, errors.Wrap(err, "transferByID") } + switch t := tx.GetTypeInfo().Type; t { + case proto.TransferTransaction: + // ok, it's transfer tx + case proto.EthereumMetamaskTransaction: + ethTx, ok := tx.(*proto.EthereumTransaction) + if !ok { + return nil, errors.Errorf("transferByID: expected ethereum transaction, got (%T)", tx) + } + kindType, ktErr := proto.GuessEthereumTransactionKindType(ethTx.Data()) + if ktErr != nil { + return nil, errors.Wrap(err, "transferByID: failed to guess ethereum transaction kind type") + } + switch kindType { + case proto.EthereumTransferWavesKindType, proto.EthereumTransferAssetsKindType: + // ok, it's an ethereum transfer tx (waves or asset) + case proto.EthereumInvokeKindType: + return rideUnit{}, nil // it's not an ethereum transfer tx + default: + return rideUnit{}, errors.Errorf("transferByID: unreachable point reached in eth kind type switch") + } + case proto.GenesisTransaction, proto.PaymentTransaction, proto.IssueTransaction, + proto.ReissueTransaction, proto.BurnTransaction, proto.ExchangeTransaction, + proto.LeaseTransaction, proto.LeaseCancelTransaction, proto.CreateAliasTransaction, + proto.MassTransferTransaction, proto.DataTransaction, proto.SetScriptTransaction, + proto.SponsorshipTransaction, proto.SetAssetScriptTransaction, proto.InvokeScriptTransaction, + proto.UpdateAssetInfoTransaction, proto.InvokeExpressionTransaction: + // it's not a transfer transaction + return rideUnit{}, nil + default: + return rideUnit{}, errors.Errorf("transferByID: unreachable point reached in tx type switch") + } obj, err := transactionToObject(env, tx) if err != nil { return nil, errors.Wrap(err, "transferByID") diff --git a/pkg/ride/functions_proto_test.go b/pkg/ride/functions_proto_test.go index 1b3df4e35..b3290e4aa 100644 --- a/pkg/ride/functions_proto_test.go +++ b/pkg/ride/functions_proto_test.go @@ -4,6 +4,9 @@ import ( "bytes" "encoding/base64" "encoding/hex" + "math/big" + "strconv" + "strings" "testing" "time" @@ -15,8 +18,11 @@ import ( "github.com/wavesplatform/gowaves/pkg/crypto" "github.com/wavesplatform/gowaves/pkg/keyvalue" "github.com/wavesplatform/gowaves/pkg/proto" + "github.com/wavesplatform/gowaves/pkg/proto/ethabi" "github.com/wavesplatform/gowaves/pkg/ride/ast" + "github.com/wavesplatform/gowaves/pkg/ride/meta" "github.com/wavesplatform/gowaves/pkg/types" + "github.com/wavesplatform/gowaves/pkg/util/byte_helpers" ) var ( @@ -763,8 +769,123 @@ func TestBlockInfoByHeight(t *testing.T) { } } +func getPtr[T any](t T) *T { return &t } + func TestTransferByID(t *testing.T) { - t.SkipNow() + dApp1 := newTestAccount(t, "DAPP1") // 3MzDtgL5yw73C2xVLnLJCrT5gCL4357a4sz + sender := newTestAccount(t, "SENDER") // 3N8CkZAyS4XcDoJTJoKNuNk2xmNKmQj7myW + txID, err := crypto.NewDigestFromBase58("GemGCop1arCvTY447FLH8tDQF7knvzNCocNTHqKQBus9") + require.NoError(t, err) + assetID := txID + stubEthPK := new(proto.EthereumPublicKey) + ethTo := getPtr(proto.EthereumAddress(assetID[:proto.EthereumAddressSize])) + + erc20HexData := "0xa9059cbb0000000000000000000000009a1989946ae4249aac19ac7a038d24aab03c3d8c00000000000000000000000000000000000000000000000000001cc92ad60000" //nolint:lll + erc20Data, err := hex.DecodeString(strings.TrimPrefix(erc20HexData, "0x")) + require.NoError(t, err) + callData, err := ethabi.NewErc20MethodsMap().ParseCallDataRide(erc20Data, true) + require.NoError(t, err) + + rideFunctionMeta := meta.Function{ + Name: "call", + Arguments: []meta.Type{meta.String}, + } + callHexData := "0x3e08c22800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000573616664730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" //nolint:lll + invokeData, err := hex.DecodeString(strings.TrimPrefix(callHexData, "0x")) + require.NoError(t, err) + mm, err := ethabi.NewMethodsMapFromRideDAppMeta(meta.DApp{Functions: []meta.Function{rideFunctionMeta}}) + require.NoError(t, err) + invokeCallData, err := mm.ParseCallDataRide(invokeData, true) + require.NoError(t, err) + + testCases := []struct { + tx proto.Transaction + unit bool + }{ + { + tx: byte_helpers.TransferWithProofs.Transaction.Clone(), + unit: false, + }, + { + tx: byte_helpers.TransferWithSig.Transaction.Clone(), + unit: false, + }, + { + tx: getPtr(proto.NewEthereumTransaction( + &proto.EthereumLegacyTx{To: ethTo, Value: big.NewInt(100500)}, + proto.NewEthereumTransferWavesTxKind(), + &txID, + stubEthPK, + 0, + )), + unit: false, + }, + { + tx: getPtr(proto.NewEthereumTransaction( + &proto.EthereumLegacyTx{ + To: ethTo, + Data: erc20Data, + }, + proto.NewEthereumTransferAssetsErc20TxKind( + *callData, + proto.NewOptionalAsset(true, assetID), + ethabi.ERC20TransferArguments{Recipient: sender.address().ID(), Amount: 100500}, + ), + &txID, + stubEthPK, + 0, + )), + unit: false, + }, + { + tx: getPtr(proto.NewEthereumTransaction( + &proto.EthereumLegacyTx{ + To: ethTo, + Data: invokeData, + }, + proto.NewEthereumInvokeScriptTxKind(*invokeCallData), + &txID, + stubEthPK, + 0, + )), + unit: true, + }, + { + tx: byte_helpers.InvokeScriptWithProofs.Transaction.Clone(), + unit: true, + }, + } + + for i, testCase := range testCases { + t.Run(strconv.Itoa(i+1), func(t *testing.T) { + env := newTestEnv(t).withLibVersion(ast.LibV5).withComplexityLimit(ast.LibV5, 26000). + withBlockV5Activated().withProtobufTx(). + withDataEntriesSizeV2().withMessageLengthV3(). + withValidateInternalPayments().withThis(dApp1). + withDApp(dApp1).withSender(sender). + withInvocation("call"). + withWavesBalance(dApp1, 1_00000000).withWavesBalance(sender, 1_00000000). + withTransaction(testCase.tx). + withAsset(&proto.FullAssetInfo{ + AssetInfo: proto.AssetInfo{ + AssetConstInfo: proto.AssetConstInfo{ + ID: txID, + }, + }, + }). + withWrappedState() + + txIDBytes := txID.Bytes() + res, tErr := transferByID(env.me, rideByteVector(txIDBytes)) + assert.NoError(t, tErr) + assert.NotNil(t, res) + if testCase.unit { + assert.Equal(t, rideUnit{}, res) + } else { + assert.NotEqual(t, rideUnit{}, res) + } + }) + } } func TestAddressToString(t *testing.T) {