diff --git a/vm/common.go b/vm/common.go index 83876162c..4ca4657f7 100644 --- a/vm/common.go +++ b/vm/common.go @@ -1,73 +1,54 @@ package vm import ( - "math/big" + "encoding/binary" ) var ( - GasStorageGet = Big(50) - GasStorageAdd = Big(20000) - GasStorageMod = Big(5000) - GasLogBase = Big(375) - GasLogTopic = Big(375) - GasLogByte = Big(8) - GasCreate = Big(32000) - GasCreateByte = Big(200) - GasCall = Big(40) - GasCallValueTransfer = Big(9000) - GasStipend = Big(2300) - GasCallNewAccount = Big(25000) - GasReturn = Big(0) - GasStop = Big(0) - GasJumpDest = Big(1) + Zero = Word{0} + One = Word{1} +) - RefundStorage = Big(15000) - RefundSuicide = Big(24000) +type Word [32]byte - GasMemWord = Big(3) - GasQuadCoeffDenom = Big(512) - GasContractByte = Big(200) - GasTransaction = Big(21000) - GasTxDataNonzeroByte = Big(68) - GasTxDataZeroByte = Big(4) - GasTx = Big(21000) - GasExp = Big(10) - GasExpByte = Big(10) +func (w Word) Copy() Word { return w } +func (w Word) Bytes() []byte { return w[:] } // copied. +func (w Word) IsZero() bool { + accum := byte(0) + for _, byt := range w { + accum |= byt + } + return accum == 0 +} - GasSha3Base = Big(30) - GasSha3Word = Big(6) - GasSha256Base = Big(60) - GasSha256Word = Big(12) - GasRipemdBase = Big(600) - GasRipemdWord = Big(12) - GasEcrecover = Big(3000) - GasIdentityBase = Big(15) - GasIdentityWord = Big(3) - GasCopyWord = Big(3) +func Uint64ToWord(i uint64) Word { + word := Word{} + PutUint64(word[:], i) + return word +} - Pow256 = BigPow(2, 256) +func BytesToWord(bz []byte) Word { + word := Word{} + copy(word[:], bz) + return word +} - LogTyPretty byte = 0x1 - LogTyDiff byte = 0x2 -) +func LeftPadWord(bz []byte) (word Word) { + copy(word[:], bz) + return +} -const MaxCallDepth = 1025 +func RightPadWord(bz []byte) (word Word) { + copy(word[32-len(bz):], bz) + return +} -func calcMemSize(off, l *big.Int) *big.Int { - if l.Cmp(Big0) == 0 { - return Big0 - } +//----------------------------------------------------------------------------- - return new(big.Int).Add(off, l) +func GetUint64(word Word) uint64 { + return binary.LittleEndian.Uint64(word[:]) } -// Mainly used for print variables and passing to Print* -func toValue(val *big.Int) interface{} { - // Let's assume a string on right padded zero's - b := val.Bytes() - if b[0] != 0 && b[len(b)-1] == 0x0 && b[len(b)-2] == 0x0 { - return string(b) - } - - return val +func PutUint64(dest []byte, i uint64) { + binary.LittleEndian.PutUint64(dest, i) } diff --git a/vm/environment.go b/vm/environment.go deleted file mode 100644 index da46c9054..000000000 --- a/vm/environment.go +++ /dev/null @@ -1,141 +0,0 @@ -package vm - -import ( - "errors" -) - -const ( - defaultDataStackCapacity = 10 -) - -var ( - ErrCallStackOverflow = errors.New("CallStackOverflow") - ErrCallStackUnderflow = errors.New("CallStackUnderflow") - ErrInsufficientGas = errors.New("InsufficientGas") -) - -type AppState interface { - - // Accounts - GetAccount([]byte) *Account - UpdateAccount(*Account) - - // Storage - GetStorage([]byte, []byte) - UpdateStorage([]byte, []byte) - RemoveStorage([]byte) - - // Logs - AddLog(*Log) -} - -type VMCall struct { - caller *Account - target *Account - code []byte - gasLimit uint64 - gasUsed uint64 - dataStack *Stack - memory *Memory -} - -func (vmCall *VMCall) gasLeft() uint { - return vmCall.gasLimit - vmCall.gasUsed -} - -type VMParams struct { - BlockHeight uint - BlockHash []byte - BlockTime int64 - GasLimit uint64 - GasPrice uint64 - CallStackLimit uint - Origin []byte -} - -//----------------------------------------------------------------------------- - -type VMEnvironment struct { - params VMParams - appState AppState - callStack []*VMCall - lastCall *VMCall -} - -func NewVMEnvironment(appState AppState, params VMParams) *VMEnvironment { - return &VMEnvironment{ - params: params, - appState: appState, - callStack: make([]*VMCall, 0, params.CallStackLimit), - lastCall: nil, - } -} - -// XXX I think this is all wrong. -// Begin a new transaction (root call) -func (env *VMEnvironment) SetupTransaction(caller, target *Account, gasLimit, value uint64, input []byte) error { - // TODO charge gas for transaction - var gasUsed uint64 = 0 - return env.setupCall(caller, target, gasUsed, gasLimit, value, input) -} - -// XXX I think this is all wrong. -// env.lastCall.target (env.callStack[-1]) is calling target. -func (env *VMEnvironment) SetupCall(target *Account, gasLimit, value uint64, input []byte) error { - - // Gas check - if env.lastCall.gasLeft() < gasLimit { - return ErrInsufficientGas - } - - // Depth check - if len(env.callStack) == env.params.CallStackLimit { - return ErrCallStackOverflow - } - - var gasUsed uint64 = 0 - var caller = env.lastCall.target - return env.setupCall(caller, target, gasUsed, gasLimit, value, input) -} - -// XXX I think this is all wrong. -func (env *VMEnvironment) setupCall(caller, target *Account, gasUsed, gasLimit uint64, input []byte) error { - - // Incr nonces - caller.IncrNonce() - - // TODO Charge TX and data gas - - // Value transfer - if value != 0 { - // TODO Charge for gas - err := caller.SubBalance(value) - if err != nil { - return err - } - - err = target.AddBalance(value) - if err != nil { - return err - } - } - - // Create new VMCall - vmCall := &VMCall{ - caller: caller, - target: target, - code: target.Code(), - gasLimit: gasLimit, - gasUsed: gasUsed, - dataStack: NewStack(defaultDataStackCapacity), - memory: NewMemory(), - } - env.callStack = append(env.callStack, vmCall) - env.lastCall = vmCall - - return nil -} - -func (env *VMEnvironment) CallStackDepth() int { - return len(env.callStack) -} diff --git a/vm/execution.go b/vm/execution.go deleted file mode 100644 index c93e7e0d7..000000000 --- a/vm/execution.go +++ /dev/null @@ -1,74 +0,0 @@ -package vm - -import ( - "math/big" - "time" - - "github.com/tendermint/tendermint/state" -) - -type Execution struct { - env Environment - address, input []byte - Gas, price, value *big.Int -} - -func NewExecution(env Environment, address, input []byte, gas, gasPrice, value *big.Int) *Execution { - return &Execution{env: env, address: address, input: input, Gas: gas, price: gasPrice, value: value} -} - -func (self *Execution) Addr() []byte { - return self.address -} - -func (self *Execution) Call(codeAddr []byte, caller ContextRef) ([]byte, error) { - // Retrieve the executing code - code := self.env.State().GetCode(codeAddr) - - return self.exec(code, codeAddr, caller) -} - -func (self *Execution) exec(code, contextAddr []byte, caller ContextRef) (ret []byte, err error) { - env := self.env - evm := NewVm(env) - if env.Depth() == MaxCallDepth { - caller.ReturnGas(self.Gas, self.price) - - return nil, DepthError{} - } - - vsnapshot := env.State().Copy() - if len(self.address) == 0 { - // Generate a new address - nonce := env.State().GetNonce(caller.Address()) - self.address = crypto.CreateAddress(caller.Address(), nonce) - env.State().SetNonce(caller.Address(), nonce+1) - } - - from, to := env.State().GetStateObject(caller.Address()), env.State().GetOrNewStateObject(self.address) - err = env.Transfer(from, to, self.value) - if err != nil { - env.State().Set(vsnapshot) - - caller.ReturnGas(self.Gas, self.price) - - return nil, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", self.value, from.Balance()) - } - - snapshot := env.State().Copy() - start := time.Now() - ret, err = evm.Run(to, caller, code, self.value, self.Gas, self.price, self.input) - chainlogger.Debugf("vm took %v\n", time.Since(start)) - if err != nil { - env.State().Set(snapshot) - } - - return -} - -func (self *Execution) Create(caller ContextRef) (ret []byte, err error, account *state.StateObject) { - ret, err = self.exec(self.input, nil, caller) - account = self.env.State().GetStateObject(self.address) - - return -} diff --git a/vm/gas.go b/vm/gas.go new file mode 100644 index 000000000..40a7b9a06 --- /dev/null +++ b/vm/gas.go @@ -0,0 +1,18 @@ +package vm + +const ( + GasSha3 uint64 = 1 + GasGetAccount uint64 = 1 + GasStorageCreate uint64 = 1 + GasStorageUpdate uint64 = 1 + + GasStackOp uint64 = 1 + + GasEcRecover uint64 = 1 + GasSha256Word uint64 = 1 + GasSha256Base uint64 = 1 + GasRipemd160Word uint64 = 1 + GasRipemd160Base uint64 = 1 + GasIdentityWord uint64 = 1 + GasIdentityBase uint64 = 1 +) diff --git a/vm/native.go b/vm/native.go new file mode 100644 index 000000000..467e0a022 --- /dev/null +++ b/vm/native.go @@ -0,0 +1,90 @@ +package vm + +import ( + "code.google.com/p/go.crypto/ripemd160" + "crypto/sha256" + "github.com/tendermint/tendermint/vm/secp256k1" + "github.com/tendermint/tendermint/vm/sha3" + + . "github.com/tendermint/tendermint/common" +) + +var nativeContracts = make(map[Word]NativeContract) + +func init() { + nativeContracts[Uint64ToWord(1)] = ecrecoverFunc + nativeContracts[Uint64ToWord(2)] = sha256Func + nativeContracts[Uint64ToWord(3)] = ripemd160Func + nativeContracts[Uint64ToWord(4)] = identityFunc +} + +//----------------------------------------------------------------------------- + +type NativeContract func(input []byte, gas *uint64) (output []byte, err error) + +func ecrecoverFunc(input []byte, gas *uint64) (output []byte, err error) { + // Deduct gas + gasRequired := GasEcRecover + if *gas < gasRequired { + return nil, ErrInsufficientGas + } else { + *gas -= gasRequired + } + // Recover + hash := input[:32] + v := byte(input[32] - 27) // ignore input[33:64], v is small. + sig := append(input[64:], v) + + recovered, err := secp256k1.RecoverPubkey(hash, sig) + if err != nil { + return nil, err + } + hashed := sha3.Sha3(recovered[1:]) + return RightPadBytes(hashed, 32), nil +} + +func sha256Func(input []byte, gas *uint64) (output []byte, err error) { + // Deduct gas + gasRequired := uint64((len(input)+31)/32)*GasSha256Word + GasSha256Base + if *gas < gasRequired { + return nil, ErrInsufficientGas + } else { + *gas -= gasRequired + } + // Hash + hasher := sha256.New() + _, err = hasher.Write(input) + if err != nil { + panic(err) + } + return hasher.Sum(nil), nil +} + +func ripemd160Func(input []byte, gas *uint64) (output []byte, err error) { + // Deduct gas + gasRequired := uint64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base + if *gas < gasRequired { + return nil, ErrInsufficientGas + } else { + *gas -= gasRequired + } + // Hash + hasher := ripemd160.New() + _, err = hasher.Write(input) + if err != nil { + panic(err) + } + return RightPadBytes(hasher.Sum(nil), 32), nil +} + +func identityFunc(input []byte, gas *uint64) (output []byte, err error) { + // Deduct gas + gasRequired := uint64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase + if *gas < gasRequired { + return nil, ErrInsufficientGas + } else { + *gas -= gasRequired + } + // Return identity + return input, nil +} diff --git a/vm/precompiled.go b/vm/precompiled.go deleted file mode 100644 index 10dbb9474..000000000 --- a/vm/precompiled.go +++ /dev/null @@ -1,74 +0,0 @@ -package vm - -import ( - "code.google.com/p/go.crypto/ripemd160" - "crypto/sha256" - "math/big" -) - -type PrecompiledAccount struct { - Gas func(l int) *big.Int - fn func(in []byte) []byte -} - -func (self PrecompiledAccount) Call(in []byte) []byte { - return self.fn(in) -} - -var Precompiled = PrecompiledContracts() - -// XXX Could set directly. Testing requires resetting and setting of pre compiled contracts. -func PrecompiledContracts() map[string]*PrecompiledAccount { - return map[string]*PrecompiledAccount{ - // ECRECOVER - /* - string(LeftPadBytes([]byte{1}, 20)): &PrecompiledAccount{func(l int) *big.Int { - return GasEcrecover - }, ecrecoverFunc}, - */ - - // SHA256 - string(LeftPadBytes([]byte{2}, 20)): &PrecompiledAccount{func(l int) *big.Int { - n := big.NewInt(int64(l+31) / 32) - n.Mul(n, GasSha256Word) - return n.Add(n, GasSha256Base) - }, sha256Func}, - - // RIPEMD160 - string(LeftPadBytes([]byte{3}, 20)): &PrecompiledAccount{func(l int) *big.Int { - n := big.NewInt(int64(l+31) / 32) - n.Mul(n, GasRipemdWord) - return n.Add(n, GasRipemdBase) - }, ripemd160Func}, - - string(LeftPadBytes([]byte{4}, 20)): &PrecompiledAccount{func(l int) *big.Int { - n := big.NewInt(int64(l+31) / 32) - n.Mul(n, GasIdentityWord) - - return n.Add(n, GasIdentityBase) - }, memCpy}, - } -} - -func sha256Func(in []byte) []byte { - hasher := sha256.New() - n, err := hasher.Write(in) - if err != nil { - panic(err) - } - return hasher.Sum(nil) -} - -func ripemd160Func(in []byte) []byte { - hasher := ripemd160.New() - n, err := hasher.Write(in) - if err != nil { - panic(err) - } - res := hasher.Sum(nil) - return LeftPadBytes(res, 32) -} - -func memCpy(in []byte) []byte { - return in -} diff --git a/vm/receipt.go b/vm/receipt.go deleted file mode 100644 index e8e0a216f..000000000 --- a/vm/receipt.go +++ /dev/null @@ -1,14 +0,0 @@ -package vm - -import () - -type Receipt struct { - Index uint - Address []byte - Topics [][]byte - Data []byte -} - -func (self *Receipt) String() string { - return fmt.Sprintf("[A=%x T=%x D=%x]", self.Address, self.Topics, self.Data) -} diff --git a/vm/secp256k1 b/vm/secp256k1 new file mode 160000 index 000000000..8a939c2f8 --- /dev/null +++ b/vm/secp256k1 @@ -0,0 +1 @@ +Subproject commit 8a939c2f861148885b43c58ce52a36882c516fd6 diff --git a/vm/stack.go b/vm/stack.go index d7fdd5669..4ac05ba9a 100644 --- a/vm/stack.go +++ b/vm/stack.go @@ -1,26 +1,9 @@ package vm import ( - "encoding/binary" - "errors" "fmt" ) -var ( - ErrDataStackOverflow = errors.New("DataStackOverflow") - ErrDataStackUnderflow = errors.New("DataStackUnderflow") -) - -type Word [4]uint64 - -func Bytes2Uint64(bz []byte) uint64 { - return binary.LittleEndian.Uint64(bz) -} - -func Uint642Bytes(dest []byte, i uint64) { - binary.LittleEndian.PutUint64(dest, i) -} - // Not goroutine safe type Stack struct { data []Word @@ -39,93 +22,88 @@ func NewStack(capacity int, gas *uint64, err *error) *Stack { } } -func (st *Stack) Push(d Word) error { - if st.ptr == cap(st.data) { - return ErrDataStackOverflow +func (st *Stack) useGas(gasToUse uint64) { + if *st.gas > gasToUse { + *st.gas -= gasToUse + } else { + st.setErr(ErrInsufficientGas) + } +} + +func (st *Stack) setErr(err error) { + if *st.err != nil { + *st.err = err } - st.data[st.ptr] = d - st.ptr++ } -func (st *Stack) Push64(i uint64) error { +func (st *Stack) Push(d Word) { + st.useGas(GasStackOp) if st.ptr == cap(st.data) { - return ErrDataStackOverflow + st.setErr(ErrDataStackOverflow) + return } - st.data[st.ptr] = [4]uint64{i, 0, 0, 0} + st.data[st.ptr] = d st.ptr++ } -func (st *Stack) PushBytes(bz []byte) error { +func (st *Stack) PushBytes(bz []byte) { if len(bz) != 32 { panic("Invalid bytes size: expected 32") } - if st.ptr == cap(st.data) { - return ErrDataStackOverflow - } - st.data[st.ptr] = [4]uint64{ - Bytes2Uint64(bz[0:8]), - Bytes2Uint64(bz[8:16]), - Bytes2Uint64(bz[16:24]), - Bytes2Uint64(bz[24:32]), - } - st.ptr++ + st.Push(BytesToWord(bz)) } -func (st *Stack) Pop() (Word, error) { - if st.ptr == 0 { - return Zero, ErrDataStackUnderflow - } - st.ptr-- - return st.data[st.ptr], nil +func (st *Stack) Push64(i uint64) { + st.Push(Uint64ToWord(i)) } -func (st *Stack) Pop64() (uint64, error) { +func (st *Stack) Pop() Word { + st.useGas(GasStackOp) if st.ptr == 0 { - return Zero, ErrDataStackUnderflow + st.setErr(ErrDataStackUnderflow) + return Zero } st.ptr-- - return st.data[st.ptr][0], nil + return st.data[st.ptr] } -func (st *Stack) PopBytes() ([]byte, error) { - if st.ptr == 0 { - return Zero, ErrDataStackUnderflow - } - st.ptr-- - res := make([]byte, 32) - copy(res[0:8], Uint642Bytes(st.data[st.ptr][0])) - copy(res[8:16], Uint642Bytes(st.data[st.ptr][1])) - copy(res[16:24], Uint642Bytes(st.data[st.ptr][2])) - copy(res[24:32], Uint642Bytes(st.data[st.ptr][3])) - return res, nil +func (st *Stack) PopBytes() []byte { + return st.Pop().Bytes() +} + +func (st *Stack) Pop64() uint64 { + return GetUint64(st.Pop()) } func (st *Stack) Len() int { return st.ptr } -func (st *Stack) Swap(n int) error { +func (st *Stack) Swap(n int) { + st.useGas(GasStackOp) if st.ptr < n { - return ErrDataStackUnderflow + st.setErr(ErrDataStackUnderflow) + return } st.data[st.ptr-n], st.data[st.ptr-1] = st.data[st.ptr-1], st.data[st.ptr-n] - return nil + return } func (st *Stack) Dup(n int) { + st.useGas(GasStackOp) + if st.ptr < n { + st.setErr(ErrDataStackUnderflow) + return + } st.Push(st.data[st.ptr-n]) + return } +// Not an opcode, costs no gas. func (st *Stack) Peek() Word { return st.data[st.ptr-1] } -func (st *Stack) Require(n int) error { - if st.ptr < n { - return ErrDataStackUnderflow - } -} - func (st *Stack) Print() { fmt.Println("### stack ###") if st.ptr > 0 { diff --git a/vm/types.go b/vm/types.go new file mode 100644 index 000000000..93dea8fb1 --- /dev/null +++ b/vm/types.go @@ -0,0 +1,39 @@ +package vm + +import () + +const ( + defaultDataStackCapacity = 10 +) + +type Account struct { + Address Word + Balance uint64 + Code []byte + Nonce uint64 + StateRoot Word +} + +type Log struct { + Address Word + Topics []Word + Data []byte + Height uint64 +} + +type AppState interface { + + // Accounts + GetAccount(Word) (*Account, error) + UpdateAccount(*Account) error + DeleteAccount(*Account) error + CreateAccount(Word, uint64) (*Account, error) + + // Storage + GetStorage(Word, Word) (Word, error) + SetStorage(Word, Word, Word) (bool, error) + RemoveStorage(Word, Word) error + + // Logs + AddLog(*Log) +} diff --git a/vm/vm.go b/vm/vm.go index 6e1457ab0..332448036 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -1,84 +1,96 @@ package vm import ( + "errors" "fmt" + "math" - sm "github.com/tendermint/tendermint/state" + . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/vm/sha3" ) -type Vm struct { - VMEnvironment -} +var ( + ErrInsufficientBalance = errors.New("Insufficient balance") + ErrInvalidJumpDest = errors.New("Invalid jump dest") + ErrInsufficientGas = errors.New("Insuffient gas") + ErrMemoryOutOfBounds = errors.New("Memory out of bounds") + ErrCodeOutOfBounds = errors.New("Code out of bounds") + ErrInputOutOfBounds = errors.New("Input out of bounds") + ErrCallStackOverflow = errors.New("Call stack overflow") + ErrCallStackUnderflow = errors.New("Call stack underflow") + ErrDataStackOverflow = errors.New("Data stack overflow") + ErrDataStackUnderflow = errors.New("Data stack underflow") + ErrInvalidContract = errors.New("Invalid contract") +) -func NewVM(appState AppState, params VMParams) *Vm { - vmEnv := NewVMEnvironment(appState, params) - return &VM{ - VMEnvironment: vmEnv, - } +const ( + dataStackCapacity = 1024 + callStackCapacity = 100 // TODO ensure usage. + memoryCapacity = 1024 * 1024 // 1 MB +) + +type VM struct { + appState AppState + params VMParams + + callDepth int } -// feeLimit: the maximum the caller is willing to pay for fees. -// gasLimit: the maximum gas that will be run. -func (vm *Vm) RunTransaction(caller, target *Account, feeLimit, gasLimit, value uint64, input []byte) (output []byte, err error) { +type VMParams struct { + BlockHeight uint64 + BlockHash Word + BlockTime int64 + GasLimit uint64 + GasPrice uint64 + CallStackLimit uint64 + Origin Word +} - if len(target.Code) == 0 { - panic("RunTransaction() requires target with code") +func NewVM(appState AppState, params VMParams) *VM { + return &VM{ + appState: appState, + params: params, + callDepth: 0, } +} - // Check the gasLimit vs feeLimit - // TODO +/* + // When running a transaction, the caller the pays for the fees. // Check caller's account balance vs feeLimit and value if caller.Balance < (feeLimit + value) { - return nil, ErrInsufficientAccountBalance + return nil, ErrInsufficientBalance } - - // Deduct balance from caller. caller.Balance -= (feeLimit + value) - vm.SetupTransaction(caller, target, gasLimit, value, input) +*/ - fmt.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.CallStackDepth(), caller.Address[:4], target.Address, len(target.Code), gasLimit, input) +// gas: the maximum gas that will be run. +// When the function returns, *gas will be the amount of remaining gas. +func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { - /* - if p := Precompiled[string(me.Address())]; p != nil { - return vm.RunPrecompiled(p, callData, context) - } - */ + if len(callee.Code) == 0 { + panic("Call() requires callee with code") + } - //----------------------------------- + fmt.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input) - // By the time we're here, the related VMCall context is already appended onto VMEnvironment.callStack var ( - code = target.Code pc uint64 = 0 - gas uint64 = call.gasLimit - err error = nil - stack = NewStack(defaultDataStackCapacity, &gas, &err) - memory = NewMemory(&gas, &err) - - // volatile, convenience - ok = false - - // TODO review this code. - jump = func(from, to uint64) error { - dest := CodeGetOp(code, to) - if dest != JUMPDEST { - return ErrInvalidJumpDest - } - pc = to - fmt.Printf(" ~> %v\n", to) - return nil - } + stack = NewStack(dataStackCapacity, gas, &err) + memory = make([]byte, memoryCapacity) + ok = false // convenience ) for { - var op = CodeGetOp(code, pc) - fmt.Printf("(pc) %-3d -o- %-14s (m) %-4d (s) %-4d ", pc, op.String(), mem.Len(), stack.Len()) + var op = codeGetOp(code, pc) + fmt.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len()) switch op { + case STOP: // 0x00 + return nil, nil + case ADD: // 0x01 x, y := stack.Pop64(), stack.Pop64() stack.Push64(x + y) @@ -241,345 +253,418 @@ func (vm *Vm) RunTransaction(caller, target *Account, feeLimit, gasLimit, value case BYTE: // 0x1A idx, val := stack.Pop64(), stack.Pop() - res := 0 + res := byte(0) if idx < 32 { - res = Uint642Bytes(val[idx/8])[idx%8] + res = val[idx] } - stack.Push64(res) - fmt.Printf(" => 0x%X", res) + stack.Push64(uint64(res)) + fmt.Printf(" => 0x%X\n", res) case SHA3: // 0x20 - if gas, ok = useGas(gas, GasSha3); !ok { - return ErrInsufficientGas + if ok = useGas(gas, GasSha3); !ok { + return nil, firstErr(err, ErrInsufficientGas) } offset, size := stack.Pop64(), stack.Pop64() - data := sha3.Sha3(memory.Get(offset, size)) + data, ok := subslice(memory, offset, size) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + data = sha3.Sha3(data) stack.PushBytes(data) - fmt.Printf(" => (%v) %X", size, data) + fmt.Printf(" => (%v) %X\n", size, data) case ADDRESS: // 0x30 - stack.PushBytes(RightPadBytes(context.Address(), 32)) - fmt.Printf(" => %X", RightPadBytes(context.Address(), 32)) + stack.Push(callee.Address) + fmt.Printf(" => %X\n", callee.Address) case BALANCE: // 0x31 - addr := stack.PopBytes() - if gas, ok = useGas(gas, GasGetAccount); !ok { - return ErrInsufficientGas + addr := stack.Pop() + if ok = useGas(gas, GasGetAccount); !ok { + return nil, firstErr(err, ErrInsufficientGas) + } + account, err_ := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported. + if err = firstErr(err, err_); err != nil { + return nil, err } - account := vm.GetAccount(addr) // TODO ensure that 20byte lengths are supported. balance := account.Balance stack.Push64(balance) - fmt.Printf(" => %v (%X)", balance, addr) + fmt.Printf(" => %v (%X)\n", balance, addr) case ORIGIN: // 0x32 - origin := vm.Origin() - stack.PushBytes(origin) - fmt.Printf(" => %X", origin) + origin := vm.params.Origin + stack.Push(origin) + fmt.Printf(" => %X\n", origin) case CALLER: // 0x33 - caller := vm.lastCall.caller - stack.PushBytes(caller.Address) - fmt.Printf(" => %X", caller.Address) + stack.Push(caller.Address) + fmt.Printf(" => %X\n", caller.Address) case CALLVALUE: // 0x34 stack.Push64(value) - fmt.Printf(" => %v", value) + fmt.Printf(" => %v\n", value) case CALLDATALOAD: // 0x35 offset := stack.Pop64() - data, _ := subslice(input, offset, 32) - stack.PushBytes(RightPadBytes(data), 32) - fmt.Printf(" => 0x%X", data) + data, ok := subslice(input, offset, 32) + if !ok { + return nil, firstErr(err, ErrInputOutOfBounds) + } + stack.Push(RightPadWord(data)) + fmt.Printf(" => 0x%X\n", data) case CALLDATASIZE: // 0x36 - stack.Push64(uint64(len(callData))) - fmt.Printf(" => %d", len(callData)) + stack.Push64(uint64(len(input))) + fmt.Printf(" => %d\n", len(input)) case CALLDATACOPY: // 0x37 memOff := stack.Pop64() inputOff := stack.Pop64() length := stack.Pop64() data, ok := subslice(input, inputOff, length) - if ok { - memory.Set(memOff, length, data) + if !ok { + return nil, firstErr(err, ErrInputOutOfBounds) } - fmt.Printf(" => [%v, %v, %v] %X", memOff, inputOff, length, data) + dest, ok := subslice(memory, memOff, length) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + copy(dest, data) + fmt.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) case CODESIZE: // 0x38 l := uint64(len(code)) stack.Push64(l) - fmt.Printf(" => %d", l) + fmt.Printf(" => %d\n", l) case CODECOPY: // 0x39 memOff := stack.Pop64() codeOff := stack.Pop64() length := stack.Pop64() data, ok := subslice(code, codeOff, length) - if ok { - memory.Set(memOff, length, data) + if !ok { + return nil, firstErr(err, ErrCodeOutOfBounds) + } + dest, ok := subslice(memory, memOff, length) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) } - fmt.Printf(" => [%v, %v, %v] %X", memOff, codeOff, length, data) + copy(dest, data) + fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case GASPRICE: // 0x3A stack.Push64(vm.params.GasPrice) - fmt.Printf(" => %X", vm.params.GasPrice) + fmt.Printf(" => %X\n", vm.params.GasPrice) case EXTCODESIZE: // 0x3B - addr := stack.PopBytes()[:20] - account := vm.GetAccount(addr) + addr := stack.Pop() + if ok = useGas(gas, GasGetAccount); !ok { + return nil, firstErr(err, ErrInsufficientGas) + } + account, err_ := vm.appState.GetAccount(addr) + if err = firstErr(err, err_); err != nil { + return nil, err + } code := account.Code l := uint64(len(code)) stack.Push64(l) - fmt.Printf(" => %d", l) + fmt.Printf(" => %d\n", l) case EXTCODECOPY: // 0x3C - addr := stack.PopBytes()[:20] - account := vm.GetAccount(addr) + addr := stack.Pop() + if ok = useGas(gas, GasGetAccount); !ok { + return nil, firstErr(err, ErrInsufficientGas) + } + account, err_ := vm.appState.GetAccount(addr) + if err = firstErr(err, err_); err != nil { + return nil, err + } code := account.Code memOff := stack.Pop64() codeOff := stack.Pop64() length := stack.Pop64() data, ok := subslice(code, codeOff, length) - if ok { - memory.Set(memOff, length, data) + if !ok { + return nil, ErrCodeOutOfBounds } - fmt.Printf(" => [%v, %v, %v] %X", memOff, codeOff, length, data) + dest, ok := subslice(memory, memOff, length) + if !ok { + return nil, ErrMemoryOutOfBounds + } + copy(dest, data) + fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case BLOCKHASH: // 0x40 - /* - num := stack.pop() - n := new(big.Int).Sub(vm.env.BlockHeight(), Big257) - if num.Cmp(n) > 0 && num.Cmp(vm.env.BlockHeight()) < 0 { - stack.push(Bytes2Big(vm.env.GetBlockHash(num.Uint64()))) - } else { - stack.push(Big0) - } - */ - stack.Push([4]Word{0, 0, 0, 0}) - fmt.Printf(" => 0x%X (NOT SUPPORTED)", stack.Peek().Bytes()) + stack.Push(Zero) + fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case COINBASE: // 0x41 - stack.Push([4]Word{0, 0, 0, 0}) - fmt.Printf(" => 0x%X (NOT SUPPORTED)", stack.Peek().Bytes()) + stack.Push(Zero) + fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case TIMESTAMP: // 0x42 time := vm.params.BlockTime stack.Push64(uint64(time)) - fmt.Printf(" => 0x%X", time) + fmt.Printf(" => 0x%X\n", time) case BLOCKHEIGHT: // 0x43 number := uint64(vm.params.BlockHeight) stack.Push64(number) - fmt.Printf(" => 0x%X", number) + fmt.Printf(" => 0x%X\n", number) case GASLIMIT: // 0x45 stack.Push64(vm.params.GasLimit) - fmt.Printf(" => %v", vm.params.GasLimit) + fmt.Printf(" => %v\n", vm.params.GasLimit) case POP: // 0x50 stack.Pop() - fmt.Printf(" => %v", vm.params.GasLimit) + fmt.Printf(" => %v\n", vm.params.GasLimit) case MLOAD: // 0x51 offset := stack.Pop64() - data, _ := subslice(input, offset, 32) - stack.PushBytes(RightPadBytes(data), 32) - fmt.Printf(" => 0x%X", data) - - offset := stack.pop() - val := Bytes2Big(mem.Get(offset.Int64(), 32)) - stack.push(val) - - fmt.Printf(" => 0x%X", val.Bytes()) - case MSTORE: // Store the value at stack top-1 in to memory at location stack top - // pop value of the stack - mStart, val := stack.pop(), stack.pop() - mem.Set(mStart.Uint64(), 32, Big2Bytes(val, 256)) - - fmt.Printf(" => 0x%X", val) - case MSTORE8: - off, val := stack.pop(), stack.pop() - - mem.store[off.Int64()] = byte(val.Int64() & 0xff) - - fmt.Printf(" => [%v] 0x%X", off, val) - case SLOAD: - loc := stack.pop() - val := Bytes2Big(state.GetState(context.Address(), loc.Bytes())) - stack.push(val) - - fmt.Printf(" {0x%X : 0x%X}", loc.Bytes(), val.Bytes()) - case SSTORE: - loc, val := stack.pop(), stack.pop() - state.SetState(context.Address(), loc.Bytes(), val) + data, ok := subslice(memory, offset, 32) + if !ok { + return nil, ErrMemoryOutOfBounds + } + stack.Push(RightPadWord(data)) + fmt.Printf(" => 0x%X\n", data) + + case MSTORE: // 0x52 + offset, data := stack.Pop64(), stack.Pop() + dest, ok := subslice(memory, offset, 32) + if !ok { + return nil, ErrMemoryOutOfBounds + } + copy(dest, data[:]) + fmt.Printf(" => 0x%X\n", data) - fmt.Printf(" {0x%X : 0x%X}", loc.Bytes(), val.Bytes()) - case JUMP: - jump(pc, stack.pop()) + case MSTORE8: // 0x53 + offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF) + if len(memory) <= int(offset) { + return nil, ErrMemoryOutOfBounds + } + memory[offset] = val + fmt.Printf(" => [%v] 0x%X\n", offset, val) + + case SLOAD: // 0x54 + loc := stack.Pop() + data, _ := vm.appState.GetStorage(callee.Address, loc) + stack.Push(data) + fmt.Printf(" {0x%X : 0x%X}\n", loc, data) + + case SSTORE: // 0x55 + loc, data := stack.Pop(), stack.Pop() + updated, err_ := vm.appState.SetStorage(callee.Address, loc, data) + if err = firstErr(err, err_); err != nil { + return nil, err + } + if updated { + useGas(gas, GasStorageUpdate) + } else { + useGas(gas, GasStorageCreate) + } + fmt.Printf(" {0x%X : 0x%X}\n", loc, data) + case JUMP: // 0x56 + err = jump(code, stack.Pop64()) continue - case JUMPI: - pos, cond := stack.pop(), stack.pop() - - if cond.Cmp(BigTrue) >= 0 { - jump(pc, pos) + case JUMPI: // 0x57 + pos, cond := stack.Pop64(), stack.Pop64() + if cond >= 1 { + err = jump(code, pos) continue } + fmt.Printf(" ~> false\n") + + case PC: // 0x58 + stack.Push64(pc) - fmt.Printf(" ~> false") + case MSIZE: // 0x59 + stack.Push64(uint64(len(memory))) - case JUMPDEST: - case PC: - stack.push(Big(int64(pc))) - case MSIZE: - stack.push(Big(int64(mem.Len()))) - case GAS: - stack.push(context.Gas) + case GAS: // 0x5A + stack.Push64(*gas) + fmt.Printf(" => %X\n", *gas) - fmt.Printf(" => %X", context.Gas) + case JUMPDEST: // 0x5B + // Do nothing 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: a := uint64(op - PUSH1 + 1) - byts := context.GetRangeValue(pc+1, a) - // push value to stack - stack.push(Bytes2Big(byts)) + codeSegment, ok := subslice(code, pc+1, a) + if !ok { + return nil, firstErr(err, ErrCodeOutOfBounds) + } + res := RightPadWord(codeSegment) + stack.Push(res) pc += a - - fmt.Printf(" => 0x%X", byts) + fmt.Printf(" => 0x%X\n", res) case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: n := int(op - DUP1 + 1) - stack.dup(n) + stack.Dup(n) + fmt.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes()) - fmt.Printf(" => [%d] 0x%X", n, stack.peek().Bytes()) case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: n := int(op - SWAP1 + 2) - stack.swap(n) + stack.Swap(n) + fmt.Printf(" => [%d]\n", n) - fmt.Printf(" => [%d]", n) case LOG0, LOG1, LOG2, LOG3, LOG4: n := int(op - LOG0) - topics := make([][]byte, n) - mStart, mSize := stack.pop(), stack.pop() + topics := make([]Word, n) + offset, size := stack.Pop64(), stack.Pop64() for i := 0; i < n; i++ { - topics[i] = LeftPadBytes(stack.pop().Bytes(), 32) + topics[i] = stack.Pop() } + data, ok := subslice(memory, offset, size) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + log := &Log{ + callee.Address, + topics, + data, + vm.params.BlockHeight, + } + vm.appState.AddLog(log) + fmt.Printf(" => %v\n", log) - data := mem.Get(mStart.Int64(), mSize.Int64()) - log := &Log{context.Address(), topics, data, vm.env.BlockHeight().Uint64()} - vm.env.AddLog(log) - - fmt.Printf(" => %v", log) + case CREATE: // 0xF0 + value := stack.Pop64() + offset, size := stack.Pop64(), stack.Pop64() + input, ok := subslice(memory, offset, size) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } - // 0x60 range - case CREATE: + // Check balance + if caller.Balance < value { + return nil, firstErr(err, ErrInsufficientBalance) + } else { + caller.Balance -= value + } - var ( - value = stack.pop() - offset, size = stack.pop(), stack.pop() - input = mem.Get(offset.Int64(), size.Int64()) - gas = new(big.Int).Set(context.Gas) - addr []byte - ) - vm.Endl() + // Create a new address + nonce := caller.Nonce + addr := createAddress(caller.Address, nonce) + caller.Nonce += 1 - context.UseGas(context.Gas) - ret, suberr, ref := Create(vm, context, nil, input, gas, price, value) - if suberr != nil { - stack.push(BigFalse) + // TODO charge for gas to create account _ the code length * GasCreateByte - fmt.Printf(" (*) 0x0 %v", suberr) + newAccount, err := vm.appState.CreateAccount(addr, value) + if err != nil { + stack.Push64(0) + fmt.Printf(" (*) 0x0 %v\n", err) } else { - - // gas < len(ret) * CreateDataGas == NO_CODE - dataGas := Big(int64(len(ret))) - dataGas.Mul(dataGas, GasCreateByte) - if context.UseGas(dataGas) { - ref.SetCode(ret) + // Run the input to get the contract code. + // The code as well as the input to the code are the same. + ret, err_ := vm.Call(callee, newAccount, input, input, value, gas) + if err_ != nil { + caller.Balance += value // Return the balance + stack.Push64(0) + } else { + newAccount.Code = ret // Set the code + stack.Push(newAccount.Address) } - addr = ref.Address() - - stack.push(Bytes2Big(addr)) - } - case CALL, CALLCODE: - gas := stack.pop() - // pop gas and value of the stack. - addr, value := stack.pop(), stack.pop() - value = U256(value) - // pop input size and offset - inOffset, inSize := stack.pop(), stack.pop() - // pop return size and offset - retOffset, retSize := stack.pop(), stack.pop() - - address := addr.Bytes() - fmt.Printf(" => %X", address).Endl() + case CALL, CALLCODE: // 0xF1, 0xF2 + gasLimit := stack.Pop64() + addr, value := stack.Pop(), stack.Pop64() + inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs + retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs + fmt.Printf(" => %X\n", addr) // Get the arguments from the memory - args := mem.Get(inOffset.Int64(), inSize.Int64()) - - if len(value.Bytes()) > 0 { - gas.Add(gas, GasStipend) + args, ok := subslice(memory, inOffset, inSize) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) } - var ( - ret []byte - err error - ) - if op == CALLCODE { - ret, err = CallCode(env, context, address, args, gas, price, value) + // Ensure that gasLimit is reasonable + if *gas < gasLimit { + return nil, firstErr(err, ErrInsufficientGas) } else { - ret, err = Call(env, context, address, args, gas, price, value) + *gas -= gasLimit + // NOTE: we will return any used gas later. } - if err != nil { - stack.push(BigFalse) + // Begin execution + var ret []byte + var err error - fmt.Printf("%v").Endl() + // If addr is in nativeContracts + if nativeContract := nativeContracts[addr]; nativeContract != nil { + ret, err = nativeContract(args, &gasLimit) } else { - stack.push(BigTrue) - - mem.Set(retOffset.Uint64(), retSize.Uint64(), ret) + if ok = useGas(gas, GasGetAccount); !ok { + return nil, firstErr(err, ErrInsufficientGas) + } + account, err_ := vm.appState.GetAccount(addr) + if err = firstErr(err, err_); err != nil { + return nil, err + } + if op == CALLCODE { + ret, err = vm.Call(callee, callee, account.Code, args, value, gas) + } else { + ret, err = vm.Call(callee, account, account.Code, args, value, gas) + } } - fmt.Printf("resume %X (%v)", context.Address(), context.Gas) - case RETURN: - offset, size := stack.pop(), stack.pop() - ret := mem.Get(offset.Int64(), size.Int64()) - fmt.Printf(" => [%v, %v] (%d) 0x%X", offset, size, len(ret), ret).Endl() - return context.Return(ret), nil - - case SUICIDE: - receiver := state.GetOrNewStateObject(stack.pop().Bytes()) - balance := state.GetBalance(context.Address()) + // Push result + if err != nil { + stack.Push(Zero) + } else { + stack.Push(One) + dest, ok := subslice(memory, retOffset, retSize) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + copy(dest, ret) + } - fmt.Printf(" => (%X) %v", receiver.Address()[:4], balance) + // Handle remaining gas. + *gas += gasLimit - receiver.AddBalance(balance) + fmt.Printf("resume %X (%v)\n", callee.Address, gas) - state.Delete(context.Address()) + case RETURN: // 0xF3 + offset, size := stack.Pop64(), stack.Pop64() + ret, ok := subslice(memory, offset, size) + if !ok { + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + fmt.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret) + return ret, nil + case SUICIDE: // 0xFF + addr := stack.Pop() + if ok = useGas(gas, GasGetAccount); !ok { + return nil, firstErr(err, ErrInsufficientGas) + } + receiver, err_ := vm.appState.GetAccount(addr) + if err = firstErr(err, err_); err != nil { + return nil, err + } + balance := callee.Balance + receiver.Balance += balance + vm.appState.UpdateAccount(receiver) + vm.appState.DeleteAccount(callee) + fmt.Printf(" => (%X) %v\n", addr[:4], balance) fallthrough - case STOP: // Stop the context - vm.Endl() - return context.Return(nil), nil default: - fmt.Printf("(pc) %-3v Invalid opcode %X\n", pc, op).Endl() - + fmt.Printf("(pc) %-3v Invalid opcode %X\n", pc, op) panic(fmt.Errorf("Invalid opcode %X", op)) } pc++ - - vm.Endl() } } /* -func (vm *Vm) RunPrecompiled(p *PrecompiledAccount, callData []byte, context *Context) (ret []byte, err error) { +func (vm *VM) CallPrecompiled(p *PrecompiledAccount, callData []byte, context *Context) (ret []byte, err error) { gas := p.Gas(len(callData)) if context.UseGas(gas) { ret = p.Call(callData) @@ -608,10 +693,44 @@ func subslice(data []byte, offset, length uint64) ([]byte, bool) { } } -func useGas(gasLeft, gasToUse uint64) (uint64, bool) { - if gasLeft > gasToUse { - return gasLeft - gasToUse, true +func codeGetOp(code []byte, n uint64) OpCode { + if uint64(len(code)) <= n { + return OpCode(0) // stop } else { - return gasLeft, false + return OpCode(code[n]) } } + +func jump(code []byte, to uint64) (err error) { + dest := codeGetOp(code, to) + if dest != JUMPDEST { + return ErrInvalidJumpDest + } + fmt.Printf(" ~> %v\n", to) + return nil +} + +func firstErr(errA, errB error) error { + if errA != nil { + return errA + } else { + return errB + } +} + +func useGas(gas *uint64, gasToUse uint64) bool { + if *gas > gasToUse { + *gas -= gasToUse + return true + } else { + return false + } +} + +// Creates a 20 byte address from the creatorAddr and nonce. +func createAddress(creatorAddr Word, nonce uint64) Word { + temp := make([]byte, 32+8) + copy(temp, creatorAddr[:]) + PutUint64(temp[32:], nonce) + return RightPadWord(sha3.Sha3(temp)[:20]) +}