diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d7e884b7da..5d2d399ac4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: 1.18.x + go-version: 1.19.x - name: Checkout code uses: actions/checkout@v3 - name: Lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 593ebd8edb..22ce39f09d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.18 + go-version: 1.19 - name: Get packr run: go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 145b1532d4..cb7691a46c 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [ 1.18.x ] + go-version: [ 1.19.x ] goarch: [ "amd64" ] e2e-group: [ 1, 2, 3, 4, 5, 6, 7 ] runs-on: ubuntu-latest @@ -57,7 +57,7 @@ jobs: contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha) strategy: matrix: - go-version: [ 1.18.x ] + go-version: [ 1.19.x ] goarch: [ "amd64" ] e2e-group: [ 1, 2, 3, 4, 5, 6, 7 ] runs-on: ubuntu-latest diff --git a/.github/workflows/test-from-prover.yml b/.github/workflows/test-from-prover.yml index 5502d4162c..ca917668a4 100644 --- a/.github/workflows/test-from-prover.yml +++ b/.github/workflows/test-from-prover.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: - go-version: [ 1.18.x ] + go-version: [ 1.19.x ] goarch: [ "amd64" ] e2e-group: [ 2 ] diff --git a/.github/workflows/test-full-non-e2e.yml b/.github/workflows/test-full-non-e2e.yml index 71bfe903f5..5514a0dde8 100644 --- a/.github/workflows/test-full-non-e2e.yml +++ b/.github/workflows/test-full-non-e2e.yml @@ -17,7 +17,7 @@ jobs: if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository strategy: matrix: - go-version: [ 1.18.x ] + go-version: [ 1.19.x ] goarch: [ "amd64" ] runs-on: ubuntu-latest steps: @@ -54,7 +54,7 @@ jobs: contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha) strategy: matrix: - go-version: [ 1.18.x ] + go-version: [ 1.19.x ] goarch: [ "amd64" ] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/updatedeps.yml b/.github/workflows/updatedeps.yml index 900cd3eb94..7f0896525c 100644 --- a/.github/workflows/updatedeps.yml +++ b/.github/workflows/updatedeps.yml @@ -14,7 +14,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: "1.18.x" + go-version: "1.19.x" env: GOARCH: "amd64" diff --git a/.golangci.yml b/.golangci.yml index 87d6e0e5f2..1abbace218 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,9 @@ --- run: timeout: 5m + skip-dirs: + - state/runtime/fakevm + - state/runtime/instrumentation linters: enable: diff --git a/Dockerfile b/Dockerfile index de1ef42b43..70785db41b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # CONTAINER FOR BUILDING BINARY -FROM golang:1.18 AS build +FROM golang:1.19 AS build # INSTALL DEPENDENCIES RUN go install github.com/gobuffalo/packr/v2/packr2@v2.8.3 diff --git a/Makefile b/Makefile index 83878cb65e..b9ebffb686 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ install-linter: ## Installs the linter .PHONY: lint lint: ## Runs the linter - $$(go env GOPATH)/bin/golangci-lint run + export "GOROOT=$$(go env GOROOT)" && $$(go env GOPATH)/bin/golangci-lint run .PHONY: update-external-dependencies update-external-dependencies: ## Updates external dependencies like images, test vectors or proto files diff --git a/README.md b/README.md index f4d362c6c7..ad8d2d1c96 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ It's recommended to use `make` for building, and testing the code, ... Run `make ### Requirements -- Go 1.18 +- Go 1.19 - Docker - Docker Compose - Make diff --git a/docker-compose.yml b/docker-compose.yml index 049e86dbb6..2619f9f65b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -107,7 +107,7 @@ services: zkevm-prover: container_name: zkevm-prover restart: unless-stopped - image: hermeznetwork/zkevm-prover:c514cd1 + image: hermeznetwork/zkevm-prover:4e3272b depends_on: zkevm-state-db: condition: service_healthy diff --git a/docs/ci/groups.md b/docs/ci/groups.md index b411805538..7efa50dd27 100644 --- a/docs/ci/groups.md +++ b/docs/ci/groups.md @@ -37,7 +37,7 @@ for each of the jobs looks like this: ``` strategy: matrix: - go-version: [ 1.18.x ] + go-version: [ 1.19.x ] goarch: [ "amd64" ] e2e-group: [ 1, 2, 3 ] ``` @@ -54,7 +54,7 @@ groups 1 and 3, the matrix strategy config should look like: ``` strategy: matrix: - go-version: [ 1.18.x ] + go-version: [ 1.19.x ] goarch: [ "amd64" ] e2e-group: [ 2 ] ``` @@ -62,7 +62,7 @@ If we want to re-add group 1: ``` strategy: matrix: - go-version: [ 1.18.x ] + go-version: [ 1.19.x ] goarch: [ "amd64" ] e2e-group: [ 1, 2 ] ``` diff --git a/etherman/etherman.go b/etherman/etherman.go index cd3b6e6add..5e2314f25c 100644 --- a/etherman/etherman.go +++ b/etherman/etherman.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" @@ -594,11 +595,11 @@ func (etherMan *Client) forcedBatchEvent(ctx context.Context, vLog types.Log, bl } else if isPending { return fmt.Errorf("error: tx is still pending. TxHash: %s", tx.Hash().String()) } - msg, err := tx.AsMessage(types.NewLondonSigner(tx.ChainId()), big.NewInt(0)) + msg, err := core.TransactionToMessage(tx, types.NewLondonSigner(tx.ChainId()), big.NewInt(0)) if err != nil { return err } - if fb.Sequencer == msg.From() { + if fb.Sequencer == msg.From { txData := tx.Data() // Extract coded txs. // Load contract ABI @@ -661,11 +662,11 @@ func (etherMan *Client) sequencedBatchesEvent(ctx context.Context, vLog types.Lo } else if isPending { return fmt.Errorf("error tx is still pending. TxHash: %s", tx.Hash().String()) } - msg, err := tx.AsMessage(types.NewLondonSigner(tx.ChainId()), big.NewInt(0)) + msg, err := core.TransactionToMessage(tx, types.NewLondonSigner(tx.ChainId()), big.NewInt(0)) if err != nil { return err } - sequences, err := decodeSequences(tx.Data(), sb.NumBatch, msg.From(), vLog.TxHash, msg.Nonce()) + sequences, err := decodeSequences(tx.Data(), sb.NumBatch, msg.From, vLog.TxHash, msg.Nonce) if err != nil { return fmt.Errorf("error decoding the sequences: %v", err) } @@ -786,7 +787,7 @@ func (etherMan *Client) forceSequencedBatchesEvent(ctx context.Context, vLog typ } else if isPending { return fmt.Errorf("error: tx is still pending. TxHash: %s", tx.Hash().String()) } - msg, err := tx.AsMessage(types.NewLondonSigner(tx.ChainId()), big.NewInt(0)) + msg, err := core.TransactionToMessage(tx, types.NewLondonSigner(tx.ChainId()), big.NewInt(0)) if err != nil { return err } @@ -794,7 +795,7 @@ func (etherMan *Client) forceSequencedBatchesEvent(ctx context.Context, vLog typ if err != nil { return fmt.Errorf("error getting hashParent. BlockNumber: %d. Error: %w", vLog.BlockNumber, err) } - sequencedForceBatch, err := decodeSequencedForceBatches(tx.Data(), fsb.NumBatch, msg.From(), vLog.TxHash, fullBlock, msg.Nonce()) + sequencedForceBatch, err := decodeSequencedForceBatches(tx.Data(), fsb.NumBatch, msg.From, vLog.TxHash, fullBlock, msg.Nonce) if err != nil { return err } diff --git a/go.mod b/go.mod index 746870c460..6fa1acfbd8 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/0xPolygonHermez/zkevm-node -go 1.18 +go 1.19 require ( github.com/didip/tollbooth/v6 v6.1.2 github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7 - github.com/ethereum/go-ethereum v1.11.0 + github.com/ethereum/go-ethereum v1.11.5 github.com/go-git/go-billy/v5 v5.4.1 github.com/go-git/go-git/v5 v5.6.1 github.com/gobuffalo/packr/v2 v2.8.3 @@ -24,7 +24,7 @@ require ( github.com/umbracle/ethgo v0.1.3 github.com/urfave/cli/v2 v2.25.1 go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.7.0 + golang.org/x/crypto v0.8.0 golang.org/x/sync v0.1.0 google.golang.org/grpc v1.54.0 google.golang.org/protobuf v1.30.0 @@ -40,7 +40,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.6.0 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.1.0 // indirect github.com/cockroachdb/errors v1.9.1 // indirect @@ -50,7 +50,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect @@ -66,6 +66,7 @@ require ( github.com/go-stack/stack v1.8.1 // indirect github.com/gobuffalo/logger v1.0.7 // indirect github.com/gobuffalo/packd v1.0.2 // indirect + github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -124,10 +125,10 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.8.0 // indirect golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/term v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.1.0 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index deae68f9ca..f3be5ed384 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,8 @@ github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqO github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -152,6 +154,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -185,6 +189,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/ethereum/go-ethereum v1.11.0 h1:5ervzucOW7z0TnTMPfWPgkb12utq7mmPb4/OmYnoTq8= github.com/ethereum/go-ethereum v1.11.0/go.mod h1:DuefStAgaxoaYGLR0FueVcVbehmn5n9QUcVrMCuOvuc= +github.com/ethereum/go-ethereum v1.11.5 h1:3M1uan+LAUvdn+7wCEFrcMM4LJTeuxDrPTg/f31a5QQ= +github.com/ethereum/go-ethereum v1.11.5/go.mod h1:it7x0DWnTDMfVFdXcU6Ti4KEFQynLHVRarcSlPr0HBo= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -260,6 +266,8 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -870,6 +878,8 @@ golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -969,6 +979,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1081,6 +1093,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1091,6 +1105,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1105,6 +1121,8 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/jsonrpc/endpoints_debug.go b/jsonrpc/endpoints_debug.go index fb89f8f3ff..dad3f900c6 100644 --- a/jsonrpc/endpoints_debug.go +++ b/jsonrpc/endpoints_debug.go @@ -3,8 +3,10 @@ package jsonrpc import ( "context" "encoding/hex" + "encoding/json" "errors" "fmt" + "strings" "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" "github.com/0xPolygonHermez/zkevm-node/log" @@ -30,11 +32,12 @@ type DebugEndpoints struct { } type traceConfig struct { - DisableStorage bool `json:"disableStorage"` - DisableStack bool `json:"disableStack"` - EnableMemory bool `json:"enableMemory"` - EnableReturnData bool `json:"enableReturnData"` - Tracer *string `json:"tracer"` + DisableStorage bool `json:"disableStorage"` + DisableStack bool `json:"disableStack"` + EnableMemory bool `json:"enableMemory"` + EnableReturnData bool `json:"enableReturnData"` + Tracer *string `json:"tracer"` + TracerConfig json.RawMessage `json:"tracerConfig"` } // StructLogRes represents the debug trace information for each opcode @@ -133,17 +136,23 @@ func (d *DebugEndpoints) buildTraceBlock(ctx context.Context, txs []*ethTypes.Tr } func (d *DebugEndpoints) buildTraceTransaction(ctx context.Context, hash common.Hash, cfg *traceConfig, dbTx pgx.Tx) (interface{}, types.Error) { - trcCfg := cfg - if trcCfg == nil { - trcCfg = defaultTraceConfig + traceCfg := cfg + if traceCfg == nil { + traceCfg = defaultTraceConfig + } + + // check tracer + if traceCfg.Tracer != nil && *traceCfg.Tracer != "" && !isBuiltInTracer(*traceCfg.Tracer) && !isJSCustomTracer(*traceCfg.Tracer) { + return rpcErrorResponse(types.DefaultErrorCode, "invalid tracer", nil) } stateTraceConfig := state.TraceConfig{ - DisableStack: trcCfg.DisableStack, - DisableStorage: trcCfg.DisableStorage, - EnableMemory: trcCfg.EnableMemory, - EnableReturnData: trcCfg.EnableReturnData, - Tracer: trcCfg.Tracer, + DisableStack: traceCfg.DisableStack, + DisableStorage: traceCfg.DisableStorage, + EnableMemory: traceCfg.EnableMemory, + EnableReturnData: traceCfg.EnableReturnData, + Tracer: traceCfg.Tracer, + TracerConfig: traceCfg.TracerConfig, } result, err := d.state.DebugTransaction(ctx, hash, stateTraceConfig, dbTx) if errors.Is(err, state.ErrNotFound) { @@ -154,17 +163,25 @@ func (d *DebugEndpoints) buildTraceTransaction(ctx context.Context, hash common. return nil, types.NewRPCError(types.DefaultErrorCode, errorMessage) } + // if a tracer was specified, then return the trace result if stateTraceConfig.Tracer != nil && *stateTraceConfig.Tracer != "" && len(result.ExecutorTraceResult) > 0 { return result.ExecutorTraceResult, nil } - failed := result.Failed() + receipt, err := d.state.GetTransactionReceipt(ctx, hash, dbTx) + if err != nil { + const errorMessage = "failed to tx receipt" + log.Errorf("%v: %v", errorMessage, err) + return nil, types.NewRPCError(types.DefaultErrorCode, errorMessage) + } + + failed := receipt.Status == ethTypes.ReceiptStatusFailed var returnValue interface{} if stateTraceConfig.EnableReturnData { returnValue = common.Bytes2Hex(result.ReturnValue) } - structLogs := d.buildStructLogs(result.StructLogs, *trcCfg) + structLogs := d.buildStructLogs(result.StructLogs, *traceCfg) resp := traceTransactionResponse{ Gas: result.GasUsed, @@ -235,3 +252,22 @@ func (d *DebugEndpoints) buildStructLogs(stateStructLogs []instrumentation.Struc } return structLogs } + +// isBuiltInTracer checks if the tracer is one of the +// built-in tracers +func isBuiltInTracer(tracer string) bool { + // built-in tracers + switch tracer { + case "callTracer", "4byteTracer", "prestateTracer", "noopTracer": + return true + default: + return false + } +} + +// isJSCustomTracer checks if the tracer contains the +// functions result and fault which are required for a custom tracer +// https://geth.ethereum.org/docs/developers/evm-tracing/custom-tracer +func isJSCustomTracer(tracer string) bool { + return strings.Contains(tracer, "result") && strings.Contains(tracer, "fault") +} diff --git a/state/converters.go b/state/converters.go index 7dceca65a2..693dcfc071 100644 --- a/state/converters.go +++ b/state/converters.go @@ -285,9 +285,10 @@ func convertToInstrumentationSteps(responses []*pb.TransactionStep) []instrument step.Contract = convertToInstrumentationContract(response.Contract) step.GasCost = fmt.Sprint(response.GasCost) step.Stack = response.Stack - step.Memory = convertByteArrayToStringArray(response.Memory) - step.ReturnData = string(response.ReturnData) - + step.Memory = make([]byte, len(response.Memory)) + copy(step.Memory, response.Memory) + step.ReturnData = make([]byte, len(response.ReturnData)) + copy(step.ReturnData, response.ReturnData) results = append(results, *step) } return results @@ -303,14 +304,6 @@ func convertToInstrumentationContract(response *pb.Contract) instrumentation.Con } } -func convertByteArrayToStringArray(responses []byte) []string { - results := make([]string, 0, len(responses)) - for _, response := range responses { - results = append(results, string(response)) - } - return results -} - func convertToCounters(resp *pb.ProcessBatchResponse) ZKCounters { return ZKCounters{ CumulativeGasUsed: resp.CumulativeGasUsed, diff --git a/state/fakedb.go b/state/fakedb.go index e47b223801..f204f8b9b3 100644 --- a/state/fakedb.go +++ b/state/fakedb.go @@ -6,6 +6,8 @@ import ( "github.com/0xPolygonHermez/zkevm-node/log" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" ) // FakeDB is the implementation of the fakeevm.FakeDB interface @@ -19,6 +21,21 @@ func (f *FakeDB) SetStateRoot(stateRoot []byte) { f.stateRoot = stateRoot } +// CreateAccount not implemented +func (f *FakeDB) CreateAccount(common.Address) { + log.Error("FakeDB: CreateAccount method not implemented") +} + +// SubBalance not implemented +func (f *FakeDB) SubBalance(common.Address, *big.Int) { + log.Error("FakeDB: SubBalance method not implemented") +} + +// AddBalance not implemented +func (f *FakeDB) AddBalance(common.Address, *big.Int) { + log.Error("FakeDB: AddBalance method not implemented") +} + // GetBalance returns the balance of the given address. func (f *FakeDB) GetBalance(address common.Address) *big.Int { ctx := context.Background() @@ -46,6 +63,23 @@ func (f *FakeDB) GetNonce(address common.Address) uint64 { return nonce.Uint64() } +// SetNonce not implemented +func (f *FakeDB) SetNonce(common.Address, uint64) { + log.Error("FakeDB: SetNonce method not implemented") +} + +// GetCodeHash gets the hash for the code at a given address +func (f *FakeDB) GetCodeHash(address common.Address) common.Hash { + ctx := context.Background() + hash, err := f.State.GetTree().GetCodeHash(ctx, address, f.stateRoot) + if err != nil { + log.Errorf("error on FakeDB GetCodeHash for address %v, err: %v", address, err) + } + + log.Debugf("FakeDB GetCodeHash for address %v => %v", address, common.BytesToHash(hash)) + return common.BytesToHash(hash) +} + // GetCode returns the SC code of the given address. func (f *FakeDB) GetCode(address common.Address) []byte { ctx := context.Background() @@ -59,6 +93,38 @@ func (f *FakeDB) GetCode(address common.Address) []byte { return code } +// SetCode not implemented +func (f *FakeDB) SetCode(common.Address, []byte) { + log.Error("FakeDB: SetCode method not implemented") +} + +// GetCodeSize get address code size +func (f *FakeDB) GetCodeSize(address common.Address) int { + return len(f.GetCode(address)) +} + +// AddRefund not implemented +func (f *FakeDB) AddRefund(uint64) { + log.Error("FakeDB: AddRefund method not implemented") +} + +// SubRefund not implemented +func (f *FakeDB) SubRefund(uint64) { + log.Error("FakeDB: SubRefund method not implemented") +} + +// GetRefund not implemented +func (f *FakeDB) GetRefund() uint64 { + log.Error("FakeDB: GetRefund method not implemented") + return 0 +} + +// GetCommittedState not implemented +func (f *FakeDB) GetCommittedState(common.Address, common.Hash) common.Hash { + log.Error("FakeDB: GetCommittedState method not implemented") + return ZeroHash +} + // GetState retrieves a value from the given account's storage trie. func (f *FakeDB) GetState(address common.Address, hash common.Hash) common.Hash { ctx := context.Background() @@ -73,20 +139,94 @@ func (f *FakeDB) GetState(address common.Address, hash common.Hash) common.Hash return common.BytesToHash(storage.Bytes()) } -// Exist determines if the given address is in use. +// SetState not implemented +func (f *FakeDB) SetState(common.Address, common.Hash, common.Hash) { + log.Error("FakeDB: SetState method not implemented") +} + +// GetTransientState not implemented +func (f *FakeDB) GetTransientState(addr common.Address, key common.Hash) common.Hash { + log.Error("FakeDB: GetTransientState method not implemented") + return ZeroHash +} + +// SetTransientState not implemented +func (f *FakeDB) SetTransientState(addr common.Address, key, value common.Hash) { + log.Error("FakeDB: SetTransientState method not implemented") +} + +// Suicide not implemented +func (f *FakeDB) Suicide(common.Address) bool { + log.Error("FakeDB: Suicide method not implemented") + return false +} + +// HasSuicided not implemented +func (f *FakeDB) HasSuicided(common.Address) bool { + log.Error("FakeDB: HasSuicided method not implemented") + return false +} + +// Exist reports whether the given account exists in state. +// Notably this should also return true for suicided accounts. func (f *FakeDB) Exist(address common.Address) bool { return !(f.GetNonce(address) == 0 && f.GetBalance(address).Int64() == 0 && f.GetCodeHash(address) == ZeroHash) } -// GetCodeHash gets the hash for the code at a given address -func (f *FakeDB) GetCodeHash(address common.Address) common.Hash { - ctx := context.Background() - hash, err := f.State.GetTree().GetCodeHash(ctx, address, f.stateRoot) +// Empty returns whether the given account is empty. Empty +// is defined according to EIP161 (balance = nonce = code = 0). +func (f *FakeDB) Empty(address common.Address) bool { + return !(f.GetNonce(address) == 0 && f.GetBalance(address).Int64() == 0 && f.GetCodeHash(address) == ZeroHash) +} - if err != nil { - log.Errorf("error on FakeDB GetCodeHash for address %v, err: %v", address, err) - } +// AddressInAccessList not implemented +func (f *FakeDB) AddressInAccessList(addr common.Address) bool { + log.Error("FakeDB: AddressInAccessList method not implemented") + return false +} - log.Debugf("FakeDB GetCodeHash for address %v => %v", address, common.BytesToHash(hash)) - return common.BytesToHash(hash) +// SlotInAccessList not implemented +func (f *FakeDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { + log.Error("FakeDB: SlotInAccessList method not implemented") + return false, false +} + +// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform + +// AddAddressToAccessList not implemented// even if the feature/fork is not active yet +func (f *FakeDB) AddAddressToAccessList(addr common.Address) { + log.Error("FakeDB: AddAddressToAccessList method not implemented") +} + +// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform + +// AddSlotToAccessList not implemented// even if the feature/fork is not active yet +func (f *FakeDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + log.Error("FakeDB: AddSlotToAccessList method not implemented") +} + +// Prepare not implemented +func (f *FakeDB) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) { + log.Error("FakeDB: Prepare method not implemented") +} + +// RevertToSnapshot not implemented +func (f *FakeDB) RevertToSnapshot(int) { + log.Error("FakeDB: RevertToSnapshot method not implemented") +} + +// Snapshot not implemented +func (f *FakeDB) Snapshot() int { + log.Error("FakeDB: Snapshot method not implemented") + return 0 +} + +// AddLog not implemented +func (f *FakeDB) AddLog(*types.Log) { + log.Error("FakeDB: AddLog method not implemented") +} + +// AddPreimage not implemented +func (f *FakeDB) AddPreimage(common.Hash, []byte) { + log.Error("FakeDB: AddPreimage method not implemented") } diff --git a/state/runtime/fakevm/analysis.go b/state/runtime/fakevm/analysis.go new file mode 100644 index 0000000000..3b6d81fad2 --- /dev/null +++ b/state/runtime/fakevm/analysis.go @@ -0,0 +1,118 @@ +// Copyright 2014 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 fakevm + +const ( + set2BitsMask = uint16(0b11) + set3BitsMask = uint16(0b111) + set4BitsMask = uint16(0b1111) + set5BitsMask = uint16(0b1_1111) + set6BitsMask = uint16(0b11_1111) + set7BitsMask = uint16(0b111_1111) +) + +// bitvec is a bit vector which maps bytes in a program. +// An unset bit means the byte is an opcode, a set bit means +// it's data (i.e. argument of PUSHxx). +type bitvec []byte + +func (bits bitvec) set1(pos uint64) { + bits[pos/8] |= 1 << (pos % 8) +} + +func (bits bitvec) setN(flag uint16, pos uint64) { + a := flag << (pos % 8) + bits[pos/8] |= byte(a) + if b := byte(a >> 8); b != 0 { + bits[pos/8+1] = b + } +} + +func (bits bitvec) set8(pos uint64) { + a := byte(0xFF << (pos % 8)) + bits[pos/8] |= a + bits[pos/8+1] = ^a +} + +func (bits bitvec) set16(pos uint64) { + a := byte(0xFF << (pos % 8)) + bits[pos/8] |= a + bits[pos/8+1] = 0xFF + bits[pos/8+2] = ^a +} + +// codeSegment checks if the position is in a code segment. +func (bits *bitvec) codeSegment(pos uint64) bool { + return (((*bits)[pos/8] >> (pos % 8)) & 1) == 0 +} + +// codeBitmap collects data locations in code. +func codeBitmap(code []byte) bitvec { + // The bitmap is 4 bytes longer than necessary, in case the code + // ends with a PUSH32, the algorithm will push zeroes onto the + // bitvector outside the bounds of the actual code. + bits := make(bitvec, len(code)/8+1+4) + return codeBitmapInternal(code, bits) +} + +// codeBitmapInternal is the internal implementation of codeBitmap. +// It exists for the purpose of being able to run benchmark tests +// without dynamic allocations affecting the results. +func codeBitmapInternal(code, bits bitvec) bitvec { + for pc := uint64(0); pc < uint64(len(code)); { + op := OpCode(code[pc]) + pc++ + if int8(op) < int8(PUSH1) { // If not PUSH (the int8(op) > int(PUSH32) is always false). + continue + } + numbits := op - PUSH1 + 1 + if numbits >= 8 { + for ; numbits >= 16; numbits -= 16 { + bits.set16(pc) + pc += 16 + } + for ; numbits >= 8; numbits -= 8 { + bits.set8(pc) + pc += 8 + } + } + switch numbits { + case 1: + bits.set1(pc) + pc += 1 + case 2: + bits.setN(set2BitsMask, pc) + pc += 2 + case 3: + bits.setN(set3BitsMask, pc) + pc += 3 + case 4: + bits.setN(set4BitsMask, pc) + pc += 4 + case 5: + bits.setN(set5BitsMask, pc) + pc += 5 + case 6: + bits.setN(set6BitsMask, pc) + pc += 6 + case 7: + bits.setN(set7BitsMask, pc) + pc += 7 + } + } + return bits +} diff --git a/state/runtime/fakevm/common.go b/state/runtime/fakevm/common.go new file mode 100644 index 0000000000..c637c7fdd0 --- /dev/null +++ b/state/runtime/fakevm/common.go @@ -0,0 +1,82 @@ +// Copyright 2014 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 fakevm + +import ( + "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 *uint256.Int) (uint64, bool) { + if !l.IsUint64() { + return 0, true + } + return calcMemSize64WithUint(off, l.Uint64()) +} + +// 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 *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 + offset64, overflow := off.Uint64WithOverflow() + if overflow { + return 0, true + } + val := offset64 + length64 + // if value < either of it's parts, then it overflowed + return val, val < offset64 +} + +// getData 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 getData(data []byte, start uint64, size uint64) []byte { + length := uint64(len(data)) + if start > length { + start = length + } + end := start + size + if end > length { + end = length + } + return common.RightPadBytes(data[start:end], int(size)) +} + +// toWordSize returns the ceiled word size required for memory expansion. +func toWordSize(size uint64) uint64 { + if size > math.MaxUint64-31 { + return math.MaxUint64/32 + 1 + } + + return (size + 31) / 32 +} + +func allZero(b []byte) bool { + for _, byte := range b { + if byte != 0 { + return false + } + } + return true +} diff --git a/state/runtime/fakevm/contract.go b/state/runtime/fakevm/contract.go new file mode 100644 index 0000000000..c4f4d90a7d --- /dev/null +++ b/state/runtime/fakevm/contract.go @@ -0,0 +1,194 @@ +// Copyright 2015 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 fakevm + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +// ContractRef is a reference to the contract's backing object +type ContractRef interface { + Address() common.Address +} + +// AccountRef implements ContractRef. +// +// Account references are used during EVM initialisation and +// it's primary use is to fetch addresses. Removing this object +// proves difficult because of the cached jump destinations which +// are fetched from the parent contract (i.e. the caller), which +// is a ContractRef. +type AccountRef common.Address + +// Address casts AccountRef to a Address +func (ar AccountRef) Address() common.Address { return (common.Address)(ar) } + +// Contract represents an ethereum contract in the state database. It contains +// the contract code, calling arguments. Contract implements ContractRef +type Contract struct { + // CallerAddress is the result of the caller which initialised this + // contract. However when the "call method" is delegated this value + // needs to be initialised to that of the caller's caller. + CallerAddress common.Address + caller ContractRef + self ContractRef + + jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis. + analysis bitvec // Locally cached result of JUMPDEST analysis + + Code []byte + CodeHash common.Hash + CodeAddr *common.Address + Input []byte + + Gas uint64 + value *big.Int +} + +// NewContract returns a new contract environment for the execution of EVM. +func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uint64) *Contract { + c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object} + + if parent, ok := caller.(*Contract); ok { + // Reuse JUMPDEST analysis from parent context if available. + c.jumpdests = parent.jumpdests + } else { + c.jumpdests = make(map[common.Hash]bitvec) + } + + // Gas should be a pointer so it can safely be reduced through the run + // This pointer will be off the state transition + c.Gas = gas + // ensures a value is set + c.value = value + + return c +} + +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 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) +} + +// 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] + if !exist { + // Do the analysis and save in parent context + // We do not need to store it in c.analysis + 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 + // in state trie. In that case, we do an analysis, and save it locally, so + // we don't have to recalculate it for every JUMP instruction in the execution + // However, we don't save it within the parent context + if c.analysis == nil { + c.analysis = codeBitmap(c.Code) + } + return c.analysis.codeSegment(udest) +} + +// AsDelegate sets the contract to be a delegate call and returns the current +// contract (for chaining calls) +func (c *Contract) AsDelegate() *Contract { + // NOTE: caller must, at all times be a contract. It should never happen + // that caller is something other than a Contract. + parent := c.caller.(*Contract) + c.CallerAddress = parent.CallerAddress + c.value = parent.value + + return c +} + +// GetOp returns the n'th element in the contract's byte array +func (c *Contract) GetOp(n uint64) OpCode { + if n < uint64(len(c.Code)) { + return OpCode(c.Code[n]) + } + + return STOP +} + +// Caller returns the caller of the contract. +// +// Caller will recursively call caller when the contract is a delegate +// call, including that of caller's caller. +func (c *Contract) Caller() common.Address { + return c.CallerAddress +} + +// UseGas attempts the use gas and subtracts it and returns true on success +func (c *Contract) UseGas(gas uint64) (ok bool) { + if c.Gas < gas { + return false + } + c.Gas -= gas + return true +} + +// Address returns the contracts address +func (c *Contract) Address() common.Address { + return c.self.Address() +} + +// Value returns the contract's value (sent to it from it's caller) +func (c *Contract) Value() *big.Int { + return c.value +} + +// SetCallCode sets the code of the contract and address of the backing data +// object +func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { + c.Code = code + c.CodeHash = hash + c.CodeAddr = addr +} + +// SetCodeOptionalHash can be used to provide code, but it's optional to provide hash. +// In case hash is not provided, the jumpdest analysis will not be saved to the parent context +func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash) { + c.Code = codeAndHash.code + c.CodeHash = codeAndHash.hash + c.CodeAddr = addr +} diff --git a/state/runtime/fakevm/contracts.go b/state/runtime/fakevm/contracts.go new file mode 100644 index 0000000000..c3ece760dc --- /dev/null +++ b/state/runtime/fakevm/contracts.go @@ -0,0 +1,1050 @@ +// Copyright 2014 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 fakevm + +import ( + "crypto/sha256" + "encoding/binary" + "errors" + "math/big" + + "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/bls12381" + "github.com/ethereum/go-ethereum/crypto/bn256" + "github.com/ethereum/go-ethereum/params" + "golang.org/x/crypto/ripemd160" +) + +// PrecompiledContract is the basic interface for native Go contracts. The implementation +// requires a deterministic gas count based on the input size of the Run method of the +// contract. +type PrecompiledContract interface { + RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use + Run(input []byte) ([]byte, error) // Run runs the precompiled contract +} + +// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum +// contracts used in the Frontier and Homestead releases. +var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, +} + +// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum +// contracts used in the Byzantium release. +var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, + common.BytesToAddress([]byte{6}): &bn256AddByzantium{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{}, + common.BytesToAddress([]byte{8}): &bn256PairingByzantium{}, +} + +// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum +// contracts used in the Istanbul release. +var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, +} + +// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum +// contracts used in the Berlin release. +var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, +} + +// PrecompiledContractsBLS contains the set of pre-compiled Ethereum +// contracts specified in EIP-2537. These are exported for testing purposes. +var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{10}): &bls12381G1Add{}, + common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, + common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, + common.BytesToAddress([]byte{13}): &bls12381G2Add{}, + common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, + common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, + common.BytesToAddress([]byte{16}): &bls12381Pairing{}, + common.BytesToAddress([]byte{17}): &bls12381MapG1{}, + common.BytesToAddress([]byte{18}): &bls12381MapG2{}, +} + +var ( + PrecompiledAddressesBerlin []common.Address + PrecompiledAddressesIstanbul []common.Address + PrecompiledAddressesByzantium []common.Address + PrecompiledAddressesHomestead []common.Address +) + +func init() { + for k := range PrecompiledContractsHomestead { + PrecompiledAddressesHomestead = append(PrecompiledAddressesHomestead, k) + } + for k := range PrecompiledContractsByzantium { + PrecompiledAddressesByzantium = append(PrecompiledAddressesByzantium, k) + } + for k := range PrecompiledContractsIstanbul { + PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) + } + for k := range PrecompiledContractsBerlin { + PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) + } +} + +// ActivePrecompiles returns the precompiles enabled with the current configuration. +func ActivePrecompiles(rules params.Rules) []common.Address { + switch { + case rules.IsBerlin: + return PrecompiledAddressesBerlin + case rules.IsIstanbul: + return PrecompiledAddressesIstanbul + case rules.IsByzantium: + return PrecompiledAddressesByzantium + default: + return PrecompiledAddressesHomestead + } +} + +// RunPrecompiledContract runs and evaluates the output of a precompiled contract. +// It returns +// - the returned bytes, +// - the _remaining_ gas, +// - any error that occurred +func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + gasCost := p.RequiredGas(input) + if suppliedGas < gasCost { + return nil, 0, ErrOutOfGas + } + suppliedGas -= gasCost + output, err := p.Run(input) + return output, suppliedGas, err +} + +// ECRECOVER implemented as a native contract. +type ecrecover struct{} + +func (c *ecrecover) RequiredGas(input []byte) uint64 { + return params.EcrecoverGas +} + +func (c *ecrecover) Run(input []byte) ([]byte, error) { + const ecRecoverInputLength = 128 + + input = common.RightPadBytes(input, ecRecoverInputLength) + // "input" is (hash, v, r, s), each 32 bytes + // but for ecrecover we want (r, s, v) + + r := new(big.Int).SetBytes(input[64:96]) + s := new(big.Int).SetBytes(input[96:128]) + v := input[63] - 27 + + // tighter sig s values input homestead only apply to tx sigs + if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) { + return nil, nil + } + // We must make sure not to modify the 'input', so placing the 'v' along with + // the signature needs to be done on a new allocation + sig := make([]byte, 65) + copy(sig, input[64:128]) + sig[64] = v + // v needs to be at the end for libsecp256k1 + pubKey, err := crypto.Ecrecover(input[:32], sig) + // make sure the public key is a valid one + if err != nil { + return nil, nil + } + + // the first byte of pubkey is bitcoin heritage + return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32), nil +} + +// SHA256 implemented as a native contract. +type sha256hash struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +// +// This method does not require any overflow checking as the input size gas costs +// required for anything significant is so high it's impossible to pay for. +func (c *sha256hash) RequiredGas(input []byte) uint64 { + return uint64(len(input)+31)/32*params.Sha256PerWordGas + params.Sha256BaseGas +} +func (c *sha256hash) Run(input []byte) ([]byte, error) { + h := sha256.Sum256(input) + return h[:], nil +} + +// RIPEMD160 implemented as a native contract. +type ripemd160hash struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +// +// This method does not require any overflow checking as the input size gas costs +// required for anything significant is so high it's impossible to pay for. +func (c *ripemd160hash) RequiredGas(input []byte) uint64 { + return uint64(len(input)+31)/32*params.Ripemd160PerWordGas + params.Ripemd160BaseGas +} +func (c *ripemd160hash) Run(input []byte) ([]byte, error) { + ripemd := ripemd160.New() + ripemd.Write(input) + return common.LeftPadBytes(ripemd.Sum(nil), 32), nil +} + +// data copy implemented as a native contract. +type dataCopy struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +// +// This method does not require any overflow checking as the input size gas costs +// required for anything significant is so high it's impossible to pay for. +func (c *dataCopy) RequiredGas(input []byte) uint64 { + return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas +} +func (c *dataCopy) Run(in []byte) ([]byte, error) { + return common.CopyBytes(in), nil +} + +// bigModExp implements a native big integer exponential modular operation. +type bigModExp struct { + eip2565 bool +} + +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) + big480 = big.NewInt(480) + big1024 = big.NewInt(1024) + big3072 = big.NewInt(3072) + big199680 = big.NewInt(199680) +) + +// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198 +// +// def mult_complexity(x): +// if x <= 64: return x ** 2 +// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072 +// else: return x ** 2 // 16 + 480 * x - 199680 +// +// where is x is max(length_of_MODULUS, length_of_BASE) +func modexpMultComplexity(x *big.Int) *big.Int { + switch { + case x.Cmp(big64) <= 0: + x.Mul(x, x) // x ** 2 + case x.Cmp(big1024) <= 0: + // (x ** 2 // 4 ) + ( 96 * x - 3072) + x = new(big.Int).Add( + new(big.Int).Div(new(big.Int).Mul(x, x), big4), + new(big.Int).Sub(new(big.Int).Mul(big96, x), big3072), + ) + default: + // (x ** 2 // 16) + (480 * x - 199680) + x = new(big.Int).Add( + new(big.Int).Div(new(big.Int).Mul(x, x), big16), + new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680), + ) + } + return x +} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bigModExp) RequiredGas(input []byte) uint64 { + var ( + baseLen = new(big.Int).SetBytes(getData(input, 0, 32)) + expLen = new(big.Int).SetBytes(getData(input, 32, 32)) + modLen = new(big.Int).SetBytes(getData(input, 64, 32)) + ) + if len(input) > 96 { + input = input[96:] + } else { + input = input[:0] + } + // Retrieve the head 32 bytes of exp for the adjusted exponent length + var expHead *big.Int + if big.NewInt(int64(len(input))).Cmp(baseLen) <= 0 { + expHead = new(big.Int) + } else { + if expLen.Cmp(big32) > 0 { + expHead = new(big.Int).SetBytes(getData(input, baseLen.Uint64(), 32)) + } else { + expHead = new(big.Int).SetBytes(getData(input, baseLen.Uint64(), expLen.Uint64())) + } + } + // Calculate the adjusted exponent length + var msb int + if bitlen := expHead.BitLen(); bitlen > 0 { + msb = bitlen - 1 + } + adjExpLen := new(big.Int) + if expLen.Cmp(big32) > 0 { + adjExpLen.Sub(expLen, big32) + adjExpLen.Mul(big8, adjExpLen) + } + adjExpLen.Add(adjExpLen, big.NewInt(int64(msb))) + // Calculate the gas cost of the operation + gas := new(big.Int).Set(math.BigMax(modLen, baseLen)) + if c.eip2565 { + // EIP-2565 has three changes + // 1. Different multComplexity (inlined here) + // in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565): + // + // def mult_complexity(x): + // ceiling(x/8)^2 + // + //where is x is max(length_of_MODULUS, length_of_BASE) + gas = gas.Add(gas, big7) + gas = gas.Div(gas, big8) + gas.Mul(gas, gas) + + gas.Mul(gas, math.BigMax(adjExpLen, big1)) + // 2. Different divisor (`GQUADDIVISOR`) (3) + gas.Div(gas, big3) + if gas.BitLen() > 64 { + return math.MaxUint64 + } + // 3. Minimum price of 200 gas + if gas.Uint64() < 200 { + return 200 + } + return gas.Uint64() + } + gas = modexpMultComplexity(gas) + gas.Mul(gas, math.BigMax(adjExpLen, big1)) + gas.Div(gas, big20) + + if gas.BitLen() > 64 { + return math.MaxUint64 + } + return gas.Uint64() +} + +func (c *bigModExp) Run(input []byte) ([]byte, error) { + var ( + baseLen = new(big.Int).SetBytes(getData(input, 0, 32)).Uint64() + expLen = new(big.Int).SetBytes(getData(input, 32, 32)).Uint64() + modLen = new(big.Int).SetBytes(getData(input, 64, 32)).Uint64() + ) + if len(input) > 96 { + input = input[96:] + } else { + input = input[:0] + } + // Handle a special case when both the base and mod length is zero + if baseLen == 0 && modLen == 0 { + return []byte{}, nil + } + // Retrieve the operands and execute the exponentiation + var ( + base = new(big.Int).SetBytes(getData(input, 0, baseLen)) + exp = new(big.Int).SetBytes(getData(input, baseLen, expLen)) + mod = new(big.Int).SetBytes(getData(input, baseLen+expLen, modLen)) + v []byte + ) + switch { + case mod.BitLen() == 0: + // Modulo 0 is undefined, return zero + return common.LeftPadBytes([]byte{}, int(modLen)), nil + case base.BitLen() == 1: // a bit length of 1 means it's 1 (or -1). + //If base == 1, then we can just return base % mod (if mod >= 1, which it is) + v = base.Mod(base, mod).Bytes() + default: + v = base.Exp(base, exp, mod).Bytes() + } + return common.LeftPadBytes(v, int(modLen)), nil +} + +// newCurvePoint unmarshals a binary blob into a bn256 elliptic curve point, +// returning it, or an error if the point is invalid. +func newCurvePoint(blob []byte) (*bn256.G1, error) { + p := new(bn256.G1) + if _, err := p.Unmarshal(blob); err != nil { + return nil, err + } + return p, nil +} + +// newTwistPoint unmarshals a binary blob into a bn256 elliptic curve point, +// returning it, or an error if the point is invalid. +func newTwistPoint(blob []byte) (*bn256.G2, error) { + p := new(bn256.G2) + if _, err := p.Unmarshal(blob); err != nil { + return nil, err + } + return p, nil +} + +// runBn256Add implements the Bn256Add precompile, referenced by both +// Byzantium and Istanbul operations. +func runBn256Add(input []byte) ([]byte, error) { + x, err := newCurvePoint(getData(input, 0, 64)) + if err != nil { + return nil, err + } + y, err := newCurvePoint(getData(input, 64, 64)) + if err != nil { + return nil, err + } + res := new(bn256.G1) + res.Add(x, y) + return res.Marshal(), nil +} + +// bn256Add implements a native elliptic curve point addition conforming to +// Istanbul consensus rules. +type bn256AddIstanbul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256AddIstanbul) RequiredGas(input []byte) uint64 { + return params.Bn256AddGasIstanbul +} + +func (c *bn256AddIstanbul) Run(input []byte) ([]byte, error) { + return runBn256Add(input) +} + +// bn256AddByzantium implements a native elliptic curve point addition +// conforming to Byzantium consensus rules. +type bn256AddByzantium struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256AddByzantium) RequiredGas(input []byte) uint64 { + return params.Bn256AddGasByzantium +} + +func (c *bn256AddByzantium) Run(input []byte) ([]byte, error) { + return runBn256Add(input) +} + +// runBn256ScalarMul implements the Bn256ScalarMul precompile, referenced by +// both Byzantium and Istanbul operations. +func runBn256ScalarMul(input []byte) ([]byte, error) { + p, err := newCurvePoint(getData(input, 0, 64)) + if err != nil { + return nil, err + } + res := new(bn256.G1) + res.ScalarMult(p, new(big.Int).SetBytes(getData(input, 64, 32))) + return res.Marshal(), nil +} + +// bn256ScalarMulIstanbul implements a native elliptic curve scalar +// multiplication conforming to Istanbul consensus rules. +type bn256ScalarMulIstanbul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256ScalarMulIstanbul) RequiredGas(input []byte) uint64 { + return params.Bn256ScalarMulGasIstanbul +} + +func (c *bn256ScalarMulIstanbul) Run(input []byte) ([]byte, error) { + return runBn256ScalarMul(input) +} + +// bn256ScalarMulByzantium implements a native elliptic curve scalar +// multiplication conforming to Byzantium consensus rules. +type bn256ScalarMulByzantium struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256ScalarMulByzantium) RequiredGas(input []byte) uint64 { + return params.Bn256ScalarMulGasByzantium +} + +func (c *bn256ScalarMulByzantium) Run(input []byte) ([]byte, error) { + return runBn256ScalarMul(input) +} + +var ( + // true32Byte is returned if the bn256 pairing check succeeds. + true32Byte = []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, 1} + + // false32Byte is returned if the bn256 pairing check fails. + false32Byte = make([]byte, 32) + + // errBadPairingInput is returned if the bn256 pairing input is invalid. + errBadPairingInput = errors.New("bad elliptic curve pairing size") +) + +// runBn256Pairing implements the Bn256Pairing precompile, referenced by both +// Byzantium and Istanbul operations. +func runBn256Pairing(input []byte) ([]byte, error) { + // Handle some corner cases cheaply + if len(input)%192 > 0 { + return nil, errBadPairingInput + } + // Convert the input into a set of coordinates + var ( + cs []*bn256.G1 + ts []*bn256.G2 + ) + for i := 0; i < len(input); i += 192 { + c, err := newCurvePoint(input[i : i+64]) + if err != nil { + return nil, err + } + t, err := newTwistPoint(input[i+64 : i+192]) + if err != nil { + return nil, err + } + cs = append(cs, c) + ts = append(ts, t) + } + // Execute the pairing checks and return the results + if bn256.PairingCheck(cs, ts) { + return true32Byte, nil + } + return false32Byte, nil +} + +// bn256PairingIstanbul implements a pairing pre-compile for the bn256 curve +// conforming to Istanbul consensus rules. +type bn256PairingIstanbul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256PairingIstanbul) RequiredGas(input []byte) uint64 { + return params.Bn256PairingBaseGasIstanbul + uint64(len(input)/192)*params.Bn256PairingPerPointGasIstanbul +} + +func (c *bn256PairingIstanbul) Run(input []byte) ([]byte, error) { + return runBn256Pairing(input) +} + +// bn256PairingByzantium implements a pairing pre-compile for the bn256 curve +// conforming to Byzantium consensus rules. +type bn256PairingByzantium struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256PairingByzantium) RequiredGas(input []byte) uint64 { + return params.Bn256PairingBaseGasByzantium + uint64(len(input)/192)*params.Bn256PairingPerPointGasByzantium +} + +func (c *bn256PairingByzantium) Run(input []byte) ([]byte, error) { + return runBn256Pairing(input) +} + +type blake2F struct{} + +func (c *blake2F) RequiredGas(input []byte) uint64 { + // If the input is malformed, we can't calculate the gas, return 0 and let the + // actual call choke and fault. + if len(input) != blake2FInputLength { + return 0 + } + return uint64(binary.BigEndian.Uint32(input[0:4])) +} + +const ( + blake2FInputLength = 213 + blake2FFinalBlockBytes = byte(1) + blake2FNonFinalBlockBytes = byte(0) +) + +var ( + errBlake2FInvalidInputLength = errors.New("invalid input length") + errBlake2FInvalidFinalFlag = errors.New("invalid final flag") +) + +func (c *blake2F) Run(input []byte) ([]byte, error) { + // Make sure the input is valid (correct length and final flag) + if len(input) != blake2FInputLength { + return nil, errBlake2FInvalidInputLength + } + if input[212] != blake2FNonFinalBlockBytes && input[212] != blake2FFinalBlockBytes { + return nil, errBlake2FInvalidFinalFlag + } + // Parse the input into the Blake2b call parameters + var ( + rounds = binary.BigEndian.Uint32(input[0:4]) + final = input[212] == blake2FFinalBlockBytes + + h [8]uint64 + m [16]uint64 + t [2]uint64 + ) + for i := 0; i < 8; i++ { + offset := 4 + i*8 + h[i] = binary.LittleEndian.Uint64(input[offset : offset+8]) + } + for i := 0; i < 16; i++ { + offset := 68 + i*8 + m[i] = binary.LittleEndian.Uint64(input[offset : offset+8]) + } + t[0] = binary.LittleEndian.Uint64(input[196:204]) + t[1] = binary.LittleEndian.Uint64(input[204:212]) + + // Execute the compression function, extract and return the result + blake2b.F(&h, m, t, final, rounds) + + output := make([]byte, 64) + for i := 0; i < 8; i++ { + offset := i * 8 + binary.LittleEndian.PutUint64(output[offset:offset+8], h[i]) + } + return output, nil +} + +var ( + errBLS12381InvalidInputLength = errors.New("invalid input length") + errBLS12381InvalidFieldElementTopBytes = errors.New("invalid field element top bytes") + errBLS12381G1PointSubgroup = errors.New("g1 point is not on correct subgroup") + errBLS12381G2PointSubgroup = errors.New("g2 point is not on correct subgroup") +) + +// bls12381G1Add implements EIP-2537 G1Add precompile. +type bls12381G1Add struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G1Add) RequiredGas(input []byte) uint64 { + return params.Bls12381G1AddGas +} + +func (c *bls12381G1Add) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G1Add precompile. + // > G1 addition call expects `256` bytes as an input that is interpreted as byte concatenation of two G1 points (`128` bytes each). + // > Output is an encoding of addition operation result - single G1 point (`128` bytes). + if len(input) != 256 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0, p1 *bls12381.PointG1 + + // Initialize G1 + g := bls12381.NewG1() + + // Decode G1 point p_0 + if p0, err = g.DecodePoint(input[:128]); err != nil { + return nil, err + } + // Decode G1 point p_1 + if p1, err = g.DecodePoint(input[128:]); err != nil { + return nil, err + } + + // Compute r = p_0 + p_1 + r := g.New() + g.Add(r, p0, p1) + + // Encode the G1 point result into 128 bytes + return g.EncodePoint(r), nil +} + +// bls12381G1Mul implements EIP-2537 G1Mul precompile. +type bls12381G1Mul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G1Mul) RequiredGas(input []byte) uint64 { + return params.Bls12381G1MulGas +} + +func (c *bls12381G1Mul) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G1Mul precompile. + // > G1 multiplication call expects `160` bytes as an input that is interpreted as byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes). + // > Output is an encoding of multiplication operation result - single G1 point (`128` bytes). + if len(input) != 160 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0 *bls12381.PointG1 + + // Initialize G1 + g := bls12381.NewG1() + + // Decode G1 point + if p0, err = g.DecodePoint(input[:128]); err != nil { + return nil, err + } + // Decode scalar value + e := new(big.Int).SetBytes(input[128:]) + + // Compute r = e * p_0 + r := g.New() + g.MulScalar(r, p0, e) + + // Encode the G1 point into 128 bytes + return g.EncodePoint(r), nil +} + +// bls12381G1MultiExp implements EIP-2537 G1MultiExp precompile. +type bls12381G1MultiExp struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G1MultiExp) RequiredGas(input []byte) uint64 { + // Calculate G1 point, scalar value pair length + k := len(input) / 160 + if k == 0 { + // Return 0 gas for small input length + return 0 + } + // Lookup discount value for G1 point, scalar value pair length + var discount uint64 + if dLen := len(params.Bls12381MultiExpDiscountTable); k < dLen { + discount = params.Bls12381MultiExpDiscountTable[k-1] + } else { + discount = params.Bls12381MultiExpDiscountTable[dLen-1] + } + // Calculate gas and return the result + return (uint64(k) * params.Bls12381G1MulGas * discount) / 1000 +} + +func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G1MultiExp precompile. + // G1 multiplication call expects `160*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes). + // Output is an encoding of multiexponentiation operation result - single G1 point (`128` bytes). + k := len(input) / 160 + if len(input) == 0 || len(input)%160 != 0 { + return nil, errBLS12381InvalidInputLength + } + var err error + points := make([]*bls12381.PointG1, k) + scalars := make([]*big.Int, k) + + // Initialize G1 + g := bls12381.NewG1() + + // Decode point scalar pairs + for i := 0; i < k; i++ { + off := 160 * i + t0, t1, t2 := off, off+128, off+160 + // Decode G1 point + if points[i], err = g.DecodePoint(input[t0:t1]); err != nil { + return nil, err + } + // Decode scalar value + scalars[i] = new(big.Int).SetBytes(input[t1:t2]) + } + + // Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1) + r := g.New() + g.MultiExp(r, points, scalars) + + // Encode the G1 point to 128 bytes + return g.EncodePoint(r), nil +} + +// bls12381G2Add implements EIP-2537 G2Add precompile. +type bls12381G2Add struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G2Add) RequiredGas(input []byte) uint64 { + return params.Bls12381G2AddGas +} + +func (c *bls12381G2Add) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G2Add precompile. + // > G2 addition call expects `512` bytes as an input that is interpreted as byte concatenation of two G2 points (`256` bytes each). + // > Output is an encoding of addition operation result - single G2 point (`256` bytes). + if len(input) != 512 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0, p1 *bls12381.PointG2 + + // Initialize G2 + g := bls12381.NewG2() + r := g.New() + + // Decode G2 point p_0 + if p0, err = g.DecodePoint(input[:256]); err != nil { + return nil, err + } + // Decode G2 point p_1 + if p1, err = g.DecodePoint(input[256:]); err != nil { + return nil, err + } + + // Compute r = p_0 + p_1 + g.Add(r, p0, p1) + + // Encode the G2 point into 256 bytes + return g.EncodePoint(r), nil +} + +// bls12381G2Mul implements EIP-2537 G2Mul precompile. +type bls12381G2Mul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G2Mul) RequiredGas(input []byte) uint64 { + return params.Bls12381G2MulGas +} + +func (c *bls12381G2Mul) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G2MUL precompile logic. + // > G2 multiplication call expects `288` bytes as an input that is interpreted as byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes). + // > Output is an encoding of multiplication operation result - single G2 point (`256` bytes). + if len(input) != 288 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0 *bls12381.PointG2 + + // Initialize G2 + g := bls12381.NewG2() + + // Decode G2 point + if p0, err = g.DecodePoint(input[:256]); err != nil { + return nil, err + } + // Decode scalar value + e := new(big.Int).SetBytes(input[256:]) + + // Compute r = e * p_0 + r := g.New() + g.MulScalar(r, p0, e) + + // Encode the G2 point into 256 bytes + return g.EncodePoint(r), nil +} + +// bls12381G2MultiExp implements EIP-2537 G2MultiExp precompile. +type bls12381G2MultiExp struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G2MultiExp) RequiredGas(input []byte) uint64 { + // Calculate G2 point, scalar value pair length + k := len(input) / 288 + if k == 0 { + // Return 0 gas for small input length + return 0 + } + // Lookup discount value for G2 point, scalar value pair length + var discount uint64 + if dLen := len(params.Bls12381MultiExpDiscountTable); k < dLen { + discount = params.Bls12381MultiExpDiscountTable[k-1] + } else { + discount = params.Bls12381MultiExpDiscountTable[dLen-1] + } + // Calculate gas and return the result + return (uint64(k) * params.Bls12381G2MulGas * discount) / 1000 +} + +func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 G2MultiExp precompile logic + // > G2 multiplication call expects `288*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes). + // > Output is an encoding of multiexponentiation operation result - single G2 point (`256` bytes). + k := len(input) / 288 + if len(input) == 0 || len(input)%288 != 0 { + return nil, errBLS12381InvalidInputLength + } + var err error + points := make([]*bls12381.PointG2, k) + scalars := make([]*big.Int, k) + + // Initialize G2 + g := bls12381.NewG2() + + // Decode point scalar pairs + for i := 0; i < k; i++ { + off := 288 * i + t0, t1, t2 := off, off+256, off+288 + // Decode G1 point + if points[i], err = g.DecodePoint(input[t0:t1]); err != nil { + return nil, err + } + // Decode scalar value + scalars[i] = new(big.Int).SetBytes(input[t1:t2]) + } + + // Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1) + r := g.New() + g.MultiExp(r, points, scalars) + + // Encode the G2 point to 256 bytes. + return g.EncodePoint(r), nil +} + +// bls12381Pairing implements EIP-2537 Pairing precompile. +type bls12381Pairing struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381Pairing) RequiredGas(input []byte) uint64 { + return params.Bls12381PairingBaseGas + uint64(len(input)/384)*params.Bls12381PairingPerPairGas +} + +func (c *bls12381Pairing) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 Pairing precompile logic. + // > Pairing call expects `384*k` bytes as an inputs that is interpreted as byte concatenation of `k` slices. Each slice has the following structure: + // > - `128` bytes of G1 point encoding + // > - `256` bytes of G2 point encoding + // > Output is a `32` bytes where last single byte is `0x01` if pairing result is equal to multiplicative identity in a pairing target field and `0x00` otherwise + // > (which is equivalent of Big Endian encoding of Solidity values `uint256(1)` and `uin256(0)` respectively). + k := len(input) / 384 + if len(input) == 0 || len(input)%384 != 0 { + return nil, errBLS12381InvalidInputLength + } + + // Initialize BLS12-381 pairing engine + e := bls12381.NewPairingEngine() + g1, g2 := e.G1, e.G2 + + // Decode pairs + for i := 0; i < k; i++ { + off := 384 * i + t0, t1, t2 := off, off+128, off+384 + + // Decode G1 point + p1, err := g1.DecodePoint(input[t0:t1]) + if err != nil { + return nil, err + } + // Decode G2 point + p2, err := g2.DecodePoint(input[t1:t2]) + if err != nil { + return nil, err + } + + // 'point is on curve' check already done, + // Here we need to apply subgroup checks. + if !g1.InCorrectSubgroup(p1) { + return nil, errBLS12381G1PointSubgroup + } + if !g2.InCorrectSubgroup(p2) { + return nil, errBLS12381G2PointSubgroup + } + + // Update pairing engine with G1 and G2 points + e.AddPair(p1, p2) + } + // Prepare 32 byte output + out := make([]byte, 32) + + // Compute pairing and set the result + if e.Check() { + out[31] = 1 + } + return out, nil +} + +// decodeBLS12381FieldElement decodes BLS12-381 elliptic curve field element. +// Removes top 16 bytes of 64 byte input. +func decodeBLS12381FieldElement(in []byte) ([]byte, error) { + if len(in) != 64 { + return nil, errors.New("invalid field element length") + } + // check top bytes + for i := 0; i < 16; i++ { + if in[i] != byte(0x00) { + return nil, errBLS12381InvalidFieldElementTopBytes + } + } + out := make([]byte, 48) + copy(out[:], in[16:]) + return out, nil +} + +// bls12381MapG1 implements EIP-2537 MapG1 precompile. +type bls12381MapG1 struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381MapG1) RequiredGas(input []byte) uint64 { + return params.Bls12381MapG1Gas +} + +func (c *bls12381MapG1) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 Map_To_G1 precompile. + // > Field-to-curve call expects `64` bytes an an input that is interpreted as a an element of the base field. + // > Output of this call is `128` bytes and is G1 point following respective encoding rules. + if len(input) != 64 { + return nil, errBLS12381InvalidInputLength + } + + // Decode input field element + fe, err := decodeBLS12381FieldElement(input) + if err != nil { + return nil, err + } + + // Initialize G1 + g := bls12381.NewG1() + + // Compute mapping + r, err := g.MapToCurve(fe) + if err != nil { + return nil, err + } + + // Encode the G1 point to 128 bytes + return g.EncodePoint(r), nil +} + +// bls12381MapG2 implements EIP-2537 MapG2 precompile. +type bls12381MapG2 struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381MapG2) RequiredGas(input []byte) uint64 { + return params.Bls12381MapG2Gas +} + +func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { + // Implements EIP-2537 Map_FP2_TO_G2 precompile logic. + // > Field-to-curve call expects `128` bytes an an input that is interpreted as a an element of the quadratic extension field. + // > Output of this call is `256` bytes and is G2 point following respective encoding rules. + if len(input) != 128 { + return nil, errBLS12381InvalidInputLength + } + + // Decode input field element + fe := make([]byte, 96) + c0, err := decodeBLS12381FieldElement(input[:64]) + if err != nil { + return nil, err + } + copy(fe[48:], c0) + c1, err := decodeBLS12381FieldElement(input[64:]) + if err != nil { + return nil, err + } + copy(fe[:48], c1) + + // Initialize G2 + g := bls12381.NewG2() + + // Compute mapping + r, err := g.MapToCurve(fe) + if err != nil { + return nil, err + } + + // Encode the G2 point to 256 bytes + return g.EncodePoint(r), nil +} diff --git a/state/runtime/fakevm/eips.go b/state/runtime/fakevm/eips.go new file mode 100644 index 0000000000..5c4674e889 --- /dev/null +++ b/state/runtime/fakevm/eips.go @@ -0,0 +1,243 @@ +// Copyright 2019 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 fakevm + +import ( + "fmt" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +var activators = map[int]func(*JumpTable){ + 3855: enable3855, + 3860: enable3860, + 3529: enable3529, + 3198: enable3198, + 2929: enable2929, + 2200: enable2200, + 1884: enable1884, + 1344: enable1344, + 1153: enable1153, +} + +// 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 { + 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 +// - Increase cost of SLOAD to 800 +// - 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 + + // New opcode + jt[SELFBALANCE] = &operation{ + execute: opSelfBalance, + constantGas: GasFastStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + balance, _ := uint256.FromBig(interpreter.evm.StateDB.GetBalance(scope.Contract.Address())) + scope.Stack.Push(balance) + return nil, nil +} + +// enable1344 applies EIP-1344 (ChainID Opcode) +// - Adds an opcode that returns the current chain’s EIP-155 unique identifier +func enable1344(jt *JumpTable) { + // New opcode + jt[CHAINID] = &operation{ + execute: opChainID, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +// opChainID implements CHAINID opcode +func opChainID(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID) + scope.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 +} + +// 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 + + jt[SLOAD].constantGas = 0 + jt[SLOAD].dynamicGas = gasSLoadEIP2929 + + jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929 + + jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck + + jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck + + jt[BALANCE].constantGas = params.WarmStorageReadCostEIP2929 + jt[BALANCE].dynamicGas = gasEip2929AccountCheck + + jt[CALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[CALL].dynamicGas = gasCallEIP2929 + + jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929 + jt[CALLCODE].dynamicGas = gasCallCodeEIP2929 + + jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[STATICCALL].dynamicGas = gasStaticCallEIP2929 + + jt[DELEGATECALL].constantGas = params.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 +} + +// 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 +} + +// 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), + } +} + +// 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), + } + + jt[TSTORE] = &operation{ + execute: opTstore, + constantGas: params.WarmStorageReadCostEIP2929, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + } +} + +// opTload implements TLOAD opcode +func opTload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + loc := scope.Stack.peek() + hash := common.Hash(loc.Bytes32()) + val := interpreter.evm.StateDB.GetTransientState(scope.Contract.Address(), hash) + loc.SetBytes(val.Bytes()) + return nil, nil +} + +// opTstore implements TSTORE opcode +func opTstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + loc := scope.Stack.pop() + val := scope.Stack.pop() + interpreter.evm.StateDB.SetTransientState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) + return nil, nil +} + +// 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 +} + +// enable3855 applies EIP-3855 (PUSH0 opcode) +func enable3855(jt *JumpTable) { + // New opcode + jt[PUSH0] = &operation{ + execute: opPush0, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +// opPush0 implements the PUSH0 opcode +func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int)) + return nil, nil +} + +// ebnable3860 enables "EIP-3860: Limit and meter initcode" +// https://eips.ethereum.org/EIPS/eip-3860 +func enable3860(jt *JumpTable) { + jt[CREATE].dynamicGas = gasCreateEip3860 + jt[CREATE2].dynamicGas = gasCreate2Eip3860 +} diff --git a/state/runtime/fakevm/errors.go b/state/runtime/fakevm/errors.go new file mode 100644 index 0000000000..ef52b4e0a1 --- /dev/null +++ b/state/runtime/fakevm/errors.go @@ -0,0 +1,73 @@ +// Copyright 2014 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 fakevm + +import ( + "errors" + "fmt" +) + +// List evm execution errors +var ( + ErrOutOfGas = errors.New("out of gas") + ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") + ErrDepth = errors.New("max call depth exceeded") + ErrInsufficientBalance = errors.New("insufficient balance for transfer") + ErrContractAddressCollision = errors.New("contract address collision") + ErrExecutionReverted = errors.New("execution reverted") + ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded") + 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") + ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") + ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + + // errStopToken is an internal token indicating interpreter loop termination, + // never returned to outside callers. + errStopToken = errors.New("stop token") +) + +// 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/state/runtime/fakevm/fakedb.go b/state/runtime/fakevm/fakedb.go index f6d521ee33..47d87815e8 100644 --- a/state/runtime/fakevm/fakedb.go +++ b/state/runtime/fakevm/fakedb.go @@ -4,15 +4,74 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" ) // FakeDB is the interface state access for the FakeEVM type FakeDB interface { SetStateRoot(stateRoot []byte) - GetBalance(address common.Address) *big.Int - GetNonce(address common.Address) uint64 - GetCode(address common.Address) []byte - GetState(address common.Address, hash common.Hash) common.Hash - Exist(address common.Address) bool - GetCodeHash(address common.Address) common.Hash + CreateAccount(common.Address) + + SubBalance(common.Address, *big.Int) + AddBalance(common.Address, *big.Int) + GetBalance(common.Address) *big.Int + + GetNonce(common.Address) uint64 + SetNonce(common.Address, uint64) + + GetCodeHash(common.Address) common.Hash + GetCode(common.Address) []byte + SetCode(common.Address, []byte) + GetCodeSize(common.Address) int + + AddRefund(uint64) + SubRefund(uint64) + GetRefund() uint64 + + GetCommittedState(common.Address, common.Hash) common.Hash + 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) + + Suicide(common.Address) bool + HasSuicided(common.Address) bool + + // Exist reports whether the given account exists in state. + // Notably this should also return true for suicided accounts. + Exist(common.Address) bool + // Empty returns whether the given account is empty. Empty + // 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(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) + + RevertToSnapshot(int) + Snapshot() int + + AddLog(*types.Log) + AddPreimage(common.Hash, []byte) +} + +// CallContext provides a basic interface for the EVM calling conventions. The EVM +// depends on this context being implemented for doing subcalls and initialising new EVM contracts. +type CallContext interface { + // Call calls another contract. + Call(env *FakeEVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) + // CallCode takes another contracts code and execute within our own context + CallCode(env *FakeEVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) + // DelegateCall is same as CallCode except sender and value is propagated from parent to child scope + DelegateCall(env *FakeEVM, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) + // Create creates a new contract + Create(env *FakeEVM, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) } diff --git a/state/runtime/fakevm/fakevm.go b/state/runtime/fakevm/fakevm.go index 9f1c3ee8d4..5cc0ab3049 100644 --- a/state/runtime/fakevm/fakevm.go +++ b/state/runtime/fakevm/fakevm.go @@ -1,22 +1,108 @@ +// Copyright 2014 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 fakevm import ( + "math/big" "sync/atomic" - "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +// emptyCodeHash is used by create to ensure deployment is disallowed to already +// deployed contract addresses (relevant after the account abstraction). +var emptyCodeHash = crypto.Keccak256Hash(nil) + +type ( + // CanTransferFunc is the signature of a transfer guard function + CanTransferFunc func(FakeDB, common.Address, *big.Int) bool + // TransferFunc is the signature of a transfer function + TransferFunc func(FakeDB, common.Address, common.Address, *big.Int) + // GetHashFunc returns the n'th block hash in the blockchain + // and is used by the BLOCKHASH EVM op code. + GetHashFunc func(uint64) common.Hash ) -// MemoryItemSize is the memory item size. -const MemoryItemSize int = 32 +func (evm *FakeEVM) precompile(addr common.Address) (PrecompiledContract, bool) { + var precompiles map[common.Address]PrecompiledContract + switch { + case evm.chainRules.IsBerlin: + precompiles = PrecompiledContractsBerlin + case evm.chainRules.IsIstanbul: + precompiles = PrecompiledContractsIstanbul + case evm.chainRules.IsByzantium: + precompiles = PrecompiledContractsByzantium + default: + precompiles = PrecompiledContractsHomestead + } + p, ok := precompiles[addr] + return p, ok +} + +// 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 + + // Block information + Coinbase common.Address // Provides information for COINBASE + GasLimit uint64 // Provides information for GASLIMIT + BlockNumber *big.Int // Provides information for NUMBER + Time uint64 // Provides information for TIME + Difficulty *big.Int // Provides information for DIFFICULTY + BaseFee *big.Int // Provides information for BASEFEE + Random *common.Hash // Provides information for PREVRANDAO +} + +// 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 +} -// FakeEVM represents the fake EVM. +// FakeEVM 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 +// generated through any of the calls should be considered a +// revert-state-and-consume-all-gas operation, no checks on +// specific errors should ever be performed. The interpreter makes +// sure that any errors generated are to be considered faulty code. +// +// The FakeEVM should never be reused and is not thread safe. type FakeEVM struct { // Context provides auxiliary blockchain related information - Context vm.BlockContext - vm.TxContext + Context BlockContext + TxContext // StateDB gives access to the underlying state StateDB FakeDB + // Depth is the current call stack + depth int + // chainConfig contains information about the current chain chainConfig *params.ChainConfig // chain rules contains the chain rules for the current epoch @@ -24,28 +110,38 @@ type FakeEVM struct { // virtual machine configuration options used to initialise the // evm. Config Config + // global (to this context) ethereum virtual machine + // used throughout the execution of the tx. + interpreter *EVMInterpreter // abort is used to abort the EVM calling operations // NOTE: must be set atomically abort int32 + // callGasTemp holds the gas available for the current call. This is needed because the + // available gas is calculated in gasCall* according to the 63/64 rule and later + // applied in opCall*. + callGasTemp uint64 } // NewFakeEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. -// func NewFakeEVM(blockCtx vm.BlockContext, txCtx vm.TxContext, statedb runtime.FakeDB, chainConfig *params.ChainConfig, config Config) *FakeEVM { -func NewFakeEVM(blockCtx vm.BlockContext, txCtx vm.TxContext, chainConfig *params.ChainConfig, config Config) *FakeEVM { +func NewFakeEVM(blockCtx BlockContext, txCtx TxContext, statedb FakeDB, chainConfig *params.ChainConfig, config Config) *FakeEVM { evm := &FakeEVM{ Context: blockCtx, TxContext: txCtx, + StateDB: statedb, Config: config, chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } + evm.interpreter = NewEVMInterpreter(evm) return evm } -// SetStateDB is the StateDB setter. -func (evm *FakeEVM) SetStateDB(stateDB FakeDB) { - evm.StateDB = stateDB +// Reset resets the EVM with a new transaction context.Reset +// This is not threadsafe and should only be done very cautiously. +func (evm *FakeEVM) Reset(txCtx TxContext, statedb FakeDB) { + evm.TxContext = txCtx + evm.StateDB = statedb } // Cancel cancels any running EVM operation. This may be called concurrently and @@ -54,13 +150,375 @@ func (evm *FakeEVM) Cancel() { atomic.StoreInt32(&evm.abort, 1) } -// ChainConfig returns the environment's chain configuration -func (evm *FakeEVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } +// Cancelled returns true if Cancel has been called +func (evm *FakeEVM) Cancelled() bool { + return atomic.LoadInt32(&evm.abort) == 1 +} + +// Interpreter returns the current interpreter +func (evm *FakeEVM) Interpreter() *EVMInterpreter { + return evm.interpreter +} + +// SetBlockContext updates the block context of the EVM. +func (evm *FakeEVM) SetBlockContext(blockCtx BlockContext) { + evm.Context = blockCtx + num := blockCtx.BlockNumber + timestamp := blockCtx.Time + evm.chainRules = evm.chainConfig.Rules(num, blockCtx.Random != nil, timestamp) +} + +// 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 +// execution error or failed value transfer. +func (evm *FakeEVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + // Fail if we're trying to transfer more than the available balance + if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + return nil, gas, ErrInsufficientBalance + } + snapshot := evm.StateDB.Snapshot() + p, isPrecompile := evm.precompile(addr) + + if !evm.StateDB.Exist(addr) { + if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { + // Calling a non existing account, don't do anything, but ping the tracer + if evm.Config.Debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) + evm.Config.Tracer.CaptureEnd(ret, 0, nil) + } else { + evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) + evm.Config.Tracer.CaptureExit(ret, 0, nil) + } + } + return nil, gas, nil + } + evm.StateDB.CreateAccount(addr) + } + evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) + + // Capture the tracer start/end events in debug mode + if evm.Config.Debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) + defer func(startGas uint64) { // Lazy evaluation of the parameters + evm.Config.Tracer.CaptureEnd(ret, startGas-gas, err) + }(gas) + } else { + // Handle tracer events for entering and exiting a call frame + evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) + defer func(startGas uint64) { + evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) + }(gas) + } + } + + if isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + // 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. + code := evm.StateDB.GetCode(addr) + if len(code) == 0 { + ret, err = nil, nil // gas is unchanged + } else { + addrCopy := addr + // If the account has no code, we can abort here + // The depth-check is already done, and precompiles handled above + contract := NewContract(caller, AccountRef(addrCopy), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + ret, err = evm.interpreter.Run(contract, input, false) + gas = contract.Gas + } + } + // 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 err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + gas = 0 + } + // TODO: consider clearing up unused snapshots: + //} else { + // evm.StateDB.DiscardSnapshot(snapshot) + } + return ret, gas, err +} + +// CallCode 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 +// execution error or failed value transfer. +// +// CallCode differs from Call in the sense that it executes the given address' +// code with the caller as context. +func (evm *FakeEVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + // Fail if we're trying to transfer more than the available balance + // Note although it's noop to transfer X ether to caller itself. But + // if caller doesn't have enough balance, it would be an error to allow + // over-charging itself. So the check here is necessary. + if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + return nil, gas, ErrInsufficientBalance + } + var snapshot = evm.StateDB.Snapshot() + + // Invoke tracer hooks that signal entering/exiting a call frame + if evm.Config.Debug { + evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value) + defer func(startGas uint64) { + evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) + }(gas) + } + + // It is allowed to call precompiles, even via delegatecall + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + addrCopy := addr + // 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(caller.Address()), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + ret, err = evm.interpreter.Run(contract, input, false) + gas = contract.Gas + } + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + gas = 0 + } + } + return ret, gas, err +} + +// DelegateCall executes the contract associated with the addr with the given input +// as parameters. It reverses the state in case of an execution error. +// +// DelegateCall differs from CallCode in the sense that it executes the given address' +// code with the caller as context and the caller is set to the caller of the caller. +func (evm *FakeEVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + var snapshot = evm.StateDB.Snapshot() + + // Invoke tracer hooks that signal entering/exiting a call frame + if evm.Config.Debug { + // NOTE: caller must, at all times be a contract. It should never happen + // that caller is something other than a Contract. + parent := caller.(*Contract) + // DELEGATECALL inherits value from parent call + evm.Config.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, parent.value) + defer func(startGas uint64) { + evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) + }(gas) + } + + // It is allowed to call precompiles, even via delegatecall + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + addrCopy := addr + // Initialise a new contract and make initialise the delegate values + contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + ret, err = evm.interpreter.Run(contract, input, false) + gas = contract.Gas + } + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + gas = 0 + } + } + return ret, gas, err +} + +// StaticCall executes the contract associated with the addr with the given input +// as parameters while disallowing any modifications to the state during the call. +// Opcodes that attempt to perform such modifications will result in exceptions +// instead of performing the modifications. +func (evm *FakeEVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. + // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced + // after all empty accounts were deleted, so this is not required. However, if we omit this, + // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json. + // We could change this, but for now it's left for legacy reasons + var snapshot = evm.StateDB.Snapshot() + + // We do an AddBalance of zero here, just in order to trigger a touch. + // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, + // but is the correct thing to do and matters on other networks, in tests, and potential + // future scenarios + evm.StateDB.AddBalance(addr, big0) + + // Invoke tracer hooks that signal entering/exiting a call frame + if evm.Config.Debug { + evm.Config.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil) + defer func(startGas uint64) { + evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) + }(gas) + } + + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(p, input, gas) + } else { + // At this point, we use a copy of address. If we don't, the go compiler will + // leak the 'contract' to the outer scope, and make allocation for 'contract' + // even if the actual execution ends on RunPrecompiled above. + addrCopy := addr + // 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(addrCopy), new(big.Int), gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + // 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. + ret, err = evm.interpreter.Run(contract, input, true) + gas = contract.Gas + } + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + gas = 0 + } + } + return ret, gas, err +} + +type codeAndHash struct { + code []byte + hash common.Hash +} + +func (c *codeAndHash) Hash() common.Hash { + if c.hash == (common.Hash{}) { + c.hash = crypto.Keccak256Hash(c.code) + } + return c.hash +} + +// create creates a new contract using code as deployment code. +func (evm *FakeEVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) { + // Depth check execution. Fail if we're trying to execute above the + // limit. + if evm.depth > int(params.CallCreateDepth) { + return nil, common.Address{}, gas, ErrDepth + } + if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + return nil, common.Address{}, gas, ErrInsufficientBalance + } + nonce := evm.StateDB.GetNonce(caller.Address()) + if nonce+1 < nonce { + return nil, common.Address{}, gas, ErrNonceUintOverflow + } + evm.StateDB.SetNonce(caller.Address(), nonce+1) + // We add this to the access list _before_ taking a snapshot. Even if the creation fails, + // the access-list change should not be rolled back + if evm.chainRules.IsBerlin { + evm.StateDB.AddAddressToAccessList(address) + } + // Ensure there's no existing contract already at the designated address + contractHash := evm.StateDB.GetCodeHash(address) + if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { + return nil, common.Address{}, 0, ErrContractAddressCollision + } + // Create a new account on the state + snapshot := evm.StateDB.Snapshot() + evm.StateDB.CreateAccount(address) + if evm.chainRules.IsEIP158 { + evm.StateDB.SetNonce(address, 1) + } + evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) + + // 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) + + if evm.Config.Debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) + } else { + evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value) + } + } + + ret, err := evm.interpreter.Run(contract, nil, false) + + // Check whether the max code size has been exceeded, assign err if the case. + if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { + err = ErrMaxCodeSizeExceeded + } + + // Reject code starting with 0xEF if EIP-3541 is enabled. + if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon { + err = ErrInvalidCode + } + + // 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 + // by the error checking condition below. + if err == nil { + createDataGas := uint64(len(ret)) * params.CreateDataGas + if contract.UseGas(createDataGas) { + evm.StateDB.SetCode(address, ret) + } else { + err = ErrCodeStoreOutOfGas + } + } + + // 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 err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + contract.UseGas(contract.Gas) + } + } + + if evm.Config.Debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, err) + } else { + evm.Config.Tracer.CaptureExit(ret, gas-contract.Gas, err) + } + } + return ret, address, contract.Gas, err +} -// ScopeContext contains the things that are per-call, such as stack and memory, -// but not transients like pc and gas -type ScopeContext struct { - Memory *Memory - Stack *Stack - Contract *vm.Contract +// Create creates a new contract using code as deployment code. +func (evm *FakeEVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { + contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address())) + return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE) } + +// Create2 creates a new contract using code as deployment code. +// +// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] +// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. +func (evm *FakeEVM) 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(), salt.Bytes32(), codeAndHash.Hash().Bytes()) + return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2) +} + +// ChainConfig returns the environment's chain configuration +func (evm *FakeEVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } diff --git a/state/runtime/fakevm/gas.go b/state/runtime/fakevm/gas.go new file mode 100644 index 0000000000..4ccf3da204 --- /dev/null +++ b/state/runtime/fakevm/gas.go @@ -0,0 +1,53 @@ +// Copyright 2015 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 fakevm + +import ( + "github.com/holiman/uint256" +) + +// Gas costs +const ( + GasQuickStep uint64 = 2 + GasFastestStep uint64 = 3 + GasFastStep uint64 = 5 + GasMidStep uint64 = 8 + GasSlowStep uint64 = 10 + GasExtStep uint64 = 20 +) + +// 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 *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. 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 callCost.Uint64(), nil +} diff --git a/state/runtime/fakevm/gas_table.go b/state/runtime/fakevm/gas_table.go new file mode 100644 index 0000000000..31e4859dc2 --- /dev/null +++ b/state/runtime/fakevm/gas_table.go @@ -0,0 +1,477 @@ +// 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 fakevm + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" +) + +// memoryGasCost calculates the quadratic gas for memory expansion. It does so +// only for the memory region that is expanded, not the total memory. +func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { + if newMemSize == 0 { + return 0, nil + } + // The maximum that will fit in a uint64 is max_word_count - 1. Anything above + // that will result in an overflow. Additionally, a newMemSize which results in + // a newMemSizeWords larger than 0xFFFFFFFF will cause the square operation to + // overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used + // without overflowing the gas calculation. + if newMemSize > 0x1FFFFFFFE0 { + return 0, ErrGasUintOverflow + } + newMemSizeWords := toWordSize(newMemSize) + newMemSize = newMemSizeWords * 32 + + if newMemSize > uint64(mem.Len()) { + square := newMemSizeWords * newMemSizeWords + linCoef := newMemSizeWords * params.MemoryGas + quadCoef := square / params.QuadCoeffDiv + newTotalFee := linCoef + quadCoef + + fee := newTotalFee - mem.lastGasCost + mem.lastGasCost = newTotalFee + + return fee, nil + } + return 0, nil +} + +// memoryCopierGas creates the gas functions for the following opcodes, and takes +// the stack position of the operand which determines the size of the data to copy +// as argument: +// CALLDATACOPY (stack position 2) +// CODECOPY (stack position 2) +// EXTCODECOPY (stack position 3) +// RETURNDATACOPY (stack position 2) +func memoryCopierGas(stackpos int) gasFunc { + return func(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // Gas for expanding the memory + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + // And gas for copying data, charged per word at param.CopyGas + words, overflow := stack.Back(stackpos).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + + if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow { + return 0, ErrGasUintOverflow + } + + if gas, overflow = math.SafeAdd(gas, words); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } +} + +var ( + gasCallDataCopy = memoryCopierGas(2) + gasCodeCopy = memoryCopierGas(2) + gasExtCodeCopy = memoryCopierGas(3) + gasReturnDataCopy = memoryCopierGas(2) +) + +func gasSStore(evm *FakeEVM, 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(), 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) + // OR Constantinople is not active + if evm.chainRules.IsPetersburg || !evm.chainRules.IsConstantinople { + // This checks for 3 scenario's and calculates gas accordingly: + // + // 1. From a zero-value address to a non-zero value (NEW VALUE) + // 2. From a non-zero value address to a zero-value address (DELETE) + // 3. From a non-zero to a non-zero (CHANGE) + switch { + case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0 + return params.SstoreSetGas, nil + case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0 + evm.StateDB.AddRefund(params.SstoreRefundGas) + return params.SstoreClearGas, nil + default: // non 0 => non 0 (or 0 => 0) + return params.SstoreResetGas, nil + } + } + + // The new gas metering is based on net gas costs (EIP-1283): + // + // (1.) If current value equals new value (this is a no-op), 200 gas is deducted. + // (2.) If current value does not equal new value + // (2.1.) If original value equals current value (this storage slot has not been changed by the current execution context) + // (2.1.1.) If original value is 0, 20000 gas is deducted. + // (2.1.2.) Otherwise, 5000 gas is deducted. If new value is 0, add 15000 gas to refund counter. + // (2.2.) If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses. + // (2.2.1.) If original value is not 0 + // (2.2.1.1.) If current value is 0 (also means that new value is not 0), remove 15000 gas from refund counter. We can prove that refund counter will never go below 0. + // (2.2.1.2.) If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter. + // (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.Hash(y.Bytes32()) + if current == value { // noop (1) + return params.NetSstoreNoopGas, nil + } + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return params.NetSstoreInitGas, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(params.NetSstoreClearRefund) + } + return params.NetSstoreCleanGas, 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.NetSstoreClearRefund) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(params.NetSstoreClearRefund) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + evm.StateDB.AddRefund(params.NetSstoreResetClearRefund) + } else { // reset to original existing slot (2.2.2.2) + evm.StateDB.AddRefund(params.NetSstoreResetRefund) + } + } + return params.NetSstoreDirtyGas, nil +} + +// Here come the EIP2200 rules: +// +// (0.) If *gasleft* is less than or equal to 2300, fail the current call. +// (1.) If current value equals new value (this is a no-op), SLOAD_GAS is deducted. +// (2.) If current value does not equal new value: +// (2.1.) If original value equals current value (this storage slot has not been changed by the current execution context): +// (2.1.1.) If original value is 0, SSTORE_SET_GAS (20K) gas is deducted. +// (2.1.2.) Otherwise, SSTORE_RESET_GAS gas is deducted. If new value is 0, add SSTORE_CLEARS_SCHEDULE to refund counter. +// (2.2.) If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses: +// (2.2.1.) If original value is not 0: +// (2.2.1.1.) If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEARS_SCHEDULE gas from refund counter. +// (2.2.1.2.) If new value is 0 (also means that current value is not 0), add SSTORE_CLEARS_SCHEDULE gas to refund counter. +// (2.2.2.) If original value equals new value (this storage slot is reset): +// (2.2.2.1.) If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter. +// (2.2.2.2.) Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. +func gasSStoreEIP2200(evm *FakeEVM, 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.Back(0) + current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) + ) + value := common.Hash(y.Bytes32()) + + if current == value { // noop (1) + return params.SloadGasEIP2200, nil + } + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return params.SstoreSetGasEIP2200, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) + } + 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.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) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) + } else { // reset to original existing slot (2.2.2.2) + evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) + } + } + return params.SloadGasEIP2200, nil // dirty update (2.2) +} + +func makeGasLog(n uint64) gasFunc { + return func(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + requestedSize, overflow := stack.Back(1).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + + if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { + return 0, ErrGasUintOverflow + } + if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { + return 0, ErrGasUintOverflow + } + + var memorySizeGas uint64 + if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { + return 0, ErrGasUintOverflow + } + if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } +} + +func gasKeccak256(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + wordGas, overflow := stack.Back(1).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { + return 0, ErrGasUintOverflow + } + if gas, overflow = math.SafeAdd(gas, wordGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +// pureMemoryGascost is used by several operations, which aside from their +// static cost have a dynamic cost which is solely based on the memory +// expansion +func pureMemoryGascost(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return memoryGasCost(mem, memorySize) +} + +var ( + gasReturn = pureMemoryGascost + gasRevert = pureMemoryGascost + gasMLoad = pureMemoryGascost + gasMStore8 = pureMemoryGascost + gasMStore = pureMemoryGascost + gasCreate = pureMemoryGascost +) + +func gasCreate2(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + wordGas, overflow := stack.Back(2).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { + return 0, ErrGasUintOverflow + } + if gas, overflow = math.SafeAdd(gas, wordGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasCreateEip3860(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + size, overflow := stack.Back(2).Uint64WithOverflow() + if overflow || size > params.MaxInitCodeSize { + return 0, ErrGasUintOverflow + } + // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow + moreGas := params.InitCodeWordGas * ((size + 31) / 32) + if gas, overflow = math.SafeAdd(gas, moreGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} +func gasCreate2Eip3860(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + size, overflow := stack.Back(2).Uint64WithOverflow() + if overflow || size > params.MaxInitCodeSize { + return 0, ErrGasUintOverflow + } + // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow + moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32) + if gas, overflow = math.SafeAdd(gas, moreGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasExpFrontier(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) + + var ( + gas = expByteLen * params.ExpByteFrontier // no overflow check required. Max is 256 * ExpByte gas + overflow bool + ) + if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasExpEIP158(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) + + var ( + gas = expByteLen * params.ExpByteEIP158 // no overflow check required. Max is 256 * ExpByte gas + overflow bool + ) + if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasCall(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + transfersValue = !stack.Back(2).IsZero() + address = common.Address(stack.Back(1).Bytes20()) + ) + if evm.chainRules.IsEIP158 { + if transfersValue && evm.StateDB.Empty(address) { + gas += params.CallNewAccountGas + } + } else if !evm.StateDB.Exist(address) { + gas += params.CallNewAccountGas + } + if transfersValue { + gas += params.CallValueTransferGas + } + memoryGas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + return 0, ErrGasUintOverflow + } + + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasCallCode(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + memoryGas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + var ( + gas uint64 + overflow bool + ) + if stack.Back(2).Sign() != 0 { + gas += params.CallValueTransferGas + } + if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + return 0, ErrGasUintOverflow + } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasDelegateCall(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasStaticCall(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasSelfdestruct(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var gas uint64 + // EIP150 homestead gas reprice fork: + if evm.chainRules.IsEIP150 { + gas = params.SelfdestructGasEIP150 + var address = common.Address(stack.Back(0).Bytes20()) + + if evm.chainRules.IsEIP158 { + // if empty and transfers value + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + gas += params.CreateBySelfdestructGas + } + } else if !evm.StateDB.Exist(address) { + gas += params.CreateBySelfdestructGas + } + } + + if !evm.StateDB.HasSuicided(contract.Address()) { + evm.StateDB.AddRefund(params.SelfdestructRefundGas) + } + return gas, nil +} diff --git a/state/runtime/fakevm/instructions.go b/state/runtime/fakevm/instructions.go new file mode 100644 index 0000000000..f87fd9d6b5 --- /dev/null +++ b/state/runtime/fakevm/instructions.go @@ -0,0 +1,919 @@ +// Copyright 2015 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 fakevm + +import ( + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Add(&x, y) + return nil, nil +} + +func opSub(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Sub(&x, y) + return nil, nil +} + +func opMul(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Mul(&x, y) + return nil, nil +} + +func opDiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Div(&x, y) + return nil, nil +} + +func opSdiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.SDiv(&x, y) + return nil, nil +} + +func opMod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Mod(&x, y) + return nil, nil +} + +func opSmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.SMod(&x, y) + return nil, nil +} + +func opExp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + base, exponent := scope.Stack.pop(), scope.Stack.peek() + exponent.Exp(&base, exponent) + return nil, nil +} + +func opSignExtend(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + back, num := scope.Stack.pop(), scope.Stack.peek() + num.ExtendSign(num, &back) + return nil, nil +} + +func opNot(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() + x.Not(x) + return nil, nil +} + +func opLt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + if x.Lt(y) { + y.SetOne() + } else { + y.Clear() + } + return nil, nil +} + +func opGt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + if x.Gt(y) { + y.SetOne() + } else { + y.Clear() + } + return nil, nil +} + +func opSlt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + if x.Slt(y) { + y.SetOne() + } else { + y.Clear() + } + return nil, nil +} + +func opSgt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + if x.Sgt(y) { + y.SetOne() + } else { + y.Clear() + } + return nil, nil +} + +func opEq(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + if x.Eq(y) { + y.SetOne() + } else { + y.Clear() + } + return nil, nil +} + +func opIszero(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() + if x.IsZero() { + x.SetOne() + } else { + x.Clear() + } + return nil, nil +} + +func opAnd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.And(&x, y) + return nil, nil +} + +func opOr(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Or(&x, y) + return nil, nil +} + +func opXor(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Xor(&x, y) + return nil, nil +} + +func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + th, val := scope.Stack.pop(), scope.Stack.peek() + val.Byte(&th) + return nil, nil +} + +func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek() + if z.IsZero() { + z.Clear() + } else { + z.AddMod(&x, &y, z) + } + return nil, nil +} + +func opMulmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.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, scope *ScopeContext) ([]byte, error) { + // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards + shift, value := scope.Stack.pop(), scope.Stack.peek() + if shift.LtUint64(256) { + value.Lsh(value, uint(shift.Uint64())) + } else { + value.Clear() + } + 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, scope *ScopeContext) ([]byte, error) { + // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards + shift, value := scope.Stack.pop(), scope.Stack.peek() + if shift.LtUint64(256) { + value.Rsh(value, uint(shift.Uint64())) + } else { + value.Clear() + } + 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, scope *ScopeContext) ([]byte, error) { + shift, value := scope.Stack.pop(), scope.Stack.peek() + if shift.GtUint64(256) { + if value.Sign() >= 0 { + value.Clear() + } else { + // Max negative shift: all bits set + value.SetAllOne() + } + return nil, nil + } + n := uint(shift.Uint64()) + value.SRsh(value, n) + return nil, nil +} + +func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + offset, size := scope.Stack.pop(), scope.Stack.peek() + data := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) + + if interpreter.hasher == nil { + interpreter.hasher = crypto.NewKeccakState() + } else { + interpreter.hasher.Reset() + } + interpreter.hasher.Write(data) + interpreter.hasher.Read(interpreter.hasherBuf[:]) + + evm := interpreter.evm + if evm.Config.EnablePreimageRecording { + evm.StateDB.AddPreimage(interpreter.hasherBuf, data) + } + + size.SetBytes(interpreter.hasherBuf[:]) + return nil, nil +} +func opAddress(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetBytes(scope.Contract.Address().Bytes())) + return nil, nil +} + +func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.peek() + address := common.Address(slot.Bytes20()) + slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) + return nil, nil +} + +func opOrigin(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetBytes(interpreter.evm.Origin.Bytes())) + return nil, nil +} +func opCaller(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetBytes(scope.Contract.Caller().Bytes())) + return nil, nil +} + +func opCallValue(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v, _ := uint256.FromBig(scope.Contract.value) + scope.Stack.Push(v) + return nil, nil +} + +func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() + if offset, overflow := x.Uint64WithOverflow(); !overflow { + data := getData(scope.Contract.Input, offset, 32) + x.SetBytes(data) + } else { + x.Clear() + } + return nil, nil +} + +func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Input)))) + return nil, nil +} + +func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + memOffset = scope.Stack.pop() + dataOffset = scope.Stack.pop() + length = scope.Stack.pop() + ) + dataOffset64, overflow := dataOffset.Uint64WithOverflow() + if overflow { + dataOffset64 = 0xffffffffffffffff + } + // These values are checked for overflow during gas cost calculation + memOffset64 := memOffset.Uint64() + length64 := length.Uint64() + scope.Memory.Set(memOffset64, length64, getData(scope.Contract.Input, dataOffset64, length64)) + + return nil, nil +} + +func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetUint64(uint64(len(interpreter.returnData)))) + return nil, nil +} + +func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + memOffset = scope.Stack.pop() + dataOffset = scope.Stack.pop() + length = scope.Stack.pop() + ) + + offset64, overflow := dataOffset.Uint64WithOverflow() + if overflow { + return nil, ErrReturnDataOutOfBounds + } + // 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 + } + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64]) + return nil, nil +} + +func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.peek() + slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) + return nil, nil +} + +func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + l := new(uint256.Int) + l.SetUint64(uint64(len(scope.Contract.Code))) + scope.Stack.Push(l) + return nil, nil +} + +func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + memOffset = scope.Stack.pop() + codeOffset = scope.Stack.pop() + length = scope.Stack.pop() + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + + return nil, nil +} + +func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + stack = scope.Stack + a = stack.pop() + memOffset = stack.pop() + codeOffset = stack.pop() + length = stack.pop() + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + addr := common.Address(a.Bytes20()) + codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + + return nil, nil +} + +// opExtCodeHash returns the code hash of a specified account. +// There are several cases when the function is called, while we can relay everything +// to `state.GetCodeHash` function to ensure the correctness. +// +// 1. Caller tries to get the code hash of a normal contract account, state +// should return the relative code hash and set it as the result. +// +// 2. Caller tries to get the code hash of a non-existent account, state should +// return common.Hash{} and zero will be set as the result. +// +// 3. Caller tries to get the code hash for an account without contract code, state +// should return emptyCodeHash(0xc5d246...) as the result. +// +// 4. Caller tries to get the code hash of a precompiled account, the result should be +// zero or emptyCodeHash. +// +// It is worth noting that in order to avoid unnecessary create and clean, all precompile +// accounts on mainnet have been transferred 1 wei, so the return here should be +// emptyCodeHash. If the precompile account is not transferred any amount on a private or +// customized chain, the return value will be zero. +// +// 5. Caller tries to get the code hash for an account which is marked as suicided +// in the current transaction, the code hash of this account should be returned. +// +// 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, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.peek() + address := common.Address(slot.Bytes20()) + if interpreter.evm.StateDB.Empty(address) { + slot.Clear() + } else { + slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) + } + return nil, nil +} + +func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.GasPrice) + scope.Stack.Push(v) + return nil, nil +} + +func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + num := scope.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 { + lower = upper - 256 + } + if num64 >= lower && num64 < upper { + num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) + } else { + num.Clear() + } + return nil, nil +} + +func opCoinbase(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetBytes(interpreter.evm.Context.Coinbase.Bytes())) + return nil, nil +} + +func opTimestamp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetUint64(interpreter.evm.Context.Time)) + return nil, nil +} + +func opNumber(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.Context.BlockNumber) + scope.Stack.Push(v) + return nil, nil +} + +func opDifficulty(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.Context.Difficulty) + scope.Stack.Push(v) + return nil, nil +} + +func opRandom(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v := new(uint256.Int).SetBytes(interpreter.evm.Context.Random.Bytes()) + scope.Stack.Push(v) + return nil, nil +} + +func opGasLimit(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit)) + return nil, nil +} + +func opPop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.pop() + return nil, nil +} + +func opMload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v := scope.Stack.peek() + offset := int64(v.Uint64()) + v.SetBytes(scope.Memory.GetPtr(offset, 32)) + return nil, nil +} + +func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + // pop value of the stack + mStart, val := scope.Stack.pop(), scope.Stack.pop() + scope.Memory.Set32(mStart.Uint64(), &val) + return nil, nil +} + +func opMstore8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + off, val := scope.Stack.pop(), scope.Stack.pop() + scope.Memory.store[off.Uint64()] = byte(val.Uint64()) + return nil, nil +} + +func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + loc := scope.Stack.peek() + hash := common.Hash(loc.Bytes32()) + val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) + loc.SetBytes(val.Bytes()) + return nil, nil +} + +func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + loc := scope.Stack.pop() + val := scope.Stack.pop() + interpreter.evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) + return nil, nil +} + +func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if atomic.LoadInt32(&interpreter.evm.abort) != 0 { + return nil, errStopToken + } + pos := scope.Stack.pop() + if !scope.Contract.validJumpdest(&pos) { + return nil, ErrInvalidJump + } + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop + return nil, nil +} + +func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if atomic.LoadInt32(&interpreter.evm.abort) != 0 { + return nil, errStopToken + } + pos, cond := scope.Stack.pop(), scope.Stack.pop() + if !cond.IsZero() { + if !scope.Contract.validJumpdest(&pos) { + return nil, ErrInvalidJump + } + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop + } + return nil, nil +} + +func opJumpdest(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, nil +} + +func opPc(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetUint64(*pc)) + return nil, nil +} + +func opMsize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetUint64(uint64(scope.Memory.Len()))) + return nil, nil +} + +func opGas(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(new(uint256.Int).SetUint64(scope.Contract.Gas)) + return nil, nil +} + +func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var ( + value = scope.Stack.pop() + offset, size = scope.Stack.pop(), scope.Stack.pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas + ) + if interpreter.evm.chainRules.IsEIP150 { + gas -= gas / 64 + } + // reuse size int for stackvalue + stackvalue := size + + scope.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(scope.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.chainRules.IsHomestead && suberr == ErrCodeStoreOutOfGas { + stackvalue.Clear() + } else if suberr != nil && suberr != ErrCodeStoreOutOfGas { + stackvalue.Clear() + } else { + stackvalue.SetBytes(addr.Bytes()) + } + scope.Stack.Push(&stackvalue) + scope.Contract.Gas += returnGas + + if suberr == ErrExecutionReverted { + interpreter.returnData = res // set REVERT data to return data buffer + return res, nil + } + interpreter.returnData = nil // clear dirty return data buffer + return nil, nil +} + +func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var ( + endowment = scope.Stack.pop() + offset, size = scope.Stack.pop(), scope.Stack.pop() + salt = scope.Stack.pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas + ) + // Apply EIP150 + gas -= gas / 64 + scope.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(scope.Contract, input, gas, + bigEndowment, &salt) + // Push item on the stack based on the returned error. + if suberr != nil { + stackvalue.Clear() + } else { + stackvalue.SetBytes(addr.Bytes()) + } + scope.Stack.Push(&stackvalue) + scope.Contract.Gas += returnGas + + if suberr == ErrExecutionReverted { + interpreter.returnData = res // set REVERT data to return data buffer + return res, nil + } + interpreter.returnData = nil // clear dirty return data buffer + return nil, nil +} + +func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + stack := scope.Stack + // Pop gas. The actual gas in interpreter.evm.callGasTemp. + // 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.Address(addr.Bytes20()) + // Get the arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + if interpreter.readOnly && !value.IsZero() { + return nil, ErrWriteProtection + } + 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(scope.Contract, toAddr, args, gas, bigVal) + + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.Push(&temp) + if err == nil || err == ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas + + interpreter.returnData = ret + return ret, nil +} + +func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + // Pop gas. The actual gas is in interpreter.evm.callGasTemp. + stack := scope.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.Address(addr.Bytes20()) + // Get arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + //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(scope.Contract, toAddr, args, gas, bigVal) + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.Push(&temp) + if err == nil || err == ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas + + interpreter.returnData = ret + return ret, nil +} + +func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + stack := scope.Stack + // Pop gas. The actual gas is in interpreter.evm.callGasTemp. + // 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.Address(addr.Bytes20()) + // Get arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas) + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.Push(&temp) + if err == nil || err == ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas + + interpreter.returnData = ret + return ret, nil +} + +func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + // Pop gas. The actual gas is in interpreter.evm.callGasTemp. + stack := scope.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.Address(addr.Bytes20()) + // Get arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas) + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.Push(&temp) + if err == nil || err == ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas + + interpreter.returnData = ret + return ret, nil +} + +func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + offset, size := scope.Stack.pop(), scope.Stack.pop() + ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) + + return ret, errStopToken +} + +func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + offset, size := scope.Stack.pop(), scope.Stack.pop() + ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) + + interpreter.returnData = ret + return ret, ErrExecutionReverted +} + +func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.Code[*pc])} +} + +func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, errStopToken +} + +func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + beneficiary := scope.Stack.pop() + balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) + interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) + interpreter.evm.StateDB.Suicide(scope.Contract.Address()) + if interpreter.evm.Config.Debug { + interpreter.evm.Config.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) + interpreter.evm.Config.Tracer.CaptureExit([]byte{}, 0, nil) + } + return nil, errStopToken +} + +// following functions are used by the instruction jump table + +// make log instruction function +func makeLog(size int) executionFunc { + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + topics := make([]common.Hash, size) + stack := scope.Stack + mStart, mSize := stack.pop(), stack.pop() + for i := 0; i < size; i++ { + addr := stack.pop() + topics[i] = addr.Bytes32() + } + + d := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) + interpreter.evm.StateDB.AddLog(&types.Log{ + Address: scope.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.Context.BlockNumber.Uint64(), + }) + + return nil, nil + } +} + +// opPush1 is a specialized version of pushN +func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = uint64(len(scope.Contract.Code)) + integer = new(uint256.Int) + ) + *pc += 1 + if *pc < codeLen { + scope.Stack.Push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + } else { + scope.Stack.Push(integer.Clear()) + } + return nil, nil +} + +// make push instruction function +func makePush(size uint64, pushByteSize int) executionFunc { + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + codeLen := len(scope.Contract.Code) + + startMin := codeLen + if int(*pc+1) < startMin { + startMin = int(*pc + 1) + } + + endMin := codeLen + if startMin+pushByteSize < endMin { + endMin = startMin + pushByteSize + } + + integer := new(uint256.Int) + scope.Stack.Push(integer.SetBytes(common.RightPadBytes( + scope.Contract.Code[startMin:endMin], pushByteSize))) + + *pc += size + return nil, nil + } +} + +// make dup instruction function +func makeDup(size int64) executionFunc { + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.dup(int(size)) + return nil, nil + } +} + +// make swap instruction function +func makeSwap(size int64) executionFunc { + // switch n + 1 otherwise n would be swapped with n + size++ + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.swap(int(size)) + return nil, nil + } +} diff --git a/state/runtime/fakevm/interpreter.go b/state/runtime/fakevm/interpreter.go index cd1984066a..a291b15411 100644 --- a/state/runtime/fakevm/interpreter.go +++ b/state/runtime/fakevm/interpreter.go @@ -1,6 +1,27 @@ +// Copyright 2014 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 fakevm -import "github.com/ethereum/go-ethereum/core/vm" +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" +) // Config are the configuration options for the Interpreter type Config struct { @@ -8,8 +29,212 @@ type Config struct { Tracer EVMLogger // Opcode logger NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages + ExtraEips []int // Additional EIPS that are to be enabled +} + +// ScopeContext contains the things that are per-call, such as stack and memory, +// but not transients like pc and gas +type ScopeContext struct { + Memory *Memory + Stack *Stack + Contract *Contract +} + +// EVMInterpreter represents an EVM interpreter +type EVMInterpreter struct { + evm *FakeEVM + table *JumpTable + + hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes + hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes + + readOnly bool // Whether to throw on stateful modifications + returnData []byte // Last CALL's return data for subsequent reuse +} + +// NewEVMInterpreter returns a new instance of the Interpreter. +func NewEVMInterpreter(evm *FakeEVM) *EVMInterpreter { + // If jump table was not initialised we set the default one. + var table *JumpTable + switch { + case evm.chainRules.IsShanghai: + table = &shanghaiInstructionSet + case evm.chainRules.IsMerge: + table = &mergeInstructionSet + case evm.chainRules.IsLondon: + table = &londonInstructionSet + case evm.chainRules.IsBerlin: + table = &berlinInstructionSet + case evm.chainRules.IsIstanbul: + table = &istanbulInstructionSet + case evm.chainRules.IsConstantinople: + table = &constantinopleInstructionSet + case evm.chainRules.IsByzantium: + table = &byzantiumInstructionSet + case evm.chainRules.IsEIP158: + table = &spuriousDragonInstructionSet + case evm.chainRules.IsEIP150: + table = &tangerineWhistleInstructionSet + case evm.chainRules.IsHomestead: + table = &homesteadInstructionSet + default: + table = &frontierInstructionSet + } + var extraEips []int + if len(evm.Config.ExtraEips) > 0 { + // Deep-copy jumptable to prevent modification of opcodes in other tables + table = copyJumpTable(table) + } + for _, eip := range evm.Config.ExtraEips { + if err := EnableEIP(eip, table); err != nil { + // Disable it, so caller can check if it's activated or not + log.Error("EIP activation failed", "eip", eip, "error", err) + } else { + extraEips = append(extraEips, eip) + } + } + evm.Config.ExtraEips = extraEips + return &EVMInterpreter{evm: evm, table: table} +} + +// Run loops and evaluates the contract's code with the given input data and returns +// the return byte-slice and an error if one occurred. +// +// 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. +func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { + // Increment the call depth which is restricted to 1024 + in.evm.depth++ + defer func() { in.evm.depth-- }() + + // Make sure the readOnly is only set if we aren't in readOnly yet. + // This also makes sure that the readOnly flag isn't removed for child calls. + if readOnly && !in.readOnly { + in.readOnly = true + defer func() { in.readOnly = false }() + } + + // Reset the previous call's return data. It's unimportant to preserve the old buffer + // as every returning call will return new data anyway. + in.returnData = nil + + // Don't bother with the execution if there's no code. + if len(contract.Code) == 0 { + return nil, nil + } + + var ( + op OpCode // current opcode + mem = NewMemory() // bound memory + stack = NewStack() // local stack + callContext = &ScopeContext{ + Memory: mem, + Stack: stack, + 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. + pc = uint64(0) // program counter + cost uint64 + // copies used by tracer + pcCopy uint64 // needed for the deferred EVMLogger + gasCopy uint64 // for EVMLogger to log gas remaining before execution + logged bool // deferred EVMLogger should ignore already logged steps + res []byte // result of the opcode execution function + ) + // Don't move this deferred 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) + }() + contract.Input = input + + if in.evm.Config.Debug { + defer func() { + if err != nil { + if !logged { + in.evm.Config.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) + } else { + in.evm.Config.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err) + } + } + }() + } + // The Interpreter main run loop (contextual). This loop runs until either an + // 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 { + if in.evm.Config.Debug { + // Capture pre-execution values for tracing. + logged, pcCopy, gasCopy = false, pc, contract.Gas + } + // Get the operation from the jump table and validate the stack to ensure there are + // enough stack items available to perform the operation. + op = contract.GetOp(pc) + operation := in.table[op] + cost = operation.constantGas // For tracing + // Validate stack + if sLen := stack.len(); sLen < operation.minStack { + return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack} + } else if sLen > operation.maxStack { + return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} + } + if !contract.UseGas(cost) { + return nil, ErrOutOfGas + } + if operation.dynamicGas != nil { + // All ops with a dynamic memory usage also has a dynamic gas cost. + var memorySize uint64 + // calculate the new memory size and expand the memory to fit + // the operation + // Memory check needs to be done prior to evaluating the dynamic gas portion, + // to detect calculation overflows + if operation.memorySize != nil { + memSize, overflow := operation.memorySize(stack) + if overflow { + 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 + } + } + // Consume the gas and return an error if not enough gas is available. + // cost is explicitly set so that the capture state defer method can get the proper cost + var dynamicCost uint64 + dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) + cost += dynamicCost // for tracing + if err != nil || !contract.UseGas(dynamicCost) { + return nil, ErrOutOfGas + } + // Do tracing before memory expansion + if in.evm.Config.Debug { + in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) + logged = true + } + if memorySize > 0 { + mem.Resize(memorySize) + } + } else if in.evm.Config.Debug { + in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) + logged = true + } + // execute the operation + res, err = operation.execute(&pc, in, callContext) + if err != nil { + break + } + pc++ + } - JumpTable *vm.JumpTable // EVM instruction table, automatically populated if unset + if err == errStopToken { + err = nil // clear stop token error + } - ExtraEips []int // Additional EIPS that are to be enabled + return res, err } diff --git a/state/runtime/fakevm/jump_table.go b/state/runtime/fakevm/jump_table.go new file mode 100644 index 0000000000..ba3aa98a89 --- /dev/null +++ b/state/runtime/fakevm/jump_table.go @@ -0,0 +1,1064 @@ +// Copyright 2015 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 fakevm + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/params" +) + +type ( + executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) + gasFunc func(*FakeEVM, *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) +) + +type operation struct { + // execute is the operation function + execute executionFunc + constantGas uint64 + dynamicGas gasFunc + // minStack tells how many stack items are required + minStack int + // maxStack specifies the max length the stack can have for this operation + // to not overflow the stack. + maxStack int + + // memorySize returns the memory size required for the operation + memorySize memorySizeFunc +} + +var ( + frontierInstructionSet = newFrontierInstructionSet() + homesteadInstructionSet = newHomesteadInstructionSet() + tangerineWhistleInstructionSet = newTangerineWhistleInstructionSet() + spuriousDragonInstructionSet = newSpuriousDragonInstructionSet() + byzantiumInstructionSet = newByzantiumInstructionSet() + constantinopleInstructionSet = newConstantinopleInstructionSet() + istanbulInstructionSet = newIstanbulInstructionSet() + berlinInstructionSet = newBerlinInstructionSet() + londonInstructionSet = newLondonInstructionSet() + mergeInstructionSet = newMergeInstructionSet() + shanghaiInstructionSet = newShanghaiInstructionSet() +) + +// JumpTable contains the EVM opcodes supported at a given fork. +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 +} + +func newShanghaiInstructionSet() JumpTable { + instructionSet := newMergeInstructionSet() + enable3855(&instructionSet) // PUSH0 instruction + enable3860(&instructionSet) // Limit and meter initcode + return validate(instructionSet) +} + +func newMergeInstructionSet() JumpTable { + instructionSet := newLondonInstructionSet() + instructionSet[PREVRANDAO] = &operation{ + execute: opRandom, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } + return validate(instructionSet) +} + +// newLondonInstructionSet returns the frontier, homestead, byzantium, +// constantinople, 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 + enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198 + return validate(instructionSet) +} + +// newBerlinInstructionSet returns the frontier, homestead, byzantium, +// constantinople, 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, +// constantinople, istanbul and petersburg instructions. +func newIstanbulInstructionSet() JumpTable { + instructionSet := newConstantinopleInstructionSet() + + enable1344(&instructionSet) // ChainID opcode - https://eips.ethereum.org/EIPS/eip-1344 + enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884 + enable2200(&instructionSet) // Net metered SSTORE - https://eips.ethereum.org/EIPS/eip-2200 + + return validate(instructionSet) +} + +// newConstantinopleInstructionSet returns the frontier, homestead, +// byzantium and constantinople instructions. +func newConstantinopleInstructionSet() JumpTable { + instructionSet := newByzantiumInstructionSet() + instructionSet[SHL] = &operation{ + execute: opSHL, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + } + instructionSet[SHR] = &operation{ + execute: opSHR, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + } + instructionSet[SAR] = &operation{ + execute: opSAR, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + } + instructionSet[EXTCODEHASH] = &operation{ + execute: opExtCodeHash, + constantGas: params.ExtcodeHashGasConstantinople, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + instructionSet[CREATE2] = &operation{ + execute: opCreate2, + constantGas: params.Create2Gas, + dynamicGas: gasCreate2, + minStack: minStack(4, 1), + maxStack: maxStack(4, 1), + memorySize: memoryCreate2, + } + return validate(instructionSet) +} + +// newByzantiumInstructionSet returns the frontier, homestead and +// byzantium instructions. +func newByzantiumInstructionSet() JumpTable { + instructionSet := newSpuriousDragonInstructionSet() + instructionSet[STATICCALL] = &operation{ + execute: opStaticCall, + constantGas: params.CallGasEIP150, + dynamicGas: gasStaticCall, + minStack: minStack(6, 1), + maxStack: maxStack(6, 1), + memorySize: memoryStaticCall, + } + instructionSet[RETURNDATASIZE] = &operation{ + execute: opReturnDataSize, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } + instructionSet[RETURNDATACOPY] = &operation{ + execute: opReturnDataCopy, + constantGas: GasFastestStep, + dynamicGas: gasReturnDataCopy, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryReturnDataCopy, + } + instructionSet[REVERT] = &operation{ + execute: opRevert, + dynamicGas: gasRevert, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryRevert, + } + return validate(instructionSet) +} + +// EIP 158 a.k.a Spurious Dragon +func newSpuriousDragonInstructionSet() JumpTable { + instructionSet := newTangerineWhistleInstructionSet() + instructionSet[EXP].dynamicGas = gasExpEIP158 + return validate(instructionSet) +} + +// EIP 150 a.k.a Tangerine Whistle +func newTangerineWhistleInstructionSet() JumpTable { + instructionSet := newHomesteadInstructionSet() + instructionSet[BALANCE].constantGas = params.BalanceGasEIP150 + instructionSet[EXTCODESIZE].constantGas = params.ExtcodeSizeGasEIP150 + instructionSet[SLOAD].constantGas = params.SloadGasEIP150 + instructionSet[EXTCODECOPY].constantGas = params.ExtcodeCopyBaseEIP150 + instructionSet[CALL].constantGas = params.CallGasEIP150 + instructionSet[CALLCODE].constantGas = params.CallGasEIP150 + instructionSet[DELEGATECALL].constantGas = params.CallGasEIP150 + return validate(instructionSet) +} + +// newHomesteadInstructionSet returns the frontier and homestead +// instructions that can be executed during the homestead phase. +func newHomesteadInstructionSet() JumpTable { + instructionSet := newFrontierInstructionSet() + instructionSet[DELEGATECALL] = &operation{ + execute: opDelegateCall, + dynamicGas: gasDelegateCall, + constantGas: params.CallGasFrontier, + minStack: minStack(6, 1), + maxStack: maxStack(6, 1), + memorySize: memoryDelegateCall, + } + return validate(instructionSet) +} + +// newFrontierInstructionSet returns the frontier instructions +// that can be executed during the frontier phase. +func newFrontierInstructionSet() JumpTable { + tbl := JumpTable{ + STOP: { + execute: opStop, + constantGas: 0, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + }, + ADD: { + execute: opAdd, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + MUL: { + execute: opMul, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SUB: { + execute: opSub, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + DIV: { + execute: opDiv, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SDIV: { + execute: opSdiv, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + MOD: { + execute: opMod, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SMOD: { + execute: opSmod, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + ADDMOD: { + execute: opAddmod, + constantGas: GasMidStep, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + }, + MULMOD: { + execute: opMulmod, + constantGas: GasMidStep, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + }, + EXP: { + execute: opExp, + dynamicGas: gasExpFrontier, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SIGNEXTEND: { + execute: opSignExtend, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + LT: { + execute: opLt, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + GT: { + execute: opGt, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SLT: { + execute: opSlt, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SGT: { + execute: opSgt, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + EQ: { + execute: opEq, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + ISZERO: { + execute: opIszero, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + AND: { + execute: opAnd, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + XOR: { + execute: opXor, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + OR: { + execute: opOr, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + NOT: { + execute: opNot, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + BYTE: { + execute: opByte, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + KECCAK256: { + execute: opKeccak256, + constantGas: params.Keccak256Gas, + dynamicGas: gasKeccak256, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + memorySize: memoryKeccak256, + }, + ADDRESS: { + execute: opAddress, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + BALANCE: { + execute: opBalance, + constantGas: params.BalanceGasFrontier, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + ORIGIN: { + execute: opOrigin, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + CALLER: { + execute: opCaller, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + CALLVALUE: { + execute: opCallValue, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + CALLDATALOAD: { + execute: opCallDataLoad, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + CALLDATASIZE: { + execute: opCallDataSize, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + CALLDATACOPY: { + execute: opCallDataCopy, + constantGas: GasFastestStep, + dynamicGas: gasCallDataCopy, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryCallDataCopy, + }, + CODESIZE: { + execute: opCodeSize, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + CODECOPY: { + execute: opCodeCopy, + constantGas: GasFastestStep, + dynamicGas: gasCodeCopy, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryCodeCopy, + }, + GASPRICE: { + execute: opGasprice, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + EXTCODESIZE: { + execute: opExtCodeSize, + constantGas: params.ExtcodeSizeGasFrontier, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + EXTCODECOPY: { + execute: opExtCodeCopy, + constantGas: params.ExtcodeCopyBaseFrontier, + dynamicGas: gasExtCodeCopy, + minStack: minStack(4, 0), + maxStack: maxStack(4, 0), + memorySize: memoryExtCodeCopy, + }, + BLOCKHASH: { + execute: opBlockhash, + constantGas: GasExtStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + COINBASE: { + execute: opCoinbase, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + TIMESTAMP: { + execute: opTimestamp, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + NUMBER: { + execute: opNumber, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + DIFFICULTY: { + execute: opDifficulty, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + GASLIMIT: { + execute: opGasLimit, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + POP: { + execute: opPop, + constantGas: GasQuickStep, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + }, + MLOAD: { + execute: opMload, + constantGas: GasFastestStep, + dynamicGas: gasMLoad, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + memorySize: memoryMLoad, + }, + MSTORE: { + execute: opMstore, + constantGas: GasFastestStep, + dynamicGas: gasMStore, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryMStore, + }, + MSTORE8: { + execute: opMstore8, + constantGas: GasFastestStep, + dynamicGas: gasMStore8, + memorySize: memoryMStore8, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + }, + SLOAD: { + execute: opSload, + constantGas: params.SloadGasFrontier, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + SSTORE: { + execute: opSstore, + dynamicGas: gasSStore, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + }, + JUMP: { + execute: opJump, + constantGas: GasMidStep, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + }, + JUMPI: { + execute: opJumpi, + constantGas: GasSlowStep, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + }, + PC: { + execute: opPc, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + MSIZE: { + execute: opMsize, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + GAS: { + execute: opGas, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + JUMPDEST: { + execute: opJumpdest, + constantGas: params.JumpdestGas, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + }, + PUSH1: { + execute: opPush1, + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH2: { + execute: makePush(2, 2), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH3: { + execute: makePush(3, 3), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH4: { + execute: makePush(4, 4), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH5: { + execute: makePush(5, 5), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH6: { + execute: makePush(6, 6), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH7: { + execute: makePush(7, 7), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH8: { + execute: makePush(8, 8), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH9: { + execute: makePush(9, 9), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH10: { + execute: makePush(10, 10), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH11: { + execute: makePush(11, 11), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH12: { + execute: makePush(12, 12), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH13: { + execute: makePush(13, 13), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH14: { + execute: makePush(14, 14), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH15: { + execute: makePush(15, 15), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH16: { + execute: makePush(16, 16), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH17: { + execute: makePush(17, 17), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH18: { + execute: makePush(18, 18), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH19: { + execute: makePush(19, 19), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH20: { + execute: makePush(20, 20), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH21: { + execute: makePush(21, 21), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH22: { + execute: makePush(22, 22), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH23: { + execute: makePush(23, 23), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH24: { + execute: makePush(24, 24), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH25: { + execute: makePush(25, 25), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH26: { + execute: makePush(26, 26), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH27: { + execute: makePush(27, 27), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH28: { + execute: makePush(28, 28), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH29: { + execute: makePush(29, 29), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH30: { + execute: makePush(30, 30), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH31: { + execute: makePush(31, 31), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH32: { + execute: makePush(32, 32), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + DUP1: { + execute: makeDup(1), + constantGas: GasFastestStep, + minStack: minDupStack(1), + maxStack: maxDupStack(1), + }, + DUP2: { + execute: makeDup(2), + constantGas: GasFastestStep, + minStack: minDupStack(2), + maxStack: maxDupStack(2), + }, + DUP3: { + execute: makeDup(3), + constantGas: GasFastestStep, + minStack: minDupStack(3), + maxStack: maxDupStack(3), + }, + DUP4: { + execute: makeDup(4), + constantGas: GasFastestStep, + minStack: minDupStack(4), + maxStack: maxDupStack(4), + }, + DUP5: { + execute: makeDup(5), + constantGas: GasFastestStep, + minStack: minDupStack(5), + maxStack: maxDupStack(5), + }, + DUP6: { + execute: makeDup(6), + constantGas: GasFastestStep, + minStack: minDupStack(6), + maxStack: maxDupStack(6), + }, + DUP7: { + execute: makeDup(7), + constantGas: GasFastestStep, + minStack: minDupStack(7), + maxStack: maxDupStack(7), + }, + DUP8: { + execute: makeDup(8), + constantGas: GasFastestStep, + minStack: minDupStack(8), + maxStack: maxDupStack(8), + }, + DUP9: { + execute: makeDup(9), + constantGas: GasFastestStep, + minStack: minDupStack(9), + maxStack: maxDupStack(9), + }, + DUP10: { + execute: makeDup(10), + constantGas: GasFastestStep, + minStack: minDupStack(10), + maxStack: maxDupStack(10), + }, + DUP11: { + execute: makeDup(11), + constantGas: GasFastestStep, + minStack: minDupStack(11), + maxStack: maxDupStack(11), + }, + DUP12: { + execute: makeDup(12), + constantGas: GasFastestStep, + minStack: minDupStack(12), + maxStack: maxDupStack(12), + }, + DUP13: { + execute: makeDup(13), + constantGas: GasFastestStep, + minStack: minDupStack(13), + maxStack: maxDupStack(13), + }, + DUP14: { + execute: makeDup(14), + constantGas: GasFastestStep, + minStack: minDupStack(14), + maxStack: maxDupStack(14), + }, + DUP15: { + execute: makeDup(15), + constantGas: GasFastestStep, + minStack: minDupStack(15), + maxStack: maxDupStack(15), + }, + DUP16: { + execute: makeDup(16), + constantGas: GasFastestStep, + minStack: minDupStack(16), + maxStack: maxDupStack(16), + }, + SWAP1: { + execute: makeSwap(1), + constantGas: GasFastestStep, + minStack: minSwapStack(2), + maxStack: maxSwapStack(2), + }, + SWAP2: { + execute: makeSwap(2), + constantGas: GasFastestStep, + minStack: minSwapStack(3), + maxStack: maxSwapStack(3), + }, + SWAP3: { + execute: makeSwap(3), + constantGas: GasFastestStep, + minStack: minSwapStack(4), + maxStack: maxSwapStack(4), + }, + SWAP4: { + execute: makeSwap(4), + constantGas: GasFastestStep, + minStack: minSwapStack(5), + maxStack: maxSwapStack(5), + }, + SWAP5: { + execute: makeSwap(5), + constantGas: GasFastestStep, + minStack: minSwapStack(6), + maxStack: maxSwapStack(6), + }, + SWAP6: { + execute: makeSwap(6), + constantGas: GasFastestStep, + minStack: minSwapStack(7), + maxStack: maxSwapStack(7), + }, + SWAP7: { + execute: makeSwap(7), + constantGas: GasFastestStep, + minStack: minSwapStack(8), + maxStack: maxSwapStack(8), + }, + SWAP8: { + execute: makeSwap(8), + constantGas: GasFastestStep, + minStack: minSwapStack(9), + maxStack: maxSwapStack(9), + }, + SWAP9: { + execute: makeSwap(9), + constantGas: GasFastestStep, + minStack: minSwapStack(10), + maxStack: maxSwapStack(10), + }, + SWAP10: { + execute: makeSwap(10), + constantGas: GasFastestStep, + minStack: minSwapStack(11), + maxStack: maxSwapStack(11), + }, + SWAP11: { + execute: makeSwap(11), + constantGas: GasFastestStep, + minStack: minSwapStack(12), + maxStack: maxSwapStack(12), + }, + SWAP12: { + execute: makeSwap(12), + constantGas: GasFastestStep, + minStack: minSwapStack(13), + maxStack: maxSwapStack(13), + }, + SWAP13: { + execute: makeSwap(13), + constantGas: GasFastestStep, + minStack: minSwapStack(14), + maxStack: maxSwapStack(14), + }, + SWAP14: { + execute: makeSwap(14), + constantGas: GasFastestStep, + minStack: minSwapStack(15), + maxStack: maxSwapStack(15), + }, + SWAP15: { + execute: makeSwap(15), + constantGas: GasFastestStep, + minStack: minSwapStack(16), + maxStack: maxSwapStack(16), + }, + SWAP16: { + execute: makeSwap(16), + constantGas: GasFastestStep, + minStack: minSwapStack(17), + maxStack: maxSwapStack(17), + }, + LOG0: { + execute: makeLog(0), + dynamicGas: makeGasLog(0), + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryLog, + }, + LOG1: { + execute: makeLog(1), + dynamicGas: makeGasLog(1), + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryLog, + }, + LOG2: { + execute: makeLog(2), + dynamicGas: makeGasLog(2), + minStack: minStack(4, 0), + maxStack: maxStack(4, 0), + memorySize: memoryLog, + }, + LOG3: { + execute: makeLog(3), + dynamicGas: makeGasLog(3), + minStack: minStack(5, 0), + maxStack: maxStack(5, 0), + memorySize: memoryLog, + }, + LOG4: { + execute: makeLog(4), + dynamicGas: makeGasLog(4), + minStack: minStack(6, 0), + maxStack: maxStack(6, 0), + memorySize: memoryLog, + }, + CREATE: { + execute: opCreate, + constantGas: params.CreateGas, + dynamicGas: gasCreate, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + memorySize: memoryCreate, + }, + CALL: { + execute: opCall, + constantGas: params.CallGasFrontier, + dynamicGas: gasCall, + minStack: minStack(7, 1), + maxStack: maxStack(7, 1), + memorySize: memoryCall, + }, + CALLCODE: { + execute: opCallCode, + constantGas: params.CallGasFrontier, + dynamicGas: gasCallCode, + minStack: minStack(7, 1), + maxStack: maxStack(7, 1), + memorySize: memoryCall, + }, + RETURN: { + execute: opReturn, + dynamicGas: gasReturn, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryReturn, + }, + SELFDESTRUCT: { + execute: opSelfdestruct, + dynamicGas: gasSelfdestruct, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + }, + } + + // Fill all unassigned slots with opUndefined. + for i, entry := range tbl { + if entry == nil { + tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)} + } + } + + return validate(tbl) +} + +func copyJumpTable(source *JumpTable) *JumpTable { + dest := *source + for i, op := range source { + if op != nil { + opCopy := *op + dest[i] = &opCopy + } + } + return &dest +} diff --git a/state/runtime/fakevm/logger.go b/state/runtime/fakevm/logger.go index cfd316d6dc..3595c003ad 100644 --- a/state/runtime/fakevm/logger.go +++ b/state/runtime/fakevm/logger.go @@ -1,4 +1,4 @@ -// Copyright 2021 The go-ethereum Authors +// Copyright 2015 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 @@ -18,10 +18,8 @@ package fakevm import ( "math/big" - "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" ) // EVMLogger is used to collect execution traces from an EVM transaction @@ -35,11 +33,11 @@ type EVMLogger interface { CaptureTxEnd(restGas uint64) // Top call frame CaptureStart(env *FakeEVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) - CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) + CaptureEnd(output []byte, gasUsed uint64, err error) // Rest of call frames - CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) + CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) CaptureExit(output []byte, gasUsed uint64, err error) // Opcode level - CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) - CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) + CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) + CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) } diff --git a/state/runtime/fakevm/memory.go b/state/runtime/fakevm/memory.go index e7453e4ef5..e88ef80152 100644 --- a/state/runtime/fakevm/memory.go +++ b/state/runtime/fakevm/memory.go @@ -17,14 +17,13 @@ package fakevm import ( - "fmt" - "github.com/holiman/uint256" ) // Memory implements a simple memory model for the ethereum virtual machine. type Memory struct { - store []byte + store []byte + lastGasCost uint64 } // NewMemory returns a new memory model. @@ -54,10 +53,9 @@ func (m *Memory) Set32(offset uint64, val *uint256.Int) { if offset+32 > uint64(len(m.store)) { panic("invalid memory: store empty") } - // 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 - val.WriteToSlice(m.store[offset:]) + b32 := val.Bytes32() + copy(m.store[offset:], b32[:]) } // Resize resizes the memory to size @@ -105,18 +103,3 @@ func (m *Memory) Len() int { func (m *Memory) Data() []byte { return m.store } - -// Print dumps the content of the memory. -func (m *Memory) Print() { - fmt.Printf("### mem %d bytes ###\n", len(m.store)) - if len(m.store) > 0 { - addr := 0 - for i := 0; i+32 <= len(m.store); i += 32 { - fmt.Printf("%03d: % x\n", addr, m.store[i:i+32]) - addr++ - } - } else { - fmt.Println("-- empty --") - } - fmt.Println("####################") -} diff --git a/state/runtime/fakevm/memory_table.go b/state/runtime/fakevm/memory_table.go new file mode 100644 index 0000000000..7f9221b1c3 --- /dev/null +++ b/state/runtime/fakevm/memory_table.go @@ -0,0 +1,113 @@ +// 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 fakevm + +func memoryKeccak256(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(1)) +} + +func memoryCallDataCopy(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(2)) +} + +func memoryReturnDataCopy(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(2)) +} + +func memoryCodeCopy(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(2)) +} + +func memoryExtCodeCopy(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(1), stack.Back(3)) +} + +func memoryMLoad(stack *Stack) (uint64, bool) { + return calcMemSize64WithUint(stack.Back(0), 32) +} + +func memoryMStore8(stack *Stack) (uint64, bool) { + return calcMemSize64WithUint(stack.Back(0), 1) +} + +func memoryMStore(stack *Stack) (uint64, bool) { + return calcMemSize64WithUint(stack.Back(0), 32) +} + +func memoryCreate(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(1), stack.Back(2)) +} + +func memoryCreate2(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(1), stack.Back(2)) +} + +func memoryCall(stack *Stack) (uint64, bool) { + x, overflow := calcMemSize64(stack.Back(5), stack.Back(6)) + if overflow { + return 0, true + } + y, overflow := calcMemSize64(stack.Back(3), stack.Back(4)) + if overflow { + return 0, true + } + if x > y { + return x, false + } + return y, false +} +func memoryDelegateCall(stack *Stack) (uint64, bool) { + x, overflow := calcMemSize64(stack.Back(4), stack.Back(5)) + if overflow { + return 0, true + } + y, overflow := calcMemSize64(stack.Back(2), stack.Back(3)) + if overflow { + return 0, true + } + if x > y { + return x, false + } + return y, false +} + +func memoryStaticCall(stack *Stack) (uint64, bool) { + x, overflow := calcMemSize64(stack.Back(4), stack.Back(5)) + if overflow { + return 0, true + } + y, overflow := calcMemSize64(stack.Back(2), stack.Back(3)) + if overflow { + return 0, true + } + if x > y { + return x, false + } + return y, false +} + +func memoryReturn(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(1)) +} + +func memoryRevert(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(1)) +} + +func memoryLog(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(1)) +} diff --git a/state/runtime/fakevm/opcodes.go b/state/runtime/fakevm/opcodes.go index 844f397216..a1ba2c7271 100644 --- a/state/runtime/fakevm/opcodes.go +++ b/state/runtime/fakevm/opcodes.go @@ -1,465 +1,266 @@ +// Copyright 2014 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 fakevm -// Ethereum Virtual Machine OpCode s -// https://ethervm.io/#opcodes +import ( + "fmt" +) -// OpCode is the EVM opcode +// OpCode is an EVM opcode type OpCode byte -const ( - - // STOP halts execution of the contract - STOP = 0x00 - - // ADD performs (u)int256 addition modulo 2**256 - ADD = 0x01 - - // MUL performs (u)int256 multiplication modulo 2**256 - MUL = 0x02 - - // SUB performs (u)int256 subtraction modulo 2**256 - SUB = 0x03 - - // DIV performs uint256 division - DIV = 0x04 - - // SDIV performs int256 division - SDIV = 0x05 - - // MOD performs uint256 modulus - MOD = 0x06 - - // SMOD performs int256 modulus - SMOD = 0x07 - - // ADDMOD performs (u)int256 addition modulo N - ADDMOD = 0x08 - - // MULMOD performs (u)int256 multiplication modulo N - MULMOD = 0x09 - - // EXP performs uint256 exponentiation modulo 2**256 - EXP = 0x0A - - // SIGNEXTEND performs sign extends x from (b + 1) * 8 bits to 256 bits. - SIGNEXTEND = 0x0B - - // LT performs int256 comparison - LT = 0x10 - - // GT performs int256 comparison - GT = 0x11 - - // SLT performs int256 comparison - SLT = 0x12 - - // SGT performs int256 comparison - SGT = 0x13 - - // EQ performs (u)int256 equality - EQ = 0x14 - - // ISZERO checks if (u)int256 is zero - ISZERO = 0x15 - - // AND performs 256-bit bitwise and - AND = 0x16 - - // OR performs 256-bit bitwise or - OR = 0x17 - - // XOR performs 256-bit bitwise xor - XOR = 0x18 - - // NOT performs 256-bit bitwise not - NOT = 0x19 - - // BYTE returns the ith byte of (u)int256 x counting from most significant byte - BYTE = 0x1A - - // SHL performs a shift left - SHL = 0x1B - - // SHR performs a logical shift right - SHR = 0x1C - - // SAR performs an arithmetic shift right - SAR = 0x1D - - // SHA3 performs the keccak256 hash function - SHA3 = 0x20 - - // ADDRESS returns the address of the executing contract - ADDRESS = 0x30 - - // BALANCE returns the address balance in wei - BALANCE = 0x31 - - // ORIGIN returns the transaction origin address - ORIGIN = 0x32 - - // CALLER returns the message caller address - CALLER = 0x33 - - // CALLVALUE returns the message funds in wei - CALLVALUE = 0x34 - - // CALLDATALOAD reads a (u)int256 from message data - CALLDATALOAD = 0x35 - - // CALLDATASIZE returns the message data length in bytes - CALLDATASIZE = 0x36 - - // CALLDATACOPY copies the message data - CALLDATACOPY = 0x37 - - // CODESIZE returns the length of the executing contract's code in bytes - CODESIZE = 0x38 - - // CODECOPY copies the executing contract bytecode - CODECOPY = 0x39 - - // GASPRICE returns the gas price of the executing transaction, in wei per unit of gas - GASPRICE = 0x3A - - // EXTCODESIZE returns the length of the contract bytecode at addr - EXTCODESIZE = 0x3B - - // EXTCODECOPY copies the contract bytecode - EXTCODECOPY = 0x3C - - // RETURNDATASIZE returns the size of the returned data from the last external call in bytes - RETURNDATASIZE = 0x3D - - // RETURNDATACOPY copies the returned data - RETURNDATACOPY = 0x3E - - // EXTCODEHASH returns the hash of the specified contract bytecode - EXTCODEHASH = 0x3F - - // BLOCKHASH returns the hash of the specific block. Only valid for the last 256 most recent blocks - BLOCKHASH = 0x40 - - // COINBASE returns the address of the current block's miner - COINBASE = 0x41 - - // TIMESTAMP returns the current block's Unix timestamp in seconds - TIMESTAMP = 0x42 - - // NUMBER returns the current block's number - NUMBER = 0x43 - - // DIFFICULTY returns the current block's difficulty - DIFFICULTY = 0x44 - - // GASLIMIT returns the current block's gas limit - GASLIMIT = 0x45 - - // CHAINID returns the id of the chain - CHAINID = 0x46 - - // SELFBALANCE returns the balance of the current account - SELFBALANCE = 0x47 - - // POP pops a (u)int256 off the stack and discards it - POP = 0x50 - - // MLOAD reads a (u)int256 from memory - MLOAD = 0x51 - - // MSTORE writes a (u)int256 to memory - MSTORE = 0x52 - - // MSTORE8 writes a uint8 to memory - MSTORE8 = 0x53 - - // SLOAD reads a (u)int256 from storage - SLOAD = 0x54 - - // SSTORE writes a (u)int256 to storage - SSTORE = 0x55 - - // JUMP performs an unconditional jump - JUMP = 0x56 - - // JUMPI performs a conditional jump if condition is truthy - JUMPI = 0x57 - - // PC returns the program counter - PC = 0x58 - - // MSIZE returns the size of memory for this contract execution, in bytes - MSIZE = 0x59 - - // GAS returns the remaining gas - GAS = 0x5A - - // JUMPDEST corresponds to a possible jump destination - JUMPDEST = 0x5B - - // PUSH1 pushes a 1-byte value onto the stack - PUSH1 = 0x60 - - // PUSH2 pushes a 2-bytes value onto the stack - PUSH2 = 0x61 - - // PUSH3 pushes a 3-bytes value onto the stack - PUSH3 = 0x62 - - // PUSH4 pushes a 4-bytes value onto the stack - PUSH4 = 0x63 - - // PUSH5 pushes a 5-bytes value onto the stack - PUSH5 = 0x64 - - // PUSH6 pushes a 6-bytes value onto the stack - PUSH6 = 0x65 - - // PUSH7 pushes a 7-bytes value onto the stack - PUSH7 = 0x66 - - // PUSH8 pushes a 8-bytes value onto the stack - PUSH8 = 0x67 - - // PUSH9 pushes a 9-bytes value onto the stack - PUSH9 = 0x68 - - // PUSH10 pushes a 10-bytes value onto the stack - PUSH10 = 0x69 - - // PUSH11 pushes a 11-bytes value onto the stack - PUSH11 = 0x6A - - // PUSH12 pushes a 12-bytes value onto the stack - PUSH12 = 0x6B - - // PUSH13 pushes a 13-bytes value onto the stack - PUSH13 = 0x6C - - // PUSH14 pushes a 14-bytes value onto the stack - PUSH14 = 0x6D - - // PUSH15 pushes a 15-bytes value onto the stack - PUSH15 = 0x6E - - // PUSH16 pushes a 16-bytes value onto the stack - PUSH16 = 0x6F - - // PUSH17 pushes a 17-bytes value onto the stack - PUSH17 = 0x70 - - // PUSH18 pushes a 18-bytes value onto the stack - PUSH18 = 0x71 - - // PUSH19 pushes a 19-bytes value onto the stack - PUSH19 = 0x72 - - // PUSH20 pushes a 20-bytes value onto the stack - PUSH20 = 0x73 - - // PUSH21 pushes a 21-bytes value onto the stack - PUSH21 = 0x74 - - // PUSH22 pushes a 22-bytes value onto the stack - PUSH22 = 0x75 - - // PUSH23 pushes a 23-bytes value onto the stack - PUSH23 = 0x76 - - // PUSH24 pushes a 24-bytes value onto the stack - PUSH24 = 0x77 - - // PUSH25 pushes a 25-bytes value onto the stack - PUSH25 = 0x78 - - // PUSH26 pushes a 26-bytes value onto the stack - PUSH26 = 0x79 - - // PUSH27 pushes a 27-bytes value onto the stack - PUSH27 = 0x7A - - // PUSH28 pushes a 28-bytes value onto the stack - PUSH28 = 0x7B - - // PUSH29 pushes a 29-bytes value onto the stack - PUSH29 = 0x7C - - // PUSH30 pushes a 30-bytes value onto the stack - PUSH30 = 0x7D - - // PUSH31 pushes a 31-bytes value onto the stack - PUSH31 = 0x7E - - // PUSH32 pushes a 32-byte value onto the stack - PUSH32 = 0x7F - - // DUP1 clones the last value on the stack - DUP1 = 0x80 - - // DUP2 clones the 2nd last value on the stack - DUP2 = 0x81 - - // DUP3 clones the 3rd last value on the stack - DUP3 = 0x82 - - // DUP4 clones the 4th last value on the stack - DUP4 = 0x83 - - // DUP5 clones the 5th last value on the stack - DUP5 = 0x84 - - // DUP6 clones the 6th last value on the stack - DUP6 = 0x85 - - // DUP7 clones the 7th last value on the stack - DUP7 = 0x86 - - // DUP8 clones the 8th last value on the stack - DUP8 = 0x87 - - // DUP9 clones the 9th last value on the stack - DUP9 = 0x88 - - // DUP10 clones the 10th last value on the stack - DUP10 = 0x89 - - // DUP11 clones the 11th last value on the stack - DUP11 = 0x8A - - // DUP12 clones the 12th last value on the stack - DUP12 = 0x8B - - // DUP13 clones the 13th last value on the stack - DUP13 = 0x8C - - // DUP14 clones the 14th last value on the stack - DUP14 = 0x8D - - // DUP15 clones the 15th last value on the stack - DUP15 = 0x8E - - // DUP16 clones the 16th last value on the stack - DUP16 = 0x8F - - // SWAP1 swaps the last two values on the stack - SWAP1 = 0x90 - - // SWAP2 swaps the top of the stack with the 3rd last element - SWAP2 = 0x91 - - // SWAP3 swaps the top of the stack with the 4th last element - SWAP3 = 0x92 - - // SWAP4 swaps the top of the stack with the 5th last element - SWAP4 = 0x93 - - // SWAP5 swaps the top of the stack with the 6th last element - SWAP5 = 0x94 - - // SWAP6 swaps the top of the stack with the 7th last element - SWAP6 = 0x95 - - // SWAP7 swaps the top of the stack with the 8th last element - SWAP7 = 0x96 - - // SWAP8 swaps the top of the stack with the 9th last element - SWAP8 = 0x97 - - // SWAP9 swaps the top of the stack with the 10th last element - SWAP9 = 0x98 - - // SWAP10 swaps the top of the stack with the 11th last element - SWAP10 = 0x99 - - // SWAP11 swaps the top of the stack with the 12th last element - SWAP11 = 0x9A - - // SWAP12 swaps the top of the stack with the 13th last element - SWAP12 = 0x9B - - // SWAP13 swaps the top of the stack with the 14th last element - SWAP13 = 0x9C - - // SWAP14 swaps the top of the stack with the 15th last element - SWAP14 = 0x9D - - // SWAP15 swaps the top of the stack with the 16th last element - SWAP15 = 0x9E - - // SWAP16 swaps the top of the stack with the 17th last element - SWAP16 = 0x9F - - // LOG0 fires an event without topics - LOG0 = 0xA0 - - // LOG1 fires an event with one topic - LOG1 = 0xA1 +// IsPush specifies if an opcode is a PUSH opcode. +func (op OpCode) IsPush() bool { + return PUSH1 <= op && op <= PUSH32 +} - // LOG2 fires an event with two topics - LOG2 = 0xA2 +// 0x0 range - arithmetic ops. +const ( + STOP OpCode = 0x0 + ADD OpCode = 0x1 + MUL OpCode = 0x2 + SUB OpCode = 0x3 + DIV OpCode = 0x4 + SDIV OpCode = 0x5 + MOD OpCode = 0x6 + SMOD OpCode = 0x7 + ADDMOD OpCode = 0x8 + MULMOD OpCode = 0x9 + EXP OpCode = 0xa + SIGNEXTEND OpCode = 0xb +) - // LOG3 fires an event with three topics - LOG3 = 0xA3 +// 0x10 range - comparison ops. +const ( + LT OpCode = 0x10 + GT OpCode = 0x11 + SLT OpCode = 0x12 + SGT OpCode = 0x13 + EQ OpCode = 0x14 + ISZERO OpCode = 0x15 + AND OpCode = 0x16 + OR OpCode = 0x17 + XOR OpCode = 0x18 + NOT OpCode = 0x19 + BYTE OpCode = 0x1a + SHL OpCode = 0x1b + SHR OpCode = 0x1c + SAR OpCode = 0x1d +) - // LOG4 fires an event with four topics - LOG4 = 0xA4 +// 0x20 range - crypto. +const ( + KECCAK256 OpCode = 0x20 +) - // CREATE creates a child contract - CREATE = 0xF0 +// 0x30 range - closure state. +const ( + ADDRESS OpCode = 0x30 + BALANCE OpCode = 0x31 + ORIGIN OpCode = 0x32 + CALLER OpCode = 0x33 + CALLVALUE OpCode = 0x34 + CALLDATALOAD OpCode = 0x35 + CALLDATASIZE OpCode = 0x36 + CALLDATACOPY OpCode = 0x37 + CODESIZE OpCode = 0x38 + CODECOPY OpCode = 0x39 + GASPRICE OpCode = 0x3a + EXTCODESIZE OpCode = 0x3b + EXTCODECOPY OpCode = 0x3c + RETURNDATASIZE OpCode = 0x3d + RETURNDATACOPY OpCode = 0x3e + EXTCODEHASH OpCode = 0x3f +) - // CALL calls a method in another contract - CALL = 0xF1 +// 0x40 range - block operations. +const ( + BLOCKHASH OpCode = 0x40 + COINBASE OpCode = 0x41 + TIMESTAMP OpCode = 0x42 + NUMBER OpCode = 0x43 + DIFFICULTY OpCode = 0x44 + RANDOM OpCode = 0x44 // Same as DIFFICULTY + PREVRANDAO OpCode = 0x44 // Same as DIFFICULTY + GASLIMIT OpCode = 0x45 + CHAINID OpCode = 0x46 + SELFBALANCE OpCode = 0x47 + BASEFEE OpCode = 0x48 +) - // CALLCODE calls a method in another contract - CALLCODE = 0xF2 +// 0x50 range - 'storage' and execution. +const ( + 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 + PUSH0 OpCode = 0x5f +) - // RETURN returns from this contract call - RETURN = 0xF3 +// 0x60 range - pushes. +const ( + PUSH1 OpCode = 0x60 + iota + PUSH2 + PUSH3 + PUSH4 + PUSH5 + PUSH6 + PUSH7 + PUSH8 + PUSH9 + PUSH10 + PUSH11 + PUSH12 + PUSH13 + PUSH14 + PUSH15 + PUSH16 + PUSH17 + PUSH18 + PUSH19 + PUSH20 + PUSH21 + PUSH22 + PUSH23 + PUSH24 + PUSH25 + PUSH26 + PUSH27 + PUSH28 + PUSH29 + PUSH30 + PUSH31 + PUSH32 +) - // DELEGATECALL calls a method in another contract using the storage of the current contract - DELEGATECALL = 0xF4 +// 0x80 range - dups. +const ( + DUP1 = 0x80 + iota + DUP2 + DUP3 + DUP4 + DUP5 + DUP6 + DUP7 + DUP8 + DUP9 + DUP10 + DUP11 + DUP12 + DUP13 + DUP14 + DUP15 + DUP16 +) - // CREATE2 creates a child contract with a salt - CREATE2 = 0xF5 +// 0x90 range - swaps. +const ( + SWAP1 = 0x90 + iota + SWAP2 + SWAP3 + SWAP4 + SWAP5 + SWAP6 + SWAP7 + SWAP8 + SWAP9 + SWAP10 + SWAP11 + SWAP12 + SWAP13 + SWAP14 + SWAP15 + SWAP16 +) - // STATICCALL calls a method in another contract - STATICCALL = 0xFA +// 0xa0 range - logging ops. +const ( + LOG0 OpCode = 0xa0 + iota + LOG1 + LOG2 + LOG3 + LOG4 +) - // REVERT reverts with return data - REVERT = 0xFD +// 0xf0 range - closures. +const ( + CREATE OpCode = 0xf0 + CALL OpCode = 0xf1 + CALLCODE OpCode = 0xf2 + RETURN OpCode = 0xf3 + DELEGATECALL OpCode = 0xf4 + CREATE2 OpCode = 0xf5 + + STATICCALL OpCode = 0xfa + REVERT OpCode = 0xfd + INVALID OpCode = 0xfe + SELFDESTRUCT OpCode = 0xff +) - // SELFDESTRUCT destroys the contract and sends all funds to addr - SELFDESTRUCT = 0xFF +// 0xb0 range. +const ( + TLOAD OpCode = 0xb3 + TSTORE OpCode = 0xb4 ) +// Since the opcodes aren't all in order we can't use a regular slice. var opCodeToString = map[OpCode]string{ - STOP: "STOP", - ADD: "ADD", - MUL: "MUL", - SUB: "SUB", - DIV: "DIV", - SDIV: "SDIV", - MOD: "MOD", - SMOD: "SMOD", - EXP: "EXP", - NOT: "NOT", - LT: "LT", - GT: "GT", - SLT: "SLT", - SGT: "SGT", - EQ: "EQ", - ISZERO: "ISZERO", - SIGNEXTEND: "SIGNEXTEND", - AND: "AND", - OR: "OR", - XOR: "XOR", - BYTE: "BYTE", - SHL: "SHL", - SHR: "SHR", - SAR: "SAR", - ADDMOD: "ADDMOD", - MULMOD: "MULMOD", - SHA3: "SHA3", + // 0x0 range - arithmetic ops. + STOP: "STOP", + ADD: "ADD", + MUL: "MUL", + SUB: "SUB", + DIV: "DIV", + SDIV: "SDIV", + MOD: "MOD", + SMOD: "SMOD", + EXP: "EXP", + NOT: "NOT", + LT: "LT", + GT: "GT", + SLT: "SLT", + SGT: "SGT", + EQ: "EQ", + ISZERO: "ISZERO", + SIGNEXTEND: "SIGNEXTEND", + + // 0x10 range - bit ops. + AND: "AND", + OR: "OR", + XOR: "XOR", + BYTE: "BYTE", + SHL: "SHL", + SHR: "SHR", + SAR: "SAR", + ADDMOD: "ADDMOD", + MULMOD: "MULMOD", + + // 0x20 range - crypto. + KECCAK256: "KECCAK256", + + // 0x30 range - closure state. ADDRESS: "ADDRESS", BALANCE: "BALANCE", ORIGIN: "ORIGIN", @@ -476,106 +277,284 @@ var opCodeToString = map[OpCode]string{ RETURNDATASIZE: "RETURNDATASIZE", RETURNDATACOPY: "RETURNDATACOPY", EXTCODEHASH: "EXTCODEHASH", - BLOCKHASH: "BLOCKHASH", - COINBASE: "COINBASE", - TIMESTAMP: "TIMESTAMP", - NUMBER: "NUMBER", - DIFFICULTY: "DIFFICULTY", - GASLIMIT: "GASLIMIT", - POP: "POP", - MLOAD: "MLOAD", - MSTORE: "MSTORE", - MSTORE8: "MSTORE8", - SLOAD: "SLOAD", - SSTORE: "SSTORE", - JUMP: "JUMP", - JUMPI: "JUMPI", - PC: "PC", - MSIZE: "MSIZE", - GAS: "GAS", - JUMPDEST: "JUMPDEST", - 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", - CALL: "CALL", - RETURN: "RETURN", - CALLCODE: "CALLCODE", - DELEGATECALL: "DELEGATECALL", - CREATE2: "CREATE2", - STATICCALL: "STATICCALL", - REVERT: "REVERT", - SELFDESTRUCT: "SELFDESTRUCT", - CHAINID: "CHAINID", - SELFBALANCE: "SELFBALANCE", + + // 0x40 range - block operations. + BLOCKHASH: "BLOCKHASH", + COINBASE: "COINBASE", + TIMESTAMP: "TIMESTAMP", + NUMBER: "NUMBER", + DIFFICULTY: "DIFFICULTY", // TODO (MariusVanDerWijden) rename to PREVRANDAO post merge + GASLIMIT: "GASLIMIT", + CHAINID: "CHAINID", + SELFBALANCE: "SELFBALANCE", + BASEFEE: "BASEFEE", + + // 0x50 range - 'storage' and execution. + POP: "POP", + //DUP: "DUP", + //SWAP: "SWAP", + MLOAD: "MLOAD", + MSTORE: "MSTORE", + MSTORE8: "MSTORE8", + SLOAD: "SLOAD", + SSTORE: "SSTORE", + JUMP: "JUMP", + JUMPI: "JUMPI", + PC: "PC", + MSIZE: "MSIZE", + GAS: "GAS", + JUMPDEST: "JUMPDEST", + PUSH0: "PUSH0", + + // 0x60 range - push. + 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", + + // 0xb0 range. + TLOAD: "TLOAD", + TSTORE: "TSTORE", + + // 0xf0 range. + CREATE: "CREATE", + CALL: "CALL", + RETURN: "RETURN", + CALLCODE: "CALLCODE", + DELEGATECALL: "DELEGATECALL", + CREATE2: "CREATE2", + STATICCALL: "STATICCALL", + REVERT: "REVERT", + INVALID: "INVALID", + SELFDESTRUCT: "SELFDESTRUCT", } func (op OpCode) String() string { - return opCodeToString[op] + str := opCodeToString[op] + if len(str) == 0 { + return fmt.Sprintf("opcode %#x not defined", int(op)) + } + + return str +} + +var stringToOp = map[string]OpCode{ + "STOP": STOP, + "ADD": ADD, + "MUL": MUL, + "SUB": SUB, + "DIV": DIV, + "SDIV": SDIV, + "MOD": MOD, + "SMOD": SMOD, + "EXP": EXP, + "NOT": NOT, + "LT": LT, + "GT": GT, + "SLT": SLT, + "SGT": SGT, + "EQ": EQ, + "ISZERO": ISZERO, + "SIGNEXTEND": SIGNEXTEND, + "AND": AND, + "OR": OR, + "XOR": XOR, + "BYTE": BYTE, + "SHL": SHL, + "SHR": SHR, + "SAR": SAR, + "ADDMOD": ADDMOD, + "MULMOD": MULMOD, + "KECCAK256": KECCAK256, + "ADDRESS": ADDRESS, + "BALANCE": BALANCE, + "ORIGIN": ORIGIN, + "CALLER": CALLER, + "CALLVALUE": CALLVALUE, + "CALLDATALOAD": CALLDATALOAD, + "CALLDATASIZE": CALLDATASIZE, + "CALLDATACOPY": CALLDATACOPY, + "CHAINID": CHAINID, + "BASEFEE": BASEFEE, + "DELEGATECALL": DELEGATECALL, + "STATICCALL": STATICCALL, + "CODESIZE": CODESIZE, + "CODECOPY": CODECOPY, + "GASPRICE": GASPRICE, + "EXTCODESIZE": EXTCODESIZE, + "EXTCODECOPY": EXTCODECOPY, + "RETURNDATASIZE": RETURNDATASIZE, + "RETURNDATACOPY": RETURNDATACOPY, + "EXTCODEHASH": EXTCODEHASH, + "BLOCKHASH": BLOCKHASH, + "COINBASE": COINBASE, + "TIMESTAMP": TIMESTAMP, + "NUMBER": NUMBER, + "DIFFICULTY": DIFFICULTY, + "GASLIMIT": GASLIMIT, + "SELFBALANCE": SELFBALANCE, + "POP": POP, + "MLOAD": MLOAD, + "MSTORE": MSTORE, + "MSTORE8": MSTORE8, + "SLOAD": SLOAD, + "SSTORE": SSTORE, + "JUMP": JUMP, + "JUMPI": JUMPI, + "PC": PC, + "MSIZE": MSIZE, + "GAS": GAS, + "JUMPDEST": JUMPDEST, + "PUSH0": PUSH0, + "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, + "INVALID": INVALID, + "SELFDESTRUCT": SELFDESTRUCT, +} + +// StringToOp finds the opcode whose name is stored in `str`. +func StringToOp(str string) OpCode { + return stringToOp[str] } diff --git a/state/runtime/fakevm/operations_acl.go b/state/runtime/fakevm/operations_acl.go new file mode 100644 index 0000000000..09ca31d3b6 --- /dev/null +++ b/state/runtime/fakevm/operations_acl.go @@ -0,0 +1,244 @@ +// 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 fakevm + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" +) + +func makeGasSStoreFunc(clearingRefund uint64) gasFunc { + return func(evm *FakeEVM, 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) + } +} + +// 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 *FakeEVM, 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 params.ColdSloadCostEIP2929, nil + } + return params.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 *FakeEVM, 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, params.ColdAccountAccessCostEIP2929-params.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 *FakeEVM, 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 params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil + } + return 0, nil +} + +func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { + return func(evm *FakeEVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(stack.Back(1).Bytes20()) + // Check slot presence in the access list + warmAccess := evm.StateDB.AddressInAccessList(addr) + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so + // the cost to charge for cold access, if any, is Cold - Warm + coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + if !warmAccess { + evm.StateDB.AddAddressToAccessList(addr) + // Charge the remaining difference here already, to correctly calculate available + // gas for call + if !contract.UseGas(coldCost) { + return 0, ErrOutOfGas + } + } + // Now call the old calculator, which takes into account + // - create new account + // - transfer value + // - memory expansion + // - 63/64ths rule + gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + if warmAccess || err != nil { + return gas, err + } + // In case of a cold access, we temporarily add the cold charge back, and also + // add it to the returned gas. By adding it to the return, it will be charged + // outside of this function, as part of the dynamic gas, and that will make it + // also become correctly reported to tracers. + contract.Gas += coldCost + return gas + coldCost, nil + } +} + +var ( + gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall) + gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall) + gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall) + gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode) + gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) + // gasSelfdestructEIP3529 implements the changes in EIP-2539 (no refunds) + gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) + + // 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 + gasSStoreEIP2929 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP2200) + + // 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) +) + +// makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-2539 +func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { + gasFunc := func(evm *FakeEVM, 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/state/runtime/fakevm/stack.go b/state/runtime/fakevm/stack.go index f778f610d0..6c9e105206 100644 --- a/state/runtime/fakevm/stack.go +++ b/state/runtime/fakevm/stack.go @@ -17,18 +17,14 @@ package fakevm import ( - "fmt" "sync" "github.com/holiman/uint256" ) -// InitialStackSize represents the initial stack size. -const InitialStackSize int = 16 - var stackPool = sync.Pool{ New: func() interface{} { - return &Stack{data: make([]uint256.Int, 0, InitialStackSize)} + return &Stack{data: make([]uint256.Int, 0, 16)} }, } @@ -39,40 +35,48 @@ type Stack struct { data []uint256.Int } -// Newstack gets a stack from the pool. -func Newstack() *Stack { +func NewStack() *Stack { return stackPool.Get().(*Stack) } +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 } -// Push adds an item to the data. func (st *Stack) Push(d *uint256.Int) { // NOTE push limit (1024) is checked in baseCheck st.data = append(st.data, *d) } +func (st *Stack) pop() (ret uint256.Int) { + ret = st.data[len(st.data)-1] + st.data = st.data[:len(st.data)-1] + return +} + func (st *Stack) len() int { return len(st.data) } +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(n int) { + st.Push(&st.data[st.len()-n]) +} + +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) *uint256.Int { return &st.data[st.len()-n-1] } - -// Print dumps the content of the stack -func (st *Stack) Print() { - fmt.Println("### stack ###") - if len(st.data) > 0 { - for i, val := range st.data { - fmt.Printf("%-3d %s\n", i, val.String()) - } - } else { - fmt.Println("-- empty --") - } - fmt.Println("#############") -} diff --git a/state/runtime/fakevm/stack_table.go b/state/runtime/fakevm/stack_table.go new file mode 100644 index 0000000000..b5bf72a42a --- /dev/null +++ b/state/runtime/fakevm/stack_table.go @@ -0,0 +1,42 @@ +// 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 fakevm + +import ( + "github.com/ethereum/go-ethereum/params" +) + +func minSwapStack(n int) int { + return minStack(n, n) +} +func maxSwapStack(n int) int { + return maxStack(n, n) +} + +func minDupStack(n int) int { + return minStack(n, n+1) +} +func maxDupStack(n int) int { + return maxStack(n, n+1) +} + +func maxStack(pop, push int) int { + return int(params.StackLimit) + pop - push +} +func minStack(pops, push int) int { + return pops +} diff --git a/state/runtime/instrumentation/executortrace.go b/state/runtime/instrumentation/executortrace.go index 237ba044ed..8a45bbd30a 100644 --- a/state/runtime/instrumentation/executortrace.go +++ b/state/runtime/instrumentation/executortrace.go @@ -36,8 +36,8 @@ type Step struct { Contract Contract `json:"contract"` GasCost string `json:"gasCost"` Stack []string `json:"stack"` - Memory []string `json:"memory"` - ReturnData string `json:"returnData"` + Memory []byte `json:"memory"` + ReturnData []byte `json:"returnData"` } // Contract represents a contract in the trace. diff --git a/state/runtime/instrumentation/js/goja.go b/state/runtime/instrumentation/js/goja.go index 7b9853c22a..6e418eaf12 100644 --- a/state/runtime/instrumentation/js/goja.go +++ b/state/runtime/instrumentation/js/goja.go @@ -1,3 +1,19 @@ +// Copyright 2022 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 js import ( @@ -5,19 +21,20 @@ import ( "errors" "fmt" "math/big" - "time" - "github.com/0xPolygonHermez/zkevm-node/log" "github.com/0xPolygonHermez/zkevm-node/state/runtime/fakevm" jsassets "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation/js/internal/tracers" "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation/tracers" "github.com/dop251/goja" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" ) +const ( + memoryPadLimit = 1024 * 1024 +) + var assetTracers = make(map[string]string) // init retrieves the JavaScript transaction tracers included in go-ethereum. @@ -27,7 +44,16 @@ func init() { if err != nil { panic(err) } - tracers.RegisterLookup(true, NewJsTracer) + type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error) + lookup := func(code string) ctorFn { + return func(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + return NewJsTracer(code, ctx, cfg) + } + } + for name, code := range assetTracers { + tracers.DefaultDirectory.Register(name, lookup(code), true) + } + tracers.DefaultDirectory.RegisterJSEval(NewJsTracer) } // bigIntProgram is compiled once and the exported function mostly invoked to convert @@ -40,11 +66,7 @@ type fromBufFn = func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byt func toBuf(vm *goja.Runtime, bufType goja.Value, val []byte) (goja.Value, error) { // bufType is usually Uint8Array. This is equivalent to `new Uint8Array(val)` in JS. - res, err := vm.New(bufType, vm.ToValue(val)) - if err != nil { - return nil, err - } - return vm.ToValue(res), nil + return vm.New(bufType, vm.ToValue(vm.NewArrayBuffer(val))) } func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString bool) ([]byte, error) { @@ -55,6 +77,7 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b break } return common.FromHex(obj.String()), nil + case "Array": var b []byte if err := vm.ExportTo(buf, &b); err != nil { @@ -66,15 +89,14 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b if !obj.Get("constructor").SameAs(bufType) { break } - var b []byte - if err := vm.ExportTo(buf, &b); err != nil { - return nil, err - } + b := obj.Get("buffer").Export().(goja.ArrayBuffer).Bytes() return b, nil } return nil, fmt.Errorf("invalid buffer type") } +// jsTracer is an implementation of the Tracer interface which evaluates +// JS functions on the relevant EVM hooks. It uses Goja as its JS engine. type jsTracer struct { vm *goja.Runtime env *fakevm.FakeEVM @@ -108,11 +130,14 @@ type jsTracer struct { frameResultValue goja.Value } -// NewJsTracer is the JS tracer constructor. -func NewJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) { - if c, ok := assetTracers[code]; ok { - code = c - } +// NewJsTracer instantiates a new JS tracer instance. code is a +// Javascript snippet which evaluates to an expression returning +// an object with certain methods: +// +// The methods `result` and `fault` are required to be present. +// The methods `step`, `enter`, and `exit` are optional, but note that +// `enter` and `exit` always go together. +func NewJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { vm := goja.New() // By default field names are exported to JS as is, i.e. capitalized. vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) @@ -131,10 +156,7 @@ func NewJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) { } } - err := t.setTypeConverters() - if err != nil { - return nil, err - } + t.setTypeConverters() t.setBuiltinFunctions() ret, err := vm.RunString("(" + code + ")") if err != nil { @@ -164,6 +186,17 @@ func NewJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) { t.exit = exit t.result = result t.fault = fault + + // Pass in config + if setup, ok := goja.AssertFunction(obj.Get("setup")); ok { + cfgStr := "{}" + if cfg != nil { + cfgStr = string(cfg) + } + if _, err := setup(obj, vm.ToValue(cfgStr)); err != nil { + return nil, err + } + } // Setup objects carrying data to JS. These are created once and re-used. t.log = &steplog{ vm: vm, @@ -188,7 +221,9 @@ func (t *jsTracer) CaptureTxStart(gasLimit uint64) { // CaptureTxEnd implements the Tracer interface and is invoked at the end of // transaction processing. -func (t *jsTracer) CaptureTxEnd(restGas uint64) {} +func (t *jsTracer) CaptureTxEnd(restGas uint64) { + t.ctx["gasUsed"] = t.vm.ToValue(t.gasLimit - restGas) +} // CaptureStart implements the Tracer interface to initialize the tracing operation. func (t *jsTracer) CaptureStart(env *fakevm.FakeEVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { @@ -214,12 +249,11 @@ func (t *jsTracer) CaptureStart(env *fakevm.FakeEVM, from common.Address, to com t.ctx["block"] = t.vm.ToValue(env.Context.BlockNumber.Uint64()) // Update list of precompiles based on current block rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) - t.activePrecompiles = vm.ActivePrecompiles(rules) - t.ctx["intrinsicGas"] = t.vm.ToValue(t.gasLimit - gas) + t.activePrecompiles = fakevm.ActivePrecompiles(rules) } // CaptureState implements the Tracer interface to trace a single step of VM execution. -func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *fakevm.ScopeContext, rData []byte, depth int, err error) { +func (t *jsTracer) CaptureState(pc uint64, op fakevm.OpCode, gas, cost uint64, scope *fakevm.ScopeContext, rData []byte, depth int, err error) { if !t.traceStep { return } @@ -232,10 +266,11 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope log.memory.memory = scope.Memory log.stack.stack = scope.Stack log.contract.contract = scope.Contract - log.pc = uint(pc) - log.gas = uint(gas) - log.cost = uint(cost) - log.depth = uint(depth) + log.pc = pc + log.gas = gas + log.cost = cost + log.refund = t.env.StateDB.GetRefund() + log.depth = depth log.err = err if _, err := t.step(t.obj, t.logValue, t.dbValue); err != nil { t.onError("step", err) @@ -243,7 +278,7 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope } // CaptureFault implements the Tracer interface to trace an execution fault -func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *fakevm.ScopeContext, depth int, err error) { +func (t *jsTracer) CaptureFault(pc uint64, op fakevm.OpCode, gas, cost uint64, scope *fakevm.ScopeContext, depth int, err error) { if t.err != nil { return } @@ -255,17 +290,15 @@ func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope } // CaptureEnd is called after the call finishes to finalize the tracing. -func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, duration time.Duration, err error) { +func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { t.ctx["output"] = t.vm.ToValue(output) - t.ctx["time"] = t.vm.ToValue(duration.String()) - t.ctx["gasUsed"] = t.vm.ToValue(gasUsed) if err != nil { t.ctx["error"] = t.vm.ToValue(err.Error()) } } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *jsTracer) CaptureEnter(typ fakevm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { if !t.traceFrame { return } @@ -342,7 +375,7 @@ func wrapError(context string, err error) error { func (t *jsTracer) setBuiltinFunctions() { vm := t.vm // TODO: load console from goja-nodejs - err := vm.Set("toHex", func(v goja.Value) string { + vm.Set("toHex", func(v goja.Value) string { b, err := t.fromBuf(vm, v, false) if err != nil { vm.Interrupt(err) @@ -350,10 +383,7 @@ func (t *jsTracer) setBuiltinFunctions() { } return hexutil.Encode(b) }) - if err != nil { - log.Error(err) - } - err = vm.Set("toWord", func(v goja.Value) goja.Value { + vm.Set("toWord", func(v goja.Value) goja.Value { // TODO: add test with []byte len < 32 or > 32 b, err := t.fromBuf(vm, v, true) if err != nil { @@ -368,10 +398,7 @@ func (t *jsTracer) setBuiltinFunctions() { } return res }) - if err != nil { - log.Error(err) - } - err = vm.Set("toAddress", func(v goja.Value) goja.Value { + vm.Set("toAddress", func(v goja.Value) goja.Value { a, err := t.fromBuf(vm, v, true) if err != nil { vm.Interrupt(err) @@ -385,10 +412,7 @@ func (t *jsTracer) setBuiltinFunctions() { } return res }) - if err != nil { - log.Error(err) - } - err = vm.Set("toContract", func(from goja.Value, nonce uint) goja.Value { + vm.Set("toContract", func(from goja.Value, nonce uint) goja.Value { a, err := t.fromBuf(vm, from, true) if err != nil { vm.Interrupt(err) @@ -403,10 +427,7 @@ func (t *jsTracer) setBuiltinFunctions() { } return res }) - if err != nil { - log.Error(err) - } - err = vm.Set("toContract2", func(from goja.Value, salt string, initcode goja.Value) goja.Value { + vm.Set("toContract2", func(from goja.Value, salt string, initcode goja.Value) goja.Value { a, err := t.fromBuf(vm, from, true) if err != nil { vm.Interrupt(err) @@ -428,10 +449,7 @@ func (t *jsTracer) setBuiltinFunctions() { } return res }) - if err != nil { - log.Error(err) - } - err = vm.Set("isPrecompiled", func(v goja.Value) bool { + vm.Set("isPrecompiled", func(v goja.Value) bool { a, err := t.fromBuf(vm, v, true) if err != nil { vm.Interrupt(err) @@ -445,10 +463,7 @@ func (t *jsTracer) setBuiltinFunctions() { } return false }) - if err != nil { - log.Error(err) - } - err = vm.Set("slice", func(slice goja.Value, start, end int) goja.Value { + vm.Set("slice", func(slice goja.Value, start, end int) goja.Value { b, err := t.fromBuf(vm, slice, false) if err != nil { vm.Interrupt(err) @@ -465,9 +480,6 @@ func (t *jsTracer) setBuiltinFunctions() { } return res }) - if err != nil { - log.Error(err) - } } // setTypeConverters sets up utilities for converting Go types into those @@ -506,7 +518,7 @@ func (t *jsTracer) setTypeConverters() error { type opObj struct { vm *goja.Runtime - op vm.OpCode + op fakevm.OpCode } func (o *opObj) ToNumber() int { @@ -523,18 +535,9 @@ func (o *opObj) IsPush() bool { func (o *opObj) setupObject() *goja.Object { obj := o.vm.NewObject() - err := obj.Set("toNumber", o.vm.ToValue(o.ToNumber)) - if err != nil { - log.Error(err) - } - err = obj.Set("toString", o.vm.ToValue(o.ToString)) - if err != nil { - log.Error(err) - } - err = obj.Set("isPush", o.vm.ToValue(o.IsPush)) - if err != nil { - log.Error(err) - } + obj.Set("toNumber", o.vm.ToValue(o.ToNumber)) + obj.Set("toString", o.vm.ToValue(o.ToString)) + obj.Set("isPush", o.vm.ToValue(o.IsPush)) return obj } @@ -565,12 +568,17 @@ func (mo *memoryObj) slice(begin, end int64) ([]byte, error) { return []byte{}, nil } if end < begin || begin < 0 { - return nil, fmt.Errorf("Tracer accessed out of bound memory: offset %d, end %d", begin, end) + return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end) } - if mo.memory.Len() < int(end) { - return nil, fmt.Errorf("Tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), begin, end-begin) + mlen := mo.memory.Len() + if end-int64(mlen) > memoryPadLimit { + return nil, fmt.Errorf("tracer reached limit for padding memory slice: end %d, memorySize %d", end, mlen) } - return mo.memory.GetCopy(begin, end-begin), nil + slice := make([]byte, end-begin) + end = min(end, int64(mo.memory.Len())) + ptr := mo.memory.GetPtr(begin, end-begin) + copy(slice[:], ptr[:]) + return slice, nil } func (mo *memoryObj) GetUint(addr int64) goja.Value { @@ -590,29 +598,20 @@ func (mo *memoryObj) GetUint(addr int64) goja.Value { // getUint returns the 32 bytes at the specified address interpreted as a uint. func (mo *memoryObj) getUint(addr int64) (*big.Int, error) { if mo.memory.Len() < int(addr)+32 || addr < 0 { - return nil, fmt.Errorf("Tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), addr, fakevm.MemoryItemSize) + return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), addr, 32) } - return new(big.Int).SetBytes(mo.memory.GetPtr(addr, int64(fakevm.MemoryItemSize))), nil + return new(big.Int).SetBytes(mo.memory.GetPtr(addr, 32)), nil } func (mo *memoryObj) Length() int { return mo.memory.Len() } -func (mo *memoryObj) setupObject() *goja.Object { - o := mo.vm.NewObject() - err := o.Set("slice", mo.vm.ToValue(mo.Slice)) - if err != nil { - log.Error(err) - } - err = o.Set("getUint", mo.vm.ToValue(mo.GetUint)) - if err != nil { - log.Error(err) - } - err = o.Set("length", mo.vm.ToValue(mo.Length)) - if err != nil { - log.Error(err) - } +func (m *memoryObj) setupObject() *goja.Object { + o := m.vm.NewObject() + o.Set("slice", m.vm.ToValue(m.Slice)) + o.Set("getUint", m.vm.ToValue(m.GetUint)) + o.Set("length", m.vm.ToValue(m.Length)) return o } @@ -639,7 +638,7 @@ func (s *stackObj) Peek(idx int) goja.Value { // peek returns the nth-from-the-top element of the stack. func (s *stackObj) peek(idx int) (*big.Int, error) { if len(s.stack.Data()) <= idx || idx < 0 { - return nil, fmt.Errorf("Tracer accessed out of bound stack: size %d, index %d", len(s.stack.Data()), idx) + return nil, fmt.Errorf("tracer accessed out of bound stack: size %d, index %d", len(s.stack.Data()), idx) } return s.stack.Back(idx).ToBig(), nil } @@ -650,14 +649,8 @@ func (s *stackObj) Length() int { func (s *stackObj) setupObject() *goja.Object { o := s.vm.NewObject() - err := o.Set("peek", s.vm.ToValue(s.Peek)) - if err != nil { - log.Error(err) - } - err = o.Set("length", s.vm.ToValue(s.Length)) - if err != nil { - log.Error(err) - } + o.Set("peek", s.vm.ToValue(s.Peek)) + o.Set("length", s.vm.ToValue(s.Length)) return o } @@ -745,31 +738,16 @@ func (do *dbObj) Exists(addrSlice goja.Value) bool { func (do *dbObj) setupObject() *goja.Object { o := do.vm.NewObject() - err := o.Set("getBalance", do.vm.ToValue(do.GetBalance)) - if err != nil { - log.Error(err) - } - err = o.Set("getNonce", do.vm.ToValue(do.GetNonce)) - if err != nil { - log.Error(err) - } - err = o.Set("getCode", do.vm.ToValue(do.GetCode)) - if err != nil { - log.Error(err) - } - err = o.Set("getState", do.vm.ToValue(do.GetState)) - if err != nil { - log.Error(err) - } - err = o.Set("exists", do.vm.ToValue(do.Exists)) - if err != nil { - log.Error(err) - } + o.Set("getBalance", do.vm.ToValue(do.GetBalance)) + o.Set("getNonce", do.vm.ToValue(do.GetNonce)) + o.Set("getCode", do.vm.ToValue(do.GetCode)) + o.Set("getState", do.vm.ToValue(do.GetState)) + o.Set("exists", do.vm.ToValue(do.Exists)) return o } type contractObj struct { - contract *vm.Contract + contract *fakevm.Contract vm *goja.Runtime toBig toBigFn toBuf toBufFn @@ -806,7 +784,7 @@ func (co *contractObj) GetValue() goja.Value { } func (co *contractObj) GetInput() goja.Value { - input := co.contract.Input + input := common.CopyBytes(co.contract.Input) res, err := co.toBuf(co.vm, input) if err != nil { co.vm.Interrupt(err) @@ -815,24 +793,12 @@ func (co *contractObj) GetInput() goja.Value { return res } -func (co *contractObj) setupObject() *goja.Object { - o := co.vm.NewObject() - err := o.Set("getCaller", co.vm.ToValue(co.GetCaller)) - if err != nil { - log.Error(err) - } - err = o.Set("getAddress", co.vm.ToValue(co.GetAddress)) - if err != nil { - log.Error(err) - } - err = o.Set("getValue", co.vm.ToValue(co.GetValue)) - if err != nil { - log.Error(err) - } - err = o.Set("getInput", co.vm.ToValue(co.GetInput)) - if err != nil { - log.Error(err) - } +func (c *contractObj) setupObject() *goja.Object { + o := c.vm.NewObject() + o.Set("getCaller", c.vm.ToValue(c.GetCaller)) + o.Set("getAddress", c.vm.ToValue(c.GetAddress)) + o.Set("getValue", c.vm.ToValue(c.GetValue)) + o.Set("getInput", c.vm.ToValue(c.GetInput)) return o } @@ -901,30 +867,12 @@ func (f *callframe) GetValue() goja.Value { func (f *callframe) setupObject() *goja.Object { o := f.vm.NewObject() - err := o.Set("getType", f.vm.ToValue(f.GetType)) - if err != nil { - log.Error(err) - } - err = o.Set("getFrom", f.vm.ToValue(f.GetFrom)) - if err != nil { - log.Error(err) - } - err = o.Set("getTo", f.vm.ToValue(f.GetTo)) - if err != nil { - log.Error(err) - } - err = o.Set("getInput", f.vm.ToValue(f.GetInput)) - if err != nil { - log.Error(err) - } - err = o.Set("getGas", f.vm.ToValue(f.GetGas)) - if err != nil { - log.Error(err) - } - err = o.Set("getValue", f.vm.ToValue(f.GetValue)) - if err != nil { - log.Error(err) - } + o.Set("getType", f.vm.ToValue(f.GetType)) + o.Set("getFrom", f.vm.ToValue(f.GetFrom)) + o.Set("getTo", f.vm.ToValue(f.GetTo)) + o.Set("getInput", f.vm.ToValue(f.GetInput)) + o.Set("getGas", f.vm.ToValue(f.GetGas)) + o.Set("getValue", f.vm.ToValue(f.GetValue)) return o } @@ -959,18 +907,9 @@ func (r *callframeResult) GetError() goja.Value { func (r *callframeResult) setupObject() *goja.Object { o := r.vm.NewObject() - err := o.Set("getGasUsed", r.vm.ToValue(r.GetGasUsed)) - if err != nil { - log.Error(err) - } - err = o.Set("getOutput", r.vm.ToValue(r.GetOutput)) - if err != nil { - log.Error(err) - } - err = o.Set("getError", r.vm.ToValue(r.GetError)) - if err != nil { - log.Error(err) - } + o.Set("getGasUsed", r.vm.ToValue(r.GetGasUsed)) + o.Set("getOutput", r.vm.ToValue(r.GetOutput)) + o.Set("getError", r.vm.ToValue(r.GetError)) return o } @@ -982,33 +921,19 @@ type steplog struct { stack *stackObj contract *contractObj - pc uint - gas uint - cost uint - depth uint - refund uint + pc uint64 + gas uint64 + cost uint64 + depth int + refund uint64 err error } -func (l *steplog) GetPC() uint { - return l.pc -} - -func (l *steplog) GetGas() uint { - return l.gas -} - -func (l *steplog) GetCost() uint { - return l.cost -} - -func (l *steplog) GetDepth() uint { - return l.depth -} - -func (l *steplog) GetRefund() uint { - return l.refund -} +func (l *steplog) GetPC() uint64 { return l.pc } +func (l *steplog) GetGas() uint64 { return l.gas } +func (l *steplog) GetCost() uint64 { return l.cost } +func (l *steplog) GetDepth() int { return l.depth } +func (l *steplog) GetRefund() uint64 { return l.refund } func (l *steplog) GetError() goja.Value { if l.err != nil { @@ -1020,46 +945,23 @@ func (l *steplog) GetError() goja.Value { func (l *steplog) setupObject() *goja.Object { o := l.vm.NewObject() // Setup basic fields. - err := o.Set("getPC", l.vm.ToValue(l.GetPC)) - if err != nil { - log.Error(err) - } - err = o.Set("getGas", l.vm.ToValue(l.GetGas)) - if err != nil { - log.Error(err) - } - err = o.Set("getCost", l.vm.ToValue(l.GetCost)) - if err != nil { - log.Error(err) - } - err = o.Set("getDepth", l.vm.ToValue(l.GetDepth)) - if err != nil { - log.Error(err) - } - err = o.Set("getRefund", l.vm.ToValue(l.GetRefund)) - if err != nil { - log.Error(err) - } - err = o.Set("getError", l.vm.ToValue(l.GetError)) - if err != nil { - log.Error(err) - } + o.Set("getPC", l.vm.ToValue(l.GetPC)) + o.Set("getGas", l.vm.ToValue(l.GetGas)) + o.Set("getCost", l.vm.ToValue(l.GetCost)) + o.Set("getDepth", l.vm.ToValue(l.GetDepth)) + o.Set("getRefund", l.vm.ToValue(l.GetRefund)) + o.Set("getError", l.vm.ToValue(l.GetError)) // Setup nested objects. - err = o.Set("op", l.op.setupObject()) - if err != nil { - log.Error(err) - } - err = o.Set("stack", l.stack.setupObject()) - if err != nil { - log.Error(err) - } - err = o.Set("memory", l.memory.setupObject()) - if err != nil { - log.Error(err) - } - err = o.Set("contract", l.contract.setupObject()) - if err != nil { - log.Error(err) - } + o.Set("op", l.op.setupObject()) + o.Set("stack", l.stack.setupObject()) + o.Set("memory", l.memory.setupObject()) + o.Set("contract", l.contract.setupObject()) return o } + +func min(a, b int64) int64 { + if a < b { + return a + } + return b +} diff --git a/state/runtime/instrumentation/tracers/native/4byte.go b/state/runtime/instrumentation/tracers/native/4byte.go new file mode 100644 index 0000000000..524f26f7fc --- /dev/null +++ b/state/runtime/instrumentation/tracers/native/4byte.go @@ -0,0 +1,132 @@ +// 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 native + +import ( + "encoding/json" + "math/big" + "strconv" + "sync/atomic" + + "github.com/0xPolygonHermez/zkevm-node/state/runtime/fakevm" + "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation/tracers" + "github.com/ethereum/go-ethereum/common" +) + +func init() { + tracers.DefaultDirectory.Register("4byteTracer", NewFourByteTracer, false) +} + +// fourByteTracer searches for 4byte-identifiers, and collects them for post-processing. +// It collects the methods identifiers along with the size of the supplied data, so +// a reversed signature can be matched against the size of the data. +// +// Example: +// +// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"}) +// { +// 0x27dc297e-128: 1, +// 0x38cc4831-0: 2, +// 0x524f3889-96: 1, +// 0xadf59f99-288: 1, +// 0xc281d19e-0: 1 +// } +type fourByteTracer struct { + noopTracer + ids map[string]int // ids aggregates the 4byte ids found + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption + activePrecompiles []common.Address // Updated on CaptureStart based on given rules +} + +// NewFourByteTracer returns a native go tracer which collects +// 4 byte-identifiers of a tx, and implements vm.EVMLogger. +func NewFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { + t := &fourByteTracer{ + ids: make(map[string]int), + } + return t, nil +} + +// isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go +func (t *fourByteTracer) isPrecompiled(addr common.Address) bool { + for _, p := range t.activePrecompiles { + if p == addr { + return true + } + } + return false +} + +// store saves the given identifier and datasize. +func (t *fourByteTracer) store(id []byte, size int) { + key := bytesToHex(id) + "-" + strconv.Itoa(size) + t.ids[key] += 1 +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *fourByteTracer) CaptureStart(env *fakevm.FakeEVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + // Update list of precompiles based on current block + rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) + t.activePrecompiles = fakevm.ActivePrecompiles(rules) + + // Save the outer calldata also + if len(input) >= 4 { + t.store(input[0:4], len(input)-4) + } +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *fourByteTracer) CaptureEnter(op fakevm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + // Skip if tracing was interrupted + if atomic.LoadUint32(&t.interrupt) > 0 { + return + } + if len(input) < 4 { + return + } + // primarily we want to avoid CREATE/CREATE2/SELFDESTRUCT + if op != fakevm.DELEGATECALL && op != fakevm.STATICCALL && + op != fakevm.CALL && op != fakevm.CALLCODE { + return + } + // Skip any pre-compile invocations, those are just fancy opcodes + if t.isPrecompiled(to) { + return + } + t.store(input[0:4], len(input)-4) +} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *fourByteTracer) GetResult() (json.RawMessage, error) { + res, err := json.Marshal(t.ids) + if err != nil { + return nil, err + } + return res, t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *fourByteTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} + +func bytesToHex(s []byte) string { + return "0x" + common.Bytes2Hex(s) +} diff --git a/state/runtime/instrumentation/tracers/native/call.go b/state/runtime/instrumentation/tracers/native/call.go new file mode 100644 index 0000000000..a4975ed114 --- /dev/null +++ b/state/runtime/instrumentation/tracers/native/call.go @@ -0,0 +1,270 @@ +// 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 native + +import ( + "encoding/json" + "errors" + "math/big" + "sync/atomic" + + "github.com/0xPolygonHermez/zkevm-node/state/runtime/fakevm" + "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation/tracers" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go + +func init() { + tracers.DefaultDirectory.Register("callTracer", NewCallTracer, false) +} + +type callLog struct { + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data hexutil.Bytes `json:"data"` +} + +type callFrame struct { + Type fakevm.OpCode `json:"-"` + From common.Address `json:"from"` + Gas uint64 `json:"gas"` + GasUsed uint64 `json:"gasUsed"` + To *common.Address `json:"to,omitempty" rlp:"optional"` + Input []byte `json:"input" rlp:"optional"` + Output []byte `json:"output,omitempty" rlp:"optional"` + Error string `json:"error,omitempty" rlp:"optional"` + RevertReason string `json:"revertReason,omitempty"` + Calls []callFrame `json:"calls,omitempty" rlp:"optional"` + Logs []callLog `json:"logs,omitempty" rlp:"optional"` + // Placed at end on purpose. The RLP will be decoded to 0 instead of + // nil if there are non-empty elements after in the struct. + Value *big.Int `json:"value,omitempty" rlp:"optional"` +} + +func (f callFrame) TypeString() string { + return f.Type.String() +} + +func (f callFrame) failed() bool { + return len(f.Error) > 0 +} + +func (f *callFrame) processOutput(output []byte, err error) { + output = common.CopyBytes(output) + if err == nil { + f.Output = output + return + } + f.Error = err.Error() + if f.Type == fakevm.CREATE || f.Type == fakevm.CREATE2 { + f.To = nil + } + if !errors.Is(err, fakevm.ErrExecutionReverted) || len(output) == 0 { + return + } + f.Output = output + if len(output) < 4 { + return + } + if unpacked, err := abi.UnpackRevert(output); err == nil { + f.RevertReason = unpacked + } +} + +type callFrameMarshaling struct { + TypeString string `json:"type"` + Gas hexutil.Uint64 + GasUsed hexutil.Uint64 + Value *hexutil.Big + Input hexutil.Bytes + Output hexutil.Bytes +} + +type callTracer struct { + noopTracer + callstack []callFrame + config callTracerConfig + gasLimit uint64 + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption +} + +type callTracerConfig struct { + OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls + WithLog bool `json:"withLog"` // If true, call tracer will collect event logs +} + +// NewCallTracer returns a native go tracer which tracks +// call frames of a tx, and implements fakevm.EVMLogger. +func NewCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + var config callTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + // First callframe contains tx context info + // and is populated on start and end. + return &callTracer{callstack: make([]callFrame, 1), config: config}, nil +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *callTracer) CaptureStart(env *fakevm.FakeEVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + toCopy := to + t.callstack[0] = callFrame{ + Type: fakevm.CALL, + From: from, + To: &toCopy, + Input: common.CopyBytes(input), + Gas: gas, + Value: value, + } + if create { + t.callstack[0].Type = fakevm.CREATE + } +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { + t.callstack[0].processOutput(output, err) +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *callTracer) CaptureState(pc uint64, op fakevm.OpCode, gas, cost uint64, scope *fakevm.ScopeContext, rData []byte, depth int, err error) { + // Only logs need to be captured via opcode processing + if !t.config.WithLog { + return + } + // Avoid processing nested calls when only caring about top call + if t.config.OnlyTopCall && depth > 0 { + return + } + // Skip if tracing was interrupted + if atomic.LoadUint32(&t.interrupt) > 0 { + return + } + switch op { + case fakevm.LOG0, fakevm.LOG1, fakevm.LOG2, fakevm.LOG3, fakevm.LOG4: + size := int(op - fakevm.LOG0) + + stack := scope.Stack + stackData := stack.Data() + + // Don't modify the stack + mStart := stackData[len(stackData)-1] + mSize := stackData[len(stackData)-2] + topics := make([]common.Hash, size) + for i := 0; i < size; i++ { + topic := stackData[len(stackData)-2-(i+1)] + topics[i] = common.Hash(topic.Bytes32()) + } + + data := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) + log := callLog{Address: scope.Contract.Address(), Topics: topics, Data: hexutil.Bytes(data)} + t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, log) + } +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *callTracer) CaptureEnter(typ fakevm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if t.config.OnlyTopCall { + return + } + // Skip if tracing was interrupted + if atomic.LoadUint32(&t.interrupt) > 0 { + return + } + + toCopy := to + call := callFrame{ + Type: typ, + From: from, + To: &toCopy, + Input: common.CopyBytes(input), + Gas: gas, + Value: value, + } + t.callstack = append(t.callstack, call) +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { + if t.config.OnlyTopCall { + return + } + size := len(t.callstack) + if size <= 1 { + return + } + // pop call + call := t.callstack[size-1] + t.callstack = t.callstack[:size-1] + size -= 1 + + call.GasUsed = gasUsed + call.processOutput(output, err) + t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) +} + +func (t *callTracer) CaptureTxStart(gasLimit uint64) { + t.gasLimit = gasLimit +} + +func (t *callTracer) CaptureTxEnd(restGas uint64) { + t.callstack[0].GasUsed = t.gasLimit - restGas + if t.config.WithLog { + // Logs are not emitted when the call fails + clearFailedLogs(&t.callstack[0], false) + } +} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *callTracer) GetResult() (json.RawMessage, error) { + if len(t.callstack) != 1 { + return nil, errors.New("incorrect number of top-level calls") + } + + res, err := json.Marshal(t.callstack[0]) + if err != nil { + return nil, err + } + return json.RawMessage(res), t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *callTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} + +// clearFailedLogs clears the logs of a callframe and all its children +// in case of execution failure. +func clearFailedLogs(cf *callFrame, parentFailed bool) { + failed := cf.failed() || parentFailed + // Clear own logs + if failed { + cf.Logs = nil + } + for i := range cf.Calls { + clearFailedLogs(&cf.Calls[i], failed) + } +} diff --git a/state/runtime/instrumentation/tracers/native/call_flat.go b/state/runtime/instrumentation/tracers/native/call_flat.go new file mode 100644 index 0000000000..c91077f9de --- /dev/null +++ b/state/runtime/instrumentation/tracers/native/call_flat.go @@ -0,0 +1,379 @@ +// Copyright 2022 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 native + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "strings" + + "github.com/0xPolygonHermez/zkevm-node/state/runtime/fakevm" + "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation/tracers" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +//go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go +//go:generate go run github.com/fjl/gencodec -type flatCallResult -field-override flatCallResultMarshaling -out gen_flatcallresult_json.go + +func init() { + tracers.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false) +} + +var parityErrorMapping = map[string]string{ + "contract creation code storage out of gas": "Out of gas", + "out of gas": "Out of gas", + "gas uint64 overflow": "Out of gas", + "max code size exceeded": "Out of gas", + "invalid jump destination": "Bad jump destination", + "execution reverted": "Reverted", + "return data out of bounds": "Out of bounds", + "stack limit reached 1024 (1023)": "Out of stack", + "precompiled failed": "Built-in failed", + "invalid input length": "Built-in failed", +} + +var parityErrorMappingStartingWith = map[string]string{ + "invalid opcode:": "Bad instruction", + "stack underflow": "Stack underflow", +} + +// flatCallFrame is a standalone callframe. +type flatCallFrame struct { + Action flatCallAction `json:"action"` + BlockHash *common.Hash `json:"blockHash"` + BlockNumber uint64 `json:"blockNumber"` + Error string `json:"error,omitempty"` + Result *flatCallResult `json:"result,omitempty"` + Subtraces int `json:"subtraces"` + TraceAddress []int `json:"traceAddress"` + TransactionHash *common.Hash `json:"transactionHash"` + TransactionPosition uint64 `json:"transactionPosition"` + Type string `json:"type"` +} + +type flatCallAction struct { + Author *common.Address `json:"author,omitempty"` + RewardType string `json:"rewardType,omitempty"` + SelfDestructed *common.Address `json:"address,omitempty"` + Balance *big.Int `json:"balance,omitempty"` + CallType string `json:"callType,omitempty"` + CreationMethod string `json:"creationMethod,omitempty"` + From *common.Address `json:"from,omitempty"` + Gas *uint64 `json:"gas,omitempty"` + Init *[]byte `json:"init,omitempty"` + Input *[]byte `json:"input,omitempty"` + RefundAddress *common.Address `json:"refundAddress,omitempty"` + To *common.Address `json:"to,omitempty"` + Value *big.Int `json:"value,omitempty"` +} + +type flatCallActionMarshaling struct { + Balance *hexutil.Big + Gas *hexutil.Uint64 + Init *hexutil.Bytes + Input *hexutil.Bytes + Value *hexutil.Big +} + +type flatCallResult struct { + Address *common.Address `json:"address,omitempty"` + Code *[]byte `json:"code,omitempty"` + GasUsed *uint64 `json:"gasUsed,omitempty"` + Output *[]byte `json:"output,omitempty"` +} + +type flatCallResultMarshaling struct { + Code *hexutil.Bytes + GasUsed *hexutil.Uint64 + Output *hexutil.Bytes +} + +// flatCallTracer reports call frame information of a tx in a flat format, i.e. +// as opposed to the nested format of `callTracer`. +type flatCallTracer struct { + tracer *callTracer + config flatCallTracerConfig + ctx *tracers.Context // Holds tracer context data + reason error // Textual reason for the interruption + activePrecompiles []common.Address // Updated on CaptureStart based on given rules +} + +type flatCallTracerConfig struct { + ConvertParityErrors bool `json:"convertParityErrors"` // If true, call tracer converts errors to parity format + IncludePrecompiles bool `json:"includePrecompiles"` // If true, call tracer includes calls to precompiled contracts +} + +// newFlatCallTracer returns a new flatCallTracer. +func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + var config flatCallTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + + tracer, err := tracers.DefaultDirectory.New("callTracer", ctx, cfg) + if err != nil { + return nil, err + } + t, ok := tracer.(*callTracer) + if !ok { + return nil, errors.New("internal error: embedded tracer has wrong type") + } + + return &flatCallTracer{tracer: t, ctx: ctx, config: config}, nil +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *flatCallTracer) CaptureStart(env *fakevm.FakeEVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + t.tracer.CaptureStart(env, from, to, create, input, gas, value) + // Update list of precompiles based on current block + rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) + t.activePrecompiles = fakevm.ActivePrecompiles(rules) +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *flatCallTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { + t.tracer.CaptureEnd(output, gasUsed, err) +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *flatCallTracer) CaptureState(pc uint64, op fakevm.OpCode, gas, cost uint64, scope *fakevm.ScopeContext, rData []byte, depth int, err error) { + t.tracer.CaptureState(pc, op, gas, cost, scope, rData, depth, err) +} + +// CaptureFault implements the EVMLogger interface to trace an execution fault. +func (t *flatCallTracer) CaptureFault(pc uint64, op fakevm.OpCode, gas, cost uint64, scope *fakevm.ScopeContext, depth int, err error) { + t.tracer.CaptureFault(pc, op, gas, cost, scope, depth, err) +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *flatCallTracer) CaptureEnter(typ fakevm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + t.tracer.CaptureEnter(typ, from, to, input, gas, value) + + // Child calls must have a value, even if it's zero. + // Practically speaking, only STATICCALL has nil value. Set it to zero. + if t.tracer.callstack[len(t.tracer.callstack)-1].Value == nil && value == nil { + t.tracer.callstack[len(t.tracer.callstack)-1].Value = big.NewInt(0) + } +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { + t.tracer.CaptureExit(output, gasUsed, err) + + // Parity traces don't include CALL/STATICCALLs to precompiles. + // By default we remove them from the callstack. + if t.config.IncludePrecompiles { + return + } + var ( + // call has been nested in parent + parent = t.tracer.callstack[len(t.tracer.callstack)-1] + call = parent.Calls[len(parent.Calls)-1] + typ = call.Type + to = call.To + ) + if typ == fakevm.CALL || typ == fakevm.STATICCALL { + if t.isPrecompiled(*to) { + t.tracer.callstack[len(t.tracer.callstack)-1].Calls = parent.Calls[:len(parent.Calls)-1] + } + } +} + +func (t *flatCallTracer) CaptureTxStart(gasLimit uint64) { + t.tracer.CaptureTxStart(gasLimit) +} + +func (t *flatCallTracer) CaptureTxEnd(restGas uint64) { + t.tracer.CaptureTxEnd(restGas) +} + +// GetResult returns an empty json object. +func (t *flatCallTracer) GetResult() (json.RawMessage, error) { + if len(t.tracer.callstack) < 1 { + return nil, errors.New("invalid number of calls") + } + + flat, err := flatFromNested(&t.tracer.callstack[0], []int{}, t.config.ConvertParityErrors, t.ctx) + if err != nil { + return nil, err + } + + res, err := json.Marshal(flat) + if err != nil { + return nil, err + } + return res, t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *flatCallTracer) Stop(err error) { + t.tracer.Stop(err) +} + +// isPrecompiled returns whether the addr is a precompile. +func (t *flatCallTracer) isPrecompiled(addr common.Address) bool { + for _, p := range t.activePrecompiles { + if p == addr { + return true + } + } + return false +} + +func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) { + var frame *flatCallFrame + switch input.Type { + case fakevm.CREATE, fakevm.CREATE2: + frame = newFlatCreate(input) + case fakevm.SELFDESTRUCT: + frame = newFlatSuicide(input) + case fakevm.CALL, fakevm.STATICCALL, fakevm.CALLCODE, fakevm.DELEGATECALL: + frame = newFlatCall(input) + default: + return nil, fmt.Errorf("unrecognized call frame type: %s", input.Type) + } + + frame.TraceAddress = traceAddress + frame.Error = input.Error + frame.Subtraces = len(input.Calls) + fillCallFrameFromContext(frame, ctx) + if convertErrs { + convertErrorToParity(frame) + } + + // Revert output contains useful information (revert reason). + // Otherwise discard result. + if input.Error != "" && input.Error != fakevm.ErrExecutionReverted.Error() { + frame.Result = nil + } + + output = append(output, *frame) + if len(input.Calls) > 0 { + for i, childCall := range input.Calls { + childAddr := childTraceAddress(traceAddress, i) + childCallCopy := childCall + flat, err := flatFromNested(&childCallCopy, childAddr, convertErrs, ctx) + if err != nil { + return nil, err + } + output = append(output, flat...) + } + } + + return output, nil +} + +func newFlatCreate(input *callFrame) *flatCallFrame { + var ( + actionInit = input.Input[:] + resultCode = input.Output[:] + ) + + return &flatCallFrame{ + Type: strings.ToLower(fakevm.CREATE.String()), + Action: flatCallAction{ + From: &input.From, + Gas: &input.Gas, + Value: input.Value, + Init: &actionInit, + }, + Result: &flatCallResult{ + GasUsed: &input.GasUsed, + Address: input.To, + Code: &resultCode, + }, + } +} + +func newFlatCall(input *callFrame) *flatCallFrame { + var ( + actionInput = input.Input[:] + resultOutput = input.Output[:] + ) + + return &flatCallFrame{ + Type: strings.ToLower(fakevm.CALL.String()), + Action: flatCallAction{ + From: &input.From, + To: input.To, + Gas: &input.Gas, + Value: input.Value, + CallType: strings.ToLower(input.Type.String()), + Input: &actionInput, + }, + Result: &flatCallResult{ + GasUsed: &input.GasUsed, + Output: &resultOutput, + }, + } +} + +func newFlatSuicide(input *callFrame) *flatCallFrame { + return &flatCallFrame{ + Type: "suicide", + Action: flatCallAction{ + SelfDestructed: &input.From, + Balance: input.Value, + RefundAddress: input.To, + }, + } +} + +func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *tracers.Context) { + if ctx == nil { + return + } + if ctx.BlockHash != (common.Hash{}) { + callFrame.BlockHash = &ctx.BlockHash + } + if ctx.BlockNumber != nil { + callFrame.BlockNumber = ctx.BlockNumber.Uint64() + } + if ctx.TxHash != (common.Hash{}) { + callFrame.TransactionHash = &ctx.TxHash + } + callFrame.TransactionPosition = uint64(ctx.TxIndex) +} + +func convertErrorToParity(call *flatCallFrame) { + if call.Error == "" { + return + } + + if parityError, ok := parityErrorMapping[call.Error]; ok { + call.Error = parityError + } else { + for gethError, parityError := range parityErrorMappingStartingWith { + if strings.HasPrefix(call.Error, gethError) { + call.Error = parityError + } + } + } +} + +func childTraceAddress(a []int, i int) []int { + child := make([]int, 0, len(a)+1) + child = append(child, a...) + child = append(child, i) + return child +} diff --git a/state/runtime/instrumentation/tracers/native/gen_account_json.go b/state/runtime/instrumentation/tracers/native/gen_account_json.go new file mode 100644 index 0000000000..4c39cbc38c --- /dev/null +++ b/state/runtime/instrumentation/tracers/native/gen_account_json.go @@ -0,0 +1,56 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*accountMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (a account) MarshalJSON() ([]byte, error) { + type account struct { + Balance *hexutil.Big `json:"balance,omitempty"` + Code hexutil.Bytes `json:"code,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + } + var enc account + enc.Balance = (*hexutil.Big)(a.Balance) + enc.Code = a.Code + enc.Nonce = a.Nonce + enc.Storage = a.Storage + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (a *account) UnmarshalJSON(input []byte) error { + type account struct { + Balance *hexutil.Big `json:"balance,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + Nonce *uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + } + var dec account + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Balance != nil { + a.Balance = (*big.Int)(dec.Balance) + } + if dec.Code != nil { + a.Code = *dec.Code + } + if dec.Nonce != nil { + a.Nonce = *dec.Nonce + } + if dec.Storage != nil { + a.Storage = dec.Storage + } + return nil +} diff --git a/state/runtime/instrumentation/tracers/native/gen_callframe_json.go b/state/runtime/instrumentation/tracers/native/gen_callframe_json.go new file mode 100644 index 0000000000..b17cb22dd8 --- /dev/null +++ b/state/runtime/instrumentation/tracers/native/gen_callframe_json.go @@ -0,0 +1,107 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/0xPolygonHermez/zkevm-node/state/runtime/fakevm" +) + +var _ = (*callFrameMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (c callFrame) MarshalJSON() ([]byte, error) { + type callFrame0 struct { + Type fakevm.OpCode `json:"-"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + To *common.Address `json:"to,omitempty" rlp:"optional"` + Input hexutil.Bytes `json:"input" rlp:"optional"` + Output hexutil.Bytes `json:"output,omitempty" rlp:"optional"` + Error string `json:"error,omitempty" rlp:"optional"` + RevertReason string `json:"revertReason,omitempty"` + Calls []callFrame `json:"calls,omitempty" rlp:"optional"` + Logs []callLog `json:"logs,omitempty" rlp:"optional"` + Value *hexutil.Big `json:"value,omitempty" rlp:"optional"` + TypeString string `json:"type"` + } + var enc callFrame0 + enc.Type = c.Type + enc.From = c.From + enc.Gas = hexutil.Uint64(c.Gas) + enc.GasUsed = hexutil.Uint64(c.GasUsed) + enc.To = c.To + enc.Input = c.Input + enc.Output = c.Output + enc.Error = c.Error + enc.RevertReason = c.RevertReason + enc.Calls = c.Calls + enc.Logs = c.Logs + enc.Value = (*hexutil.Big)(c.Value) + enc.TypeString = c.TypeString() + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (c *callFrame) UnmarshalJSON(input []byte) error { + type callFrame0 struct { + Type *fakevm.OpCode `json:"-"` + From *common.Address `json:"from"` + Gas *hexutil.Uint64 `json:"gas"` + GasUsed *hexutil.Uint64 `json:"gasUsed"` + To *common.Address `json:"to,omitempty" rlp:"optional"` + Input *hexutil.Bytes `json:"input" rlp:"optional"` + Output *hexutil.Bytes `json:"output,omitempty" rlp:"optional"` + Error *string `json:"error,omitempty" rlp:"optional"` + RevertReason *string `json:"revertReason,omitempty"` + Calls []callFrame `json:"calls,omitempty" rlp:"optional"` + Logs []callLog `json:"logs,omitempty" rlp:"optional"` + Value *hexutil.Big `json:"value,omitempty" rlp:"optional"` + } + var dec callFrame0 + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Type != nil { + c.Type = *dec.Type + } + if dec.From != nil { + c.From = *dec.From + } + if dec.Gas != nil { + c.Gas = uint64(*dec.Gas) + } + if dec.GasUsed != nil { + c.GasUsed = uint64(*dec.GasUsed) + } + if dec.To != nil { + c.To = dec.To + } + if dec.Input != nil { + c.Input = *dec.Input + } + if dec.Output != nil { + c.Output = *dec.Output + } + if dec.Error != nil { + c.Error = *dec.Error + } + if dec.RevertReason != nil { + c.RevertReason = *dec.RevertReason + } + if dec.Calls != nil { + c.Calls = dec.Calls + } + if dec.Logs != nil { + c.Logs = dec.Logs + } + if dec.Value != nil { + c.Value = (*big.Int)(dec.Value) + } + return nil +} diff --git a/state/runtime/instrumentation/tracers/native/gen_flatcallaction_json.go b/state/runtime/instrumentation/tracers/native/gen_flatcallaction_json.go new file mode 100644 index 0000000000..c075606983 --- /dev/null +++ b/state/runtime/instrumentation/tracers/native/gen_flatcallaction_json.go @@ -0,0 +1,110 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*flatCallActionMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (f flatCallAction) MarshalJSON() ([]byte, error) { + type flatCallAction struct { + Author *common.Address `json:"author,omitempty"` + RewardType string `json:"rewardType,omitempty"` + SelfDestructed *common.Address `json:"address,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + CallType string `json:"callType,omitempty"` + CreationMethod string `json:"creationMethod,omitempty"` + From *common.Address `json:"from,omitempty"` + Gas *hexutil.Uint64 `json:"gas,omitempty"` + Init *hexutil.Bytes `json:"init,omitempty"` + Input *hexutil.Bytes `json:"input,omitempty"` + RefundAddress *common.Address `json:"refundAddress,omitempty"` + To *common.Address `json:"to,omitempty"` + Value *hexutil.Big `json:"value,omitempty"` + } + var enc flatCallAction + enc.Author = f.Author + enc.RewardType = f.RewardType + enc.SelfDestructed = f.SelfDestructed + enc.Balance = (*hexutil.Big)(f.Balance) + enc.CallType = f.CallType + enc.CreationMethod = f.CreationMethod + enc.From = f.From + enc.Gas = (*hexutil.Uint64)(f.Gas) + enc.Init = (*hexutil.Bytes)(f.Init) + enc.Input = (*hexutil.Bytes)(f.Input) + enc.RefundAddress = f.RefundAddress + enc.To = f.To + enc.Value = (*hexutil.Big)(f.Value) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (f *flatCallAction) UnmarshalJSON(input []byte) error { + type flatCallAction struct { + Author *common.Address `json:"author,omitempty"` + RewardType *string `json:"rewardType,omitempty"` + SelfDestructed *common.Address `json:"address,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + CallType *string `json:"callType,omitempty"` + CreationMethod *string `json:"creationMethod,omitempty"` + From *common.Address `json:"from,omitempty"` + Gas *hexutil.Uint64 `json:"gas,omitempty"` + Init *hexutil.Bytes `json:"init,omitempty"` + Input *hexutil.Bytes `json:"input,omitempty"` + RefundAddress *common.Address `json:"refundAddress,omitempty"` + To *common.Address `json:"to,omitempty"` + Value *hexutil.Big `json:"value,omitempty"` + } + var dec flatCallAction + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Author != nil { + f.Author = dec.Author + } + if dec.RewardType != nil { + f.RewardType = *dec.RewardType + } + if dec.SelfDestructed != nil { + f.SelfDestructed = dec.SelfDestructed + } + if dec.Balance != nil { + f.Balance = (*big.Int)(dec.Balance) + } + if dec.CallType != nil { + f.CallType = *dec.CallType + } + if dec.CreationMethod != nil { + f.CreationMethod = *dec.CreationMethod + } + if dec.From != nil { + f.From = dec.From + } + if dec.Gas != nil { + f.Gas = (*uint64)(dec.Gas) + } + if dec.Init != nil { + f.Init = (*[]byte)(dec.Init) + } + if dec.Input != nil { + f.Input = (*[]byte)(dec.Input) + } + if dec.RefundAddress != nil { + f.RefundAddress = dec.RefundAddress + } + if dec.To != nil { + f.To = dec.To + } + if dec.Value != nil { + f.Value = (*big.Int)(dec.Value) + } + return nil +} diff --git a/state/runtime/instrumentation/tracers/native/gen_flatcallresult_json.go b/state/runtime/instrumentation/tracers/native/gen_flatcallresult_json.go new file mode 100644 index 0000000000..e9fa5e44da --- /dev/null +++ b/state/runtime/instrumentation/tracers/native/gen_flatcallresult_json.go @@ -0,0 +1,55 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*flatCallResultMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (f flatCallResult) MarshalJSON() ([]byte, error) { + type flatCallResult struct { + Address *common.Address `json:"address,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` + Output *hexutil.Bytes `json:"output,omitempty"` + } + var enc flatCallResult + enc.Address = f.Address + enc.Code = (*hexutil.Bytes)(f.Code) + enc.GasUsed = (*hexutil.Uint64)(f.GasUsed) + enc.Output = (*hexutil.Bytes)(f.Output) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (f *flatCallResult) UnmarshalJSON(input []byte) error { + type flatCallResult struct { + Address *common.Address `json:"address,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` + Output *hexutil.Bytes `json:"output,omitempty"` + } + var dec flatCallResult + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Address != nil { + f.Address = dec.Address + } + if dec.Code != nil { + f.Code = (*[]byte)(dec.Code) + } + if dec.GasUsed != nil { + f.GasUsed = (*uint64)(dec.GasUsed) + } + if dec.Output != nil { + f.Output = (*[]byte)(dec.Output) + } + return nil +} diff --git a/state/runtime/instrumentation/tracers/native/mux.go b/state/runtime/instrumentation/tracers/native/mux.go new file mode 100644 index 0000000000..db8ddd6438 --- /dev/null +++ b/state/runtime/instrumentation/tracers/native/mux.go @@ -0,0 +1,138 @@ +// Copyright 2022 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 native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +func init() { + tracers.DefaultDirectory.Register("muxTracer", newMuxTracer, false) +} + +// muxTracer is a go implementation of the Tracer interface which +// runs multiple tracers in one go. +type muxTracer struct { + names []string + tracers []tracers.Tracer +} + +// newMuxTracer returns a new mux tracer. +func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + var config map[string]json.RawMessage + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + objects := make([]tracers.Tracer, 0, len(config)) + names := make([]string, 0, len(config)) + for k, v := range config { + t, err := tracers.DefaultDirectory.New(k, ctx, v) + if err != nil { + return nil, err + } + objects = append(objects, t) + names = append(names, k) + } + + return &muxTracer{names: names, tracers: objects}, nil +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *muxTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + for _, t := range t.tracers { + t.CaptureStart(env, from, to, create, input, gas, value) + } +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { + for _, t := range t.tracers { + t.CaptureEnd(output, gasUsed, err) + } +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *muxTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + for _, t := range t.tracers { + t.CaptureState(pc, op, gas, cost, scope, rData, depth, err) + } +} + +// CaptureFault implements the EVMLogger interface to trace an execution fault. +func (t *muxTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { + for _, t := range t.tracers { + t.CaptureFault(pc, op, gas, cost, scope, depth, err) + } +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + for _, t := range t.tracers { + t.CaptureEnter(typ, from, to, input, gas, value) + } +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error) { + for _, t := range t.tracers { + t.CaptureExit(output, gasUsed, err) + } +} + +func (t *muxTracer) CaptureTxStart(gasLimit uint64) { + for _, t := range t.tracers { + t.CaptureTxStart(gasLimit) + } +} + +func (t *muxTracer) CaptureTxEnd(restGas uint64) { + for _, t := range t.tracers { + t.CaptureTxEnd(restGas) + } +} + +// GetResult returns an empty json object. +func (t *muxTracer) GetResult() (json.RawMessage, error) { + resObject := make(map[string]json.RawMessage) + for i, tt := range t.tracers { + r, err := tt.GetResult() + if err != nil { + return nil, err + } + resObject[t.names[i]] = r + } + res, err := json.Marshal(resObject) + if err != nil { + return nil, err + } + return res, nil +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *muxTracer) Stop(err error) { + for _, t := range t.tracers { + t.Stop(err) + } +} diff --git a/state/runtime/instrumentation/tracers/native/noop.go b/state/runtime/instrumentation/tracers/native/noop.go new file mode 100644 index 0000000000..fc692363fb --- /dev/null +++ b/state/runtime/instrumentation/tracers/native/noop.go @@ -0,0 +1,77 @@ +// 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 native + +import ( + "encoding/json" + "math/big" + + "github.com/0xPolygonHermez/zkevm-node/state/runtime/fakevm" + "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation/tracers" + "github.com/ethereum/go-ethereum/common" +) + +func init() { + tracers.DefaultDirectory.Register("noopTracer", NewNoopTracer, false) +} + +// noopTracer is a go implementation of the Tracer interface which +// performs no action. It's mostly useful for testing purposes. +type noopTracer struct{} + +// NewNoopTracer returns a new noop tracer. +func NewNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { + return &noopTracer{}, nil +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *noopTracer) CaptureStart(env *fakevm.FakeEVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *noopTracer) CaptureState(pc uint64, op fakevm.OpCode, gas, cost uint64, scope *fakevm.ScopeContext, rData []byte, depth int, err error) { +} + +// CaptureFault implements the EVMLogger interface to trace an execution fault. +func (t *noopTracer) CaptureFault(pc uint64, op fakevm.OpCode, gas, cost uint64, _ *fakevm.ScopeContext, depth int, err error) { +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *noopTracer) CaptureEnter(typ fakevm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +} + +func (*noopTracer) CaptureTxStart(gasLimit uint64) {} + +func (*noopTracer) CaptureTxEnd(restGas uint64) {} + +// GetResult returns an empty json object. +func (t *noopTracer) GetResult() (json.RawMessage, error) { + return json.RawMessage(`{}`), nil +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *noopTracer) Stop(err error) { +} diff --git a/state/runtime/instrumentation/tracers/native/prestate.go b/state/runtime/instrumentation/tracers/native/prestate.go new file mode 100644 index 0000000000..16d99a23d4 --- /dev/null +++ b/state/runtime/instrumentation/tracers/native/prestate.go @@ -0,0 +1,285 @@ +// Copyright 2022 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 native + +import ( + "bytes" + "encoding/json" + "math/big" + "sync/atomic" + + "github.com/0xPolygonHermez/zkevm-node/state/runtime/fakevm" + "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation/tracers" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" +) + +//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go + +func init() { + tracers.DefaultDirectory.Register("prestateTracer", NewPrestateTracer, false) +} + +type state = map[common.Address]*account + +type account struct { + Balance *big.Int `json:"balance,omitempty"` + Code []byte `json:"code,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` +} + +func (a *account) exists() bool { + return a.Nonce > 0 || len(a.Code) > 0 || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0) +} + +type accountMarshaling struct { + Balance *hexutil.Big + Code hexutil.Bytes +} + +type prestateTracer struct { + noopTracer + env *fakevm.FakeEVM + pre state + post state + create bool + to common.Address + gasLimit uint64 // Amount of gas bought for the whole tx + config prestateTracerConfig + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption + created map[common.Address]bool + deleted map[common.Address]bool +} + +type prestateTracerConfig struct { + DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications +} + +func NewPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + var config prestateTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + return &prestateTracer{ + pre: state{}, + post: state{}, + config: config, + created: make(map[common.Address]bool), + deleted: make(map[common.Address]bool), + }, nil +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *prestateTracer) CaptureStart(env *fakevm.FakeEVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + t.env = env + t.create = create + t.to = to + + t.lookupAccount(from) + t.lookupAccount(to) + t.lookupAccount(env.Context.Coinbase) + + // The recipient balance includes the value transferred. + toBal := new(big.Int).Sub(t.pre[to].Balance, value) + t.pre[to].Balance = toBal + + // The sender balance is after reducing: value and gasLimit. + // We need to re-add them to get the pre-tx balance. + fromBal := new(big.Int).Set(t.pre[from].Balance) + gasPrice := env.TxContext.GasPrice + consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit)) + fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas)) + t.pre[from].Balance = fromBal + t.pre[from].Nonce-- + + if create && t.config.DiffMode { + t.created[to] = true + } +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { + if t.config.DiffMode { + return + } + + if t.create { + // Keep existing account prior to contract creation at that address + if s := t.pre[t.to]; s != nil && !s.exists() { + // Exclude newly created contract. + delete(t.pre, t.to) + } + } +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *prestateTracer) CaptureState(pc uint64, op fakevm.OpCode, gas, cost uint64, scope *fakevm.ScopeContext, rData []byte, depth int, err error) { + stack := scope.Stack + stackData := stack.Data() + stackLen := len(stackData) + caller := scope.Contract.Address() + switch { + case stackLen >= 1 && (op == fakevm.SLOAD || op == fakevm.SSTORE): + slot := common.Hash(stackData[stackLen-1].Bytes32()) + t.lookupStorage(caller, slot) + case stackLen >= 1 && (op == fakevm.EXTCODECOPY || op == fakevm.EXTCODEHASH || op == fakevm.EXTCODESIZE || op == fakevm.BALANCE || op == fakevm.SELFDESTRUCT): + addr := common.Address(stackData[stackLen-1].Bytes20()) + t.lookupAccount(addr) + if op == fakevm.SELFDESTRUCT { + t.deleted[caller] = true + } + case stackLen >= 5 && (op == fakevm.DELEGATECALL || op == fakevm.CALL || op == fakevm.STATICCALL || op == fakevm.CALLCODE): + addr := common.Address(stackData[stackLen-2].Bytes20()) + t.lookupAccount(addr) + case op == fakevm.CREATE: + nonce := t.env.StateDB.GetNonce(caller) + addr := crypto.CreateAddress(caller, nonce) + t.lookupAccount(addr) + t.created[addr] = true + case stackLen >= 4 && op == fakevm.CREATE2: + offset := stackData[stackLen-2] + size := stackData[stackLen-3] + init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + inithash := crypto.Keccak256(init) + salt := stackData[stackLen-4] + addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash) + t.lookupAccount(addr) + t.created[addr] = true + } +} + +func (t *prestateTracer) CaptureTxStart(gasLimit uint64) { + t.gasLimit = gasLimit +} + +func (t *prestateTracer) CaptureTxEnd(restGas uint64) { + if !t.config.DiffMode { + return + } + + for addr, state := range t.pre { + // The deleted account's state is pruned from `post` but kept in `pre` + if _, ok := t.deleted[addr]; ok { + continue + } + modified := false + postAccount := &account{Storage: make(map[common.Hash]common.Hash)} + newBalance := t.env.StateDB.GetBalance(addr) + newNonce := t.env.StateDB.GetNonce(addr) + newCode := t.env.StateDB.GetCode(addr) + + if newBalance.Cmp(t.pre[addr].Balance) != 0 { + modified = true + postAccount.Balance = newBalance + } + if newNonce != t.pre[addr].Nonce { + modified = true + postAccount.Nonce = newNonce + } + if !bytes.Equal(newCode, t.pre[addr].Code) { + modified = true + postAccount.Code = newCode + } + + for key, val := range state.Storage { + // don't include the empty slot + if val == (common.Hash{}) { + delete(t.pre[addr].Storage, key) + } + + newVal := t.env.StateDB.GetState(addr, key) + if val == newVal { + // Omit unchanged slots + delete(t.pre[addr].Storage, key) + } else { + modified = true + if newVal != (common.Hash{}) { + postAccount.Storage[key] = newVal + } + } + } + + if modified { + t.post[addr] = postAccount + } else { + // if state is not modified, then no need to include into the pre state + delete(t.pre, addr) + } + } + // the new created contracts' prestate were empty, so delete them + for a := range t.created { + // the created contract maybe exists in statedb before the creating tx + if s := t.pre[a]; s != nil && !s.exists() { + delete(t.pre, a) + } + } +} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *prestateTracer) GetResult() (json.RawMessage, error) { + var res []byte + var err error + if t.config.DiffMode { + res, err = json.Marshal(struct { + Post state `json:"post"` + Pre state `json:"pre"` + }{t.post, t.pre}) + } else { + res, err = json.Marshal(t.pre) + } + if err != nil { + return nil, err + } + return json.RawMessage(res), t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *prestateTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} + +// lookupAccount fetches details of an account and adds it to the prestate +// if it doesn't exist there. +func (t *prestateTracer) lookupAccount(addr common.Address) { + if _, ok := t.pre[addr]; ok { + return + } + + t.pre[addr] = &account{ + Balance: t.env.StateDB.GetBalance(addr), + Nonce: t.env.StateDB.GetNonce(addr), + Code: t.env.StateDB.GetCode(addr), + Storage: make(map[common.Hash]common.Hash), + } +} + +// lookupStorage fetches the requested storage slot and adds +// it to the prestate of the given contract. It assumes `lookupAccount` +// has been performed on the contract before. +func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) { + if _, ok := t.pre[addr].Storage[key]; ok { + return + } + t.pre[addr].Storage[key] = t.env.StateDB.GetState(addr, key) +} diff --git a/state/runtime/instrumentation/tracers/tracers.go b/state/runtime/instrumentation/tracers/tracers.go index 3523834660..ec21f91e2c 100644 --- a/state/runtime/instrumentation/tracers/tracers.go +++ b/state/runtime/instrumentation/tracers/tracers.go @@ -19,7 +19,7 @@ package tracers import ( "encoding/json" - "errors" + "math/big" "github.com/0xPolygonHermez/zkevm-node/state/runtime/fakevm" "github.com/ethereum/go-ethereum/common" @@ -28,9 +28,10 @@ import ( // Context contains some contextual infos for a transaction execution that is not // available from within the EVM object. type Context struct { - BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call) - TxIndex int // Index of the transaction within a block (zero if dangling tx or call) - TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) + BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call) + BlockNumber *big.Int // Number of the block the tx is contained within (zero if dangling tx or call) + TxIndex int // Index of the transaction within a block (zero if dangling tx or call) + TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) } // Tracer interface extends vm.EVMLogger and additionally @@ -42,31 +43,55 @@ type Tracer interface { Stop(err error) } -type lookupFunc func(string, *Context) (Tracer, error) +type ctorFn func(*Context, json.RawMessage) (Tracer, error) +type jsCtorFn func(string, *Context, json.RawMessage) (Tracer, error) -var ( - lookups []lookupFunc -) +type elem struct { + ctor ctorFn + isJS bool +} -// RegisterLookup registers a method as a lookup for tracers, meaning that -// users can invoke a named tracer through that lookup. If 'wildcard' is true, -// then the lookup will be placed last. This is typically meant for interpreted -// engines (js) which can evaluate dynamic user-supplied code. -func RegisterLookup(wildcard bool, lookup lookupFunc) { - if wildcard { - lookups = append(lookups, lookup) - } else { - lookups = append([]lookupFunc{lookup}, lookups...) - } +// DefaultDirectory is the collection of tracers bundled by default. +var DefaultDirectory = directory{elems: make(map[string]elem)} + +// directory provides functionality to lookup a tracer by name +// and a function to instantiate it. It falls back to a JS code evaluator +// if no tracer of the given name exists. +type directory struct { + elems map[string]elem + jsEval jsCtorFn +} + +// Register registers a method as a lookup for tracers, meaning that +// users can invoke a named tracer through that lookup. +func (d *directory) Register(name string, f ctorFn, isJS bool) { + d.elems[name] = elem{ctor: f, isJS: isJS} +} + +// RegisterJSEval registers a tracer that is able to parse +// dynamic user-provided JS code. +func (d *directory) RegisterJSEval(f jsCtorFn) { + d.jsEval = f } // New returns a new instance of a tracer, by iterating through the -// registered lookups. -func New(code string, ctx *Context) (Tracer, error) { - for _, lookup := range lookups { - if tracer, err := lookup(code, ctx); err == nil { - return tracer, nil - } +// registered lookups. Name is either name of an existing tracer +// or an arbitrary JS code. +func (d *directory) New(name string, ctx *Context, cfg json.RawMessage) (Tracer, error) { + if elem, ok := d.elems[name]; ok { + return elem.ctor(ctx, cfg) + } + // Assume JS code + return d.jsEval(name, ctx, cfg) +} + +// IsJS will return true if the given tracer will evaluate +// JS code. Because code evaluation has high overhead, this +// info will be used in determining fast and slow code paths. +func (d *directory) IsJS(name string) bool { + if elem, ok := d.elems[name]; ok { + return elem.isJS } - return nil, errors.New("tracer not found") + // JS eval will execute JS code + return true } diff --git a/state/state.go b/state/state.go index 8628b5b56e..59329f269f 100644 --- a/state/state.go +++ b/state/state.go @@ -24,10 +24,10 @@ import ( "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation" "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation/js" "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation/tracers" + "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation/tracers/native" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" "github.com/holiman/uint256" @@ -36,10 +36,12 @@ import ( const ( // Size of the memory in bytes reserved by the zkEVM - zkEVMReservedMemorySize int = 128 - two uint = 2 - cTrue = 1 - cFalse = 0 + two uint = 2 +) + +const ( + cTrue = 1 + cFalse = 0 ) var ( @@ -849,8 +851,6 @@ func (s *State) GetLastBatch(ctx context.Context, dbTx pgx.Tx) (*Batch, error) { // DebugTransaction re-executes a tx to generate its trace func (s *State) DebugTransaction(ctx context.Context, transactionHash common.Hash, traceConfig TraceConfig, dbTx pgx.Tx) (*runtime.ExecutionResult, error) { - result := new(runtime.ExecutionResult) - // gets the transaction tx, err := s.GetTransactionByHash(ctx, transactionHash, dbTx) if err != nil { @@ -918,9 +918,10 @@ func (s *State) DebugTransaction(ctx context.Context, transactionHash common.Has traceConfigRequest.EnableReturnData = cTrue } + oldStateRoot := previousBlock.Root() processBatchRequest := &pb.ProcessBatchRequest{ OldBatchNum: batch.BatchNumber - 1, - OldStateRoot: previousBlock.Root().Bytes(), + OldStateRoot: oldStateRoot.Bytes(), OldAccInputHash: previousBatch.AccInputHash.Bytes(), BatchL2Data: batchL2Data, @@ -936,6 +937,7 @@ func (s *State) DebugTransaction(ctx context.Context, transactionHash common.Has // Send Batch to the Executor startTime := time.Now() processBatchResponse, err := s.executorClient.ProcessBatch(ctx, processBatchRequest) + endTime := time.Now() if err != nil { return nil, err } else if processBatchResponse.Error != executor.EXECUTOR_ERROR_NO_ERROR { @@ -943,7 +945,6 @@ func (s *State) DebugTransaction(ctx context.Context, transactionHash common.Has s.eventLog.LogExecutorError(ctx, processBatchResponse.Error, processBatchRequest) return nil, err } - endTime := time.Now() // //save process batch response file // b, err := json.Marshal(processBatchResponse) @@ -977,25 +978,37 @@ func (s *State) DebugTransaction(ctx context.Context, transactionHash common.Has return nil, fmt.Errorf("tx hash not found in executor response") } - result.CreateAddress = response.CreateAddress - result.GasLeft = response.GasLeft - result.GasUsed = response.GasUsed - result.ReturnValue = response.ReturnValue - result.StateRoot = response.StateRoot.Bytes() - result.StructLogs = response.ExecutionTrace + result := &runtime.ExecutionResult{ + CreateAddress: response.CreateAddress, + GasLeft: response.GasLeft, + GasUsed: response.GasUsed, + ReturnValue: response.ReturnValue, + StateRoot: response.StateRoot.Bytes(), + StructLogs: response.ExecutionTrace, + ExecutorTrace: response.CallTrace, + } - if traceConfig.Tracer == nil || *traceConfig.Tracer == "" { + // if is the default trace, return the result + if traceConfig.IsDefaultTracer() { return result, nil } - // Parse the executor-like trace using the FakeEVM - jsTracer, err := js.NewJsTracer(*traceConfig.Tracer, new(tracers.Context)) + senderAddress, err := GetSender(*tx) if err != nil { - log.Errorf("debug transaction: failed to create jsTracer, err: %v", err) - return nil, fmt.Errorf("failed to create jsTracer, err: %v", err) + return nil, err } - context := instrumentation.Context{} + context := instrumentation.Context{ + From: senderAddress.String(), + Input: hex.EncodeToHex(tx.Data()), + Gas: strconv.FormatUint(tx.Gas(), encoding.Base10), + Value: tx.Value().String(), + Output: hex.EncodeToHex(result.ReturnValue), + GasPrice: tx.GasPrice().String(), + OldStateRoot: oldStateRoot.String(), + Time: uint64(endTime.Sub(startTime)), + GasUsed: strconv.FormatUint(result.GasUsed, encoding.Base10), + } // Fill trace context if tx.To() == nil { @@ -1006,21 +1019,6 @@ func (s *State) DebugTransaction(ctx context.Context, transactionHash common.Has context.To = tx.To().Hex() } - senderAddress, err := GetSender(*tx) - if err != nil { - return nil, err - } - - context.From = senderAddress.String() - context.Input = "0x" + hex.EncodeToString(tx.Data()) - context.Gas = strconv.FormatUint(tx.Gas(), encoding.Base10) - context.Value = tx.Value().String() - context.Output = "0x" + hex.EncodeToString(result.ReturnValue) - context.GasPrice = tx.GasPrice().String() - context.OldStateRoot = batch.StateRoot.String() - context.Time = uint64(endTime.Sub(startTime)) - context.GasUsed = strconv.FormatUint(result.GasUsed, encoding.Base10) - result.ExecutorTrace.Context = context gasPrice, ok := new(big.Int).SetString(context.GasPrice, encoding.Base10) @@ -1029,11 +1027,52 @@ func (s *State) DebugTransaction(ctx context.Context, transactionHash common.Has return nil, fmt.Errorf("failed to parse gasPrice") } - env := fakevm.NewFakeEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: gasPrice}, params.TestChainConfig, fakevm.Config{Debug: true, Tracer: jsTracer}) + tracerContext := &tracers.Context{ + BlockHash: receipt.BlockHash, + BlockNumber: receipt.BlockNumber, + TxIndex: int(receipt.TransactionIndex), + TxHash: transactionHash, + } + + var evmTracer tracers.Tracer + if traceConfig.Is4ByteTracer() { + evmTracer, err = native.NewFourByteTracer(tracerContext, traceConfig.TracerConfig) + if err != nil { + log.Errorf("debug transaction: failed to create 4byteTracer, err: %v", err) + return nil, fmt.Errorf("failed to create 4byteTracer, err: %v", err) + } + } else if traceConfig.IsCallTracer() { + evmTracer, err = native.NewCallTracer(tracerContext, traceConfig.TracerConfig) + if err != nil { + log.Errorf("debug transaction: failed to create callTracer, err: %v", err) + return nil, fmt.Errorf("failed to create callTracer, err: %v", err) + } + } else if traceConfig.IsNoopTracer() { + evmTracer, err = native.NewNoopTracer(tracerContext, traceConfig.TracerConfig) + if err != nil { + log.Errorf("debug transaction: failed to create noopTracer, err: %v", err) + return nil, fmt.Errorf("failed to create noopTracer, err: %v", err) + } + } else if traceConfig.IsPrestateTracer() { + evmTracer, err = native.NewPrestateTracer(tracerContext, traceConfig.TracerConfig) + if err != nil { + log.Errorf("debug transaction: failed to create prestateTracer, err: %v", err) + return nil, fmt.Errorf("failed to create prestateTracer, err: %v", err) + } + } else if traceConfig.IsJSCustomTracer() { + evmTracer, err = js.NewJsTracer(*traceConfig.Tracer, tracerContext, traceConfig.TracerConfig) + if err != nil { + log.Errorf("debug transaction: failed to create jsTracer, err: %v", err) + return nil, fmt.Errorf("failed to create jsTracer, err: %v", err) + } + } else { + return nil, fmt.Errorf("invalid tracer: %v, err: %v", traceConfig.Tracer, err) + } + fakeDB := &FakeDB{State: s, stateRoot: batch.StateRoot.Bytes()} - env.SetStateDB(fakeDB) + evm := fakevm.NewFakeEVM(fakevm.BlockContext{BlockNumber: big.NewInt(1)}, fakevm.TxContext{GasPrice: gasPrice}, fakeDB, params.TestChainConfig, fakevm.Config{Debug: true, Tracer: evmTracer}) - traceResult, err := s.ParseTheTraceUsingTheTracer(env, result.ExecutorTrace, jsTracer) + traceResult, err := s.ParseTheTraceUsingTheTracer(evm, result.ExecutorTrace, evmTracer) if err != nil { log.Errorf("debug transaction: failed parse the trace using the tracer: %v", err) return nil, fmt.Errorf("failed parse the trace using the tracer: %v", err) @@ -1045,8 +1084,9 @@ func (s *State) DebugTransaction(ctx context.Context, transactionHash common.Has } // ParseTheTraceUsingTheTracer parses the given trace with the given tracer. -func (s *State) ParseTheTraceUsingTheTracer(env *fakevm.FakeEVM, trace instrumentation.ExecutorTrace, jsTracer tracers.Tracer) (json.RawMessage, error) { +func (s *State) ParseTheTraceUsingTheTracer(evm *fakevm.FakeEVM, trace instrumentation.ExecutorTrace, tracer tracers.Tracer) (json.RawMessage, error) { var previousDepth int + var previousOp, previousGas *big.Int var previousOpcode string var stateRoot []byte @@ -1061,11 +1101,8 @@ func (s *State) ParseTheTraceUsingTheTracer(env *fakevm.FakeEVM, trace instrumen return nil, ErrParsingExecutorTrace } - jsTracer.CaptureTxStart(contextGas.Uint64()) - jsTracer.CaptureStart(env, common.HexToAddress(trace.Context.From), common.HexToAddress(trace.Context.To), trace.Context.Type == "CREATE", common.Hex2Bytes(strings.TrimLeft(trace.Context.Input, "0x")), contextGas.Uint64(), value) - - stack := fakevm.Newstack() - memory := fakevm.NewMemory() + tracer.CaptureTxStart(contextGas.Uint64()) + tracer.CaptureStart(evm, common.HexToAddress(trace.Context.From), common.HexToAddress(trace.Context.To), trace.Context.Type == "CREATE", common.Hex2Bytes(strings.TrimLeft(trace.Context.Input, "0x")), contextGas.Uint64(), value) bigStateRoot, ok := new(big.Int).SetString(trace.Context.OldStateRoot, 0) if !ok { @@ -1073,9 +1110,17 @@ func (s *State) ParseTheTraceUsingTheTracer(env *fakevm.FakeEVM, trace instrumen return nil, ErrParsingExecutorTrace } stateRoot = bigStateRoot.Bytes() - env.StateDB.SetStateRoot(stateRoot) + evm.StateDB.SetStateRoot(stateRoot) + + output := common.FromHex(trace.Context.Output) + var stepError error for i, step := range trace.Steps { + stepErrorMsg := strings.TrimSpace(step.Error) + if stepErrorMsg != "" { + stepError = fmt.Errorf(stepErrorMsg) + } + gas, ok := new(big.Int).SetString(step.Gas, encoding.Base10) if !ok { log.Debugf("error while parsing step gas") @@ -1088,20 +1133,36 @@ func (s *State) ParseTheTraceUsingTheTracer(env *fakevm.FakeEVM, trace instrumen return nil, ErrParsingExecutorTrace } - value, ok := new(big.Int).SetString(step.Contract.Value, encoding.Base10) - if !ok { - log.Debugf("error while parsing step value") - return nil, ErrParsingExecutorTrace - } - op, ok := new(big.Int).SetString(step.Op, 0) if !ok { log.Debugf("error while parsing step op") return nil, ErrParsingExecutorTrace } + // set Stack + stack := fakevm.NewStack() + for _, stackContent := range step.Stack { + valueBigInt, ok := new(big.Int).SetString(stackContent, hex.Base) + if !ok { + log.Debugf("error while parsing stack valueBigInt") + return nil, ErrParsingExecutorTrace + } + value, _ := uint256.FromBig(valueBigInt) + stack.Push(value) + } + + // set Memory + memory := fakevm.NewMemory() + if len(step.Memory) > 0 { + memory.Resize(uint64(len(step.Memory))) + memory.Set(0, uint64(len(step.Memory)), step.Memory) + } else { + memory = fakevm.NewMemory() + } + + value := hex.DecodeBig(step.Contract.Value) scope := &fakevm.ScopeContext{ - Contract: vm.NewContract(fakevm.NewAccount(common.HexToAddress(step.Contract.Caller)), fakevm.NewAccount(common.HexToAddress(step.Contract.Address)), value, gas.Uint64()), + Contract: fakevm.NewContract(fakevm.NewAccount(common.HexToAddress(step.Contract.Caller)), fakevm.NewAccount(common.HexToAddress(step.Contract.Address)), value, gas.Uint64()), Memory: memory, Stack: stack, } @@ -1109,65 +1170,49 @@ func (s *State) ParseTheTraceUsingTheTracer(env *fakevm.FakeEVM, trace instrumen codeAddr := common.HexToAddress(step.Contract.Address) scope.Contract.CodeAddr = &codeAddr - opcode := vm.OpCode(op.Uint64()).String() + // when a revert is detected, we stop the execution + if step.OpCode == "REVERT" { + stepError = fakevm.ErrExecutionReverted + break + } if previousOpcode == "CALL" && step.Pc != 0 { - jsTracer.CaptureExit(common.Hex2Bytes(step.ReturnData), gasCost.Uint64(), fmt.Errorf(step.Error)) + tracer.CaptureExit(step.ReturnData, gasCost.Uint64(), stepError) } - if opcode != "CALL" || trace.Steps[i+1].Pc == 0 { - if step.Error != "" { - err := fmt.Errorf(step.Error) - jsTracer.CaptureFault(step.Pc, vm.OpCode(op.Uint64()), gas.Uint64(), gasCost.Uint64(), scope, step.Depth, err) + if step.OpCode != "CALL" || trace.Steps[i+1].Pc == 0 { + if stepError != nil { + tracer.CaptureFault(step.Pc, fakevm.OpCode(op.Uint64()), gas.Uint64(), gasCost.Uint64(), scope, step.Depth, stepError) } else { - jsTracer.CaptureState(step.Pc, vm.OpCode(op.Uint64()), gas.Uint64(), gasCost.Uint64(), scope, common.Hex2Bytes(strings.TrimLeft(step.ReturnData, "0x")), step.Depth, nil) + tracer.CaptureState(step.Pc, fakevm.OpCode(op.Uint64()), gas.Uint64(), gasCost.Uint64(), scope, step.ReturnData, step.Depth, nil) } } - if opcode == "CREATE" || opcode == "CREATE2" || opcode == "CALL" || opcode == "CALLCODE" || opcode == "DELEGATECALL" || opcode == "STATICCALL" || opcode == "SELFDESTRUCT" { - jsTracer.CaptureEnter(vm.OpCode(op.Uint64()), common.HexToAddress(step.Contract.Caller), common.HexToAddress(step.Contract.Address), common.Hex2Bytes(strings.TrimLeft(step.Contract.Input, "0x")), gas.Uint64(), value) + if step.OpCode == "CALL" || step.OpCode == "CALLCODE" || step.OpCode == "DELEGATECALL" || step.OpCode == "STATICCALL" || step.OpCode == "SELFDESTRUCT" { + tracer.CaptureEnter(fakevm.OpCode(op.Uint64()), common.HexToAddress(step.Contract.Caller), common.HexToAddress(step.Contract.Address), []byte(step.Contract.Input), gas.Uint64(), value) if step.OpCode == "SELFDESTRUCT" { - jsTracer.CaptureExit(common.Hex2Bytes(step.ReturnData), gasCost.Uint64(), fmt.Errorf(step.Error)) + tracer.CaptureExit(step.ReturnData, gasCost.Uint64(), stepError) } } - // Set Memory - if len(step.Memory) > 0 { - memory.Resize(uint64(fakevm.MemoryItemSize*len(step.Memory) + zkEVMReservedMemorySize)) - for offset, memoryContent := range step.Memory { - memory.Set(uint64((offset*fakevm.MemoryItemSize)+zkEVMReservedMemorySize), uint64(fakevm.MemoryItemSize), common.Hex2Bytes(memoryContent)) - } - } else { - memory = fakevm.NewMemory() - } - - // Set Stack - stack = fakevm.Newstack() - for _, stackContent := range step.Stack { - valueBigInt, ok := new(big.Int).SetString(stackContent, 0) - if !ok { - log.Debugf("error while parsing stack valueBigInt") - return nil, ErrParsingExecutorTrace - } - value, _ := uint256.FromBig(valueBigInt) - stack.Push(value) + // when a create2 is detected, the next step contains the contract updated + if previousOpcode == "CREATE" || previousOpcode == "CREATE2" { + tracer.CaptureEnter(fakevm.OpCode(previousOp.Uint64()), common.HexToAddress(step.Contract.Caller), common.HexToAddress(step.Contract.Address), []byte(step.Contract.Input), previousGas.Uint64(), value) } - // Returning from a call or create + // returning from a call or create if previousDepth > step.Depth { - jsTracer.CaptureExit(common.Hex2Bytes(step.ReturnData), gasCost.Uint64(), fmt.Errorf(step.Error)) + tracer.CaptureExit(step.ReturnData, gasCost.Uint64(), stepError) } - // Set StateRoot - bigStateRoot, ok := new(big.Int).SetString(step.StateRoot, 0) - if !ok { - log.Debugf("error while parsing step stateRoot") - return nil, ErrParsingExecutorTrace - } + // set StateRoot + stateRoot = []byte(step.StateRoot) + evm.StateDB.SetStateRoot(stateRoot) - stateRoot = bigStateRoot.Bytes() - env.StateDB.SetStateRoot(stateRoot) + // set previous step values previousDepth = step.Depth + previousOp = op + previousGas = gas previousOpcode = step.OpCode } @@ -1177,10 +1222,11 @@ func (s *State) ParseTheTraceUsingTheTracer(env *fakevm.FakeEVM, trace instrumen return nil, ErrParsingExecutorTrace } - jsTracer.CaptureTxEnd(gasUsed.Uint64()) - jsTracer.CaptureEnd(common.Hex2Bytes(trace.Context.Output), gasUsed.Uint64(), time.Duration(trace.Context.Time), nil) + restGas := contextGas.Uint64() - gasUsed.Uint64() + tracer.CaptureTxEnd(restGas) + tracer.CaptureEnd(output, gasUsed.Uint64(), stepError) - return jsTracer.GetResult() + return tracer.GetResult() } // PreProcessTransaction processes the transaction in order to calculate its zkCounters before adding it to the pool diff --git a/state/types.go b/state/types.go index add0b713ed..2575a66336 100644 --- a/state/types.go +++ b/state/types.go @@ -1,8 +1,10 @@ package state import ( + "encoding/json" "fmt" "math/big" + "strings" "github.com/0xPolygonHermez/zkevm-node/state/runtime/instrumentation" "github.com/ethereum/go-ethereum/common" @@ -145,6 +147,37 @@ type TraceConfig struct { EnableMemory bool EnableReturnData bool Tracer *string + TracerConfig json.RawMessage +} + +// IsDefaultTracer returns true when no custom tracer is set +func (t *TraceConfig) IsDefaultTracer() bool { + return t.Tracer == nil || *t.Tracer == "" +} + +// Is4ByteTracer returns true when should use 4byteTracer +func (t *TraceConfig) Is4ByteTracer() bool { + return t.Tracer != nil && *t.Tracer == "4byteTracer" +} + +// IsCallTracer returns true when should use callTracer +func (t *TraceConfig) IsCallTracer() bool { + return t.Tracer != nil && *t.Tracer == "callTracer" +} + +// IsNoopTracer returns true when should use noopTracer +func (t *TraceConfig) IsNoopTracer() bool { + return t.Tracer != nil && *t.Tracer == "noopTracer" +} + +// IsPrestateTracer returns true when should use prestateTracer +func (t *TraceConfig) IsPrestateTracer() bool { + return t.Tracer != nil && *t.Tracer == "prestateTracer" +} + +// IsJSCustomTracer returns true when should use js custom tracer +func (t *TraceConfig) IsJSCustomTracer() bool { + return t.Tracer != nil && strings.Contains(*t.Tracer, "result") && strings.Contains(*t.Tracer, "fault") } // TrustedReorg represents a trusted reorg diff --git a/test/config/debug.node.config.toml b/test/config/debug.node.config.toml index 7fe30c4dd6..1f91238b66 100644 --- a/test/config/debug.node.config.toml +++ b/test/config/debug.node.config.toml @@ -147,7 +147,7 @@ ProfilingEnabled = false User = "event_user" Password = "event_password" Name = "event_db" - Host = "zkevm-event-db" - Port = "5432" + Host = "localhost" + Port = "5435" EnableLog = false MaxConns = 200 diff --git a/test/contracts/auto/Creates.sol b/test/contracts/auto/Creates.sol new file mode 100644 index 0000000000..cdd7c517ba --- /dev/null +++ b/test/contracts/auto/Creates.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +contract Creates { + function opCreate(bytes memory bytecode, uint length) public returns(address) { + address addr; + assembly { + addr := create(0, 0xa0, length) + sstore(0x0, addr) + } + return addr; + } + + function opCreate2(bytes memory bytecode, uint length) public returns(address) { + address addr; + assembly { + addr := create2(0, 0xa0, length, 0x2) + sstore(0x0, addr) + } + return addr; + } + + function opCreate2Complex(bytes memory bytecode, uint length) public returns(address, uint256) { + uint256 number = add(1, 2); + + address addr; + assembly { + addr := create2(0, add(bytecode, 0x20), length, 0x2) + sstore(0x0, addr) + } + + number = add(2, 4); + + return (addr, number); + } + + function add(uint256 a, uint256 b) public pure returns(uint256) { + return a + b; + } + + function sendValue() public payable { + uint bal; + assembly{ + bal := add(bal,callvalue()) + sstore(0x1, bal) + } + } + + function opCreateValue(bytes memory bytecode, uint length) public payable returns(address) { + address addr; + assembly { + addr := create(500, 0xa0, length) + sstore(0x0, addr) + } + return addr; + } + + function opCreate2Value(bytes memory bytecode, uint length) public payable returns(address) { + address addr; + assembly { + addr := create2(300, 0xa0, length, 0x55555) + sstore(0x0, addr) + } + return addr; + } +} \ No newline at end of file diff --git a/test/contracts/bin/Creates/Creates.go b/test/contracts/bin/Creates/Creates.go new file mode 100644 index 0000000000..af2bf2e773 --- /dev/null +++ b/test/contracts/bin/Creates/Creates.go @@ -0,0 +1,360 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package Creates + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// CreatesMetaData contains all meta data concerning the Creates contract. +var CreatesMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"a\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"b\",\"type\":\"uint256\"}],\"name\":\"add\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"bytecode\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"opCreate\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"bytecode\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"opCreate2\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"bytecode\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"opCreate2Complex\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"bytecode\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"opCreate2Value\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"bytecode\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"opCreateValue\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sendValue\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b50610369806100206000396000f3fe6080604052600436106100705760003560e01c8063771602f71161004e578063771602f7146100d0578063b88c4aa9146100fe578063c935aee414610111578063e3306a251461015057600080fd5b806327c845dc146100755780633c77eba3146100805780635b8e9959146100b0575b600080fd5b61007e34600155565b005b61009361008e366004610236565b610170565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100bc57600080fd5b506100936100cb366004610236565b610187565b3480156100dc57600080fd5b506100f06100eb3660046102eb565b61019d565b6040519081526020016100a7565b61009361010c366004610236565b6101b0565b34801561011d57600080fd5b5061013161012c366004610236565b6101cb565b604080516001600160a01b0390931683526020830191909152016100a7565b34801561015c57600080fd5b5061009361016b366004610236565b610208565b6000808260a06101f4f06000819055949350505050565b6000808260a06000f06000819055949350505050565b60006101a9828461030d565b9392505050565b600080620555558360a061012cf56000819055949350505050565b60008060006101dc6001600261019d565b90506000600285602088016000f59050806000556101fc6002600461019d565b90969095509350505050565b60008060028360a06000f56000819055949350505050565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561024957600080fd5b823567ffffffffffffffff8082111561026157600080fd5b818501915085601f83011261027557600080fd5b81358181111561028757610287610220565b604051601f8201601f19908116603f011681019083821181831017156102af576102af610220565b816040528281528860208487010111156102c857600080fd5b826020860160208301376000602093820184015298969091013596505050505050565b600080604083850312156102fe57600080fd5b50508035926020909101359150565b6000821982111561032e57634e487b7160e01b600052601160045260246000fd5b50019056fea2646970667358221220cdc0e0bdc2487139b3aa0666f32d3f0ed1e40a81659b28e6dea427224cc6104f64736f6c634300080c0033", +} + +// CreatesABI is the input ABI used to generate the binding from. +// Deprecated: Use CreatesMetaData.ABI instead. +var CreatesABI = CreatesMetaData.ABI + +// CreatesBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use CreatesMetaData.Bin instead. +var CreatesBin = CreatesMetaData.Bin + +// DeployCreates deploys a new Ethereum contract, binding an instance of Creates to it. +func DeployCreates(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Creates, error) { + parsed, err := CreatesMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(CreatesBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Creates{CreatesCaller: CreatesCaller{contract: contract}, CreatesTransactor: CreatesTransactor{contract: contract}, CreatesFilterer: CreatesFilterer{contract: contract}}, nil +} + +// Creates is an auto generated Go binding around an Ethereum contract. +type Creates struct { + CreatesCaller // Read-only binding to the contract + CreatesTransactor // Write-only binding to the contract + CreatesFilterer // Log filterer for contract events +} + +// CreatesCaller is an auto generated read-only Go binding around an Ethereum contract. +type CreatesCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// CreatesTransactor is an auto generated write-only Go binding around an Ethereum contract. +type CreatesTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// CreatesFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type CreatesFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// CreatesSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type CreatesSession struct { + Contract *Creates // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// CreatesCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type CreatesCallerSession struct { + Contract *CreatesCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// CreatesTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type CreatesTransactorSession struct { + Contract *CreatesTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// CreatesRaw is an auto generated low-level Go binding around an Ethereum contract. +type CreatesRaw struct { + Contract *Creates // Generic contract binding to access the raw methods on +} + +// CreatesCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type CreatesCallerRaw struct { + Contract *CreatesCaller // Generic read-only contract binding to access the raw methods on +} + +// CreatesTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type CreatesTransactorRaw struct { + Contract *CreatesTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewCreates creates a new instance of Creates, bound to a specific deployed contract. +func NewCreates(address common.Address, backend bind.ContractBackend) (*Creates, error) { + contract, err := bindCreates(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Creates{CreatesCaller: CreatesCaller{contract: contract}, CreatesTransactor: CreatesTransactor{contract: contract}, CreatesFilterer: CreatesFilterer{contract: contract}}, nil +} + +// NewCreatesCaller creates a new read-only instance of Creates, bound to a specific deployed contract. +func NewCreatesCaller(address common.Address, caller bind.ContractCaller) (*CreatesCaller, error) { + contract, err := bindCreates(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &CreatesCaller{contract: contract}, nil +} + +// NewCreatesTransactor creates a new write-only instance of Creates, bound to a specific deployed contract. +func NewCreatesTransactor(address common.Address, transactor bind.ContractTransactor) (*CreatesTransactor, error) { + contract, err := bindCreates(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &CreatesTransactor{contract: contract}, nil +} + +// NewCreatesFilterer creates a new log filterer instance of Creates, bound to a specific deployed contract. +func NewCreatesFilterer(address common.Address, filterer bind.ContractFilterer) (*CreatesFilterer, error) { + contract, err := bindCreates(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &CreatesFilterer{contract: contract}, nil +} + +// bindCreates binds a generic wrapper to an already deployed contract. +func bindCreates(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := CreatesMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Creates *CreatesRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Creates.Contract.CreatesCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Creates *CreatesRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Creates.Contract.CreatesTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Creates *CreatesRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Creates.Contract.CreatesTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Creates *CreatesCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Creates.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Creates *CreatesTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Creates.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Creates *CreatesTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Creates.Contract.contract.Transact(opts, method, params...) +} + +// Add is a free data retrieval call binding the contract method 0x771602f7. +// +// Solidity: function add(uint256 a, uint256 b) pure returns(uint256) +func (_Creates *CreatesCaller) Add(opts *bind.CallOpts, a *big.Int, b *big.Int) (*big.Int, error) { + var out []interface{} + err := _Creates.contract.Call(opts, &out, "add", a, b) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Add is a free data retrieval call binding the contract method 0x771602f7. +// +// Solidity: function add(uint256 a, uint256 b) pure returns(uint256) +func (_Creates *CreatesSession) Add(a *big.Int, b *big.Int) (*big.Int, error) { + return _Creates.Contract.Add(&_Creates.CallOpts, a, b) +} + +// Add is a free data retrieval call binding the contract method 0x771602f7. +// +// Solidity: function add(uint256 a, uint256 b) pure returns(uint256) +func (_Creates *CreatesCallerSession) Add(a *big.Int, b *big.Int) (*big.Int, error) { + return _Creates.Contract.Add(&_Creates.CallOpts, a, b) +} + +// OpCreate is a paid mutator transaction binding the contract method 0x5b8e9959. +// +// Solidity: function opCreate(bytes bytecode, uint256 length) returns(address) +func (_Creates *CreatesTransactor) OpCreate(opts *bind.TransactOpts, bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.contract.Transact(opts, "opCreate", bytecode, length) +} + +// OpCreate is a paid mutator transaction binding the contract method 0x5b8e9959. +// +// Solidity: function opCreate(bytes bytecode, uint256 length) returns(address) +func (_Creates *CreatesSession) OpCreate(bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.Contract.OpCreate(&_Creates.TransactOpts, bytecode, length) +} + +// OpCreate is a paid mutator transaction binding the contract method 0x5b8e9959. +// +// Solidity: function opCreate(bytes bytecode, uint256 length) returns(address) +func (_Creates *CreatesTransactorSession) OpCreate(bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.Contract.OpCreate(&_Creates.TransactOpts, bytecode, length) +} + +// OpCreate2 is a paid mutator transaction binding the contract method 0xe3306a25. +// +// Solidity: function opCreate2(bytes bytecode, uint256 length) returns(address) +func (_Creates *CreatesTransactor) OpCreate2(opts *bind.TransactOpts, bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.contract.Transact(opts, "opCreate2", bytecode, length) +} + +// OpCreate2 is a paid mutator transaction binding the contract method 0xe3306a25. +// +// Solidity: function opCreate2(bytes bytecode, uint256 length) returns(address) +func (_Creates *CreatesSession) OpCreate2(bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.Contract.OpCreate2(&_Creates.TransactOpts, bytecode, length) +} + +// OpCreate2 is a paid mutator transaction binding the contract method 0xe3306a25. +// +// Solidity: function opCreate2(bytes bytecode, uint256 length) returns(address) +func (_Creates *CreatesTransactorSession) OpCreate2(bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.Contract.OpCreate2(&_Creates.TransactOpts, bytecode, length) +} + +// OpCreate2Complex is a paid mutator transaction binding the contract method 0xc935aee4. +// +// Solidity: function opCreate2Complex(bytes bytecode, uint256 length) returns(address, uint256) +func (_Creates *CreatesTransactor) OpCreate2Complex(opts *bind.TransactOpts, bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.contract.Transact(opts, "opCreate2Complex", bytecode, length) +} + +// OpCreate2Complex is a paid mutator transaction binding the contract method 0xc935aee4. +// +// Solidity: function opCreate2Complex(bytes bytecode, uint256 length) returns(address, uint256) +func (_Creates *CreatesSession) OpCreate2Complex(bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.Contract.OpCreate2Complex(&_Creates.TransactOpts, bytecode, length) +} + +// OpCreate2Complex is a paid mutator transaction binding the contract method 0xc935aee4. +// +// Solidity: function opCreate2Complex(bytes bytecode, uint256 length) returns(address, uint256) +func (_Creates *CreatesTransactorSession) OpCreate2Complex(bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.Contract.OpCreate2Complex(&_Creates.TransactOpts, bytecode, length) +} + +// OpCreate2Value is a paid mutator transaction binding the contract method 0xb88c4aa9. +// +// Solidity: function opCreate2Value(bytes bytecode, uint256 length) payable returns(address) +func (_Creates *CreatesTransactor) OpCreate2Value(opts *bind.TransactOpts, bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.contract.Transact(opts, "opCreate2Value", bytecode, length) +} + +// OpCreate2Value is a paid mutator transaction binding the contract method 0xb88c4aa9. +// +// Solidity: function opCreate2Value(bytes bytecode, uint256 length) payable returns(address) +func (_Creates *CreatesSession) OpCreate2Value(bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.Contract.OpCreate2Value(&_Creates.TransactOpts, bytecode, length) +} + +// OpCreate2Value is a paid mutator transaction binding the contract method 0xb88c4aa9. +// +// Solidity: function opCreate2Value(bytes bytecode, uint256 length) payable returns(address) +func (_Creates *CreatesTransactorSession) OpCreate2Value(bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.Contract.OpCreate2Value(&_Creates.TransactOpts, bytecode, length) +} + +// OpCreateValue is a paid mutator transaction binding the contract method 0x3c77eba3. +// +// Solidity: function opCreateValue(bytes bytecode, uint256 length) payable returns(address) +func (_Creates *CreatesTransactor) OpCreateValue(opts *bind.TransactOpts, bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.contract.Transact(opts, "opCreateValue", bytecode, length) +} + +// OpCreateValue is a paid mutator transaction binding the contract method 0x3c77eba3. +// +// Solidity: function opCreateValue(bytes bytecode, uint256 length) payable returns(address) +func (_Creates *CreatesSession) OpCreateValue(bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.Contract.OpCreateValue(&_Creates.TransactOpts, bytecode, length) +} + +// OpCreateValue is a paid mutator transaction binding the contract method 0x3c77eba3. +// +// Solidity: function opCreateValue(bytes bytecode, uint256 length) payable returns(address) +func (_Creates *CreatesTransactorSession) OpCreateValue(bytecode []byte, length *big.Int) (*types.Transaction, error) { + return _Creates.Contract.OpCreateValue(&_Creates.TransactOpts, bytecode, length) +} + +// SendValue is a paid mutator transaction binding the contract method 0x27c845dc. +// +// Solidity: function sendValue() payable returns() +func (_Creates *CreatesTransactor) SendValue(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Creates.contract.Transact(opts, "sendValue") +} + +// SendValue is a paid mutator transaction binding the contract method 0x27c845dc. +// +// Solidity: function sendValue() payable returns() +func (_Creates *CreatesSession) SendValue() (*types.Transaction, error) { + return _Creates.Contract.SendValue(&_Creates.TransactOpts) +} + +// SendValue is a paid mutator transaction binding the contract method 0x27c845dc. +// +// Solidity: function sendValue() payable returns() +func (_Creates *CreatesTransactorSession) SendValue() (*types.Transaction, error) { + return _Creates.Contract.SendValue(&_Creates.TransactOpts) +} diff --git a/test/docker-compose.yml b/test/docker-compose.yml index a1f3676dd4..1d4f6d9e82 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -321,7 +321,7 @@ services: zkevm-prover: container_name: zkevm-prover - image: hermeznetwork/zkevm-prover:c514cd1 + image: hermeznetwork/zkevm-prover:4e3272b ports: # - 50051:50051 # Prover - 50052:50052 # Mock prover @@ -407,7 +407,7 @@ services: zkevm-permissionless-prover: container_name: zkevm-permissionless-prover - image: hermeznetwork/zkevm-prover:c514cd1 + image: hermeznetwork/zkevm-prover:4e3272b ports: # - 50058:50058 # Prover - 50059:50052 # Mock prover diff --git a/test/e2e/debug_test.go b/test/e2e/debug_test.go index 0e9cb9c8f7..18d99c2f11 100644 --- a/test/e2e/debug_test.go +++ b/test/e2e/debug_test.go @@ -12,6 +12,8 @@ import ( "github.com/0xPolygonHermez/zkevm-node/jsonrpc/client" "github.com/0xPolygonHermez/zkevm-node/jsonrpc/types" "github.com/0xPolygonHermez/zkevm-node/log" + "github.com/0xPolygonHermez/zkevm-node/test/contracts/bin/Counter" + "github.com/0xPolygonHermez/zkevm-node/test/contracts/bin/Creates" "github.com/0xPolygonHermez/zkevm-node/test/contracts/bin/ERC20" "github.com/0xPolygonHermez/zkevm-node/test/contracts/bin/EmitLog" "github.com/0xPolygonHermez/zkevm-node/test/contracts/bin/Revert2" @@ -21,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/stretchr/testify/require" ) @@ -285,19 +288,69 @@ func TestDebugTraceTransaction(t *testing.T) { {name: "sc deployment", createSignedTx: createScDeploySignedTx}, {name: "sc call", prepare: prepareScCall, createSignedTx: createScCallSignedTx}, {name: "erc20 transfer", prepare: prepareERC20Transfer, createSignedTx: createERC20TransferSignedTx}, + {name: "create", prepare: prepareCreate, createSignedTx: createCreateSignedTx}, + {name: "create2", prepare: prepareCreate, createSignedTx: createCreate2SignedTx}, // failed transactions {name: "sc deployment reverted", createSignedTx: createScDeployRevertedSignedTx}, {name: "sc call reverted", prepare: prepareScCallReverted, createSignedTx: createScCallRevertedSignedTx}, {name: "erc20 transfer reverted", prepare: prepareERC20TransferReverted, createSignedTx: createERC20TransferRevertedSignedTx}, } + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + for _, network := range networks { + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(0).SetUint64(network.ChainID)) + require.NoError(t, err) + + ethereumClient := operations.MustGetClient(network.URL) + sourceAuth := operations.MustGetAuth(network.PrivateKey, network.ChainID) + + nonce, err := ethereumClient.NonceAt(ctx, sourceAuth.From, nil) + require.NoError(t, err) + + balance, err := ethereumClient.BalanceAt(ctx, sourceAuth.From, nil) + require.NoError(t, err) + + gasPrice, err := ethereumClient.SuggestGasPrice(ctx) + require.NoError(t, err) + + value := big.NewInt(0).Quo(balance, big.NewInt(2)) + + gas, err := ethereumClient.EstimateGas(ctx, ethereum.CallMsg{ + From: sourceAuth.From, + To: &auth.From, + GasPrice: gasPrice, + Value: value, + }) + require.NoError(t, err) + + tx := ethTypes.NewTx(ðTypes.LegacyTx{ + To: &auth.From, + Nonce: nonce, + GasPrice: gasPrice, + Value: value, + Gas: gas, + }) + + signedTx, err := sourceAuth.Signer(sourceAuth.From, tx) + require.NoError(t, err) + + err = ethereumClient.SendTransaction(ctx, signedTx) + require.NoError(t, err) + + err = operations.WaitTxToBeMined(ctx, ethereumClient, signedTx, operations.DefaultTimeoutTxToBeMined) + require.NoError(t, err) + } + for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - log.Debugf(tc.name) + log.Debug("************************ ", tc.name, " ************************") + for _, network := range networks { - log.Debugf(network.Name) + log.Debug("------------------------ ", network.Name, " ------------------------") ethereumClient := operations.MustGetClient(network.URL) - auth := operations.MustGetAuth(network.PrivateKey, network.ChainID) + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(0).SetUint64(network.ChainID)) + require.NoError(t, err) var customData map[string]interface{} if tc.prepare != nil { @@ -366,6 +419,8 @@ func TestDebugTraceTransaction(t *testing.T) { err = json.Unmarshal(result, &resultMap) require.NoError(t, err) + require.Equal(t, referenceValueMap["failed"], resultMap["failed"], fmt.Sprintf("invalid `failed` for network %s", networkName)) + resultStructLogsMap := resultMap["structLogs"].([]interface{}) require.Equal(t, len(referenceStructLogsMap), len(resultStructLogsMap)) @@ -417,6 +472,251 @@ func TestDebugTraceTransaction(t *testing.T) { } } +func TestDebugTraceTransactionCallTracer(t *testing.T) { + if testing.Short() { + t.Skip() + } + + const l2NetworkURL = "http://localhost:8124" + const l2ExplorerRPCComponentName = "l2-explorer-json-rpc" + + var err error + err = operations.Teardown() + require.NoError(t, err) + + defer func() { + require.NoError(t, operations.Teardown()) + require.NoError(t, operations.StopComponent(l2ExplorerRPCComponentName)) + }() + + ctx := context.Background() + opsCfg := operations.GetDefaultOperationsConfig() + opsMan, err := operations.NewManager(ctx, opsCfg) + require.NoError(t, err) + err = opsMan.Setup() + require.NoError(t, err) + + err = operations.StartComponent(l2ExplorerRPCComponentName, func() (bool, error) { return operations.NodeUpCondition(l2NetworkURL) }) + require.NoError(t, err) + + const l1NetworkName, l2NetworkName = "Local L1", "Local L2" + + networks := []struct { + Name string + URL string + WebSocketURL string + ChainID uint64 + PrivateKey string + }{ + { + Name: l1NetworkName, + URL: operations.DefaultL1NetworkURL, + ChainID: operations.DefaultL1ChainID, + PrivateKey: operations.DefaultSequencerPrivateKey, + }, + { + Name: l2NetworkName, + URL: l2NetworkURL, + ChainID: operations.DefaultL2ChainID, + PrivateKey: operations.DefaultSequencerPrivateKey, + }, + } + + results := map[string]json.RawMessage{} + + type testCase struct { + name string + prepare func(t *testing.T, ctx context.Context, auth *bind.TransactOpts, client *ethclient.Client) (map[string]interface{}, error) + createSignedTx func(t *testing.T, ctx context.Context, auth *bind.TransactOpts, client *ethclient.Client, customData map[string]interface{}) (*ethTypes.Transaction, error) + } + testCases := []testCase{ + // successful transactions + {name: "eth transfer", createSignedTx: createEthTransferSignedTx}, + {name: "sc deployment", createSignedTx: createScDeploySignedTx}, + {name: "sc call", prepare: prepareScCall, createSignedTx: createScCallSignedTx}, + {name: "erc20 transfer", prepare: prepareERC20Transfer, createSignedTx: createERC20TransferSignedTx}, + {name: "create", prepare: prepareCreate, createSignedTx: createCreateSignedTx}, + {name: "create2", prepare: prepareCreate, createSignedTx: createCreate2SignedTx}, + // failed transactions + {name: "sc deployment reverted", createSignedTx: createScDeployRevertedSignedTx}, + {name: "sc call reverted", prepare: prepareScCallReverted, createSignedTx: createScCallRevertedSignedTx}, + {name: "erc20 transfer reverted", prepare: prepareERC20TransferReverted, createSignedTx: createERC20TransferRevertedSignedTx}, + } + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + for _, network := range networks { + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(0).SetUint64(network.ChainID)) + require.NoError(t, err) + + ethereumClient := operations.MustGetClient(network.URL) + sourceAuth := operations.MustGetAuth(network.PrivateKey, network.ChainID) + + nonce, err := ethereumClient.NonceAt(ctx, sourceAuth.From, nil) + require.NoError(t, err) + + balance, err := ethereumClient.BalanceAt(ctx, sourceAuth.From, nil) + require.NoError(t, err) + + gasPrice, err := ethereumClient.SuggestGasPrice(ctx) + require.NoError(t, err) + + value := big.NewInt(0).Quo(balance, big.NewInt(2)) + + gas, err := ethereumClient.EstimateGas(ctx, ethereum.CallMsg{ + From: sourceAuth.From, + To: &auth.From, + GasPrice: gasPrice, + Value: value, + }) + require.NoError(t, err) + + tx := ethTypes.NewTx(ðTypes.LegacyTx{ + To: &auth.From, + Nonce: nonce, + GasPrice: gasPrice, + Value: value, + Gas: gas, + }) + + signedTx, err := sourceAuth.Signer(sourceAuth.From, tx) + require.NoError(t, err) + + err = ethereumClient.SendTransaction(ctx, signedTx) + require.NoError(t, err) + + err = operations.WaitTxToBeMined(ctx, ethereumClient, signedTx, operations.DefaultTimeoutTxToBeMined) + require.NoError(t, err) + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + log.Debug("************************ ", tc.name, " ************************") + + for _, network := range networks { + log.Debug("------------------------ ", network.Name, " ------------------------") + ethereumClient := operations.MustGetClient(network.URL) + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(0).SetUint64(network.ChainID)) + require.NoError(t, err) + + var customData map[string]interface{} + if tc.prepare != nil { + customData, err = tc.prepare(t, ctx, auth, ethereumClient) + require.NoError(t, err) + } + + signedTx, err := tc.createSignedTx(t, ctx, auth, ethereumClient, customData) + require.NoError(t, err) + + err = ethereumClient.SendTransaction(ctx, signedTx) + require.NoError(t, err) + + log.Debugf("tx sent: %v", signedTx.Hash().String()) + + err = operations.WaitTxToBeMined(ctx, ethereumClient, signedTx, operations.DefaultTimeoutTxToBeMined) + if err != nil && !strings.HasPrefix(err.Error(), "transaction has failed, reason:") { + require.NoError(t, err) + } + + debugOptions := map[string]interface{}{ + "disableStorage": false, + "disableStack": false, + "enableMemory": true, + "enableReturnData": true, + "tracer": "callTracer", + "tracerConfig": map[string]interface{}{ + "onlyTopCall": false, + "withLog": true, + }, + } + + response, err := client.JSONRPCCall(network.URL, "debug_traceTransaction", signedTx.Hash().String(), debugOptions) + require.NoError(t, err) + require.Nil(t, response.Error) + require.NotNil(t, response.Result) + + results[network.Name] = response.Result + log.Debug(string(response.Result)) + + // save result in a file + // sanitizedNetworkName := strings.ReplaceAll(network.Name+"_"+tc.name, " ", "_") + // filePath := fmt.Sprintf("/home/tclemos/github.com/0xPolygonHermez/zkevm-node/dist/%v.json", sanitizedNetworkName) + // b, _ := signedTx.MarshalBinary() + // fileContent := struct { + // Tx *ethTypes.Transaction + // RLP string + // Trace json.RawMessage + // }{ + // Tx: signedTx, + // RLP: hex.EncodeToHex(b), + // Trace: response.Result, + // } + // c, err := json.MarshalIndent(fileContent, "", " ") + // require.NoError(t, err) + // err = os.WriteFile(filePath, c, 0644) + // require.NoError(t, err) + } + + referenceValueMap := map[string]interface{}{} + err = json.Unmarshal(results[l1NetworkName], &referenceValueMap) + require.NoError(t, err) + + for networkName, result := range results { + if networkName == l1NetworkName { + continue + } + + resultMap := map[string]interface{}{} + err = json.Unmarshal(result, &resultMap) + require.NoError(t, err) + + compareCallFrame(t, referenceValueMap, resultMap, networkName) + } + }) + } +} + +func compareCallFrame(t *testing.T, referenceValueMap, resultMap map[string]interface{}, networkName string) { + require.Equal(t, referenceValueMap["from"], resultMap["from"], fmt.Sprintf("invalid `from` for network %s", networkName)) + require.Equal(t, referenceValueMap["input"], resultMap["input"], fmt.Sprintf("invalid `input` for network %s", networkName)) + require.Equal(t, referenceValueMap["output"], resultMap["output"], fmt.Sprintf("invalid `output` for network %s", networkName)) + require.Equal(t, referenceValueMap["value"], resultMap["value"], fmt.Sprintf("invalid `value` for network %s", networkName)) + require.Equal(t, referenceValueMap["type"], resultMap["type"], fmt.Sprintf("invalid `type` for network %s", networkName)) + require.Equal(t, referenceValueMap["error"], resultMap["error"], fmt.Sprintf("invalid `error` for network %s", networkName)) + require.Equal(t, referenceValueMap["revertReason"], resultMap["revertReason"], fmt.Sprintf("invalid `revertReason` for network %s", networkName)) + + referenceLogs, found := referenceValueMap["logs"].([]interface{}) + if found { + resultLogs := resultMap["logs"].([]interface{}) + require.Equal(t, len(referenceLogs), len(resultLogs), "logs size doesn't match") + for logIndex := range referenceLogs { + referenceLog := referenceLogs[logIndex].(map[string]interface{}) + resultLog := resultLogs[logIndex].(map[string]interface{}) + + require.Equal(t, referenceLog["data"], resultLog["data"], fmt.Sprintf("log index %v data doesn't match", logIndex)) + referenceTopics, found := referenceLog["topics"].([]interface{}) + if found { + resultTopics := resultLog["topics"].([]interface{}) + require.Equal(t, len(referenceTopics), len(resultTopics), "log index %v topics size doesn't match", logIndex) + for topicIndex := range referenceTopics { + require.Equal(t, referenceTopics[topicIndex], resultTopics[topicIndex], fmt.Sprintf("log index %v topic index %v doesn't match", logIndex, topicIndex)) + } + } + } + } + + referenceCalls, found := referenceValueMap["calls"].([]interface{}) + if found { + resultCalls := resultMap["calls"].([]interface{}) + require.Equal(t, len(referenceCalls), len(resultCalls), "logs size doesn't match") + for callIndex := range referenceCalls { + referenceCall := referenceCalls[callIndex].(map[string]interface{}) + resultCall := resultCalls[callIndex].(map[string]interface{}) + + compareCallFrame(t, referenceCall, resultCall, networkName) + } + } +} + func TestDebugTraceBlock(t *testing.T) { if testing.Short() { t.Skip() @@ -498,8 +798,10 @@ func TestDebugTraceBlock(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + log.Debug("************************ ", tc.name, " ************************") + for _, network := range networks { - log.Debugf(network.Name) + log.Debug("------------------------ ", network.Name, " ------------------------") ethereumClient := operations.MustGetClient(network.URL) auth := operations.MustGetAuth(network.PrivateKey, network.ChainID) @@ -805,3 +1107,45 @@ func createERC20TransferRevertedSignedTx(t *testing.T, ctx context.Context, auth return tx, nil } + +func prepareCreate(t *testing.T, ctx context.Context, auth *bind.TransactOpts, client *ethclient.Client) (map[string]interface{}, error) { + _, tx, sc, err := Creates.DeployCreates(auth, client) + require.NoError(t, err) + + err = operations.WaitTxToBeMined(ctx, client, tx, operations.DefaultTimeoutTxToBeMined) + require.NoError(t, err) + + return map[string]interface{}{ + "sc": sc, + }, nil +} + +func createCreateSignedTx(t *testing.T, ctx context.Context, auth *bind.TransactOpts, client *ethclient.Client, customData map[string]interface{}) (*ethTypes.Transaction, error) { + scInterface := customData["sc"] + sc := scInterface.(*Creates.Creates) + + opts := *auth + opts.NoSend = true + + byteCode := hex.DecodeBig(Counter.CounterBin).Bytes() + + tx, err := sc.OpCreate(&opts, byteCode, big.NewInt(0).SetInt64(int64(len(byteCode)))) + require.NoError(t, err) + + return tx, nil +} + +func createCreate2SignedTx(t *testing.T, ctx context.Context, auth *bind.TransactOpts, client *ethclient.Client, customData map[string]interface{}) (*ethTypes.Transaction, error) { + scInterface := customData["sc"] + sc := scInterface.(*Creates.Creates) + + opts := *auth + opts.NoSend = true + + byteCode := hex.DecodeBig(Counter.CounterBin).Bytes() + + tx, err := sc.OpCreate2(&opts, byteCode, big.NewInt(0).SetInt64(int64(len(byteCode)))) + require.NoError(t, err) + + return tx, nil +} diff --git a/test/e2e/sc_test.go b/test/e2e/sc_test.go index 599aa3673b..a6fb0f7f73 100644 --- a/test/e2e/sc_test.go +++ b/test/e2e/sc_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/0xPolygonHermez/zkevm-node/log" + "github.com/0xPolygonHermez/zkevm-node/test/contracts/bin/Counter" "github.com/0xPolygonHermez/zkevm-node/test/contracts/bin/EmitLog2" "github.com/0xPolygonHermez/zkevm-node/test/contracts/bin/FailureTest" "github.com/0xPolygonHermez/zkevm-node/test/contracts/bin/Read" @@ -17,6 +18,54 @@ import ( "github.com/stretchr/testify/require" ) +func TestCounter(t *testing.T) { + if testing.Short() { + t.Skip() + } + + var err error + err = operations.Teardown() + require.NoError(t, err) + + defer func() { require.NoError(t, operations.Teardown()) }() + + ctx := context.Background() + opsCfg := operations.GetDefaultOperationsConfig() + opsMan, err := operations.NewManager(ctx, opsCfg) + require.NoError(t, err) + err = opsMan.Setup() + require.NoError(t, err) + + for _, network := range networks { + log.Debugf(network.Name) + client := operations.MustGetClient(network.URL) + auth := operations.MustGetAuth(network.PrivateKey, network.ChainID) + + _, scTx, sc, err := Counter.DeployCounter(auth, client) + require.NoError(t, err) + + logTx(scTx) + err = operations.WaitTxToBeMined(ctx, client, scTx, operations.DefaultTimeoutTxToBeMined) + require.NoError(t, err) + + count, err := sc.GetCount(&bind.CallOpts{Pending: false}) + require.NoError(t, err) + + assert.Equal(t, 0, count.Cmp(big.NewInt(0))) + + scCallTx, err := sc.Increment(auth) + require.NoError(t, err) + + logTx(scCallTx) + err = operations.WaitTxToBeMined(ctx, client, scCallTx, operations.DefaultTimeoutTxToBeMined) + require.NoError(t, err) + + count, err = sc.GetCount(&bind.CallOpts{Pending: false}) + require.NoError(t, err) + assert.Equal(t, 0, count.Cmp(big.NewInt(1))) + } +} + func TestEmitLog2(t *testing.T) { if testing.Short() { t.Skip() diff --git a/tools/zkevmprovermock/Dockerfile b/tools/zkevmprovermock/Dockerfile index a8e3140dbf..d4d21e0d8c 100644 --- a/tools/zkevmprovermock/Dockerfile +++ b/tools/zkevmprovermock/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18-alpine AS build +FROM golang:1.19-alpine AS build ENV CGO_ENABLED=0 WORKDIR /app