Skip to content

Commit

Permalink
fix(jsonrpc): require tx.GasPrice == chain gas price
Browse files Browse the repository at this point in the history
  • Loading branch information
dessaya committed Oct 4, 2023
1 parent ee51144 commit d4f94b9
Show file tree
Hide file tree
Showing 26 changed files with 178 additions and 161 deletions.
9 changes: 8 additions & 1 deletion packages/chainutil/evmcall.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 9 additions & 4 deletions packages/chainutil/evmestimategas.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
23 changes: 23 additions & 0 deletions packages/evm/evmutil/gasprice.go
Original file line number Diff line number Diff line change
@@ -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
}
33 changes: 8 additions & 25 deletions packages/evm/jsonrpc/evmchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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) {
Expand Down
27 changes: 15 additions & 12 deletions packages/evm/jsonrpc/jsonrpctest/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
)
Expand All @@ -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,
)
Expand Down Expand Up @@ -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,
)
Expand All @@ -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())
}

Expand Down
8 changes: 4 additions & 4 deletions packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand All @@ -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),
})
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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,
)
Expand Down
3 changes: 1 addition & 2 deletions packages/evm/jsonrpc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions packages/isc/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -57,6 +59,7 @@ type OffLedgerRequest interface {
ChainID() ChainID
Nonce() uint64
VerifySignature() error
EVMTransaction() *types.Transaction
}

type OnLedgerRequest interface {
Expand Down
5 changes: 5 additions & 0 deletions packages/isc/request_evmcall.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions packages/isc/request_evmtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,7 @@ func (req *evmOffLedgerTxRequest) VerifySignature() error {
}
return nil
}

func (req *evmOffLedgerTxRequest) EVMTransaction() *types.Transaction {
return req.tx
}
6 changes: 6 additions & 0 deletions packages/isc/request_offledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -232,3 +234,7 @@ func (req *OffLedgerRequestData) WithSender(sender *cryptolib.PublicKey) Unsigne
}
return req
}

func (*OffLedgerRequestData) EVMTransaction() *types.Transaction {
return nil
}
2 changes: 1 addition & 1 deletion packages/testutil/testdbhash/TestStorageContract.hex
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0xab29bd35687b3fb2d8008be9b7beae844b93b647a3a397e535328f4a48795a4e
0x0db336907593bdd7de084b947deb51f85783b6774a189a5dbdd72843a588c84d
2 changes: 1 addition & 1 deletion packages/vm/core/accounts/impl_views.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 3 additions & 16 deletions packages/vm/core/accounts/nonce.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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")
}
Expand All @@ -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
}
2 changes: 1 addition & 1 deletion packages/vm/core/accounts/stateaccess.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit d4f94b9

Please sign in to comment.