diff --git a/block/factory/factory.go b/block/factory/factory.go index 1601c291a6..072b580fdb 100644 --- a/block/factory/factory.go +++ b/block/factory/factory.go @@ -9,6 +9,7 @@ import ( v1 "github.com/harmony-one/harmony/block/v1" v2 "github.com/harmony-one/harmony/block/v2" v3 "github.com/harmony-one/harmony/block/v3" + v4 "github.com/harmony-one/harmony/block/v4" "github.com/harmony-one/harmony/internal/params" ) @@ -30,6 +31,8 @@ func NewFactory(chainConfig *params.ChainConfig) Factory { func (f *factory) NewHeader(epoch *big.Int) *block.Header { var impl blockif.Header switch { + case f.chainConfig.IsLondon(epoch): + impl = v4.NewHeader() case f.chainConfig.IsPreStaking(epoch) || f.chainConfig.IsStaking(epoch): impl = v3.NewHeader() case f.chainConfig.IsCrossLink(epoch): diff --git a/block/interface/header.go b/block/interface/header.go index f75810c68a..4656b30c38 100644 --- a/block/interface/header.go +++ b/block/interface/header.go @@ -236,4 +236,8 @@ type Header interface { // SetSlashes sets the RLP-encoded form of slashes // It stores a copy; the caller may freely modify the original. SetSlashes(newSlashes []byte) + + BaseFee() *big.Int + + SetBaseFee(newBaseFee *big.Int) } diff --git a/block/v0/header.go b/block/v0/header.go index 194132863e..7975080599 100644 --- a/block/v0/header.go +++ b/block/v0/header.go @@ -445,3 +445,15 @@ func (h *Header) Copy() blockif.Header { cpy := *h return &cpy } + +// BaseFee returns the base fee of the block. +func (h *Header) BaseFee() *big.Int { + return nil +} + +// SetBaseFee sets the base fee of the block. +func (h *Header) SetBaseFee(newBaseFee *big.Int) { + h.Logger(utils.Logger()).Warn(). + Str("baseFee", newBaseFee.String()). + Msg("cannot store base fee in V0 header") +} diff --git a/block/v1/header.go b/block/v1/header.go index ab153d4e17..ae7e92092f 100644 --- a/block/v1/header.go +++ b/block/v1/header.go @@ -429,3 +429,14 @@ func (h *Header) Copy() blockif.Header { cpy := *h return &cpy } + +// BaseFee returns the base fee of the header. +func (h *Header) BaseFee() *big.Int { + return nil +} + +// SetBaseFee sets the base fee of the header. +func (h *Header) SetBaseFee(newBaseFee *big.Int) { + h.Logger(utils.Logger()).Error(). + Msg("cannot store base fee in V1 header") +} diff --git a/block/v2/header.go b/block/v2/header.go index 7e2736ab50..80b2e4cfd9 100644 --- a/block/v2/header.go +++ b/block/v2/header.go @@ -428,3 +428,14 @@ func (h *Header) Copy() blockif.Header { cpy := *h return &cpy } + +// BaseFee returns the base fee of the block. +func (h *Header) BaseFee() *big.Int { + return nil +} + +// SetBaseFee sets the base fee of the block. +func (h *Header) SetBaseFee(newBaseFee *big.Int) { + h.Logger(utils.Logger()).Error(). + Msg("cannot store base fee in V2 header") +} diff --git a/block/v3/header.go b/block/v3/header.go index ea62204a9f..b207a5be02 100644 --- a/block/v3/header.go +++ b/block/v3/header.go @@ -422,3 +422,15 @@ func (h *Header) Copy() blockif.Header { cpy := *h return &cpy } + +// BaseFee returns the base fee of the header. +func (h *Header) BaseFee() *big.Int { + return nil +} + +// SetBaseFee sets the base fee of the header. +func (h *Header) SetBaseFee(newBaseFee *big.Int) { + h.Logger(utils.Logger()).Warn(). + Str("baseFee", newBaseFee.String()). + Msg("cannot store BaseFee in V3 header") +} diff --git a/block/v4/header.go b/block/v4/header.go new file mode 100644 index 0000000000..a9df258056 --- /dev/null +++ b/block/v4/header.go @@ -0,0 +1,442 @@ +package v4 + +import ( + "io" + "math/big" + "unsafe" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/rs/zerolog" + + blockif "github.com/harmony-one/harmony/block/interface" + "github.com/harmony-one/harmony/crypto/hash" + "github.com/harmony-one/harmony/internal/utils" + "github.com/harmony-one/harmony/shard" +) + +// Header is the V3 block header. +// V3 block header is exactly the same +// we copy the code instead of embedded v2 header into v3 +// when we do type checking in NewBodyForMatchingHeader +// the embedded structure will return v2 header type instead of v3 type +type Header struct { + fields headerFields +} + +// EncodeRLP encodes the header fields into RLP format. +func (h *Header) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, &h.fields) +} + +// DecodeRLP decodes the given RLP decode stream into the header fields. +func (h *Header) DecodeRLP(s *rlp.Stream) error { + return s.Decode(&h.fields) +} + +// NewHeader creates a new header object. +func NewHeader() *Header { + return &Header{headerFields{ + Number: new(big.Int), + Time: new(big.Int), + ViewID: new(big.Int), + Epoch: new(big.Int), + }} +} + +type headerFields struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + Coinbase common.Address `json:"miner" gencodec:"required"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + OutgoingReceiptHash common.Hash `json:"outgoingReceiptsRoot" gencodec:"required"` + IncomingReceiptHash common.Hash `json:"incomingReceiptsRoot" gencodec:"required"` + Bloom ethtypes.Bloom `json:"logsBloom" gencodec:"required"` + Number *big.Int `json:"number" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + Time *big.Int `json:"timestamp" gencodec:"required"` + Extra []byte `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash" gencodec:"required"` + // Additional Fields + ViewID *big.Int `json:"viewID" gencodec:"required"` + Epoch *big.Int `json:"epoch" gencodec:"required"` + ShardID uint32 `json:"shardID" gencodec:"required"` + LastCommitSignature [96]byte `json:"lastCommitSignature" gencodec:"required"` + LastCommitBitmap []byte `json:"lastCommitBitmap" gencodec:"required"` // Contains which validator signed + Vrf []byte `json:"vrf"` + Vdf []byte `json:"vdf"` + ShardState []byte `json:"shardState"` + CrossLinks []byte `json:"crossLink"` + Slashes []byte `json:"slashes"` + // BaseFee was added by EIP-1559 and is ignored in legacy headers. + BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` +} + +// ParentHash is the header hash of the parent block. For the genesis block +// which has no parent by definition, this field is zeroed out. +func (h *Header) ParentHash() common.Hash { + return h.fields.ParentHash +} + +// SetParentHash sets the parent hash field. +func (h *Header) SetParentHash(newParentHash common.Hash) { + h.fields.ParentHash = newParentHash +} + +// Coinbase is now the first 20 bytes of the SHA256 hash of the leader's +// public BLS key. This is required for EVM compatibility. +func (h *Header) Coinbase() common.Address { + return h.fields.Coinbase +} + +// SetCoinbase sets the coinbase address field. +func (h *Header) SetCoinbase(newCoinbase common.Address) { + h.fields.Coinbase = newCoinbase +} + +// Root is the state (account) trie root hash. +func (h *Header) Root() common.Hash { + return h.fields.Root +} + +// SetRoot sets the state trie root hash field. +func (h *Header) SetRoot(newRoot common.Hash) { + h.fields.Root = newRoot +} + +// TxHash is the transaction trie root hash. +func (h *Header) TxHash() common.Hash { + return h.fields.TxHash +} + +// SetTxHash sets the transaction trie root hash field. +func (h *Header) SetTxHash(newTxHash common.Hash) { + h.fields.TxHash = newTxHash +} + +// ReceiptHash is the same-shard transaction receipt trie hash. +func (h *Header) ReceiptHash() common.Hash { + return h.fields.ReceiptHash +} + +// SetReceiptHash sets the same-shard transaction receipt trie hash. +func (h *Header) SetReceiptHash(newReceiptHash common.Hash) { + h.fields.ReceiptHash = newReceiptHash +} + +// OutgoingReceiptHash is the egress transaction receipt trie hash. +func (h *Header) OutgoingReceiptHash() common.Hash { + return h.fields.OutgoingReceiptHash +} + +// SetOutgoingReceiptHash sets the egress transaction receipt trie hash. +func (h *Header) SetOutgoingReceiptHash(newOutgoingReceiptHash common.Hash) { + h.fields.OutgoingReceiptHash = newOutgoingReceiptHash +} + +// IncomingReceiptHash is the ingress transaction receipt trie hash. +func (h *Header) IncomingReceiptHash() common.Hash { + return h.fields.IncomingReceiptHash +} + +// SetIncomingReceiptHash sets the ingress transaction receipt trie hash. +func (h *Header) SetIncomingReceiptHash(newIncomingReceiptHash common.Hash) { + h.fields.IncomingReceiptHash = newIncomingReceiptHash +} + +// Bloom is the Bloom filter that indexes accounts and topics logged by smart +// contract transactions (executions) in this block. +func (h *Header) Bloom() ethtypes.Bloom { + return h.fields.Bloom +} + +// SetBloom sets the smart contract log Bloom filter for this block. +func (h *Header) SetBloom(newBloom ethtypes.Bloom) { + h.fields.Bloom = newBloom +} + +// Number is the block number. +// +// The returned instance is a copy; the caller may do anything with it. +func (h *Header) Number() *big.Int { + return new(big.Int).Set(h.fields.Number) +} + +// SetNumber sets the block number. +// +// It stores a copy; the caller may freely modify the original. +func (h *Header) SetNumber(newNumber *big.Int) { + h.fields.Number = new(big.Int).Set(newNumber) +} + +// GasLimit is the gas limit for transactions in this block. +func (h *Header) GasLimit() uint64 { + return h.fields.GasLimit +} + +// SetGasLimit sets the gas limit for transactions in this block. +func (h *Header) SetGasLimit(newGasLimit uint64) { + h.fields.GasLimit = newGasLimit +} + +// GasUsed is the amount of gas used by transactions in this block. +func (h *Header) GasUsed() uint64 { + return h.fields.GasUsed +} + +// SetGasUsed sets the amount of gas used by transactions in this block. +func (h *Header) SetGasUsed(newGasUsed uint64) { + h.fields.GasUsed = newGasUsed +} + +// Time is the UNIX timestamp of this block. +// +// The returned instance is a copy; the caller may do anything with it. +func (h *Header) Time() *big.Int { + return new(big.Int).Set(h.fields.Time) +} + +// SetTime sets the UNIX timestamp of this block. +// +// It stores a copy; the caller may freely modify the original. +func (h *Header) SetTime(newTime *big.Int) { + h.fields.Time = new(big.Int).Set(newTime) +} + +// Extra is the extra data field of this block. +// +// The returned slice is a copy; the caller may do anything with it. +func (h *Header) Extra() []byte { + return append(h.fields.Extra[:0:0], h.fields.Extra...) +} + +// SetExtra sets the extra data field of this block. +// +// It stores a copy; the caller may freely modify the original. +func (h *Header) SetExtra(newExtra []byte) { + h.fields.Extra = append(newExtra[:0:0], newExtra...) +} + +// MixDigest is the mixhash. +// +// This field is a remnant from Ethereum, and Harmony does not use it and always +// zeroes it out. +func (h *Header) MixDigest() common.Hash { + return h.fields.MixDigest +} + +// SetMixDigest sets the mixhash of this block. +func (h *Header) SetMixDigest(newMixDigest common.Hash) { + h.fields.MixDigest = newMixDigest +} + +// ViewID is the ID of the view in which this block was originally proposed. +// +// It normally increases by one for each subsequent block, or by more than one +// if one or more PBFT/FBFT view changes have occurred. +// +// The returned instance is a copy; the caller may do anything with it. +func (h *Header) ViewID() *big.Int { + return new(big.Int).Set(h.fields.ViewID) +} + +// SetViewID sets the view ID in which the block was originally proposed. +// +// It stores a copy; the caller may freely modify the original. +func (h *Header) SetViewID(newViewID *big.Int) { + h.fields.ViewID = new(big.Int).Set(newViewID) +} + +// Epoch is the epoch number of this block. +// +// The returned instance is a copy; the caller may do anything with it. +func (h *Header) Epoch() *big.Int { + return new(big.Int).Set(h.fields.Epoch) +} + +// SetEpoch sets the epoch number of this block. +// +// It stores a copy; the caller may freely modify the original. +func (h *Header) SetEpoch(newEpoch *big.Int) { + h.fields.Epoch = new(big.Int).Set(newEpoch) +} + +// ShardID is the shard ID to which this block belongs. +func (h *Header) ShardID() uint32 { + return h.fields.ShardID +} + +// SetShardID sets the shard ID to which this block belongs. +func (h *Header) SetShardID(newShardID uint32) { + h.fields.ShardID = newShardID +} + +// LastCommitSignature is the FBFT commit group signature for the last block. +func (h *Header) LastCommitSignature() [96]byte { + return h.fields.LastCommitSignature +} + +// SetLastCommitSignature sets the FBFT commit group signature for the last +// block. +func (h *Header) SetLastCommitSignature(newLastCommitSignature [96]byte) { + h.fields.LastCommitSignature = newLastCommitSignature +} + +// LastCommitBitmap is the signatory bitmap of the previous block. Bit +// positions index into committee member array. +// +// The returned slice is a copy; the caller may do anything with it. +func (h *Header) LastCommitBitmap() []byte { + return append(h.fields.LastCommitBitmap[:0:0], h.fields.LastCommitBitmap...) +} + +// SetLastCommitBitmap sets the signatory bitmap of the previous block. +// +// It stores a copy; the caller may freely modify the original. +func (h *Header) SetLastCommitBitmap(newLastCommitBitmap []byte) { + h.fields.LastCommitBitmap = append(newLastCommitBitmap[:0:0], newLastCommitBitmap...) +} + +// ShardStateHash is the shard state hash. +func (h *Header) ShardStateHash() common.Hash { + return common.Hash{} +} + +// SetShardStateHash sets the shard state hash. +func (h *Header) SetShardStateHash(newShardStateHash common.Hash) { + h.Logger(utils.Logger()).Warn(). + Str("shardStateHash", newShardStateHash.Hex()). + Msg("cannot store ShardStateHash in V3 header") +} + +// Vrf is the output of the VRF for the epoch. +// +// The returned slice is a copy; the caller may do anything with it. +func (h *Header) Vrf() []byte { + return append(h.fields.Vrf[:0:0], h.fields.Vrf...) +} + +// SetVrf sets the output of the VRF for the epoch. +// +// It stores a copy; the caller may freely modify the original. +func (h *Header) SetVrf(newVrf []byte) { + h.fields.Vrf = append(newVrf[:0:0], newVrf...) +} + +// Vdf is the output of the VDF for the epoch. +// +// The returned slice is a copy; the caller may do anything with it. +func (h *Header) Vdf() []byte { + return append(h.fields.Vdf[:0:0], h.fields.Vdf...) +} + +// SetVdf sets the output of the VDF for the epoch. +// +// It stores a copy; the caller may freely modify the original. +func (h *Header) SetVdf(newVdf []byte) { + h.fields.Vdf = append(newVdf[:0:0], newVdf...) +} + +// ShardState is the RLP-encoded form of shard state (list of committees) for +// the next epoch. +// +// The returned slice is a copy; the caller may do anything with it. +func (h *Header) ShardState() []byte { + return append(h.fields.ShardState[:0:0], h.fields.ShardState...) +} + +// SetShardState sets the RLP-encoded form of shard state +// +// It stores a copy; the caller may freely modify the original. +func (h *Header) SetShardState(newShardState []byte) { + h.fields.ShardState = append(newShardState[:0:0], newShardState...) +} + +// CrossLinks is the RLP-encoded form of non-beacon block headers chosen to be +// canonical by the beacon committee. This field is present only on beacon +// chain block headers. +// +// The returned slice is a copy; the caller may do anything with it. +func (h *Header) CrossLinks() []byte { + return append(h.fields.CrossLinks[:0:0], h.fields.CrossLinks...) +} + +// SetCrossLinks sets the RLP-encoded form of non-beacon block headers chosen to +// be canonical by the beacon committee. +// +// It stores a copy; the caller may freely modify the original. +func (h *Header) SetCrossLinks(newCrossLinks []byte) { + h.fields.CrossLinks = append(newCrossLinks[:0:0], newCrossLinks...) +} + +// Slashes .. +func (h *Header) Slashes() []byte { + return append(h.fields.Slashes[:0:0], h.fields.Slashes...) +} + +// SetSlashes .. +func (h *Header) SetSlashes(newSlashes []byte) { + h.fields.Slashes = append(newSlashes[:0:0], newSlashes...) +} + +// Hash returns the block hash of the header, which is simply the keccak256 hash of its +// RLP encoding. +func (h *Header) Hash() common.Hash { + return hash.FromRLP(h) +} + +// Size returns the approximate memory used by all internal contents. It is used +// to approximate and limit the memory consumption of various caches. +func (h *Header) Size() common.StorageSize { + // TODO: update with new fields + var baseFeeBits int + if h.BaseFee() != nil { + baseFeeBits = h.BaseFee().BitLen() + } + return common.StorageSize(unsafe.Sizeof(*h)) + + common.StorageSize(len(h.Extra())+(h.Number().BitLen()+ + h.Time().BitLen()+baseFeeBits)/8, + ) +} + +// Logger returns a sub-logger with block contexts added. +func (h *Header) Logger(logger *zerolog.Logger) *zerolog.Logger { + nlogger := logger. + With(). + Str("blockHash", h.Hash().Hex()). + Uint32("blockShard", h.ShardID()). + Uint64("blockEpoch", h.Epoch().Uint64()). + Uint64("blockNumber", h.Number().Uint64()). + Logger() + return &nlogger +} + +// GetShardState returns the deserialized shard state object. +func (h *Header) GetShardState() (shard.State, error) { + state, err := shard.DecodeWrapper(h.ShardState()) + if err != nil { + return shard.State{}, err + } + return *state, nil +} + +// Copy returns a copy of the given header. +func (h *Header) Copy() blockif.Header { + cpy := *h + return &cpy +} + +func (h *Header) BaseFee() *big.Int { + if h.fields.BaseFee == nil { + return nil + } + out := new(big.Int).Set(h.fields.BaseFee) + return out +} + +func (h *Header) SetBaseFee(newBaseFee *big.Int) { + h.fields.BaseFee = new(big.Int).Set(newBaseFee) +} diff --git a/consensus/misc/eip1559/eip1559.go b/consensus/misc/eip1559/eip1559.go new file mode 100644 index 0000000000..6f92fa6472 --- /dev/null +++ b/consensus/misc/eip1559/eip1559.go @@ -0,0 +1,98 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eip1559 + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/harmony-one/harmony/block" + "github.com/harmony-one/harmony/internal/params" + "github.com/pkg/errors" +) + +// VerifyEIP1559Header verifies some header attributes which were changed in EIP-1559, +// - gas limit check +// - basefee check +func VerifyEIP1559Header(config *params.ChainConfig, parent, header *block.Header) error { + // Verify that the gas limit remains within allowed bounds + parentGasLimit := parent.GasLimit() + if !config.IsLondon(parent.Number()) { + parentGasLimit = parent.GasLimit() * config.ElasticityMultiplier() + } + if err := misc.VerifyGaslimit(parentGasLimit, header.GasLimit()); err != nil { + return err + } + // Verify the header is not malformed + if header.BaseFee == nil { + return errors.New("header is missing baseFee") + } + // Verify the baseFee is correct based on the parent header. + expectedBaseFee := CalcBaseFee(config, parent) + if header.BaseFee().Cmp(expectedBaseFee) != 0 { + return fmt.Errorf("invalid baseFee: have %s, want %s, parentBaseFee %s, parentGasUsed %d", + header.BaseFee(), expectedBaseFee, parent.BaseFee(), parent.GasUsed()) + } + return nil +} + +// CalcBaseFee calculates the basefee of the header. +func CalcBaseFee(config *params.ChainConfig, parent *block.Header) *big.Int { + // If the current block is the first EIP-1559 block, return the InitialBaseFee. + if !config.IsLondon(parent.Epoch()) { + return new(big.Int).SetUint64(params.InitialBaseFee) + } + + parentGasTarget := parent.GasLimit() / config.ElasticityMultiplier() + // If the parent gasUsed is the same as the target, the baseFee remains unchanged. + if parent.GasUsed() == parentGasTarget { + return new(big.Int).Set(parent.BaseFee()) + } + + var ( + num = new(big.Int) + denom = new(big.Int) + ) + + if parent.GasUsed() > parentGasTarget { + // If the parent block used more gas than its target, the baseFee should increase. + // max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + num.SetUint64(parent.GasUsed() - parentGasTarget) + num.Mul(num, parent.BaseFee()) + num.Div(num, denom.SetUint64(parentGasTarget)) + num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + if num.Cmp(common.Big1) < 0 { + return num.Add(parent.BaseFee(), common.Big1) + } + return num.Add(parent.BaseFee(), num) + } else { + // Otherwise if the parent block used less gas than its target, the baseFee should decrease. + // max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + num.SetUint64(parentGasTarget - parent.GasUsed()) + num.Mul(num, parent.BaseFee()) + num.Div(num, denom.SetUint64(parentGasTarget)) + num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + + baseFee := num.Sub(parent.BaseFee(), num) + if baseFee.Cmp(common.Big0) < 0 { + baseFee = common.Big0 + } + return baseFee + } +} diff --git a/consensus/misc/eip1559/eip1559_test.go b/consensus/misc/eip1559/eip1559_test.go new file mode 100644 index 0000000000..54f0e7df4d --- /dev/null +++ b/consensus/misc/eip1559/eip1559_test.go @@ -0,0 +1,134 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eip1559 + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/harmony-one/harmony/internal/params" + + "github.com/harmony-one/harmony/block" + v4 "github.com/harmony-one/harmony/block/v4" +) + +// copyConfig does a _shallow_ copy of a given config. Safe to set new values, but +// do not use e.g. SetInt() on the numbers. For testing only +func copyConfig(original *params.ChainConfig) *params.ChainConfig { + return ¶ms.ChainConfig{ + ChainID: original.ChainID, + HomesteadBlock: original.HomesteadBlock, + DAOForkBlock: original.DAOForkBlock, + DAOForkSupport: original.DAOForkSupport, + EIP150Block: original.EIP150Block, + EIP155Block: original.EIP155Block, + EIP158Block: original.EIP158Block, + ByzantiumBlock: original.ByzantiumBlock, + ConstantinopleBlock: original.ConstantinopleBlock, + PetersburgBlock: original.PetersburgBlock, + IstanbulBlock: original.IstanbulBlock, + MuirGlacierBlock: original.MuirGlacierBlock, + BerlinBlock: original.BerlinBlock, + LondonBlock: original.LondonBlock, + TerminalTotalDifficulty: original.TerminalTotalDifficulty, + Ethash: original.Ethash, + Clique: original.Clique, + } +} + +func config() *params.ChainConfig { + config := copyConfig(params.TestChainConfig) + config.EIP1559Epoch = big.NewInt(1) + return config +} + +// TestBlockGasLimits tests the gasLimit checks for blocks both across +// the EIP-1559 boundary and post-1559 blocks +func TestBlockGasLimits(t *testing.T) { + initial := new(big.Int).SetUint64(params.InitialBaseFee) + + for i, tc := range []struct { + pGasLimit uint64 + pNum int64 + gasLimit uint64 + ok bool + }{ + // Transitions from non-london to london + {10000000, 4, 20000000, true}, // No change + {10000000, 4, 20019530, true}, // Upper limit + {10000000, 4, 20019531, false}, // Upper +1 + {10000000, 4, 19980470, true}, // Lower limit + {10000000, 4, 19980469, false}, // Lower limit -1 + // London to London + {20000000, 5, 20000000, true}, + {20000000, 5, 20019530, true}, // Upper limit + {20000000, 5, 20019531, false}, // Upper limit +1 + {20000000, 5, 19980470, true}, // Lower limit + {20000000, 5, 19980469, false}, // Lower limit -1 + {40000000, 5, 40039061, true}, // Upper limit + {40000000, 5, 40039062, false}, // Upper limit +1 + {40000000, 5, 39960939, true}, // lower limit + {40000000, 5, 39960938, false}, // Lower limit -1 + } { + parent := &block.Header{ + GasUsed: tc.pGasLimit / 2, + GasLimit: tc.pGasLimit, + BaseFee: initial, + Number: big.NewInt(tc.pNum), + } + header := &types.Header{ + GasUsed: tc.gasLimit / 2, + GasLimit: tc.gasLimit, + BaseFee: initial, + Number: big.NewInt(tc.pNum + 1), + } + err := VerifyEIP1559Header(config(), parent, header) + if tc.ok && err != nil { + t.Errorf("test %d: Expected valid header: %s", i, err) + } + if !tc.ok && err == nil { + t.Errorf("test %d: Expected invalid header", i) + } + } +} + +// TestCalcBaseFee assumes all blocks are 1559-blocks +func TestCalcBaseFee(t *testing.T) { + tests := []struct { + parentBaseFee int64 + parentGasLimit uint64 + parentGasUsed uint64 + expectedBaseFee int64 + }{ + {params.InitialBaseFee, 20000000, 10000000, params.InitialBaseFee}, // usage == target + {params.InitialBaseFee, 20000000, 9000000, 987500000}, // usage below target + {params.InitialBaseFee, 20000000, 11000000, 1012500000}, // usage above target + } + for i, test := range tests { + parent := &v4.Header{ + Number: common.Big32, + GasLimit: test.parentGasLimit, + GasUsed: test.parentGasUsed, + BaseFee: big.NewInt(test.parentBaseFee), + } + if have, want := CalcBaseFee(config(), parent), big.NewInt(test.expectedBaseFee); have.Cmp(want) != 0 { + t.Errorf("test %d: have %d want %d, ", i, have, want) + } + } +} diff --git a/core/evm.go b/core/evm.go index 8fbdeec778..d959bf990f 100644 --- a/core/evm.go +++ b/core/evm.go @@ -59,8 +59,8 @@ type ChainContext interface { ShardID() uint32 // this is implemented by blockchain.go already } -// NewEVMContext creates a new context for use in the EVM. -func NewEVMContext(msg Message, header *block.Header, chain ChainContext, author *common.Address) vm.Context { +// NewEVMBlockContext creates a new context for use in the EVM. +func NewEVMBlockContext(msg Message, header *block.Header, chain ChainContext, author *common.Address) vm.BlockContext { // If we don't have an explicit author (i.e. not mining), extract from the header var beneficiary common.Address if author == nil { @@ -73,21 +73,21 @@ func NewEVMContext(msg Message, header *block.Header, chain ChainContext, author vrfAndProof := header.Vrf() copy(vrf[:], vrfAndProof[:32]) } - return vm.Context{ - CanTransfer: CanTransfer, - Transfer: Transfer, - GetHash: GetHashFn(header, chain), - GetVRF: GetVRFFn(header, chain), - IsValidator: IsValidator, - Origin: msg.From(), - GasPrice: new(big.Int).Set(msg.GasPrice()), - Coinbase: beneficiary, - GasLimit: header.GasLimit(), - BlockNumber: header.Number(), - EpochNumber: header.Epoch(), - Time: header.Time(), - VRF: vrf, - TxType: 0, + return vm.BlockContext{ + CanTransfer: CanTransfer, + Transfer: Transfer, + GetHash: GetHashFn(header, chain), + GetVRF: GetVRFFn(header, chain), + IsValidator: IsValidator, + //Origin: msg.From(), + //GasPrice: new(big.Int).Set(msg.GasPrice()), + Coinbase: beneficiary, + GasLimit: header.GasLimit(), + BlockNumber: header.Number(), + EpochNumber: header.Epoch(), + Time: header.Time(), + VRF: vrf, + //TxType: 0, CreateValidator: CreateValidatorFn(header, chain), EditValidator: EditValidatorFn(header, chain), Delegate: DelegateFn(header, chain), @@ -99,6 +99,19 @@ func NewEVMContext(msg Message, header *block.Header, chain ChainContext, author } } +// NewEVMTxContext creates a new transaction context for a single transaction. +func NewEVMTxContext(msg Message) vm.TxContext { + ctx := vm.TxContext{ + Origin: msg.From(), + GasPrice: new(big.Int).Set(msg.GasPrice()), + //BlobHashes: msg.BlobHashes, + } + //if msg.BlobGasFeeCap != nil { + // ctx.BlobFeeCap = new(big.Int).Set(msg.BlobGasFeeCap) + //} + return ctx +} + // HandleStakeMsgFn returns a function which accepts // (1) the chain state database // (2) the processed staking parameters diff --git a/core/evm_test.go b/core/evm_test.go index 3778cad260..e4c63d538c 100644 --- a/core/evm_test.go +++ b/core/evm_test.go @@ -67,7 +67,7 @@ func TestEVMStaking(t *testing.T) { // transaction as message (chainId = 2) msg, _ := tx.AsMessage(types.NewEIP155Signer(common.Big2)) // context - ctx := NewEVMContext(msg, header, chain, nil /* coinbase */) + ctx := NewEVMBlockContext(msg, header, chain, nil /* coinbase */) // createValidator test createValidator := sampleCreateValidator(*key) @@ -105,7 +105,7 @@ func TestEVMStaking(t *testing.T) { }, } // redelegate using epoch1, so that we can cover the locked tokens use case as well - ctx2 := NewEVMContext(msg, blockfactory.ForTest.NewHeader(common.Big1), chain, nil) + ctx2 := NewEVMBlockContext(msg, blockfactory.ForTest.NewHeader(common.Big1), chain, nil) err = db.UpdateValidatorWrapper(wrapper.Address, wrapper) err = ctx2.Delegate(db, nil, &delegate) if err != nil { @@ -437,8 +437,8 @@ func TestWriteCapablePrecompilesIntegration(t *testing.T) { // gp := new(GasPool).AddGas(math.MaxUint64) tx := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), 0, big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) msg, _ := tx.AsMessage(types.NewEIP155Signer(common.Big2)) - ctx := NewEVMContext(msg, header, chain, nil /* coinbase */) - evm := vm.NewEVM(ctx, db, params.TestChainConfig, vm.Config{}) + ctx := NewEVMBlockContext(msg, header, chain, nil /* coinbase */) + evm := vm.NewEVM(ctx, NewEVMTxContext(msg), db, params.TestChainConfig, vm.Config{}) // interpreter := vm.NewEVMInterpreter(evm, vm.Config{}) address := common.BytesToAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252}) // caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) @@ -466,7 +466,7 @@ func TestWriteCapablePrecompilesIntegration(t *testing.T) { // now without staking precompile cfg := params.TestChainConfig cfg.StakingPrecompileEpoch = big.NewInt(10000000) - evm = vm.NewEVM(ctx, db, cfg, vm.Config{}) + evm = vm.NewEVM(ctx, NewEVMTxContext(msg), db, cfg, vm.Config{}) _, _, err = evm.Call(vm.AccountRef(common.Address{}), createValidator.ValidatorAddress, []byte{}, diff --git a/core/state_processor.go b/core/state_processor.go index ce6a249fd5..d576161702 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -301,11 +301,11 @@ func ApplyTransaction(bc ChainContext, author *common.Address, gp *GasPool, stat } // Create a new context to be used in the EVM environment - context := NewEVMContext(msg, header, bc, author) + context := NewEVMBlockContext(msg, header, bc, author) context.TxType = txType // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. - vmenv := vm.NewEVM(context, statedb, config, cfg) + vmenv := vm.NewEVM(context, NewEVMTxContext(msg), statedb, config, cfg) // Apply the transaction to the current state (included in the env) result, err := ApplyMessage(vmenv, msg, gp) if err != nil { @@ -337,7 +337,7 @@ func ApplyTransaction(bc ChainContext, author *common.Address, gp *GasPool, stat receipt.EffectiveGasPrice = tx.EffectiveGasPrice(big.NewInt(0), nil) // if the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { - receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) + receipt.ContractAddress = crypto.CreateAddress(vmenv.TxContext.Origin, tx.Nonce()) } // Set the receipt logs and create a bloom for filtering @@ -393,11 +393,11 @@ func ApplyStakingTransaction( } // Create a new context to be used in the EVM environment - context := NewEVMContext(msg, header, bc, author) + context := NewEVMBlockContext(msg, header, bc, author) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. - vmenv := vm.NewEVM(context, statedb, config, cfg) + vmenv := vm.NewEVM(context, NewEVMTxContext(msg), statedb, config, cfg) // Apply the transaction to the current state (included in the env) gas, err = ApplyStakingMessage(vmenv, msg, gp) diff --git a/core/state_transition.go b/core/state_transition.go index 3b11f774fc..c0da3b2d5c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -150,6 +150,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (ExecutionResult, error) { + evm.SetTxContext(NewEVMTxContext(msg)) return NewStateTransition(evm, msg, gp).TransitionDb() } @@ -216,8 +217,8 @@ func (st *StateTransition) TransitionDb() (ExecutionResult, error) { } msg := st.msg sender := vm.AccountRef(msg.From()) - homestead := st.evm.ChainConfig().IsS3(st.evm.EpochNumber) // s3 includes homestead - istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.EpochNumber) + homestead := st.evm.ChainConfig().IsS3(st.evm.Context.EpochNumber) // s3 includes homestead + istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.EpochNumber) contractCreation := msg.To() == nil // Pay intrinsic gas @@ -283,15 +284,15 @@ func (st *StateTransition) refundGas() { } func (st *StateTransition) collectGas() { - if config := st.evm.ChainConfig(); !config.IsStaking(st.evm.EpochNumber) { + if config := st.evm.ChainConfig(); !config.IsStaking(st.evm.Context.EpochNumber) { // Before staking epoch, add the fees to the block producer txFee := new(big.Int).Mul( new(big.Int).SetUint64(st.gasUsed()), st.gasPrice, ) - st.state.AddBalance(st.evm.Coinbase, txFee) + st.state.AddBalance(st.evm.Context.Coinbase, txFee) } else if feeCollectors := shard.Schedule.InstanceForEpoch( - st.evm.EpochNumber, + st.evm.Context.EpochNumber, ).FeeCollectors(); len(feeCollectors) > 0 { // The caller must ensure that the feeCollectors are accurately set // at the appropriate epochs @@ -323,8 +324,8 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) { msg := st.msg sender := vm.AccountRef(msg.From()) - homestead := st.evm.ChainConfig().IsS3(st.evm.EpochNumber) // s3 includes homestead - istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.EpochNumber) + homestead := st.evm.ChainConfig().IsS3(st.evm.Context.EpochNumber) // s3 includes homestead + istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.EpochNumber) // Pay intrinsic gas gas, err := vm.IntrinsicGas(st.data, false, homestead, istanbul, msg.Type() == types.StakeCreateVal) @@ -358,7 +359,7 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) { if msg.From() != stkMsg.ValidatorAddress { return 0, errInvalidSigner } - err = st.evm.CreateValidator(st.evm.StateDB, nil, stkMsg) + err = st.evm.Context.CreateValidator(st.evm.StateDB, nil, stkMsg) case types.StakeEditVal: stkMsg := &stakingTypes.EditValidator{} if err = rlp.DecodeBytes(msg.Data(), stkMsg); err != nil { @@ -369,7 +370,7 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) { if msg.From() != stkMsg.ValidatorAddress { return 0, errInvalidSigner } - err = st.evm.EditValidator(st.evm.StateDB, nil, stkMsg) + err = st.evm.Context.EditValidator(st.evm.StateDB, nil, stkMsg) case types.Delegate: stkMsg := &stakingTypes.Delegate{} if err = rlp.DecodeBytes(msg.Data(), stkMsg); err != nil { @@ -379,7 +380,7 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) { if msg.From() != stkMsg.DelegatorAddress { return 0, errInvalidSigner } - err = st.evm.Delegate(st.evm.StateDB, nil, stkMsg) + err = st.evm.Context.Delegate(st.evm.StateDB, nil, stkMsg) case types.Undelegate: stkMsg := &stakingTypes.Undelegate{} if err = rlp.DecodeBytes(msg.Data(), stkMsg); err != nil { @@ -389,7 +390,7 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) { if msg.From() != stkMsg.DelegatorAddress { return 0, errInvalidSigner } - err = st.evm.Undelegate(st.evm.StateDB, nil, stkMsg) + err = st.evm.Context.Undelegate(st.evm.StateDB, nil, stkMsg) case types.CollectRewards: stkMsg := &stakingTypes.CollectRewards{} if err = rlp.DecodeBytes(msg.Data(), stkMsg); err != nil { @@ -399,7 +400,7 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) { if msg.From() != stkMsg.DelegatorAddress { return 0, errInvalidSigner } - err = st.evm.CollectRewards(st.evm.StateDB, nil, stkMsg) + err = st.evm.Context.CollectRewards(st.evm.StateDB, nil, stkMsg) default: return 0, stakingTypes.ErrInvalidStakingKind } diff --git a/core/state_transition_test.go b/core/state_transition_test.go index 925fe0848c..1dab62de90 100644 --- a/core/state_transition_test.go +++ b/core/state_transition_test.go @@ -72,8 +72,8 @@ func testApplyStakingMessage(test applyStakingMessageTest, t *testing.T) { msg, _ := StakingToMessage(test.tx, header.Number()) // make EVM - ctx := NewEVMContext(msg, header, chain, nil /* coinbase */) - vmenv := vm.NewEVM(ctx, db, params.TestChainConfig, vm.Config{}) + ctx := NewEVMBlockContext(msg, header, chain, nil /* coinbase */) + vmenv := vm.NewEVM(ctx, NewEVMTxContext(msg), db, params.TestChainConfig, vm.Config{}) // run the staking tx _, err := ApplyStakingMessage(vmenv, msg, gp) @@ -115,10 +115,10 @@ func TestCollectGas(t *testing.T) { initialBalance := big.NewInt(2e18) db.AddBalance(from, initialBalance) msg, _ := tx.AsMessage(types.NewEIP155Signer(common.Big2)) - ctx := NewEVMContext(msg, header, chain, nil /* coinbase is nil, no block reward */) + ctx := NewEVMBlockContext(msg, header, chain, nil /* coinbase is nil, no block reward */) ctx.TxType = types.SameShardTx - vmenv := vm.NewEVM(ctx, db, params.TestChainConfig, vm.Config{}) + vmenv := vm.NewEVM(ctx, NewEVMTxContext(msg), db, params.TestChainConfig, vm.Config{}) gasPool := new(GasPool).AddGas(math.MaxUint64) _, err := ApplyMessage(vmenv, msg, gasPool) if err != nil { @@ -191,10 +191,11 @@ func TestCollectGasRounding(t *testing.T) { initialBalance := big.NewInt(2e18) db.AddBalance(from, initialBalance) msg, _ := tx.AsMessage(types.NewEIP155Signer(common.Big2)) - ctx := NewEVMContext(msg, header, chain, nil /* coinbase is nil, no block reward */) + ctx := NewEVMBlockContext(msg, header, chain, nil /* coinbase is nil, no block reward */) ctx.TxType = types.SameShardTx - vmenv := vm.NewEVM(ctx, db, params.TestChainConfig, vm.Config{}) + vmenv := vm.NewEVM(ctx, NewEVMTxContext(msg), db, params.TestChainConfig, vm.Config{}) + vmenv.SetTxContext(NewEVMTxContext(msg)) gasPool := new(GasPool).AddGas(math.MaxUint64) st := NewStateTransition(vmenv, msg, gasPool) // buy gas to set initial gas to 5: gasLimit * gasPrice @@ -242,7 +243,7 @@ func TestPrepare(t *testing.T) { initialBalance := big.NewInt(2e18) db.AddBalance(from, initialBalance) msg, _ := tx.AsMessage(types.NewEIP155Signer(common.Big2)) - ctx := NewEVMContext(msg, header, chain, nil /* coinbase is nil, no block reward */) + ctx := NewEVMBlockContext(msg, header, chain, nil /* coinbase is nil, no block reward */) ctx.TxType = types.SameShardTx // populate transient storage @@ -254,7 +255,7 @@ func TestPrepare(t *testing.T) { } // transition state db by calling apply message - vmenv := vm.NewEVM(ctx, db, params.TestChainConfig, vm.Config{}) + vmenv := vm.NewEVM(ctx, NewEVMTxContext(msg), db, params.TestChainConfig, vm.Config{}) gasPool := new(GasPool).AddGas(math.MaxUint64) _, err := ApplyMessage(vmenv, msg, gasPool) if err != nil { diff --git a/core/vm/common.go b/core/vm/common.go index d592a9410d..90ba4a4ad1 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -17,15 +17,14 @@ package vm import ( - "math/big" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/holiman/uint256" ) // calcMemSize64 calculates the required memory size, and returns // the size and whether the result overflowed uint64 -func calcMemSize64(off, l *big.Int) (uint64, bool) { +func calcMemSize64(off, l *uint256.Int) (uint64, bool) { if !l.IsUint64() { return 0, true } @@ -35,16 +34,16 @@ func calcMemSize64(off, l *big.Int) (uint64, bool) { // calcMemSize64WithUint calculates the required memory size, and returns // the size and whether the result overflowed uint64 // Identical to calcMemSize64, but length is a uint64 -func calcMemSize64WithUint(off *big.Int, length64 uint64) (uint64, bool) { +func calcMemSize64WithUint(off *uint256.Int, length64 uint64) (uint64, bool) { // if length is zero, memsize is always zero, regardless of offset if length64 == 0 { return 0, false } // Check that offset doesn't overflow - if !off.IsUint64() { + offset64, overflow := off.Uint64WithOverflow() + if overflow { return 0, true } - offset64 := off.Uint64() val := offset64 + length64 // if value < either of it's parts, then it overflowed return val, val < offset64 @@ -64,22 +63,6 @@ func getData(data []byte, start uint64, size uint64) []byte { return common.RightPadBytes(data[start:end], int(size)) } -// getDataBig returns a slice from the data based on the start and size and pads -// up to size with zero's. This function is overflow safe. -func getDataBig(data []byte, start *big.Int, size *big.Int) []byte { - dlen := big.NewInt(int64(len(data))) - - s := math.BigMin(start, dlen) - e := math.BigMin(new(big.Int).Add(s, size), dlen) - return common.RightPadBytes(data[s.Uint64():e.Uint64()], int(size.Uint64())) -} - -// bigUint64 returns the integer casted to a uint64 and returns whether it -// overflowed in the process. -func bigUint64(v *big.Int) (uint64, bool) { - return v.Uint64(), !v.IsUint64() -} - // toWordSize returns the ceiled word size required for memory expansion. func toWordSize(size uint64) uint64 { if size > math.MaxUint64-31 { diff --git a/core/vm/contract.go b/core/vm/contract.go index aa18231e0c..a262d7b625 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -20,6 +20,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" ) // ContractRef is a reference to the contract's backing object @@ -82,18 +83,43 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin return c } -func (c *Contract) validJumpdest(dest *big.Int) bool { - udest := dest.Uint64() +func (c *Contract) validJumpdest(dest *uint256.Int) bool { + udest, overflow := dest.Uint64WithOverflow() // PC cannot go beyond len(code) and certainly can't be bigger than 63bits. // Don't bother checking for JUMPDEST in that case. - if dest.BitLen() >= 63 || udest >= uint64(len(c.Code)) { + if overflow || udest >= uint64(len(c.Code)) { return false } // Only JUMPDESTs allowed for destinations if OpCode(c.Code[udest]) != JUMPDEST { return false } + return c.isCode(udest) +} + +func (c *Contract) validJumpSubdest(udest uint64) bool { + // PC cannot go beyond len(code) and certainly can't be bigger than 63 bits. + // Don't bother checking for BEGINSUB in that case. + if int64(udest) < 0 || udest >= uint64(len(c.Code)) { + return false + } + // Only BEGINSUBs allowed for destinations + if OpCode(c.Code[udest]) != BEGINSUB { + return false + } + return c.isCode(udest) +} + +// isCode returns true if the provided PC location is an actual opcode, as +// opposed to a data-segment following a PUSHN operation. +func (c *Contract) isCode(udest uint64) bool { + // Do we already have an analysis laying around? + if c.analysis != nil { + return c.analysis.codeSegment(udest) + } // Do we have a contract hash already? + // If we do have a hash, that means it's a 'regular' contract. For regular + // contracts ( not temporary initcode), we store the analysis in a map if c.CodeHash != (common.Hash{}) { // Does parent context have the analysis? analysis, exist := c.jumpdests[c.CodeHash] @@ -103,6 +129,8 @@ func (c *Contract) validJumpdest(dest *big.Int) bool { analysis = codeBitmap(c.Code) c.jumpdests[c.CodeHash] = analysis } + // Also stash it in current contract for faster access + c.analysis = analysis return analysis.codeSegment(udest) } // We don't have the code hash, most likely a piece of initcode not already @@ -163,7 +191,7 @@ func (c *Contract) Address() common.Address { return c.self.Address() } -// Value returns the contracts value (sent to it from it's caller) +// Value returns the contract's value (sent to it from it's caller) func (c *Contract) Value() *big.Int { return c.value } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 01625cb3d8..4d32a8e49c 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -19,22 +19,18 @@ package vm import ( "crypto/sha256" "encoding/binary" + "encoding/hex" //Needed for SHA3-256 FIPS202 "errors" "fmt" "math/big" - "github.com/ethereum/go-ethereum/crypto/blake2b" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/blake2b" "github.com/ethereum/go-ethereum/crypto/bn256" "github.com/harmony-one/harmony/internal/params" "golang.org/x/crypto/ripemd160" - - //Needed for SHA3-256 FIPS202 - "encoding/hex" - "golang.org/x/crypto/sha3" ) @@ -249,10 +245,14 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) { type bigModExp struct{} var ( + big0 = big.NewInt(0) big1 = big.NewInt(1) + big3 = big.NewInt(3) big4 = big.NewInt(4) + big7 = big.NewInt(7) big8 = big.NewInt(8) big16 = big.NewInt(16) + big20 = big.NewInt(20) big32 = big.NewInt(32) big64 = big.NewInt(64) big96 = big.NewInt(96) diff --git a/core/vm/contracts_write.go b/core/vm/contracts_write.go index 46ba7fe923..e1f6a10476 100644 --- a/core/vm/contracts_write.go +++ b/core/vm/contracts_write.go @@ -89,10 +89,10 @@ func (c *stakingPrecompile) RequiredGas( // otherwise charge similar to a regular staking tx if migrationMsg, ok := stakeMsg.(*stakingTypes.MigrationMsg); ok { // charge per delegation to migrate - return evm.CalculateMigrationGas(evm.StateDB, + return evm.Context.CalculateMigrationGas(evm.StateDB, migrationMsg, - evm.ChainConfig().IsS3(evm.EpochNumber), - evm.ChainConfig().IsIstanbul(evm.EpochNumber), + evm.ChainConfig().IsS3(evm.Context.EpochNumber), + evm.ChainConfig().IsIstanbul(evm.Context.EpochNumber), ) } else if encoded, err := rlp.EncodeToBytes(stakeMsg); err == nil { payload = encoded @@ -101,9 +101,9 @@ func (c *stakingPrecompile) RequiredGas( } if gas, err := IntrinsicGas( payload, - false, // contractCreation - evm.ChainConfig().IsS3(evm.EpochNumber), // homestead - evm.ChainConfig().IsIstanbul(evm.EpochNumber), // istanbul + false, // contractCreation + evm.ChainConfig().IsS3(evm.Context.EpochNumber), // homestead + evm.ChainConfig().IsIstanbul(evm.Context.EpochNumber), // istanbul false, // isValidatorCreation ); err != nil { return 0, err // ErrOutOfGas occurs when gas payable > uint64 @@ -132,7 +132,7 @@ func (c *stakingPrecompile) RunWriteCapable( } if delegate, ok := stakeMsg.(*stakingTypes.Delegate); ok { - if err := evm.Delegate(evm.StateDB, rosettaBlockTracer, delegate); err != nil { + if err := evm.Context.Delegate(evm.StateDB, rosettaBlockTracer, delegate); err != nil { return nil, err } else { evm.StakeMsgs = append(evm.StakeMsgs, delegate) @@ -140,10 +140,10 @@ func (c *stakingPrecompile) RunWriteCapable( } } if undelegate, ok := stakeMsg.(*stakingTypes.Undelegate); ok { - return nil, evm.Undelegate(evm.StateDB, rosettaBlockTracer, undelegate) + return nil, evm.Context.Undelegate(evm.StateDB, rosettaBlockTracer, undelegate) } if collectRewards, ok := stakeMsg.(*stakingTypes.CollectRewards); ok { - return nil, evm.CollectRewards(evm.StateDB, rosettaBlockTracer, collectRewards) + return nil, evm.Context.CollectRewards(evm.StateDB, rosettaBlockTracer, collectRewards) } // Migrate is not supported in precompile and will be done in a batch hard fork //if migrationMsg, ok := stakeMsg.(*stakingTypes.MigrationMsg); ok { @@ -242,7 +242,7 @@ func (c *crossShardXferPrecompile) RunWriteCapable( return nil, err } // validate not a contract (toAddress can still be a contract) - if len(evm.StateDB.GetCode(fromAddress)) > 0 && !evm.IsValidator(evm.StateDB, fromAddress) { + if len(evm.StateDB.GetCode(fromAddress)) > 0 && !evm.Context.IsValidator(evm.StateDB, fromAddress) { return nil, errors.New("cross shard xfer not yet implemented for contracts") } // can't have too many shards @@ -259,10 +259,10 @@ func (c *crossShardXferPrecompile) RunWriteCapable( } // now do the actual transfer // step 1 -> remove funds from the precompile address - if !evm.CanTransfer(evm.StateDB, contract.Address(), value) { + if !evm.Context.CanTransfer(evm.StateDB, contract.Address(), value) { return nil, errors.New("not enough balance received") } - evm.Transfer(evm.StateDB, contract.Address(), toAddress, value, types.SubtractionOnly) + evm.Context.Transfer(evm.StateDB, contract.Address(), toAddress, value, types.SubtractionOnly) // step 2 -> make a cross link // note that the transaction hash is added by state_processor.go to this receipt // and that the receiving shard does not care about the `From` but we use the original @@ -301,5 +301,5 @@ func parseCrossShardXferData(evm *EVM, contract *Contract, input []byte) ( if err != nil { return common.Address{}, common.Address{}, 0, 0, nil, err } - return contract.Caller(), toAddress, evm.ShardID, toShardID, value, nil + return contract.Caller(), toAddress, evm.Context.ShardID, toShardID, value, nil } diff --git a/core/vm/contracts_write_test.go b/core/vm/contracts_write_test.go index 479bf420fc..1331956f68 100644 --- a/core/vm/contracts_write_test.go +++ b/core/vm/contracts_write_test.go @@ -92,7 +92,7 @@ func testWriteCapablePrecompile(test writeCapablePrecompileTest, t *testing.T, e } func testStakingPrecompile(test writeCapablePrecompileTest, t *testing.T) { - var env = NewEVM(Context{CollectRewards: CollectRewardsFn(), + var env = NewEVM(BlockContext{CollectRewards: CollectRewardsFn(), Delegate: DelegateFn(), Undelegate: UndelegateFn(), CreateValidator: CreateValidatorFn(), @@ -100,7 +100,7 @@ func testStakingPrecompile(test writeCapablePrecompileTest, t *testing.T) { ShardID: 0, //MigrateDelegations: MigrateDelegationsFn(), CalculateMigrationGas: CalculateMigrationGasFn(), - }, nil, params.TestChainConfig, Config{}) + }, TxContext{}, nil, params.TestChainConfig, Config{}) p := &stakingPrecompile{} testWriteCapablePrecompile(test, t, env, p) } @@ -239,13 +239,13 @@ func testCrossShardXferPrecompile(test writeCapablePrecompileTest, t *testing.T) if err != nil { t.Fatalf("Error while initializing state %s", err) } - var env = NewEVM(Context{ + var env = NewEVM(BlockContext{ NumShards: 4, Transfer: transfer, CanTransfer: func(_ StateDB, _ common.Address, _ *big.Int) bool { return true }, - }, state, params.TestChainConfig, Config{}) + }, TxContext{}, state, params.TestChainConfig, Config{}) p := &crossShardXferPrecompile{} testWriteCapablePrecompile(test, t, env, p) } diff --git a/core/vm/eips.go b/core/vm/eips.go index 3274c9fee7..6a7a00d9d3 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -18,31 +18,45 @@ package vm import ( "fmt" - "math/big" + "sort" - "github.com/ethereum/go-ethereum/common" - "github.com/harmony-one/harmony/internal/params" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" ) +var activators = map[int]func(*JumpTable){ + 2929: enable2929, + 2200: enable2200, + 1884: enable1884, + 1344: enable1344, + 2315: enable2315, +} + // EnableEIP enables the given EIP on the config. // This operation writes in-place, and callers need to ensure that the globally // defined jump tables are not polluted. func EnableEIP(eipNum int, jt *JumpTable) error { - switch eipNum { - case 2200: - enable2200(jt) - case 1884: - enable1884(jt) - case 1344: - enable1344(jt) - case 1153: - enable1153(jt) - default: + enablerFn, ok := activators[eipNum] + if !ok { return fmt.Errorf("undefined eip %d", eipNum) } + enablerFn(jt) return nil } +func ValidEip(eipNum int) bool { + _, ok := activators[eipNum] + return ok +} +func ActivateableEips() []string { + var nums []string + for k := range activators { + nums = append(nums, fmt.Sprintf("%d", k)) + } + sort.Strings(nums) + return nums +} + // enable1884 applies EIP-1884 to the given jump table: // - Increase cost of BALANCE to 700 // - Increase cost of EXTCODEHASH to 700 @@ -50,23 +64,22 @@ func EnableEIP(eipNum int, jt *JumpTable) error { // - Define SELFBALANCE, with cost GasFastStep (5) func enable1884(jt *JumpTable) { // Gas cost changes + jt[SLOAD].constantGas = params.SloadGasEIP1884 jt[BALANCE].constantGas = params.BalanceGasEIP1884 jt[EXTCODEHASH].constantGas = params.ExtcodeHashGasEIP1884 - jt[SLOAD].constantGas = params.SloadGasEIP1884 // New opcode - jt[SELFBALANCE] = operation{ + jt[SELFBALANCE] = &operation{ execute: opSelfBalance, constantGas: GasFastStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, } } -func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - balance := interpreter.intPool.get().Set(interpreter.evm.StateDB.GetBalance(contract.Address())) - stack.push(balance) +func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + balance, _ := uint256.FromBig(interpreter.evm.StateDB.GetBalance(callContext.contract.Address())) + callContext.stack.push(balance) return nil, nil } @@ -74,80 +87,117 @@ func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, // - Adds an opcode that returns the current chain’s EIP-155 unique identifier func enable1344(jt *JumpTable) { // New opcode - jt[CHAINID] = operation{ + jt[CHAINID] = &operation{ execute: opChainID, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, } } // opChainID implements CHAINID opcode -func opChainID(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - var chainID *big.Int - if interpreter.evm.chainConfig.IsChainIdFix(interpreter.evm.EpochNumber) { - chainID = interpreter.intPool.get().Set(interpreter.evm.chainConfig.EthCompatibleChainID) - } else { - chainID = interpreter.intPool.get().Set(interpreter.evm.chainConfig.ChainID) - } - stack.push(chainID) +func opChainID(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID) + callContext.stack.push(chainId) return nil, nil } // enable2200 applies EIP-2200 (Rebalance net-metered SSTORE) func enable2200(jt *JumpTable) { + jt[SLOAD].constantGas = params.SloadGasEIP2200 jt[SSTORE].dynamicGas = gasSStoreEIP2200 } -// enable1153 applies EIP-1153 "Transient Storage" -// - Adds TLOAD that reads from transient storage -// - Adds TSTORE that writes to transient storage -func enable1153(jt *JumpTable) { - jt[TLOAD] = operation{ - execute: opTload, - constantGas: params.WarmStorageReadCostEIP2929, - minStack: minStack(1, 1), - maxStack: maxStack(1, 1), +// enable2315 applies EIP-2315 (Simple Subroutines) +// - Adds opcodes that jump to and return from subroutines +func enable2315(jt *JumpTable) { + // New opcode + jt[BEGINSUB] = &operation{ + execute: opBeginSub, + constantGas: GasQuickStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), } - - jt[TSTORE] = operation{ - execute: opTstore, - constantGas: params.WarmStorageReadCostEIP2929, - minStack: minStack(2, 0), - maxStack: maxStack(2, 0), + // New opcode + jt[JUMPSUB] = &operation{ + execute: opJumpSub, + constantGas: GasSlowStep, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + jumps: true, + } + // New opcode + jt[RETURNSUB] = &operation{ + execute: opReturnSub, + constantGas: GasFastStep, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + jumps: true, } } -// opTload implements TLOAD opcode -func opTload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - loc := stack.peek() +// enable2929 enables "EIP-2929: Gas cost increases for state access opcodes" +// https://eips.ethereum.org/EIPS/eip-2929 +func enable2929(jt *JumpTable) { + jt[SSTORE].dynamicGas = gasSStoreEIP2929 - keyBuf := make([]byte, 32) - key := common.BytesToHash(loc.FillBytes(keyBuf)) + jt[SLOAD].constantGas = 0 + jt[SLOAD].dynamicGas = gasSLoadEIP2929 - val := interpreter.evm.StateDB.GetTransientState(contract.Address(), key) - valBuf := make([]byte, 32) - val.Big().FillBytes(valBuf) + jt[EXTCODECOPY].constantGas = WarmStorageReadCostEIP2929 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929 - loc.SetBytes(valBuf) - return nil, nil -} + jt[EXTCODESIZE].constantGas = WarmStorageReadCostEIP2929 + jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck -// opTstore implements TSTORE opcode -func opTstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - if interpreter.readOnly { - return nil, errWriteProtection - } + jt[EXTCODEHASH].constantGas = WarmStorageReadCostEIP2929 + jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck + + jt[BALANCE].constantGas = WarmStorageReadCostEIP2929 + jt[BALANCE].dynamicGas = gasEip2929AccountCheck + + jt[CALL].constantGas = WarmStorageReadCostEIP2929 + jt[CALL].dynamicGas = gasCallEIP2929 + + jt[CALLCODE].constantGas = WarmStorageReadCostEIP2929 + jt[CALLCODE].dynamicGas = gasCallCodeEIP2929 + + jt[STATICCALL].constantGas = WarmStorageReadCostEIP2929 + jt[STATICCALL].dynamicGas = gasStaticCallEIP2929 + + jt[DELEGATECALL].constantGas = WarmStorageReadCostEIP2929 + jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929 + + // This was previously part of the dynamic cost, but we're using it as a constantGas + // factor here + jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929 +} - keyBuf := make([]byte, 32) - stack.pop().FillBytes(keyBuf) - key := common.Hash(keyBuf) +// enable3529 enabled "EIP-3529: Reduction in refunds": +// - Removes refunds for selfdestructs +// - Reduces refunds for SSTORE +// - Reduces max refunds to 20% gas +func enable3529(jt *JumpTable) { + jt[SSTORE].dynamicGas = gasSStoreEIP3529 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP3529 +} - valBuf := make([]byte, 32) - stack.pop().FillBytes(valBuf) - val := common.Hash(valBuf) +// enable3198 applies EIP-3198 (BASEFEE Opcode) +// - Adds an opcode that returns the current block's base fee. +func enable3198(jt *JumpTable) { + // New opcode + jt[BASEFEE] = &operation{ + execute: opBaseFee, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} - interpreter.evm.StateDB.SetTransientState(contract.Address(), key, val) +// opBaseFee implements BASEFEE opcode +func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + baseFee, _ := uint256.FromBig(interpreter.evm.Context.BaseFee) + scope.Stack.push(baseFee) return nil, nil } diff --git a/core/vm/errors.go b/core/vm/errors.go index 7f88f324ea..9086561342 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -16,15 +16,57 @@ package vm -import "errors" +import ( + "errors" + "fmt" +) -// List execution errors +// List evm execution errors var ( + // ErrInvalidSubroutineEntry means that a BEGINSUB was reached via iteration, + // as opposed to from a JUMPSUB instruction + ErrInvalidSubroutineEntry = errors.New("invalid subroutine entry") ErrOutOfGas = errors.New("out of gas") ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") ErrDepth = errors.New("max call depth exceeded") - ErrTraceLimitReached = errors.New("the number of logs reached the specified limit") ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrContractAddressCollision = errors.New("contract address collision") + ErrExecutionReverted = errors.New("execution reverted") + ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") + ErrInvalidJump = errors.New("invalid jump destination") + ErrWriteProtection = errors.New("write protection") + ErrReturnDataOutOfBounds = errors.New("return data out of bounds") + ErrGasUintOverflow = errors.New("gas uint64 overflow") + ErrInvalidRetsub = errors.New("invalid retsub") + ErrReturnStackExceeded = errors.New("return stack limit reached") ErrNoCompatibleInterpreter = errors.New("no compatible interpreter") ) + +// ErrStackUnderflow wraps an evm error when the items on the stack less +// than the minimal requirement. +type ErrStackUnderflow struct { + stackLen int + required int +} + +func (e *ErrStackUnderflow) Error() string { + return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required) +} + +// ErrStackOverflow wraps an evm error when the items on the stack exceeds +// the maximum allowance. +type ErrStackOverflow struct { + stackLen int + limit int +} + +func (e *ErrStackOverflow) Error() string { + return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit) +} + +// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered. +type ErrInvalidOpCode struct { + opcode OpCode +} + +func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } diff --git a/core/vm/evm.go b/core/vm/evm.go index 55f6f8c93d..3f83b22f05 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -26,6 +26,7 @@ import ( "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/internal/params" stakingTypes "github.com/harmony-one/harmony/staking/types" + "github.com/holiman/uint256" ) // emptyCodeHash is used by create to ensure deployment is disallowed to already @@ -71,7 +72,7 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err precompiles := PrecompiledContractsHomestead // assign empty write capable precompiles till they are available in the fork var writeCapablePrecompiles map[common.Address]WriteCapablePrecompiledContract - if evm.ChainConfig().IsS3(evm.EpochNumber) { + if evm.ChainConfig().IsS3(evm.Context.EpochNumber) { precompiles = PrecompiledContractsByzantium } if evm.chainRules.IsIstanbul { @@ -94,13 +95,13 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err if _, ok := p.(*vrf); ok { if evm.chainRules.IsPrevVRF { requestedBlockNum := big.NewInt(0).SetBytes(input) - minBlockNum := big.NewInt(0).Sub(evm.BlockNumber, common.Big257) + minBlockNum := big.NewInt(0).Sub(evm.Context.BlockNumber, common.Big257) - if requestedBlockNum.Cmp(evm.BlockNumber) == 0 { + if requestedBlockNum.Cmp(evm.Context.BlockNumber) == 0 { input = evm.Context.VRF.Bytes() - } else if requestedBlockNum.Cmp(minBlockNum) > 0 && requestedBlockNum.Cmp(evm.BlockNumber) < 0 { + } else if requestedBlockNum.Cmp(minBlockNum) > 0 && requestedBlockNum.Cmp(evm.Context.BlockNumber) < 0 { // requested block number is in range - input = evm.GetVRF(requestedBlockNum.Uint64()).Bytes() + input = evm.Context.GetVRF(requestedBlockNum.Uint64()).Bytes() } else { // else default to the current block's VRF input = evm.Context.VRF.Bytes() @@ -110,7 +111,7 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err input = evm.Context.VRF.Bytes() } } else if _, ok := p.(*epoch); ok { - input = evm.EpochNumber.Bytes() + input = evm.Context.EpochNumber.Bytes() } return RunPrecompiledContract(p, input, contract) } @@ -131,7 +132,7 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err evm.interpreter = interpreter } - if evm.ChainConfig().IsDataCopyFixEpoch(evm.EpochNumber) { + if evm.ChainConfig().IsDataCopyFixEpoch(evm.Context.EpochNumber) { contract.WithDataCopyFix = true } return interpreter.Run(contract, input, readOnly) @@ -183,6 +184,53 @@ type Context struct { NumShards uint32 // Used by cross shard transfer precompile } +// BlockContext provides the EVM with auxiliary information. Once provided +// it shouldn't be modified. +type BlockContext struct { + // CanTransfer returns whether the account contains + // sufficient ether to transfer the value + CanTransfer CanTransferFunc + // Transfer transfers ether from one account to the other + Transfer TransferFunc + // GetHash returns the hash corresponding to n + GetHash GetHashFunc + // GetVRF returns the VRF corresponding to n + GetVRF GetVRFFunc + + // IsValidator determines whether the address corresponds to a validator or a smart contract + // true: is a validator address; false: is smart contract address + IsValidator IsValidatorFunc + + // Block information + Coinbase common.Address // Provides information for COINBASE + GasLimit uint64 // Provides information for GASLIMIT + BlockNumber *big.Int // Provides information for NUMBER + EpochNumber *big.Int // Provides information for EPOCH + Time *big.Int // Provides information for TIME + VRF common.Hash // Provides information for VRF + + ShardID uint32 // Used by staking and cross shard transfer precompile + NumShards uint32 // Used by cross shard transfer precompile + + CreateValidator CreateValidatorFunc + EditValidator EditValidatorFunc + Delegate DelegateFunc + Undelegate UndelegateFunc + CollectRewards CollectRewardsFunc + CalculateMigrationGas CalculateMigrationGasFunc + + TxType types.TransactionType +} + +// TxContext provides the EVM with information about a transaction. +// All fields can change between transactions. +type TxContext struct { + // Message information + Origin common.Address // Provides information for ORIGIN + GasPrice *big.Int // Provides information for GASPRICE + +} + // EVM is the Ethereum Virtual Machine base object and provides // the necessary tools to run a contract on the given state with // the provided context. It should be noted that any error @@ -194,8 +242,9 @@ type Context struct { // The EVM should never be reused and is not thread safe. type EVM struct { // Context provides auxiliary blockchain related information - Context - // DB gives access to the underlying state + Context BlockContext + TxContext + // StateDB gives access to the underlying state StateDB StateDB // Depth is the current call stack depth int @@ -218,6 +267,7 @@ type EVM struct { // available gas is calculated in gasCall* according to the 63/64 rule and later // applied in opCall*. callGasTemp uint64 + // stored temporarily by stakingPrecompile and cleared immediately after return // (although the EVM object itself is ephemeral) StakeMsgs []stakingTypes.StakeMsg @@ -226,13 +276,14 @@ type EVM struct { // NewEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. -func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { +func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { evm := &EVM{ - Context: ctx, + Context: blockCtx, + TxContext: txCtx, StateDB: statedb, vmConfig: vmConfig, chainConfig: chainConfig, - chainRules: chainConfig.Rules(ctx.EpochNumber), + chainRules: chainConfig.Rules(blockCtx.EpochNumber), interpreters: make([]Interpreter, 0, 1), } @@ -276,6 +327,15 @@ func (evm *EVM) Interpreter() Interpreter { return evm.interpreter } +// SetTxContext resets the EVM with a new transaction context. +// This is not threadsafe and should only be done very cautiously. +func (evm *EVM) SetTxContext(txCtx TxContext) { + //if evm.chainRules.IsEIP4762 { + // txCtx.AccessEvents = state.NewAccessEvents(evm.StateDB.PointCache()) + //} + evm.TxContext = txCtx +} + // Call executes the contract associated with the addr with the given input as // parameters. It also handles any necessary value transfer required and takes // the necessary steps to create accounts and reverses the state in case of an @@ -304,7 +364,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if !evm.StateDB.Exist(addr) && txType != types.SubtractionOnly { precompiles := PrecompiledContractsHomestead var writeCapablePrecompiles map[common.Address]WriteCapablePrecompiledContract - if evm.ChainConfig().IsS3(evm.EpochNumber) { + if evm.ChainConfig().IsS3(evm.Context.EpochNumber) { precompiles = PrecompiledContractsByzantium } if evm.chainRules.IsIstanbul { @@ -323,7 +383,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if evm.chainRules.IsCrossShardXferPrecompile { writeCapablePrecompiles = WriteCapablePrecompiledContractsCrossXfer } - if (len(writeCapablePrecompiles) == 0 || writeCapablePrecompiles[addr] == nil) && precompiles[addr] == nil && evm.ChainConfig().IsS3(evm.EpochNumber) && value.Sign() == 0 { + if (len(writeCapablePrecompiles) == 0 || writeCapablePrecompiles[addr] == nil) && precompiles[addr] == nil && evm.ChainConfig().IsS3(evm.Context.EpochNumber) && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) @@ -333,7 +393,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } evm.StateDB.CreateAccount(addr) } - evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value, txType) + evm.Context.Transfer(evm.StateDB, caller.Address(), to.Address(), value, txType) codeHash := evm.StateDB.GetCodeHash(addr) code := evm.StateDB.GetCode(addr) @@ -390,7 +450,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, return nil, gas, ErrDepth } // Fail if we're trying to transfer more than the available balance - if !evm.CanTransfer(evm.StateDB, caller.Address(), value) { + if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } @@ -508,7 +568,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.depth > int(params.CallCreateDepth) { return nil, common.Address{}, gas, ErrDepth } - if !evm.CanTransfer(evm.StateDB, caller.Address(), value) { + if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance } nonce := evm.StateDB.GetNonce(caller.Address()) @@ -522,14 +582,13 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Create a new account on the state snapshot := evm.StateDB.Snapshot() evm.StateDB.CreateAccount(address) - if evm.ChainConfig().IsEIP155(evm.EpochNumber) { + if evm.chainRules.IsS3 { evm.StateDB.SetNonce(address, 1) } - evm.Transfer(evm.StateDB, caller.Address(), address, value, types.SameShardTx) + evm.Context.Transfer(evm.StateDB, caller.Address(), address, value, types.SameShardTx) - // initialise a new contract and set the code that is to be used by the - // EVM. The contract is a scoped environment for this execution context - // only. + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(address), value, gas) contract.SetCodeOptionalHash(&address, codeAndHash) @@ -545,7 +604,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ret, err := run(evm, contract, nil, false) // check whether the max code size has been exceeded - maxCodeSizeExceeded := evm.ChainConfig().IsEIP155(evm.EpochNumber) && len(ret) > params.MaxCodeSize + maxCodeSizeExceeded := evm.ChainConfig().IsEIP155(evm.Context.EpochNumber) && len(ret) > params.MaxCodeSize // if the contract creation ran successfully and no errors were returned // calculate the gas required to store the code. If the code could not // be stored due to not enough gas set an error and let it be handled @@ -562,7 +621,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in homestead this also counts for code storage gas errors. - if maxCodeSizeExceeded || (err != nil && (evm.ChainConfig().IsS3(evm.EpochNumber) || err != ErrCodeStoreOutOfGas)) { + if maxCodeSizeExceeded || (err != nil && (evm.ChainConfig().IsS3(evm.Context.EpochNumber) || err != ErrCodeStoreOutOfGas)) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { contract.UseGas(contract.Gas) @@ -589,9 +648,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I // // The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. -func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { codeAndHash := &codeAndHash{code: code} - contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes()) + contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) return evm.create(caller, codeAndHash, gas, endowment, contractAddr) } diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index 10e50bdbf2..f3c0f179af 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -11,9 +11,9 @@ import ( // this test is here so we can cover the input = epoch.bytes() line as well func TestEpochPrecompile(t *testing.T) { targetEpoch := big.NewInt(1) - evm := NewEVM(Context{ + evm := NewEVM(BlockContext{ EpochNumber: targetEpoch, - }, nil, params.TestChainConfig, Config{}) + }, TxContext{}, nil, params.TestChainConfig, Config{}) input := []byte{} precompileAddr := common.BytesToAddress([]byte{251}) contract := Contract{ diff --git a/core/vm/gas.go b/core/vm/gas.go index 1b70753407..54a039326e 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -18,9 +18,9 @@ package vm import ( "math" - "math/big" "github.com/harmony-one/harmony/internal/params" + "github.com/holiman/uint256" ) // Gas costs @@ -33,23 +33,23 @@ const ( GasExtStep uint64 = 20 ) -// calcGas returns the actual gas cost of the call. +// callGas returns the actual gas cost of the call. // // The cost of gas was changed during the homestead price change HF. // As part of EIP 150 (TangerineWhistle), the returned gas is gas - base * 63 / 64. -func callGas(isEip150 bool, availableGas, base uint64, callCost *big.Int) (uint64, error) { +func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (uint64, error) { if isEip150 { availableGas = availableGas - base gas := availableGas - availableGas/64 // If the bit length exceeds 64 bit we know that the newly calculated "gas" for EIP150 - // is smaller than the requested amount. Therefor we return the new gas instead + // is smaller than the requested amount. Therefore we return the new gas instead // of returning an error. if !callCost.IsUint64() || gas < callCost.Uint64() { return gas, nil } } if !callCost.IsUint64() { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return callCost.Uint64(), nil diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 892feaa786..71d8ef30ee 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -70,17 +70,17 @@ func memoryCopierGas(stackpos int) gasFunc { return 0, err } // And gas for copying data, charged per word at param.CopyGas - words, overflow := bigUint64(stack.Back(stackpos)) + words, overflow := stack.Back(stackpos).Uint64WithOverflow() if overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, words); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -96,7 +96,7 @@ var ( func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) + current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) ) // The legacy gas metering only takes into consideration the current state // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) @@ -131,11 +131,11 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // 2.2.2. If original value equals new value (this storage slot is reset) // 2.2.2.1. If original value is 0, add 19800 gas to refund counter. // 2.2.2.2. Otherwise, add 4800 gas to refund counter. - value := common.BigToHash(y) + value := common.Hash(y.Bytes32()) if current == value { // noop (1) return params.NetSstoreNoopGas, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x)) + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.NetSstoreInitGas, nil @@ -183,45 +183,45 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) + current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) ) - value := common.BigToHash(y) + value := common.Hash(y.Bytes32()) if current == value { // noop (1) - return params.SstoreNoopGasEIP2200, nil + return params.SloadGasEIP2200, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x)) + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return params.SstoreInitGasEIP2200, nil + return params.SstoreSetGasEIP2200, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) - evm.StateDB.AddRefund(params.SstoreClearRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) } - return params.SstoreCleanGasEIP2200, nil // write existing slot (2.1.2) + return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) } if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) - evm.StateDB.SubRefund(params.SstoreClearRefundEIP2200) + evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200) } else if value == (common.Hash{}) { // delete slot (2.2.1.2) - evm.StateDB.AddRefund(params.SstoreClearRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) } } if original == value { if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) - evm.StateDB.AddRefund(params.SstoreInitRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) } else { // reset to original existing slot (2.2.2.2) - evm.StateDB.AddRefund(params.SstoreCleanRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) } } - return params.SstoreDirtyGasEIP2200, nil // dirty update (2.2) + return params.SloadGasEIP2200, nil // dirty update (2.2) } func makeGasLog(n uint64) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - requestedSize, overflow := bigUint64(stack.Back(1)) + requestedSize, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } gas, err := memoryGasCost(mem, memorySize) @@ -230,18 +230,18 @@ func makeGasLog(n uint64) gasFunc { } if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } var memorySizeGas uint64 if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -252,15 +252,15 @@ func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if err != nil { return 0, err } - wordGas, overflow := bigUint64(stack.Back(1)) + wordGas, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -286,15 +286,15 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS if err != nil { return 0, err } - wordGas, overflow := bigUint64(stack.Back(2)) + wordGas, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -328,8 +328,8 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( gas uint64 - transfersValue = stack.Back(2).Sign() != 0 - address = common.BigToAddress(stack.Back(1)) + transfersValue = !stack.Back(2).IsZero() + address = common.Address(stack.Back(1).Bytes20()) ) if evm.chainRules.IsS3 { if transfersValue && evm.StateDB.Empty(address) { @@ -347,7 +347,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } var overflow bool if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } evm.callGasTemp, err = callGas(evm.chainRules.IsS3, contract.Gas, gas, stack.Back(0)) @@ -355,7 +355,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, errGasUintOverflow + return 0, ErrGasUintOverflow } return gas, nil } @@ -422,7 +422,7 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // EIP150 homestead gas reprice fork: if evm.chainRules.IsS3 { gas = params.SelfdestructGasEIP150 - var address = common.BigToAddress(stack.Back(0)) + var address = common.Address(stack.Back(0).Bytes20()) if evm.chainRules.IsS3 { // if empty and transfers value diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 65973e832c..2e4902bab0 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -89,12 +89,12 @@ func TestEIP2200(t *testing.T) { statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original})) statedb.Finalise(true) // Push the state into the "original" slot - vmctx := Context{ + vmctx := BlockContext{ CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true }, Transfer: func(StateDB, common.Address, common.Address, *big.Int, types.TransactionType) {}, IsValidator: func(StateDB, common.Address) bool { return false }, } - vmenv := NewEVM(vmctx, statedb, params.AllProtocolChanges, Config{ExtraEips: []int{2200}}) + vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllProtocolChanges, Config{ExtraEips: []int{2200}}) _, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int)) if err != tt.failure { diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go index c87cc8fa6b..4abbd0b91e 100644 --- a/core/vm/gen_structlog.go +++ b/core/vm/gen_structlog.go @@ -25,6 +25,8 @@ func (s StructLog) MarshalJSON() ([]byte, error) { Memory hexutil.Bytes `json:"memory"` MemorySize int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` + ReturnStack []math.HexOrDecimal64 `json:"returnStack"` + ReturnData hexutil.Bytes `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` @@ -50,6 +52,13 @@ func (s StructLog) MarshalJSON() ([]byte, error) { enc.Stack[k] = (*math.HexOrDecimal256)(v) } } + if s.ReturnStack != nil { + enc.ReturnStack = make([]math.HexOrDecimal64, len(s.ReturnStack)) + for k, v := range s.ReturnStack { + enc.ReturnStack[k] = math.HexOrDecimal64(v) + } + } + enc.ReturnData = s.ReturnData enc.Storage = s.Storage enc.Depth = s.Depth enc.RefundCounter = s.RefundCounter @@ -74,6 +83,8 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { Memory *hexutil.Bytes `json:"memory"` MemorySize *int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` + ReturnStack []math.HexOrDecimal64 `json:"returnStack"` + ReturnData *hexutil.Bytes `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` RefundCounter *uint64 `json:"refund"` @@ -116,6 +127,15 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { s.Stack[k] = (*big.Int)(v) } } + if dec.ReturnStack != nil { + s.ReturnStack = make([]uint32, len(dec.ReturnStack)) + for k, v := range dec.ReturnStack { + s.ReturnStack[k] = uint32(v) + } + } + if dec.ReturnData != nil { + s.ReturnData = *dec.ReturnData + } if dec.Storage != nil { s.Storage = dec.Storage } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 091ba28ff6..e00250a0af 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -22,9 +22,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" - "github.com/harmony-one/harmony/core/types" - "github.com/harmony-one/harmony/internal/params" - "github.com/harmony-one/harmony/shard" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + coreTypes "github.com/harmony-one/harmony/core/types" + "github.com/holiman/uint256" "golang.org/x/crypto/sha3" ) @@ -33,359 +34,221 @@ var ( tt255 = math.BigPow(2, 255) errWriteProtection = errors.New("evm: write protection") errReturnDataOutOfBounds = errors.New("evm: return data out of bounds") - ErrExecutionReverted = errors.New("evm: execution reverted") - errMaxCodeSizeExceeded = errors.New("evm: max code size exceeded") - errInvalidJump = errors.New("evm: invalid jump destination") + //ErrExecutionReverted = errors.New("evm: execution reverted") + errMaxCodeSizeExceeded = errors.New("evm: max code size exceeded") + errInvalidJump = errors.New("evm: invalid jump destination") ) -func opAdd(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - math.U256(y.Add(x, y)) - - interpreter.intPool.put(x) +func opAdd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Add(&x, y) return nil, nil } -func opSub(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - math.U256(y.Sub(x, y)) - - interpreter.intPool.put(x) +func opSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Sub(&x, y) return nil, nil } -func opMul(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.pop() - stack.push(math.U256(x.Mul(x, y))) - - interpreter.intPool.put(y) - +func opMul(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Mul(&x, y) return nil, nil } -func opDiv(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - if y.Sign() != 0 { - math.U256(y.Div(x, y)) - } else { - y.SetUint64(0) - } - interpreter.intPool.put(x) +func opDiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Div(&x, y) return nil, nil } -func opSdiv(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := math.S256(stack.pop()), math.S256(stack.pop()) - res := interpreter.intPool.getZero() - - if y.Sign() == 0 || x.Sign() == 0 { - stack.push(res) - } else { - if x.Sign() != y.Sign() { - res.Div(x.Abs(x), y.Abs(y)) - res.Neg(res) - } else { - res.Div(x.Abs(x), y.Abs(y)) - } - stack.push(math.U256(res)) - } - interpreter.intPool.put(x, y) +func opSdiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.SDiv(&x, y) return nil, nil } -func opMod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.pop() - if y.Sign() == 0 { - stack.push(x.SetUint64(0)) - } else { - stack.push(math.U256(x.Mod(x, y))) - } - interpreter.intPool.put(y) +func opMod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Mod(&x, y) return nil, nil } -func opSmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := math.S256(stack.pop()), math.S256(stack.pop()) - res := interpreter.intPool.getZero() - - if y.Sign() == 0 { - stack.push(res) - } else { - if x.Sign() < 0 { - res.Mod(x.Abs(x), y.Abs(y)) - res.Neg(res) - } else { - res.Mod(x.Abs(x), y.Abs(y)) - } - stack.push(math.U256(res)) - } - interpreter.intPool.put(x, y) +func opSmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.SMod(&x, y) return nil, nil } -func opExp(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - base, exponent := stack.pop(), stack.pop() - // some shortcuts - cmpToOne := exponent.Cmp(big1) - if cmpToOne < 0 { // Exponent is zero - // x ^ 0 == 1 - stack.push(base.SetUint64(1)) - } else if base.Sign() == 0 { - // 0 ^ y, if y != 0, == 0 - stack.push(base.SetUint64(0)) - } else if cmpToOne == 0 { // Exponent is one - // x ^ 1 == x - stack.push(base) - } else { - stack.push(math.Exp(base, exponent)) - interpreter.intPool.put(base) - } - interpreter.intPool.put(exponent) +func opExp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + base, exponent := callContext.stack.pop(), callContext.stack.peek() + exponent.Exp(&base, exponent) return nil, nil } -func opSignExtend(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - back := stack.pop() - if back.Cmp(big.NewInt(31)) < 0 { - bit := uint(back.Uint64()*8 + 7) - num := stack.pop() - mask := back.Lsh(common.Big1, bit) - mask.Sub(mask, common.Big1) - if num.Bit(int(bit)) > 0 { - num.Or(num, mask.Not(mask)) - } else { - num.And(num, mask) - } - - stack.push(math.U256(num)) - } - - interpreter.intPool.put(back) +func opSignExtend(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + back, num := callContext.stack.pop(), callContext.stack.peek() + num.ExtendSign(num, &back) return nil, nil } -func opNot(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x := stack.peek() - math.U256(x.Not(x)) +func opNot(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x := callContext.stack.peek() + x.Not(x) return nil, nil } -func opLt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - if x.Cmp(y) < 0 { - y.SetUint64(1) +func opLt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + if x.Lt(y) { + y.SetOne() } else { - y.SetUint64(0) + y.Clear() } - interpreter.intPool.put(x) return nil, nil } -func opGt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - if x.Cmp(y) > 0 { - y.SetUint64(1) +func opGt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + if x.Gt(y) { + y.SetOne() } else { - y.SetUint64(0) + y.Clear() } - interpreter.intPool.put(x) return nil, nil } -func opSlt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - - xSign := x.Cmp(tt255) - ySign := y.Cmp(tt255) - - switch { - case xSign >= 0 && ySign < 0: - y.SetUint64(1) - - case xSign < 0 && ySign >= 0: - y.SetUint64(0) - - default: - if x.Cmp(y) < 0 { - y.SetUint64(1) - } else { - y.SetUint64(0) - } +func opSlt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + if x.Slt(y) { + y.SetOne() + } else { + y.Clear() } - interpreter.intPool.put(x) return nil, nil } -func opSgt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - - xSign := x.Cmp(tt255) - ySign := y.Cmp(tt255) - - switch { - case xSign >= 0 && ySign < 0: - y.SetUint64(0) - - case xSign < 0 && ySign >= 0: - y.SetUint64(1) - - default: - if x.Cmp(y) > 0 { - y.SetUint64(1) - } else { - y.SetUint64(0) - } +func opSgt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + if x.Sgt(y) { + y.SetOne() + } else { + y.Clear() } - interpreter.intPool.put(x) return nil, nil } -func opEq(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - if x.Cmp(y) == 0 { - y.SetUint64(1) +func opEq(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + if x.Eq(y) { + y.SetOne() } else { - y.SetUint64(0) + y.Clear() } - interpreter.intPool.put(x) return nil, nil } -func opIszero(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x := stack.peek() - if x.Sign() > 0 { - x.SetUint64(0) +func opIszero(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x := callContext.stack.peek() + if x.IsZero() { + x.SetOne() } else { - x.SetUint64(1) + x.Clear() } return nil, nil } -func opAnd(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.pop() - stack.push(x.And(x, y)) - - interpreter.intPool.put(y) +func opAnd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.And(&x, y) return nil, nil } -func opOr(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - y.Or(x, y) - - interpreter.intPool.put(x) +func opOr(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Or(&x, y) return nil, nil } -func opXor(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() - y.Xor(x, y) - - interpreter.intPool.put(x) +func opXor(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() + y.Xor(&x, y) return nil, nil } -func opByte(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - th, val := stack.pop(), stack.peek() - if th.Cmp(common.Big32) < 0 { - b := math.Byte(val, 32, int(th.Int64())) - val.SetUint64(uint64(b)) - } else { - val.SetUint64(0) - } - interpreter.intPool.put(th) +func opByte(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + th, val := callContext.stack.pop(), callContext.stack.peek() + val.Byte(&th) return nil, nil } -func opAddmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y, z := stack.pop(), stack.pop(), stack.pop() - if z.Cmp(bigZero) > 0 { - x.Add(x, y) - x.Mod(x, z) - stack.push(math.U256(x)) +func opAddmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.peek() + if z.IsZero() { + z.Clear() } else { - stack.push(x.SetUint64(0)) + z.AddMod(&x, &y, z) } - interpreter.intPool.put(y, z) return nil, nil } -func opMulmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y, z := stack.pop(), stack.pop(), stack.pop() - if z.Cmp(bigZero) > 0 { - x.Mul(x, y) - x.Mod(x, z) - stack.push(math.U256(x)) - } else { - stack.push(x.SetUint64(0)) - } - interpreter.intPool.put(y, z) +func opMulmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.peek() + z.MulMod(&x, &y, z) return nil, nil } // opSHL implements Shift Left // The SHL instruction (shift left) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the left by arg1 number of bits. -func opSHL(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opSHL(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards - shift, value := math.U256(stack.pop()), math.U256(stack.peek()) - defer interpreter.intPool.put(shift) // First operand back into the pool - - if shift.Cmp(common.Big256) >= 0 { - value.SetUint64(0) - return nil, nil + shift, value := callContext.stack.pop(), callContext.stack.peek() + if shift.LtUint64(256) { + value.Lsh(value, uint(shift.Uint64())) + } else { + value.Clear() } - n := uint(shift.Uint64()) - math.U256(value.Lsh(value, n)) - return nil, nil } // opSHR implements Logical Shift Right // The SHR instruction (logical shift right) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill. -func opSHR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opSHR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards - shift, value := math.U256(stack.pop()), math.U256(stack.peek()) - defer interpreter.intPool.put(shift) // First operand back into the pool - - if shift.Cmp(common.Big256) >= 0 { - value.SetUint64(0) - return nil, nil + shift, value := callContext.stack.pop(), callContext.stack.peek() + if shift.LtUint64(256) { + value.Rsh(value, uint(shift.Uint64())) + } else { + value.Clear() } - n := uint(shift.Uint64()) - math.U256(value.Rsh(value, n)) - return nil, nil } // opSAR implements Arithmetic Shift Right // The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension. -func opSAR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - // Note, S256 returns (potentially) a new bigint, so we're popping, not peeking this one - shift, value := math.U256(stack.pop()), math.S256(stack.pop()) - defer interpreter.intPool.put(shift) // First operand back into the pool - - if shift.Cmp(common.Big256) >= 0 { +func opSAR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + shift, value := callContext.stack.pop(), callContext.stack.peek() + if shift.GtUint64(256) { if value.Sign() >= 0 { - value.SetUint64(0) + value.Clear() } else { - value.SetInt64(-1) + // Max negative shift: all bits set + value.SetAllOne() } - stack.push(math.U256(value)) return nil, nil } n := uint(shift.Uint64()) - value.Rsh(value, n) - stack.push(math.U256(value)) - + value.SRsh(value, n) return nil, nil } -func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - offset, size := stack.pop(), stack.pop() - data := memory.GetPtr(offset.Int64(), size.Int64()) +func opSha3(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + offset, size := callContext.stack.pop(), callContext.stack.peek() + data := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) if interpreter.hasher == nil { interpreter.hasher = sha3.NewLegacyKeccak256().(keccakState) @@ -399,141 +262,143 @@ func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory if evm.vmConfig.EnablePreimageRecording { evm.StateDB.AddPreimage(interpreter.hasherBuf, data) } - stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:])) - interpreter.intPool.put(offset, size) + size.SetBytes(interpreter.hasherBuf[:]) return nil, nil } - -func opAddress(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(contract.Address().Bytes())) +func opAddress(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetBytes(callContext.contract.Address().Bytes())) return nil, nil } -func opBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - slot := stack.peek() - slot.Set(interpreter.evm.StateDB.GetBalance(common.BigToAddress(slot))) +func opBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + slot := callContext.stack.peek() + address := common.Address(slot.Bytes20()) + slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) return nil, nil } -func opOrigin(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Origin.Bytes())) +func opOrigin(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Origin.Bytes())) return nil, nil } - -func opCaller(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(contract.Caller().Bytes())) +func opCaller(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetBytes(callContext.contract.Caller().Bytes())) return nil, nil } -func opCallValue(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().Set(contract.value)) +func opCallValue(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v, _ := uint256.FromBig(callContext.contract.value) + callContext.stack.push(v) return nil, nil } -func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(getDataBig(contract.Input, stack.pop(), big32))) +func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x := callContext.stack.peek() + if offset, overflow := x.Uint64WithOverflow(); !overflow { + data := getData(callContext.contract.Input, offset, 32) + x.SetBytes(data) + } else { + x.Clear() + } return nil, nil } -func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetInt64(int64(len(contract.Input)))) +func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(uint64(len(callContext.contract.Input)))) return nil, nil } -func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - memOffset = stack.pop() - dataOffset = stack.pop() - length = stack.pop() + memOffset = callContext.stack.pop() + dataOffset = callContext.stack.pop() + length = callContext.stack.pop() ) - memory.Set(memOffset.Uint64(), length.Uint64(), getDataBig(contract.Input, dataOffset, length)) + dataOffset64, overflow := dataOffset.Uint64WithOverflow() + if overflow { + dataOffset64 = 0xffffffffffffffff + } + // These values are checked for overflow during gas cost calculation + memOffset64 := memOffset.Uint64() + length64 := length.Uint64() + callContext.memory.Set(memOffset64, length64, getData(callContext.contract.Input, dataOffset64, length64)) - interpreter.intPool.put(memOffset, dataOffset, length) return nil, nil } -func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetUint64(uint64(len(interpreter.returnData)))) +func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(uint64(len(interpreter.returnData)))) return nil, nil } -func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - memOffset = stack.pop() - dataOffset = stack.pop() - length = stack.pop() - - end = interpreter.intPool.get().Add(dataOffset, length) + memOffset = callContext.stack.pop() + dataOffset = callContext.stack.pop() + length = callContext.stack.pop() ) - defer interpreter.intPool.put(memOffset, dataOffset, length, end) - if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() { - return nil, errReturnDataOutOfBounds + offset64, overflow := dataOffset.Uint64WithOverflow() + if overflow { + return nil, ErrReturnDataOutOfBounds } - memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()]) - + // we can reuse dataOffset now (aliasing it for clarity) + var end = dataOffset + end.Add(&dataOffset, &length) + end64, overflow := end.Uint64WithOverflow() + if overflow || uint64(len(interpreter.returnData)) < end64 { + return nil, ErrReturnDataOutOfBounds + } + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64]) return nil, nil } -func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - slot := stack.peek() - address := common.BigToAddress(slot) - fixValidatorCode := interpreter.evm.chainRules.IsValidatorCodeFix && - interpreter.evm.ShardID == shard.BeaconChainShardID && - interpreter.evm.StateDB.IsValidator(address) - if fixValidatorCode { - // https://github.com/ethereum/solidity/blob/develop/Changelog.md#081-2021-01-27 - // per this link,
.code.length calls extcodesize on the address so this fix will work - slot.SetUint64(0) - return nil, nil - } - slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(common.BigToAddress(slot)))) - +func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + slot := callContext.stack.peek() + slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) return nil, nil } -func opCodeSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - l := interpreter.intPool.get().SetInt64(int64(len(contract.Code))) - stack.push(l) - +func opCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + l := new(uint256.Int) + l.SetUint64(uint64(len(callContext.contract.Code))) + callContext.stack.push(l) return nil, nil } -func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - memOffset = stack.pop() - codeOffset = stack.pop() - length = stack.pop() + memOffset = callContext.stack.pop() + codeOffset = callContext.stack.pop() + length = callContext.stack.pop() ) - codeCopy := getDataBig(contract.Code, codeOffset, length) - memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + codeCopy := getData(callContext.contract.Code, uint64CodeOffset, length.Uint64()) + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) - interpreter.intPool.put(memOffset, codeOffset, length) return nil, nil } -func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - addr = common.BigToAddress(stack.pop()) + stack = callContext.stack + a = stack.pop() memOffset = stack.pop() codeOffset = stack.pop() length = stack.pop() ) - var code []byte - fixValidatorCode := interpreter.evm.chainRules.IsValidatorCodeFix && - interpreter.evm.ShardID == shard.BeaconChainShardID && - interpreter.evm.StateDB.IsValidator(addr) - if fixValidatorCode { - // for EOAs that are not validators, statedb returns nil - code = nil - } else { - code = interpreter.evm.StateDB.GetCode(addr) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff } - codeCopy := getDataBig(code, codeOffset, length) - memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + addr := common.Address(a.Bytes20()) + codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) - interpreter.intPool.put(memOffset, codeOffset, length) return nil, nil } @@ -570,182 +435,223 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, // (6) Caller tries to get the code hash for an account which is marked as deleted, // // this account should be regarded as a non-existent account and zero should be returned. -func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - slot := stack.peek() - address := common.BigToAddress(slot) +func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + slot := callContext.stack.peek() + address := common.Address(slot.Bytes20()) if interpreter.evm.StateDB.Empty(address) { - slot.SetUint64(0) + slot.Clear() } else { - fixValidatorCode := interpreter.evm.chainRules.IsValidatorCodeFix && - interpreter.evm.ShardID == shard.BeaconChainShardID && - interpreter.evm.StateDB.IsValidator(address) - if fixValidatorCode { - slot.SetBytes(emptyCodeHash.Bytes()) - } else { - slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) - } + slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) } return nil, nil } -func opGasprice(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().Set(interpreter.evm.GasPrice)) +func opGasprice(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.GasPrice) + callContext.stack.push(v) return nil, nil } -func opBlockhash(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - num := stack.pop() - - n := interpreter.intPool.get().Sub(interpreter.evm.BlockNumber, common.Big257) - if num.Cmp(n) > 0 && num.Cmp(interpreter.evm.BlockNumber) < 0 { - stack.push(interpreter.evm.GetHash(num.Uint64()).Big()) +func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + num := callContext.stack.peek() + num64, overflow := num.Uint64WithOverflow() + if overflow { + num.Clear() + return nil, nil + } + var upper, lower uint64 + upper = interpreter.evm.Context.BlockNumber.Uint64() + if upper < 257 { + lower = 0 } else { - stack.push(interpreter.intPool.getZero()) + lower = upper - 256 + } + if num64 >= lower && num64 < upper { + num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) + } else { + num.Clear() } - interpreter.intPool.put(num, n) return nil, nil } -func opCoinbase(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Coinbase.Bytes())) +func opCoinbase(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Context.Coinbase.Bytes())) return nil, nil } -func opTimestamp(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.Time))) +func opTimestamp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.Context.Time) + callContext.stack.push(v) return nil, nil } -func opNumber(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.BlockNumber))) +func opNumber(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.Context.BlockNumber) + callContext.stack.push(v) return nil, nil } -func opDifficulty(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().Set(big.NewInt(0)))) +/* +func opDifficulty(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.Context.Difficulty) + callContext.stack.push(v) return nil, nil } +*/ -func opGasLimit(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().SetUint64(interpreter.evm.GasLimit))) +func opGasLimit(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit)) return nil, nil } -func opPop(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - interpreter.intPool.put(stack.pop()) +func opPop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.pop() return nil, nil } -func opMload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - v := stack.peek() - offset := v.Int64() - v.SetBytes(memory.GetPtr(offset, 32)) +func opMload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v := callContext.stack.peek() + offset := int64(v.Uint64()) + v.SetBytes(callContext.memory.GetPtr(offset, 32)) return nil, nil } -func opMstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opMstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // pop value of the stack - mStart, val := stack.pop(), stack.pop() - memory.Set32(mStart.Uint64(), val) - - interpreter.intPool.put(mStart, val) + mStart, val := callContext.stack.pop(), callContext.stack.pop() + callContext.memory.Set32(mStart.Uint64(), &val) return nil, nil } -func opMstore8(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - off, val := stack.pop().Int64(), stack.pop().Int64() - memory.store[off] = byte(val & 0xff) - +func opMstore8(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + off, val := callContext.stack.pop(), callContext.stack.pop() + callContext.memory.store[off.Uint64()] = byte(val.Uint64()) return nil, nil } -func opSload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - loc := stack.peek() - val := interpreter.evm.StateDB.GetState(contract.Address(), common.BigToHash(loc)) +func opSload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + loc := callContext.stack.peek() + hash := common.Hash(loc.Bytes32()) + val := interpreter.evm.StateDB.GetState(callContext.contract.Address(), hash) loc.SetBytes(val.Bytes()) return nil, nil } -func opSstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - loc := common.BigToHash(stack.pop()) - val := stack.pop() - interpreter.evm.StateDB.SetState(contract.Address(), loc, common.BigToHash(val)) - - interpreter.intPool.put(val) +func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + loc := callContext.stack.pop() + val := callContext.stack.pop() + interpreter.evm.StateDB.SetState(callContext.contract.Address(), + loc.Bytes32(), val.Bytes32()) return nil, nil } -func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - pos := stack.pop() - if !contract.validJumpdest(pos) { - return nil, errInvalidJump +func opJump(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + pos := callContext.stack.pop() + if !callContext.contract.validJumpdest(&pos) { + return nil, ErrInvalidJump } *pc = pos.Uint64() - - interpreter.intPool.put(pos) return nil, nil } -func opJumpi(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - pos, cond := stack.pop(), stack.pop() - if cond.Sign() != 0 { - if !contract.validJumpdest(pos) { - return nil, errInvalidJump +func opJumpi(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + pos, cond := callContext.stack.pop(), callContext.stack.pop() + if !cond.IsZero() { + if !callContext.contract.validJumpdest(&pos) { + return nil, ErrInvalidJump } *pc = pos.Uint64() } else { *pc++ } + return nil, nil +} - interpreter.intPool.put(pos, cond) +func opJumpdest(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { return nil, nil } -func opJumpdest(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opBeginSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + return nil, ErrInvalidSubroutineEntry +} + +func opJumpSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + if len(callContext.rstack.data) >= 1023 { + return nil, ErrReturnStackExceeded + } + pos := callContext.stack.pop() + if !pos.IsUint64() { + return nil, ErrInvalidJump + } + posU64 := pos.Uint64() + if !callContext.contract.validJumpSubdest(posU64) { + return nil, ErrInvalidJump + } + callContext.rstack.push(uint32(*pc)) + *pc = posU64 + 1 return nil, nil } -func opPc(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetUint64(*pc)) +func opReturnSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + if len(callContext.rstack.data) == 0 { + return nil, ErrInvalidRetsub + } + // Other than the check that the return stack is not empty, there is no + // need to validate the pc from 'returns', since we only ever push valid + //values onto it via jumpsub. + *pc = uint64(callContext.rstack.pop()) + 1 return nil, nil } -func opMsize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetInt64(int64(memory.Len()))) +func opPc(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(*pc)) return nil, nil } -func opGas(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetUint64(contract.Gas)) +func opMsize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(uint64(callContext.memory.Len()))) return nil, nil } -func opCreate(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opGas(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(new(uint256.Int).SetUint64(callContext.contract.Gas)) + return nil, nil +} + +func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - value = stack.pop() - offset, size = stack.pop(), stack.pop() - input = memory.GetCopy(offset.Int64(), size.Int64()) - gas = contract.Gas + value = callContext.stack.pop() + offset, size = callContext.stack.pop(), callContext.stack.pop() + input = callContext.memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = callContext.contract.Gas ) - if interpreter.evm.ChainConfig().IsS3(interpreter.evm.EpochNumber) { + if interpreter.evm.ChainConfig().IsS3(interpreter.evm.Context.EpochNumber) { gas -= gas / 64 } + // reuse size int for stackvalue + stackvalue := size - contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create(contract, input, gas, value) + callContext.contract.UseGas(gas) + //TODO: use uint256.Int instead of converting with toBig() + var bigVal = big0 + if !value.IsZero() { + bigVal = value.ToBig() + } + + res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, bigVal) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must // ignore this error and pretend the operation was successful. - if interpreter.evm.ChainConfig().IsS3(interpreter.evm.EpochNumber) && suberr == ErrCodeStoreOutOfGas { - stack.push(interpreter.intPool.getZero()) + if interpreter.evm.ChainConfig().IsS3(interpreter.evm.Context.EpochNumber) && suberr == ErrCodeStoreOutOfGas { + stackvalue.Clear() } else if suberr != nil && suberr != ErrCodeStoreOutOfGas { - stack.push(interpreter.intPool.getZero()) + stackvalue.Clear() } else { - stack.push(interpreter.intPool.get().SetBytes(addr.Bytes())) + stackvalue.SetBytes(addr.Bytes()) } - contract.Gas += returnGas - interpreter.intPool.put(value, offset, size) + callContext.stack.push(&stackvalue) + callContext.contract.Gas += returnGas if suberr == ErrExecutionReverted { return res, nil @@ -753,27 +659,35 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memor return nil, nil } -func opCreate2(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - endowment = stack.pop() - offset, size = stack.pop(), stack.pop() - salt = stack.pop() - input = memory.GetCopy(offset.Int64(), size.Int64()) - gas = contract.Gas + endowment = callContext.stack.pop() + offset, size = callContext.stack.pop(), callContext.stack.pop() + salt = callContext.stack.pop() + input = callContext.memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = callContext.contract.Gas ) // Apply EIP150 gas -= gas / 64 - contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create2(contract, input, gas, endowment, salt) + callContext.contract.UseGas(gas) + // reuse size int for stackvalue + stackvalue := size + //TODO: use uint256.Int instead of converting with toBig() + bigEndowment := big0 + if !endowment.IsZero() { + bigEndowment = endowment.ToBig() + } + res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas, + bigEndowment, &salt) // Push item on the stack based on the returned error. if suberr != nil { - stack.push(interpreter.intPool.getZero()) + stackvalue.Clear() } else { - stack.push(interpreter.intPool.get().SetBytes(addr.Bytes())) + stackvalue.SetBytes(addr.Bytes()) } - contract.Gas += returnGas - interpreter.intPool.put(endowment, offset, size, salt) + callContext.stack.push(&stackvalue) + callContext.contract.Gas += returnGas if suberr == ErrExecutionReverted { return res, nil @@ -781,151 +695,154 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memo return nil, nil } -func opCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + stack := callContext.stack // Pop gas. The actual gas in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + // We can use this as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - toAddr := common.BigToAddress(addr) - value = math.U256(value) + toAddr := common.Address(addr.Bytes20()) // Get the arguments from the memory. - args := memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - if value.Sign() != 0 { + var bigVal = big0 + //TODO: use uint256.Int instead of converting with toBig() + // By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls), + // but it would make more sense to extend the usage of uint256.Int + if !value.IsZero() { gas += params.CallStipend + bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.Call(contract, toAddr, args, gas, value) + + ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, bigVal) + if err != nil { - stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } + stack.push(&temp) if err == nil || err == ErrExecutionReverted { - if contract.WithDataCopyFix { - ret = common.CopyBytes(ret) - } - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize) return ret, nil } -func opCallCode(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + stack := callContext.stack + // We use it as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - toAddr := common.BigToAddress(addr) - value = math.U256(value) + toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - if value.Sign() != 0 { + //TODO: use uint256.Int instead of converting with toBig() + var bigVal = big0 + if !value.IsZero() { gas += params.CallStipend + bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.CallCode(contract, toAddr, args, gas, value) + + ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, bigVal) if err != nil { - stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } + stack.push(&temp) if err == nil || err == ErrExecutionReverted { - if contract.WithDataCopyFix { - ret = common.CopyBytes(ret) - } - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize) return ret, nil } -func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + stack := callContext.stack // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + // We use it as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - toAddr := common.BigToAddress(addr) + toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - ret, returnGas, err := interpreter.evm.DelegateCall(contract, toAddr, args, gas) + ret, returnGas, err := interpreter.evm.DelegateCall(callContext.contract, toAddr, args, gas) if err != nil { - stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } + stack.push(&temp) if err == nil || err == ErrExecutionReverted { - if contract.WithDataCopyFix { - ret = common.CopyBytes(ret) - } - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize) return ret, nil } -func opStaticCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opStaticCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + stack := callContext.stack + // We use it as a temporary value + temp := stack.pop() gas := interpreter.evm.callGasTemp // Pop other call parameters. addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() - toAddr := common.BigToAddress(addr) + toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - ret, returnGas, err := interpreter.evm.StaticCall(contract, toAddr, args, gas) + ret, returnGas, err := interpreter.evm.StaticCall(callContext.contract, toAddr, args, gas) if err != nil { - stack.push(interpreter.intPool.getZero()) + temp.Clear() } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + temp.SetOne() } + stack.push(&temp) if err == nil || err == ErrExecutionReverted { - if contract.WithDataCopyFix { - ret = common.CopyBytes(ret) - } - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas - interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize) return ret, nil } -func opReturn(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - offset, size := stack.pop(), stack.pop() - ret := memory.GetPtr(offset.Int64(), size.Int64()) +func opReturn(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + offset, size := callContext.stack.pop(), callContext.stack.pop() + ret := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) - interpreter.intPool.put(offset, size) return ret, nil } -func opRevert(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - offset, size := stack.pop(), stack.pop() - ret := memory.GetPtr(offset.Int64(), size.Int64()) +func opRevert(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + offset, size := callContext.stack.pop(), callContext.stack.pop() + ret := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) - interpreter.intPool.put(offset, size) return ret, nil } -func opStop(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opStop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { return nil, nil } -func opSuicide(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - balance := interpreter.evm.StateDB.GetBalance(contract.Address()) - interpreter.evm.StateDB.AddBalance(common.BigToAddress(stack.pop()), balance) - - interpreter.evm.StateDB.Suicide(contract.Address()) +func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + beneficiary := callContext.stack.pop() + balance := interpreter.evm.StateDB.GetBalance(callContext.contract.Address()) + interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) + interpreter.evm.StateDB.Suicide(callContext.contract.Address()) return nil, nil } @@ -933,47 +850,48 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memo // make log instruction function func makeLog(size int) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { topics := make([]common.Hash, size) + stack := callContext.stack mStart, mSize := stack.pop(), stack.pop() for i := 0; i < size; i++ { - topics[i] = common.BigToHash(stack.pop()) + addr := stack.pop() + topics[i] = addr.Bytes32() } - d := memory.GetCopy(mStart.Int64(), mSize.Int64()) - interpreter.evm.StateDB.AddLog(&types.Log{ - Address: contract.Address(), + d := callContext.memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) + interpreter.evm.StateDB.AddLog((*coreTypes.Log)(&types.Log{ + Address: callContext.contract.Address(), Topics: topics, Data: d, // This is a non-consensus field, but assigned here because // core/state doesn't know the current block number. - BlockNumber: interpreter.evm.BlockNumber.Uint64(), - }) + BlockNumber: interpreter.evm.Context.BlockNumber.Uint64(), + })) - interpreter.intPool.put(mStart, mSize) return nil, nil } } // opPush1 is a specialized version of pushN -func opPush1(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opPush1(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - codeLen = uint64(len(contract.Code)) - integer = interpreter.intPool.get() + codeLen = uint64(len(callContext.contract.Code)) + integer = new(uint256.Int) ) - *pc++ + *pc += 1 if *pc < codeLen { - stack.push(integer.SetUint64(uint64(contract.Code[*pc]))) + callContext.stack.push(integer.SetUint64(uint64(callContext.contract.Code[*pc]))) } else { - stack.push(integer.SetUint64(0)) + callContext.stack.push(integer.Clear()) } return nil, nil } // make push instruction function func makePush(size uint64, pushByteSize int) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - codeLen := len(contract.Code) + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + codeLen := len(callContext.contract.Code) startMin := codeLen if int(*pc+1) < startMin { @@ -985,8 +903,9 @@ func makePush(size uint64, pushByteSize int) executionFunc { endMin = startMin + pushByteSize } - integer := interpreter.intPool.get() - stack.push(integer.SetBytes(common.RightPadBytes(contract.Code[startMin:endMin], pushByteSize))) + integer := new(uint256.Int) + callContext.stack.push(integer.SetBytes(common.RightPadBytes( + callContext.contract.Code[startMin:endMin], pushByteSize))) *pc += size return nil, nil @@ -995,8 +914,8 @@ func makePush(size uint64, pushByteSize int) executionFunc { // make dup instruction function func makeDup(size int64) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.dup(interpreter.intPool, int(size)) + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.dup(int(size)) return nil, nil } } @@ -1005,8 +924,8 @@ func makeDup(size int64) executionFunc { func makeSwap(size int64) executionFunc { // switch n + 1 otherwise n would be swapped with n size++ - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.swap(int(size)) + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.swap(int(size)) return nil, nil } } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 7c03b6ccbb..05bc24ad05 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -20,16 +20,13 @@ import ( "bytes" "encoding/json" "fmt" - "math/big" - "os" + "io/ioutil" "testing" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/internal/params" + "github.com/holiman/uint256" ) type TwoOperandTestcase struct { @@ -95,47 +92,29 @@ func init() { func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() + rstack = newReturnStack() pc = uint64(0) evmInterpreter = env.interpreter.(*EVMInterpreter) ) - // Stuff a couple of nonzero bigints into pool, to ensure that ops do not rely on pooled integers to be zero - evmInterpreter.intPool = poolOfIntPools.get() - evmInterpreter.intPool.put(big.NewInt(-1337)) - evmInterpreter.intPool.put(big.NewInt(-1337)) - evmInterpreter.intPool.put(big.NewInt(-1337)) for i, test := range tests { - x := new(big.Int).SetBytes(common.Hex2Bytes(test.X)) - y := new(big.Int).SetBytes(common.Hex2Bytes(test.Y)) - expected := new(big.Int).SetBytes(common.Hex2Bytes(test.Expected)) + x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.X)) + y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Y)) + expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) - opFn(&pc, evmInterpreter, nil, nil, stack) + opFn(&pc, evmInterpreter, &callCtx{nil, stack, rstack, nil}) + if len(stack.data) != 1 { + t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) + } actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %v %d, %v(%x, %x): expected %x, got %x", name, i, name, x, y, expected, actual) } - // Check pool usage - // 1.pool is not allowed to contain anything on the stack - // 2.pool is not allowed to contain the same pointers twice - if evmInterpreter.intPool.pool.len() > 0 { - - poolvals := make(map[*big.Int]struct{}) - poolvals[actual] = struct{}{} - - for evmInterpreter.intPool.pool.len() > 0 { - key := evmInterpreter.intPool.get() - if _, exist := poolvals[key]; exist { - t.Errorf("Testcase %v %d, pool contains double-entry", name, i) - } - poolvals[key] = struct{}{} - } - } } - poolOfIntPools.put(evmInterpreter.intPool) } func TestByteOp(t *testing.T) { @@ -211,22 +190,59 @@ func TestSAR(t *testing.T) { testTwoOperandOp(t, tests, opSAR, "sar") } +func TestAddMod(t *testing.T) { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + evmInterpreter = NewEVMInterpreter(env, env.vmConfig) + pc = uint64(0) + ) + tests := []struct { + x string + y string + z string + expected string + }{ + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + }, + } + // x + y = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd + // in 256 bit repr, fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd + + for i, test := range tests { + x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.x)) + y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.y)) + z := new(uint256.Int).SetBytes(common.Hex2Bytes(test.z)) + expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.expected)) + stack.push(z) + stack.push(y) + stack.push(x) + opAddmod(&pc, evmInterpreter, &callCtx{nil, stack, nil, nil}) + actual := stack.pop() + if actual.Cmp(expected) != 0 { + t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) + } + } +} + // getResult is a convenience function to generate the expected values func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) - stack = newstack() - pc = uint64(0) - interpreter = env.interpreter.(*EVMInterpreter) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack, rstack = newstack(), newReturnStack() + pc = uint64(0) + interpreter = env.interpreter.(*EVMInterpreter) ) - interpreter.intPool = poolOfIntPools.get() result := make([]TwoOperandTestcase, len(args)) for i, param := range args { - x := new(big.Int).SetBytes(common.Hex2Bytes(param.x)) - y := new(big.Int).SetBytes(common.Hex2Bytes(param.y)) + x := new(uint256.Int).SetBytes(common.Hex2Bytes(param.x)) + y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, interpreter, nil, nil, stack) + opFn(&pc, interpreter, &callCtx{nil, stack, rstack, nil}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -243,7 +259,7 @@ func TestWriteExpectedValues(t *testing.T) { if err != nil { t.Fatal(err) } - _ = os.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0644) + _ = ioutil.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0644) if err != nil { t.Fatal(err) } @@ -253,7 +269,7 @@ func TestWriteExpectedValues(t *testing.T) { // TestJsonTestcases runs through all the testcases defined as json-files func TestJsonTestcases(t *testing.T) { for name := range twoOpMethods { - data, err := os.ReadFile(fmt.Sprintf("testdata/testcases_%v.json", name)) + data, err := ioutil.ReadFile(fmt.Sprintf("testdata/testcases_%v.json", name)) if err != nil { t.Fatal("Failed to read file", err) } @@ -263,15 +279,14 @@ func TestJsonTestcases(t *testing.T) { } } -func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) { +func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) - stack = newstack() + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack, rstack = newstack(), newReturnStack() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) env.interpreter = evmInterpreter - evmInterpreter.intPool = poolOfIntPools.get() // convert args byteArgs := make([][]byte, len(args)) for i, arg := range args { @@ -281,13 +296,13 @@ func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpret bench.ResetTimer() for i := 0; i < bench.N; i++ { for _, arg := range byteArgs { - a := new(big.Int).SetBytes(arg) + a := new(uint256.Int) + a.SetBytes(arg) stack.push(a) } - op(&pc, evmInterpreter, nil, nil, stack) + op(&pc, evmInterpreter, &callCtx{nil, stack, rstack, nil}) stack.pop() } - poolOfIntPools.put(evmInterpreter.intPool) } func BenchmarkOpAdd64(b *testing.B) { @@ -500,72 +515,66 @@ func BenchmarkOpIsZero(b *testing.B) { func TestOpMstore(t *testing.T) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) - stack = newstack() + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) env.interpreter = evmInterpreter - evmInterpreter.intPool = poolOfIntPools.get() mem.Resize(64) pc := uint64(0) v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" - stack.pushN(new(big.Int).SetBytes(common.Hex2Bytes(v)), big.NewInt(0)) - opMstore(&pc, evmInterpreter, nil, mem, stack) + stack.pushN(*new(uint256.Int).SetBytes(common.Hex2Bytes(v)), *new(uint256.Int)) + opMstore(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } - stack.pushN(big.NewInt(0x1), big.NewInt(0)) - opMstore(&pc, evmInterpreter, nil, mem, stack) + stack.pushN(*new(uint256.Int).SetUint64(0x1), *new(uint256.Int)) + opMstore(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } - poolOfIntPools.put(evmInterpreter.intPool) } func BenchmarkOpMstore(bench *testing.B) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) - stack = newstack() + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) env.interpreter = evmInterpreter - evmInterpreter.intPool = poolOfIntPools.get() mem.Resize(64) pc := uint64(0) - memStart := big.NewInt(0) - value := big.NewInt(0x1337) + memStart := new(uint256.Int) + value := new(uint256.Int).SetUint64(0x1337) bench.ResetTimer() for i := 0; i < bench.N; i++ { - stack.pushN(value, memStart) - opMstore(&pc, evmInterpreter, nil, mem, stack) + stack.pushN(*value, *memStart) + opMstore(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) } - poolOfIntPools.put(evmInterpreter.intPool) } func BenchmarkOpSHA3(bench *testing.B) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) - stack = newstack() + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) env.interpreter = evmInterpreter - evmInterpreter.intPool = poolOfIntPools.get() mem.Resize(32) pc := uint64(0) - start := big.NewInt(0) + start := uint256.NewInt(0) bench.ResetTimer() for i := 0; i < bench.N; i++ { - stack.pushN(big.NewInt(32), start) - opSha3(&pc, evmInterpreter, nil, mem, stack) + stack.pushN(*uint256.NewInt(0).SetUint64(32), *start) + opSha3(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) } - poolOfIntPools.put(evmInterpreter.intPool) } func TestCreate2Addreses(t *testing.T) { @@ -639,52 +648,5 @@ func TestCreate2Addreses(t *testing.T) { if !bytes.Equal(expected.Bytes(), address.Bytes()) { t.Errorf("test %d: expected %s, got %s", i, expected.String(), address.String()) } - - } -} - -func TestOpTstoreAndTload(t *testing.T) { - // initialize context, evm, statedb, etc. - var ( - stateDB, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - env = NewEVM(Context{}, stateDB, params.TestChainConfig, Config{}) - stack = newstack() - memory = NewMemory() - evmInterpreter = NewEVMInterpreter(env, env.vmConfig) - caller = common.Address{} - to = common.Address{1} - contractRef = vm.AccountRef(caller) - contract = NewContract(contractRef, vm.AccountRef(to), new(big.Int), 0) - value = common.Hex2Bytes("12345") - ) - - stateDB.CreateAccount(caller) - stateDB.CreateAccount(to) - env.interpreter = evmInterpreter - pc := uint64(0) - - // push to value then location to the stack - stack.push(new(big.Int).SetBytes(value)) - stack.push(new(big.Int)) - - // call tstore and ensure stack length is 0 - opTstore(&pc, evmInterpreter, contract, memory, stack) - if stack.len() != 0 { - t.Fatal("stack should be empty") - } - - // push location to the stack - stack.push(new(big.Int)) - - // call tload and ensure stack length is 1 - opTload(&pc, evmInterpreter, contract, memory, stack) - if stack.len() != 1 { - t.Fatal("stack should have a single element") - } - - // ensure the value read is same as the original value - val := stack.peek() - if !bytes.Equal(value, val.Bytes()) { - t.Fatalf("wrong value loaded to the stack") } } diff --git a/core/vm/int_pool_verifier.go b/core/vm/int_pool_verifier.go deleted file mode 100644 index 72d55ff671..0000000000 --- a/core/vm/int_pool_verifier.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -//go:build VERIFY_EVM_INTEGER_POOL - -package vm - -import "fmt" - -const verifyPool = true - -func verifyIntegerPool(ip *intPool) { - for i, item := range ip.pool.data { - if item.Cmp(checkVal) != 0 { - panic(fmt.Sprintf("%d'th item failed aggressive pool check. Value was modified", i)) - } - } -} diff --git a/core/vm/int_pool_verifier_empty.go b/core/vm/int_pool_verifier_empty.go deleted file mode 100644 index 6cbe6cafe6..0000000000 --- a/core/vm/int_pool_verifier_empty.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -//go:build !VERIFY_EVM_INTEGER_POOL - -package vm - -const verifyPool = false - -func verifyIntegerPool(ip *intPool) {} diff --git a/core/vm/interface.go b/core/vm/interface.go index 4e4b2073c0..e2fe76907d 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -19,10 +19,9 @@ package vm import ( "math/big" - "github.com/harmony-one/harmony/numeric" - "github.com/ethereum/go-ethereum/common" "github.com/harmony-one/harmony/core/types" + "github.com/harmony-one/harmony/numeric" staking "github.com/harmony-one/harmony/staking/types" ) @@ -59,8 +58,8 @@ type StateDB interface { GetState(common.Address, common.Hash) common.Hash SetState(common.Address, common.Hash, common.Hash) - GetTransientState(addr common.Address, key common.Hash) common.Hash - SetTransientState(addr common.Address, key, value common.Hash) + //GetTransientState(addr common.Address, key common.Hash) common.Hash + //SetTransientState(addr common.Address, key, value common.Hash) Suicide(common.Address) bool HasSuicided(common.Address) bool @@ -72,6 +71,15 @@ type StateDB interface { // is defined according to EIP161 (balance = nonce = code = 0). Empty(common.Address) bool + AddressInAccessList(addr common.Address) bool + SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) + // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddAddressToAccessList(addr common.Address) + // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddSlotToAccessList(addr common.Address, slot common.Hash) + Prepare() RevertToSnapshot(int) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 3b8df026f8..299c58bb14 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -17,39 +17,27 @@ package vm import ( - "fmt" "hash" "sync/atomic" - "github.com/harmony-one/harmony/internal/utils" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/harmony-one/harmony/internal/utils" ) // Config are the configuration options for the Interpreter type Config struct { - // Debug enabled debugging Interpreter options - Debug bool - // Tracer is the op code logger - Tracer Tracer - // NoRecursion disabled Interpreter call, callcode, - // delegate call and create. - NoRecursion bool - // Enable recording of SHA3/keccak preimages - EnablePreimageRecording bool - // JumpTable contains the EVM instruction table. This - // may be left uninitialised and will be set to the default - // table. - JumpTable [256]operation + Debug bool // Enables debugging + Tracer Tracer // Opcode logger + NoRecursion bool // Disables call, callcode, delegate call and create + EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages - // Type of the EWASM interpreter - EWASMInterpreter string - // Type of the EVM interpreter - EVMInterpreter string + JumpTable [256]*operation // EVM instruction table, automatically populated if unset - // ExtraEips the additional EIPS that are to be enabled - ExtraEips []int + EWASMInterpreter string // External EWASM interpreter options + EVMInterpreter string // External EVM interpreter options + + ExtraEips []int // Additional EIPS that are to be enabled } // Interpreter is used to run Ethereum based contracts and will utilise the @@ -74,6 +62,15 @@ type Interpreter interface { CanRun([]byte) bool } +// callCtx contains the things that are per-call, such as stack and memory, +// but not transients like pc and gas +type callCtx struct { + memory *Memory + stack *Stack + rstack *ReturnStack + contract *Contract +} + // keccakState wraps sha3.state. In addition to the usual hash methods, it also supports // Read to get a variable amount of data from the hash state. Read is faster than Sum // because it doesn't copy the internal state, but also modifies the internal state. @@ -87,8 +84,6 @@ type EVMInterpreter struct { evm *EVM cfg Config - intPool *intPool - hasher keccakState // Keccak256 hasher instance shared across opcodes hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes @@ -101,7 +96,7 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { // We use the STOP instruction whether to see // the jump table was initialised. If it was not // we'll set the default jump table. - if !cfg.JumpTable[STOP].valid { + if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { case evm.chainRules.IsIstanbul: @@ -132,15 +127,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { // // It's important to note that any errors returned by the interpreter should be // considered a revert-and-consume-all-gas operation except for -// errExecutionReverted which means revert-and-keep-gas-left. +// ErrExecutionReverted which means revert-and-keep-gas-left. func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { - if in.intPool == nil { - in.intPool = poolOfIntPools.get() - defer func() { - poolOfIntPools.put(in.intPool) - in.intPool = nil - }() - } // Increment the call depth which is restricted to 1024 in.evm.depth++ @@ -163,9 +151,16 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } var ( - op OpCode // current opcode - mem = NewMemory() // bound memory - stack = newstack() // local stack + op OpCode // current opcode + mem = NewMemory() // bound memory + stack = newstack() // local stack + returns = newReturnStack() // local returns stack + callContext = &callCtx{ + memory: mem, + stack: stack, + rstack: returns, + contract: contract, + } // For optimisation reason we're using uint64 as the program counter. // It's theoretically possible to go above 2^64. The YP defines the PC // to be uint256. Practically much less so feasible. @@ -177,18 +172,22 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged bool // deferred Tracer should ignore already logged steps res []byte // result of the opcode execution function ) + // Don't move this deferrred function, it's placed before the capturestate-deferred method, + // so that it get's executed _after_: the capturestate needs the stacks before + // they are returned to the pools + defer func() { + returnStack(stack) + returnRStack(returns) + }() contract.Input = input - // Reclaim the stack as an int pool when the execution stops - defer func() { in.intPool.put(stack.data...) }() - if in.cfg.Debug { defer func() { if err != nil { if !logged { - in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err) } else { - in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err) } } }() @@ -197,7 +196,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during // the execution of one of the operations or until the done flag is set by the // parent context. - for atomic.LoadInt32(&in.evm.abort) == 0 { + steps := 0 + for { + steps++ + if steps%1000 == 0 && atomic.LoadInt32(&in.evm.abort) != 0 { + break + } if in.cfg.Debug { // Capture pre-execution values for tracing. logged, pcCopy, gasCopy = false, pc, contract.Gas @@ -207,14 +211,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // enough stack items available to perform the operation. op = contract.GetOp(pc) operation := in.cfg.JumpTable[op] - if !operation.valid { - return nil, fmt.Errorf("invalid opcode 0x%x", int(op)) + if operation == nil { + return nil, &ErrInvalidOpCode{opcode: op} } // Validate stack if sLen := stack.len(); sLen < operation.minStack { - return nil, fmt.Errorf("stack underflow (%d <=> %d)", sLen, operation.minStack) + return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack} } else if sLen > operation.maxStack { - return nil, fmt.Errorf("stack limit reached %d (%d)", sLen, operation.maxStack) + return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } // If the operation is valid, enforce and write restrictions if in.readOnly && in.evm.chainRules.IsS3 { @@ -224,7 +228,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // account to the others means the state is modified and should also // return with an error. if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) { - return nil, errWriteProtection + return nil, ErrWriteProtection } } // Static portion of gas @@ -241,12 +245,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( if operation.memorySize != nil { memSize, overflow := operation.memorySize(stack) if overflow { - return nil, errGasUintOverflow + return nil, ErrGasUintOverflow } // memory is expanded in words of 32 bytes. Gas // is also calculated in words. if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { - return nil, errGasUintOverflow + return nil, ErrGasUintOverflow } } // Dynamic portion of gas @@ -264,29 +268,17 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( mem.Resize(memorySize) } - var afterHook HookAfter if in.cfg.Debug { - afterHook, _ = in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err) logged = true } // execute the operation - res, err = operation.execute(&pc, in, contract, mem, stack) - - // record the after executed operation content - if afterHook != nil { - afterHook(mem, stack) - } - - // verifyPool is a build flag. Pool verification makes sure the integrity - // of the integer pool by comparing values to a default value. - if verifyPool { - verifyIntegerPool(in.intPool) - } + res, err = operation.execute(&pc, in, callContext) // if the operation clears the return data (e.g. it has returning data) // set the last return to the result of the operation. if operation.returns { - in.returnData = res + in.returnData = common.CopyBytes(res) } switch { diff --git a/core/vm/intpool.go b/core/vm/intpool.go deleted file mode 100644 index 917a78d560..0000000000 --- a/core/vm/intpool.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package vm - -import ( - "math/big" - "sync" -) - -var checkVal = big.NewInt(-42) - -const poolLimit = 256 - -// intPool is a pool of big integers that -// can be reused for all big.Int operations. -type intPool struct { - pool *Stack -} - -func newIntPool() *intPool { - return &intPool{pool: newstack()} -} - -// get retrieves a big int from the pool, allocating one if the pool is empty. -// Note, the returned int's value is arbitrary and will not be zeroed! -func (p *intPool) get() *big.Int { - if p.pool.len() > 0 { - return p.pool.pop() - } - return new(big.Int) -} - -// getZero retrieves a big int from the pool, setting it to zero or allocating -// a new one if the pool is empty. -func (p *intPool) getZero() *big.Int { - if p.pool.len() > 0 { - return p.pool.pop().SetUint64(0) - } - return new(big.Int) -} - -// put returns an allocated big int to the pool to be later reused by get calls. -// Note, the values as saved as is; neither put nor get zeroes the ints out! -func (p *intPool) put(is ...*big.Int) { - if len(p.pool.data) > poolLimit { - return - } - for _, i := range is { - // verifyPool is a build flag. Pool verification makes sure the integrity - // of the integer pool by comparing values to a default value. - if verifyPool { - i.Set(checkVal) - } - p.pool.push(i) - } -} - -// The intPool pool's default capacity -const poolDefaultCap = 25 - -// intPoolPool manages a pool of intPools. -type intPoolPool struct { - pools []*intPool - lock sync.Mutex -} - -var poolOfIntPools = &intPoolPool{ - pools: make([]*intPool, 0, poolDefaultCap), -} - -// get is looking for an available pool to return. -func (ipp *intPoolPool) get() *intPool { - ipp.lock.Lock() - defer ipp.lock.Unlock() - - if len(poolOfIntPools.pools) > 0 { - ip := ipp.pools[len(ipp.pools)-1] - ipp.pools = ipp.pools[:len(ipp.pools)-1] - return ip - } - return newIntPool() -} - -// put a pool that has been allocated with get. -func (ipp *intPoolPool) put(ip *intPool) { - ipp.lock.Lock() - defer ipp.lock.Unlock() - - if len(ipp.pools) < cap(ipp.pools) { - ipp.pools = append(ipp.pools, ip) - } -} diff --git a/core/vm/intpool_test.go b/core/vm/intpool_test.go deleted file mode 100644 index 6c0d00f3ce..0000000000 --- a/core/vm/intpool_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package vm - -import ( - "testing" -) - -func TestIntPoolPoolGet(t *testing.T) { - poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap) - - nip := poolOfIntPools.get() - if nip == nil { - t.Fatalf("Invalid pool allocation") - } -} - -func TestIntPoolPoolPut(t *testing.T) { - poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap) - - nip := poolOfIntPools.get() - if len(poolOfIntPools.pools) != 0 { - t.Fatalf("Pool got added to list when none should have been") - } - - poolOfIntPools.put(nip) - if len(poolOfIntPools.pools) == 0 { - t.Fatalf("Pool did not get added to list when one should have been") - } -} - -func TestIntPoolPoolReUse(t *testing.T) { - poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap) - nip := poolOfIntPools.get() - poolOfIntPools.put(nip) - poolOfIntPools.get() - - if len(poolOfIntPools.pools) != 0 { - t.Fatalf("Invalid number of pools. Got %d, expected %d", len(poolOfIntPools.pools), 0) - } -} diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 1a5af77f11..302d43c6db 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -18,12 +18,13 @@ package vm import ( "errors" + "fmt" "github.com/harmony-one/harmony/internal/params" ) type ( - executionFunc func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) + executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 memorySizeFunc func(*Stack) (size uint64, overflow bool) @@ -48,7 +49,6 @@ type operation struct { halts bool // indicates whether the operation should halt further execution jumps bool // indicates whether the program counter should not increment writes bool // determines whether this a state modifying operation - valid bool // indication whether the retrieved operation is valid and known reverts bool // determines whether the operation reverts state (implicitly halts) returns bool // determines whether the operations sets the return data content } @@ -64,7 +64,42 @@ var ( ) // JumpTable contains the EVM opcodes supported at a given fork. -type JumpTable [256]operation +type JumpTable [256]*operation + +func validate(jt JumpTable) JumpTable { + for i, op := range jt { + if op == nil { + panic(fmt.Sprintf("op %#x is not set", i)) + } + // The interpreter has an assumption that if the memorySize function is + // set, then the dynamicGas function is also set. This is a somewhat + // arbitrary assumption, and can be removed if we need to -- but it + // allows us to avoid a condition check. As long as we have that assumption + // in there, this little sanity check prevents us from merging in a + // change which violates it. + if op.memorySize != nil && op.dynamicGas == nil { + panic(fmt.Sprintf("op %v has dynamic memory but not dynamic gas", OpCode(i).String())) + } + } + return jt +} + +// newLondonInstructionSet returns the frontier, homestead, byzantium, +// contantinople, istanbul, petersburg, berlin and london instructions. +func newLondonInstructionSet() JumpTable { + instructionSet := newBerlinInstructionSet() + //enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529 // wee d + enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198 + return instructionSet +} + +// newBerlinInstructionSet returns the frontier, homestead, byzantium, +// contantinople, istanbul, petersburg and berlin instructions. +func newBerlinInstructionSet() JumpTable { + instructionSet := newIstanbulInstructionSet() + enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929 + return validate(instructionSet) +} // newIstanbulInstructionSet returns the frontier, homestead // byzantium, contantinople and petersburg instructions. @@ -82,42 +117,37 @@ func newIstanbulInstructionSet() JumpTable { // byzantium and contantinople instructions. func newConstantinopleInstructionSet() JumpTable { instructionSet := newByzantiumInstructionSet() - instructionSet[SHL] = operation{ + instructionSet[SHL] = &operation{ execute: opSHL, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, } - instructionSet[SHR] = operation{ + instructionSet[SHR] = &operation{ execute: opSHR, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, } - instructionSet[SAR] = operation{ + instructionSet[SAR] = &operation{ execute: opSAR, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, } - instructionSet[EXTCODEHASH] = operation{ + instructionSet[EXTCODEHASH] = &operation{ execute: opExtCodeHash, constantGas: params.ExtcodeHashGasConstantinople, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, } - instructionSet[CREATE2] = operation{ + instructionSet[CREATE2] = &operation{ execute: opCreate2, constantGas: params.Create2Gas, dynamicGas: gasCreate2, minStack: minStack(4, 1), maxStack: maxStack(4, 1), memorySize: memoryCreate2, - valid: true, writes: true, returns: true, } @@ -128,39 +158,35 @@ func newConstantinopleInstructionSet() JumpTable { // byzantium instructions. func newByzantiumInstructionSet() JumpTable { instructionSet := newSpuriousDragonInstructionSet() - instructionSet[STATICCALL] = operation{ + instructionSet[STATICCALL] = &operation{ execute: opStaticCall, constantGas: params.CallGasEIP150, dynamicGas: gasStaticCall, minStack: minStack(6, 1), maxStack: maxStack(6, 1), memorySize: memoryStaticCall, - valid: true, returns: true, } - instructionSet[RETURNDATASIZE] = operation{ + instructionSet[RETURNDATASIZE] = &operation{ execute: opReturnDataSize, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, } - instructionSet[RETURNDATACOPY] = operation{ + instructionSet[RETURNDATACOPY] = &operation{ execute: opReturnDataCopy, constantGas: GasFastestStep, dynamicGas: gasReturnDataCopy, minStack: minStack(3, 0), maxStack: maxStack(3, 0), memorySize: memoryReturnDataCopy, - valid: true, } - instructionSet[REVERT] = operation{ + instructionSet[REVERT] = &operation{ execute: opRevert, dynamicGas: gasRevert, minStack: minStack(2, 0), maxStack: maxStack(2, 0), memorySize: memoryRevert, - valid: true, reverts: true, returns: true, } @@ -192,14 +218,13 @@ func newTangerineWhistleInstructionSet() JumpTable { // instructions that can be executed during the homestead phase. func newHomesteadInstructionSet() JumpTable { instructionSet := newFrontierInstructionSet() - instructionSet[DELEGATECALL] = operation{ + instructionSet[DELEGATECALL] = &operation{ execute: opDelegateCall, dynamicGas: gasDelegateCall, constantGas: params.CallGasFrontier, minStack: minStack(6, 1), maxStack: maxStack(6, 1), memorySize: memoryDelegateCall, - valid: true, returns: true, } return instructionSet @@ -215,161 +240,138 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(0, 0), maxStack: maxStack(0, 0), halts: true, - valid: true, }, ADD: { execute: opAdd, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, MUL: { execute: opMul, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SUB: { execute: opSub, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, DIV: { execute: opDiv, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SDIV: { execute: opSdiv, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, MOD: { execute: opMod, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SMOD: { execute: opSmod, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, ADDMOD: { execute: opAddmod, constantGas: GasMidStep, minStack: minStack(3, 1), maxStack: maxStack(3, 1), - valid: true, }, MULMOD: { execute: opMulmod, constantGas: GasMidStep, minStack: minStack(3, 1), maxStack: maxStack(3, 1), - valid: true, }, EXP: { execute: opExp, dynamicGas: gasExpFrontier, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SIGNEXTEND: { execute: opSignExtend, constantGas: GasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, LT: { execute: opLt, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, GT: { execute: opGt, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SLT: { execute: opSlt, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SGT: { execute: opSgt, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, EQ: { execute: opEq, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, ISZERO: { execute: opIszero, constantGas: GasFastestStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, AND: { execute: opAnd, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, XOR: { execute: opXor, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, OR: { execute: opOr, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, NOT: { execute: opNot, constantGas: GasFastestStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, BYTE: { execute: opByte, constantGas: GasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), - valid: true, }, SHA3: { execute: opSha3, @@ -378,56 +380,48 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(2, 1), maxStack: maxStack(2, 1), memorySize: memorySha3, - valid: true, }, ADDRESS: { execute: opAddress, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, BALANCE: { execute: opBalance, constantGas: params.BalanceGasFrontier, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, ORIGIN: { execute: opOrigin, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, CALLER: { execute: opCaller, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, CALLVALUE: { execute: opCallValue, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, CALLDATALOAD: { execute: opCallDataLoad, constantGas: GasFastestStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, CALLDATASIZE: { execute: opCallDataSize, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, CALLDATACOPY: { execute: opCallDataCopy, @@ -436,14 +430,12 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(3, 0), maxStack: maxStack(3, 0), memorySize: memoryCallDataCopy, - valid: true, }, CODESIZE: { execute: opCodeSize, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, CODECOPY: { execute: opCodeCopy, @@ -452,21 +444,18 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(3, 0), maxStack: maxStack(3, 0), memorySize: memoryCodeCopy, - valid: true, }, GASPRICE: { execute: opGasprice, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, EXTCODESIZE: { execute: opExtCodeSize, constantGas: params.ExtcodeSizeGasFrontier, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, EXTCODECOPY: { execute: opExtCodeCopy, @@ -475,56 +464,49 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(4, 0), maxStack: maxStack(4, 0), memorySize: memoryExtCodeCopy, - valid: true, }, BLOCKHASH: { execute: opBlockhash, constantGas: GasExtStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, COINBASE: { execute: opCoinbase, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, TIMESTAMP: { execute: opTimestamp, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, NUMBER: { execute: opNumber, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, - DIFFICULTY: { + /*DIFFICULTY: { execute: opDifficulty, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, - }, + + },*/ GASLIMIT: { execute: opGasLimit, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, POP: { execute: opPop, constantGas: GasQuickStep, minStack: minStack(1, 0), maxStack: maxStack(1, 0), - valid: true, }, MLOAD: { execute: opMload, @@ -533,7 +515,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(1, 1), maxStack: maxStack(1, 1), memorySize: memoryMLoad, - valid: true, }, MSTORE: { execute: opMstore, @@ -542,7 +523,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(2, 0), maxStack: maxStack(2, 0), memorySize: memoryMStore, - valid: true, }, MSTORE8: { execute: opMstore8, @@ -551,22 +531,18 @@ func newFrontierInstructionSet() JumpTable { memorySize: memoryMStore8, minStack: minStack(2, 0), maxStack: maxStack(2, 0), - - valid: true, }, SLOAD: { execute: opSload, constantGas: params.SloadGasFrontier, minStack: minStack(1, 1), maxStack: maxStack(1, 1), - valid: true, }, SSTORE: { execute: opSstore, dynamicGas: gasSStore, minStack: minStack(2, 0), maxStack: maxStack(2, 0), - valid: true, writes: true, }, JUMP: { @@ -575,7 +551,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(1, 0), maxStack: maxStack(1, 0), jumps: true, - valid: true, }, JUMPI: { execute: opJumpi, @@ -583,483 +558,414 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(2, 0), maxStack: maxStack(2, 0), jumps: true, - valid: true, }, PC: { execute: opPc, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, MSIZE: { execute: opMsize, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, GAS: { execute: opGas, constantGas: GasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, JUMPDEST: { execute: opJumpdest, constantGas: params.JumpdestGas, minStack: minStack(0, 0), maxStack: maxStack(0, 0), - valid: true, }, PUSH1: { execute: opPush1, constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH2: { execute: makePush(2, 2), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH3: { execute: makePush(3, 3), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH4: { execute: makePush(4, 4), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH5: { execute: makePush(5, 5), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH6: { execute: makePush(6, 6), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH7: { execute: makePush(7, 7), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH8: { execute: makePush(8, 8), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH9: { execute: makePush(9, 9), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH10: { execute: makePush(10, 10), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH11: { execute: makePush(11, 11), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH12: { execute: makePush(12, 12), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH13: { execute: makePush(13, 13), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH14: { execute: makePush(14, 14), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH15: { execute: makePush(15, 15), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH16: { execute: makePush(16, 16), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH17: { execute: makePush(17, 17), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH18: { execute: makePush(18, 18), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH19: { execute: makePush(19, 19), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH20: { execute: makePush(20, 20), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH21: { execute: makePush(21, 21), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH22: { execute: makePush(22, 22), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH23: { execute: makePush(23, 23), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH24: { execute: makePush(24, 24), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH25: { execute: makePush(25, 25), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH26: { execute: makePush(26, 26), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH27: { execute: makePush(27, 27), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH28: { execute: makePush(28, 28), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH29: { execute: makePush(29, 29), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH30: { execute: makePush(30, 30), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH31: { execute: makePush(31, 31), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, PUSH32: { execute: makePush(32, 32), constantGas: GasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), - valid: true, }, DUP1: { execute: makeDup(1), constantGas: GasFastestStep, minStack: minDupStack(1), maxStack: maxDupStack(1), - valid: true, }, DUP2: { execute: makeDup(2), constantGas: GasFastestStep, minStack: minDupStack(2), maxStack: maxDupStack(2), - valid: true, }, DUP3: { execute: makeDup(3), constantGas: GasFastestStep, minStack: minDupStack(3), maxStack: maxDupStack(3), - valid: true, }, DUP4: { execute: makeDup(4), constantGas: GasFastestStep, minStack: minDupStack(4), maxStack: maxDupStack(4), - valid: true, }, DUP5: { execute: makeDup(5), constantGas: GasFastestStep, minStack: minDupStack(5), maxStack: maxDupStack(5), - valid: true, }, DUP6: { execute: makeDup(6), constantGas: GasFastestStep, minStack: minDupStack(6), maxStack: maxDupStack(6), - valid: true, }, DUP7: { execute: makeDup(7), constantGas: GasFastestStep, minStack: minDupStack(7), maxStack: maxDupStack(7), - valid: true, }, DUP8: { execute: makeDup(8), constantGas: GasFastestStep, minStack: minDupStack(8), maxStack: maxDupStack(8), - valid: true, }, DUP9: { execute: makeDup(9), constantGas: GasFastestStep, minStack: minDupStack(9), maxStack: maxDupStack(9), - valid: true, }, DUP10: { execute: makeDup(10), constantGas: GasFastestStep, minStack: minDupStack(10), maxStack: maxDupStack(10), - valid: true, }, DUP11: { execute: makeDup(11), constantGas: GasFastestStep, minStack: minDupStack(11), maxStack: maxDupStack(11), - valid: true, }, DUP12: { execute: makeDup(12), constantGas: GasFastestStep, minStack: minDupStack(12), maxStack: maxDupStack(12), - valid: true, }, DUP13: { execute: makeDup(13), constantGas: GasFastestStep, minStack: minDupStack(13), maxStack: maxDupStack(13), - valid: true, }, DUP14: { execute: makeDup(14), constantGas: GasFastestStep, minStack: minDupStack(14), maxStack: maxDupStack(14), - valid: true, }, DUP15: { execute: makeDup(15), constantGas: GasFastestStep, minStack: minDupStack(15), maxStack: maxDupStack(15), - valid: true, }, DUP16: { execute: makeDup(16), constantGas: GasFastestStep, minStack: minDupStack(16), maxStack: maxDupStack(16), - valid: true, }, SWAP1: { execute: makeSwap(1), constantGas: GasFastestStep, minStack: minSwapStack(2), maxStack: maxSwapStack(2), - valid: true, }, SWAP2: { execute: makeSwap(2), constantGas: GasFastestStep, minStack: minSwapStack(3), maxStack: maxSwapStack(3), - valid: true, }, SWAP3: { execute: makeSwap(3), constantGas: GasFastestStep, minStack: minSwapStack(4), maxStack: maxSwapStack(4), - valid: true, }, SWAP4: { execute: makeSwap(4), constantGas: GasFastestStep, minStack: minSwapStack(5), maxStack: maxSwapStack(5), - valid: true, }, SWAP5: { execute: makeSwap(5), constantGas: GasFastestStep, minStack: minSwapStack(6), maxStack: maxSwapStack(6), - valid: true, }, SWAP6: { execute: makeSwap(6), constantGas: GasFastestStep, minStack: minSwapStack(7), maxStack: maxSwapStack(7), - valid: true, }, SWAP7: { execute: makeSwap(7), constantGas: GasFastestStep, minStack: minSwapStack(8), maxStack: maxSwapStack(8), - valid: true, }, SWAP8: { execute: makeSwap(8), constantGas: GasFastestStep, minStack: minSwapStack(9), maxStack: maxSwapStack(9), - valid: true, }, SWAP9: { execute: makeSwap(9), constantGas: GasFastestStep, minStack: minSwapStack(10), maxStack: maxSwapStack(10), - valid: true, }, SWAP10: { execute: makeSwap(10), constantGas: GasFastestStep, minStack: minSwapStack(11), maxStack: maxSwapStack(11), - valid: true, }, SWAP11: { execute: makeSwap(11), constantGas: GasFastestStep, minStack: minSwapStack(12), maxStack: maxSwapStack(12), - valid: true, }, SWAP12: { execute: makeSwap(12), constantGas: GasFastestStep, minStack: minSwapStack(13), maxStack: maxSwapStack(13), - valid: true, }, SWAP13: { execute: makeSwap(13), constantGas: GasFastestStep, minStack: minSwapStack(14), maxStack: maxSwapStack(14), - valid: true, }, SWAP14: { execute: makeSwap(14), constantGas: GasFastestStep, minStack: minSwapStack(15), maxStack: maxSwapStack(15), - valid: true, }, SWAP15: { execute: makeSwap(15), constantGas: GasFastestStep, minStack: minSwapStack(16), maxStack: maxSwapStack(16), - valid: true, }, SWAP16: { execute: makeSwap(16), constantGas: GasFastestStep, minStack: minSwapStack(17), maxStack: maxSwapStack(17), - valid: true, }, LOG0: { execute: makeLog(0), @@ -1067,7 +973,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(2, 0), maxStack: maxStack(2, 0), memorySize: memoryLog, - valid: true, writes: true, }, LOG1: { @@ -1076,7 +981,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(3, 0), maxStack: maxStack(3, 0), memorySize: memoryLog, - valid: true, writes: true, }, LOG2: { @@ -1085,7 +989,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(4, 0), maxStack: maxStack(4, 0), memorySize: memoryLog, - valid: true, writes: true, }, LOG3: { @@ -1094,7 +997,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(5, 0), maxStack: maxStack(5, 0), memorySize: memoryLog, - valid: true, writes: true, }, LOG4: { @@ -1103,7 +1005,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(6, 0), maxStack: maxStack(6, 0), memorySize: memoryLog, - valid: true, writes: true, }, CREATE: { @@ -1113,7 +1014,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(3, 1), maxStack: maxStack(3, 1), memorySize: memoryCreate, - valid: true, writes: true, returns: true, }, @@ -1124,7 +1024,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(7, 1), maxStack: maxStack(7, 1), memorySize: memoryCall, - valid: true, returns: true, }, CALLCODE: { @@ -1134,7 +1033,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(7, 1), maxStack: maxStack(7, 1), memorySize: memoryCall, - valid: true, returns: true, }, RETURN: { @@ -1144,7 +1042,6 @@ func newFrontierInstructionSet() JumpTable { maxStack: maxStack(2, 0), memorySize: memoryReturn, halts: true, - valid: true, }, SELFDESTRUCT: { execute: opSuicide, @@ -1152,7 +1049,6 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(1, 0), maxStack: maxStack(1, 0), halts: true, - valid: true, writes: true, }, } diff --git a/core/vm/logger.go b/core/vm/logger.go index e3ecd9e748..d0ed716276 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -18,17 +18,22 @@ package vm import ( "encoding/hex" + "errors" "fmt" "io" "math/big" + "strings" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" ) +var errTraceLimitReached = errors.New("the number of logs reached the specified limit") + // Storage represents a contract's storage. type Storage map[common.Hash]common.Hash @@ -38,20 +43,19 @@ func (s Storage) Copy() Storage { for key, value := range s { cpy[key] = value } - return cpy } -type LogFilter func(pc uint64, op OpCode) bool - // LogConfig are the configuration options for structured logger the EVM type LogConfig struct { - DisableMemory bool // disable memory capture - DisableStack bool // disable stack capture - DisableStorage bool // disable storage capture - Debug bool // print output during capture end - Limit int // maximum length of output, but zero means unlimited - LogFilter LogFilter + DisableMemory bool // disable memory capture + DisableStack bool // disable stack capture + DisableStorage bool // disable storage capture + DisableReturnData bool // disable return data capture + Debug bool // print output during capture end + Limit int // maximum length of output, but zero means unlimited + // Chain overrides, can be used to execute a trace using future fork rules + Overrides *params.ChainConfig `json:"overrides,omitempty"` } //go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go @@ -68,6 +72,8 @@ type StructLog struct { Memory []byte `json:"memory"` MemorySize int `json:"memSize"` Stack []*big.Int `json:"stack"` + ReturnStack []uint32 `json:"returnStack"` + ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` @@ -80,9 +86,11 @@ type StructLog struct { // overrides for gencodec type structLogMarshaling struct { Stack []*math.HexOrDecimal256 + ReturnStack []math.HexOrDecimal64 Gas math.HexOrDecimal64 GasCost math.HexOrDecimal64 Memory hexutil.Bytes + ReturnData hexutil.Bytes OpName string `json:"opName"` // adds call to OpName() in MarshalJSON ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON } @@ -100,8 +108,6 @@ func (s *StructLog) ErrorString() string { return "" } -type HookAfter = func(memory *Memory, stack *Stack) - // Tracer is used to collect execution traces from an EVM transaction // execution. CaptureState is called for each step of the VM with the // current VM state. @@ -109,8 +115,8 @@ type HookAfter = func(memory *Memory, stack *Stack) // if you need to retain them beyond the current call. type Tracer interface { CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error - CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) (HookAfter, error) - CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error + CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error + CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error } @@ -122,16 +128,16 @@ type Tracer interface { type StructLogger struct { cfg LogConfig - logs []*StructLog - changedValues map[common.Address]Storage - output []byte - err error + storage map[common.Address]Storage + logs []StructLog + output []byte + err error } // NewStructLogger returns a new logger func NewStructLogger(cfg *LogConfig) *StructLogger { logger := &StructLogger{ - changedValues: make(map[common.Address]Storage), + storage: make(map[common.Address]Storage), } if cfg != nil { logger.cfg = *cfg @@ -140,39 +146,17 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { +func (l *StructLogger) CaptureStart(_ *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { return nil } // CaptureState logs a new structured log message and pushes it out to the environment // -// CaptureState also tracks SSTORE ops to track dirty values. -func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) (HookAfter, error) { +// CaptureState also tracks SLOAD/SSTORE ops to track storage change. +func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { // check if already accumulated the specified number of logs if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { - return nil, ErrTraceLimitReached - } - - if l.cfg.LogFilter != nil { - if !l.cfg.LogFilter(pc, op) { - return nil, nil - } - } - - // initialise new changed values storage container for this contract - // if not present. - if l.changedValues[contract.Address()] == nil { - l.changedValues[contract.Address()] = make(Storage) - } - - // capture SSTORE opcodes and determine the changed value and store - // it in the local storage container. - if op == SSTORE && stack.len() >= 2 { - var ( - value = common.BigToHash(stack.data[stack.len()-2]) - address = common.BigToHash(stack.data[stack.len()-1]) - ) - l.changedValues[contract.Address()][address] = value + return errTraceLimitReached } // Copy a snapshot of the current memory state to a new buffer var mem []byte @@ -185,13 +169,44 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui if !l.cfg.DisableStack { stck = make([]*big.Int, len(stack.Data())) for i, item := range stack.Data() { - stck[i] = new(big.Int).Set(item) + stck[i] = new(big.Int).Set(item.ToBig()) } } + var rstack []uint32 + if !l.cfg.DisableStack && rStack != nil { + rstck := make([]uint32, len(rStack.data)) + copy(rstck, rStack.data) + } // Copy a snapshot of the current storage to a new container var storage Storage if !l.cfg.DisableStorage { - storage = l.changedValues[contract.Address()].Copy() + // initialise new changed values storage container for this contract + // if not present. + if l.storage[contract.Address()] == nil { + l.storage[contract.Address()] = make(Storage) + } + // capture SLOAD opcodes and record the read entry in the local storage + if op == SLOAD && stack.len() >= 1 { + var ( + address = common.Hash(stack.data[stack.len()-1].Bytes32()) + value = env.StateDB.GetState(contract.Address(), address) + ) + l.storage[contract.Address()][address] = value + } + // capture SSTORE opcodes and record the written entry in the local storage. + if op == SSTORE && stack.len() >= 2 { + var ( + value = common.Hash(stack.data[stack.len()-2].Bytes32()) + address = common.Hash(stack.data[stack.len()-1].Bytes32()) + ) + l.storage[contract.Address()][address] = value + } + storage = l.storage[contract.Address()].Copy() + } + var rdata []byte + if !l.cfg.DisableReturnData { + rdata = make([]byte, len(rData)) + copy(rdata, rData) } var operatorEvent map[string]string @@ -203,35 +218,14 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui } // create a new snapshot of the EVM. - log := &StructLog{pc, op, contract.CallerAddress, contract.Address(), gas, cost, mem, memory.Len(), stck, storage, depth, env.StateDB.GetRefund(), err, nil, nil, operatorEvent} - afterHook := func(memory *Memory, stack *Stack) { - // Copy a snapshot of the current memory state to a new buffer - var mem []byte - if !l.cfg.DisableMemory { - mem = make([]byte, len(memory.Data())) - copy(mem, memory.Data()) - } - - // Copy a snapshot of the current stack state to a new buffer - var stck []*big.Int - if !l.cfg.DisableStack { - stck = make([]*big.Int, len(stack.Data())) - for i, item := range stack.Data() { - stck[i] = new(big.Int).Set(item) - } - } - - log.AfterStack = stck - log.AfterMemory = mem - } - + log := StructLog{pc, op, contract.CallerAddress, contract.Address(), gas, cost, mem, memory.Len(), stck, rstack, rdata, storage, depth, env.StateDB.GetRefund(), err, nil, nil, operatorEvent} l.logs = append(l.logs, log) - return afterHook, nil + return nil } // CaptureFault implements the Tracer interface to trace an execution fault // while running an opcode. -func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { +func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { return nil } @@ -249,7 +243,7 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration } // StructLogs returns the captured log entries. -func (l *StructLogger) StructLogs() []*StructLog { return l.logs } +func (l *StructLogger) StructLogs() []StructLog { return l.logs } // Error returns the VM error captured by the trace. func (l *StructLogger) Error() error { return l.err } @@ -272,6 +266,12 @@ func WriteTrace(writer io.Writer, logs []StructLog) { fmt.Fprintf(writer, "%08d %x\n", len(log.Stack)-i-1, math.PaddedBigBytes(log.Stack[i], 32)) } } + if len(log.ReturnStack) > 0 { + fmt.Fprintln(writer, "ReturnStack:") + for i := len(log.Stack) - 1; i >= 0; i-- { + fmt.Fprintf(writer, "%08d 0x%x (%d)\n", len(log.Stack)-i-1, log.ReturnStack[i], log.ReturnStack[i]) + } + } if len(log.Memory) > 0 { fmt.Fprintln(writer, "Memory:") fmt.Fprint(writer, hex.Dump(log.Memory)) @@ -282,6 +282,10 @@ func WriteTrace(writer io.Writer, logs []StructLog) { fmt.Fprintf(writer, "%x: %x\n", h, item) } } + if len(log.ReturnData) > 0 { + fmt.Fprintln(writer, "ReturnData:") + fmt.Fprint(writer, hex.Dump(log.ReturnData)) + } fmt.Fprintln(writer) } } @@ -299,3 +303,77 @@ func WriteLogs(writer io.Writer, logs []*types.Log) { fmt.Fprintln(writer) } } + +type mdLogger struct { + out io.Writer + cfg *LogConfig +} + +// NewMarkdownLogger creates a logger which outputs information in a format adapted +// for human readability, and is also a valid markdown table +func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger { + l := &mdLogger{writer, cfg} + if l.cfg == nil { + l.cfg = &LogConfig{} + } + return l +} + +func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { + if !create { + fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", + from.String(), to.String(), + input, gas, value) + } else { + fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", + from.String(), to.String(), + input, gas, value) + } + + fmt.Fprintf(t.out, ` +| Pc | Op | Cost | Stack | RStack | Refund | +|-------|-------------|------|-----------|-----------|---------| +`) + return nil +} + +func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { + fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) + + if !t.cfg.DisableStack { + // format stack + var a []string + for _, elem := range stack.data { + a = append(a, fmt.Sprintf("%v", elem.String())) + } + b := fmt.Sprintf("[%v]", strings.Join(a, ",")) + fmt.Fprintf(t.out, "%10v |", b) + + // format return stack + a = a[:0] + for _, elem := range rStack.data { + a = append(a, fmt.Sprintf("%2d", elem)) + } + b = fmt.Sprintf("[%v]", strings.Join(a, ",")) + fmt.Fprintf(t.out, "%10v |", b) + } + fmt.Fprintf(t.out, "%10v |", env.StateDB.GetRefund()) + fmt.Fprintln(t.out, "") + if err != nil { + fmt.Fprintf(t.out, "Error: %v\n", err) + } + return nil +} + +func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { + + fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) + + return nil +} + +func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) error { + fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n", + output, gasUsed, err) + return nil +} diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index fefe06ffff..5ae15a2e2b 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -1,18 +1,18 @@ // Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. +// This file is part of the go-ethereum library. // -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// go-ethereum is distributed in the hope that it will be useful, +// The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. +// GNU Lesser General Public License for more details. // -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . package vm @@ -26,7 +26,6 @@ import ( "github.com/ethereum/go-ethereum/common/math" ) -// JSONLogger ... type JSONLogger struct { encoder *json.Encoder cfg *LogConfig @@ -42,13 +41,12 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger { return l } -// CaptureStart ... func (l *JSONLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { return nil } // CaptureState outputs state information on the logger. -func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) (HookAfter, error) { +func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { log := StructLog{ Pc: pc, Op: op, @@ -66,13 +64,22 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint log.Memory = memory.Data() } if !l.cfg.DisableStack { - log.Stack = stack.Data() + //TODO(@holiman) improve this + logstack := make([]*big.Int, len(stack.Data())) + for i, item := range stack.Data() { + logstack[i] = item.ToBig() + } + log.Stack = logstack + log.ReturnStack = rStack.data } - return nil, l.encoder.Encode(log) + if !l.cfg.DisableReturnData { + log.ReturnData = rData + } + return l.encoder.Encode(log) } // CaptureFault outputs state information on the logger. -func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { +func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { return nil } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index 191733eed2..2f08d5dca8 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -21,17 +21,19 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/internal/params" + "github.com/holiman/uint256" ) type dummyContractRef struct { calledForEach bool } -func (dummyContractRef) ReturnGas(*big.Int) {} -func (dummyContractRef) Address() common.Address { return common.Address{} } -func (dummyContractRef) Value() *big.Int { return new(big.Int) } -func (dummyContractRef) SetCode(common.Hash, []byte, bool) {} +func (dummyContractRef) ReturnGas(*big.Int) {} +func (dummyContractRef) Address() common.Address { return common.Address{} } +func (dummyContractRef) Value() *big.Int { return new(big.Int) } +func (dummyContractRef) SetCode(common.Hash, []byte) {} func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) { d.calledForEach = true } @@ -42,28 +44,29 @@ func (d *dummyContractRef) SetNonce(uint64) {} func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } type dummyStatedb struct { - StateDB + state.DB } func (*dummyStatedb) GetRefund() uint64 { return 1337 } func TestStoreCapture(t *testing.T) { var ( - env = NewEVM(Context{}, &dummyStatedb{}, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{}) logger = NewStructLogger(nil) mem = NewMemory() stack = newstack() + rstack = newReturnStack() contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 0) ) - stack.push(big.NewInt(1)) - stack.push(big.NewInt(0)) + stack.push(uint256.NewInt(0).SetUint64(1)) + stack.push(uint256.NewInt(0)) var index common.Hash - logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, contract, 0, nil) - if len(logger.changedValues[contract.Address()]) == 0 { - t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()])) + logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, nil, contract, 0, nil) + if len(logger.storage[contract.Address()]) == 0 { + t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()])) } exp := common.BigToHash(big.NewInt(1)) - if logger.changedValues[contract.Address()][index] != exp { - t.Errorf("expected %x, got %x", exp, logger.changedValues[contract.Address()][index]) + if logger.storage[contract.Address()][index] != exp { + t.Errorf("expected %x, got %x", exp, logger.storage[contract.Address()][index]) } } diff --git a/core/vm/memory.go b/core/vm/memory.go index c06b0054b8..dcc7099569 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -18,9 +18,8 @@ package vm import ( "fmt" - "math/big" - "github.com/ethereum/go-ethereum/common/math" + "github.com/holiman/uint256" ) // Memory implements a simple memory model for the ethereum virtual machine. @@ -50,7 +49,7 @@ func (m *Memory) Set(offset, size uint64, value []byte) { // Set32 sets the 32 bytes starting at offset to the value of val, left-padded with zeroes to // 32 bytes. -func (m *Memory) Set32(offset uint64, val *big.Int) { +func (m *Memory) Set32(offset uint64, val *uint256.Int) { // length of store may never be less than offset + size. // The store should be resized PRIOR to setting the memory if offset+32 > uint64(len(m.store)) { @@ -59,7 +58,7 @@ func (m *Memory) Set32(offset uint64, val *big.Int) { // Zero the memory area copy(m.store[offset:offset+32], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) // Fill in relevant bits - math.ReadBits(val, m.store[offset:offset+32]) + val.WriteToSlice(m.store[offset:]) } // Resize resizes the memory to size diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 9faafaf0a3..72ca2b8eb3 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -101,26 +101,30 @@ const ( NUMBER DIFFICULTY GASLIMIT - CHAINID = 0x46 - SELFBALANCE = 0x47 + CHAINID = 0x46 + SELFBALANCE = 0x47 + BASEFEE OpCode = 0x48 ) // 0x50 range - 'storage' and execution. const ( - POP OpCode = 0x50 + iota - MLOAD - MSTORE - MSTORE8 - SLOAD - SSTORE - JUMP - JUMPI - PC - MSIZE - GAS - JUMPDEST - TLOAD // 0x5c - TSTORE // 0x5d + POP OpCode = 0x50 + MLOAD OpCode = 0x51 + MSTORE OpCode = 0x52 + MSTORE8 OpCode = 0x53 + SLOAD OpCode = 0x54 + SSTORE OpCode = 0x55 + JUMP OpCode = 0x56 + JUMPI OpCode = 0x57 + PC OpCode = 0x58 + MSIZE OpCode = 0x59 + GAS OpCode = 0x5a + JUMPDEST OpCode = 0x5b + BEGINSUB OpCode = 0x5c + RETURNSUB OpCode = 0x5d + JUMPSUB OpCode = 0x5e + //TLOAD OpCode = 0x5f // 0x5c + //TSTORE OpCode = 0x60 // 0x5d ) // 0x60 range. @@ -283,6 +287,7 @@ var opCodeToString = map[OpCode]string{ GASLIMIT: "GASLIMIT", CHAINID: "CHAINID", SELFBALANCE: "SELFBALANCE", + BASEFEE: "BASEFEE", // 0x50 range - 'storage' and execution. POP: "POP", @@ -299,8 +304,8 @@ var opCodeToString = map[OpCode]string{ MSIZE: "MSIZE", GAS: "GAS", JUMPDEST: "JUMPDEST", - TLOAD: "TLOAD", - TSTORE: "TSTORE", + //TLOAD: "TLOAD", + //TSTORE: "TSTORE", // 0x60 range - push. PUSH1: "PUSH1", @@ -466,84 +471,84 @@ var stringToOp = map[string]OpCode{ "MSIZE": MSIZE, "GAS": GAS, "JUMPDEST": JUMPDEST, - "TLOAD": TLOAD, - "TSTORE": TSTORE, - "PUSH1": PUSH1, - "PUSH2": PUSH2, - "PUSH3": PUSH3, - "PUSH4": PUSH4, - "PUSH5": PUSH5, - "PUSH6": PUSH6, - "PUSH7": PUSH7, - "PUSH8": PUSH8, - "PUSH9": PUSH9, - "PUSH10": PUSH10, - "PUSH11": PUSH11, - "PUSH12": PUSH12, - "PUSH13": PUSH13, - "PUSH14": PUSH14, - "PUSH15": PUSH15, - "PUSH16": PUSH16, - "PUSH17": PUSH17, - "PUSH18": PUSH18, - "PUSH19": PUSH19, - "PUSH20": PUSH20, - "PUSH21": PUSH21, - "PUSH22": PUSH22, - "PUSH23": PUSH23, - "PUSH24": PUSH24, - "PUSH25": PUSH25, - "PUSH26": PUSH26, - "PUSH27": PUSH27, - "PUSH28": PUSH28, - "PUSH29": PUSH29, - "PUSH30": PUSH30, - "PUSH31": PUSH31, - "PUSH32": PUSH32, - "DUP1": DUP1, - "DUP2": DUP2, - "DUP3": DUP3, - "DUP4": DUP4, - "DUP5": DUP5, - "DUP6": DUP6, - "DUP7": DUP7, - "DUP8": DUP8, - "DUP9": DUP9, - "DUP10": DUP10, - "DUP11": DUP11, - "DUP12": DUP12, - "DUP13": DUP13, - "DUP14": DUP14, - "DUP15": DUP15, - "DUP16": DUP16, - "SWAP1": SWAP1, - "SWAP2": SWAP2, - "SWAP3": SWAP3, - "SWAP4": SWAP4, - "SWAP5": SWAP5, - "SWAP6": SWAP6, - "SWAP7": SWAP7, - "SWAP8": SWAP8, - "SWAP9": SWAP9, - "SWAP10": SWAP10, - "SWAP11": SWAP11, - "SWAP12": SWAP12, - "SWAP13": SWAP13, - "SWAP14": SWAP14, - "SWAP15": SWAP15, - "SWAP16": SWAP16, - "LOG0": LOG0, - "LOG1": LOG1, - "LOG2": LOG2, - "LOG3": LOG3, - "LOG4": LOG4, - "CREATE": CREATE, - "CREATE2": CREATE2, - "CALL": CALL, - "RETURN": RETURN, - "CALLCODE": CALLCODE, - "REVERT": REVERT, - "SELFDESTRUCT": SELFDESTRUCT, + //"TLOAD": TLOAD, + //"TSTORE": TSTORE, + "PUSH1": PUSH1, + "PUSH2": PUSH2, + "PUSH3": PUSH3, + "PUSH4": PUSH4, + "PUSH5": PUSH5, + "PUSH6": PUSH6, + "PUSH7": PUSH7, + "PUSH8": PUSH8, + "PUSH9": PUSH9, + "PUSH10": PUSH10, + "PUSH11": PUSH11, + "PUSH12": PUSH12, + "PUSH13": PUSH13, + "PUSH14": PUSH14, + "PUSH15": PUSH15, + "PUSH16": PUSH16, + "PUSH17": PUSH17, + "PUSH18": PUSH18, + "PUSH19": PUSH19, + "PUSH20": PUSH20, + "PUSH21": PUSH21, + "PUSH22": PUSH22, + "PUSH23": PUSH23, + "PUSH24": PUSH24, + "PUSH25": PUSH25, + "PUSH26": PUSH26, + "PUSH27": PUSH27, + "PUSH28": PUSH28, + "PUSH29": PUSH29, + "PUSH30": PUSH30, + "PUSH31": PUSH31, + "PUSH32": PUSH32, + "DUP1": DUP1, + "DUP2": DUP2, + "DUP3": DUP3, + "DUP4": DUP4, + "DUP5": DUP5, + "DUP6": DUP6, + "DUP7": DUP7, + "DUP8": DUP8, + "DUP9": DUP9, + "DUP10": DUP10, + "DUP11": DUP11, + "DUP12": DUP12, + "DUP13": DUP13, + "DUP14": DUP14, + "DUP15": DUP15, + "DUP16": DUP16, + "SWAP1": SWAP1, + "SWAP2": SWAP2, + "SWAP3": SWAP3, + "SWAP4": SWAP4, + "SWAP5": SWAP5, + "SWAP6": SWAP6, + "SWAP7": SWAP7, + "SWAP8": SWAP8, + "SWAP9": SWAP9, + "SWAP10": SWAP10, + "SWAP11": SWAP11, + "SWAP12": SWAP12, + "SWAP13": SWAP13, + "SWAP14": SWAP14, + "SWAP15": SWAP15, + "SWAP16": SWAP16, + "LOG0": LOG0, + "LOG1": LOG1, + "LOG2": LOG2, + "LOG3": LOG3, + "LOG4": LOG4, + "CREATE": CREATE, + "CREATE2": CREATE2, + "CALL": CALL, + "RETURN": RETURN, + "CALLCODE": CALLCODE, + "REVERT": REVERT, + "SELFDESTRUCT": SELFDESTRUCT, } // StringToOp finds the opcode whose name is stored in `str`. diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go new file mode 100644 index 0000000000..55e8331c42 --- /dev/null +++ b/core/vm/operations_acl.go @@ -0,0 +1,323 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" +) + +const ( + ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST + ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST + WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST +) + +func makeGasSStoreFunc(clearingRefund uint64) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGasEIP2200 { + return 0, errors.New("not enough gas for reentrancy sentry") + } + // Gas sentry honoured, do the actual gas calculation based on the stored value + var ( + y, x = stack.Back(1), stack.peek() + slot = common.Hash(x.Bytes32()) + current = evm.StateDB.GetState(contract.Address(), slot) + cost = uint64(0) + ) + // Check slot presence in the access list + if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + cost = params.ColdSloadCostEIP2929 + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + if !addrPresent { + // Once we're done with YOLOv2 and schedule this for mainnet, might + // be good to remove this panic here, which is just really a + // canary to have during testing + panic("impossible case: address was not present in access list during sstore op") + } + } + value := common.Hash(y.Bytes32()) + + if current == value { // noop (1) + // EIP 2200 original clause: + // return params.SloadGasEIP2200, nil + return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS + } + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return cost + params.SstoreSetGasEIP2200, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(clearingRefund) + } + // EIP-2200 original clause: + // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) + return cost + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929), nil // write existing slot (2.1.2) + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(clearingRefund) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(clearingRefund) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + // EIP 2200 Original clause: + //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.WarmStorageReadCostEIP2929) + } else { // reset to original existing slot (2.2.2.2) + // EIP 2200 Original clause: + // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) + // - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST) + // - SLOAD_GAS redefined as WARM_STORAGE_READ_COST + // Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST + evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929) + } + } + // EIP-2200 original clause: + //return params.SloadGasEIP2200, nil // dirty update (2.2) + return cost + params.WarmStorageReadCostEIP2929, nil // dirty update (2.2) + } +} + +// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929" +// +// When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys. +// If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys. +// Additionally, modify the parameters defined in EIP 2200 as follows: +// +// Parameter Old value New value +// SLOAD_GAS 800 = WARM_STORAGE_READ_COST +// SSTORE_RESET_GAS 5000 5000 - COLD_SLOAD_COST +// +// The other parameters defined in EIP 2200 are unchanged. +// see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified +func gasSStoreEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGasEIP2200 { + return 0, errors.New("not enough gas for reentrancy sentry") + } + // Gas sentry honoured, do the actual gas calculation based on the stored value + var ( + y, x = stack.Back(1), stack.peek() + slot = common.Hash(x.Bytes32()) + current = evm.StateDB.GetState(contract.Address(), slot) + cost = uint64(0) + ) + // Check slot presence in the access list + if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + cost = ColdSloadCostEIP2929 + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + if !addrPresent { + // Once we're done with YOLOv2 and schedule this for mainnet, might + // be good to remove this panic here, which is just really a + // canary to have during testing + panic("impossible case: address was not present in access list during sstore op") + } + } + value := common.Hash(y.Bytes32()) + + if current == value { // noop (1) + // EIP 2200 original clause: + // return params.SloadGasEIP2200, nil + return cost + WarmStorageReadCostEIP2929, nil // SLOAD_GAS + } + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return cost + params.SstoreSetGasEIP2200, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) + } + // EIP-2200 original clause: + // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) + return cost + (params.SstoreResetGasEIP2200 - ColdSloadCostEIP2929), nil // write existing slot (2.1.2) + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + // EIP 2200 Original clause: + //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - WarmStorageReadCostEIP2929) + } else { // reset to original existing slot (2.2.2.2) + // EIP 2200 Original clause: + // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) + // - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST) + // - SLOAD_GAS redefined as WARM_STORAGE_READ_COST + // Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST + evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - ColdSloadCostEIP2929) - WarmStorageReadCostEIP2929) + } + } + // EIP-2200 original clause: + //return params.SloadGasEIP2200, nil // dirty update (2.2) + return cost + WarmStorageReadCostEIP2929, nil // dirty update (2.2) +} + +// gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929 +// For SLOAD, if the (address, storage_key) pair (where address is the address of the contract +// whose storage is being read) is not yet in accessed_storage_keys, +// charge 2100 gas and add the pair to accessed_storage_keys. +// If the pair is already in accessed_storage_keys, charge 100 gas. +func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + loc := stack.peek() + slot := common.Hash(loc.Bytes32()) + // Check slot presence in the access list + if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + // If the caller cannot afford the cost, this change will be rolled back + // If he does afford it, we can skip checking the same thing later on, during execution + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + return ColdSloadCostEIP2929, nil + } + return WarmStorageReadCostEIP2929, nil +} + +// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929 +// EIP spec: +// > If the target is not in accessed_addresses, +// > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses. +// > Otherwise, charge WARM_STORAGE_READ_COST gas. +func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // memory expansion first (dynamic part of pre-2929 implementation) + gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + addr := common.Address(stack.peek().Bytes20()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + evm.StateDB.AddAddressToAccessList(addr) + var overflow bool + // We charge (cold-warm), since 'warm' is already charged as constantGas + if gas, overflow = math.SafeAdd(gas, ColdAccountAccessCostEIP2929-WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } + return gas, nil +} + +// gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list. +// If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it +// is also using 'warm' as constant factor. +// This method is used by: +// - extcodehash, +// - extcodesize, +// - (ext) balance +func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(stack.peek().Bytes20()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(addr) + // The warm storage read cost is already charged as constantGas + return ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929, nil + } + return 0, nil +} + +func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(stack.Back(1).Bytes20()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + evm.StateDB.AddAddressToAccessList(addr) + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost + if !contract.UseGas(ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929) { + return 0, ErrOutOfGas + } + } + // Now call the old calculator, which takes into account + // - create new account + // - transfer value + // - memory expansion + // - 63/64ths rule + return oldCalculator(evm, contract, stack, mem, memorySize) + } +} + +var ( + gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall) + gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall) + gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall) + gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode) + + gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) + + // gasSStoreEIP2539 implements gas cost for SSTORE according to EIP-2539 + // Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800) + gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) +) + +func gasSelfdestructEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + address = common.Address(stack.peek().Bytes20()) + ) + if !evm.StateDB.AddressInAccessList(address) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(address) + gas = ColdAccountAccessCostEIP2929 + } + // if empty and transfers value + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + gas += params.CreateBySelfdestructGas + } + if !evm.StateDB.HasSuicided(contract.Address()) { + evm.StateDB.AddRefund(params.SelfdestructRefundGas) + } + return gas, nil + +} + +// makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-2539 +func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { + gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + address = common.Address(stack.peek().Bytes20()) + ) + if !evm.StateDB.AddressInAccessList(address) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(address) + gas = params.ColdAccountAccessCostEIP2929 + } + // if empty and transfers value + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + gas += params.CreateBySelfdestructGas + } + if refundsEnabled && !evm.StateDB.HasSuicided(contract.Address()) { + evm.StateDB.AddRefund(params.SelfdestructRefundGas) + } + return gas, nil + } + return gasFunc +} diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index ab37b97093..9668728019 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -24,22 +24,25 @@ import ( // NewEnv ... func NewEnv(cfg *Config) *vm.EVM { - context := vm.Context{ + context := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, IsValidator: core.IsValidator, GetHash: func(uint64) common.Hash { return common.Hash{} }, GetVRF: func(uint64) common.Hash { return common.Hash{} }, - Origin: cfg.Origin, + //Origin: cfg.Origin, Coinbase: cfg.Coinbase, BlockNumber: cfg.BlockNumber, EpochNumber: cfg.EpochNumber, VRF: cfg.VRF, Time: cfg.Time, GasLimit: cfg.GasLimit, - GasPrice: cfg.GasPrice, + //GasPrice: cfg.GasPrice, } - return vm.NewEVM(context, cfg.State, cfg.ChainConfig, cfg.EVMConfig) + return vm.NewEVM(context, vm.TxContext{ + Origin: cfg.Origin, + GasPrice: cfg.GasPrice, + }, cfg.State, cfg.ChainConfig, cfg.EVMConfig) } diff --git a/core/vm/stack.go b/core/vm/stack.go index c9c3d07f4b..af27d6552c 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -18,36 +18,48 @@ package vm import ( "fmt" - "math/big" + "sync" + + "github.com/holiman/uint256" ) +var stackPool = sync.Pool{ + New: func() interface{} { + return &Stack{data: make([]uint256.Int, 0, 16)} + }, +} + // Stack is an object for basic stack operations. Items popped to the stack are // expected to be changed and modified. stack does not take care of adding newly // initialised objects. type Stack struct { - data []*big.Int + data []uint256.Int } func newstack() *Stack { - return &Stack{data: make([]*big.Int, 0, 1024)} + return stackPool.Get().(*Stack) } -// Data returns the underlying big.Int array. -func (st *Stack) Data() []*big.Int { +func returnStack(s *Stack) { + s.data = s.data[:0] + stackPool.Put(s) +} + +// Data returns the underlying uint256.Int array. +func (st *Stack) Data() []uint256.Int { return st.data } -func (st *Stack) push(d *big.Int) { +func (st *Stack) push(d *uint256.Int) { // NOTE push limit (1024) is checked in baseCheck - //stackItem := new(big.Int).Set(d) - //st.data = append(st.data, stackItem) - st.data = append(st.data, d) + st.data = append(st.data, *d) } -func (st *Stack) pushN(ds ...*big.Int) { +func (st *Stack) pushN(ds ...uint256.Int) { + // FIXME: Is there a way to pass args by pointers. st.data = append(st.data, ds...) } -func (st *Stack) pop() (ret *big.Int) { +func (st *Stack) pop() (ret uint256.Int) { ret = st.data[len(st.data)-1] st.data = st.data[:len(st.data)-1] return @@ -61,17 +73,17 @@ func (st *Stack) swap(n int) { st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n] } -func (st *Stack) dup(pool *intPool, n int) { - st.push(pool.get().Set(st.data[st.len()-n])) +func (st *Stack) dup(n int) { + st.push(&st.data[st.len()-n]) } -func (st *Stack) peek() *big.Int { - return st.data[st.len()-1] +func (st *Stack) peek() *uint256.Int { + return &st.data[st.len()-1] } // Back returns the n'th item in stack -func (st *Stack) Back(n int) *big.Int { - return st.data[st.len()-n-1] +func (st *Stack) Back(n int) *uint256.Int { + return &st.data[st.len()-n-1] } // Print dumps the content of the stack @@ -86,3 +98,34 @@ func (st *Stack) Print() { } fmt.Println("#############") } + +var rStackPool = sync.Pool{ + New: func() interface{} { + return &ReturnStack{data: make([]uint32, 0, 10)} + }, +} + +// ReturnStack is an object for basic return stack operations. +type ReturnStack struct { + data []uint32 +} + +func newReturnStack() *ReturnStack { + return rStackPool.Get().(*ReturnStack) +} + +func returnRStack(rs *ReturnStack) { + rs.data = rs.data[:0] + rStackPool.Put(rs) +} + +func (st *ReturnStack) push(d uint32) { + st.data = append(st.data, d) +} + +// A uint32 is sufficient as for code below 4.2G +func (st *ReturnStack) pop() (ret uint32) { + ret = st.data[len(st.data)-1] + st.data = st.data[:len(st.data)-1] + return +} diff --git a/hmy/hmy.go b/hmy/hmy.go index c82ea7e78e..4081538fd1 100644 --- a/hmy/hmy.go +++ b/hmy/hmy.go @@ -240,8 +240,8 @@ func (hmy *Harmony) GetNodeMetadata() commonRPC.NodeMetadata { // GetEVM returns a new EVM entity func (hmy *Harmony) GetEVM(ctx context.Context, msg core.Message, state *state.DB, header *block.Header) (*vm.EVM, error) { state.SetBalance(msg.From(), math.MaxBig256) - vmCtx := core.NewEVMContext(msg, header, hmy.BlockChain, nil) - return vm.NewEVM(vmCtx, state, hmy.BlockChain.Config(), *hmy.BlockChain.GetVMConfig()), nil + vmCtx := core.NewEVMBlockContext(msg, header, hmy.BlockChain, nil) + return vm.NewEVM(vmCtx, core.NewEVMTxContext(msg), state, hmy.BlockChain.Config(), *hmy.BlockChain.GetVMConfig()), nil } // ChainDb .. diff --git a/hmy/override.go b/hmy/override.go index dfe7431671..50a7955cdf 100644 --- a/hmy/override.go +++ b/hmy/override.go @@ -121,7 +121,7 @@ type BlockOverrides struct { // apply overrides the given header fields into the given block context // difficulty & random not part of vm.Context -func (b *BlockOverrides) Apply(blockCtx *vm.Context) { +func (b *BlockOverrides) Apply(blockCtx *vm.BlockContext) { if b == nil { return } diff --git a/hmy/override_test.go b/hmy/override_test.go index 5f07bcbd14..8b25d6a0cd 100644 --- a/hmy/override_test.go +++ b/hmy/override_test.go @@ -173,7 +173,7 @@ func TestBlockOverrides(t *testing.T) { // Difficulty, PrevRandao, BaseFeePerGas, and BlobBaseFee are no-op } - ctx := &vm.Context{ + ctx := &vm.BlockContext{ BlockNumber: big.NewInt(0), Time: big.NewInt(0), GasLimit: 0, diff --git a/hmy/tracer.go b/hmy/tracer.go index b0af45820c..7fdf79ea38 100644 --- a/hmy/tracer.go +++ b/hmy/tracer.go @@ -175,7 +175,7 @@ func (hmy *Harmony) TraceChain(ctx context.Context, start, end *types.Block, con signer = ethSigner } msg, _ := tx.AsMessage(signer) - vmCtx := core.NewEVMContext(msg, task.block.Header(), hmy.BlockChain, nil) + vmCtx := core.NewEVMBlockContext(msg, task.block.Header(), hmy.BlockChain, nil) res, err := hmy.TraceTx(ctx, msg, vmCtx, task.statedb, config) if err != nil { @@ -384,7 +384,7 @@ traceLoop: msg, _ := tx.AsMessage(signer) statedb.SetTxContext(tx.Hash(), blockHash, i) statedb.SetTxHashETH(tx.ConvertToEth().Hash()) - vmctx := core.NewEVMContext(msg, block.Header(), hmy.BlockChain, nil) + vmctx := core.NewEVMBlockContext(msg, block.Header(), hmy.BlockChain, nil) res, err := hmy.TraceTx(ctx, msg, vmctx, statedb, config) if err != nil { results[i] = &TxTraceResult{Error: err.Error()} @@ -466,7 +466,7 @@ func (hmy *Harmony) TraceBlock(ctx context.Context, block *types.Block, config * } msg, _ := txs[task.index].AsMessage(signer) - vmctx := core.NewEVMContext(msg, block.Header(), hmy.BlockChain, nil) + vmctx := core.NewEVMBlockContext(msg, block.Header(), hmy.BlockChain, nil) tx := txs[task.index] task.statedb.SetTxContext(tx.Hash(), blockHash, task.index) task.statedb.SetTxHashETH(tx.ConvertToEth().Hash()) @@ -493,9 +493,9 @@ func (hmy *Harmony) TraceBlock(ctx context.Context, block *types.Block, config * msg, _ := tx.AsMessage(signer) statedb.SetTxContext(tx.Hash(), block.Hash(), i) statedb.SetTxHashETH(tx.ConvertToEth().Hash()) - vmctx := core.NewEVMContext(msg, block.Header(), hmy.BlockChain, nil) + vmctx := core.NewEVMBlockContext(msg, block.Header(), hmy.BlockChain, nil) - vmenv := vm.NewEVM(vmctx, statedb, hmy.BlockChain.Config(), vm.Config{}) + vmenv := vm.NewEVM(vmctx, core.NewEVMTxContext(msg), statedb, hmy.BlockChain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { failed = err break @@ -566,7 +566,7 @@ func (hmy *Harmony) standardTraceBlockToFile(ctx context.Context, block *types.B // Prepare the transaction for un-traced execution var ( msg, _ = tx.AsMessage(signer) - vmctx = core.NewEVMContext(msg, block.Header(), hmy.BlockChain, nil) + vmctx = core.NewEVMBlockContext(msg, block.Header(), hmy.BlockChain, nil) vmConf vm.Config dump *os.File @@ -593,7 +593,7 @@ func (hmy *Harmony) standardTraceBlockToFile(ctx context.Context, block *types.B } } // Execute the transaction and flush any traces to disk - vmenv := vm.NewEVM(vmctx, statedb, hmy.BlockChain.Config(), vmConf) + vmenv := vm.NewEVM(vmctx, core.NewEVMTxContext(msg), statedb, hmy.BlockChain.Config(), vmConf) _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) if writer != nil { writer.Flush() @@ -710,7 +710,7 @@ func (hmy *Harmony) ComputeStateDB(block *types.Block, reexec uint64) (*state.DB // executes the given message in the provided environment. The return value will // be tracer dependent. // NOTE: Only support default StructLogger tracer -func (hmy *Harmony) TraceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.DB, config *TraceConfig) (interface{}, error) { +func (hmy *Harmony) TraceTx(ctx context.Context, message core.Message, vmctx vm.BlockContext, statedb *state.DB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.Tracer @@ -751,7 +751,7 @@ func (hmy *Harmony) TraceTx(ctx context.Context, message core.Message, vmctx vm. tracer = vm.NewStructLogger(config.LogConfig) } // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(vmctx, statedb, hmy.BlockChain.Config(), vm.Config{Debug: true, Tracer: tracer}) + vmenv := vm.NewEVM(vmctx, core.NewEVMTxContext(message), statedb, hmy.BlockChain.Config(), vm.Config{Debug: true, Tracer: tracer}) result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { @@ -780,19 +780,19 @@ func (hmy *Harmony) TraceTx(ctx context.Context, message core.Message, vmctx vm. } // ComputeTxEnv returns the execution environment of a certain transaction. -func (hmy *Harmony) ComputeTxEnv(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.Context, *state.DB, error) { +func (hmy *Harmony) ComputeTxEnv(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.DB, error) { // Create the parent state database parent := hmy.BlockChain.GetBlock(block.ParentHash(), block.NumberU64()-1) if parent == nil { - return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + return nil, vm.BlockContext{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) } statedb, err := hmy.ComputeStateDB(parent, reexec) if err != nil { - return nil, vm.Context{}, nil, err + return nil, vm.BlockContext{}, nil, err } if txIndex == 0 && len(block.Transactions()) == 0 { - return nil, vm.Context{}, statedb, nil + return nil, vm.BlockContext{}, statedb, nil } // Recompute transactions up to the target index. @@ -807,23 +807,23 @@ func (hmy *Harmony) ComputeTxEnv(block *types.Block, txIndex int, reexec uint64) // Assemble the transaction call message and return if the requested offset msg, _ := tx.AsMessage(signer) - context := core.NewEVMContext(msg, block.Header(), hmy.BlockChain, nil) + context := core.NewEVMBlockContext(msg, block.Header(), hmy.BlockChain, nil) if idx == txIndex { return msg, context, statedb, nil } // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, hmy.BlockChain.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, core.NewEVMTxContext(msg), statedb, hmy.BlockChain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.GasLimit())); err != nil { - return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state statedb.Finalise(true) } - return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } // ComputeTxEnvEachBlockWithoutApply returns the execution environment of a certain transaction. -func (hmy *Harmony) ComputeTxEnvEachBlockWithoutApply(block *types.Block, reexec uint64, cb func(int, *types.Transaction, core.Message, vm.Context, *state.DB) bool) error { +func (hmy *Harmony) ComputeTxEnvEachBlockWithoutApply(block *types.Block, reexec uint64, cb func(int, *types.Transaction, core.Message, vm.BlockContext, *state.DB) bool) error { // Create the parent state database parent := hmy.BlockChain.GetBlock(block.ParentHash(), block.NumberU64()-1) if parent == nil { @@ -846,7 +846,7 @@ func (hmy *Harmony) ComputeTxEnvEachBlockWithoutApply(block *types.Block, reexec // Assemble the transaction call message and return if the requested offset msg, _ := tx.AsMessage(signer) - context := core.NewEVMContext(msg, block.Header(), hmy.BlockChain, nil) + context := core.NewEVMBlockContext(msg, block.Header(), hmy.BlockChain, nil) if !cb(idx, tx, msg, context, statedb) { return nil } @@ -965,7 +965,7 @@ func (r *StructLogRes) GetOperatorEvent(key string) string { } // FormatLogs formats EVM returned structured logs for json output -func FormatLogs(logs []*vm.StructLog, conf *TraceConfig) []StructLogRes { +func FormatLogs(logs []vm.StructLog, conf *TraceConfig) []StructLogRes { formatted := make([]StructLogRes, len(logs)) for index, trace := range logs { formatted[index] = StructLogRes{ diff --git a/hmy/tracers/block_tracer.go b/hmy/tracers/block_tracer.go index 12391c6fb9..cb6e0a8cec 100644 --- a/hmy/tracers/block_tracer.go +++ b/hmy/tracers/block_tracer.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" "github.com/harmony-one/harmony/core/vm" + "github.com/holiman/uint256" ) type action struct { @@ -41,7 +42,7 @@ type action struct { gasUsed uint64 outOff int64 outLen int64 - value *big.Int + value *uint256.Int err error revert []byte subCalls []*action @@ -64,7 +65,7 @@ func (c *action) fromStorage(blockStorage *TraceBlockStorage, acStorage *ActionS if c.op == vm.CREATE || c.op == vm.CREATE2 { fromIndex := int(acStorage.readNumber().Int64()) toIndex := int(acStorage.readNumber().Int64()) - c.value = acStorage.readNumber() + c.value, _ = uint256.FromBig(acStorage.readNumber()) inputIndex := int(acStorage.readNumber().Int64()) outputIndex := int(acStorage.readNumber().Int64()) c.gas = acStorage.readNumber().Uint64() @@ -78,7 +79,7 @@ func (c *action) fromStorage(blockStorage *TraceBlockStorage, acStorage *ActionS if c.op == vm.CALL || c.op == vm.CALLCODE || c.op == vm.DELEGATECALL || c.op == vm.STATICCALL { fromIndex := int(acStorage.readNumber().Int64()) toIndex := int(acStorage.readNumber().Int64()) - c.value = acStorage.readNumber() + c.value, _ = uint256.FromBig(acStorage.readNumber()) inputIndex := int(acStorage.readNumber().Int64()) outputIndex := int(acStorage.readNumber().Int64()) c.gas = acStorage.readNumber().Uint64() @@ -94,7 +95,7 @@ func (c *action) fromStorage(blockStorage *TraceBlockStorage, acStorage *ActionS toIndex := int(acStorage.readNumber().Int64()) c.from = blockStorage.getAddress(fromIndex) c.to = blockStorage.getAddress(toIndex) - c.value = acStorage.readNumber() + c.value, _ = uint256.FromBig(acStorage.readNumber()) } } @@ -111,44 +112,44 @@ func (c action) toStorage(blockStorage *TraceBlockStorage) *ActionStorage { acStorage.appendByte(errByte) if errByte != 0 { revertIndex := blockStorage.indexData(c.revert) - acStorage.appendNumber(big.NewInt(int64(revertIndex))) + acStorage.appendNumber(uint256.NewInt(uint64(revertIndex))) } if c.op == vm.CREATE || c.op == vm.CREATE2 { fromIndex := blockStorage.indexAddress(c.from) toIndex := blockStorage.indexAddress(c.to) inputIndex := blockStorage.indexData(c.input) outputIndex := blockStorage.indexData(c.output) - acStorage.appendNumber(big.NewInt(int64(fromIndex))) - acStorage.appendNumber(big.NewInt(int64(toIndex))) + acStorage.appendNumber(uint256.NewInt(uint64(fromIndex))) + acStorage.appendNumber(uint256.NewInt(uint64(toIndex))) acStorage.appendNumber(c.value) - acStorage.appendNumber(big.NewInt(int64(inputIndex))) - acStorage.appendNumber(big.NewInt(int64(outputIndex))) - acStorage.appendNumber((&big.Int{}).SetUint64(c.gas)) - acStorage.appendNumber((&big.Int{}).SetUint64(c.gasUsed)) + acStorage.appendNumber(uint256.NewInt(uint64(inputIndex))) + acStorage.appendNumber(uint256.NewInt(uint64(outputIndex))) + acStorage.appendNumber((&uint256.Int{}).SetUint64(c.gas)) + acStorage.appendNumber((&uint256.Int{}).SetUint64(c.gasUsed)) return acStorage } if c.op == vm.CALL || c.op == vm.CALLCODE || c.op == vm.DELEGATECALL || c.op == vm.STATICCALL { if c.value == nil { - c.value = big.NewInt(0) + c.value = uint256.NewInt(0) } fromIndex := blockStorage.indexAddress(c.from) toIndex := blockStorage.indexAddress(c.to) inputIndex := blockStorage.indexData(c.input) outputIndex := blockStorage.indexData(c.output) - acStorage.appendNumber(big.NewInt(int64(fromIndex))) - acStorage.appendNumber(big.NewInt(int64(toIndex))) + acStorage.appendNumber(uint256.NewInt(uint64((fromIndex)))) + acStorage.appendNumber(uint256.NewInt(uint64(toIndex))) acStorage.appendNumber(c.value) - acStorage.appendNumber(big.NewInt(int64(inputIndex))) - acStorage.appendNumber(big.NewInt(int64(outputIndex))) - acStorage.appendNumber((&big.Int{}).SetUint64(c.gas)) - acStorage.appendNumber((&big.Int{}).SetUint64(c.gasUsed)) + acStorage.appendNumber(uint256.NewInt(uint64(inputIndex))) + acStorage.appendNumber(uint256.NewInt(uint64(outputIndex))) + acStorage.appendNumber((&uint256.Int{}).SetUint64(c.gas)) + acStorage.appendNumber((&uint256.Int{}).SetUint64(c.gasUsed)) return acStorage } if c.op == vm.SELFDESTRUCT { fromIndex := blockStorage.indexAddress(c.from) toIndex := blockStorage.indexAddress(c.to) - acStorage.appendNumber(big.NewInt(int64(fromIndex))) - acStorage.appendNumber(big.NewInt(int64(toIndex))) + acStorage.appendNumber(uint256.NewInt(uint64(fromIndex))) + acStorage.appendNumber(uint256.NewInt(uint64(toIndex))) acStorage.appendNumber(c.value) return acStorage } @@ -159,8 +160,8 @@ func (c action) toJsonStr() (string, *string, *string) { callType := strings.ToLower(c.op.String()) if c.op == vm.CREATE || c.op == vm.CREATE2 { action := fmt.Sprintf( - `{"from":"0x%x","gas":"0x%x","init":"0x%x","value":"0x%s"}`, - c.from, c.gas, c.input, c.value.Text(16), + `{"from":"0x%x","gas":"0x%x","init":"0x%x","value":"%s"}`, + c.from, c.gas, c.input, c.value.Hex(), ) output := fmt.Sprintf( `{"address":"0x%x","code":"0x%x","gasUsed":"0x%x"}`, @@ -170,12 +171,12 @@ func (c action) toJsonStr() (string, *string, *string) { } if c.op == vm.CALL || c.op == vm.CALLCODE || c.op == vm.DELEGATECALL || c.op == vm.STATICCALL { if c.value == nil { - c.value = big.NewInt(0) + c.value = uint256.NewInt(0) } var valueStr string if c.op != vm.STATICCALL && c.op != vm.DELEGATECALL { - valueStr = fmt.Sprintf(`,"value":"0x%s"`, c.value.Text(16)) + valueStr = fmt.Sprintf(`,"value":"%s"`, c.value.Hex()) } action := fmt.Sprintf( @@ -191,8 +192,8 @@ func (c action) toJsonStr() (string, *string, *string) { } if c.op == vm.SELFDESTRUCT { action := fmt.Sprintf( - `{"refundAddress":"0x%x","balance":"0x%s","address":"0x%x"}`, - c.to, c.value.Text(16), c.from, + `{"refundAddress":"0x%x","balance":"%s","address":"0x%x"}`, + c.to, c.value.Hex(), c.from, ) return "suicide", &action, nil } @@ -241,30 +242,31 @@ func (jst *ParityBlockTracer) CaptureStart(env *vm.EVM, from common.Address, to if create { jst.cur.op = vm.CREATE // virtual create } + vv, _ := uint256.FromBig(value) jst.cur.from = from jst.cur.to = to jst.cur.input = input jst.cur.gas = gas - jst.cur.value = (&big.Int{}).Set(value) + jst.cur.value = vv jst.cur.blockHash = env.StateDB.BlockHash() jst.cur.transactionPosition = uint64(env.StateDB.TxIndex()) jst.cur.transactionHash = env.StateDB.TxHashETH() - jst.cur.blockNumber = env.BlockNumber.Uint64() + jst.cur.blockNumber = env.Context.BlockNumber.Uint64() jst.cur.descended = false jst.cur.push(&jst.cur.action) return nil } // CaptureState implements the ParityBlockTracer interface to trace a single step of VM execution. -func (jst *ParityBlockTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) (vm.HookAfter, error) { +func (jst *ParityBlockTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, returnStack *vm.ReturnStack, bts []byte, contract *vm.Contract, depth int, err error) error { if err != nil { - return nil, jst.CaptureFault(env, pc, op, gas, cost, memory, stack, contract, depth, err) + return jst.CaptureFault(env, pc, op, gas, cost, memory, stack, nil, contract, depth, err) } var retErr error - stackPeek := func(n int) *big.Int { + stackPeek := func(n int) *uint256.Int { if n >= len(stack.Data()) { retErr = errors.New("tracer bug:stack overflow") - return big.NewInt(0) + return uint256.NewInt(0) } return stack.Back(n) } @@ -278,41 +280,42 @@ func (jst *ParityBlockTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, switch op { case vm.CREATE, vm.CREATE2: - inOff := stackPeek(1).Int64() - inSize := stackPeek(2).Int64() + inOff := int64(stackPeek(1).Uint64()) + inSize := int64(stackPeek(2).Uint64()) jst.cur.push(&action{ op: op, from: contract.Address(), input: memoryCopy(inOff, inSize), gasIn: gas, gasCost: cost, - value: (&big.Int{}).Set(stackPeek(0)), + value: stackPeek(0), }) jst.cur.descended = true - return nil, retErr + return retErr case vm.SELFDESTRUCT: ac := jst.cur.last() + value, _ := uint256.FromBig(env.StateDB.GetBalance(contract.Address())) ac.push(&action{ op: op, from: contract.Address(), - to: common.BigToAddress(stackPeek(0)), + to: stackPeek(0).Bytes20(), gasIn: gas, gasCost: cost, - value: env.StateDB.GetBalance(contract.Address()), + value: value, }) - return nil, retErr + return retErr case vm.CALL, vm.CALLCODE, vm.DELEGATECALL, vm.STATICCALL: - to := common.BigToAddress(stackPeek(1)) + to := stackPeek(1).Bytes20() precompiles := vm.PrecompiledContractsVRF if _, exist := precompiles[to]; exist { - return nil, nil + return nil } off := 1 if op == vm.DELEGATECALL || op == vm.STATICCALL { off = 0 } - inOff := stackPeek(2 + off).Int64() - inSize := stackPeek(3 + off).Int64() + inOff := int64(stackPeek(2 + off).Uint64()) + inSize := int64(stackPeek(3 + off).Uint64()) callObj := &action{ op: op, from: contract.Address(), @@ -320,15 +323,15 @@ func (jst *ParityBlockTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, input: memoryCopy(inOff, inSize), gasIn: gas, gasCost: cost, - outOff: stackPeek(4 + off).Int64(), - outLen: stackPeek(5 + off).Int64(), + outOff: stackPeek(4 + off).ToBig().Int64(), + outLen: stackPeek(5 + off).ToBig().Int64(), } if op != vm.DELEGATECALL && op != vm.STATICCALL { - callObj.value = (&big.Int{}).Set(stackPeek(2)) + callObj.value = stackPeek(2).Clone() } jst.cur.push(callObj) jst.cur.descended = true - return nil, retErr + return retErr } if jst.cur.descended { @@ -340,10 +343,10 @@ func (jst *ParityBlockTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, if op == vm.REVERT { last := jst.cur.last() last.err = errors.New("execution reverted") - revertOff := stackPeek(0).Int64() - revertLen := stackPeek(1).Int64() + revertOff := int64(stackPeek(0).Uint64()) + revertLen := int64(stackPeek(1).Uint64()) last.revert = memoryCopy(revertOff, revertLen) - return nil, retErr + return retErr } if depth == jst.cur.len()-1 { // depth == len - 1 call := jst.cur.pop() @@ -352,7 +355,7 @@ func (jst *ParityBlockTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, ret := stackPeek(0) if ret.Sign() != 0 { - call.to = common.BigToAddress(ret) + call.to = ret.Bytes20() call.output = env.StateDB.GetCode(call.to) } else if call.err == nil { call.err = errors.New("internal failure") @@ -370,12 +373,12 @@ func (jst *ParityBlockTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, } jst.cur.last().push(call) } - return nil, retErr + return retErr } // CaptureFault implements the ParityBlockTracer interface to trace an execution fault // while running an opcode. -func (jst *ParityBlockTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { +func (jst *ParityBlockTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, returnStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error { if jst.cur.last().err != nil { return nil } @@ -459,7 +462,7 @@ func (jst *ParityBlockTracer) GetResult() ([]json.RawMessage, error) { ) var resultPiece string if ac.err != nil { - resultPiece = fmt.Sprintf(`,"error":"Reverted","revert":"0x%x"`, ac.revert) + resultPiece = fmt.Sprintf(`,"error":"Reverted","revert":"%x"`, ac.revert) } else if outStr != nil { resultPiece = fmt.Sprintf(`,"result":%s`, *outStr) diff --git a/hmy/tracers/block_tracer_storage.go b/hmy/tracers/block_tracer_storage.go index 2377102734..86829ea365 100644 --- a/hmy/tracers/block_tracer_storage.go +++ b/hmy/tracers/block_tracer_storage.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" "github.com/harmony-one/harmony/crypto/hash" + "github.com/holiman/uint256" ) type ActionStorage struct { @@ -40,7 +41,7 @@ func (storage *ActionStorage) appendByte(byt byte) { func (storage *ActionStorage) appendFixed(data []byte) { storage.TraceData = append(storage.TraceData, data...) } -func (storage *ActionStorage) appendNumber(num *big.Int) { +func (storage *ActionStorage) appendNumber(num *uint256.Int) { bytes, _ := rlp.EncodeToBytes(num) storage.appendByte(uint8(len(bytes))) storage.appendFixed(bytes) diff --git a/hmy/tracers/block_tracer_test.go b/hmy/tracers/block_tracer_test.go index 5589546801..b56a96b0f5 100644 --- a/hmy/tracers/block_tracer_test.go +++ b/hmy/tracers/block_tracer_test.go @@ -4,9 +4,9 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "math/big" "strconv" - "strings" "testing" @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/harmony-one/harmony/core/vm" "github.com/harmony-one/harmony/internal/utils" + "github.com/holiman/uint256" ) var TestJsonsMock = []byte(`{"14054302":[{"blockNumber":14054302,"blockHash":"0x04d7a0d62d3211151db0dadcaebcb1686c4a3df0e551a00c023c651546293975","transactionHash":"0xce49e42e0fbd37a0cfd08c2da3f1acc371ddbc02c428afa123a43663e57953d7","transactionPosition":0,"subtraces":0,"traceAddress":[0],"type":"suicide","action":{"refundAddress":"0x12e49d93588e0056bd25530c3b1e8aac68f4b70a","balance":"0x0","address":"0x7006c42d6fa41844baa53b0388f9542e634cf55a"},"result":null}],"14833359":[{"blockNumber":14833359,"blockHash":"0x6d6660f3d042a145c7f95c408f28cbf036a18eaf603161c2c00ca3f6041d8b52","transactionHash":"0x9fd0daef346c72d51f7482ddc9a466caf52fa6a116ed13ee0c003e57e632b7c0","transactionPosition":0,"subtraces":0,"traceAddress":[],"type":"create","action":{"from":"0x8520021f89450394244cd4abda4cfe2f1b0ef61c","gas":"0x1017d","init":"0x608060405234801561001057600080fd5b50610149806100206000396000f3fe6080604052600436106100295760003560e01c80630c2ad69c1461002e57806315d55b281461007a575b600080fd5b6100646004803603604081101561004457600080fd5b810190808035906020019092919080359060200190929190505050610091565b6040518082815260200191505060405180910390f35b34801561008657600080fd5b5061008f6100a5565b005b600081838161009c57fe5b04905092915050565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f68656c6c6f00000000000000000000000000000000000000000000000000000081525060200191505060405180910390fdfea26469706673582212202f9958b958267c4ed653e54dc0161cfb9b772209cbe086f4a9ac3d967f22f09564736f6c634300060c0033","value":"0x0"},"result":{"address":"0xf29fcf3a375ce5dd1c58f0e8a584ab5d782cc12b","code":"0x6080604052600436106100295760003560e01c80630c2ad69c1461002e57806315d55b281461007a575b600080fd5b6100646004803603604081101561004457600080fd5b810190808035906020019092919080359060200190929190505050610091565b6040518082815260200191505060405180910390f35b34801561008657600080fd5b5061008f6100a5565b005b600081838161009c57fe5b04905092915050565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f68656c6c6f00000000000000000000000000000000000000000000000000000081525060200191505060405180910390fdfea26469706673582212202f9958b958267c4ed653e54dc0161cfb9b772209cbe086f4a9ac3d967f22f09564736f6c634300060c0033","gasUsed":"0x1017d"}},{"blockNumber":14833359,"blockHash":"0x6d6660f3d042a145c7f95c408f28cbf036a18eaf603161c2c00ca3f6041d8b52","transactionHash":"0xc3b81fa2f6786ffd11a588b9d951a39adb46b6e29abad819b0cb09ee32ea7072","transactionPosition":1,"subtraces":2,"traceAddress":[],"type":"call","action":{"callType":"call","value":"0x0","to":"0x4596817192fbbf0142c576ed3e7cfc0e8f40bbbe","gas":"0x2b71c","from":"0x87946ddc76a4c0a75c8ca1f63dffd0612ae6458c","input":"0x1801fbe5aebcf6e3d785238603dd88bb43cbdfcfeb51c95b570113ee65d2f9271d3b59510000000dcdf493a5e1610e23c037bc4c4e04ab9a6d8fe9d0d462ecd8d45643ac"},"result":{"output":"0x0000000000000000000000000000000000000000000000000000000000000001","gasUsed":"0x13c58"}}]}`) @@ -95,7 +96,12 @@ func initFromJson(ts *TraceBlockStorage, bytes []byte) { } ac.from = callAc.From ac.to = callAc.To - ac.value, _ = new(big.Int).SetString(callAc.Value[2:], 16) + ac.value = new(uint256.Int) + _ = ac.value.SetFromHex(callAc.Value[2:]) //, 16) + _prev, _ := new(big.Int).SetString(callAc.Value[2:], 16) + if _prev.Uint64() != ac.value.Uint64() { + panic(fmt.Sprintf("big int set from hex failed, expected be equal %s and %s", _prev.String(), ac.value.String())) + } ac.gas, _ = strconv.ParseUint(callAc.Gas, 0, 64) ac.input = utils.FromHex(callAc.Input) @@ -125,7 +131,12 @@ func initFromJson(ts *TraceBlockStorage, bytes []byte) { panic(err) } ac.from = callAc.From - ac.value, _ = new(big.Int).SetString(callAc.Value[2:], 16) + ac.value = new(uint256.Int) + _ = ac.value.SetFromHex(callAc.Value[2:]) //, 16) + _prev, _ := new(big.Int).SetString(callAc.Value[2:], 16) + if _prev.Uint64() != ac.value.Uint64() { + panic(fmt.Sprintf("big int set from hex failed, expected be equal, %s and %s", _prev.String(), ac.value.String())) + } ac.gas, _ = strconv.ParseUint(callAc.Gas, 0, 64) ac.input = utils.FromHex(callAc.Init) @@ -145,7 +156,14 @@ func initFromJson(ts *TraceBlockStorage, bytes []byte) { ac.from = callAc.Address ac.to = callAc.RefundAddress - ac.value, _ = new(big.Int).SetString(callAc.Balance[2:], 16) + + ac.value = new(uint256.Int) + _ = ac.value.SetFromHex(callAc.Balance[2:]) //, 16) + + _prev, _ := new(big.Int).SetString(callAc.Balance[2:], 16) + if _prev.Uint64() != ac.value.Uint64() { + panic(fmt.Sprintf("big int set from hex failed, expected be equal, %d and %d", _prev.Uint64(), ac.value.Uint64())) + } } ts.Hash = obj.BlockHash ts.Number = obj.BlockNumber @@ -188,7 +206,7 @@ func TestStorage(t *testing.T) { t.Error(err) } if !bytes.Equal(jsonRaw, testJson) { - t.Fatal("restroe failed!") + t.Fatalf("restroe failed!, expected: %s, got %s", testJson, jsonRaw) } block.ToDB(func(key, data []byte) { for _, kv := range memDB { @@ -248,7 +266,7 @@ func unmarshalAction(jsonstr string) (*action, error) { ac.to = common.HexToAddress(actionInterface["to"]) ac.gas, _ = strconv.ParseUint(actionInterface["gas"], 0, 64) ac.input = utils.FromHex(actionInterface["input"]) - ac.value = big.NewInt(0) + ac.value = uint256.NewInt(0) ac.value.UnmarshalText([]byte(actionInterface["value"])) switch strings.ToUpper(callType) { case "CALL": @@ -265,7 +283,7 @@ func unmarshalAction(jsonstr string) (*action, error) { if initCode, exist := actionInterface["init"]; exist { ac.op = vm.CREATE ac.from = common.HexToAddress(actionInterface["from"]) - ac.value = big.NewInt(0) + ac.value = uint256.NewInt(0) ac.value.UnmarshalText([]byte(actionInterface["value"])) ac.gas, _ = strconv.ParseUint(actionInterface["gas"], 0, 64) ac.input = utils.FromHex(initCode) @@ -275,7 +293,7 @@ func unmarshalAction(jsonstr string) (*action, error) { ac.op = vm.SELFDESTRUCT ac.from = common.HexToAddress(actionInterface["address"]) ac.to = common.HexToAddress(refundAddress) - ac.value = big.NewInt(0) + ac.value = uint256.NewInt(0) ac.value.UnmarshalText([]byte(actionInterface["balance"])) return &ac, nil } diff --git a/hmy/tracers/rosetta_block_tracer.go b/hmy/tracers/rosetta_block_tracer.go index d1b573d7a9..d865e20536 100644 --- a/hmy/tracers/rosetta_block_tracer.go +++ b/hmy/tracers/rosetta_block_tracer.go @@ -20,6 +20,7 @@ import ( "math/big" "github.com/harmony-one/harmony/core/vm" + "github.com/holiman/uint256" ) type RosettaLogItem struct { @@ -41,7 +42,7 @@ type RosettaBlockTracer struct { func (rbt *RosettaBlockTracer) formatAction(depth []int, parentErr error, ac *action) *RosettaLogItem { val := ac.value if val == nil { - val = big.NewInt(0) + val = uint256.NewInt(0) } return &RosettaLogItem{ @@ -51,7 +52,7 @@ func (rbt *RosettaBlockTracer) formatAction(depth []int, parentErr error, ac *ac Depth: depth, From: &vm.RosettaLogAddressItem{Account: &ac.from}, To: &vm.RosettaLogAddressItem{Account: &ac.to}, - Value: val, + Value: val.ToBig(), } } diff --git a/hmy/tracers/tracer.go b/hmy/tracers/tracer.go index bc349a5147..2498e348ca 100644 --- a/hmy/tracers/tracer.go +++ b/hmy/tracers/tracer.go @@ -162,7 +162,7 @@ func (sw *stackWrapper) peek(idx int) *big.Int { log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx) return new(big.Int) } - return sw.stack.Data()[len(sw.stack.Data())-idx-1] + return sw.stack.Data()[len(sw.stack.Data())-idx-1].ToBig() } // pushObject assembles a JSVM object wrapping a swappable stack and pushes it @@ -543,17 +543,17 @@ func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr } // CaptureState implements the Tracer interface to trace a single step of VM execution. -func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) (vm.HookAfter, error) { +func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, returnStack *vm.ReturnStack, bts []byte, contract *vm.Contract, depth int, err error) error { if jst.err == nil { // Initialize the context if it wasn't done yet if !jst.inited { - jst.ctx["block"] = env.BlockNumber.Uint64() + jst.ctx["block"] = env.Context.BlockNumber.Uint64() jst.inited = true } // If tracing was interrupted, set the error and stop if atomic.LoadUint32(&jst.interrupt) > 0 { jst.err = jst.reason - return nil, nil + return nil } jst.opWrapper.op = op jst.stackWrapper.stack = stack @@ -577,12 +577,12 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost jst.err = wrapError("step", err) } } - return nil, nil + return nil } // CaptureFault implements the Tracer interface to trace an execution fault // while running an opcode. -func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { +func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, returnStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error { if jst.err == nil { // Apart from the error, everything matches the previous invocation jst.errorValue = new(string) diff --git a/internal/params/config.go b/internal/params/config.go index 62d55d83f5..4dd018e648 100644 --- a/internal/params/config.go +++ b/internal/params/config.go @@ -328,6 +328,7 @@ var ( MaxRateEpoch: EpochTBD, DevnetExternalEpoch: EpochTBD, TestnetExternalEpoch: EpochTBD, + EIP1559Epoch: big.NewInt(1), IsOneSecondEpoch: big.NewInt(4), } @@ -381,6 +382,7 @@ var ( big.NewInt(0), big.NewInt(0), big.NewInt(0), + big.NewInt(0), } // TestChainConfig ... @@ -433,6 +435,7 @@ var ( big.NewInt(0), big.NewInt(0), big.NewInt(0), + big.NewInt(0), } // TestRules ... @@ -615,6 +618,9 @@ type ChainConfig struct { // if crosslink are not sent for an entire epoch signed and toSign will be 0 and 0. when that happen, next epoch there will no shard 1 validator elected in the committee. HIP32Epoch *big.Int `json:"hip32-epoch,omitempty"` + // HIP33Epoch: effectiveGasPrice+block v4+evm opcode BASEFEE + EIP1559Epoch *big.Int `json:"eip1559-epoch,omitempty"` + IsOneSecondEpoch *big.Int `json:"is-one-second-epoch,omitempty"` } @@ -909,6 +915,20 @@ func (c *ChainConfig) IsOneEpochBeforeHIP30(epoch *big.Int) bool { return new(big.Int).Sub(c.HIP30Epoch, epoch).Cmp(common.Big1) == 0 } +func (c *ChainConfig) IsLondon(epoch *big.Int) bool { + return false +} + +// BaseFeeChangeDenominator +func (c *ChainConfig) BaseFeeChangeDenominator() uint64 { + return DefaultBaseFeeChangeDenominator +} + +// ElasticityMultiplier bounds the maximum gas limit an EIP-1559 block may have. +func (c *ChainConfig) ElasticityMultiplier() uint64 { + return DefaultElasticityMultiplier +} + // UpdateEthChainIDByShard update the ethChainID based on shard ID. func UpdateEthChainIDByShard(shardID uint32) { once.Do(func() { diff --git a/internal/params/protocol_params.go b/internal/params/protocol_params.go index e67bb29ae3..d6e3aed3fc 100644 --- a/internal/params/protocol_params.go +++ b/internal/params/protocol_params.go @@ -62,12 +62,9 @@ const ( // NetSstoreDirtyGas ... NetSstoreDirtyGas uint64 = 200 // Once per SSTORE operation from dirty. - // NetSstoreClearRefund ... - NetSstoreClearRefund uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot - // NetSstoreResetRefund ... - NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value - // NetSstoreResetClearRefund ... - NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value + SstoreSetGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero + SstoreResetGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else + SstoreClearsScheduleRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot // SstoreSentryGasEIP2200 ... SstoreSentryGasEIP2200 uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed @@ -86,6 +83,10 @@ const ( // SstoreClearRefundEIP2200 ... SstoreClearRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot + NetSstoreClearRefund uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot + NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value + NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value + // todo(sun): implement eip2929 ColdAccountAccessCostEIP2929 uint64 = 2600 // COLD_ACCOUNT_ACCESS_COST ColdSloadCostEIP2929 uint64 = 2100 // COLD_SLOAD_COST @@ -126,6 +127,10 @@ const ( // TxDataNonZeroGasEIP2028 ... TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + DefaultBaseFeeChangeDenominator = 8 // Bounds the amount the base fee can change between blocks. + DefaultElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have. + InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks. + // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. CallGasEIP150 uint64 = 700 // Static portion of gas for CALL-derivates after EIP 150 (Tangerine) @@ -137,6 +142,7 @@ const ( SloadGasFrontier uint64 = 50 SloadGasEIP150 uint64 = 200 SloadGasEIP1884 uint64 = 800 // Cost of SLOAD after EIP 1884 (part of Istanbul) + SloadGasEIP2200 uint64 = 800 // Cost of SLOAD after EIP 2200 (part of Istanbul) ExtcodeHashGasConstantinople uint64 = 400 // Cost of EXTCODEHASH (introduced in Constantinople) ExtcodeHashGasEIP1884 uint64 = 700 // Cost of EXTCODEHASH after EIP 1884 (part in Istanbul) SelfdestructGasEIP150 uint64 = 5000 // Cost of SELFDESTRUCT post EIP 150 (Tangerine) diff --git a/node/harmony/worker/worker.go b/node/harmony/worker/worker.go index 5d94e24112..a644be84af 100644 --- a/node/harmony/worker/worker.go +++ b/node/harmony/worker/worker.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/harmony-one/harmony/block" blockfactory "github.com/harmony-one/harmony/block/factory" + "github.com/harmony-one/harmony/consensus/misc/eip1559" "github.com/harmony-one/harmony/consensus/reward" "github.com/harmony-one/harmony/core" "github.com/harmony-one/harmony/core/state" @@ -368,6 +369,11 @@ func (w *Worker) UpdateCurrent() (Environment, error) { Time(big.NewInt(timestamp)). ShardID(w.chain.ShardID()). Header() + + if w.chain.Config().IsLondon(header.Epoch()) { + header.SetBaseFee(eip1559.CalcBaseFee(w.chain.Config(), parent)) + } + return w.makeCurrent(parent, header) } diff --git a/p2p/gater.go b/p2p/gater.go deleted file mode 100644 index 8b63b9fb73..0000000000 --- a/p2p/gater.go +++ /dev/null @@ -1,49 +0,0 @@ -package p2p - -import ( - libp2p_dht "github.com/libp2p/go-libp2p-kad-dht" - "github.com/libp2p/go-libp2p/core/connmgr" - "github.com/libp2p/go-libp2p/core/control" - "github.com/libp2p/go-libp2p/core/network" - "github.com/libp2p/go-libp2p/core/peer" - ma "github.com/multiformats/go-multiaddr" -) - -type Gater struct { - isGating bool -} - -func NewGater(disablePrivateIPScan bool) connmgr.ConnectionGater { - return Gater{ - isGating: disablePrivateIPScan, - } -} - -func (gater Gater) InterceptPeerDial(p peer.ID) (allow bool) { - return true -} - -// Blocking connections at this stage is typical for address filtering. -func (gater Gater) InterceptAddrDial(p peer.ID, m ma.Multiaddr) (allow bool) { - if gater.isGating { - return libp2p_dht.PublicQueryFilter(nil, peer.AddrInfo{ - ID: p, - Addrs: []ma.Multiaddr{m}, - }) - } else { - return true - } -} - -func (gater Gater) InterceptAccept(network.ConnMultiaddrs) (allow bool) { - return true -} - -func (gater Gater) InterceptSecured(network.Direction, peer.ID, network.ConnMultiaddrs) (allow bool) { - return true -} - -// NOTE: the go-libp2p implementation currently IGNORES the disconnect reason. -func (gater Gater) InterceptUpgraded(network.Conn) (allow bool, reason control.DisconnectReason) { - return true, 0 -} diff --git a/p2p/gating/blocking.go b/p2p/gating/blocking.go index a156446752..6ab416870e 100644 --- a/p2p/gating/blocking.go +++ b/p2p/gating/blocking.go @@ -1,36 +1,31 @@ package gating import ( - "net" - - ds "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p/core/connmgr" + libp2p_dht "github.com/libp2p/go-libp2p-kad-dht" "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/p2p/net/conngater" + ma "github.com/multiformats/go-multiaddr" ) -type BlockingConnectionGater interface { - connmgr.ConnectionGater - - // BlockPeer adds a peer to the set of blocked peers. - // Note: active connections to the peer are not automatically closed. - BlockPeer(p peer.ID) error - UnblockPeer(p peer.ID) error - ListBlockedPeers() []peer.ID - - // BlockAddr adds an IP address to the set of blocked addresses. - // Note: active connections to the IP address are not automatically closed. - BlockAddr(ip net.IP) error - UnblockAddr(ip net.IP) error - ListBlockedAddrs() []net.IP +// ExpiryConnectionGater enhances a ExtendedConnectionGater by implementing ban-expiration +type BlockingConnectionGater struct { + ExtendedConnectionGater + isGating bool +} - // BlockSubnet adds an IP subnet to the set of blocked addresses. - // Note: active connections to the IP subnet are not automatically closed. - BlockSubnet(ipnet *net.IPNet) error - UnblockSubnet(ipnet *net.IPNet) error - ListBlockedSubnets() []*net.IPNet +func AddBlocking(gater ExtendedConnectionGater, disablePrivateIPScan bool) *BlockingConnectionGater { + return &BlockingConnectionGater{ + ExtendedConnectionGater: gater, + isGating: disablePrivateIPScan, + } } -func NewBlockingConnectionGater(store ds.Batching) (BlockingConnectionGater, error) { - return conngater.NewBasicConnectionGater(store) +// Blocking connections at this stage is typical for address filtering. +func (g *BlockingConnectionGater) InterceptAddrDial(p peer.ID, m ma.Multiaddr) (allow bool) { + if g.isGating { + return libp2p_dht.PublicQueryFilter(nil, peer.AddrInfo{ + ID: p, + Addrs: []ma.Multiaddr{m}, + }) + } + return true } diff --git a/p2p/gating/expiry.go b/p2p/gating/expiry.go index d221845b82..ff9d489f26 100644 --- a/p2p/gating/expiry.go +++ b/p2p/gating/expiry.go @@ -24,23 +24,23 @@ type ExpiryStore interface { store.PeerBanStore } -// ExpiryConnectionGater enhances a BlockingConnectionGater by implementing ban-expiration +// ExpiryConnectionGater enhances a ExtendedConnectionGater by implementing ban-expiration type ExpiryConnectionGater struct { - BlockingConnectionGater + ExtendedConnectionGater store ExpiryStore clock clock.Clock } -func AddBanExpiry(gater BlockingConnectionGater, store ExpiryStore, clock clock.Clock) *ExpiryConnectionGater { +func AddBanExpiry(gater ExtendedConnectionGater, store ExpiryStore, clock clock.Clock) *ExpiryConnectionGater { return &ExpiryConnectionGater{ - BlockingConnectionGater: gater, + ExtendedConnectionGater: gater, store: store, clock: clock, } } func (g *ExpiryConnectionGater) UnblockPeer(p peer.ID) error { - if err := g.BlockingConnectionGater.UnblockPeer(p); err != nil { + if err := g.ExtendedConnectionGater.UnblockPeer(p); err != nil { utils.Logger().Warn(). Str("method", "UnblockPeer"). Str("peer_id", p.String()). @@ -131,7 +131,7 @@ func (g *ExpiryConnectionGater) addrBanExpiryCheck(ma multiaddr.Multiaddr) (allo } func (g *ExpiryConnectionGater) InterceptPeerDial(p peer.ID) (allow bool) { - if !g.BlockingConnectionGater.InterceptPeerDial(p) { + if !g.ExtendedConnectionGater.InterceptPeerDial(p) { return false } peerBan := g.peerBanExpiryCheck(p) @@ -145,7 +145,7 @@ func (g *ExpiryConnectionGater) InterceptPeerDial(p peer.ID) (allow bool) { } func (g *ExpiryConnectionGater) InterceptAddrDial(id peer.ID, ma multiaddr.Multiaddr) (allow bool) { - if !g.BlockingConnectionGater.InterceptAddrDial(id, ma) { + if !g.ExtendedConnectionGater.InterceptAddrDial(id, ma) { return false } peerBan := g.peerBanExpiryCheck(id) @@ -170,7 +170,7 @@ func (g *ExpiryConnectionGater) InterceptAddrDial(id peer.ID, ma multiaddr.Multi } func (g *ExpiryConnectionGater) InterceptAccept(mas network.ConnMultiaddrs) (allow bool) { - if !g.BlockingConnectionGater.InterceptAccept(mas) { + if !g.ExtendedConnectionGater.InterceptAccept(mas) { return false } addrBan := g.addrBanExpiryCheck(mas.RemoteMultiaddr()) @@ -187,7 +187,7 @@ func (g *ExpiryConnectionGater) InterceptSecured(direction network.Direction, id if direction == network.DirOutbound { return true } - if !g.BlockingConnectionGater.InterceptSecured(direction, id, mas) { + if !g.ExtendedConnectionGater.InterceptSecured(direction, id, mas) { return false } peerBan := g.peerBanExpiryCheck(id) diff --git a/p2p/gating/gater.go b/p2p/gating/gater.go new file mode 100644 index 0000000000..bc3135e7ef --- /dev/null +++ b/p2p/gating/gater.go @@ -0,0 +1,36 @@ +package gating + +import ( + "net" + + ds "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p/core/connmgr" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/p2p/net/conngater" +) + +type ExtendedConnectionGater interface { + connmgr.ConnectionGater + + // BlockPeer adds a peer to the set of blocked peers. + // Note: active connections to the peer are not automatically closed. + BlockPeer(p peer.ID) error + UnblockPeer(p peer.ID) error + ListBlockedPeers() []peer.ID + + // BlockAddr adds an IP address to the set of blocked addresses. + // Note: active connections to the IP address are not automatically closed. + BlockAddr(ip net.IP) error + UnblockAddr(ip net.IP) error + ListBlockedAddrs() []net.IP + + // BlockSubnet adds an IP subnet to the set of blocked addresses. + // Note: active connections to the IP subnet are not automatically closed. + BlockSubnet(ipnet *net.IPNet) error + UnblockSubnet(ipnet *net.IPNet) error + ListBlockedSubnets() []*net.IPNet +} + +func NewExtendedConnectionGater(store ds.Batching) (ExtendedConnectionGater, error) { + return conngater.NewBasicConnectionGater(store) +} diff --git a/p2p/gater_test.go b/p2p/gating/gater_test.go similarity index 70% rename from p2p/gater_test.go rename to p2p/gating/gater_test.go index 1a5ee37de1..40616ae972 100644 --- a/p2p/gater_test.go +++ b/p2p/gating/gater_test.go @@ -1,17 +1,26 @@ -package p2p +package gating import ( "testing" + ds "github.com/ipfs/go-datastore" + dsSync "github.com/ipfs/go-datastore/sync" ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func createTestDataStore() ds.Batching { + return dsSync.MutexWrap(ds.NewMapDatastore()) +} func TestGaterBlocking(t *testing.T) { - gater := NewGater(true) + store := createTestDataStore() + gater, err := NewExtendedConnectionGater(store) + assert.Nil(t, err, "%s", err) require.NotNil(t, &gater, "%s", &gater) + gater = AddBlocking(gater, true) + public, err := ma.NewMultiaddr("/ip4/1.1.1.1/udp/53") assert.Nil(t, err, "%s", err) allowed := gater.InterceptAddrDial("somePeer", public) @@ -24,7 +33,9 @@ func TestGaterBlocking(t *testing.T) { } func TestGaterNotBlocking(t *testing.T) { - gater := NewGater(false) + store := createTestDataStore() + gater, err := NewExtendedConnectionGater(store) + assert.Nil(t, err, "%s", err) require.NotNil(t, &gater, "%s", &gater) public, err := ma.NewMultiaddr("/ip4/1.1.1.1/udp/53") diff --git a/p2p/gating/metrics.go b/p2p/gating/metrics.go index 3a2a1cf775..4e34e96493 100644 --- a/p2p/gating/metrics.go +++ b/p2p/gating/metrics.go @@ -12,30 +12,30 @@ type ConnectionGaterMetrics interface { } type MeteredConnectionGater struct { - BlockingConnectionGater + ExtendedConnectionGater //m ConnectionGaterMetrics } -func AddMetering(gater BlockingConnectionGater) *MeteredConnectionGater { - return &MeteredConnectionGater{BlockingConnectionGater: gater} +func AddMetering(gater ExtendedConnectionGater) *MeteredConnectionGater { + return &MeteredConnectionGater{ExtendedConnectionGater: gater} } func (g *MeteredConnectionGater) InterceptPeerDial(p peer.ID) (allow bool) { - allow = g.BlockingConnectionGater.InterceptPeerDial(p) + allow = g.ExtendedConnectionGater.InterceptPeerDial(p) return allow } func (g *MeteredConnectionGater) InterceptAddrDial(id peer.ID, ma multiaddr.Multiaddr) (allow bool) { - allow = g.BlockingConnectionGater.InterceptAddrDial(id, ma) + allow = g.ExtendedConnectionGater.InterceptAddrDial(id, ma) return allow } func (g *MeteredConnectionGater) InterceptAccept(mas network.ConnMultiaddrs) (allow bool) { - allow = g.BlockingConnectionGater.InterceptAccept(mas) + allow = g.ExtendedConnectionGater.InterceptAccept(mas) return allow } func (g *MeteredConnectionGater) InterceptSecured(dir network.Direction, id peer.ID, mas network.ConnMultiaddrs) (allow bool) { - allow = g.BlockingConnectionGater.InterceptSecured(dir, id, mas) + allow = g.ExtendedConnectionGater.InterceptSecured(dir, id, mas) return allow } diff --git a/p2p/gating/mocks/BlockingConnectionGater.go b/p2p/gating/mocks/BlockingConnectionGater.go index ade24d40c6..fa3dee5f69 100644 --- a/p2p/gating/mocks/BlockingConnectionGater.go +++ b/p2p/gating/mocks/BlockingConnectionGater.go @@ -16,8 +16,8 @@ import ( peer "github.com/libp2p/go-libp2p/core/peer" ) -// BlockingConnectionGater is an autogenerated mock type for the BlockingConnectionGater type -type BlockingConnectionGater struct { +// ExtendedConnectionGater is an autogenerated mock type for the ExtendedConnectionGater type +type ExtendedConnectionGater struct { mock.Mock } @@ -25,12 +25,12 @@ type BlockingConnectionGater_Expecter struct { mock *mock.Mock } -func (_m *BlockingConnectionGater) EXPECT() *BlockingConnectionGater_Expecter { +func (_m *ExtendedConnectionGater) EXPECT() *BlockingConnectionGater_Expecter { return &BlockingConnectionGater_Expecter{mock: &_m.Mock} } // BlockAddr provides a mock function with given fields: ip -func (_m *BlockingConnectionGater) BlockAddr(ip net.IP) error { +func (_m *ExtendedConnectionGater) BlockAddr(ip net.IP) error { ret := _m.Called(ip) if len(ret) == 0 { @@ -76,7 +76,7 @@ func (_c *BlockingConnectionGater_BlockAddr_Call) RunAndReturn(run func(net.IP) } // BlockPeer provides a mock function with given fields: p -func (_m *BlockingConnectionGater) BlockPeer(p peer.ID) error { +func (_m *ExtendedConnectionGater) BlockPeer(p peer.ID) error { ret := _m.Called(p) if len(ret) == 0 { @@ -122,7 +122,7 @@ func (_c *BlockingConnectionGater_BlockPeer_Call) RunAndReturn(run func(peer.ID) } // BlockSubnet provides a mock function with given fields: ipnet -func (_m *BlockingConnectionGater) BlockSubnet(ipnet *net.IPNet) error { +func (_m *ExtendedConnectionGater) BlockSubnet(ipnet *net.IPNet) error { ret := _m.Called(ipnet) if len(ret) == 0 { @@ -168,7 +168,7 @@ func (_c *BlockingConnectionGater_BlockSubnet_Call) RunAndReturn(run func(*net.I } // InterceptAccept provides a mock function with given fields: _a0 -func (_m *BlockingConnectionGater) InterceptAccept(_a0 network.ConnMultiaddrs) bool { +func (_m *ExtendedConnectionGater) InterceptAccept(_a0 network.ConnMultiaddrs) bool { ret := _m.Called(_a0) if len(ret) == 0 { @@ -214,7 +214,7 @@ func (_c *BlockingConnectionGater_InterceptAccept_Call) RunAndReturn(run func(ne } // InterceptAddrDial provides a mock function with given fields: _a0, _a1 -func (_m *BlockingConnectionGater) InterceptAddrDial(_a0 peer.ID, _a1 multiaddr.Multiaddr) bool { +func (_m *ExtendedConnectionGater) InterceptAddrDial(_a0 peer.ID, _a1 multiaddr.Multiaddr) bool { ret := _m.Called(_a0, _a1) if len(ret) == 0 { @@ -261,7 +261,7 @@ func (_c *BlockingConnectionGater_InterceptAddrDial_Call) RunAndReturn(run func( } // InterceptPeerDial provides a mock function with given fields: p -func (_m *BlockingConnectionGater) InterceptPeerDial(p peer.ID) bool { +func (_m *ExtendedConnectionGater) InterceptPeerDial(p peer.ID) bool { ret := _m.Called(p) if len(ret) == 0 { @@ -307,7 +307,7 @@ func (_c *BlockingConnectionGater_InterceptPeerDial_Call) RunAndReturn(run func( } // InterceptSecured provides a mock function with given fields: _a0, _a1, _a2 -func (_m *BlockingConnectionGater) InterceptSecured(_a0 network.Direction, _a1 peer.ID, _a2 network.ConnMultiaddrs) bool { +func (_m *ExtendedConnectionGater) InterceptSecured(_a0 network.Direction, _a1 peer.ID, _a2 network.ConnMultiaddrs) bool { ret := _m.Called(_a0, _a1, _a2) if len(ret) == 0 { @@ -355,7 +355,7 @@ func (_c *BlockingConnectionGater_InterceptSecured_Call) RunAndReturn(run func(n } // InterceptUpgraded provides a mock function with given fields: _a0 -func (_m *BlockingConnectionGater) InterceptUpgraded(_a0 network.Conn) (bool, control.DisconnectReason) { +func (_m *ExtendedConnectionGater) InterceptUpgraded(_a0 network.Conn) (bool, control.DisconnectReason) { ret := _m.Called(_a0) if len(ret) == 0 { @@ -411,7 +411,7 @@ func (_c *BlockingConnectionGater_InterceptUpgraded_Call) RunAndReturn(run func( } // ListBlockedAddrs provides a mock function with given fields: -func (_m *BlockingConnectionGater) ListBlockedAddrs() []net.IP { +func (_m *ExtendedConnectionGater) ListBlockedAddrs() []net.IP { ret := _m.Called() if len(ret) == 0 { @@ -458,7 +458,7 @@ func (_c *BlockingConnectionGater_ListBlockedAddrs_Call) RunAndReturn(run func() } // ListBlockedPeers provides a mock function with given fields: -func (_m *BlockingConnectionGater) ListBlockedPeers() []peer.ID { +func (_m *ExtendedConnectionGater) ListBlockedPeers() []peer.ID { ret := _m.Called() if len(ret) == 0 { @@ -505,7 +505,7 @@ func (_c *BlockingConnectionGater_ListBlockedPeers_Call) RunAndReturn(run func() } // ListBlockedSubnets provides a mock function with given fields: -func (_m *BlockingConnectionGater) ListBlockedSubnets() []*net.IPNet { +func (_m *ExtendedConnectionGater) ListBlockedSubnets() []*net.IPNet { ret := _m.Called() if len(ret) == 0 { @@ -552,7 +552,7 @@ func (_c *BlockingConnectionGater_ListBlockedSubnets_Call) RunAndReturn(run func } // UnblockAddr provides a mock function with given fields: ip -func (_m *BlockingConnectionGater) UnblockAddr(ip net.IP) error { +func (_m *ExtendedConnectionGater) UnblockAddr(ip net.IP) error { ret := _m.Called(ip) if len(ret) == 0 { @@ -598,7 +598,7 @@ func (_c *BlockingConnectionGater_UnblockAddr_Call) RunAndReturn(run func(net.IP } // UnblockPeer provides a mock function with given fields: p -func (_m *BlockingConnectionGater) UnblockPeer(p peer.ID) error { +func (_m *ExtendedConnectionGater) UnblockPeer(p peer.ID) error { ret := _m.Called(p) if len(ret) == 0 { @@ -644,7 +644,7 @@ func (_c *BlockingConnectionGater_UnblockPeer_Call) RunAndReturn(run func(peer.I } // UnblockSubnet provides a mock function with given fields: ipnet -func (_m *BlockingConnectionGater) UnblockSubnet(ipnet *net.IPNet) error { +func (_m *ExtendedConnectionGater) UnblockSubnet(ipnet *net.IPNet) error { ret := _m.Called(ipnet) if len(ret) == 0 { @@ -689,13 +689,13 @@ func (_c *BlockingConnectionGater_UnblockSubnet_Call) RunAndReturn(run func(*net return _c } -// NewBlockingConnectionGater creates a new instance of BlockingConnectionGater. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewBlockingConnectionGater creates a new instance of ExtendedConnectionGater. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewBlockingConnectionGater(t interface { mock.TestingT Cleanup(func()) -}) *BlockingConnectionGater { - mock := &BlockingConnectionGater{} +}) *ExtendedConnectionGater { + mock := &ExtendedConnectionGater{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/p2p/gating/scoring.go b/p2p/gating/scoring.go index 90bf188c8c..c0759a3184 100644 --- a/p2p/gating/scoring.go +++ b/p2p/gating/scoring.go @@ -13,13 +13,13 @@ type Scores interface { // ScoringConnectionGater enhances a ConnectionGater by enforcing a minimum score for peer connections type ScoringConnectionGater struct { - BlockingConnectionGater + ExtendedConnectionGater scores Scores minScore float64 } -func AddScoring(gater BlockingConnectionGater, scores Scores, minScore float64) *ScoringConnectionGater { - return &ScoringConnectionGater{BlockingConnectionGater: gater, scores: scores, minScore: minScore} +func AddScoring(gater ExtendedConnectionGater, scores Scores, minScore float64) *ScoringConnectionGater { + return &ScoringConnectionGater{ExtendedConnectionGater: gater, scores: scores, minScore: minScore} } func (g *ScoringConnectionGater) checkScore(p peer.ID) (allow bool, score float64) { @@ -32,7 +32,7 @@ func (g *ScoringConnectionGater) checkScore(p peer.ID) (allow bool, score float6 } func (g *ScoringConnectionGater) InterceptPeerDial(p peer.ID) (allow bool) { - if !g.BlockingConnectionGater.InterceptPeerDial(p) { + if !g.ExtendedConnectionGater.InterceptPeerDial(p) { return false } check, score := g.checkScore(p) @@ -43,7 +43,7 @@ func (g *ScoringConnectionGater) InterceptPeerDial(p peer.ID) (allow bool) { } func (g *ScoringConnectionGater) InterceptAddrDial(id peer.ID, ma multiaddr.Multiaddr) (allow bool) { - if !g.BlockingConnectionGater.InterceptAddrDial(id, ma) { + if !g.ExtendedConnectionGater.InterceptAddrDial(id, ma) { return false } check, score := g.checkScore(id) @@ -54,7 +54,7 @@ func (g *ScoringConnectionGater) InterceptAddrDial(id peer.ID, ma multiaddr.Mult } func (g *ScoringConnectionGater) InterceptSecured(dir network.Direction, id peer.ID, mas network.ConnMultiaddrs) (allow bool) { - if !g.BlockingConnectionGater.InterceptSecured(dir, id, mas) { + if !g.ExtendedConnectionGater.InterceptSecured(dir, id, mas) { return false } check, score := g.checkScore(id) @@ -65,7 +65,7 @@ func (g *ScoringConnectionGater) InterceptSecured(dir network.Direction, id peer } func (g *ScoringConnectionGater) InterceptAccept(mas network.ConnMultiaddrs) (allow bool) { - if !g.BlockingConnectionGater.InterceptAccept(mas) { + if !g.ExtendedConnectionGater.InterceptAccept(mas) { return false } utils.Logger().Info().Str("multi_addr", mas.RemoteMultiaddr().String()).Msg("connection accepted") diff --git a/p2p/host.go b/p2p/host.go index 530446fbfa..db0948fbe4 100644 --- a/p2p/host.go +++ b/p2p/host.go @@ -181,11 +181,15 @@ func NewHost(cfg HostConfig) (Host, error) { if err := ps.AddPubKey(peerID, pub); err != nil { return nil, fmt.Errorf("failed to set up peerstore with pub key: %w", err) } - var connGtr gating.BlockingConnectionGater - connGtr, err = gating.NewBlockingConnectionGater(datastore) + var connGtr gating.ExtendedConnectionGater + connGtr, err = gating.NewExtendedConnectionGater(datastore) if err != nil { return nil, fmt.Errorf("failed to open connection gater: %w", err) } + if cfg.DisablePrivateIPScan { + // Prevent dialing of public addresses + connGtr = gating.AddBlocking(connGtr, cfg.DisablePrivateIPScan) + } connGtr = gating.AddBanExpiry(connGtr, ps, clock.SystemClock) connGtr = gating.AddMetering(connGtr) @@ -302,12 +306,6 @@ func NewHost(cfg HostConfig) (Host, error) { p2pHostConfig = append(p2pHostConfig, libp2p.ForceReachabilityPublic()) } - // TODO: this should be moved to main gater - if cfg.DisablePrivateIPScan { - // Prevent dialing of public addresses - p2pHostConfig = append(p2pHostConfig, libp2p.ConnectionGater(NewGater(cfg.DisablePrivateIPScan))) - } - // create p2p host p2pHost, err := libp2p.New(p2pHostConfig...) if err != nil { diff --git a/rosetta/services/block.go b/rosetta/services/block.go index d187f2ea6d..40c33c6a0d 100644 --- a/rosetta/services/block.go +++ b/rosetta/services/block.go @@ -318,7 +318,7 @@ func (s *BlockAPI) getTransactionTrace( var blockError *types.Error var foundResult []*tracers.RosettaLogItem var tracer = "RosettaBlockTracer" - err := s.hmy.ComputeTxEnvEachBlockWithoutApply(blk, defaultTraceReExec, func(txIndex int, tx *coreTypes.Transaction, msg core.Message, vmctx vm.Context, statedb *state.DB) bool { + err := s.hmy.ComputeTxEnvEachBlockWithoutApply(blk, defaultTraceReExec, func(txIndex int, tx *coreTypes.Transaction, msg core.Message, vmctx vm.BlockContext, statedb *state.DB) bool { execResultInterface, err := s.hmy.TraceTx(ctx, msg, vmctx, statedb, &hmy.TraceConfig{ Tracer: &tracer, LogConfig: &vm.LogConfig{ diff --git a/rpc/harmony/tracer.go b/rpc/harmony/tracer.go index c8ab490ad0..5aba086b78 100644 --- a/rpc/harmony/tracer.go +++ b/rpc/harmony/tracer.go @@ -205,7 +205,7 @@ func (s *PublicTracerService) TraceCall(ctx context.Context, args CallArgs, bloc // Execute the trace msg := args.ToMessage(s.hmy.RPCGasCap) - vmctx := core.NewEVMContext(msg, header, s.hmy.BlockChain, nil) + vmctx := core.NewEVMBlockContext(msg, header, s.hmy.BlockChain, nil) // Apply overrides customization if required if config != nil { @@ -223,7 +223,6 @@ func (s *PublicTracerService) TraceCall(ctx context.Context, args CallArgs, bloc config.Stateoverrides.Apply(statedb, precompiles) } } - // Trace the transaction and return return s.hmy.TraceTx(ctx, msg, vmctx, statedb, config) }