tracer_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. // Copyright 2021 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package js
  17. import (
  18. "encoding/json"
  19. "errors"
  20. "math/big"
  21. "strings"
  22. "testing"
  23. "time"
  24. "github.com/ethereum/go-ethereum/common"
  25. "github.com/ethereum/go-ethereum/core/state"
  26. "github.com/ethereum/go-ethereum/core/vm"
  27. "github.com/ethereum/go-ethereum/eth/tracers"
  28. "github.com/ethereum/go-ethereum/params"
  29. )
  30. type account struct{}
  31. func (account) SubBalance(amount *big.Int) {}
  32. func (account) AddBalance(amount *big.Int) {}
  33. func (account) SetAddress(common.Address) {}
  34. func (account) Value() *big.Int { return nil }
  35. func (account) SetBalance(*big.Int) {}
  36. func (account) SetNonce(uint64) {}
  37. func (account) Balance() *big.Int { return nil }
  38. func (account) Address() common.Address { return common.Address{} }
  39. func (account) SetCode(common.Hash, []byte) {}
  40. func (account) ForEachStorage(cb func(key, value common.Hash) bool) {}
  41. type dummyStatedb struct {
  42. state.StateDB
  43. }
  44. func (*dummyStatedb) GetRefund() uint64 { return 1337 }
  45. func (*dummyStatedb) GetBalance(addr common.Address) *big.Int { return new(big.Int) }
  46. type vmContext struct {
  47. blockCtx vm.BlockContext
  48. txCtx vm.TxContext
  49. }
  50. func testCtx() *vmContext {
  51. return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
  52. }
  53. func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) {
  54. var (
  55. env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer})
  56. gasLimit uint64 = 31000
  57. startGas uint64 = 10000
  58. value = big.NewInt(0)
  59. contract = vm.NewContract(account{}, account{}, value, startGas)
  60. )
  61. contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
  62. tracer.CaptureTxStart(gasLimit)
  63. tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
  64. ret, err := env.Interpreter().Run(contract, []byte{}, false)
  65. tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err)
  66. // Rest gas assumes no refund
  67. tracer.CaptureTxEnd(startGas - contract.Gas)
  68. if err != nil {
  69. return nil, err
  70. }
  71. return tracer.GetResult()
  72. }
  73. func TestTracer(t *testing.T) {
  74. execTracer := func(code string) ([]byte, string) {
  75. t.Helper()
  76. tracer, err := newJsTracer(code, nil, nil)
  77. if err != nil {
  78. t.Fatal(err)
  79. }
  80. ret, err := runTrace(tracer, testCtx(), params.TestChainConfig)
  81. if err != nil {
  82. return nil, err.Error() // Stringify to allow comparison without nil checks
  83. }
  84. return ret, ""
  85. }
  86. for i, tt := range []struct {
  87. code string
  88. want string
  89. fail string
  90. }{
  91. { // tests that we don't panic on bad arguments to memory access
  92. code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}",
  93. want: ``,
  94. fail: "tracer accessed out of bound memory: offset -1, end -2 at step (<eval>:1:53(15)) in server-side tracer function 'step'",
  95. }, { // tests that we don't panic on bad arguments to stack peeks
  96. code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}",
  97. want: ``,
  98. fail: "tracer accessed out of bound stack: size 0, index -1 at step (<eval>:1:53(13)) in server-side tracer function 'step'",
  99. }, { // tests that we don't panic on bad arguments to memory getUint
  100. code: "{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}",
  101. want: ``,
  102. fail: "tracer accessed out of bound memory: available 0, offset -64, size 32 at step (<eval>:1:58(13)) in server-side tracer function 'step'",
  103. }, { // tests some general counting
  104. code: "{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}",
  105. want: `3`,
  106. }, { // tests that depth is reported correctly
  107. code: "{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}",
  108. want: `[0,1,2]`,
  109. }, { // tests memory length
  110. code: "{lengths: [], step: function(log) { this.lengths.push(log.memory.length()); }, fault: function() {}, result: function() { return this.lengths; }}",
  111. want: `[0,0,0]`,
  112. }, { // tests to-string of opcodes
  113. code: "{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}",
  114. want: `["PUSH1","PUSH1","STOP"]`,
  115. }, { // tests intrinsic gas
  116. code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}",
  117. want: `"100000.6.21000"`,
  118. }, {
  119. code: "{res: null, step: function(log) {}, fault: function() {}, result: function() { return toWord('0xffaa') }}",
  120. want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":255,"31":170}`,
  121. }, { // test feeding a buffer back into go
  122. code: "{res: null, step: function(log) { var address = log.contract.getAddress(); this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}",
  123. want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`,
  124. }, {
  125. code: "{res: null, step: function(log) { var address = '0x0000000000000000000000000000000000000000'; this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}",
  126. want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`,
  127. }, {
  128. code: "{res: null, step: function(log) { var address = Array.prototype.slice.call(log.contract.getAddress()); this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}",
  129. want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`,
  130. },
  131. } {
  132. if have, err := execTracer(tt.code); tt.want != string(have) || tt.fail != err {
  133. t.Errorf("testcase %d: expected return value to be '%s' got '%s', error to be '%s' got '%s'\n\tcode: %v", i, tt.want, string(have), tt.fail, err, tt.code)
  134. }
  135. }
  136. }
  137. func TestHalt(t *testing.T) {
  138. timeout := errors.New("stahp")
  139. tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil, nil)
  140. if err != nil {
  141. t.Fatal(err)
  142. }
  143. go func() {
  144. time.Sleep(1 * time.Second)
  145. tracer.Stop(timeout)
  146. }()
  147. if _, err = runTrace(tracer, testCtx(), params.TestChainConfig); !strings.Contains(err.Error(), "stahp") {
  148. t.Errorf("Expected timeout error, got %v", err)
  149. }
  150. }
  151. func TestHaltBetweenSteps(t *testing.T) {
  152. tracer, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil, nil)
  153. if err != nil {
  154. t.Fatal(err)
  155. }
  156. env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
  157. scope := &vm.ScopeContext{
  158. Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
  159. }
  160. tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0))
  161. tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil)
  162. timeout := errors.New("stahp")
  163. tracer.Stop(timeout)
  164. tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil)
  165. if _, err := tracer.GetResult(); !strings.Contains(err.Error(), timeout.Error()) {
  166. t.Errorf("Expected timeout error, got %v", err)
  167. }
  168. }
  169. // testNoStepExec tests a regular value transfer (no exec), and accessing the statedb
  170. // in 'result'
  171. func TestNoStepExec(t *testing.T) {
  172. execTracer := func(code string) []byte {
  173. t.Helper()
  174. tracer, err := newJsTracer(code, nil, nil)
  175. if err != nil {
  176. t.Fatal(err)
  177. }
  178. env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
  179. tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0))
  180. tracer.CaptureEnd(nil, 0, 1, nil)
  181. ret, err := tracer.GetResult()
  182. if err != nil {
  183. t.Fatal(err)
  184. }
  185. return ret
  186. }
  187. for i, tt := range []struct {
  188. code string
  189. want string
  190. }{
  191. { // tests that we don't panic on accessing the db methods
  192. code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx, db){ return db.getBalance(ctx.to)} }",
  193. want: `"0"`,
  194. },
  195. } {
  196. if have := execTracer(tt.code); tt.want != string(have) {
  197. t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code)
  198. }
  199. }
  200. }
  201. func TestIsPrecompile(t *testing.T) {
  202. chaincfg := &params.ChainConfig{ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP150Hash: common.Hash{}, EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(100), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(200), MuirGlacierBlock: big.NewInt(0), BerlinBlock: big.NewInt(300), LondonBlock: big.NewInt(0), TerminalTotalDifficulty: nil, Ethash: new(params.EthashConfig), Clique: nil}
  203. chaincfg.ByzantiumBlock = big.NewInt(100)
  204. chaincfg.IstanbulBlock = big.NewInt(200)
  205. chaincfg.BerlinBlock = big.NewInt(300)
  206. txCtx := vm.TxContext{GasPrice: big.NewInt(100000)}
  207. tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
  208. if err != nil {
  209. t.Fatal(err)
  210. }
  211. blockCtx := vm.BlockContext{BlockNumber: big.NewInt(150)}
  212. res, err := runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
  213. if err != nil {
  214. t.Error(err)
  215. }
  216. if string(res) != "false" {
  217. t.Errorf("tracer should not consider blake2f as precompile in byzantium")
  218. }
  219. tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
  220. blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)}
  221. res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
  222. if err != nil {
  223. t.Error(err)
  224. }
  225. if string(res) != "true" {
  226. t.Errorf("tracer should consider blake2f as precompile in istanbul")
  227. }
  228. }
  229. func TestEnterExit(t *testing.T) {
  230. // test that either both or none of enter() and exit() are defined
  231. if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context), nil); err == nil {
  232. t.Fatal("tracer creation should've failed without exit() definition")
  233. }
  234. if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context), nil); err != nil {
  235. t.Fatal(err)
  236. }
  237. // test that the enter and exit method are correctly invoked and the values passed
  238. tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context), nil)
  239. if err != nil {
  240. t.Fatal(err)
  241. }
  242. scope := &vm.ScopeContext{
  243. Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
  244. }
  245. tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int))
  246. tracer.CaptureExit([]byte{}, 400, nil)
  247. have, err := tracer.GetResult()
  248. if err != nil {
  249. t.Fatal(err)
  250. }
  251. want := `{"enters":1,"exits":1,"enterGas":1000,"gasUsed":400}`
  252. if string(have) != want {
  253. t.Errorf("Number of invocations of enter() and exit() is wrong. Have %s, want %s\n", have, want)
  254. }
  255. }
  256. func TestSetup(t *testing.T) {
  257. // Test empty config
  258. _, err := newJsTracer(`{setup: function(cfg) { if (cfg !== "{}") { throw("invalid empty config") } }, fault: function() {}, result: function() {}}`, new(tracers.Context), nil)
  259. if err != nil {
  260. t.Error(err)
  261. }
  262. cfg, err := json.Marshal(map[string]string{"foo": "bar"})
  263. if err != nil {
  264. t.Fatal(err)
  265. }
  266. // Test no setup func
  267. _, err = newJsTracer(`{fault: function() {}, result: function() {}}`, new(tracers.Context), cfg)
  268. if err != nil {
  269. t.Fatal(err)
  270. }
  271. // Test config value
  272. tracer, err := newJsTracer("{config: null, setup: function(cfg) { this.config = JSON.parse(cfg) }, step: function() {}, fault: function() {}, result: function() { return this.config.foo }}", new(tracers.Context), cfg)
  273. if err != nil {
  274. t.Fatal(err)
  275. }
  276. have, err := tracer.GetResult()
  277. if err != nil {
  278. t.Fatal(err)
  279. }
  280. if string(have) != `"bar"` {
  281. t.Errorf("tracer returned wrong result. have: %s, want: \"bar\"\n", string(have))
  282. }
  283. }