mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-16 16:41:20 +00:00
vm makeover
This commit is contained in:
parent
f03547007a
commit
c21369cebd
101
vm/common.go
101
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)
|
||||
|
||||
RefundStorage = Big(15000)
|
||||
RefundSuicide = Big(24000)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
Pow256 = BigPow(2, 256)
|
||||
|
||||
LogTyPretty byte = 0x1
|
||||
LogTyDiff byte = 0x2
|
||||
Zero = Word{0}
|
||||
One = Word{1}
|
||||
)
|
||||
|
||||
const MaxCallDepth = 1025
|
||||
type Word [32]byte
|
||||
|
||||
func calcMemSize(off, l *big.Int) *big.Int {
|
||||
if l.Cmp(Big0) == 0 {
|
||||
return Big0
|
||||
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 new(big.Int).Add(off, l)
|
||||
return accum == 0
|
||||
}
|
||||
|
||||
// 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 Uint64ToWord(i uint64) Word {
|
||||
word := Word{}
|
||||
PutUint64(word[:], i)
|
||||
return word
|
||||
}
|
||||
|
||||
func BytesToWord(bz []byte) Word {
|
||||
word := Word{}
|
||||
copy(word[:], bz)
|
||||
return word
|
||||
}
|
||||
|
||||
func LeftPadWord(bz []byte) (word Word) {
|
||||
copy(word[:], bz)
|
||||
return
|
||||
}
|
||||
|
||||
func RightPadWord(bz []byte) (word Word) {
|
||||
copy(word[32-len(bz):], bz)
|
||||
return
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
func GetUint64(word Word) uint64 {
|
||||
return binary.LittleEndian.Uint64(word[:])
|
||||
}
|
||||
|
||||
func PutUint64(dest []byte, i uint64) {
|
||||
binary.LittleEndian.PutUint64(dest, i)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
18
vm/gas.go
Normal file
18
vm/gas.go
Normal file
@ -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
|
||||
)
|
90
vm/native.go
Normal file
90
vm/native.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
1
vm/secp256k1
Submodule
1
vm/secp256k1
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 8a939c2f861148885b43c58ce52a36882c516fd6
|
112
vm/stack.go
112
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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] = d
|
||||
st.ptr++
|
||||
}
|
||||
|
||||
func (st *Stack) Push64(i uint64) error {
|
||||
if st.ptr == cap(st.data) {
|
||||
return ErrDataStackOverflow
|
||||
}
|
||||
st.data[st.ptr] = [4]uint64{i, 0, 0, 0}
|
||||
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 {
|
||||
|
39
vm/types.go
Normal file
39
vm/types.go
Normal file
@ -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)
|
||||
}
|
627
vm/vm.go
627
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")
|
||||
)
|
||||
|
||||
const (
|
||||
dataStackCapacity = 1024
|
||||
callStackCapacity = 100 // TODO ensure usage.
|
||||
memoryCapacity = 1024 * 1024 // 1 MB
|
||||
)
|
||||
|
||||
type VM struct {
|
||||
appState AppState
|
||||
params VMParams
|
||||
|
||||
callDepth int
|
||||
}
|
||||
|
||||
func NewVM(appState AppState, params VMParams) *Vm {
|
||||
vmEnv := NewVMEnvironment(appState, params)
|
||||
type VMParams struct {
|
||||
BlockHeight uint64
|
||||
BlockHash Word
|
||||
BlockTime int64
|
||||
GasLimit uint64
|
||||
GasPrice uint64
|
||||
CallStackLimit uint64
|
||||
Origin Word
|
||||
}
|
||||
|
||||
func NewVM(appState AppState, params VMParams) *VM {
|
||||
return &VM{
|
||||
VMEnvironment: vmEnv,
|
||||
appState: appState,
|
||||
params: params,
|
||||
callDepth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
if len(target.Code) == 0 {
|
||||
panic("RunTransaction() requires target with code")
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
fmt.Printf(" => [%v, %v, %v] %X", memOff, codeOff, 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, 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)
|
||||
data, ok := subslice(memory, offset, 32)
|
||||
if !ok {
|
||||
return nil, ErrMemoryOutOfBounds
|
||||
}
|
||||
stack.Push(RightPadWord(data))
|
||||
fmt.Printf(" => 0x%X\n", data)
|
||||
|
||||
offset := stack.pop()
|
||||
val := Bytes2Big(mem.Get(offset.Int64(), 32))
|
||||
stack.push(val)
|
||||
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", 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))
|
||||
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)
|
||||
|
||||
fmt.Printf(" => 0x%X", val)
|
||||
case MSTORE8:
|
||||
off, val := stack.pop(), stack.pop()
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
fmt.Printf(" {0x%X : 0x%X}", loc.Bytes(), val.Bytes())
|
||||
case JUMP:
|
||||
jump(pc, stack.pop())
|
||||
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")
|
||||
|
||||
fmt.Printf(" ~> false")
|
||||
case PC: // 0x58
|
||||
stack.Push64(pc)
|
||||
|
||||
case JUMPDEST:
|
||||
case PC:
|
||||
stack.push(Big(int64(pc)))
|
||||
case MSIZE:
|
||||
stack.push(Big(int64(mem.Len())))
|
||||
case GAS:
|
||||
stack.push(context.Gas)
|
||||
case MSIZE: // 0x59
|
||||
stack.Push64(uint64(len(memory)))
|
||||
|
||||
fmt.Printf(" => %X", context.Gas)
|
||||
case GAS: // 0x5A
|
||||
stack.Push64(*gas)
|
||||
fmt.Printf(" => %X\n", *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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 0x60 range
|
||||
case CREATE:
|
||||
|
||||
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()
|
||||
|
||||
context.UseGas(context.Gas)
|
||||
ret, suberr, ref := Create(vm, context, nil, input, gas, price, value)
|
||||
if suberr != nil {
|
||||
stack.push(BigFalse)
|
||||
|
||||
fmt.Printf(" (*) 0x0 %v", suberr)
|
||||
// Check balance
|
||||
if caller.Balance < value {
|
||||
return nil, firstErr(err, ErrInsufficientBalance)
|
||||
} else {
|
||||
|
||||
// gas < len(ret) * CreateDataGas == NO_CODE
|
||||
dataGas := Big(int64(len(ret)))
|
||||
dataGas.Mul(dataGas, GasCreateByte)
|
||||
if context.UseGas(dataGas) {
|
||||
ref.SetCode(ret)
|
||||
}
|
||||
addr = ref.Address()
|
||||
|
||||
stack.push(Bytes2Big(addr))
|
||||
|
||||
caller.Balance -= value
|
||||
}
|
||||
|
||||
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()
|
||||
// Create a new address
|
||||
nonce := caller.Nonce
|
||||
addr := createAddress(caller.Address, nonce)
|
||||
caller.Nonce += 1
|
||||
|
||||
address := addr.Bytes()
|
||||
fmt.Printf(" => %X", address).Endl()
|
||||
// TODO charge for gas to create account _ the code length * GasCreateByte
|
||||
|
||||
newAccount, err := vm.appState.CreateAccount(addr, value)
|
||||
if err != nil {
|
||||
stack.Push64(0)
|
||||
fmt.Printf(" (*) 0x0 %v\n", err)
|
||||
} else {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
// Begin execution
|
||||
var ret []byte
|
||||
var err error
|
||||
|
||||
// If addr is in nativeContracts
|
||||
if nativeContract := nativeContracts[addr]; nativeContract != nil {
|
||||
ret, err = nativeContract(args, &gasLimit)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// Push result
|
||||
if err != nil {
|
||||
stack.push(BigFalse)
|
||||
|
||||
fmt.Printf("%v").Endl()
|
||||
stack.Push(Zero)
|
||||
} else {
|
||||
stack.push(BigTrue)
|
||||
|
||||
mem.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||
stack.Push(One)
|
||||
dest, ok := subslice(memory, retOffset, retSize)
|
||||
if !ok {
|
||||
return nil, firstErr(err, ErrMemoryOutOfBounds)
|
||||
}
|
||||
copy(dest, ret)
|
||||
}
|
||||
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
|
||||
// Handle remaining gas.
|
||||
*gas += gasLimit
|
||||
|
||||
case SUICIDE:
|
||||
receiver := state.GetOrNewStateObject(stack.pop().Bytes())
|
||||
balance := state.GetBalance(context.Address())
|
||||
fmt.Printf("resume %X (%v)\n", callee.Address, gas)
|
||||
|
||||
fmt.Printf(" => (%X) %v", receiver.Address()[:4], balance)
|
||||
|
||||
receiver.AddBalance(balance)
|
||||
|
||||
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])
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user