mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 21:26:42 +00:00
Merge 5027822bde into 9c3db1be1d
This commit is contained in:
commit
0ab465e84e
11 changed files with 570 additions and 88 deletions
118
cmd/evm/main.go
118
cmd/evm/main.go
|
|
@ -22,79 +22,135 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
code = flag.String("code", "", "evm code")
|
app *cli.App
|
||||||
loglevel = flag.Int("log", 4, "log level")
|
DebugFlag = cli.BoolFlag{
|
||||||
gas = flag.String("gas", "1000000000", "gas amount")
|
Name: "debug",
|
||||||
price = flag.String("price", "0", "gas price")
|
Usage: "output full trace logs",
|
||||||
value = flag.String("value", "0", "tx value")
|
}
|
||||||
dump = flag.Bool("dump", false, "dump state after run")
|
SegmentationFlag = cli.BoolFlag{
|
||||||
data = flag.String("data", "", "data")
|
Name: "nosegmentation",
|
||||||
|
Usage: "Disabled VM segmentation",
|
||||||
|
}
|
||||||
|
CodeFlag = cli.StringFlag{
|
||||||
|
Name: "code",
|
||||||
|
Usage: "EVM code",
|
||||||
|
}
|
||||||
|
GasFlag = cli.StringFlag{
|
||||||
|
Name: "gas",
|
||||||
|
Usage: "gas limit for the evm",
|
||||||
|
Value: "10000000000",
|
||||||
|
}
|
||||||
|
PriceFlag = cli.StringFlag{
|
||||||
|
Name: "price",
|
||||||
|
Usage: "price set for the evm",
|
||||||
|
Value: "0",
|
||||||
|
}
|
||||||
|
ValueFlag = cli.StringFlag{
|
||||||
|
Name: "value",
|
||||||
|
Usage: "value set for the evm",
|
||||||
|
Value: "0",
|
||||||
|
}
|
||||||
|
DumpFlag = cli.BoolFlag{
|
||||||
|
Name: "dump",
|
||||||
|
Usage: "dumps the state after the run",
|
||||||
|
}
|
||||||
|
InputFlag = cli.StringFlag{
|
||||||
|
Name: "input",
|
||||||
|
Usage: "input for the EVM",
|
||||||
|
}
|
||||||
|
SysStatFlag = cli.BoolFlag{
|
||||||
|
Name: "sysstat",
|
||||||
|
Usage: "display system stats",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func perr(v ...interface{}) {
|
func init() {
|
||||||
fmt.Println(v...)
|
app = utils.NewApp("0.2", "the evm command line interface")
|
||||||
//os.Exit(1)
|
app.Flags = []cli.Flag{
|
||||||
|
DebugFlag,
|
||||||
|
SegmentationFlag,
|
||||||
|
SysStatFlag,
|
||||||
|
CodeFlag,
|
||||||
|
GasFlag,
|
||||||
|
PriceFlag,
|
||||||
|
ValueFlag,
|
||||||
|
DumpFlag,
|
||||||
|
InputFlag,
|
||||||
|
}
|
||||||
|
app.Action = run
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func run(ctx *cli.Context) {
|
||||||
flag.Parse()
|
vm.Debug = ctx.GlobalBool(DebugFlag.Name)
|
||||||
|
vm.DisableSegmentation = ctx.GlobalBool(SegmentationFlag.Name)
|
||||||
|
|
||||||
logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(*loglevel)))
|
|
||||||
|
|
||||||
vm.Debug = true
|
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
statedb := state.New(common.Hash{}, db)
|
statedb := state.New(common.Hash{}, db)
|
||||||
sender := statedb.CreateAccount(common.StringToAddress("sender"))
|
sender := statedb.CreateAccount(common.StringToAddress("sender"))
|
||||||
receiver := statedb.CreateAccount(common.StringToAddress("receiver"))
|
receiver := statedb.CreateAccount(common.StringToAddress("receiver"))
|
||||||
receiver.SetCode(common.Hex2Bytes(*code))
|
receiver.SetCode(common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name)))
|
||||||
|
|
||||||
vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(*value))
|
vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)))
|
||||||
|
|
||||||
tstart := time.Now()
|
tstart := time.Now()
|
||||||
|
ret, e := vmenv.Call(
|
||||||
|
sender,
|
||||||
|
receiver.Address(),
|
||||||
|
common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)),
|
||||||
|
common.Big(ctx.GlobalString(GasFlag.Name)),
|
||||||
|
common.Big(ctx.GlobalString(PriceFlag.Name)),
|
||||||
|
common.Big(ctx.GlobalString(ValueFlag.Name)),
|
||||||
|
)
|
||||||
|
vmdone := time.Since(tstart)
|
||||||
|
|
||||||
ret, e := vmenv.Call(sender, receiver.Address(), common.Hex2Bytes(*data), common.Big(*gas), common.Big(*price), common.Big(*value))
|
|
||||||
|
|
||||||
logger.Flush()
|
|
||||||
if e != nil {
|
if e != nil {
|
||||||
perr(e)
|
fmt.Println(e)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *dump {
|
if ctx.GlobalBool(DumpFlag.Name) {
|
||||||
fmt.Println(string(statedb.Dump()))
|
fmt.Println(string(statedb.Dump()))
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.StdErrFormat(vmenv.StructLogs())
|
vm.StdErrFormat(vmenv.StructLogs())
|
||||||
|
|
||||||
var mem runtime.MemStats
|
if ctx.GlobalBool(SysStatFlag.Name) {
|
||||||
runtime.ReadMemStats(&mem)
|
var mem runtime.MemStats
|
||||||
fmt.Printf("vm took %v\n", time.Since(tstart))
|
runtime.ReadMemStats(&mem)
|
||||||
fmt.Printf(`alloc: %d
|
fmt.Printf("vm took %v\n", vmdone)
|
||||||
|
fmt.Printf(`alloc: %d
|
||||||
tot alloc: %d
|
tot alloc: %d
|
||||||
no. malloc: %d
|
no. malloc: %d
|
||||||
heap alloc: %d
|
heap alloc: %d
|
||||||
heap objs: %d
|
heap objs: %d
|
||||||
num gc: %d
|
num gc: %d
|
||||||
`, mem.Alloc, mem.TotalAlloc, mem.Mallocs, mem.HeapAlloc, mem.HeapObjects, mem.NumGC)
|
`, mem.Alloc, mem.TotalAlloc, mem.Mallocs, mem.HeapAlloc, mem.HeapObjects, mem.NumGC)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("%x\n", ret)
|
fmt.Printf("OUT: 0x%x\n", ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type VMEnv struct {
|
type VMEnv struct {
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ func (self *StateObject) AddGas(gas, price *big.Int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StateObject) Copy() *StateObject {
|
func (self *StateObject) Copy() *StateObject {
|
||||||
stateObject := NewStateObject(self.Address(), self.db)
|
stateObject := &StateObject{db: self.db, address: self.address, balance: new(big.Int), gasPool: new(big.Int), dirty: true}
|
||||||
stateObject.balance.Set(self.balance)
|
stateObject.balance.Set(self.balance)
|
||||||
stateObject.codeHash = common.CopyBytes(self.codeHash)
|
stateObject.codeHash = common.CopyBytes(self.codeHash)
|
||||||
stateObject.nonce = self.nonce
|
stateObject.nonce = self.nonce
|
||||||
|
|
|
||||||
|
|
@ -26,16 +26,25 @@ type Context struct {
|
||||||
Args []byte
|
Args []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dests destinations
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dests = make(destinations)
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new context for the given data items.
|
// Create a new context for the given data items.
|
||||||
func NewContext(caller ContextRef, object ContextRef, value, gas, price *big.Int) *Context {
|
func NewContext(caller ContextRef, object ContextRef, value, gas, price *big.Int) *Context {
|
||||||
c := &Context{caller: caller, self: object, Args: nil}
|
c := &Context{caller: caller, self: object, Args: nil}
|
||||||
|
|
||||||
if parent, ok := caller.(*Context); ok {
|
c.jumpdests = dests
|
||||||
// Reuse JUMPDEST analysis from parent context if available.
|
/*
|
||||||
c.jumpdests = parent.jumpdests
|
if parent, ok := caller.(*Context); ok {
|
||||||
} else {
|
// Reuse JUMPDEST analysis from parent context if available.
|
||||||
c.jumpdests = make(destinations)
|
c.jumpdests = parent.jumpdests
|
||||||
}
|
} else {
|
||||||
|
c.jumpdests = make(destinations)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Gas should be a pointer so it can safely be reduced through the run
|
// Gas should be a pointer so it can safely be reduced through the run
|
||||||
// This pointer will be off the state transition
|
// This pointer will be off the state transition
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ func baseCheck(op OpCode, stack *stack, gas *big.Int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.stackPush > 0 && len(stack.data)-r.stackPop+r.stackPush > int(params.StackLimit.Int64())+1 {
|
if r.stackPush > 0 && stack.len()-r.stackPop+r.stackPush > int(params.StackLimit.Int64()) {
|
||||||
return fmt.Errorf("stack limit reached %d (%d)", len(stack.data), params.StackLimit.Int64())
|
return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit.Int64())
|
||||||
}
|
}
|
||||||
|
|
||||||
gas.Add(gas, r.gas)
|
gas.Add(gas, r.gas)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func StdErrFormat(logs []StructLog) {
|
func StdErrFormat(logs []StructLog) {
|
||||||
fmt.Fprintf(os.Stderr, "VM STAT %d OPs\n", len(logs))
|
|
||||||
for _, log := range logs {
|
for _, log := range logs {
|
||||||
fmt.Fprintf(os.Stderr, "PC %08d: %s GAS: %v COST: %v", log.Pc, log.Op, log.Gas, log.GasCost)
|
fmt.Fprintf(os.Stderr, "PC %08d: %s GAS: %v COST: %v", log.Pc, log.Op, log.Gas, log.GasCost)
|
||||||
if log.Err != nil {
|
if log.Err != nil {
|
||||||
|
|
|
||||||
283
core/vm/native.go
Normal file
283
core/vm/native.go
Normal file
|
|
@ -0,0 +1,283 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/hashicorp/golang-lru"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
segments *lru.Cache
|
||||||
|
DisableSegmentation bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
segments, _ = lru.New(256)
|
||||||
|
}
|
||||||
|
|
||||||
|
type codeSegments map[uint64]*segment
|
||||||
|
|
||||||
|
// operation is a "native" implementation of an OpCode
|
||||||
|
type operation struct {
|
||||||
|
fn func(*stack, []byte)
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type segment struct {
|
||||||
|
cstart, cend, next uint64
|
||||||
|
msize uint64
|
||||||
|
gas *big.Int
|
||||||
|
|
||||||
|
ssize int
|
||||||
|
|
||||||
|
ops []operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *segment) String() string {
|
||||||
|
return fmt.Sprintf("{start: %d end: %d ssize: %d gas: %v}", c.cstart, c.cend, c.ssize, c.gas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// native operations
|
||||||
|
|
||||||
|
func opPush(s *stack, data []byte) {
|
||||||
|
s.push(common.BytesToBig(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opAdd(s *stack, data []byte) {
|
||||||
|
s.push(U256(new(big.Int).Add(s.pop(), s.pop())))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opSub(s *stack, data []byte) {
|
||||||
|
s.push(U256(new(big.Int).Sub(s.pop(), s.pop())))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opMul(s *stack, data []byte) {
|
||||||
|
s.push(U256(new(big.Int).Mul(s.pop(), s.pop())))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opDiv(s *stack, data []byte) {
|
||||||
|
x, y := s.pop(), s.pop()
|
||||||
|
|
||||||
|
if y.Cmp(common.Big0) != 0 {
|
||||||
|
new(big.Int).Div(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
U256(new(big.Int))
|
||||||
|
|
||||||
|
// pop result back on the s
|
||||||
|
s.push(new(big.Int))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opSdiv(s *stack, data []byte) {
|
||||||
|
x, y := S256(s.pop()), S256(s.pop())
|
||||||
|
|
||||||
|
if y.Cmp(common.Big0) == 0 {
|
||||||
|
new(big.Int).Set(common.Big0)
|
||||||
|
} else {
|
||||||
|
n := new(big.Int)
|
||||||
|
if new(big.Int).Mul(x, y).Cmp(common.Big0) < 0 {
|
||||||
|
n.SetInt64(-1)
|
||||||
|
} else {
|
||||||
|
n.SetInt64(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
new(big.Int).Div(x.Abs(x), y.Abs(y)).Mul(new(big.Int), n)
|
||||||
|
|
||||||
|
U256(new(big.Int))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.push(new(big.Int))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opMod(s *stack, data []byte) {
|
||||||
|
x, y := s.pop(), s.pop()
|
||||||
|
|
||||||
|
if y.Cmp(common.Big0) == 0 {
|
||||||
|
new(big.Int).Set(common.Big0)
|
||||||
|
} else {
|
||||||
|
new(big.Int).Mod(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
U256(new(big.Int))
|
||||||
|
|
||||||
|
s.push(new(big.Int))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opSmod(s *stack, data []byte) {
|
||||||
|
x, y := S256(s.pop()), S256(s.pop())
|
||||||
|
|
||||||
|
if y.Cmp(common.Big0) == 0 {
|
||||||
|
new(big.Int).Set(common.Big0)
|
||||||
|
} else {
|
||||||
|
n := new(big.Int)
|
||||||
|
if x.Cmp(common.Big0) < 0 {
|
||||||
|
n.SetInt64(-1)
|
||||||
|
} else {
|
||||||
|
n.SetInt64(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
new(big.Int).Mod(x.Abs(x), y.Abs(y)).Mul(new(big.Int), n)
|
||||||
|
|
||||||
|
U256(new(big.Int))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.push(new(big.Int))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func opExp(s *stack, data []byte) {
|
||||||
|
x, y := s.pop(), s.pop()
|
||||||
|
|
||||||
|
new(big.Int).Exp(x, y, Pow256)
|
||||||
|
|
||||||
|
U256(new(big.Int))
|
||||||
|
|
||||||
|
s.push(new(big.Int))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opSignextend(s *stack, data []byte) {
|
||||||
|
back := s.pop()
|
||||||
|
if back.Cmp(big.NewInt(31)) < 0 {
|
||||||
|
bit := uint(back.Uint64()*8 + 7)
|
||||||
|
num := s.pop()
|
||||||
|
mask := new(big.Int).Lsh(common.Big1, bit)
|
||||||
|
mask.Sub(mask, common.Big1)
|
||||||
|
if common.BitTest(num, int(bit)) {
|
||||||
|
num.Or(num, mask.Not(mask))
|
||||||
|
} else {
|
||||||
|
num.And(num, mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
num = U256(num)
|
||||||
|
|
||||||
|
s.push(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func opNot(s *stack, data []byte) {
|
||||||
|
s.push(U256(new(big.Int).Not(s.pop())))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opLt(s *stack, data []byte) {
|
||||||
|
x, y := s.pop(), s.pop()
|
||||||
|
|
||||||
|
// x < y
|
||||||
|
if x.Cmp(y) < 0 {
|
||||||
|
s.push(common.BigTrue)
|
||||||
|
} else {
|
||||||
|
s.push(common.BigFalse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func opGt(s *stack, data []byte) {
|
||||||
|
x, y := s.pop(), s.pop()
|
||||||
|
|
||||||
|
// x > y
|
||||||
|
if x.Cmp(y) > 0 {
|
||||||
|
s.push(common.BigTrue)
|
||||||
|
} else {
|
||||||
|
s.push(common.BigFalse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func opSlt(s *stack, data []byte) {
|
||||||
|
x, y := S256(s.pop()), S256(s.pop())
|
||||||
|
|
||||||
|
// x < y
|
||||||
|
if x.Cmp(S256(y)) < 0 {
|
||||||
|
s.push(common.BigTrue)
|
||||||
|
} else {
|
||||||
|
s.push(common.BigFalse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func opSgt(s *stack, data []byte) {
|
||||||
|
x, y := S256(s.pop()), S256(s.pop())
|
||||||
|
|
||||||
|
// x > y
|
||||||
|
if x.Cmp(y) > 0 {
|
||||||
|
s.push(common.BigTrue)
|
||||||
|
} else {
|
||||||
|
s.push(common.BigFalse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func opEq(s *stack, data []byte) {
|
||||||
|
x, y := s.pop(), s.pop()
|
||||||
|
|
||||||
|
// x == y
|
||||||
|
if x.Cmp(y) == 0 {
|
||||||
|
s.push(common.BigTrue)
|
||||||
|
} else {
|
||||||
|
s.push(common.BigFalse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func opIszero(s *stack, data []byte) {
|
||||||
|
x := s.pop()
|
||||||
|
if x.Cmp(common.BigFalse) > 0 {
|
||||||
|
s.push(common.BigFalse)
|
||||||
|
} else {
|
||||||
|
s.push(common.BigTrue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func opAnd(s *stack, data []byte) {
|
||||||
|
s.push(new(big.Int).And(s.pop(), s.pop()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opOr(s *stack, data []byte) {
|
||||||
|
s.push(new(big.Int).Or(s.pop(), s.pop()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opXor(s *stack, data []byte) {
|
||||||
|
s.push(new(big.Int).Xor(s.pop(), s.pop()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func opByte(s *stack, data []byte) {
|
||||||
|
th, val := s.pop(), s.pop()
|
||||||
|
|
||||||
|
res := new(big.Int)
|
||||||
|
if th.Cmp(big.NewInt(32)) < 0 {
|
||||||
|
byt := big.NewInt(int64(common.LeftPadBytes(val.Bytes(), 32)[th.Int64()]))
|
||||||
|
|
||||||
|
res.Set(byt)
|
||||||
|
} else {
|
||||||
|
res.Set(common.BigFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.push(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func opAddmod(s *stack, data []byte) {
|
||||||
|
x := s.pop()
|
||||||
|
y := s.pop()
|
||||||
|
z := s.pop()
|
||||||
|
|
||||||
|
res := new(big.Int)
|
||||||
|
if z.Cmp(Zero) > 0 {
|
||||||
|
add := new(big.Int).Add(x, y)
|
||||||
|
res.Mod(add, z)
|
||||||
|
res = U256(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.push(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func opMulmod(s *stack, data []byte) {
|
||||||
|
x := s.pop()
|
||||||
|
y := s.pop()
|
||||||
|
z := s.pop()
|
||||||
|
|
||||||
|
res := new(big.Int)
|
||||||
|
if z.Cmp(Zero) > 0 {
|
||||||
|
res.Mul(x, y)
|
||||||
|
res.Mod(res, z)
|
||||||
|
|
||||||
|
res = U256(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.push(res)
|
||||||
|
}
|
||||||
|
|
@ -332,3 +332,15 @@ func (o OpCode) String() string {
|
||||||
|
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDynamic(op OpCode) bool {
|
||||||
|
switch op {
|
||||||
|
case CREATE, CALL, CALLCODE, CALLDATACOPY, GAS, MLOAD, JUMP, JUMPI, SUICIDE, STOP, RETURN, EXTCODECOPY, CODECOPY, MSTORE, MSTORE8, SSTORE:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := opCodeToString[op]; !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,32 +11,26 @@ func newstack() *stack {
|
||||||
|
|
||||||
type stack struct {
|
type stack struct {
|
||||||
data []*big.Int
|
data []*big.Int
|
||||||
ptr int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stack) Data() []*big.Int {
|
func (st *stack) Data() []*big.Int {
|
||||||
return st.data[:st.ptr]
|
return st.data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stack) push(d *big.Int) {
|
func (st *stack) push(d *big.Int) {
|
||||||
// NOTE push limit (1024) is checked in baseCheck
|
// NOTE push limit (1024) is checked in baseCheck
|
||||||
stackItem := new(big.Int).Set(d)
|
stackItem := new(big.Int).Set(d)
|
||||||
if len(st.data) > st.ptr {
|
st.data = append(st.data, stackItem)
|
||||||
st.data[st.ptr] = stackItem
|
|
||||||
} else {
|
|
||||||
st.data = append(st.data, stackItem)
|
|
||||||
}
|
|
||||||
st.ptr++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stack) pop() (ret *big.Int) {
|
func (st *stack) pop() (ret *big.Int) {
|
||||||
st.ptr--
|
ret = st.data[len(st.data)-1]
|
||||||
ret = st.data[st.ptr]
|
st.data = st.data[:len(st.data)-1]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stack) len() int {
|
func (st *stack) len() int {
|
||||||
return st.ptr
|
return len(st.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stack) swap(n int) {
|
func (st *stack) swap(n int) {
|
||||||
|
|
|
||||||
178
core/vm/vm.go
178
core/vm/vm.go
|
|
@ -2,6 +2,7 @@ package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -10,6 +11,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var t int
|
||||||
|
|
||||||
// Vm implements VirtualMachine
|
// Vm implements VirtualMachine
|
||||||
type Vm struct {
|
type Vm struct {
|
||||||
env Environment
|
env Environment
|
||||||
|
|
@ -37,6 +40,13 @@ func New(env Environment) *Vm {
|
||||||
func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
self.env.SetDepth(self.env.Depth() + 1)
|
self.env.SetDepth(self.env.Depth() + 1)
|
||||||
defer self.env.SetDepth(self.env.Depth() - 1)
|
defer self.env.SetDepth(self.env.Depth() - 1)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
StdErrFormat(self.env.StructLogs())
|
||||||
|
ret = nil
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
caller = context.caller
|
caller = context.caller
|
||||||
|
|
@ -44,14 +54,24 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
value = context.value
|
value = context.value
|
||||||
price = context.Price
|
price = context.Price
|
||||||
|
|
||||||
op OpCode // current opcode
|
op OpCode // current opcode
|
||||||
codehash = crypto.Sha3Hash(code) // codehash is used when doing jump dest caching
|
codehash = crypto.FnvHash(code) // codehash is used when doing jump dest caching
|
||||||
mem = NewMemory() // bound memory
|
mem = NewMemory() // bound memory
|
||||||
stack = newstack() // local stack
|
stack = newstack() // local stack
|
||||||
statedb = self.env.State() // current state
|
statedb = self.env.State() // current state
|
||||||
// For optimisation reason we're using uint64 as the program counter.
|
// 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. Pratically much less so feasible.
|
// It's theoretically possible to go above 2^64. The YP defines the PC to be uint256. Pratically much less so feasible.
|
||||||
pc = uint64(0) // program counter
|
pc = uint64(0) // program counter
|
||||||
|
ppc = uint64(0) // previous program counter
|
||||||
|
csegment *segment // current code segment
|
||||||
|
csegments codeSegments // program segments
|
||||||
|
nopay uint64 = 0
|
||||||
|
|
||||||
|
addop = func(op operation) {
|
||||||
|
if csegment != nil && !DisableSegmentation {
|
||||||
|
csegment.ops = append(csegment.ops, op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// jump evaluates and checks whether the given jump destination is a valid one
|
// jump evaluates and checks whether the given jump destination is a valid one
|
||||||
// if valid move the `pc` otherwise return an error.
|
// if valid move the `pc` otherwise return an error.
|
||||||
|
|
@ -69,13 +89,15 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
newMemSize *big.Int
|
newMemSize *big.Int
|
||||||
cost *big.Int
|
cost *big.Int
|
||||||
)
|
)
|
||||||
|
if c, ok := segments.Get(codehash); ok {
|
||||||
|
csegments = c.(codeSegments)
|
||||||
|
} else {
|
||||||
|
csegments = make(codeSegments)
|
||||||
|
segments.Add(codehash, csegments)
|
||||||
|
}
|
||||||
|
|
||||||
// User defer pattern to check for an error and, based on the error being nil or not, use all gas and return.
|
// User defer pattern to check for an error and, based on the error being nil or not, use all gas and return.
|
||||||
defer func() {
|
defer func() {
|
||||||
if self.After != nil {
|
|
||||||
self.After(context, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
self.log(pc, op, context.Gas, cost, mem, stack, context, err)
|
self.log(pc, op, context.Gas, cost, mem, stack, context, err)
|
||||||
|
|
||||||
|
|
@ -84,6 +106,16 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
|
|
||||||
ret = context.Return(nil)
|
ret = context.Return(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !DisableSegmentation && csegment != nil && csegment.cend == 0 {
|
||||||
|
if err != nil {
|
||||||
|
// delete this tracking segment. We couldn't fully determine the stats
|
||||||
|
delete(csegments, csegment.cstart)
|
||||||
|
} else {
|
||||||
|
csegment.cend = pc
|
||||||
|
csegment.ssize = int(math.Abs(math.Min(float64(stack.len()-csegment.ssize), 0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if context.CodeAddr != nil {
|
if context.CodeAddr != nil {
|
||||||
|
|
@ -98,31 +130,100 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// The base for all big integer arithmetic
|
cost = new(big.Int)
|
||||||
base := new(big.Int)
|
// Get the current operation location of pc
|
||||||
|
|
||||||
// Get the memory location of pc
|
|
||||||
op = context.GetOp(pc)
|
op = context.GetOp(pc)
|
||||||
|
if !DisableSegmentation {
|
||||||
|
if isDynamic(op) {
|
||||||
|
//fmt.Println("encountered dyn")
|
||||||
|
if csegment != nil && csegment.cend == 0 {
|
||||||
|
//fmt.Println("closing segment with", op, "@", ppc, csegment.gas)
|
||||||
|
// mark end of segment bound
|
||||||
|
csegment.cend = ppc
|
||||||
|
// set the next op for continuation
|
||||||
|
csegment.next = pc
|
||||||
|
// write out the required required elements on the stack. i.e. the amount of items
|
||||||
|
// required outside of the segment itself.
|
||||||
|
// 0: P, 1
|
||||||
|
// 1: J, 2
|
||||||
|
// 2: D -+
|
||||||
|
// 3: P, 2 |- M,0 = M,0 pushes 1. Requires 2. 1 outside bounds of M,0
|
||||||
|
// 4: ADD -+
|
||||||
|
// 5: J, 2
|
||||||
|
csegment.ssize = int(math.Abs(math.Min(float64(stack.len()-csegment.ssize), 0)))
|
||||||
|
}
|
||||||
|
csegment = nil
|
||||||
|
nopay = 0
|
||||||
|
} else if csegment == nil && pc != 0 {
|
||||||
|
// check if a segment is available to us and set it.
|
||||||
|
if seg, ok := csegments[pc]; ok {
|
||||||
|
//fmt.Println(pc, "using seg", seg)
|
||||||
|
nopay = seg.cend
|
||||||
|
|
||||||
// calculate the new memory size and gas price for the current executing opcode
|
// make sure there's enough stack items to complete the segment
|
||||||
newMemSize, cost, err = self.calculateGasAndSize(context, caller, op, statedb, mem, stack)
|
if err := stack.require(seg.ssize); err != nil {
|
||||||
if err != nil {
|
return context.Return(nil), err
|
||||||
return nil, err
|
}
|
||||||
|
|
||||||
|
cost.Set(seg.gas)
|
||||||
|
// use required gas for this segment
|
||||||
|
if !context.UseGas(seg.gas) {
|
||||||
|
context.UseGas(context.Gas)
|
||||||
|
|
||||||
|
return context.Return(nil), OutOfGasError{}
|
||||||
|
}
|
||||||
|
csegment = seg
|
||||||
|
|
||||||
|
/*
|
||||||
|
for _, operation := range seg.ops {
|
||||||
|
operation.fn(stack, operation.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pc = seg.next
|
||||||
|
continue
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
//fmt.Println("creating", pc)
|
||||||
|
// allocate a new segment for tracking information
|
||||||
|
csegment = &segment{pc, 0, 0, 0, new(big.Int), stack.len(), nil}
|
||||||
|
csegments[pc] = csegment
|
||||||
|
nopay = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// set previous program counter
|
||||||
|
ppc = pc
|
||||||
|
|
||||||
// Use the calculated gas. When insufficient gas is present, use all gas and return an
|
//fmt.Println(pc, nopay, pc > nopay, pc == 0)
|
||||||
// Out Of Gas error
|
if pc > nopay || pc == 0 || DisableSegmentation {
|
||||||
if !context.UseGas(cost) {
|
// calculate the new memory size and gas price for the current executing opcode
|
||||||
|
newMemSize, cost, err = self.calculateGasAndSize(context, caller, op, statedb, mem, stack)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
context.UseGas(context.Gas)
|
if csegment != nil {
|
||||||
|
csegment.gas.Add(csegment.gas, cost)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the calculated gas. When insufficient gas is present, use all gas and return an
|
||||||
|
// Out Of Gas error
|
||||||
|
if !context.UseGas(cost) {
|
||||||
|
context.UseGas(context.Gas)
|
||||||
|
|
||||||
|
return context.Return(nil), OutOfGasError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize the memory calculated previously
|
||||||
|
mem.Resize(newMemSize.Uint64())
|
||||||
|
|
||||||
return context.Return(nil), OutOfGasError{}
|
|
||||||
}
|
}
|
||||||
// Resize the memory calculated previously
|
|
||||||
mem.Resize(newMemSize.Uint64())
|
|
||||||
// Add a log message
|
// Add a log message
|
||||||
self.log(pc, op, context.Gas, cost, mem, stack, context, nil)
|
self.log(pc, op, context.Gas, cost, mem, stack, context, nil)
|
||||||
|
|
||||||
|
// The base for all big integer arithmetic
|
||||||
|
base := new(big.Int)
|
||||||
|
|
||||||
switch op {
|
switch op {
|
||||||
case ADD:
|
case ADD:
|
||||||
x, y := stack.pop(), stack.pop()
|
x, y := stack.pop(), stack.pop()
|
||||||
|
|
@ -131,6 +232,8 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
|
|
||||||
U256(base)
|
U256(base)
|
||||||
|
|
||||||
|
addop(operation{opAdd, nil})
|
||||||
|
|
||||||
// pop result back on the stack
|
// pop result back on the stack
|
||||||
stack.push(base)
|
stack.push(base)
|
||||||
case SUB:
|
case SUB:
|
||||||
|
|
@ -145,12 +248,12 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
case MUL:
|
case MUL:
|
||||||
x, y := stack.pop(), stack.pop()
|
x, y := stack.pop(), stack.pop()
|
||||||
|
|
||||||
base.Mul(x, y)
|
res := new(big.Int).Mul(x, y)
|
||||||
|
|
||||||
U256(base)
|
U256(res)
|
||||||
|
|
||||||
// pop result back on the stack
|
// pop result back on the stack
|
||||||
stack.push(base)
|
stack.push(res)
|
||||||
case DIV:
|
case DIV:
|
||||||
x, y := stack.pop(), stack.pop()
|
x, y := stack.pop(), stack.pop()
|
||||||
|
|
||||||
|
|
@ -221,6 +324,8 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
U256(base)
|
U256(base)
|
||||||
|
|
||||||
stack.push(base)
|
stack.push(base)
|
||||||
|
|
||||||
|
addop(operation{opExp, nil})
|
||||||
case SIGNEXTEND:
|
case SIGNEXTEND:
|
||||||
back := stack.pop()
|
back := stack.pop()
|
||||||
if back.Cmp(big.NewInt(31)) < 0 {
|
if back.Cmp(big.NewInt(31)) < 0 {
|
||||||
|
|
@ -348,7 +453,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
|
|
||||||
case SHA3:
|
case SHA3:
|
||||||
offset, size := stack.pop(), stack.pop()
|
offset, size := stack.pop(), stack.pop()
|
||||||
data := crypto.Sha3(mem.Get(offset.Int64(), size.Int64()))
|
data := crypto.Sha3(mem.GetPtr(offset.Int64(), size.Int64()))
|
||||||
|
|
||||||
stack.push(common.BigD(data))
|
stack.push(common.BigD(data))
|
||||||
|
|
||||||
|
|
@ -463,6 +568,9 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
case PUSH1, 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:
|
case PUSH1, 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:
|
||||||
size := uint64(op - PUSH1 + 1)
|
size := uint64(op - PUSH1 + 1)
|
||||||
byts := getData(code, new(big.Int).SetUint64(pc+1), new(big.Int).SetUint64(size))
|
byts := getData(code, new(big.Int).SetUint64(pc+1), new(big.Int).SetUint64(size))
|
||||||
|
|
||||||
|
addop(operation{opPush, byts})
|
||||||
|
|
||||||
// push value to stack
|
// push value to stack
|
||||||
stack.push(common.Bytes2Big(byts))
|
stack.push(common.Bytes2Big(byts))
|
||||||
pc += size
|
pc += size
|
||||||
|
|
@ -491,7 +599,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
|
|
||||||
case MLOAD:
|
case MLOAD:
|
||||||
offset := stack.pop()
|
offset := stack.pop()
|
||||||
val := common.BigD(mem.Get(offset.Int64(), 32))
|
val := common.BigD(mem.GetPtr(offset.Int64(), 32))
|
||||||
stack.push(val)
|
stack.push(val)
|
||||||
|
|
||||||
case MSTORE:
|
case MSTORE:
|
||||||
|
|
@ -538,6 +646,10 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
|
||||||
case MSIZE:
|
case MSIZE:
|
||||||
stack.push(big.NewInt(int64(mem.Len())))
|
stack.push(big.NewInt(int64(mem.Len())))
|
||||||
case GAS:
|
case GAS:
|
||||||
|
if t > 1 {
|
||||||
|
//return context.Return(nil), nil
|
||||||
|
}
|
||||||
|
t++
|
||||||
stack.push(context.Gas)
|
stack.push(context.Gas)
|
||||||
|
|
||||||
case CREATE:
|
case CREATE:
|
||||||
|
|
@ -800,7 +912,9 @@ func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, st
|
||||||
mem := make([]byte, len(memory.Data()))
|
mem := make([]byte, len(memory.Data()))
|
||||||
copy(mem, memory.Data())
|
copy(mem, memory.Data())
|
||||||
stck := make([]*big.Int, len(stack.Data()))
|
stck := make([]*big.Int, len(stack.Data()))
|
||||||
copy(stck, stack.Data())
|
for i, item := range stack.Data() {
|
||||||
|
stck[i] = new(big.Int).Set(item)
|
||||||
|
}
|
||||||
|
|
||||||
object := context.self.(*state.StateObject)
|
object := context.self.(*state.StateObject)
|
||||||
storage := make(map[common.Hash][]byte)
|
storage := make(map[common.Hash][]byte)
|
||||||
|
|
@ -808,7 +922,7 @@ func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, st
|
||||||
storage[common.BytesToHash(k)] = v
|
storage[common.BytesToHash(k)] = v
|
||||||
})
|
})
|
||||||
|
|
||||||
self.env.AddStructLog(StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, err})
|
self.env.AddStructLog(StructLog{pc, op, new(big.Int).Set(gas), new(big.Int).Set(cost), mem, stck, storage, err})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -35,6 +36,15 @@ func init() {
|
||||||
secp256k1n = common.String2Big("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")
|
secp256k1n = common.String2Big("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FnvHash(data ...[]byte) (h common.Hash) {
|
||||||
|
fnvHash := fnv.New32()
|
||||||
|
for _, b := range data {
|
||||||
|
fnvHash.Write(b)
|
||||||
|
}
|
||||||
|
fnvHash.Sum(h[:0])
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
func Sha3(data ...[]byte) []byte {
|
func Sha3(data ...[]byte) []byte {
|
||||||
d := sha3.NewKeccak256()
|
d := sha3.NewKeccak256()
|
||||||
for _, b := range data {
|
for _, b := range data {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,13 @@ func runStateTests(tests map[string]VmTest, skipTests []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
|
//vm.Debug = true
|
||||||
|
//vm.DisableSegmentation = true
|
||||||
|
/*
|
||||||
|
if name != "Call10" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
*/
|
||||||
if skipTest[name] {
|
if skipTest[name] {
|
||||||
glog.Infoln("Skipping state test", name)
|
glog.Infoln("Skipping state test", name)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -91,9 +98,7 @@ func runStateTest(test VmTest) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ret []byte
|
ret []byte
|
||||||
// gas *big.Int
|
|
||||||
// err error
|
|
||||||
logs state.Logs
|
logs state.Logs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -113,7 +118,7 @@ func runStateTest(test VmTest) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.Balance().Cmp(common.Big(account.Balance)) != 0 {
|
if obj.Balance().Cmp(common.Big(account.Balance)) != 0 {
|
||||||
return fmt.Errorf("(%x) balance failed. Expected %v, got %v => %v\n", obj.Address().Bytes()[:4], account.Balance, obj.Balance(), new(big.Int).Sub(common.Big(account.Balance), obj.Balance()))
|
return fmt.Errorf("(%x) balance failed. Expected %v, got %v => %v\n", obj.Address().Bytes()[:4], common.Big(account.Balance), obj.Balance(), new(big.Int).Sub(common.Big(account.Balance), obj.Balance()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.Nonce() != common.String2Big(account.Nonce).Uint64() {
|
if obj.Nonce() != common.String2Big(account.Nonce).Uint64() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue