diff --git a/pkg/metamask/service.go b/pkg/metamask/service.go index 53bdb8367..e9a76c967 100644 --- a/pkg/metamask/service.go +++ b/pkg/metamask/service.go @@ -163,14 +163,14 @@ func (s RPCService) Eth_EstimateGas(req estimateGasRequest) (string, error) { } } - txKind, err := state.GuessEthereumTransactionKind(data) + txKind, err := proto.GuessEthereumTransactionKind(data) if err != nil { return "", errors.Errorf("failed to guess ethereum tx kind, %v", err) } switch txKind { - case state.EthereumTransferWavesKind: + case proto.EthereumTransferWavesKind: return fmt.Sprintf("%d", proto.MinFee), nil - case state.EthereumTransferAssetsKind: + case proto.EthereumTransferAssetsKind: fee := proto.MinFee assetID := (*proto.AssetID)(req.To) @@ -182,7 +182,7 @@ func (s RPCService) Eth_EstimateGas(req estimateGasRequest) (string, error) { fee += proto.MinFeeScriptedAsset } return fmt.Sprintf("%d", fee), nil - case state.EthereumInvokeKind: + case proto.EthereumInvokeKind: fee := proto.MinFeeInvokeScript scriptAddr, err := req.To.ToWavesAddress(s.nodeRPCApp.Scheme) @@ -201,7 +201,7 @@ func (s RPCService) Eth_EstimateGas(req estimateGasRequest) (string, error) { if err != nil { return "", err } - decodedData, err := db.ParseCallDataRide(data) + decodedData, err := db.ParseCallData(data) if err != nil { return "", errors.Errorf("failed to parse ethereum data, %v", err) } diff --git a/pkg/proto/eth_access_list_tx_test.go b/pkg/proto/eth_access_list_tx_test.go index 52be8ce17..9e094aad1 100644 --- a/pkg/proto/eth_access_list_tx_test.go +++ b/pkg/proto/eth_access_list_tx_test.go @@ -13,7 +13,7 @@ func TestEthereumAccessListTxCanonical(t *testing.T) { require.NoError(t, err) return data } - const expectedCanonical = "0x01f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521" + const expectedCanonical = "0x01f8650103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a84bdc01110c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521" var ( expectedCanonicalBytes = mustDecodeFomHexString(expectedCanonical) testAddrBytes = mustDecodeFomHexString("0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b") @@ -34,7 +34,7 @@ func TestEthereumAccessListTxCanonical(t *testing.T) { Value: big.NewInt(10), Gas: 25000, GasPrice: big.NewInt(1), - Data: mustDecodeFomHexString("5544"), + Data: mustDecodeFomHexString("0xbdc01110"), V: &v, R: &r, S: &s, diff --git a/pkg/proto/eth_signer.go b/pkg/proto/eth_signer.go index 46de87a4e..bb7a1943d 100644 --- a/pkg/proto/eth_signer.go +++ b/pkg/proto/eth_signer.go @@ -180,7 +180,7 @@ func (s londonSigner) SenderPK(tx *EthereumTransaction) (*EthereumPublicKey, err // DynamicFee txs are defined to use 0 and 1 as their recovery // id, add 27 to become equivalent to unprotected Homestead signatures. V = new(big.Int).Add(V, big.NewInt(27)) - if tx.ChainId().Cmp(s.chainId) != 0 { + if tx.inner.chainID().Cmp(s.chainId) != 0 { return nil, ErrInvalidChainId } return recoverEthereumPubKey(s.Hash(tx), R, S, V, true) @@ -262,7 +262,7 @@ func (s eip2930Signer) SenderPK(tx *EthereumTransaction) (*EthereumPublicKey, er // AL txs are defined to use 0 and 1 as their recovery // id, add 27 to become equivalent to unprotected Homestead signatures. V = new(big.Int).Add(V, big.NewInt(27)) - if tx.ChainId().Cmp(s.chainId) != 0 { + if tx.inner.chainID().Cmp(s.chainId) != 0 { return nil, ErrInvalidChainId } return recoverEthereumPubKey(s.Hash(tx), R, S, V, true) @@ -342,7 +342,7 @@ func (s eip155Signer) SenderPK(tx *EthereumTransaction) (*EthereumPublicKey, err if !tx.Protected() { return HomesteadSigner{}.SenderPK(tx) } - if tx.ChainId().Cmp(s.chainId) != 0 { + if tx.inner.chainID().Cmp(s.chainId) != 0 { return nil, ErrInvalidChainId } V, R, S := tx.RawSignatureValues() diff --git a/pkg/proto/eth_transaction.go b/pkg/proto/eth_transaction.go index 068b3cb22..3af62d35d 100644 --- a/pkg/proto/eth_transaction.go +++ b/pkg/proto/eth_transaction.go @@ -27,6 +27,12 @@ const ( EthereumDynamicFeeTxType ) +const ( + EthereumTransferWavesKind = iota + 1 + EthereumTransferAssetsKind + EthereumInvokeKind +) + const ( EthereumTransferMinFee uint64 = 100_000 EthereumScriptedAssetMinFee uint64 = 400_000 @@ -91,7 +97,7 @@ type EthereumTxData interface { type EthereumTransactionKind interface { String() string - DecodedData() *ethabi.DecodedCallData + DecodedData() (*ethabi.DecodedCallData, error) } type EthereumTransferWavesTxKind struct { @@ -101,8 +107,8 @@ func NewEthereumTransferWavesTxKind() *EthereumTransferWavesTxKind { return &EthereumTransferWavesTxKind{} } -func (tx *EthereumTransferWavesTxKind) DecodedData() *ethabi.DecodedCallData { - return nil +func (tx *EthereumTransferWavesTxKind) DecodedData() (*ethabi.DecodedCallData, error) { + return nil, errors.New("transfer waves ethereum tx kind does not have decoded call data") } func (tx *EthereumTransferWavesTxKind) String() string { @@ -112,15 +118,21 @@ func (tx *EthereumTransferWavesTxKind) String() string { type EthereumTransferAssetsErc20TxKind struct { decodedData ethabi.DecodedCallData Arguments ethabi.ERC20TransferArguments - Asset OptionalAsset + OptAsset *OptionalAsset } -func NewEthereumTransferAssetsErc20TxKind(decodedData ethabi.DecodedCallData, asset OptionalAsset, arguments ethabi.ERC20TransferArguments) *EthereumTransferAssetsErc20TxKind { - return &EthereumTransferAssetsErc20TxKind{Asset: asset, decodedData: decodedData, Arguments: arguments} +func NewEthereumTransferAssetsErc20TxKind(decodedData ethabi.DecodedCallData, asset *OptionalAsset, arguments ethabi.ERC20TransferArguments) *EthereumTransferAssetsErc20TxKind { + return &EthereumTransferAssetsErc20TxKind{OptAsset: asset, decodedData: decodedData, Arguments: arguments} } -func (tx *EthereumTransferAssetsErc20TxKind) DecodedData() *ethabi.DecodedCallData { - return &tx.decodedData +func (tx *EthereumTransferAssetsErc20TxKind) DecodedData() (*ethabi.DecodedCallData, error) { + return &tx.decodedData, nil +} +func (tx *EthereumTransferAssetsErc20TxKind) Asset() (*OptionalAsset, error) { + if tx.OptAsset == nil { + return nil, errors.New("asset field of ethereum transfer assets tx is empty") + } + return tx.OptAsset, nil } func (tx *EthereumTransferAssetsErc20TxKind) String() string { @@ -128,15 +140,18 @@ func (tx *EthereumTransferAssetsErc20TxKind) String() string { } type EthereumInvokeScriptTxKind struct { - decodedData ethabi.DecodedCallData + DecodedCallData *ethabi.DecodedCallData } -func NewEthereumInvokeScriptTxKind(decodedData ethabi.DecodedCallData) *EthereumInvokeScriptTxKind { - return &EthereumInvokeScriptTxKind{decodedData: decodedData} +func NewEthereumInvokeScriptTxKind(decodedData *ethabi.DecodedCallData) *EthereumInvokeScriptTxKind { + return &EthereumInvokeScriptTxKind{DecodedCallData: decodedData} } -func (tx *EthereumInvokeScriptTxKind) DecodedData() *ethabi.DecodedCallData { - return &tx.decodedData +func (tx *EthereumInvokeScriptTxKind) DecodedData() (*ethabi.DecodedCallData, error) { + if tx.DecodedCallData == nil { + return nil, errors.New("ethereum invoke script tx has empty decoded data") + } + return tx.DecodedCallData, nil } func (tx *EthereumInvokeScriptTxKind) String() string { @@ -144,10 +159,16 @@ func (tx *EthereumInvokeScriptTxKind) String() string { } type EthereumTransaction struct { - inner EthereumTxData - innerBinarySize int - senderPK atomic.Value // *EthereumPublicKey + // Ethereum representation + senderPK atomic.Value // *EthereumPublicKey + inner EthereumTxData // + + // Waves representation TxKind EthereumTransactionKind + value int64 + chainID int64 + gasPrice uint64 + innerBinarySize int ID *crypto.Digest } @@ -159,6 +180,9 @@ func NewEthereumTransaction(inner EthereumTxData, txKind EthereumTransactionKind TxKind: txKind, ID: id, } + res := new(big.Int).Div(tx.inner.value(), big.NewInt(int64(DiffEthWaves))) + tx.value = res.Int64() + tx.threadSafeSetSenderPK(senderPK) return tx } @@ -215,7 +239,7 @@ func (tx *EthereumTransaction) Verify() (*EthereumPublicKey, error) { if senderPK := tx.threadSafeGetSenderPK(); senderPK != nil { return senderPK, nil } - signer := MakeEthereumSigner(tx.ChainId()) + signer := MakeEthereumSigner(tx.inner.chainID()) senderPK, err := signer.SenderPK(tx) if err != nil { return nil, errors.Wrap(err, "failed to verify EthereumTransaction") @@ -228,9 +252,9 @@ func (tx *EthereumTransaction) Verify() (*EthereumPublicKey, error) { // This method doesn't include signature verification. Use Verify method for signature verification func (tx *EthereumTransaction) Validate(scheme Scheme) (Transaction, error) { // same chainID - if tx.ChainId().Cmp(big.NewInt(int64(scheme))) != 0 { + if tx.ChainId() != int64(scheme) { // TODO: introduce new error type for scheme validation - txChainID := tx.ChainId().Uint64() + txChainID := tx.ChainId() return nil, errs.NewTxValidationError(fmt.Sprintf( "Address belongs to another network: expected: %d(%c), actual: %d(%c)", scheme, scheme, @@ -250,6 +274,7 @@ func (tx *EthereumTransaction) Validate(scheme Scheme) (Transaction, error) { return nil, errs.NewFeeValidation("insufficient fee") } // too many waves (this check doesn't exist in scala) + // TODO I'm not sure this is should be checked for all eth tx kinds. Only for transfer waves kind wavelets, err := EthereumWeiToWavelet(tx.Value()) if err != nil { return nil, errs.NewFeeValidation(err.Error()) @@ -259,15 +284,15 @@ func (tx *EthereumTransaction) Validate(scheme Scheme) (Transaction, error) { return nil, errs.NewNonPositiveAmount(wavelets, "waves") } // a cancel transaction: value == 0 && data == 0x - if tx.Value().Cmp(big0) == 0 && len(tx.Data()) == 0 { + if tx.Value() == 0 && len(tx.Data()) == 0 { return nil, errs.NewTxValidationError("Transaction cancellation is not supported") } // either data or value field is set - if tx.Value().Cmp(big0) != 0 && len(tx.Data()) != 0 { + if tx.Value() != 0 && len(tx.Data()) != 0 { return nil, errs.NewTxValidationError("Transaction should have either data or value") } // gasPrice == 10GWei - if tx.GasPrice().Cmp(new(big.Int).SetUint64(EthereumGasPrice)) != 0 { + if tx.GasPrice() != EthereumGasPrice { return nil, errs.NewTxValidationError("Gas price must be 10 Gwei") } // deny a contract creation transaction (this check doesn't exist in scala) @@ -375,8 +400,8 @@ func (tx *EthereumTransaction) EthereumTxType() EthereumTxType { // ChainId returns the EIP155 chain ID of the transaction. The return value will always be // non-nil. For legacy transactions which are not replay-protected, the return value is // zero. -func (tx *EthereumTransaction) ChainId() *big.Int { - return tx.inner.chainID() +func (tx *EthereumTransaction) ChainId() int64 { + return tx.chainID } // Data returns the input data of the transaction. @@ -389,16 +414,10 @@ func (tx *EthereumTransaction) AccessList() EthereumAccessList { return tx.inner func (tx *EthereumTransaction) Gas() uint64 { return tx.inner.gas() } // GasPrice returns the gas price of the transaction. -func (tx *EthereumTransaction) GasPrice() *big.Int { return copyBigInt(tx.inner.gasPrice()) } - -// GasTipCap returns the gasTipCap per gas of the transaction. -func (tx *EthereumTransaction) GasTipCap() *big.Int { return copyBigInt(tx.inner.gasTipCap()) } - -// GasFeeCap returns the fee cap per gas of the transaction. -func (tx *EthereumTransaction) GasFeeCap() *big.Int { return copyBigInt(tx.inner.gasFeeCap()) } +func (tx *EthereumTransaction) GasPrice() uint64 { return tx.gasPrice } // Value returns the ether amount of the transaction. -func (tx *EthereumTransaction) Value() *big.Int { return copyBigInt(tx.inner.value()) } +func (tx *EthereumTransaction) Value() int64 { return tx.value } // Nonce returns the sender account nonce of the transaction. func (tx *EthereumTransaction) Nonce() uint64 { return tx.inner.nonce() } @@ -458,6 +477,98 @@ func (tx *EthereumTransaction) RawSignatureValues() (v, r, s *big.Int) { return tx.inner.rawSignatureValues() } +func validateEthereumTx(tx *EthereumTransaction) error { + switch tx.TxKind.(type) { + case *EthereumTransferWavesTxKind: + res := new(big.Int).Div(tx.inner.value(), big.NewInt(int64(DiffEthWaves))) + if ok := res.IsInt64(); !ok { + return errors.Errorf("failed to convert amount from ethreum transaction (big int) to int64. value is %d", tx.Value()) + } + tx.value = res.Int64() + return nil + case *EthereumTransferAssetsErc20TxKind: + return nil + case *EthereumInvokeScriptTxKind: + return nil + default: + return errors.New("failed to check ethereum transaction, wrong kind of tx") + } +} + +func GuessEthereumTransactionKind(data []byte) (int64, error) { + if len(data) == 0 { + return EthereumTransferWavesKind, nil + } + + selectorBytes := data + if len(data) < ethabi.SelectorSize { + return 0, errors.Errorf("length of data from ethereum transaction is less than %d", ethabi.SelectorSize) + } + selector, err := ethabi.NewSelectorFromBytes(selectorBytes[:ethabi.SelectorSize]) + if err != nil { + return 0, errors.Wrap(err, "failed to guess ethereum transaction kind") + } + + if ethabi.IsERC20TransferSelector(selector) { + return EthereumTransferAssetsKind, nil + } + + return EthereumInvokeKind, nil +} + +// note: in this method not all of the fields are filled +func GetEthereumTransactionKind(ethTx EthereumTransaction) (EthereumTransactionKind, error) { + txKind, err := GuessEthereumTransactionKind(ethTx.Data()) + if err != nil { + return nil, errors.Wrap(err, "failed to guess ethereum tx kind") + } + + switch txKind { + case EthereumTransferWavesKind: + return NewEthereumTransferWavesTxKind(), nil + case EthereumTransferAssetsKind: + db := ethabi.NewErc20MethodsMap() + decodedData, err := db.ParseCallData(ethTx.Data()) + if err != nil { + return nil, errors.Errorf("failed to parse ethereum data") + } + if len(decodedData.Inputs) != ethabi.NumberOfERC20TransferArguments { + return nil, errors.Errorf("the number of arguments of erc20 function is %d, but expected it to be %d", len(decodedData.Inputs), ethabi.NumberOfERC20TransferArguments) + } + + erc20Arguments, err := ethabi.GetERC20TransferArguments(decodedData) + if err != nil { + return nil, errors.Wrap(err, "failed to get erc20 arguments from decoded data") + } + return NewEthereumTransferAssetsErc20TxKind(*decodedData, nil, erc20Arguments), nil + case EthereumInvokeKind: + return NewEthereumInvokeScriptTxKind(nil), nil + default: + return nil, errors.New("unexpected ethereum tx kind") + } +} + +func (tx *EthereumTransaction) initTransactionFields() error { + if ok := tx.inner.chainID().IsInt64(); !ok { + return errors.Errorf("failed to recognize chainID of ethereum transaction: over int64") + } + tx.chainID = tx.inner.chainID().Int64() + if ok := tx.inner.gasPrice().IsUint64(); !ok { + return errors.Errorf("failed to recognize GasPrice of ethereum transaction: over uint64") + } + tx.gasPrice = tx.inner.gasPrice().Uint64() + var err error + tx.TxKind, err = GetEthereumTransactionKind(*tx) + if err != nil { + return errors.Errorf("failed to guess ethereum transaction kind, %v", err) + } + err = validateEthereumTx(tx) + if err != nil { + return errors.Errorf("validation of ethereum transaction after initialization failed , %v", err) + } + return nil +} + // DecodeCanonical decodes the canonical binary encoding of transactions. // It supports legacy RLP transactions and EIP2718 typed transactions. func (tx *EthereumTransaction) DecodeCanonical(canonicalData []byte) error { @@ -488,6 +599,11 @@ func (tx *EthereumTransaction) DecodeCanonical(canonicalData []byte) error { tx.inner = inner } tx.innerBinarySize = len(canonicalData) + + err := tx.initTransactionFields() + if err != nil { + return errors.Wrap(err, "failed to initialize waves representation fields of ethereum transaction") + } return nil } diff --git a/pkg/proto/eth_utils.go b/pkg/proto/eth_utils.go index 1ae508407..d9f84dd2d 100644 --- a/pkg/proto/eth_utils.go +++ b/pkg/proto/eth_utils.go @@ -21,12 +21,10 @@ func WaveletToEthereumWei(waveletAmount uint64) *big.Int { ) } -func EthereumWeiToWavelet(weiAmount *big.Int) (int64, error) { - wavelets := new(big.Int).Div(weiAmount, new(big.Int).SetUint64(waveletToWeiMultiplier)) - if !wavelets.IsInt64() { - return 0, errors.Errorf("too many wavelets=%d", wavelets) - } - return wavelets.Int64(), nil +func EthereumWeiToWavelet(weiAmount int64) (int64, error) { + wavelets := weiAmount / int64(waveletToWeiMultiplier) + + return wavelets, nil } func unmarshalTransactionToFieldFastRLP(value *fastrlp.Value) (*EthereumAddress, error) { diff --git a/pkg/proto/ethabi/argument.go b/pkg/proto/ethabi/argument.go index f9787a275..6a7b6ae77 100644 --- a/pkg/proto/ethabi/argument.go +++ b/pkg/proto/ethabi/argument.go @@ -26,10 +26,10 @@ func NewArgumentFromRideTypeMeta(name string, rideT meta.Type) (Argument, error) return arg, err } -// UnpackRideValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, +// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, // without supplying a struct to unpack into. Instead, this method returns a list containing the // values. An atomic argument will be a list with one element. -func (arguments Arguments) UnpackRideValues(data []byte) ([]DataType, int, error) { +func (arguments Arguments) UnpackValues(data []byte) ([]DataType, int, error) { retval := make([]DataType, 0, len(arguments)) virtualArgs := 0 for index, arg := range arguments { diff --git a/pkg/proto/ethabi/methods_map.go b/pkg/proto/ethabi/methods_map.go index 94826aa12..626ef887f 100644 --- a/pkg/proto/ethabi/methods_map.go +++ b/pkg/proto/ethabi/methods_map.go @@ -72,7 +72,7 @@ func (db MethodsMap) MethodBySelector(id Selector) (Method, error) { return Method{}, fmt.Errorf("signature %q not found", id.String()) } -func (db MethodsMap) ParseCallDataRide(data []byte) (*DecodedCallData, error) { +func (db MethodsMap) ParseCallData(data []byte) (*DecodedCallData, error) { // If the data is empty, we have a plain value transfer, nothing more to do if len(data) == 0 { return nil, errors.New("transaction doesn't contain data") @@ -91,7 +91,7 @@ func (db MethodsMap) ParseCallDataRide(data []byte) (*DecodedCallData, error) { return nil, errors.Errorf("Transaction contains data, but the ABI signature could not be found: %v", err) } - info, err := parseArgDataToRideTypes(&method, data[SelectorSize:], db.parsePayments) + info, err := parseArgDataToEthABITypes(&method, data[SelectorSize:], db.parsePayments) if err != nil { return nil, errors.Errorf("Transaction contains data, but provided ABI signature could not be verified: %v", err) } @@ -122,8 +122,8 @@ func (da *DecodedArg) InternalType() byte { return byte(da.Soltype.Type.T) } -func parseArgDataToRideTypes(method *Method, argData []byte, parsePayments bool) (*DecodedCallData, error) { - values, paymentsOffset, err := method.Inputs.UnpackRideValues(argData) +func parseArgDataToEthABITypes(method *Method, argData []byte, parsePayments bool) (*DecodedCallData, error) { + values, paymentsOffset, err := method.Inputs.UnpackValues(argData) if err != nil { return nil, errors.Wrap(err, "failed to unpack Inputs arguments ABI data") } diff --git a/pkg/proto/ethabi/parsing_test.go b/pkg/proto/ethabi/parsing_test.go index 1700ce4cd..619d5ea9f 100644 --- a/pkg/proto/ethabi/parsing_test.go +++ b/pkg/proto/ethabi/parsing_test.go @@ -25,7 +25,7 @@ func TestERC20EthereumTransfer(t *testing.T) { require.NoError(t, err) erc20Db := NewErc20MethodsMap() - callData, err := erc20Db.ParseCallDataRide(data) + callData, err := erc20Db.ParseCallData(data) require.NoError(t, err) require.Equal(t, expectedSignature, callData.Signature.String()) @@ -50,7 +50,7 @@ func TestGetERC20TransferArguments(t *testing.T) { require.NoError(t, err) erc20Db := NewErc20MethodsMap() - callData, err := erc20Db.ParseCallDataRide(data) + callData, err := erc20Db.ParseCallData(data) require.NoError(t, err) transferArgs, err := GetERC20TransferArguments(callData) @@ -89,7 +89,7 @@ func TestRandomFunctionABIParsing(t *testing.T) { methods: customDB, parsePayments: false, } - callData, err := db.ParseCallDataRide(data) + callData, err := db.ParseCallData(data) require.NoError(t, err) require.Equal(t, "minta", callData.Name) @@ -315,7 +315,7 @@ func TestParsingABIUsingRideMeta(t *testing.T) { db, err := newMethodsMapFromRideDAppMeta(dAppMeta, tc.parsePayments) require.NoError(t, err) - decodedCallData, err := db.ParseCallDataRide(data) + decodedCallData, err := db.ParseCallData(data) require.NoError(t, err) values := make([]DataType, 0, len(decodedCallData.Inputs)) diff --git a/pkg/ride/converters.go b/pkg/ride/converters.go index 841fc0105..d565280a0 100644 --- a/pkg/ride/converters.go +++ b/pkg/ride/converters.go @@ -1,8 +1,6 @@ package ride import ( - "math/big" - "github.com/pkg/errors" "github.com/wavesplatform/gowaves/pkg/crypto" "github.com/wavesplatform/gowaves/pkg/proto" @@ -960,13 +958,7 @@ func ethereumTransactionToObject(scheme proto.Scheme, tx *proto.EthereumTransact 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))) - if ok := res.IsInt64(); !ok { - return nil, EvaluationFailure.Errorf( - "transferWithProofsToObject: failed to convert amount from ethreum transaction (big int) to int64. value is %s", - tx.Value().String()) - } - amount := res.Int64() + amount := tx.Value() r["amount"] = rideInt(amount) r["fee"] = rideInt(tx.GetFee()) r["feeAssetId"] = optionalAsset(proto.NewOptionalAssetWaves()) @@ -985,7 +977,12 @@ func ethereumTransactionToObject(scheme proto.Scheme, tx *proto.EthereumTransact return nil, errors.Wrap(err, "failed to convert ethereum ERC20 transfer recipient to WavesAddress") } r["recipient"] = rideRecipient(proto.NewRecipientFromAddress(recipientAddr)) - r["assetId"] = optionalAsset(kind.Asset) + asset, err := kind.Asset() + if err != nil { + return nil, err + } + + r["assetId"] = optionalAsset(*asset) r["amount"] = rideInt(kind.Arguments.Amount) r["fee"] = rideInt(tx.GetFee()) r["feeAssetId"] = optionalAsset(proto.NewOptionalAssetWaves()) @@ -1000,8 +997,12 @@ func ethereumTransactionToObject(scheme proto.Scheme, tx *proto.EthereumTransact r["senderPublicKey"] = rideBytes(callerPK) r["dApp"] = rideRecipient(proto.NewRecipientFromAddress(*to)) + decodedData, err := tx.TxKind.DecodedData() + if err != nil { + return nil, err + } var scriptPayments []proto.ScriptPayment - for _, p := range tx.TxKind.DecodedData().Payments { + for _, p := range decodedData.Payments { var optAsset proto.OptionalAsset if p.PresentAssetID { optAsset = *proto.NewOptionalAssetFromDigest(p.AssetID) @@ -1029,8 +1030,8 @@ func ethereumTransactionToObject(scheme proto.Scheme, tx *proto.EthereumTransact r["payments"] = make(rideList, 0) } r["feeAssetId"] = optionalAsset(proto.NewOptionalAssetWaves()) - r["function"] = rideString(tx.TxKind.DecodedData().Name) - arguments, err := ConvertDecodedEthereumArgumentsToProtoArguments(tx.TxKind.DecodedData().Inputs) + r["function"] = rideString(decodedData.Name) + arguments, err := ConvertDecodedEthereumArgumentsToProtoArguments(decodedData.Inputs) if err != nil { return nil, errors.Errorf("failed to convert ethereum arguments, %v", err) } diff --git a/pkg/ride/converters_test.go b/pkg/ride/converters_test.go index 6779e6c2a..3261a059b 100644 --- a/pkg/ride/converters_test.go +++ b/pkg/ride/converters_test.go @@ -2226,7 +2226,7 @@ func TestEthereumTransferAssetsTransformTxToRideObj(t *testing.T) { tx := proto.NewEthereumTransaction(txData, nil, &crypto.Digest{}, &senderPK, 0) db := ethabi.NewErc20MethodsMap() assert.NotNil(t, tx.Data()) - decodedData, err := db.ParseCallDataRide(tx.Data()) + decodedData, err := db.ParseCallData(tx.Data()) assert.NoError(t, err) makeLessDataAmount(t, decodedData) @@ -2238,7 +2238,7 @@ func TestEthereumTransferAssetsTransformTxToRideObj(t *testing.T) { erc20arguments, err := ethabi.GetERC20TransferArguments(decodedData) assert.NoError(t, err) - tx.TxKind = proto.NewEthereumTransferAssetsErc20TxKind(*decodedData, *proto.NewOptionalAssetFromDigest(asset.ID), erc20arguments) + tx.TxKind = proto.NewEthereumTransferAssetsErc20TxKind(*decodedData, proto.NewOptionalAssetFromDigest(asset.ID), erc20arguments) rideObj, err := transactionToObject(proto.MainNetScheme, &tx) assert.NoError(t, err) diff --git a/pkg/state/appender.go b/pkg/state/appender.go index 77f008c62..99bdf9da7 100644 --- a/pkg/state/appender.go +++ b/pkg/state/appender.go @@ -470,10 +470,11 @@ func (a *txAppender) appendTx(tx proto.Transaction, params *appendTxParams) erro if !ok { return errors.New("failed to cast interface transaction to ethereum transaction structure") } - ethTx.TxKind, err = a.ethInfo.ethereumTransactionKind(ethTx, params) + err := a.ethInfo.fillRequiredTxFields(ethTx, params) if err != nil { - return errors.Errorf("failed to guess ethereum transaction kind, %v", err) + return errors.Errorf("failed to fill required fields in ethereum transaction, %v", err) } + switch ethTx.TxKind.(type) { case *proto.EthereumTransferWavesTxKind, *proto.EthereumTransferAssetsErc20TxKind: applicationRes, err = a.handleDefaultTransaction(tx, params, accountHasVerifierScript) diff --git a/pkg/state/eth_info.go b/pkg/state/eth_info.go index d8d4bbf78..1fc7b22b0 100644 --- a/pkg/state/eth_info.go +++ b/pkg/state/eth_info.go @@ -7,12 +7,6 @@ import ( "github.com/wavesplatform/gowaves/pkg/settings" ) -const ( - EthereumTransferWavesKind = iota + 1 - EthereumTransferAssetsKind - EthereumInvokeKind -) - type ethInfo struct { stor *blockchainEntitiesStorage settings *settings.BlockchainSettings @@ -22,79 +16,48 @@ func newEthInfo(stor *blockchainEntitiesStorage, settings *settings.BlockchainSe return ðInfo{stor: stor, settings: settings} } -func GuessEthereumTransactionKind(data []byte) (int64, error) { - if len(data) == 0 { - return EthereumTransferWavesKind, nil - } - - selectorBytes := data - if len(data) < ethabi.SelectorSize { - return 0, errors.Errorf("length of data from ethereum transaction is less than %d", ethabi.SelectorSize) - } - selector, err := ethabi.NewSelectorFromBytes(selectorBytes[:ethabi.SelectorSize]) - if err != nil { - return 0, errors.Wrap(err, "failed to guess ethereum transaction kind") - } - - if ethabi.IsERC20TransferSelector(selector) { - return EthereumTransferAssetsKind, nil - } - - return EthereumInvokeKind, nil -} - -func (e *ethInfo) ethereumTransactionKind(ethTx *proto.EthereumTransaction, params *appendTxParams) (proto.EthereumTransactionKind, error) { - txKind, err := GuessEthereumTransactionKind(ethTx.Data()) - if err != nil { - return nil, errors.Wrap(err, "failed to guess ethereum tx kind") - } - - switch txKind { - case EthereumTransferWavesKind: - return proto.NewEthereumTransferWavesTxKind(), nil - case EthereumTransferAssetsKind: +func (e *ethInfo) fillRequiredTxFields(ethTx *proto.EthereumTransaction, params *appendTxParams) error { + switch kind := ethTx.TxKind.(type) { + case *proto.EthereumTransferWavesTxKind: + case *proto.EthereumTransferAssetsErc20TxKind: db := ethabi.NewErc20MethodsMap() - decodedData, err := db.ParseCallDataRide(ethTx.Data()) + decodedData, err := db.ParseCallData(ethTx.Data()) if err != nil { - return nil, errors.Errorf("failed to parse ethereum data") + return errors.Errorf("failed to parse ethereum data") } if len(decodedData.Inputs) != ethabi.NumberOfERC20TransferArguments { - return nil, errors.Errorf("the number of arguments of erc20 function is %d, but expected it to be %d", len(decodedData.Inputs), ethabi.NumberOfERC20TransferArguments) + return errors.Errorf("the number of arguments of erc20 function is %d, but expected it to be %d", len(decodedData.Inputs), ethabi.NumberOfERC20TransferArguments) } assetID := (*proto.AssetID)(ethTx.To()) - assetInfo, err := e.stor.assets.newestAssetInfo(*assetID, true) if err != nil { - return nil, errors.Wrap(err, "failed to get asset info") + return errors.Wrap(err, "failed to get asset info") } fullAssetID := proto.ReconstructDigest(*assetID, assetInfo.tail) - erc20Arguments, err := ethabi.GetERC20TransferArguments(decodedData) - if err != nil { - return nil, errors.Wrap(err, "failed to get erc20 arguments from decoded data") - } - return proto.NewEthereumTransferAssetsErc20TxKind(*decodedData, *proto.NewOptionalAssetFromDigest(fullAssetID), erc20Arguments), nil - case EthereumInvokeKind: + + kind.OptAsset = proto.NewOptionalAssetFromDigest(fullAssetID) + case *proto.EthereumInvokeScriptTxKind: scriptAddr, err := ethTx.WavesAddressTo(e.settings.AddressSchemeCharacter) if err != nil { - return nil, err + return err } tree, err := e.stor.scriptsStorage.newestScriptByAddr(*scriptAddr, !params.initialisation) if err != nil { - return nil, errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptAddr.String()) + return errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptAddr.String()) } db, err := ethabi.NewMethodsMapFromRideDAppMeta(tree.Meta) if err != nil { - return nil, err + return err } - decodedData, err := db.ParseCallDataRide(ethTx.Data()) + decodedData, err := db.ParseCallData(ethTx.Data()) if err != nil { - return nil, errors.Wrap(err, "failed to parse ethereum data") + return errors.Wrap(err, "failed to parse ethereum data") } - return proto.NewEthereumInvokeScriptTxKind(*decodedData), nil - + kind.DecodedCallData = decodedData default: - return nil, errors.New("unexpected ethereum tx kind") + return errors.New("unexpected ethereum tx kind") } + return nil } diff --git a/pkg/state/ethereum_tx_test.go b/pkg/state/ethereum_tx_test.go index e09f9b815..96290d17b 100644 --- a/pkg/state/ethereum_tx_test.go +++ b/pkg/state/ethereum_tx_test.go @@ -80,8 +80,9 @@ func TestEthereumTransferWaves(t *testing.T) { txData := defaultEthereumLegacyTxData(1000000000000000, &recipientEth, nil, 100000) tx := proto.NewEthereumTransaction(txData, nil, nil, &senderPK, 0) - tx.TxKind, err = txAppender.ethInfo.ethereumTransactionKind(&tx, nil) - + tx.TxKind, err = proto.GetEthereumTransactionKind(tx) + assert.NoError(t, err) + err = txAppender.ethInfo.fillRequiredTxFields(&tx, appendTxParams) assert.NoError(t, err) applRes, err := txAppender.handleDefaultTransaction(&tx, appendTxParams, false) assert.NoError(t, err) @@ -144,7 +145,7 @@ func TestEthereumTransferAssets(t *testing.T) { db := ethabi.NewErc20MethodsMap() assert.NotNil(t, tx.Data()) - decodedData, err := db.ParseCallDataRide(tx.Data()) + decodedData, err := db.ParseCallData(tx.Data()) assert.NoError(t, err) lessenDecodedDataAmount(t, decodedData) @@ -156,7 +157,7 @@ func TestEthereumTransferAssets(t *testing.T) { assetInfo, err := txAppender.ethInfo.stor.assets.newestAssetInfo(*assetID, true) require.NoError(t, err) fullAssetID := proto.ReconstructDigest(*assetID, assetInfo.tail) - tx.TxKind = proto.NewEthereumTransferAssetsErc20TxKind(*decodedData, *proto.NewOptionalAssetFromDigest(fullAssetID), erc20arguments) + tx.TxKind = proto.NewEthereumTransferAssetsErc20TxKind(*decodedData, proto.NewOptionalAssetFromDigest(fullAssetID), erc20arguments) applRes, err := txAppender.handleDefaultTransaction(&tx, appendTxParams, false) assert.NoError(t, err) assert.True(t, applRes.status) @@ -251,7 +252,7 @@ func TestEthereumInvoke(t *testing.T) { txData := defaultEthereumLegacyTxData(1000000000000000, &recipientEth, nil, 500000) 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) tx := proto.NewEthereumTransaction(txData, txKind, &crypto.Digest{}, &senderPK, 0) fallibleInfo := &fallibleValidationParams{appendTxParams: appendTxParams, senderScripted: false, senderAddress: sender} @@ -287,7 +288,9 @@ func TestTransferZeroAmount(t *testing.T) { txData := defaultEthereumLegacyTxData(0, &recipientEth, nil, 100000) tx := proto.NewEthereumTransaction(txData, nil, nil, &senderPK, 0) - tx.TxKind, err = txAppender.ethInfo.ethereumTransactionKind(&tx, nil) + tx.TxKind, err = proto.GetEthereumTransactionKind(tx) + assert.NoError(t, err) + err = txAppender.ethInfo.fillRequiredTxFields(&tx, appendTxParams) assert.NoError(t, err) _, err = txAppender.handleDefaultTransaction(&tx, appendTxParams, false) @@ -305,9 +308,10 @@ func TestTransferMainNetTestnet(t *testing.T) { txData := defaultEthereumLegacyTxData(100, &recipientEth, nil, 100000) tx := proto.NewEthereumTransaction(txData, nil, nil, &senderPK, 0) - tx.TxKind, err = txAppender.ethInfo.ethereumTransactionKind(&tx, nil) + tx.TxKind, err = proto.GetEthereumTransactionKind(tx) + assert.NoError(t, err) + err = txAppender.ethInfo.fillRequiredTxFields(&tx, appendTxParams) assert.NoError(t, err) - _, err = txAppender.handleDefaultTransaction(&tx, appendTxParams, false) require.Error(t, err) } @@ -323,7 +327,9 @@ func TestTransferCheckFee(t *testing.T) { txData := defaultEthereumLegacyTxData(100, &recipientEth, nil, 100) tx := proto.NewEthereumTransaction(txData, nil, nil, &senderPK, 0) - tx.TxKind, err = txAppender.ethInfo.ethereumTransactionKind(&tx, nil) + tx.TxKind, err = proto.GetEthereumTransactionKind(tx) + assert.NoError(t, err) + err = txAppender.ethInfo.fillRequiredTxFields(&tx, appendTxParams) assert.NoError(t, err) _, err = txAppender.handleDefaultTransaction(&tx, appendTxParams, false) @@ -380,7 +386,7 @@ func TestEthereumInvokeWithoutPaymentsAndArguments(t *testing.T) { txData := defaultEthereumLegacyTxData(1000000000000000, &recipientEth, nil, 500000) decodedData := defaultDecodedData("call", nil, nil) - tx := proto.NewEthereumTransaction(txData, proto.NewEthereumInvokeScriptTxKind(decodedData), &crypto.Digest{}, &senderPK, 0) + tx := proto.NewEthereumTransaction(txData, proto.NewEthereumInvokeScriptTxKind(&decodedData), &crypto.Digest{}, &senderPK, 0) fallibleInfo := &fallibleValidationParams{appendTxParams: appendTxParams, senderScripted: false, senderAddress: sender} scriptAddress, tree := applyScript(t, &tx, storage, fallibleInfo) @@ -454,7 +460,7 @@ 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) + tx := proto.NewEthereumTransaction(txData, proto.NewEthereumInvokeScriptTxKind(&decodedData), &crypto.Digest{}, &senderPK, 0) fallibleInfo := &fallibleValidationParams{appendTxParams: appendTxParams, senderScripted: false, senderAddress: sender} scriptAddress, tree := applyScript(t, &tx, storage, fallibleInfo) diff --git a/pkg/state/invoke_applier.go b/pkg/state/invoke_applier.go index 7e6d69306..3d73cd9f2 100644 --- a/pkg/state/invoke_applier.go +++ b/pkg/state/invoke_applier.go @@ -751,13 +751,17 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV if err != nil { return nil, err } - decodedData := transaction.TxKind.DecodedData() + decodedData, err := transaction.TxKind.DecodedData() + if err != nil { + return nil, err + } 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") + return nil, err } + default: return nil, errors.New("failed to apply an invoke script: unexpected type of transaction ") } diff --git a/pkg/state/script_caller.go b/pkg/state/script_caller.go index 8ee4ce7d7..2e0e22edf 100644 --- a/pkg/state/script_caller.go +++ b/pkg/state/script_caller.go @@ -239,7 +239,11 @@ func (a *scriptCaller) invokeFunction(tree *ride.Tree, tx proto.Transaction, inf defaultFunction = transaction.FunctionCall.Default case *proto.EthereumTransaction: - abiPayments := transaction.TxKind.DecodedData().Payments + decodedData, err := transaction.TxKind.DecodedData() + if err != nil { + return nil, err + } + abiPayments := decodedData.Payments scriptPayments := make([]proto.ScriptPayment, 0, len(abiPayments)) for _, p := range abiPayments { var optAsset proto.OptionalAsset @@ -261,7 +265,6 @@ func (a *scriptCaller) invokeFunction(tree *ride.Tree, tx proto.Transaction, inf if err != nil { return nil, errors.Errorf("failed to get waves address from ethereum transaction %v", err) } - decodedData := transaction.TxKind.DecodedData() functionName = decodedData.Name arguments, err := ride.ConvertDecodedEthereumArgumentsToProtoArguments(decodedData.Inputs) if err != nil { diff --git a/pkg/state/transaction_checker.go b/pkg/state/transaction_checker.go index 338f2115c..6377a83b5 100644 --- a/pkg/state/transaction_checker.go +++ b/pkg/state/transaction_checker.go @@ -354,14 +354,7 @@ func (tc *transactionChecker) checkEthereumTransactionWithProofs(transaction pro } // check if the amount is 0 - if tx.Value() == nil { - return nil, errors.New("the amount of ethereum transfer waves is 0, which is forbidden") - } - res := new(big.Int).Div(tx.Value(), big.NewInt(int64(proto.DiffEthWaves))) - if ok := res.IsInt64(); !ok { - return nil, errors.Errorf("failed to convert amount from ethreum transaction (big int) to int64. value is %s", tx.Value().String()) - } - if res.Int64() == 0 { + if tx.Value() == 0 { return nil, errors.New("the amount of ethereum transfer waves is 0, which is forbidden") } @@ -373,8 +366,12 @@ func (tc *transactionChecker) checkEthereumTransactionWithProofs(transaction pro if kind.Arguments.Amount == 0 { return nil, errors.New("the amount of ethereum transfer assets is 0, which is forbidden") } + asset, err := kind.Asset() + if err != nil { + return nil, err + } - isSmart, err := tc.stor.scriptsStorage.newestIsSmartAsset(proto.AssetIDFromDigest(kind.Asset.ID), true) + isSmart, err := tc.stor.scriptsStorage.newestIsSmartAsset(proto.AssetIDFromDigest(asset.ID), true) if err != nil { return nil, errors.Errorf("failed to get asset info, %v", err) } @@ -386,7 +383,7 @@ func (tc *transactionChecker) checkEthereumTransactionWithProofs(transaction pro return nil, errors.Errorf("the fee for ethereum transfer assets tx is not enough, min fee is %d, got %d", proto.EthereumTransferMinFee, tx.GetFee()) } - allAssets := []proto.OptionalAsset{kind.Asset} + allAssets := []proto.OptionalAsset{*asset} smartAssets, err := tc.smartAssets(allAssets, info.initialisation) if err != nil { return nil, err @@ -400,7 +397,10 @@ func (tc *transactionChecker) checkEthereumTransactionWithProofs(transaction pro if err := tc.checkTimestamps(tx.GetTimestamp(), info.currentTimestamp, info.parentTimestamp); err != nil { return nil, errs.Extend(err, "invalid timestamp") } - decodedData := tx.TxKind.DecodedData() + decodedData, err := tx.TxKind.DecodedData() + if err != nil { + return nil, err + } abiPayments := decodedData.Payments if len(abiPayments) > 10 { diff --git a/pkg/state/transaction_differ.go b/pkg/state/transaction_differ.go index 7e09e9a16..aa2417107 100644 --- a/pkg/state/transaction_differ.go +++ b/pkg/state/transaction_differ.go @@ -1,8 +1,6 @@ package state import ( - "math/big" - "github.com/ericlagergren/decimal" "github.com/ericlagergren/decimal/math" "github.com/mr-tron/base58" @@ -470,11 +468,11 @@ func (td *transactionDiffer) createDiffEthereumTransferWaves(tx *proto.EthereumT updateMinIntermediateBalance = true } // Append sender diff. + wavesAsset := proto.NewOptionalAssetWaves() senderAddress, err := tx.WavesAddressFrom(td.settings.AddressSchemeCharacter) if err != nil { return txBalanceChanges{}, err } - wavesAsset := proto.NewOptionalAssetWaves() senderFeeKey := byteKey(senderAddress.ID(), wavesAsset) senderFeeBalanceDiff := -int64(tx.GetFee()) @@ -482,11 +480,7 @@ func (td *transactionDiffer) createDiffEthereumTransferWaves(tx *proto.EthereumT return txBalanceChanges{}, err } - res := new(big.Int).Div(tx.Value(), big.NewInt(int64(proto.DiffEthWaves))) - if ok := res.IsInt64(); !ok { - return txBalanceChanges{}, errors.Errorf("failed to convert amount from ethreum transaction (big int) to int64. value is %s", tx.Value().String()) - } - amount := res.Int64() + amount := tx.Value() senderAmountKey := byteKey(senderAddress.ID(), wavesAsset) @@ -523,20 +517,16 @@ func (td *transactionDiffer) createDiffEthereumErc20(tx *proto.EthereumTransacti return txBalanceChanges{}, errors.New("failed to convert ethereum tx kind to EthereumTransferAssetsErc20TxKind") } - decodedData := txErc20Kind.DecodedData() - - var senderAddress proto.WavesAddress - // Append sender diff. + decodedData, err := txErc20Kind.DecodedData() + if err != nil { + return txBalanceChanges{}, nil + } if !ethabi.IsERC20TransferSelector(decodedData.Signature.Selector()) { return txBalanceChanges{}, errors.New("unexpected type of eth selector") } - EthSenderAddr, err := tx.From() - if err != nil { - return txBalanceChanges{}, err - } - senderAddress, err = EthSenderAddr.ToWavesAddress(td.settings.AddressSchemeCharacter) + senderAddress, err := tx.WavesAddressFrom(td.settings.AddressSchemeCharacter) if err != nil { return txBalanceChanges{}, err } @@ -550,8 +540,11 @@ func (td *transactionDiffer) createDiffEthereumErc20(tx *proto.EthereumTransacti } // transfer - - senderAmountKey := byteKey(senderAddress.ID(), txErc20Kind.Asset) + asset, err := txErc20Kind.Asset() + if err != nil { + return txBalanceChanges{}, err + } + senderAmountKey := byteKey(senderAddress.ID(), *asset) senderAmountBalanceDiff := -txErc20Kind.Arguments.Amount if err := diff.appendBalanceDiff(senderAmountKey, newBalanceDiff(senderAmountBalanceDiff, 0, 0, updateMinIntermediateBalance)); err != nil { @@ -564,7 +557,7 @@ func (td *transactionDiffer) createDiffEthereumErc20(tx *proto.EthereumTransacti } // Append receiver diff. - receiverKey := byteKey(etc20TransferRecipient.ID(), txErc20Kind.Asset) + receiverKey := byteKey(etc20TransferRecipient.ID(), *asset) receiverBalanceDiff := txErc20Kind.Arguments.Amount if err := diff.appendBalanceDiff(receiverKey, newBalanceDiff(receiverBalanceDiff, 0, 0, updateMinIntermediateBalance)); err != nil { return txBalanceChanges{}, err @@ -1434,7 +1427,10 @@ func (td *transactionDiffer) createDiffEthereumInvokeScript(tx *proto.EthereumTr return txBalanceChanges{}, errors.New("failed to convert ethereum tx kind to EthereumTransferAssetsErc20TxKind") } - decodedData := txInvokeScriptKind.DecodedData() + decodedData, err := txInvokeScriptKind.DecodedData() + if err != nil { + return txBalanceChanges{}, err + } noPayments := len(decodedData.Payments) == 0 if info.blockInfo.Timestamp >= td.settings.CheckTempNegativeAfterTime && !noPayments {