diff --git a/pkg/metamask/service.go b/pkg/metamask/service.go index 53bdb8367c..b7c0ad9205 100644 --- a/pkg/metamask/service.go +++ b/pkg/metamask/service.go @@ -143,10 +143,6 @@ func (s RPCService) Eth_EstimateGas(req estimateGasRequest) (string, error) { value = new(big.Int) data []byte ) - if req.To == nil { - zap.S().Debug("Eth_EstimateGas: trying estimate gas for set dApp transaction") - return "", errors.New("gas estimation for set dApp transaction is not permitted") - } if req.Value != nil { var _, ok = value.SetString(strings.TrimPrefix(*req.Value, "0x"), 16) if !ok { @@ -163,7 +159,7 @@ func (s RPCService) Eth_EstimateGas(req estimateGasRequest) (string, error) { } } - txKind, err := state.GuessEthereumTransactionKind(data) + txKind, err := state.GuessEthereumTransactionKind(data, req.To) if err != nil { return "", errors.Errorf("failed to guess ethereum tx kind, %v", err) } diff --git a/pkg/proto/eth_transaction.go b/pkg/proto/eth_transaction.go index 068b3cb22f..53163757d8 100644 --- a/pkg/proto/eth_transaction.go +++ b/pkg/proto/eth_transaction.go @@ -129,10 +129,13 @@ func (tx *EthereumTransferAssetsErc20TxKind) String() string { type EthereumInvokeScriptTxKind struct { decodedData ethabi.DecodedCallData + TxID *crypto.Digest + From WavesAddress + To WavesAddress } -func NewEthereumInvokeScriptTxKind(decodedData ethabi.DecodedCallData) *EthereumInvokeScriptTxKind { - return &EthereumInvokeScriptTxKind{decodedData: decodedData} +func NewEthereumInvokeScriptTxKind(decodedData ethabi.DecodedCallData, txID *crypto.Digest, from WavesAddress, to WavesAddress) *EthereumInvokeScriptTxKind { + return &EthereumInvokeScriptTxKind{decodedData: decodedData, TxID: txID, From: from, To: to} } func (tx *EthereumInvokeScriptTxKind) DecodedData() *ethabi.DecodedCallData { @@ -143,6 +146,32 @@ func (tx *EthereumInvokeScriptTxKind) String() string { return "EthereumInvokeScriptTxKind" } +func (tx *EthereumInvokeScriptTxKind) markerInvokeScriptTx() { + +} + +type EthereumInvokeExpressionTxKind struct { + Expression string + TxID *crypto.Digest + From WavesAddress +} + +func NewEthereumInvokeExpressionTxKind(expression string, txID *crypto.Digest, from WavesAddress) *EthereumInvokeExpressionTxKind { + return &EthereumInvokeExpressionTxKind{Expression: expression, TxID: txID, From: from} +} + +func (tx *EthereumInvokeExpressionTxKind) DecodedData() *ethabi.DecodedCallData { + return nil +} + +func (tx *EthereumInvokeExpressionTxKind) String() string { + return "EthereumInvokeExpressionTxKind" +} + +func (tx *EthereumInvokeExpressionTxKind) markerInvokeExpressionTx() { + +} + type EthereumTransaction struct { inner EthereumTxData innerBinarySize int diff --git a/pkg/proto/invoke_union.go b/pkg/proto/invoke_union.go new file mode 100644 index 0000000000..421221e4ef --- /dev/null +++ b/pkg/proto/invoke_union.go @@ -0,0 +1,41 @@ +package proto + +import "github.com/wavesplatform/gowaves/pkg/crypto" + +type InvokeTxUnion interface { + TxID() crypto.Digest +} + +type InvokeScriptSubTransaction interface { + markerInvokeScriptTx() +} + +type InvokeExpressionSubTransaction interface { + markerInvokeExpressionTx() +} + +type InvokeScriptTxUnion struct { + SubTx InvokeScriptSubTransaction + txID crypto.Digest +} + +func NewInvokeScriptTxUnion(subTransaction InvokeScriptSubTransaction, id crypto.Digest) *InvokeScriptTxUnion { + return &InvokeScriptTxUnion{SubTx: subTransaction, txID: id} +} + +func (invScript InvokeScriptTxUnion) TxID() crypto.Digest { + return invScript.txID +} + +type InvokeExpressionTxUnion struct { + SubTx InvokeExpressionSubTransaction + txID crypto.Digest +} + +func NewInvokeExpressionTxUnion(subTransaction InvokeExpressionSubTransaction, id crypto.Digest) *InvokeExpressionTxUnion { + return &InvokeExpressionTxUnion{SubTx: subTransaction, txID: id} +} + +func (invExp InvokeExpressionTxUnion) TxID() crypto.Digest { + return invExp.txID +} diff --git a/pkg/proto/transactions.go b/pkg/proto/transactions.go index 8d396bf16b..a315b23aee 100644 --- a/pkg/proto/transactions.go +++ b/pkg/proto/transactions.go @@ -39,7 +39,6 @@ const ( UpdateAssetInfoTransaction // 17 - UpdateAssetInfoTransaction InvokeExpressionTransaction // 18 - InvokeExpressionTransaction EthereumMetamaskTransaction // 19 - EthereumMetamaskTransaction is a transaction which is received from metamask - ) // TxFailureReason indicates Transactions failure reasons. diff --git a/pkg/proto/transactions_with_proofs.go b/pkg/proto/transactions_with_proofs.go index c45bb068e6..ec10a3b7f6 100644 --- a/pkg/proto/transactions_with_proofs.go +++ b/pkg/proto/transactions_with_proofs.go @@ -4758,6 +4758,9 @@ func (tx *InvokeScriptWithProofs) ToProtobufSigned(scheme Scheme) (*g.SignedTran }, nil } +func (tx *InvokeScriptWithProofs) markerInvokeScriptTx() { +} + type UpdateAssetInfoWithProofs struct { Type TransactionType `json:"type"` Version byte `json:"version,omitempty"` @@ -5136,3 +5139,7 @@ func (tx *InvokeExpressionTransactionWithProofs) ToProtobufSigned(scheme Scheme) Proofs: tx.Proofs.Bytes(), }, nil } + +func (tx *InvokeExpressionTransactionWithProofs) markerInvokeExpressionTx() { + +} diff --git a/pkg/ride/converters.go b/pkg/ride/converters.go index aee8b194f6..70472cb141 100644 --- a/pkg/ride/converters.go +++ b/pkg/ride/converters.go @@ -886,7 +886,7 @@ func invokeScriptWithProofsToObject(scheme byte, tx *proto.InvokeScriptWithProof return r, nil } -// TODO think of reusing "InvokeScripTToObject" function. Also should we fill "payments" and "function name" fields"? +// TODO think of reusing "InvokeScripTToObject" function.TestEthereumInvokeWithoutPaymentsAndArguments func invokeExpressionWithProofsToObject(scheme byte, tx *proto.InvokeExpressionTransactionWithProofs) (rideObject, error) { sender, err := proto.NewAddressFromPublicKey(scheme, tx.SenderPK) if err != nil { @@ -982,13 +982,16 @@ func ethereumTransactionToObject(scheme proto.Scheme, tx *proto.EthereumTransact r["bodyBytes"] = rideBytes(nil) r["proofs"] = proofs(proto.NewProofs()) + r["version"] = rideInt(tx.GetVersion()) + r["id"] = rideBytes(tx.ID.Bytes()) + r["sender"] = rideAddress(sender) + r["senderPublicKey"] = rideBytes(callerPK) + r["feeAssetId"] = optionalAsset(proto.NewOptionalAssetWaves()) + r["timestamp"] = rideInt(tx.GetTimestamp()) + r["fee"] = rideInt(tx.GetFee()) switch kind := tx.TxKind.(type) { case *proto.EthereumTransferWavesTxKind: r[instanceFieldName] = rideString("TransferTransaction") - r["version"] = rideInt(tx.GetVersion()) - r["id"] = rideBytes(tx.ID.Bytes()) - r["sender"] = rideAddress(sender) - r["senderPublicKey"] = rideBytes(callerPK) r["recipient"] = rideRecipient(proto.NewRecipientFromAddress(*to)) r["assetId"] = optionalAsset(proto.NewOptionalAssetWaves()) res := new(big.Int).Div(tx.Value(), big.NewInt(int64(proto.DiffEthWaves))) @@ -999,18 +1002,10 @@ func ethereumTransactionToObject(scheme proto.Scheme, tx *proto.EthereumTransact } amount := res.Int64() r["amount"] = rideInt(amount) - r["fee"] = rideInt(tx.GetFee()) - r["feeAssetId"] = optionalAsset(proto.NewOptionalAssetWaves()) r["attachment"] = rideBytes(nil) - r["timestamp"] = rideInt(tx.GetTimestamp()) case *proto.EthereumTransferAssetsErc20TxKind: r[instanceFieldName] = rideString("TransferTransaction") - r["version"] = rideInt(tx.GetVersion()) - r["id"] = rideBytes(tx.ID.Bytes()) - r["sender"] = rideAddress(sender) - r["senderPublicKey"] = rideBytes(callerPK) - recipientAddr, err := proto.EthereumAddress(kind.Arguments.Recipient).ToWavesAddress(scheme) if err != nil { return nil, errors.Wrap(err, "failed to convert ethereum ERC20 transfer recipient to WavesAddress") @@ -1018,17 +1013,10 @@ func ethereumTransactionToObject(scheme proto.Scheme, tx *proto.EthereumTransact r["recipient"] = rideRecipient(proto.NewRecipientFromAddress(recipientAddr)) r["assetId"] = optionalAsset(kind.Asset) r["amount"] = rideInt(kind.Arguments.Amount) - r["fee"] = rideInt(tx.GetFee()) - r["feeAssetId"] = optionalAsset(proto.NewOptionalAssetWaves()) r["attachment"] = rideBytes(nil) - r["timestamp"] = rideInt(tx.GetTimestamp()) case *proto.EthereumInvokeScriptTxKind: r[instanceFieldName] = rideString("InvokeScriptTransaction") - r["version"] = rideInt(tx.GetVersion()) - r["id"] = rideBytes(tx.ID.Bytes()) - r["sender"] = rideAddress(sender) - r["senderPublicKey"] = rideBytes(callerPK) r["dApp"] = rideRecipient(proto.NewRecipientFromAddress(*to)) var scriptPayments []proto.ScriptPayment @@ -1059,7 +1047,6 @@ func ethereumTransactionToObject(scheme proto.Scheme, tx *proto.EthereumTransact r["payment"] = rideUnit{} r["payments"] = make(rideList, 0) } - r["feeAssetId"] = optionalAsset(proto.NewOptionalAssetWaves()) r["function"] = rideString(tx.TxKind.DecodedData().Name) arguments, err := ConvertDecodedEthereumArgumentsToProtoArguments(tx.TxKind.DecodedData().Inputs) if err != nil { @@ -1074,9 +1061,14 @@ func ethereumTransactionToObject(scheme proto.Scheme, tx *proto.EthereumTransact args[i] = a } r["args"] = args - r["fee"] = rideInt(tx.GetFee()) - r["timestamp"] = rideInt(tx.GetTimestamp()) - + case *proto.EthereumInvokeExpressionTxKind: + r := make(rideObject) + r[instanceFieldName] = rideString("InvokeExpressionTransaction") + r["dApp"] = rideRecipient(proto.NewRecipientFromAddress(sender)) + r["payment"] = rideUnit{} + r["payments"] = make(rideList, 0) + r["function"] = rideString("default") + r["args"] = rideList{} default: return nil, errors.New("unknown ethereum transaction kind") } @@ -1189,6 +1181,7 @@ func invocationToObject(rideVersion int, scheme byte, tx proto.Transaction) (rid ID = *transaction.ID FeeAsset = transaction.FeeAsset Fee = transaction.Fee + switch rideVersion { case 1, 2, 3: r["payment"] = rideUnit{} diff --git a/pkg/ride/environment.go b/pkg/ride/environment.go index 9065b9dd31..f7eb69ec5c 100644 --- a/pkg/ride/environment.go +++ b/pkg/ride/environment.go @@ -1365,8 +1365,12 @@ func (e *EvaluationEnvironment) SetInvoke(tx proto.Transaction, v int) error { return nil } -func (e *EvaluationEnvironment) SetEthereumInvoke(tx *proto.EthereumTransaction, v int, payments []proto.ScriptPayment) error { - obj, err := ethereumInvocationToObject(v, e.sch, tx, payments) +func (e *EvaluationEnvironment) SetEthereumInvoke(tx proto.Transaction, v int, payments []proto.ScriptPayment) error { + ethTx, ok := tx.(*proto.EthereumTransaction) + if !ok { + return errors.New("failed to transform interface to ethereum transaction") + } + obj, err := ethereumInvocationToObject(v, e.sch, ethTx, payments) if err != nil { return err } diff --git a/pkg/state/appender.go b/pkg/state/appender.go index cb0c89ff31..f6e8b2909f 100644 --- a/pkg/state/appender.go +++ b/pkg/state/appender.go @@ -5,7 +5,6 @@ import ( "github.com/mr-tron/base58/base58" "github.com/pkg/errors" - "github.com/wavesplatform/gowaves/pkg/crypto" "github.com/wavesplatform/gowaves/pkg/errs" "github.com/wavesplatform/gowaves/pkg/proto" "github.com/wavesplatform/gowaves/pkg/settings" @@ -212,9 +211,9 @@ func (a *txAppender) checkTxFees(tx proto.Transaction, info *fallibleValidationP if err != nil { return err } - // TODO handle ethereum invoke expression tx + // ethereum InvokeScript and InvokeExpression case proto.EthereumMetamaskTransaction: - feeChanges, err = a.txHandler.td.createFeeDiffEthereumInvokeScriptWithProofs(tx, differInfo) + feeChanges, err = a.txHandler.td.createFeeDiffEthereumInvokeWithProofs(tx, differInfo) if err != nil { return err } @@ -497,9 +496,7 @@ func (a *txAppender) appendTx(tx proto.Transaction, params *appendTxParams) erro ethTx.TxKind.String(), ethTx.ID.String(), params.checkerInfo.height+1, err, ) } - // In UTX balances are always validated. - needToValidateBalanceDiff = params.validatingUtx - case *proto.EthereumInvokeScriptTxKind: + case *proto.EthereumInvokeScriptTxKind, *proto.EthereumInvokeExpressionTxKind: fallibleInfo := &fallibleValidationParams{ appendTxParams: params, senderScripted: accountHasVerifierScript, @@ -512,7 +509,10 @@ func (a *txAppender) appendTx(tx proto.Transaction, params *appendTxParams) erro ethTx.TxKind.String(), ethTx.ID.String(), params.checkerInfo.height+1, err, ) } + } + // In UTX balances are always validated. + needToValidateBalanceDiff = params.validatingUtx default: applicationRes, err = a.handleDefaultTransaction(tx, params, accountHasVerifierScript) if err != nil { @@ -646,27 +646,28 @@ type applicationResult struct { } func (a *txAppender) handleInvoke(tx proto.Transaction, info *fallibleValidationParams) (*applicationResult, error) { - var ID crypto.Digest + var invokeUnion proto.InvokeTxUnion switch t := tx.(type) { case *proto.InvokeScriptWithProofs: - ID = *t.ID + invokeUnion = proto.NewInvokeScriptTxUnion(t, *t.ID) case *proto.InvokeExpressionTransactionWithProofs: - ID = *t.ID - case *proto.EthereumTransaction: - // TODO: handle ethereum invoke expression tx - switch t.TxKind.(type) { + invokeUnion = proto.NewInvokeExpressionTxUnion(t, *t.ID) + case *proto.EthereumTransaction: // ethereum InvokeExpression and InvokeScript + switch kind := t.TxKind.(type) { case *proto.EthereumInvokeScriptTxKind: - ID = *t.ID + invokeUnion = proto.NewInvokeScriptTxUnion(kind, *t.ID) + case *proto.EthereumInvokeExpressionTxKind: + invokeUnion = proto.NewInvokeExpressionTxUnion(kind, *t.ID) default: - return nil, errors.Errorf("unexpected ethereum tx kind (%T)", tx) + return nil, errors.Errorf("failed to handle invoke: wrong type of ethereum transactiion (%T)", tx) } - ID = *t.ID default: return nil, errors.Errorf("failed to handle invoke: wrong type of transaction (%T)", tx) } - res, err := a.ia.applyInvokeScript(tx, info) + + res, err := a.ia.applyInvokeScript(tx, invokeUnion, info) if err != nil { - zap.S().Debugf("failed to apply InvokeScript transaction %s to state: %v", ID.String(), err) + zap.S().Debugf("failed to apply InvokeScript transaction %s to state: %v", invokeUnion.TxID().String(), err) return nil, err } return res, nil diff --git a/pkg/state/block_differ.go b/pkg/state/block_differ.go index 1c01ce2f4c..4be7867f20 100644 --- a/pkg/state/block_differ.go +++ b/pkg/state/block_differ.go @@ -125,7 +125,7 @@ func (d *blockDiffer) createFailedTransactionDiff(tx proto.Transaction, block *p return txBalanceChanges{}, err } case proto.EthereumMetamaskTransaction: - txChanges, err = d.handler.td.createFeeDiffEthereumInvokeScriptWithProofs(tx, differInfo) + txChanges, err = d.handler.td.createFeeDiffEthereumInvokeWithProofs(tx, differInfo) if err != nil { return txBalanceChanges{}, err } diff --git a/pkg/state/eth_info.go b/pkg/state/eth_info.go index d8d4bbf78b..29952e969a 100644 --- a/pkg/state/eth_info.go +++ b/pkg/state/eth_info.go @@ -11,6 +11,7 @@ const ( EthereumTransferWavesKind = iota + 1 EthereumTransferAssetsKind EthereumInvokeKind + EthereumInvokeExpressionKind ) type ethInfo struct { @@ -22,11 +23,15 @@ func newEthInfo(stor *blockchainEntitiesStorage, settings *settings.BlockchainSe return ðInfo{stor: stor, settings: settings} } -func GuessEthereumTransactionKind(data []byte) (int64, error) { +func GuessEthereumTransactionKind(data []byte, receiver *proto.EthereumAddress) (int64, error) { if len(data) == 0 { return EthereumTransferWavesKind, nil } + if receiver == nil { + return EthereumInvokeExpressionKind, nil + } + selectorBytes := data if len(data) < ethabi.SelectorSize { return 0, errors.Errorf("length of data from ethereum transaction is less than %d", ethabi.SelectorSize) @@ -44,7 +49,7 @@ func GuessEthereumTransactionKind(data []byte) (int64, error) { } func (e *ethInfo) ethereumTransactionKind(ethTx *proto.EthereumTransaction, params *appendTxParams) (proto.EthereumTransactionKind, error) { - txKind, err := GuessEthereumTransactionKind(ethTx.Data()) + txKind, err := GuessEthereumTransactionKind(ethTx.Data(), ethTx.To()) if err != nil { return nil, errors.Wrap(err, "failed to guess ethereum tx kind") } @@ -91,9 +96,22 @@ func (e *ethInfo) ethereumTransactionKind(ethTx *proto.EthereumTransaction, para if err != nil { return nil, errors.Wrap(err, "failed to parse ethereum data") } - - return proto.NewEthereumInvokeScriptTxKind(*decodedData), nil - + from, err := ethTx.WavesAddressFrom(e.settings.AddressSchemeCharacter) + if err != nil { + return nil, err + } + to, err := ethTx.WavesAddressTo(e.settings.AddressSchemeCharacter) + if err != nil { + return nil, err + } + return proto.NewEthereumInvokeScriptTxKind(*decodedData, ethTx.ID, from, *to), nil + case EthereumInvokeExpressionKind: + expression := ethTx.Data() + from, err := ethTx.WavesAddressFrom(e.settings.AddressSchemeCharacter) + if err != nil { + return nil, err + } + return proto.NewEthereumInvokeExpressionTxKind(string(expression), ethTx.ID, from), nil default: return nil, errors.New("unexpected ethereum tx kind") } diff --git a/pkg/state/ethereum_tx_test.go b/pkg/state/ethereum_tx_test.go index 2bef71ce3d..a222bf7cdc 100644 --- a/pkg/state/ethereum_tx_test.go +++ b/pkg/state/ethereum_tx_test.go @@ -261,16 +261,18 @@ func TestEthereumInvoke(t *testing.T) { proto.AssetID(recipientEth): {}, } txAppender := defaultTxAppender(t, storage, state, assetsUncertain, proto.MainNetScheme) - + recipient, err := recipientEth.ToWavesAddress(0) + assert.NoError(t, err) txData := defaultEthereumLegacyTxData(1000000000000000, &recipientEth, nil, 500000, proto.MainNetScheme) decodedData := defaultDecodedData("call", []ethabi.DecodedArg{{Value: ethabi.Int(10)}}, []ethabi.Payment{{Amount: 5, AssetID: proto.NewOptionalAssetWaves().ID}}) - txKind := proto.NewEthereumInvokeScriptTxKind(decodedData) + txKind := proto.NewEthereumInvokeScriptTxKind(decodedData, &crypto.Digest{}, sender, recipient) tx := proto.NewEthereumTransaction(txData, txKind, &crypto.Digest{}, &senderPK, 0) fallibleInfo := &fallibleValidationParams{appendTxParams: appendTxParams, senderScripted: false, senderAddress: sender} scriptAddress, tree := applyScript(t, &tx, storage, fallibleInfo) fallibleInfo.rideV5Activated = true - res, err := txAppender.ia.sc.invokeFunction(tree, &tx, fallibleInfo, scriptAddress, *tx.ID) + invokeUnion := proto.NewInvokeScriptTxUnion(txKind, *tx.ID) + res, err := txAppender.ia.sc.invokeFunction(tree, &tx, fallibleInfo, scriptAddress, invokeUnion) assert.NoError(t, err) assert.True(t, res.Result()) @@ -393,12 +395,17 @@ func TestEthereumInvokeWithoutPaymentsAndArguments(t *testing.T) { txData := defaultEthereumLegacyTxData(1000000000000000, &recipientEth, nil, 500000, proto.MainNetScheme) decodedData := defaultDecodedData("call", nil, nil) - tx := proto.NewEthereumTransaction(txData, proto.NewEthereumInvokeScriptTxKind(decodedData), &crypto.Digest{}, &senderPK, 0) + + recipient, err := recipientEth.ToWavesAddress(0) + assert.NoError(t, err) + txKind := proto.NewEthereumInvokeScriptTxKind(decodedData, &crypto.Digest{}, sender, recipient) + tx := proto.NewEthereumTransaction(txData, txKind, &crypto.Digest{}, &senderPK, 0) fallibleInfo := &fallibleValidationParams{appendTxParams: appendTxParams, senderScripted: false, senderAddress: sender} scriptAddress, tree := applyScript(t, &tx, storage, fallibleInfo) fallibleInfo.rideV5Activated = true - res, err := txAppender.ia.sc.invokeFunction(tree, &tx, fallibleInfo, scriptAddress, *tx.ID) + invokeUnion := proto.NewInvokeScriptTxUnion(txKind, *tx.ID) + res, err := txAppender.ia.sc.invokeFunction(tree, &tx, fallibleInfo, scriptAddress, invokeUnion) assert.NoError(t, err) assert.True(t, res.Result()) @@ -467,12 +474,17 @@ func TestEthereumInvokeAllArguments(t *testing.T) { {Value: ethabi.Bool(true)}, // will leave it here {Value: ethabi.List{ethabi.Int(4)}}, }, nil) - tx := proto.NewEthereumTransaction(txData, proto.NewEthereumInvokeScriptTxKind(decodedData), &crypto.Digest{}, &senderPK, 0) + + recipient, err := recipientEth.ToWavesAddress(0) + assert.NoError(t, err) + txKind := proto.NewEthereumInvokeScriptTxKind(decodedData, &crypto.Digest{}, sender, recipient) + tx := proto.NewEthereumTransaction(txData, txKind, &crypto.Digest{}, &senderPK, 0) fallibleInfo := &fallibleValidationParams{appendTxParams: appendTxParams, senderScripted: false, senderAddress: sender} scriptAddress, tree := applyScript(t, &tx, storage, fallibleInfo) fallibleInfo.rideV5Activated = true - res, err := txAppender.ia.sc.invokeFunction(tree, &tx, fallibleInfo, scriptAddress, *tx.ID) + invokeUnion := proto.NewInvokeScriptTxUnion(txKind, *tx.ID) + res, err := txAppender.ia.sc.invokeFunction(tree, &tx, fallibleInfo, scriptAddress, invokeUnion) assert.NoError(t, err) assert.True(t, res.Result()) diff --git a/pkg/state/invoke_applier.go b/pkg/state/invoke_applier.go index ed6ec2f2c6..502ce355da 100644 --- a/pkg/state/invoke_applier.go +++ b/pkg/state/invoke_applier.go @@ -722,7 +722,7 @@ func (ia *invokeApplier) fallibleValidation(tx proto.Transaction, info *addlInvo // If the transaction does not fail, changes are committed (moved from uncertain to normal storage) // later in performInvokeScriptWithProofs(). // If the transaction fails, performInvokeScriptWithProofs() is not called and changes are discarded later using dropUncertain(). -func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleValidationParams) (*applicationResult, error) { +func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, invokeUnion proto.InvokeTxUnion, info *fallibleValidationParams) (*applicationResult, error) { // In defer we should clean all the temp changes invoke does to state. defer func() { ia.invokeDiffStor.invokeDiffsStor.reset() @@ -732,59 +732,37 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV paymentsLength int scriptAddr *proto.WavesAddress txID crypto.Digest - sender proto.Address + sender proto.WavesAddress tree *ride.Tree scriptPK crypto.PublicKey ) - switch transaction := tx.(type) { - case *proto.InvokeScriptWithProofs: - var err error - scriptAddr, err = recipientToAddress(transaction.ScriptRecipient, ia.stor.aliases, !info.initialisation) - if err != nil { - return nil, errors.Wrap(err, "recipientToAddress() failed") - } - paymentsLength = len(transaction.Payments) - txID = *transaction.ID - sender, err = proto.NewAddressFromPublicKey(ia.settings.AddressSchemeCharacter, transaction.SenderPK) - if err != nil { - return nil, errors.Wrapf(err, "failed to apply script invocation") - } - tree, err = ia.stor.scriptsStorage.newestScriptByAddr(*scriptAddr, !info.initialisation) - if err != nil { - return nil, errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptAddr.String()) - } - scriptPK, err = ia.stor.scriptsStorage.newestScriptPKByAddr(*scriptAddr, !info.initialisation) - if err != nil { - return nil, errors.Wrapf(err, "failed to get script's public key on address '%s'", scriptAddr.String()) - } + switch invokeType := invokeUnion.(type) { + case *proto.InvokeScriptTxUnion: + switch subTx := invokeType.SubTx.(type) { + case *proto.InvokeScriptWithProofs: + var err error + scriptAddr, err = recipientToAddress(subTx.ScriptRecipient, ia.stor.aliases, !info.initialisation) + if err != nil { + return nil, errors.Wrap(err, "recipientToAddress() failed") + } + paymentsLength = len(subTx.Payments) + txID = *subTx.ID + sender, err = proto.NewAddressFromPublicKey(ia.settings.AddressSchemeCharacter, subTx.SenderPK) + if err != nil { + return nil, errors.Wrapf(err, "failed to apply script invocation") + } + case *proto.EthereumInvokeScriptTxKind: + scriptAddr = &subTx.To + decodedData := subTx.DecodedData() + paymentsLength = len(decodedData.Payments) + txID = *subTx.TxID + sender = subTx.From + default: + return nil, errors.New("wrong sub transaction of invoke script transaction") - case *proto.InvokeExpressionTransactionWithProofs: - addr, err := proto.NewAddressFromPublicKey(ia.settings.AddressSchemeCharacter, transaction.SenderPK) - if err != nil { - return nil, errors.Wrap(err, "recipientToAddress() failed") - } - sender = addr - scriptAddr = &addr - tree, err = ride.Parse(transaction.Expression) - if err != nil { - return nil, errors.Wrap(err, "failed to parse decoded invoke expression into tree") } - txID = *transaction.ID - scriptPK = transaction.SenderPK - case *proto.EthereumTransaction: var err error - scriptAddr, err = transaction.WavesAddressTo(ia.settings.AddressSchemeCharacter) - if err != nil { - return nil, err - } - decodedData := transaction.TxKind.DecodedData() - paymentsLength = len(decodedData.Payments) - txID = *transaction.ID - sender, err = transaction.WavesAddressFrom(ia.settings.AddressSchemeCharacter) - if err != nil { - return nil, errors.Wrapf(err, "failed to apply script invocation") - } tree, err = ia.stor.scriptsStorage.newestScriptByAddr(*scriptAddr, !info.initialisation) if err != nil { return nil, errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptAddr.String()) @@ -793,11 +771,44 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV if err != nil { return nil, errors.Wrapf(err, "failed to get script's public key on address '%s'", scriptAddr.String()) } + case *proto.InvokeExpressionTxUnion: + switch subTx := invokeType.SubTx.(type) { + case *proto.InvokeExpressionTransactionWithProofs: + + var err error + sender, err = proto.NewAddressFromPublicKey(ia.settings.AddressSchemeCharacter, subTx.SenderPK) + if err != nil { + return nil, errors.Wrap(err, "recipientToAddress() failed") + } + scriptAddr = &sender + tree, err = ride.Parse(subTx.Expression) + if err != nil { + return nil, errors.Wrap(err, "failed to parse decoded invoke expression into tree") + } + txID = *subTx.ID + scriptPK = subTx.SenderPK + case *proto.EthereumInvokeExpressionTxKind: + var err error + sender = subTx.From + scriptAddr = &sender + tree, err = ride.Parse([]byte(subTx.Expression)) + if err != nil { + return nil, errors.Wrap(err, "failed to parse decoded invoke expression into tree") + } + + txID = *subTx.TxID + scriptPK, err = ia.stor.scriptsStorage.newestScriptPKByAddr(*scriptAddr, !info.initialisation) + if err != nil { + return nil, err + } + default: + return nil, errors.Errorf("wrong sub transaction of invoke expression transaction: unexpected type of transaction (%T)", tx) + } default: + return nil, errors.Errorf("failed to apply an invoke script: unexpected type of transaction (%T)", tx) } - // If BlockV5 feature is not activated, we never accept failed transactions. info.acceptFailed = info.blockV5Activated && info.acceptFailed // Check sender script, if any. @@ -834,7 +845,7 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV } // Call script function. - r, err := ia.sc.invokeFunction(tree, tx, info, *scriptAddr, txID) + r, err := ia.sc.invokeFunction(tree, tx, info, *scriptAddr, invokeUnion) if err != nil { // Script returned error, it's OK, but we have to decide is it failed or rejected transaction. // In the following cases the transaction is rejected: diff --git a/pkg/state/invoke_applier_test.go b/pkg/state/invoke_applier_test.go index f7ec887975..3be0eba0f3 100644 --- a/pkg/state/invoke_applier_test.go +++ b/pkg/state/invoke_applier_test.go @@ -108,8 +108,8 @@ func (to *invokeApplierTestObjects) applyAndSaveInvoke(t *testing.T, tx *proto.I to.state.stor.dropUncertain() to.state.appender.ia.sc.resetComplexity() }() - - res, err := to.state.appender.ia.applyInvokeScript(tx, info) + invokeUnion := proto.NewInvokeScriptTxUnion(tx, *tx.ID) + res, err := to.state.appender.ia.applyInvokeScript(tx, invokeUnion, info) require.NoError(t, err) err = to.state.appender.diffStor.saveTxDiff(res.changes.diff) assert.NoError(t, err) @@ -175,7 +175,8 @@ func (id *invokeApplierTestData) applyTest(t *testing.T, to *invokeApplierTestOb tx := createInvokeScriptWithProofs(t, id.payments, id.fc, feeAsset, invokeFee) if id.errorRes { - _, err := to.state.appender.ia.applyInvokeScript(tx, id.info) + invokeUnion := proto.NewInvokeScriptTxUnion(tx, *tx.ID) + _, err := to.state.appender.ia.applyInvokeScript(tx, invokeUnion, id.info) assert.Error(t, err) return } diff --git a/pkg/state/script_caller.go b/pkg/state/script_caller.go index b5e8d572ac..4614833f66 100644 --- a/pkg/state/script_caller.go +++ b/pkg/state/script_caller.go @@ -205,139 +205,158 @@ func (a *scriptCaller) callAssetScript(tx proto.Transaction, assetID crypto.Dige return a.callAssetScriptCommon(env, assetID, params) } -func (a *scriptCaller) invokeFunction(tree *ride.Tree, tx proto.Transaction, info *fallibleValidationParams, scriptAddress proto.WavesAddress, txID crypto.Digest) (ride.Result, error) { - env, err := ride.NewEnvironment(a.settings.AddressSchemeCharacter, a.state, a.settings.InternalInvokePaymentsValidationAfterHeight) - if err != nil { - return nil, errors.Wrap(err, "failed to create RIDE environment") - } - env.SetThisFromAddress(scriptAddress) - env.SetLastBlock(info.blockInfo) - env.SetTimestamp(tx.GetTimestamp()) - err = env.SetTransaction(tx) - if err != nil { - return nil, err - } - env.ChooseSizeCheck(tree.LibVersion) - env.ChooseTakeString(info.rideV5Activated) - env.ChooseMaxDataEntriesSize(info.rideV5Activated) +type invokeParameters struct { + tree *ride.Tree + info *fallibleValidationParams + functionName string + defaultFunction bool +} +func (a *scriptCaller) invokeScriptFunction(invokeScriptUnion *proto.InvokeScriptTxUnion, + env *ride.EvaluationEnvironment, tx proto.Transaction, invParams *invokeParameters) (ride.Result, error) { var ( - functionName string + err error functionArguments proto.Arguments - defaultFunction bool payments proto.ScriptPayments sender proto.WavesAddress - r ride.Result ) - switch transaction := tx.(type) { + switch subTx := invokeScriptUnion.SubTx.(type) { case *proto.InvokeScriptWithProofs: - err = env.SetInvoke(tx, tree.LibVersion) + err = env.SetInvoke(tx, invParams.tree.LibVersion) if err != nil { return nil, err } - payments = transaction.Payments - sender, err = proto.NewAddressFromPublicKey(a.settings.AddressSchemeCharacter, transaction.SenderPK) + payments = subTx.Payments + sender, err = proto.NewAddressFromPublicKey(a.settings.AddressSchemeCharacter, subTx.SenderPK) if err != nil { return nil, err } - functionName = transaction.FunctionCall.Name - functionArguments = transaction.FunctionCall.Arguments - defaultFunction = transaction.FunctionCall.Default - // Since V5 we have to create environment with wrapped state to which we put attached payments - if tree.LibVersion >= 5 { - env, err = ride.NewEnvironmentWithWrappedState(env, payments, sender, info.rideV5Activated, info.rideV6Activated) - if err != nil { - return nil, errors.Wrapf(err, "failed to create RIDE environment with wrapped state") + invParams.functionName = subTx.FunctionCall.Name + functionArguments = subTx.FunctionCall.Arguments + invParams.defaultFunction = subTx.FunctionCall.Default + case *proto.EthereumInvokeScriptTxKind: + abiPayments := subTx.DecodedData().Payments + scriptPayments := make([]proto.ScriptPayment, 0, len(abiPayments)) + for _, p := range abiPayments { + var optAsset proto.OptionalAsset + if p.PresentAssetID { + optAsset = *proto.NewOptionalAssetFromDigest(p.AssetID) + } else { + optAsset = proto.NewOptionalAssetWaves() } + scriptPayment := proto.ScriptPayment{Amount: uint64(p.Amount), Asset: optAsset} + scriptPayments = append(scriptPayments, scriptPayment) } - - r, err = ride.CallFunction(env, tree, functionName, functionArguments) + payments = scriptPayments + err = env.SetEthereumInvoke(tx, invParams.tree.LibVersion, scriptPayments) if err != nil { - if appendErr := a.appendFunctionComplexity(ride.EvaluationErrorSpentComplexity(err), scriptAddress, functionName, defaultFunction, info); appendErr != nil { - return nil, appendErr - } return nil, err } - case *proto.InvokeExpressionTransactionWithProofs: - err = env.SetInvoke(tx, tree.LibVersion) + sender = subTx.From + decodedData := subTx.DecodedData() + invParams.functionName = decodedData.Name + arguments, err := ride.ConvertDecodedEthereumArgumentsToProtoArguments(decodedData.Inputs) if err != nil { - return nil, err + return nil, errors.Errorf("failed to convert ethereum arguments, %v", err) } - sender, err = proto.NewAddressFromPublicKey(a.settings.AddressSchemeCharacter, transaction.SenderPK) + functionArguments = arguments + invParams.defaultFunction = true + default: + return nil, errors.New("wrong sub transaction of invoke script transaction") + } + + // Since V5 we have to create environment with wrapped state to which we put attached payments + if invParams.tree.LibVersion >= 5 { + env, err = ride.NewEnvironmentWithWrappedState(env, payments, sender, invParams.info.rideV5Activated, invParams.info.rideV6Activated) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to create RIDE environment with wrapped state") } - functionName = "" + } - // Since V5 we have to create environment with wrapped state to which we put attached payments - if tree.LibVersion >= 5 { - env, err = ride.NewEnvironmentWithWrappedState(env, payments, sender, info.rideV5Activated, info.rideV6Activated) - if err != nil { - return nil, errors.Wrapf(err, "failed to create RIDE environment with wrapped state") - } - } + return ride.CallFunction(env, invParams.tree, invParams.functionName, functionArguments) +} - r, err = ride.CallVerifier(env, tree) +func (a *scriptCaller) invokeExpressionFunction(invokeExpressionUnion *proto.InvokeExpressionTxUnion, + env *ride.EvaluationEnvironment, + tx proto.Transaction, invParams *invokeParameters) (ride.Result, error) { + var ( + err error + sender proto.WavesAddress + ) + switch subTx := invokeExpressionUnion.SubTx.(type) { + case *proto.InvokeExpressionTransactionWithProofs: + err = env.SetInvoke(tx, invParams.tree.LibVersion) if err != nil { - if appendErr := a.appendFunctionComplexity(ride.EvaluationErrorSpentComplexity(err), scriptAddress, functionName, defaultFunction, info); appendErr != nil { - return nil, appendErr - } return nil, err } - - case *proto.EthereumTransaction: - abiPayments := transaction.TxKind.DecodedData().Payments - scriptPayments := make([]proto.ScriptPayment, 0, len(abiPayments)) - for _, p := range abiPayments { - var optAsset proto.OptionalAsset - if p.PresentAssetID { - optAsset = *proto.NewOptionalAssetFromDigest(p.AssetID) - } else { - optAsset = proto.NewOptionalAssetWaves() - } - scriptPayment := proto.ScriptPayment{Amount: uint64(p.Amount), Asset: optAsset} - scriptPayments = append(scriptPayments, scriptPayment) - } - payments = scriptPayments - - err = env.SetEthereumInvoke(transaction, tree.LibVersion, scriptPayments) + sender, err = proto.NewAddressFromPublicKey(a.settings.AddressSchemeCharacter, subTx.SenderPK) if err != nil { return nil, err } - sender, err = transaction.WavesAddressFrom(a.settings.AddressSchemeCharacter) + case *proto.EthereumInvokeExpressionTxKind: + err = env.SetEthereumInvoke(tx, invParams.tree.LibVersion, nil) if err != nil { - return nil, errors.Errorf("failed to get waves address from ethereum transaction %v", err) + return nil, err } - decodedData := transaction.TxKind.DecodedData() - functionName = decodedData.Name - arguments, err := ride.ConvertDecodedEthereumArgumentsToProtoArguments(decodedData.Inputs) + sender = subTx.From + default: + return nil, errors.New("wrong sub transaction of invoke expression transaction") + } + + // Since V5 we have to create environment with wrapped state to which we put attached payments + if invParams.tree.LibVersion >= 5 { + env, err = ride.NewEnvironmentWithWrappedState(env, nil, sender, invParams.info.rideV5Activated, invParams.info.rideV6Activated) if err != nil { - return nil, errors.Errorf("failed to convert ethereum arguments, %v", err) + return nil, errors.Wrapf(err, "failed to create RIDE environment with wrapped state") } - functionArguments = arguments - defaultFunction = true - // Since V5 we have to create environment with wrapped state to which we put attached payments - if tree.LibVersion >= 5 { - env, err = ride.NewEnvironmentWithWrappedState(env, payments, sender, info.rideV5Activated, info.rideV6Activated) - if err != nil { - return nil, errors.Wrapf(err, "failed to create RIDE environment with wrapped state") + } + return ride.CallVerifier(env, invParams.tree) + +} + +func (a *scriptCaller) invokeFunction(tree *ride.Tree, tx proto.Transaction, info *fallibleValidationParams, scriptAddress proto.WavesAddress, invokeUnion proto.InvokeTxUnion) (ride.Result, error) { + env, err := ride.NewEnvironment(a.settings.AddressSchemeCharacter, a.state, a.settings.InternalInvokePaymentsValidationAfterHeight) + if err != nil { + return nil, errors.Wrap(err, "failed to create RIDE environment") + } + env.SetThisFromAddress(scriptAddress) + env.SetLastBlock(info.blockInfo) + env.SetTimestamp(tx.GetTimestamp()) + err = env.SetTransaction(tx) + if err != nil { + return nil, err + } + env.ChooseSizeCheck(tree.LibVersion) + env.ChooseTakeString(info.rideV5Activated) + env.ChooseMaxDataEntriesSize(info.rideV5Activated) + + var r ride.Result + + invParams := &invokeParameters{tree: tree, info: info, functionName: "", defaultFunction: false} + + switch invokeType := invokeUnion.(type) { + case *proto.InvokeScriptTxUnion: + r, err = a.invokeScriptFunction(invokeType, env, tx, invParams) + if err != nil { + if appendErr := a.appendFunctionComplexity(ride.EvaluationErrorSpentComplexity(err), scriptAddress, invParams.functionName, invParams.defaultFunction, info); appendErr != nil { + return nil, appendErr } + return nil, err // here is forbidden to make a custom error } - - r, err = ride.CallFunction(env, tree, functionName, functionArguments) + case *proto.InvokeExpressionTxUnion: + r, err = a.invokeExpressionFunction(invokeType, env, tx, invParams) if err != nil { - if appendErr := a.appendFunctionComplexity(ride.EvaluationErrorSpentComplexity(err), scriptAddress, functionName, defaultFunction, info); appendErr != nil { + if appendErr := a.appendFunctionComplexity(ride.EvaluationErrorSpentComplexity(err), scriptAddress, invParams.functionName, invParams.defaultFunction, info); appendErr != nil { return nil, appendErr } - return nil, err + return nil, err // here is forbidden to make a custom error } - default: - return nil, errors.Errorf("failed to invoke function: unexpected type of transaction (%T)", transaction) + return nil, errors.Errorf("failed to invoke function: unexpected type of transaction (%T)", tx) } - if err := a.appendFunctionComplexity(r.Complexity(), scriptAddress, functionName, defaultFunction, info); err != nil { + if err := a.appendFunctionComplexity(r.Complexity(), scriptAddress, invParams.functionName, invParams.defaultFunction, info); err != nil { return nil, err } return r, nil diff --git a/pkg/state/state.go b/pkg/state/state.go index b6ed1ed9b1..5ad407a39b 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -984,7 +984,7 @@ func (s *stateManager) addNewBlock(block, parent *proto.Block, initialisation bo height: height, initialisation: initialisation, } - // Check and perform block's transactions, create balance diffs, write transactions to storage. + // Check and perform block's transactions, create balance diffs, write transactions to storage.\ if err := s.appender.appendBlock(params); err != nil { return err } diff --git a/pkg/state/transaction_checker.go b/pkg/state/transaction_checker.go index e5b3552448..b5a595fb8d 100644 --- a/pkg/state/transaction_checker.go +++ b/pkg/state/transaction_checker.go @@ -483,7 +483,20 @@ func (tc *transactionChecker) checkEthereumTransactionWithProofs(transaction pro } return smartAssets, nil + case *proto.EthereumInvokeExpressionTxKind: + minFee := proto.EthereumInvokeMinFee + + if l := len(kind.Expression); l > proto.MaxContractScriptSize { + return nil, errors.Errorf("size of the expression %d is exceeded limit %d", l, proto.MaxContractScriptSize) + } + if err := tc.checkTimestamps(tx.GetTimestamp(), info.currentTimestamp, info.parentTimestamp); err != nil { + return nil, errs.Extend(err, "invalid timestamp") + } + if tx.GetFee() < minFee { + return nil, errors.Errorf("the fee for ethereum invoke tx is not enough, min fee is %d, got %d", proto.EthereumInvokeMinFee, tx.GetFee()) + } + return nil, nil default: return nil, errors.New("failed to check ethereum transaction, wrong kind of tx") } diff --git a/pkg/state/transaction_differ.go b/pkg/state/transaction_differ.go index 19e232c2d9..770383ec01 100644 --- a/pkg/state/transaction_differ.go +++ b/pkg/state/transaction_differ.go @@ -599,8 +599,8 @@ func (td *transactionDiffer) createDiffEthereumTransactionWithProofs(transaction return td.createDiffEthereumTransferWaves(ethTx, info) case *proto.EthereumTransferAssetsErc20TxKind: return td.createDiffEthereumErc20(ethTx, info) - case *proto.EthereumInvokeScriptTxKind: - return td.createDiffEthereumInvokeScript(ethTx, info) + case *proto.EthereumInvokeScriptTxKind, *proto.EthereumInvokeExpressionTxKind: + return td.createDiffEthereumInvoke(ethTx, info) default: return txBalanceChanges{}, errors.New("wrong kind of ethereum transaction") @@ -1463,27 +1463,35 @@ func (td *transactionDiffer) createDiffInvokeExpressionWithProofs(transaction pr return changes, nil } -func (td *transactionDiffer) createDiffEthereumInvokeScript(tx *proto.EthereumTransaction, info *differInfo) (txBalanceChanges, error) { +func (td *transactionDiffer) createDiffEthereumInvoke(tx *proto.EthereumTransaction, info *differInfo) (txBalanceChanges, error) { updateMinIntermediateBalance := false - - txInvokeScriptKind, ok := tx.TxKind.(*proto.EthereumInvokeScriptTxKind) - if !ok { - return txBalanceChanges{}, errors.New("failed to convert ethereum tx kind to EthereumTransferAssetsErc20TxKind") - } - - decodedData := txInvokeScriptKind.DecodedData() - - noPayments := len(decodedData.Payments) == 0 - if info.blockInfo.Timestamp >= td.settings.CheckTempNegativeAfterTime && !noPayments { - updateMinIntermediateBalance = true - } + var ( + decodedData *ethabi.DecodedCallData + noPayments = true + scriptAddress *proto.WavesAddress + ) diff := newTxDiff() // Append sender diff. senderAddress, err := tx.WavesAddressFrom(td.settings.AddressSchemeCharacter) if err != nil { return txBalanceChanges{}, errors.Wrapf(err, "failed to get sender address from ethereum invoke tx") } + switch kind := tx.TxKind.(type) { + case *proto.EthereumInvokeScriptTxKind: + decodedData = kind.DecodedData() + noPayments = len(decodedData.Payments) == 0 + scriptAddress, err = tx.WavesAddressTo(td.settings.AddressSchemeCharacter) + if err != nil { + return txBalanceChanges{}, err + } + case *proto.EthereumInvokeExpressionTxKind: + scriptAddress = &senderAddress + } + + if info.blockInfo.Timestamp >= td.settings.CheckTempNegativeAfterTime && !noPayments { + updateMinIntermediateBalance = true + } senderAddrID := senderAddress.ID() assetFee := proto.NewOptionalAssetWaves() @@ -1492,28 +1500,27 @@ func (td *transactionDiffer) createDiffEthereumInvokeScript(tx *proto.EthereumTr if err := diff.appendBalanceDiff(senderFeeKey, newBalanceDiff(senderFeeBalanceDiff, 0, 0, updateMinIntermediateBalance)); err != nil { return txBalanceChanges{}, err } - scriptAddr, err := tx.WavesAddressTo(td.settings.AddressSchemeCharacter) - if err != nil { - return txBalanceChanges{}, err - } - scriptAddrID := scriptAddr.ID() + scriptAddrID := scriptAddress.ID() - addresses := []proto.WavesAddress{senderAddress, *scriptAddr} + addresses := []proto.WavesAddress{senderAddress, *scriptAddress} changes := newTxBalanceChanges(addresses, diff) - for _, payment := range decodedData.Payments { - assetID := proto.NewOptionalAssetFromDigest(payment.AssetID) - senderPaymentKey := byteKey(senderAddrID, *assetID) - senderBalanceDiff := -payment.Amount - if err := diff.appendBalanceDiff(senderPaymentKey, newBalanceDiff(senderBalanceDiff, 0, 0, updateMinIntermediateBalance)); err != nil { - return txBalanceChanges{}, err - } - receiverKey := byteKey(scriptAddrID, *assetID) - receiverBalanceDiff := payment.Amount - if err := diff.appendBalanceDiff(receiverKey, newBalanceDiff(receiverBalanceDiff, 0, 0, updateMinIntermediateBalance)); err != nil { - return txBalanceChanges{}, err + if decodedData != nil { + for _, payment := range decodedData.Payments { + assetID := proto.NewOptionalAssetFromDigest(payment.AssetID) + senderPaymentKey := byteKey(senderAddrID, *assetID) + senderBalanceDiff := -payment.Amount + if err := diff.appendBalanceDiff(senderPaymentKey, newBalanceDiff(senderBalanceDiff, 0, 0, updateMinIntermediateBalance)); err != nil { + return txBalanceChanges{}, err + } + receiverKey := byteKey(scriptAddrID, *assetID) + receiverBalanceDiff := payment.Amount + if err := diff.appendBalanceDiff(receiverKey, newBalanceDiff(receiverBalanceDiff, 0, 0, updateMinIntermediateBalance)); err != nil { + return txBalanceChanges{}, err + } } } + if err := td.payoutMinerWithSponsorshipHandling(&changes, tx.GetFee(), proto.NewOptionalAssetWaves(), info); err != nil { return txBalanceChanges{}, err } @@ -1574,7 +1581,7 @@ func (td *transactionDiffer) createFeeDiffInvokeScriptWithProofs(transaction pro return changes, nil } -func (td *transactionDiffer) createFeeDiffEthereumInvokeScriptWithProofs(transaction proto.Transaction, info *differInfo) (txBalanceChanges, error) { +func (td *transactionDiffer) createFeeDiffEthereumInvokeWithProofs(transaction proto.Transaction, info *differInfo) (txBalanceChanges, error) { tx, ok := transaction.(*proto.EthereumTransaction) if !ok { return txBalanceChanges{}, errors.New("failed to convert interface to InvokeScriptWithProofs transaction") @@ -1589,16 +1596,24 @@ func (td *transactionDiffer) createFeeDiffEthereumInvokeScriptWithProofs(transac if err != nil { return txBalanceChanges{}, err } + + var scriptAddress proto.WavesAddress + switch tx.TxKind.(type) { + case *proto.EthereumInvokeScriptTxKind: + scriptAddress, err = tx.To().ToWavesAddress(td.settings.AddressSchemeCharacter) + if err != nil { + return txBalanceChanges{}, err + } + case *proto.EthereumInvokeExpressionTxKind: + scriptAddress = senderAddress + } + wavesAsset := proto.NewOptionalAssetWaves() senderFeeKey := byteKey(senderAddress.ID(), wavesAsset) senderFeeBalanceDiff := -int64(tx.GetFee()) if err := diff.appendBalanceDiff(senderFeeKey, newBalanceDiff(senderFeeBalanceDiff, 0, 0, true)); err != nil { return txBalanceChanges{}, err } - scriptAddress, err := tx.To().ToWavesAddress(td.settings.AddressSchemeCharacter) - if err != nil { - return txBalanceChanges{}, err - } addresses := []proto.WavesAddress{senderAddress, scriptAddress} changes := newTxBalanceChanges(addresses, diff) diff --git a/pkg/state/transaction_performer.go b/pkg/state/transaction_performer.go index 701234666b..48629caa8a 100644 --- a/pkg/state/transaction_performer.go +++ b/pkg/state/transaction_performer.go @@ -359,10 +359,14 @@ func (tp *transactionPerformer) performEthereumTransactionWithProofs(transaction if !ok { return errors.New("failed to convert interface to EthereumTransaction transaction") } - if _, ok := ethTx.TxKind.(*proto.EthereumInvokeScriptTxKind); ok { - if err := tp.stor.commitUncertain(info.blockID); err != nil { - return errors.Wrap(err, "failed to commit invoke changes") - } + _, isEthInvokeScript := ethTx.TxKind.(*proto.EthereumInvokeScriptTxKind) + _, isEthInvokeExpression := ethTx.TxKind.(*proto.EthereumInvokeExpressionTxKind) + if !(isEthInvokeScript || isEthInvokeExpression) { + return errors.New("performing transaction was called, but the transaction is not InvokeScript or InvokeExpression") + } + err := tp.stor.commitUncertain(info.blockID) + if err != nil { + return errors.Wrap(err, "failed to commit invoke changes") } // nothing to do for proto.EthereumTransferWavesTxKind and proto.EthereumTransferAssetsErc20TxKind return nil