github.com/amazechain/amc@v0.1.3/internal/tracers/js/tracer_test.go (about)

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