diff --git a/packages/chainutil/evmcall.go b/packages/chainutil/evmcall.go index b3c9ec4ad7..4f0afd7d4e 100644 --- a/packages/chainutil/evmcall.go +++ b/packages/chainutil/evmcall.go @@ -8,18 +8,25 @@ import ( "github.com/iotaledger/wasp/packages/chain" "github.com/iotaledger/wasp/packages/isc" + "github.com/iotaledger/wasp/packages/parameters" "github.com/iotaledger/wasp/packages/vm/core/evm" + "github.com/iotaledger/wasp/packages/vm/gas" ) // EVMCall executes an EVM contract call and returns its output, discarding any state changes func EVMCall(ch chain.ChainCore, aliasOutput *isc.AliasOutputWithID, call ethereum.CallMsg) ([]byte, error) { - gasLimit := getMaxCallGasLimit(ch) + info := getChainInfo(ch) // 0 means view call + gasLimit := gas.EVMCallGasLimit(info.GasLimits, &info.GasFeePolicy.EVMGasRatio) if call.Gas != 0 && call.Gas > gasLimit { call.Gas = gasLimit } + if call.GasPrice == nil { + call.GasPrice = info.GasFeePolicy.GasPriceWei(parameters.L1().BaseToken.Decimals) + } + iscReq := isc.NewEVMOffLedgerCallRequest(ch.ID(), call) // TODO: setting EstimateGasMode = true feels wrong here res, err := runISCRequest(ch, aliasOutput, time.Now(), iscReq, true) diff --git a/packages/chainutil/evmestimategas.go b/packages/chainutil/evmestimategas.go index c1a7d17fc4..1a1b30c7ce 100644 --- a/packages/chainutil/evmestimategas.go +++ b/packages/chainutil/evmestimategas.go @@ -10,6 +10,7 @@ import ( "github.com/iotaledger/wasp/packages/chain" "github.com/iotaledger/wasp/packages/isc" + "github.com/iotaledger/wasp/packages/parameters" "github.com/iotaledger/wasp/packages/vm" "github.com/iotaledger/wasp/packages/vm/core/governance" "github.com/iotaledger/wasp/packages/vm/gas" @@ -27,14 +28,19 @@ func EVMEstimateGas(ch chain.ChainCore, aliasOutput *isc.AliasOutputWithID, call gasCap uint64 ) - maximumPossibleGas := getMaxCallGasLimit(ch) + info := getChainInfo(ch) + maximumPossibleGas := gas.EVMCallGasLimit(info.GasLimits, &info.GasFeePolicy.EVMGasRatio) if call.Gas >= params.TxGas { hi = call.Gas } else { hi = maximumPossibleGas } + if call.GasPrice == nil { + call.GasPrice = info.GasFeePolicy.GasPriceWei(parameters.L1().BaseToken.Decimals) + } + gasCap = hi // Create a helper to check if a gas allowance results in an executable transaction @@ -114,9 +120,8 @@ func EVMEstimateGas(ch chain.ChainCore, aliasOutput *isc.AliasOutputWithID, call return hi, nil } -func getMaxCallGasLimit(ch chain.ChainCore) uint64 { - info := governance.NewStateAccess(mustLatestState(ch)).ChainInfo(ch.ID()) - return gas.EVMCallGasLimit(info.GasLimits, &info.GasFeePolicy.EVMGasRatio) +func getChainInfo(ch chain.ChainCore) *isc.ChainInfo { + return governance.NewStateAccess(mustLatestState(ch)).ChainInfo(ch.ID()) } func resolveError(ch chain.ChainCore, receiptError *isc.UnresolvedVMError) (isOutOfGas bool, resolved *isc.VMError, err error) { diff --git a/packages/evm/evmutil/gasprice.go b/packages/evm/evmutil/gasprice.go new file mode 100644 index 0000000000..81cd47dccc --- /dev/null +++ b/packages/evm/evmutil/gasprice.go @@ -0,0 +1,23 @@ +package evmutil + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/core/types" + + "github.com/iotaledger/wasp/packages/parameters" + "github.com/iotaledger/wasp/packages/vm/gas" +) + +func CheckGasPrice(tx *types.Transaction, gasFeePolicy *gas.FeePolicy) error { + expectedGasPrice := gasFeePolicy.GasPriceWei(parameters.L1().BaseToken.Decimals) + gasPrice := tx.GasPrice() + if gasPrice.Cmp(expectedGasPrice) != 0 { + return fmt.Errorf( + "invalid gas price: got %s, want %s", + gasPrice.Text(10), + expectedGasPrice.Text(10), + ) + } + return nil +} diff --git a/packages/evm/jsonrpc/evmchain.go b/packages/evm/jsonrpc/evmchain.go index fd7eb1d925..4759183599 100644 --- a/packages/evm/jsonrpc/evmchain.go +++ b/packages/evm/jsonrpc/evmchain.go @@ -185,19 +185,22 @@ func (e *EVMChain) SendTransaction(tx *types.Transaction) error { return fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), expectedNonce) } - if err := e.checkEnoughL2FundsForGasBudget(sender, tx.Gas()); err != nil { + gasFeePolicy := e.GasFeePolicy() + if err := e.checkEnoughL2FundsForGasBudget(sender, tx.Gas(), gasFeePolicy); err != nil { + return err + } + if err := evmutil.CheckGasPrice(tx, gasFeePolicy); err != nil { return err } return e.backend.EVMSendTransaction(tx) } -func (e *EVMChain) checkEnoughL2FundsForGasBudget(sender common.Address, evmGas uint64) error { - gasRatio := e.GasRatio() +func (e *EVMChain) checkEnoughL2FundsForGasBudget(sender common.Address, evmGas uint64, gasFeePolicy *gas.FeePolicy) error { + gasRatio := gasFeePolicy.EVMGasRatio balance, err := e.Balance(sender, nil) if err != nil { return fmt.Errorf("could not fetch sender balance: %w", err) } - gasFeePolicy := e.GasFeePolicy() gasLimits := e.gasLimits() @@ -443,27 +446,7 @@ func (e *EVMChain) EstimateGas(callMsg ethereum.CallMsg, blockNumberOrHash *rpc. func (e *EVMChain) GasPrice() *big.Int { e.log.Debugf("GasPrice()") - - iscState := e.backend.ISCLatestState() - governancePartition := subrealm.NewReadOnly(iscState, kv.Key(governance.Contract.Hname().Bytes())) - feePolicy := governance.MustGetGasFeePolicy(governancePartition) - - // special case '0:0' for free request - if feePolicy.GasPerToken.IsZero() { - return big.NewInt(0) - } - - // convert to wei (18 decimals) - decimalsDifference := 18 - parameters.L1().BaseToken.Decimals - price := big.NewInt(10) - price.Exp(price, new(big.Int).SetUint64(uint64(decimalsDifference)), nil) - - price.Mul(price, new(big.Int).SetUint64(uint64(feePolicy.GasPerToken.B))) - price.Div(price, new(big.Int).SetUint64(uint64(feePolicy.GasPerToken.A))) - price.Mul(price, new(big.Int).SetUint64(uint64(feePolicy.EVMGasRatio.A))) - price.Div(price, new(big.Int).SetUint64(uint64(feePolicy.EVMGasRatio.B))) - - return price + return e.GasFeePolicy().GasPriceWei(parameters.L1().BaseToken.Decimals) } func (e *EVMChain) StorageAt(address common.Address, key common.Hash, blockNumberOrHash *rpc.BlockNumberOrHash) (common.Hash, error) { diff --git a/packages/evm/jsonrpc/jsonrpctest/env.go b/packages/evm/jsonrpc/jsonrpctest/env.go index ae3b0ea242..17d6111dd7 100644 --- a/packages/evm/jsonrpc/jsonrpctest/env.go +++ b/packages/evm/jsonrpc/jsonrpctest/env.go @@ -26,7 +26,6 @@ import ( "github.com/iotaledger/wasp/packages/evm/evmutil" "github.com/iotaledger/wasp/packages/evm/jsonrpc" "github.com/iotaledger/wasp/packages/isc" - "github.com/iotaledger/wasp/packages/vm/core/evm" ) // Env is a testing environment for the EVM JSON-RPC support, allowing to run the same tests @@ -58,15 +57,14 @@ func (e *Env) DeployEVMContract(creator *ecdsa.PrivateKey, contractABI abi.ABI, value := big.NewInt(0) gasLimit := e.estimateGas(ethereum.CallMsg{ - From: creatorAddress, - To: nil, // contract creation - GasPrice: evm.GasPrice, - Value: value, - Data: data, + From: creatorAddress, + To: nil, // contract creation + Value: value, + Data: data, }) tx, err := types.SignTx( - types.NewContractCreation(nonce, value, gasLimit, evm.GasPrice, data), + types.NewContractCreation(nonce, value, gasLimit, e.MustGetGasPrice(), data), e.Signer(), creator, ) @@ -278,6 +276,12 @@ func (e *Env) MustSendTransaction(args *jsonrpc.SendTxArgs) common.Hash { return res } +func (e *Env) MustGetGasPrice() *big.Int { + res, err := e.Client.SuggestGasPrice(context.Background()) + require.NoError(e.T, err) + return res +} + func (e *Env) getLogs(q ethereum.FilterQuery) []types.Log { logs, err := e.Client.FilterLogs(context.Background(), q) require.NoError(e.T, err) @@ -312,7 +316,7 @@ func (e *Env) TestRPCGetLogs() { Data: callArguments, }) transferTx, err := types.SignTx( - types.NewTransaction(e.NonceAt(creatorAddress), contractAddress, value, gas, evm.GasPrice, callArguments), + types.NewTransaction(e.NonceAt(creatorAddress), contractAddress, value, gas, e.MustGetGasPrice(), callArguments), e.Signer(), creator, ) @@ -328,7 +332,7 @@ func (e *Env) TestRPCInvalidNonce() { // try sending correct nonces in invalid order 1,2, then 0 - this should succeed createTx := func(nonce uint64) *types.Transaction { tx, err := types.SignTx( - types.NewTransaction(nonce, toAddress, big.NewInt(0), math.MaxUint64, evm.GasPrice, nil), + types.NewTransaction(nonce, toAddress, big.NewInt(0), math.MaxUint64, e.MustGetGasPrice(), nil), e.Signer(), from, ) @@ -358,7 +362,7 @@ func (e *Env) TestRPCGasLimitTooLow() { nonce := e.NonceAt(fromAddress) gasLimit := uint64(1) // lower than intrinsic gas tx, err := types.SignTx( - types.NewTransaction(nonce, toAddress, value, gasLimit, evm.GasPrice, nil), + types.NewTransaction(nonce, toAddress, value, gasLimit, e.MustGetGasPrice(), nil), e.Signer(), from, ) @@ -372,8 +376,7 @@ func (e *Env) TestRPCGasLimitTooLow() { } func (e *Env) TestGasPrice() { - gasPrice, err := e.Client.SuggestGasPrice(context.Background()) - require.NoError(e.T, err) + gasPrice := e.MustGetGasPrice() require.NotZero(e.T, gasPrice.Uint64()) } diff --git a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go index e1374dd33e..44a03c3407 100644 --- a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go +++ b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go @@ -277,7 +277,7 @@ func TestRPCSignTransaction(t *testing.T) { From: ethAddr, To: &to, Gas: &gas, - GasPrice: (*hexutil.Big)(evm.GasPrice), + GasPrice: (*hexutil.Big)(big.NewInt(1000)), Value: (*hexutil.Big)(big.NewInt(42)), Nonce: &nonce, }) @@ -304,7 +304,7 @@ func TestRPCSendTransaction(t *testing.T) { txHash := env.MustSendTransaction(&jsonrpc.SendTxArgs{ From: ethAddr, Gas: &gas, - GasPrice: (*hexutil.Big)(evm.GasPrice), + GasPrice: (*hexutil.Big)(env.MustGetGasPrice()), Nonce: &nonce, Data: (*hexutil.Bytes)(&data), }) @@ -406,7 +406,7 @@ func TestRPCLogIndex(t *testing.T) { value := big.NewInt(0) gas := uint64(100_000) tx, err := types.SignTx( - types.NewTransaction(env.NonceAt(creatorAddress), contractAddress, value, gas, evm.GasPrice, callArguments), + types.NewTransaction(env.NonceAt(creatorAddress), contractAddress, value, gas, env.MustGetGasPrice(), callArguments), env.Signer(), creator, ) @@ -443,7 +443,7 @@ func TestRPCTxRejectedIfNotEnoughFunds(t *testing.T) { value := big.NewInt(0) gasLimit := uint64(10_000) tx, err := types.SignTx( - types.NewContractCreation(nonce, value, gasLimit, evm.GasPrice, data), + types.NewContractCreation(nonce, value, gasLimit, env.MustGetGasPrice(), data), env.Signer(), creator, ) diff --git a/packages/evm/jsonrpc/types.go b/packages/evm/jsonrpc/types.go index 26d0a10212..f0f0c182ac 100644 --- a/packages/evm/jsonrpc/types.go +++ b/packages/evm/jsonrpc/types.go @@ -19,7 +19,6 @@ import ( iotago "github.com/iotaledger/iota.go/v3" "github.com/iotaledger/wasp/packages/evm/evmutil" - "github.com/iotaledger/wasp/packages/vm/core/evm" ) // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction @@ -238,7 +237,7 @@ type SendTxArgs struct { // setDefaults is a helper function that fills in default values for unspecified tx fields. func (args *SendTxArgs) setDefaults(e *EthService) error { if args.GasPrice == nil { - args.GasPrice = (*hexutil.Big)(evm.GasPrice) + args.GasPrice = (*hexutil.Big)(e.evmChain.GasPrice()) } if args.Value == nil { args.Value = new(hexutil.Big) diff --git a/packages/isc/request.go b/packages/isc/request.go index b772ccf865..f75403c95f 100644 --- a/packages/isc/request.go +++ b/packages/isc/request.go @@ -5,6 +5,8 @@ import ( "io" "time" + "github.com/ethereum/go-ethereum/core/types" + iotago "github.com/iotaledger/iota.go/v3" "github.com/iotaledger/wasp/packages/cryptolib" "github.com/iotaledger/wasp/packages/hashing" @@ -57,6 +59,7 @@ type OffLedgerRequest interface { ChainID() ChainID Nonce() uint64 VerifySignature() error + EVMTransaction() *types.Transaction } type OnLedgerRequest interface { diff --git a/packages/isc/request_evmcall.go b/packages/isc/request_evmcall.go index 2273ce3e0b..bcd3d170f5 100644 --- a/packages/isc/request_evmcall.go +++ b/packages/isc/request_evmcall.go @@ -6,6 +6,7 @@ import ( "io" "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/core/types" iotago "github.com/iotaledger/iota.go/v3" "github.com/iotaledger/wasp/packages/evm/evmtypes" @@ -122,3 +123,7 @@ func (req *evmOffLedgerCallRequest) TargetAddress() iotago.Address { func (req *evmOffLedgerCallRequest) VerifySignature() error { return fmt.Errorf("%T should never be used to send regular requests", req) } + +func (*evmOffLedgerCallRequest) EVMTransaction() *types.Transaction { + return nil +} diff --git a/packages/isc/request_evmtx.go b/packages/isc/request_evmtx.go index 2cf9ad2a54..5ade9724b8 100644 --- a/packages/isc/request_evmtx.go +++ b/packages/isc/request_evmtx.go @@ -148,3 +148,7 @@ func (req *evmOffLedgerTxRequest) VerifySignature() error { } return nil } + +func (req *evmOffLedgerTxRequest) EVMTransaction() *types.Transaction { + return req.tx +} diff --git a/packages/isc/request_offledger.go b/packages/isc/request_offledger.go index 5726884a99..72f6d6ae7d 100644 --- a/packages/isc/request_offledger.go +++ b/packages/isc/request_offledger.go @@ -6,6 +6,8 @@ import ( "io" "time" + "github.com/ethereum/go-ethereum/core/types" + iotago "github.com/iotaledger/iota.go/v3" "github.com/iotaledger/wasp/packages/cryptolib" "github.com/iotaledger/wasp/packages/hashing" @@ -232,3 +234,7 @@ func (req *OffLedgerRequestData) WithSender(sender *cryptolib.PublicKey) Unsigne } return req } + +func (*OffLedgerRequestData) EVMTransaction() *types.Transaction { + return nil +} diff --git a/packages/testutil/testdbhash/TestStorageContract.hex b/packages/testutil/testdbhash/TestStorageContract.hex index 46f4f44737..2d02933c45 100644 --- a/packages/testutil/testdbhash/TestStorageContract.hex +++ b/packages/testutil/testdbhash/TestStorageContract.hex @@ -1 +1 @@ -0xab29bd35687b3fb2d8008be9b7beae844b93b647a3a397e535328f4a48795a4e +0x0db336907593bdd7de084b947deb51f85783b6774a189a5dbdd72843a588c84d diff --git a/packages/vm/core/accounts/impl_views.go b/packages/vm/core/accounts/impl_views.go index da99762004..a9157770fb 100644 --- a/packages/vm/core/accounts/impl_views.go +++ b/packages/vm/core/accounts/impl_views.go @@ -66,7 +66,7 @@ func viewAccounts(ctx isc.SandboxView) dict.Dict { // nonces are only sent with off-ledger requests func viewGetAccountNonce(ctx isc.SandboxView) dict.Dict { account := ctx.Params().MustGetAgentID(ParamAgentID, ctx.Caller()) - nonce := accountNonce(ctx.StateR(), account, ctx.ChainID()) + nonce := AccountNonce(ctx.StateR(), account, ctx.ChainID()) ret := dict.New() ret.Set(ParamAccountNonce, codec.EncodeUint64(nonce)) return ret diff --git a/packages/vm/core/accounts/nonce.go b/packages/vm/core/accounts/nonce.go index 3fa8510d0b..c8d21e97bb 100644 --- a/packages/vm/core/accounts/nonce.go +++ b/packages/vm/core/accounts/nonce.go @@ -1,8 +1,6 @@ package accounts import ( - "fmt" - "github.com/iotaledger/wasp/packages/isc" "github.com/iotaledger/wasp/packages/kv" "github.com/iotaledger/wasp/packages/kv/codec" @@ -12,8 +10,8 @@ func nonceKey(callerAgentID isc.AgentID, chainID isc.ChainID) kv.Key { return keyNonce + accountKey(callerAgentID, chainID) } -// Nonce returns the "total request count" for an account (it's the accountNonce that is expected in the next request) -func accountNonce(state kv.KVStoreReader, callerAgentID isc.AgentID, chainID isc.ChainID) uint64 { +// Nonce returns the "total request count" for an account (it's the AccountNonce that is expected in the next request) +func AccountNonce(state kv.KVStoreReader, callerAgentID isc.AgentID, chainID isc.ChainID) uint64 { if callerAgentID.Kind() == isc.AgentIDKindEthereumAddress { panic("to get EVM nonce, call EVM contract") } @@ -29,17 +27,6 @@ func IncrementNonce(state kv.KVStore, callerAgentID isc.AgentID, chainID isc.Cha // don't update EVM nonces return } - next := accountNonce(state, callerAgentID, chainID) + next := AccountNonce(state, callerAgentID, chainID) state.Set(nonceKey(callerAgentID, chainID), codec.EncodeUint64(next)) } - -func CheckNonce(state kv.KVStoreReader, agentID isc.AgentID, nonce uint64, chainID isc.ChainID) error { - if agentID.Kind() == isc.AgentIDKindEthereumAddress { - panic("to get EVM nonce, call EVM contract") - } - expected := accountNonce(state, agentID, chainID) - if nonce != expected { - return fmt.Errorf("invalid nonce, expected %d, got %d", expected, nonce) - } - return nil -} diff --git a/packages/vm/core/accounts/stateaccess.go b/packages/vm/core/accounts/stateaccess.go index 5b8913de00..09b3c09c2d 100644 --- a/packages/vm/core/accounts/stateaccess.go +++ b/packages/vm/core/accounts/stateaccess.go @@ -22,7 +22,7 @@ func NewStateAccess(store kv.KVStoreReader) *StateAccess { } func (sa *StateAccess) Nonce(agentID isc.AgentID, chainID isc.ChainID) uint64 { - return accountNonce(sa.state, agentID, chainID) + return AccountNonce(sa.state, agentID, chainID) } func (sa *StateAccess) AccountExists(agentID isc.AgentID, chainID isc.ChainID) bool { diff --git a/packages/vm/core/evm/emulator/emulator_test.go b/packages/vm/core/evm/emulator/emulator_test.go index 0e44572b57..059c323f55 100644 --- a/packages/vm/core/evm/emulator/emulator_test.go +++ b/packages/vm/core/evm/emulator/emulator_test.go @@ -69,6 +69,8 @@ var gasLimits = GasLimits{ Call: gas.EVMCallGasLimit(gas.LimitsDefault, &util.Ratio32{A: 1, B: 1}), } +var gasPrice = big.NewInt(0) // ignored in the emulator + func estimateGas(callMsg ethereum.CallMsg, e *EVMEmulator) (uint64, error) { lo := params.TxGas hi := gasLimits.Call @@ -122,7 +124,7 @@ func sendTransaction( nonce := emu.StateDB().GetNonce(senderAddress) tx, err := types.SignTx( - types.NewTransaction(nonce, receiverAddress, amount, gasLimit, evm.GasPrice, data), + types.NewTransaction(nonce, receiverAddress, amount, gasLimit, gasPrice, data), emu.Signer(), sender, ) @@ -366,7 +368,7 @@ func deployEVMContract(t testing.TB, emu *EVMEmulator, creator *ecdsa.PrivateKey require.NoError(t, err) tx, err := types.SignTx( - types.NewContractCreation(nonce, txValue, gasLimit, evm.GasPrice, data), + types.NewContractCreation(nonce, txValue, gasLimit, gasPrice, data), emu.Signer(), creator, ) @@ -659,7 +661,7 @@ func initBenchmark(b *testing.B) (*EVMEmulator, []*types.Transaction, *context) gasLimit := uint64(100000) txs[i], err = types.SignTx( - types.NewTransaction(nonce, contractAddress, amount, gasLimit, evm.GasPrice, callArguments), + types.NewTransaction(nonce, contractAddress, amount, gasLimit, gasPrice, callArguments), emu.Signer(), sender, ) diff --git a/packages/vm/core/evm/evmimpl/external.go b/packages/vm/core/evm/evmimpl/external.go index e8b3234338..d59070877a 100644 --- a/packages/vm/core/evm/evmimpl/external.go +++ b/packages/vm/core/evm/evmimpl/external.go @@ -1,8 +1,6 @@ package evmimpl import ( - "fmt" - "github.com/ethereum/go-ethereum/common" "github.com/iotaledger/wasp/packages/kv" @@ -15,11 +13,3 @@ func Nonce(evmPartition kv.KVStoreReader, addr common.Address) uint64 { stateDBStore := emulator.StateDBSubrealmR(emuState) return emulator.GetNonce(stateDBStore, addr) } - -func CheckNonce(evmPartition kv.KVStore, addr common.Address, nonce uint64) error { - expected := Nonce(evmPartition, addr) - if nonce != expected { - return fmt.Errorf("Invalid nonce (%s) expected %d, got %d", addr, expected, nonce) - } - return nil -} diff --git a/packages/vm/core/evm/evmtest/evm_test.go b/packages/vm/core/evm/evmtest/evm_test.go index 42bad95b76..b0be6b057c 100644 --- a/packages/vm/core/evm/evmtest/evm_test.go +++ b/packages/vm/core/evm/evmtest/evm_test.go @@ -42,7 +42,6 @@ import ( "github.com/iotaledger/wasp/packages/util" "github.com/iotaledger/wasp/packages/vm" "github.com/iotaledger/wasp/packages/vm/core/accounts" - "github.com/iotaledger/wasp/packages/vm/core/evm" "github.com/iotaledger/wasp/packages/vm/core/evm/iscmagic" "github.com/iotaledger/wasp/packages/vm/gas" ) @@ -390,10 +389,9 @@ func TestCallViewGasLimit(t *testing.T) { require.NoError(t, err) senderAddress := crypto.PubkeyToAddress(loop.defaultSender.PublicKey) callMsg := loop.callMsg(ethereum.CallMsg{ - From: senderAddress, - Gas: math.MaxUint64, - GasPrice: evm.GasPrice, - Data: callArguments, + From: senderAddress, + Gas: math.MaxUint64, + Data: callArguments, }) _, err = loop.chain.evmChain.CallContract(callMsg, nil) require.Contains(t, err.Error(), "out of gas") @@ -1494,17 +1492,14 @@ func TestEVMWithdrawAll(t *testing.T) { require.EqualValues(t, tokensToWithdraw, env.solo.L1BaseTokens(receiver)) } -func TestEVMNonZeroGasPriceRequest(t *testing.T) { +func TestEVMGasPriceMismatch(t *testing.T) { env := initEVM(t) ethKey, senderAddress := env.soloChain.NewEthereumAccountWithL2Funds() // deploy solidity `storage` contract storage := env.deployStorageContract(ethKey) - // call FuncCallView to call EVM contract's `retrieve` view, get 42 - require.EqualValues(t, 42, storage.retrieve()) - - // issue a tx with non-0 gas price + // issue a tx with an arbitrary gas price valueToStore := uint32(888) gasPrice := big.NewInt(1234) // non 0 callArguments, err := storage.abi.Pack("store", valueToStore) @@ -1516,18 +1511,7 @@ func TestEVMNonZeroGasPriceRequest(t *testing.T) { require.NoError(t, err) err = storage.chain.evmChain.SendTransaction(tx) - require.NoError(t, err) - - rec := env.soloChain.LastReceipt() - - require.EqualValues(t, valueToStore, storage.retrieve()) - - // assert the gas fee is the same as a normal request (with 0 gas price) - res, err := storage.store(999) - require.NoError(t, err) - require.EqualValues(t, 999, storage.retrieve()) - require.Equal(t, res.iscReceipt.GasBurned, rec.GasBurned) - require.Equal(t, res.iscReceipt.GasFeeCharged, rec.GasFeeCharged) + require.ErrorContains(t, err, "invalid gas price") } func TestEVMTransferBaseTokens(t *testing.T) { @@ -1538,7 +1522,7 @@ func TestEVMTransferBaseTokens(t *testing.T) { sendTx := func(amount *big.Int) { nonce := env.getNonce(ethAddr) - unsignedTx := types.NewTransaction(nonce, someEthereumAddr, amount, env.maxGasLimit(), util.Big0, []byte{}) + unsignedTx := types.NewTransaction(nonce, someEthereumAddr, amount, env.maxGasLimit(), env.evmChain.GasPrice(), []byte{}) tx, err := types.SignTx(unsignedTx, evmutil.Signer(big.NewInt(int64(env.evmChainID))), ethKey) require.NoError(t, err) err = env.evmChain.SendTransaction(tx) @@ -1664,7 +1648,7 @@ func TestSendEntireBalance(t *testing.T) { testparameters.GetL1ParamsForTesting().BaseToken.Decimals, ) - unsignedTx := types.NewTransaction(0, someEthereumAddr, initialBalanceInEthDecimals, env.maxGasLimit(), util.Big0, []byte{}) + unsignedTx := types.NewTransaction(0, someEthereumAddr, initialBalanceInEthDecimals, env.maxGasLimit(), env.evmChain.GasPrice(), []byte{}) tx, err := types.SignTx(unsignedTx, evmutil.Signer(big.NewInt(int64(env.evmChainID))), ethKey) require.NoError(t, err) err = env.evmChain.SendTransaction(tx) @@ -1685,11 +1669,10 @@ func TestSendEntireBalance(t *testing.T) { ) estimatedGas, err := env.evmChain.EstimateGas(ethereum.CallMsg{ - From: ethAddr, - To: &someEthereumAddr, - GasPrice: evm.GasPrice, - Value: currentBalanceInEthDecimals, - Data: []byte{}, + From: ethAddr, + To: &someEthereumAddr, + Value: currentBalanceInEthDecimals, + Data: []byte{}, }, nil) require.NoError(t, err) @@ -1702,7 +1685,7 @@ func TestSendEntireBalance(t *testing.T) { currentBalance-tokensForGasBudget, testparameters.GetL1ParamsForTesting().BaseToken.Decimals, ) - unsignedTx = types.NewTransaction(1, someEthereumAddr, valueToSendInEthDecimals, gasLimit, util.Big0, []byte{}) + unsignedTx = types.NewTransaction(1, someEthereumAddr, valueToSendInEthDecimals, gasLimit, env.evmChain.GasPrice(), []byte{}) tx, err = types.SignTx(unsignedTx, evmutil.Signer(big.NewInt(int64(env.evmChainID))), ethKey) require.NoError(t, err) err = env.evmChain.SendTransaction(tx) @@ -1862,11 +1845,10 @@ func TestChangeGasPerToken(t *testing.T) { require.Greater(t, fee2, fee) } -func TestGasPriceIgnored(t *testing.T) { +func TestGasPriceIgnoredInEstimateGas(t *testing.T) { env := initEVM(t) var gasLimit []uint64 - var gasUsed []uint64 for _, gasPrice := range []*big.Int{ nil, @@ -1884,22 +1866,12 @@ func TestGasPriceIgnored(t *testing.T) { }}, "store", uint32(3)) require.NoError(t, err) - res, err := storage.store(uint32(3), ethCallOptions{ - sender: ethKey, - gasLimit: gas, - gasPrice: gasPrice, - }) - require.NoError(t, err) - gasLimit = append(gasLimit, gas) - gasUsed = append(gasUsed, res.evmReceipt.GasUsed) }) } t.Log("gas limit", gasLimit) - t.Log("gas used", gasUsed) require.Len(t, lo.Uniq(gasLimit), 1) - require.Len(t, lo.Uniq(gasUsed), 1) } // calling views via eth_call must not cost gas (still has a maximum budget, but simple view calls should pass) diff --git a/packages/vm/core/evm/evmtest/utils_test.go b/packages/vm/core/evm/evmtest/utils_test.go index 6448da0af9..11004c2d82 100644 --- a/packages/vm/core/evm/evmtest/utils_test.go +++ b/packages/vm/core/evm/evmtest/utils_test.go @@ -319,15 +319,14 @@ func (e *soloChainEnv) deployContract(creator *ecdsa.PrivateKey, abiJSON string, value := big.NewInt(0) gasLimit, err := e.evmChain.EstimateGas(ethereum.CallMsg{ - From: creatorAddress, - GasPrice: evm.GasPrice, - Value: value, - Data: data, + From: creatorAddress, + Value: value, + Data: data, }, nil) require.NoError(e.t, err) tx, err := types.SignTx( - types.NewContractCreation(nonce, value, gasLimit, evm.GasPrice, data), + types.NewContractCreation(nonce, value, gasLimit, e.evmChain.GasPrice(), data), e.signer(), creator, ) @@ -425,7 +424,7 @@ func (e *evmContractInstance) parseEthCallOptions(opts []ethCallOptions, callDat opt.value = big.NewInt(0) } if opt.gasPrice == nil { - opt.gasPrice = evm.GasPrice + opt.gasPrice = e.chain.evmChain.GasPrice() } if opt.gasLimit == 0 { var err error @@ -509,10 +508,8 @@ func (e *evmContractInstance) callView(fnName string, args []interface{}, v inte require.NoError(e.chain.t, err) senderAddress := crypto.PubkeyToAddress(e.defaultSender.PublicKey) callMsg := e.callMsg(ethereum.CallMsg{ - From: senderAddress, - Gas: 0, - GasPrice: evm.GasPrice, - Data: callArguments, + From: senderAddress, + Data: callArguments, }) var bn *rpc.BlockNumberOrHash if len(blockNumberOrHash) > 0 { diff --git a/packages/vm/core/evm/interface.go b/packages/vm/core/evm/interface.go index 935db8d971..876a4c00b2 100644 --- a/packages/vm/core/evm/interface.go +++ b/packages/vm/core/evm/interface.go @@ -4,8 +4,6 @@ package evm import ( - "math/big" - "github.com/iotaledger/wasp/packages/isc/coreutil" "github.com/iotaledger/wasp/packages/vm/core/evm/evmnames" ) @@ -60,6 +58,3 @@ const ( // TODO shouldn't this be different between chain, to prevent replay attacks? (maybe derived from ISC ChainID) DefaultChainID = uint16(1074) // IOTA -- get it? ) - -// Gas is charged in isc VM (L1 currencies), not ETH -var GasPrice = big.NewInt(0) diff --git a/packages/vm/gas/feepolicy.go b/packages/vm/gas/feepolicy.go index bc82d57711..ab765f9ae0 100644 --- a/packages/vm/gas/feepolicy.go +++ b/packages/vm/gas/feepolicy.go @@ -3,6 +3,7 @@ package gas import ( "fmt" "io" + "math/big" "github.com/iotaledger/hive.go/serializer/v2" "github.com/iotaledger/wasp/packages/util" @@ -129,3 +130,23 @@ func (p *FeePolicy) Write(w io.Writer) error { ww.WriteUint8(p.ValidatorFeeShare) return ww.Err } + +// GasPriceWei returns the gas price converted to wei +func (p *FeePolicy) GasPriceWei(l1BaseTokenDecimals uint32) *big.Int { + // special case '0:0' for free request + if p.GasPerToken.IsZero() { + return big.NewInt(0) + } + + // convert to wei (18 decimals) + decimalsDifference := 18 - l1BaseTokenDecimals + price := big.NewInt(10) + price.Exp(price, new(big.Int).SetUint64(uint64(decimalsDifference)), nil) + + price.Mul(price, new(big.Int).SetUint64(uint64(p.GasPerToken.B))) + price.Div(price, new(big.Int).SetUint64(uint64(p.GasPerToken.A))) + price.Mul(price, new(big.Int).SetUint64(uint64(p.EVMGasRatio.A))) + price.Div(price, new(big.Int).SetUint64(uint64(p.EVMGasRatio.B))) + + return price +} diff --git a/packages/vm/vmimpl/skipreq.go b/packages/vm/vmimpl/skipreq.go index 9c2240e170..47793788e4 100644 --- a/packages/vm/vmimpl/skipreq.go +++ b/packages/vm/vmimpl/skipreq.go @@ -6,6 +6,7 @@ import ( "time" iotago "github.com/iotaledger/iota.go/v3" + "github.com/iotaledger/wasp/packages/evm/evmutil" "github.com/iotaledger/wasp/packages/isc" "github.com/iotaledger/wasp/packages/kv" "github.com/iotaledger/wasp/packages/vm/core/accounts" @@ -68,25 +69,35 @@ func (reqctx *requestContext) checkReasonToSkipOffLedger() error { return err } senderAccount := offledgerReq.SenderAccount() - reqNonce := offledgerReq.Nonce() - var nonceErr error + reqNonce := offledgerReq.Nonce() + var expectedNonce uint64 if evmAgentID, ok := senderAccount.(*isc.EthereumAddressAgentID); ok { withContractState(reqctx.uncommittedState, evm.Contract, func(s kv.KVStore) { - nonceErr = evmimpl.CheckNonce(s, evmAgentID.EthAddress(), reqNonce) + expectedNonce = evmimpl.Nonce(s, evmAgentID.EthAddress()) + }) + } else { + withContractState(reqctx.uncommittedState, accounts.Contract, func(s kv.KVStore) { + expectedNonce = accounts.AccountNonce( + s, + senderAccount, + reqctx.ChainID(), + ) }) - return nonceErr } - - withContractState(reqctx.uncommittedState, accounts.Contract, func(s kv.KVStore) { - nonceErr = accounts.CheckNonce( - s, - senderAccount, - reqNonce, - reqctx.ChainID(), + if reqNonce != expectedNonce { + return fmt.Errorf( + "invalid nonce (%s): expected %d, got %d", + offledgerReq.SenderAccount(), expectedNonce, reqNonce, ) - }) - return nonceErr + } + + if evmTx := offledgerReq.EVMTransaction(); evmTx != nil { + if err := evmutil.CheckGasPrice(evmTx, reqctx.vm.chainInfo.GasFeePolicy); err != nil { + return err + } + } + return nil } // checkReasonToSkipOnLedger check reasons to skip UTXO request diff --git a/tools/cluster/tests/env.go b/tools/cluster/tests/env.go index 12803ccd9a..7a3b88ed3c 100644 --- a/tools/cluster/tests/env.go +++ b/tools/cluster/tests/env.go @@ -161,15 +161,14 @@ func (e *ChainEnv) DeploySolidityContract(creator *ecdsa.PrivateKey, abiJSON str jsonRPCClient := e.EVMJSONRPClient(0) // send request to node #0 gasLimit, err := jsonRPCClient.EstimateGas(context.Background(), ethereum.CallMsg{ - From: creatorAddress, - GasPrice: evm.GasPrice, - Value: value, - Data: data, + From: creatorAddress, + Value: value, + Data: data, }) require.NoError(e.t, err) tx, err := types.SignTx( - types.NewContractCreation(nonce, value, gasLimit, evm.GasPrice, data), + types.NewContractCreation(nonce, value, gasLimit, e.GetGasPriceEVM(), data), EVMSigner(), creator, ) @@ -191,6 +190,12 @@ func (e *ChainEnv) GetNonceEVM(addr common.Address) uint64 { return nonce } +func (e *ChainEnv) GetGasPriceEVM() *big.Int { + res, err := e.EVMJSONRPClient(0).SuggestGasPrice(context.Background()) + require.NoError(e.t, err) + return res +} + func (e *ChainEnv) EVMJSONRPClient(nodeIndex int) *ethclient.Client { return NewEVMJSONRPClient(e.t, e.Chain.ChainID.String(), e.Clu, nodeIndex) } diff --git a/tools/cluster/tests/pruning_test.go b/tools/cluster/tests/pruning_test.go index 80dcab5323..b84b2ea052 100644 --- a/tools/cluster/tests/pruning_test.go +++ b/tools/cluster/tests/pruning_test.go @@ -19,7 +19,6 @@ import ( "github.com/iotaledger/wasp/packages/testutil/testmisc" "github.com/iotaledger/wasp/packages/testutil/utxodb" "github.com/iotaledger/wasp/packages/vm/core/blocklog" - "github.com/iotaledger/wasp/packages/vm/core/evm" "github.com/iotaledger/wasp/tools/cluster/templates" ) @@ -72,7 +71,7 @@ func TestPruning(t *testing.T) { callArguments, err2 := storageContractABI.Pack("store", uint32(i)) require.NoError(t, err2) tx, err2 := types.SignTx( - types.NewTransaction(nonce+i, storageContractAddr, big.NewInt(0), 100000, evm.GasPrice, callArguments), + types.NewTransaction(nonce+i, storageContractAddr, big.NewInt(0), 100000, env.GetGasPriceEVM(), callArguments), EVMSigner(), evmPvtKey, ) diff --git a/tools/cluster/tests/spam_test.go b/tools/cluster/tests/spam_test.go index fce9bf85c6..638967bfb8 100644 --- a/tools/cluster/tests/spam_test.go +++ b/tools/cluster/tests/spam_test.go @@ -24,7 +24,6 @@ import ( "github.com/iotaledger/wasp/packages/solo" "github.com/iotaledger/wasp/packages/testutil" "github.com/iotaledger/wasp/packages/testutil/utxodb" - "github.com/iotaledger/wasp/packages/vm/core/evm" ) // executed in cluster_test.go @@ -268,7 +267,7 @@ func testSpamEVM(t *testing.T, env *ChainEnv) { callArguments, err2 := storageContractABI.Pack("store", uint32(i)) require.NoError(t, err2) tx, err2 := types.SignTx( - types.NewTransaction(nonce+i, storageContractAddr, big.NewInt(0), 100000, evm.GasPrice, callArguments), + types.NewTransaction(nonce+i, storageContractAddr, big.NewInt(0), 100000, env.GetGasPriceEVM(), callArguments), EVMSigner(), evmPvtKey, ) diff --git a/tools/cluster/tests/wasp-cli_test.go b/tools/cluster/tests/wasp-cli_test.go index b65f7e7481..1aac057070 100644 --- a/tools/cluster/tests/wasp-cli_test.go +++ b/tools/cluster/tests/wasp-cli_test.go @@ -20,10 +20,10 @@ import ( iotago "github.com/iotaledger/iota.go/v3" "github.com/iotaledger/wasp/clients/apiclient" "github.com/iotaledger/wasp/packages/kv/codec" + "github.com/iotaledger/wasp/packages/parameters" "github.com/iotaledger/wasp/packages/vm/core/accounts" "github.com/iotaledger/wasp/packages/vm/core/blob" "github.com/iotaledger/wasp/packages/vm/core/blocklog" - "github.com/iotaledger/wasp/packages/vm/core/evm" "github.com/iotaledger/wasp/packages/vm/gas" "github.com/iotaledger/wasp/packages/vm/vmtypes" "github.com/iotaledger/wasp/tools/cluster/templates" @@ -848,9 +848,10 @@ func TestEVMISCReceipt(t *testing.T) { w.MustRun("chain", "deposit", ethAddr.String(), "base:100000000", "--node=0") // send some arbitrary EVM tx + gasPrice := gas.DefaultFeePolicy().GasPriceWei(parameters.L1().BaseToken.Decimals) jsonRPCClient := NewEVMJSONRPClient(t, w.ChainID(0), w.Cluster, 0) tx, err := types.SignTx( - types.NewTransaction(0, ethAddr, big.NewInt(123), 100000, evm.GasPrice, []byte{}), + types.NewTransaction(0, ethAddr, big.NewInt(123), 100000, gasPrice, []byte{}), EVMSigner(), ethPvtKey, )