549 lines
13 KiB
Go
549 lines
13 KiB
Go
package ethchain
|
|
|
|
import (
|
|
_ "bytes"
|
|
"fmt"
|
|
"github.com/ethereum/eth-go/ethutil"
|
|
_ "github.com/obscuren/secp256k1-go"
|
|
_ "math"
|
|
"math/big"
|
|
)
|
|
|
|
var (
|
|
GasStep = big.NewInt(1)
|
|
GasSha = big.NewInt(20)
|
|
GasSLoad = big.NewInt(20)
|
|
GasSStore = big.NewInt(100)
|
|
GasBalance = big.NewInt(20)
|
|
GasCreate = big.NewInt(100)
|
|
GasCall = big.NewInt(20)
|
|
GasMemory = big.NewInt(1)
|
|
)
|
|
|
|
func CalculateTxGas(initSize, scriptSize *big.Int) *big.Int {
|
|
totalGas := new(big.Int)
|
|
totalGas.Add(totalGas, GasCreate)
|
|
|
|
txTotalBytes := new(big.Int).Add(initSize, scriptSize)
|
|
txTotalBytes.Div(txTotalBytes, ethutil.Big32)
|
|
totalGas.Add(totalGas, new(big.Int).Mul(txTotalBytes, GasSStore))
|
|
|
|
return totalGas
|
|
}
|
|
|
|
type Vm struct {
|
|
txPool *TxPool
|
|
// Stack for processing contracts
|
|
stack *Stack
|
|
// non-persistent key/value memory storage
|
|
mem map[string]*big.Int
|
|
|
|
vars RuntimeVars
|
|
|
|
state *State
|
|
|
|
stateManager *StateManager
|
|
}
|
|
|
|
type RuntimeVars struct {
|
|
Origin []byte
|
|
BlockNumber uint64
|
|
PrevHash []byte
|
|
Coinbase []byte
|
|
Time int64
|
|
Diff *big.Int
|
|
TxData []string
|
|
}
|
|
|
|
func NewVm(state *State, stateManager *StateManager, vars RuntimeVars) *Vm {
|
|
return &Vm{vars: vars, state: state, stateManager: stateManager}
|
|
}
|
|
|
|
var Pow256 = ethutil.BigPow(2, 256)
|
|
|
|
var isRequireError = false
|
|
|
|
func (vm *Vm) RunClosure(closure *Closure, hook DebugHook) (ret []byte, err error) {
|
|
// Recover from any require exception
|
|
defer func() {
|
|
if r := recover(); r != nil /*&& isRequireError*/ {
|
|
ret = closure.Return(nil)
|
|
err = fmt.Errorf("%v", r)
|
|
fmt.Println("vm err", err)
|
|
}
|
|
}()
|
|
|
|
// If the amount of gas supplied is less equal to 0
|
|
if closure.Gas.Cmp(big.NewInt(0)) <= 0 {
|
|
// TODO Do something
|
|
}
|
|
|
|
// Memory for the current closure
|
|
mem := &Memory{}
|
|
// New stack (should this be shared?)
|
|
stack := NewStack()
|
|
require := func(m int) {
|
|
if stack.Len() < m {
|
|
isRequireError = true
|
|
panic(fmt.Sprintf("stack = %d, req = %d", stack.Len(), m))
|
|
}
|
|
}
|
|
|
|
// Instruction pointer
|
|
pc := big.NewInt(0)
|
|
// Current step count
|
|
step := 0
|
|
|
|
if ethutil.Config.Debug {
|
|
ethutil.Config.Log.Debugf("# op\n")
|
|
}
|
|
|
|
for {
|
|
// The base for all big integer arithmetic
|
|
base := new(big.Int)
|
|
|
|
step++
|
|
// Get the memory location of pc
|
|
val := closure.Get(pc)
|
|
// Get the opcode (it must be an opcode!)
|
|
op := OpCode(val.Uint())
|
|
if ethutil.Config.Debug {
|
|
ethutil.Config.Log.Debugf("%-3d %-4s", pc, op.String())
|
|
}
|
|
|
|
gas := new(big.Int)
|
|
useGas := func(amount *big.Int) {
|
|
gas.Add(gas, amount)
|
|
}
|
|
|
|
switch op {
|
|
case oSHA3:
|
|
useGas(GasSha)
|
|
case oSLOAD:
|
|
useGas(GasSLoad)
|
|
case oSSTORE:
|
|
var mult *big.Int
|
|
y, x := stack.Peekn()
|
|
val := closure.GetMem(x)
|
|
if val.IsEmpty() && len(y.Bytes()) > 0 {
|
|
mult = ethutil.Big2
|
|
} else if !val.IsEmpty() && len(y.Bytes()) == 0 {
|
|
mult = ethutil.Big0
|
|
} else {
|
|
mult = ethutil.Big1
|
|
}
|
|
useGas(new(big.Int).Mul(mult, GasSStore))
|
|
case oBALANCE:
|
|
useGas(GasBalance)
|
|
case oCREATE:
|
|
require(3)
|
|
|
|
args := stack.Get(big.NewInt(3))
|
|
initSize := new(big.Int).Add(args[1], args[0])
|
|
|
|
useGas(CalculateTxGas(initSize, ethutil.Big0))
|
|
case oCALL:
|
|
useGas(GasCall)
|
|
case oMLOAD, oMSIZE, oMSTORE8, oMSTORE:
|
|
useGas(GasMemory)
|
|
default:
|
|
useGas(GasStep)
|
|
}
|
|
|
|
if closure.Gas.Cmp(gas) < 0 {
|
|
ethutil.Config.Log.Debugln("Insufficient gas", closure.Gas, gas)
|
|
|
|
return closure.Return(nil), fmt.Errorf("insufficient gas %v %v", closure.Gas, gas)
|
|
}
|
|
|
|
// Sub the amount of gas from the remaining
|
|
closure.Gas.Sub(closure.Gas, gas)
|
|
|
|
switch op {
|
|
case oLOG:
|
|
stack.Print()
|
|
mem.Print()
|
|
case oSTOP: // Stop the closure
|
|
return closure.Return(nil), nil
|
|
|
|
// 0x20 range
|
|
case oADD:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
// (x + y) % 2 ** 256
|
|
base.Add(x, y)
|
|
// Pop result back on the stack
|
|
stack.Push(base)
|
|
case oSUB:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
// (x - y) % 2 ** 256
|
|
base.Sub(x, y)
|
|
// Pop result back on the stack
|
|
stack.Push(base)
|
|
case oMUL:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
// (x * y) % 2 ** 256
|
|
base.Mul(x, y)
|
|
// Pop result back on the stack
|
|
stack.Push(base)
|
|
case oDIV:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
// floor(x / y)
|
|
base.Div(x, y)
|
|
// Pop result back on the stack
|
|
stack.Push(base)
|
|
case oSDIV:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
// n > 2**255
|
|
if x.Cmp(Pow256) > 0 {
|
|
x.Sub(Pow256, x)
|
|
}
|
|
if y.Cmp(Pow256) > 0 {
|
|
y.Sub(Pow256, y)
|
|
}
|
|
z := new(big.Int)
|
|
z.Div(x, y)
|
|
if z.Cmp(Pow256) > 0 {
|
|
z.Sub(Pow256, z)
|
|
}
|
|
// Push result on to the stack
|
|
stack.Push(z)
|
|
case oMOD:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
base.Mod(x, y)
|
|
stack.Push(base)
|
|
case oSMOD:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
// n > 2**255
|
|
if x.Cmp(Pow256) > 0 {
|
|
x.Sub(Pow256, x)
|
|
}
|
|
if y.Cmp(Pow256) > 0 {
|
|
y.Sub(Pow256, y)
|
|
}
|
|
z := new(big.Int)
|
|
z.Mod(x, y)
|
|
if z.Cmp(Pow256) > 0 {
|
|
z.Sub(Pow256, z)
|
|
}
|
|
// Push result on to the stack
|
|
stack.Push(z)
|
|
case oEXP:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
base.Exp(x, y, Pow256)
|
|
|
|
stack.Push(base)
|
|
case oNEG:
|
|
require(1)
|
|
base.Sub(Pow256, stack.Pop())
|
|
stack.Push(base)
|
|
case oLT:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
// x < y
|
|
if x.Cmp(y) < 0 {
|
|
stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oGT:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
// x > y
|
|
if x.Cmp(y) > 0 {
|
|
stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oEQ:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
// x == y
|
|
if x.Cmp(y) == 0 {
|
|
stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oNOT:
|
|
require(1)
|
|
x := stack.Pop()
|
|
if x.Cmp(ethutil.BigFalse) == 0 {
|
|
stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
stack.Push(ethutil.BigFalse)
|
|
}
|
|
|
|
// 0x10 range
|
|
case oAND:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
if (x.Cmp(ethutil.BigTrue) >= 0) && (y.Cmp(ethutil.BigTrue) >= 0) {
|
|
stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
stack.Push(ethutil.BigFalse)
|
|
}
|
|
|
|
case oOR:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
if (x.Cmp(ethutil.BigInt0) >= 0) || (y.Cmp(ethutil.BigInt0) >= 0) {
|
|
stack.Push(ethutil.BigTrue)
|
|
} else {
|
|
stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oXOR:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
stack.Push(base.Xor(x, y))
|
|
case oBYTE:
|
|
require(2)
|
|
val, th := stack.Popn()
|
|
if th.Cmp(big.NewInt(32)) < 0 {
|
|
stack.Push(big.NewInt(int64(len(val.Bytes())-1) - th.Int64()))
|
|
} else {
|
|
stack.Push(ethutil.BigFalse)
|
|
}
|
|
|
|
// 0x20 range
|
|
case oSHA3:
|
|
require(2)
|
|
size, offset := stack.Popn()
|
|
data := mem.Get(offset.Int64(), size.Int64())
|
|
|
|
stack.Push(ethutil.BigD(data))
|
|
// 0x30 range
|
|
case oADDRESS:
|
|
stack.Push(ethutil.BigD(closure.Object().Address()))
|
|
case oBALANCE:
|
|
stack.Push(closure.Value)
|
|
case oORIGIN:
|
|
stack.Push(ethutil.BigD(vm.vars.Origin))
|
|
case oCALLER:
|
|
stack.Push(ethutil.BigD(closure.Callee().Address()))
|
|
case oCALLVALUE:
|
|
// FIXME: Original value of the call, not the current value
|
|
stack.Push(closure.Value)
|
|
case oCALLDATALOAD:
|
|
require(1)
|
|
offset := stack.Pop().Int64()
|
|
val := closure.Args[offset : offset+32]
|
|
|
|
stack.Push(ethutil.BigD(val))
|
|
case oCALLDATASIZE:
|
|
stack.Push(big.NewInt(int64(len(closure.Args))))
|
|
case oGASPRICE:
|
|
stack.Push(closure.Price)
|
|
|
|
// 0x40 range
|
|
case oPREVHASH:
|
|
stack.Push(ethutil.BigD(vm.vars.PrevHash))
|
|
case oCOINBASE:
|
|
stack.Push(ethutil.BigD(vm.vars.Coinbase))
|
|
case oTIMESTAMP:
|
|
stack.Push(big.NewInt(vm.vars.Time))
|
|
case oNUMBER:
|
|
stack.Push(big.NewInt(int64(vm.vars.BlockNumber)))
|
|
case oDIFFICULTY:
|
|
stack.Push(vm.vars.Diff)
|
|
case oGASLIMIT:
|
|
// TODO
|
|
stack.Push(big.NewInt(0))
|
|
|
|
// 0x50 range
|
|
case oPUSH: // Push PC+1 on to the stack
|
|
pc.Add(pc, ethutil.Big1)
|
|
data := closure.Gets(pc, big.NewInt(32))
|
|
val := ethutil.BigD(data.Bytes())
|
|
|
|
// Push value to stack
|
|
stack.Push(val)
|
|
|
|
pc.Add(pc, big.NewInt(31))
|
|
step++
|
|
case oPUSH20:
|
|
pc.Add(pc, ethutil.Big1)
|
|
data := closure.Gets(pc, big.NewInt(20))
|
|
val := ethutil.BigD(data.Bytes())
|
|
|
|
// Push value to stack
|
|
stack.Push(val)
|
|
|
|
pc.Add(pc, big.NewInt(19))
|
|
step++
|
|
case oPOP:
|
|
require(1)
|
|
stack.Pop()
|
|
case oDUP:
|
|
require(1)
|
|
stack.Push(stack.Peek())
|
|
case oSWAP:
|
|
require(2)
|
|
x, y := stack.Popn()
|
|
stack.Push(y)
|
|
stack.Push(x)
|
|
case oMLOAD:
|
|
require(1)
|
|
offset := stack.Pop()
|
|
stack.Push(ethutil.BigD(mem.Get(offset.Int64(), 32)))
|
|
case oMSTORE: // Store the value at stack top-1 in to memory at location stack top
|
|
require(2)
|
|
// Pop value of the stack
|
|
val, mStart := stack.Popn()
|
|
mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(val, 256))
|
|
case oMSTORE8:
|
|
require(2)
|
|
val, mStart := stack.Popn()
|
|
base.And(val, new(big.Int).SetInt64(0xff))
|
|
mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(base, 256))
|
|
case oSLOAD:
|
|
require(1)
|
|
loc := stack.Pop()
|
|
val := closure.GetMem(loc)
|
|
stack.Push(val.BigInt())
|
|
case oSSTORE:
|
|
require(2)
|
|
val, loc := stack.Popn()
|
|
closure.SetMem(loc, ethutil.NewValue(val))
|
|
case oJUMP:
|
|
require(1)
|
|
pc = stack.Pop()
|
|
// Reduce pc by one because of the increment that's at the end of this for loop
|
|
pc.Sub(pc, ethutil.Big1)
|
|
case oJUMPI:
|
|
require(2)
|
|
cond, pos := stack.Popn()
|
|
if cond.Cmp(ethutil.BigTrue) == 0 {
|
|
pc = pos
|
|
pc.Sub(pc, ethutil.Big1)
|
|
}
|
|
case oPC:
|
|
stack.Push(pc)
|
|
case oMSIZE:
|
|
stack.Push(big.NewInt(int64(mem.Len())))
|
|
// 0x60 range
|
|
case oCREATE:
|
|
require(3)
|
|
|
|
value := stack.Pop()
|
|
size, offset := stack.Popn()
|
|
|
|
// Generate a new address
|
|
addr := ethutil.CreateAddress(closure.callee.Address(), closure.callee.N())
|
|
// Create a new contract
|
|
contract := NewContract(addr, value, []byte(""))
|
|
// Set the init script
|
|
contract.initScript = mem.Get(offset.Int64(), size.Int64())
|
|
// Transfer all remaining gas to the new
|
|
// contract so it may run the init script
|
|
gas := new(big.Int).Set(closure.Gas)
|
|
closure.Gas.Sub(closure.Gas, gas)
|
|
// Create the closure
|
|
closure := NewClosure(closure.callee,
|
|
closure.Object(),
|
|
contract.initScript,
|
|
vm.state,
|
|
gas,
|
|
closure.Price,
|
|
value)
|
|
// Call the closure and set the return value as
|
|
// main script.
|
|
closure.Script, err = closure.Call(vm, nil, hook)
|
|
if err != nil {
|
|
stack.Push(ethutil.BigFalse)
|
|
} else {
|
|
stack.Push(ethutil.BigD(addr))
|
|
|
|
vm.state.SetStateObject(contract)
|
|
}
|
|
case oCALL:
|
|
require(7)
|
|
// Closure addr
|
|
addr := stack.Pop()
|
|
// Pop gas and value of the stack.
|
|
gas, value := stack.Popn()
|
|
// Pop input size and offset
|
|
inSize, inOffset := stack.Popn()
|
|
// Pop return size and offset
|
|
retSize, retOffset := stack.Popn()
|
|
// Make sure there's enough gas
|
|
if closure.Gas.Cmp(gas) < 0 {
|
|
stack.Push(ethutil.BigFalse)
|
|
|
|
break
|
|
}
|
|
// Get the arguments from the memory
|
|
args := mem.Get(inOffset.Int64(), inSize.Int64())
|
|
// Fetch the contract which will serve as the closure body
|
|
contract := vm.state.GetContract(addr.Bytes())
|
|
|
|
if contract != nil {
|
|
// Prepay for the gas
|
|
// If gas is set to 0 use all remaining gas for the next call
|
|
if gas.Cmp(big.NewInt(0)) == 0 {
|
|
// Copy
|
|
gas = new(big.Int).Set(closure.Gas)
|
|
}
|
|
closure.Gas.Sub(closure.Gas, gas)
|
|
// Create a new callable closure
|
|
closure := NewClosure(closure, contract, contract.script, vm.state, gas, closure.Price, value)
|
|
// Executer the closure and get the return value (if any)
|
|
ret, err := closure.Call(vm, args, hook)
|
|
if err != nil {
|
|
stack.Push(ethutil.BigFalse)
|
|
// Reset the changes applied this object
|
|
//contract.State().Reset()
|
|
} else {
|
|
stack.Push(ethutil.BigTrue)
|
|
// Notify of the changes
|
|
vm.stateManager.Changed(contract)
|
|
}
|
|
|
|
mem.Set(retOffset.Int64(), retSize.Int64(), ret)
|
|
} else {
|
|
ethutil.Config.Log.Debugf("Contract %x not found\n", addr.Bytes())
|
|
stack.Push(ethutil.BigFalse)
|
|
}
|
|
case oRETURN:
|
|
require(2)
|
|
size, offset := stack.Popn()
|
|
ret := mem.Get(offset.Int64(), size.Int64())
|
|
|
|
return closure.Return(ret), nil
|
|
case oSUICIDE:
|
|
/*
|
|
recAddr := stack.Pop().Bytes()
|
|
// Purge all memory
|
|
deletedMemory := contract.state.Purge()
|
|
// Add refunds to the pop'ed address
|
|
refund := new(big.Int).Mul(StoreFee, big.NewInt(int64(deletedMemory)))
|
|
account := state.GetAccount(recAddr)
|
|
account.Amount.Add(account.Amount, refund)
|
|
// Update the refunding address
|
|
state.UpdateAccount(recAddr, account)
|
|
// Delete the contract
|
|
state.trie.Update(string(addr), "")
|
|
|
|
ethutil.Config.Log.Debugf("(%d) => %x\n", deletedMemory, recAddr)
|
|
break out
|
|
*/
|
|
default:
|
|
ethutil.Config.Log.Debugf("Invalid opcode %x\n", op)
|
|
|
|
return closure.Return(nil), fmt.Errorf("Invalid opcode %x", op)
|
|
}
|
|
|
|
pc.Add(pc, ethutil.Big1)
|
|
|
|
if hook != nil {
|
|
hook(step-1, op, mem, stack)
|
|
}
|
|
}
|
|
}
|