aboutsummaryrefslogtreecommitdiffstats
path: root/ethchain
diff options
context:
space:
mode:
authorMaran <maran.hidskes@gmail.com>2014-03-24 17:24:39 +0800
committerMaran <maran.hidskes@gmail.com>2014-03-24 17:24:39 +0800
commit97786d03d57e1ca79e34ce5fd9aa172c61c3e665 (patch)
tree251a5e54305e5cedf568f2fb73dbff9302df4185 /ethchain
parent274d5cc91c45349ec8d7a1f5a20ef29896b38b2e (diff)
parent6a86c517c4f4b372cad0ae1d92e926a482eac5ba (diff)
downloadgo-tangerine-97786d03d57e1ca79e34ce5fd9aa172c61c3e665.tar.gz
go-tangerine-97786d03d57e1ca79e34ce5fd9aa172c61c3e665.tar.zst
go-tangerine-97786d03d57e1ca79e34ce5fd9aa172c61c3e665.zip
Merge branch 'master' into miner
Diffstat (limited to 'ethchain')
-rw-r--r--ethchain/address.go34
-rw-r--r--ethchain/block.go2
-rw-r--r--ethchain/block_manager_test.go5
-rw-r--r--ethchain/closure.go90
-rw-r--r--ethchain/contract.go52
-rw-r--r--ethchain/stack.go296
-rw-r--r--ethchain/state.go55
-rw-r--r--ethchain/state_manager.go15
-rw-r--r--ethchain/transaction.go21
-rw-r--r--ethchain/vm.go461
-rw-r--r--ethchain/vm_test.go70
11 files changed, 658 insertions, 443 deletions
diff --git a/ethchain/address.go b/ethchain/address.go
index aa1709f2c..0b3ef7c05 100644
--- a/ethchain/address.go
+++ b/ethchain/address.go
@@ -6,23 +6,39 @@ import (
)
type Account struct {
- Amount *big.Int
- Nonce uint64
+ address []byte
+ Amount *big.Int
+ Nonce uint64
}
-func NewAccount(amount *big.Int) *Account {
- return &Account{Amount: amount, Nonce: 0}
+func NewAccount(address []byte, amount *big.Int) *Account {
+ return &Account{address, amount, 0}
}
-func NewAccountFromData(data []byte) *Account {
- address := &Account{}
- address.RlpDecode(data)
+func NewAccountFromData(address, data []byte) *Account {
+ account := &Account{address: address}
+ account.RlpDecode(data)
- return address
+ return account
}
func (a *Account) AddFee(fee *big.Int) {
- a.Amount.Add(a.Amount, fee)
+ a.AddFunds(fee)
+}
+
+func (a *Account) AddFunds(funds *big.Int) {
+ a.Amount.Add(a.Amount, funds)
+}
+
+func (a *Account) Address() []byte {
+ return a.address
+}
+
+// Implements Callee
+func (a *Account) ReturnGas(value *big.Int, state *State) {
+ // Return the value back to the sender
+ a.AddFunds(value)
+ state.UpdateAccount(a.address, a)
}
func (a *Account) RlpEncode() []byte {
diff --git a/ethchain/block.go b/ethchain/block.go
index d42aa7d83..732739c1b 100644
--- a/ethchain/block.go
+++ b/ethchain/block.go
@@ -142,7 +142,7 @@ func (block *Block) PayFee(addr []byte, fee *big.Int) bool {
data := block.state.trie.Get(string(block.Coinbase))
// Get the ether (Coinbase) and add the fee (gief fee to miner)
- ether := NewAccountFromData([]byte(data))
+ ether := NewAccountFromData(block.Coinbase, []byte(data))
base = new(big.Int)
ether.Amount = base.Add(ether.Amount, fee)
diff --git a/ethchain/block_manager_test.go b/ethchain/block_manager_test.go
index ec4fbe8c5..3a1e5f510 100644
--- a/ethchain/block_manager_test.go
+++ b/ethchain/block_manager_test.go
@@ -1,5 +1,6 @@
package ethchain
+/*
import (
_ "fmt"
"github.com/ethereum/eth-go/ethdb"
@@ -14,9 +15,10 @@ func TestVm(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
ethutil.Config.Db = db
- bm := NewBlockManager(nil)
+ bm := NewStateManager(nil)
block := bm.bc.genesisBlock
+ bm.Prepare(block.State(), block.State())
script := Compile([]string{
"PUSH",
"1",
@@ -31,3 +33,4 @@ func TestVm(t *testing.T) {
tx2.Sign([]byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
bm.ApplyTransactions(block, []*Transaction{tx2})
}
+*/
diff --git a/ethchain/closure.go b/ethchain/closure.go
new file mode 100644
index 000000000..2e809aa9d
--- /dev/null
+++ b/ethchain/closure.go
@@ -0,0 +1,90 @@
+package ethchain
+
+// TODO Re write VM to use values instead of big integers?
+
+import (
+ "github.com/ethereum/eth-go/ethutil"
+ "math/big"
+)
+
+type Callee interface {
+ ReturnGas(*big.Int, *State)
+ Address() []byte
+}
+
+type ClosureBody interface {
+ Callee
+ ethutil.RlpEncodable
+ GetMem(*big.Int) *ethutil.Value
+ SetMem(*big.Int, *ethutil.Value)
+}
+
+// Basic inline closure object which implement the 'closure' interface
+type Closure struct {
+ callee Callee
+ object ClosureBody
+ State *State
+
+ Gas *big.Int
+ Value *big.Int
+
+ Args []byte
+}
+
+// Create a new closure for the given data items
+func NewClosure(callee Callee, object ClosureBody, state *State, gas, val *big.Int) *Closure {
+ return &Closure{callee, object, state, gas, val, nil}
+}
+
+// Retuns the x element in data slice
+func (c *Closure) GetMem(x *big.Int) *ethutil.Value {
+ m := c.object.GetMem(x)
+ if m == nil {
+ return ethutil.EmptyValue()
+ }
+
+ return m
+}
+
+func (c *Closure) SetMem(x *big.Int, val *ethutil.Value) {
+ c.object.SetMem(x, val)
+}
+
+func (c *Closure) Address() []byte {
+ return c.object.Address()
+}
+
+func (c *Closure) Call(vm *Vm, args []byte) []byte {
+ c.Args = args
+
+ return vm.RunClosure(c)
+}
+
+func (c *Closure) Return(ret []byte) []byte {
+ // Return the remaining gas to the callee
+ // If no callee is present return it to
+ // the origin (i.e. contract or tx)
+ if c.callee != nil {
+ c.callee.ReturnGas(c.Gas, c.State)
+ } else {
+ c.object.ReturnGas(c.Gas, c.State)
+ // TODO incase it's a POST contract we gotta serialise the contract again.
+ // But it's not yet defined
+ }
+
+ return ret
+}
+
+// Implement the Callee interface
+func (c *Closure) ReturnGas(gas *big.Int, state *State) {
+ // Return the gas to the closure
+ c.Gas.Add(c.Gas, gas)
+}
+
+func (c *Closure) Object() ClosureBody {
+ return c.object
+}
+
+func (c *Closure) Callee() Callee {
+ return c.callee
+}
diff --git a/ethchain/contract.go b/ethchain/contract.go
index 21ac828fe..f7ae01753 100644
--- a/ethchain/contract.go
+++ b/ethchain/contract.go
@@ -9,26 +9,22 @@ type Contract struct {
Amount *big.Int
Nonce uint64
//state *ethutil.Trie
- state *State
+ state *State
+ address []byte
}
-func NewContract(Amount *big.Int, root []byte) *Contract {
- contract := &Contract{Amount: Amount, Nonce: 0}
+func NewContract(address []byte, Amount *big.Int, root []byte) *Contract {
+ contract := &Contract{address: address, Amount: Amount, Nonce: 0}
contract.state = NewState(ethutil.NewTrie(ethutil.Config.Db, string(root)))
return contract
}
-func (c *Contract) RlpEncode() []byte {
- return ethutil.Encode([]interface{}{c.Amount, c.Nonce, c.state.trie.Root})
-}
+func NewContractFromBytes(address, data []byte) *Contract {
+ contract := &Contract{address: address}
+ contract.RlpDecode(data)
-func (c *Contract) RlpDecode(data []byte) {
- decoder := ethutil.NewValueFromBytes(data)
-
- c.Amount = decoder.Get(0).BigInt()
- c.Nonce = decoder.Get(1).Uint()
- c.state = NewState(ethutil.NewTrie(ethutil.Config.Db, decoder.Get(2).Interface()))
+ return contract
}
func (c *Contract) Addr(addr []byte) *ethutil.Value {
@@ -43,19 +39,45 @@ func (c *Contract) State() *State {
return c.state
}
-func (c *Contract) GetMem(num int) *ethutil.Value {
- nb := ethutil.BigToBytes(big.NewInt(int64(num)), 256)
+func (c *Contract) GetMem(num *big.Int) *ethutil.Value {
+ nb := ethutil.BigToBytes(num, 256)
return c.Addr(nb)
}
+func (c *Contract) SetMem(num *big.Int, val *ethutil.Value) {
+ addr := ethutil.BigToBytes(num, 256)
+ c.state.trie.Update(string(addr), string(val.Encode()))
+}
+
+// Return the gas back to the origin. Used by the Virtual machine or Closures
+func (c *Contract) ReturnGas(val *big.Int, state *State) {
+ c.Amount.Add(c.Amount, val)
+}
+
+func (c *Contract) Address() []byte {
+ return c.address
+}
+
+func (c *Contract) RlpEncode() []byte {
+ return ethutil.Encode([]interface{}{c.Amount, c.Nonce, c.state.trie.Root})
+}
+
+func (c *Contract) RlpDecode(data []byte) {
+ decoder := ethutil.NewValueFromBytes(data)
+
+ c.Amount = decoder.Get(0).BigInt()
+ c.Nonce = decoder.Get(1).Uint()
+ c.state = NewState(ethutil.NewTrie(ethutil.Config.Db, decoder.Get(2).Interface()))
+}
+
func MakeContract(tx *Transaction, state *State) *Contract {
// Create contract if there's no recipient
if tx.IsContract() {
addr := tx.Hash()[12:]
value := tx.Value
- contract := NewContract(value, []byte(""))
+ contract := NewContract(addr, value, []byte(""))
state.trie.Update(string(addr), string(contract.RlpEncode()))
for i, val := range tx.Data {
if len(val) > 0 {
diff --git a/ethchain/stack.go b/ethchain/stack.go
index 13b0f247b..3c2899e62 100644
--- a/ethchain/stack.go
+++ b/ethchain/stack.go
@@ -2,6 +2,7 @@ package ethchain
import (
"fmt"
+ _ "github.com/ethereum/eth-go/ethutil"
"math/big"
)
@@ -9,111 +10,142 @@ type OpCode int
// Op codes
const (
- oSTOP = 0x00
- oADD = 0x01
- oMUL = 0x02
- oSUB = 0x03
- oDIV = 0x04
- oSDIV = 0x05
- oMOD = 0x06
- oSMOD = 0x07
- oEXP = 0x08
- oNEG = 0x09
- oLT = 0x0a
- oLE = 0x0b
- oGT = 0x0c
- oGE = 0x0d
- oEQ = 0x0e
- oNOT = 0x0f
- oMYADDRESS = 0x10
- oTXSENDER = 0x11
- oTXVALUE = 0x12
- oTXDATAN = 0x13
- oTXDATA = 0x14
- oBLK_PREVHASH = 0x15
- oBLK_COINBASE = 0x16
- oBLK_TIMESTAMP = 0x17
- oBLK_NUMBER = 0x18
- oBLK_DIFFICULTY = 0x19
- oBLK_NONCE = 0x1a
- oBASEFEE = 0x1b
- oSHA256 = 0x20
- oRIPEMD160 = 0x21
- oECMUL = 0x22
- oECADD = 0x23
- oECSIGN = 0x24
- oECRECOVER = 0x25
- oECVALID = 0x26
- oSHA3 = 0x27
- oPUSH = 0x30
- oPOP = 0x31
- oDUP = 0x32
- oSWAP = 0x33
- oMLOAD = 0x34
- oMSTORE = 0x35
- oSLOAD = 0x36
- oSSTORE = 0x37
- oJMP = 0x38
- oJMPI = 0x39
- oIND = 0x3a
- oEXTRO = 0x3b
- oBALANCE = 0x3c
- oMKTX = 0x3d
- oSUICIDE = 0x3f
+ // 0x0 range - arithmetic ops
+ oSTOP = 0x00
+ oADD = 0x01
+ oMUL = 0x02
+ oSUB = 0x03
+ oDIV = 0x04
+ oSDIV = 0x05
+ oMOD = 0x06
+ oSMOD = 0x07
+ oEXP = 0x08
+ oNEG = 0x09
+ oLT = 0x0a
+ oGT = 0x0b
+ oEQ = 0x0c
+ oNOT = 0x0d
+
+ // 0x10 range - bit ops
+ oAND = 0x10
+ oOR = 0x11
+ oXOR = 0x12
+ oBYTE = 0x13
+
+ // 0x20 range - crypto
+ oSHA3 = 0x20
+
+ // 0x30 range - closure state
+ oADDRESS = 0x30
+ oBALANCE = 0x31
+ oORIGIN = 0x32
+ oCALLER = 0x33
+ oCALLVALUE = 0x34
+ oCALLDATA = 0x35
+ oCALLDATASIZE = 0x36
+ oGASPRICE = 0x37
+
+ // 0x40 range - block operations
+ oPREVHASH = 0x40
+ oCOINBASE = 0x41
+ oTIMESTAMP = 0x42
+ oNUMBER = 0x43
+ oDIFFICULTY = 0x44
+ oGASLIMIT = 0x45
+
+ // 0x50 range - 'storage' and execution
+ oPUSH = 0x50
+ oPOP = 0x51
+ oDUP = 0x52
+ oSWAP = 0x53
+ oMLOAD = 0x54
+ oMSTORE = 0x55
+ oMSTORE8 = 0x56
+ oSLOAD = 0x57
+ oSSTORE = 0x58
+ oJUMP = 0x59
+ oJUMPI = 0x5a
+ oPC = 0x5b
+ oMSIZE = 0x5c
+
+ // 0x60 range - closures
+ oCREATE = 0x60
+ oCALL = 0x61
+ oRETURN = 0x62
+
+ // 0x70 range - other
+ oLOG = 0x70 // XXX Unofficial
+ oSUICIDE = 0x7f
)
// Since the opcodes aren't all in order we can't use a regular slice
var opCodeToString = map[OpCode]string{
- oSTOP: "STOP",
- oADD: "ADD",
- oMUL: "MUL",
- oSUB: "SUB",
- oDIV: "DIV",
- oSDIV: "SDIV",
- oMOD: "MOD",
- oSMOD: "SMOD",
- oEXP: "EXP",
- oNEG: "NEG",
- oLT: "LT",
- oLE: "LE",
- oGT: "GT",
- oGE: "GE",
- oEQ: "EQ",
- oNOT: "NOT",
- oMYADDRESS: "MYADDRESS",
- oTXSENDER: "TXSENDER",
- oTXVALUE: "TXVALUE",
- oTXDATAN: "TXDATAN",
- oTXDATA: "TXDATA",
- oBLK_PREVHASH: "BLK_PREVHASH",
- oBLK_COINBASE: "BLK_COINBASE",
- oBLK_TIMESTAMP: "BLK_TIMESTAMP",
- oBLK_NUMBER: "BLK_NUMBER",
- oBLK_DIFFICULTY: "BLK_DIFFICULTY",
- oBASEFEE: "BASEFEE",
- oSHA256: "SHA256",
- oRIPEMD160: "RIPEMD160",
- oECMUL: "ECMUL",
- oECADD: "ECADD",
- oECSIGN: "ECSIGN",
- oECRECOVER: "ECRECOVER",
- oECVALID: "ECVALID",
- oSHA3: "SHA3",
- oPUSH: "PUSH",
- oPOP: "POP",
- oDUP: "DUP",
- oSWAP: "SWAP",
- oMLOAD: "MLOAD",
- oMSTORE: "MSTORE",
- oSLOAD: "SLOAD",
- oSSTORE: "SSTORE",
- oJMP: "JMP",
- oJMPI: "JMPI",
- oIND: "IND",
- oEXTRO: "EXTRO",
- oBALANCE: "BALANCE",
- oMKTX: "MKTX",
- oSUICIDE: "SUICIDE",
+ // 0x0 range - arithmetic ops
+ oSTOP: "STOP",
+ oADD: "ADD",
+ oMUL: "MUL",
+ oSUB: "SUB",
+ oDIV: "DIV",
+ oSDIV: "SDIV",
+ oMOD: "MOD",
+ oSMOD: "SMOD",
+ oEXP: "EXP",
+ oNEG: "NEG",
+ oLT: "LT",
+ oGT: "GT",
+ oEQ: "EQ",
+ oNOT: "NOT",
+
+ // 0x10 range - bit ops
+ oAND: "AND",
+ oOR: "OR",
+ oXOR: "XOR",
+ oBYTE: "BYTE",
+
+ // 0x20 range - crypto
+ oSHA3: "SHA3",
+
+ // 0x30 range - closure state
+ oADDRESS: "ADDRESS",
+ oBALANCE: "BALANCE",
+ oORIGIN: "ORIGIN",
+ oCALLER: "CALLER",
+ oCALLVALUE: "CALLVALUE",
+ oCALLDATA: "CALLDATA",
+ oCALLDATASIZE: "CALLDATASIZE",
+ oGASPRICE: "TXGASPRICE",
+
+ // 0x40 range - block operations
+ oPREVHASH: "PREVHASH",
+ oCOINBASE: "COINBASE",
+ oTIMESTAMP: "TIMESTAMP",
+ oNUMBER: "NUMBER",
+ oDIFFICULTY: "DIFFICULTY",
+ oGASLIMIT: "GASLIMIT",
+
+ // 0x50 range - 'storage' and execution
+ oPUSH: "PUSH",
+ oPOP: "POP",
+ oDUP: "DUP",
+ oSWAP: "SWAP",
+ oMLOAD: "MLOAD",
+ oMSTORE: "MSTORE",
+ oMSTORE8: "MSTORE8",
+ oSLOAD: "SLOAD",
+ oSSTORE: "SSTORE",
+ oJUMP: "JUMP",
+ oJUMPI: "JUMPI",
+ oPC: "PC",
+ oMSIZE: "MSIZE",
+
+ // 0x60 range - closures
+ oCREATE: "CREATE",
+ oCALL: "CALL",
+ oRETURN: "RETURN",
+
+ // 0x70 range - other
+ oLOG: "LOG",
+ oSUICIDE: "SUICIDE",
}
func (o OpCode) String() string {
@@ -141,35 +173,27 @@ func NewStack() *Stack {
}
func (st *Stack) Pop() *big.Int {
- s := len(st.data)
-
- str := st.data[s-1]
- st.data = st.data[:s-1]
+ str := st.data[0]
+ st.data = st.data[1:]
return str
}
func (st *Stack) Popn() (*big.Int, *big.Int) {
- s := len(st.data)
-
- ints := st.data[s-2:]
- st.data = st.data[:s-2]
+ ints := st.data[:2]
+ st.data = st.data[2:]
return ints[0], ints[1]
}
func (st *Stack) Peek() *big.Int {
- s := len(st.data)
-
- str := st.data[s-1]
+ str := st.data[0]
return str
}
func (st *Stack) Peekn() (*big.Int, *big.Int) {
- s := len(st.data)
-
- ints := st.data[s-2:]
+ ints := st.data[:2]
return ints[0], ints[1]
}
@@ -188,3 +212,45 @@ func (st *Stack) Print() {
}
fmt.Println("#############")
}
+
+type Memory struct {
+ store []byte
+}
+
+func (m *Memory) Set(offset, size int64, value []byte) {
+ totSize := offset + size
+ lenSize := int64(len(m.store) - 1)
+ if totSize > lenSize {
+ // Calculate the diff between the sizes
+ diff := totSize - lenSize
+ if diff > 0 {
+ // Create a new empty slice and append it
+ newSlice := make([]byte, diff-1)
+ // Resize slice
+ m.store = append(m.store, newSlice...)
+ }
+ }
+ copy(m.store[offset:offset+size], value)
+}
+
+func (m *Memory) Get(offset, size int64) []byte {
+ return m.store[offset : offset+size]
+}
+
+func (m *Memory) Len() int {
+ return len(m.store)
+}
+
+func (m *Memory) Print() {
+ fmt.Println("### MEM ###")
+ if len(m.store) > 0 {
+ addr := 0
+ for i := 0; i+32 < len(m.store); i += 32 {
+ fmt.Printf("%03d %v\n", addr, m.store[i:i+32])
+ addr++
+ }
+ } else {
+ fmt.Println("-- empty --")
+ }
+ fmt.Println("###########")
+}
diff --git a/ethchain/state.go b/ethchain/state.go
index b9c2c576d..1860647f2 100644
--- a/ethchain/state.go
+++ b/ethchain/state.go
@@ -63,8 +63,7 @@ func (s *State) GetContract(addr []byte) *Contract {
}
// build contract
- contract := &Contract{}
- contract.RlpDecode([]byte(data))
+ contract := NewContractFromBytes(addr, []byte(data))
// Check if there's a cached state for this contract
cachedState := s.states[string(addr)]
@@ -78,27 +77,19 @@ func (s *State) GetContract(addr []byte) *Contract {
return contract
}
-func (s *State) UpdateContract(addr []byte, contract *Contract) {
- s.trie.Update(string(addr), string(contract.RlpEncode()))
-}
-
-func Compile(code []string) (script []string) {
- script = make([]string, len(code))
- for i, val := range code {
- instr, _ := ethutil.CompileInstr(val)
-
- script[i] = string(instr)
- }
+func (s *State) UpdateContract(contract *Contract) {
+ addr := contract.Address()
- return
+ s.states[string(addr)] = contract.state
+ s.trie.Update(string(addr), string(contract.RlpEncode()))
}
func (s *State) GetAccount(addr []byte) (account *Account) {
data := s.trie.Get(string(addr))
if data == "" {
- account = NewAccount(big.NewInt(0))
+ account = NewAccount(addr, big.NewInt(0))
} else {
- account = NewAccountFromData([]byte(data))
+ account = NewAccountFromData(addr, []byte(data))
}
return
@@ -153,3 +144,35 @@ func (s *State) Get(key []byte) (*ethutil.Value, ObjType) {
return val, typ
}
+
+func (s *State) Put(key, object []byte) {
+ s.trie.Update(string(key), string(object))
+}
+
+func (s *State) Root() interface{} {
+ return s.trie.Root
+}
+
+// Script compilation functions
+// Compiles strings to machine code
+func Compile(code []string) (script []string) {
+ script = make([]string, len(code))
+ for i, val := range code {
+ instr, _ := ethutil.CompileInstr(val)
+
+ script[i] = string(instr)
+ }
+
+ return
+}
+
+func CompileToValues(code []string) (script []*ethutil.Value) {
+ script = make([]*ethutil.Value, len(code))
+ for i, val := range code {
+ instr, _ := ethutil.CompileInstr(val)
+
+ script[i] = ethutil.NewValue(instr)
+ }
+
+ return
+}
diff --git a/ethchain/state_manager.go b/ethchain/state_manager.go
index 9118db211..5692a1d88 100644
--- a/ethchain/state_manager.go
+++ b/ethchain/state_manager.go
@@ -308,18 +308,17 @@ func (sm *StateManager) ProcessContract(contract *Contract, tx *Transaction, blo
}
}()
*/
-
- vm := &Vm{}
- //vm.Process(contract, block.state, RuntimeVars{
- vm.Process(contract, sm.procState, RuntimeVars{
- address: tx.Hash()[12:],
+ caller := sm.procState.GetAccount(tx.Sender())
+ closure := NewClosure(caller, contract, sm.procState, tx.Gas, tx.Value)
+ vm := NewVm(sm.procState, RuntimeVars{
+ origin: caller.Address(),
blockNumber: block.BlockInfo().Number,
- sender: tx.Sender(),
prevHash: block.PrevHash,
coinbase: block.Coinbase,
time: block.Time,
diff: block.Difficulty,
- txValue: tx.Value,
- txData: tx.Data,
+ // XXX Tx data? Could be just an argument to the closure instead
+ txData: nil,
})
+ closure.Call(vm, nil)
}
diff --git a/ethchain/transaction.go b/ethchain/transaction.go
index 57df9cdc4..3b07c81d4 100644
--- a/ethchain/transaction.go
+++ b/ethchain/transaction.go
@@ -13,22 +13,31 @@ type Transaction struct {
Nonce uint64
Recipient []byte
Value *big.Int
+ Gas *big.Int
+ Gasprice *big.Int
Data []string
- Memory []int
v byte
r, s []byte
}
func NewTransaction(to []byte, value *big.Int, data []string) *Transaction {
- tx := Transaction{Recipient: to, Value: value}
- tx.Nonce = 0
-
- // Serialize the data
- tx.Data = data
+ tx := Transaction{Recipient: to, Value: value, Nonce: 0, Data: data}
return &tx
}
+func NewContractCreationTx(value, gasprice *big.Int, data []string) *Transaction {
+ return &Transaction{Value: value, Gasprice: gasprice, Data: data}
+}
+
+func NewContractMessageTx(to []byte, value, gasprice, gas *big.Int, data []string) *Transaction {
+ return &Transaction{Recipient: to, Value: value, Gasprice: gasprice, Gas: gas, Data: data}
+}
+
+func NewTx(to []byte, value *big.Int, data []string) *Transaction {
+ return &Transaction{Recipient: to, Value: value, Gasprice: big.NewInt(0), Gas: big.NewInt(0), Nonce: 0, Data: data}
+}
+
// XXX Deprecated
func NewTransactionFromData(data []byte) *Transaction {
return NewTransactionFromBytes(data)
diff --git a/ethchain/vm.go b/ethchain/vm.go
index 7e119ac99..126592b25 100644
--- a/ethchain/vm.go
+++ b/ethchain/vm.go
@@ -1,12 +1,12 @@
package ethchain
import (
- "bytes"
- "fmt"
+ _ "bytes"
+ _ "fmt"
"github.com/ethereum/eth-go/ethutil"
- "github.com/obscuren/secp256k1-go"
+ _ "github.com/obscuren/secp256k1-go"
"log"
- "math"
+ _ "math"
"math/big"
)
@@ -18,122 +18,102 @@ type Vm struct {
mem map[string]*big.Int
vars RuntimeVars
+
+ state *State
}
type RuntimeVars struct {
- address []byte
+ origin []byte
blockNumber uint64
- sender []byte
prevHash []byte
coinbase []byte
time int64
diff *big.Int
- txValue *big.Int
txData []string
}
-func (vm *Vm) Process(contract *Contract, state *State, vars RuntimeVars) {
- vm.mem = make(map[string]*big.Int)
- vm.stack = NewStack()
+func NewVm(state *State, vars RuntimeVars) *Vm {
+ return &Vm{vars: vars, state: state}
+}
- addr := vars.address // tx.Hash()[12:]
- // Instruction pointer
- pc := 0
+var Pow256 = ethutil.BigPow(2, 256)
- if contract == nil {
- fmt.Println("Contract not found")
- return
+func (vm *Vm) RunClosure(closure *Closure) []byte {
+ // If the amount of gas supplied is less equal to 0
+ if closure.Gas.Cmp(big.NewInt(0)) <= 0 {
+ // TODO Do something
}
- Pow256 := ethutil.BigPow(2, 256)
+ // Memory for the current closure
+ mem := &Memory{}
+ // New stack (should this be shared?)
+ stack := NewStack()
+ // Instruction pointer
+ pc := big.NewInt(0)
+ // Current step count
+ step := 0
+ // The base for all big integer arithmetic
+ base := new(big.Int)
if ethutil.Config.Debug {
ethutil.Config.Log.Debugf("# op\n")
}
- stepcount := 0
- totalFee := new(big.Int)
-
-out:
for {
- stepcount++
- // The base big int for all calculations. Use this for any results.
- base := new(big.Int)
- val := contract.GetMem(pc)
- //fmt.Printf("%x = %d, %v %x\n", r, len(r), v, nb)
+ step++
+ // Get the memory location of pc
+ val := closure.GetMem(pc)
+ // Get the opcode (it must be an opcode!)
op := OpCode(val.Uint())
-
- var fee *big.Int = new(big.Int)
- var fee2 *big.Int = new(big.Int)
- if stepcount > 16 {
- fee.Add(fee, StepFee)
- }
-
- // Calculate the fees
- switch op {
- case oSSTORE:
- y, x := vm.stack.Peekn()
- val := contract.Addr(ethutil.BigToBytes(x, 256))
- if val.IsEmpty() && len(y.Bytes()) > 0 {
- fee2.Add(DataFee, StoreFee)
- } else {
- fee2.Sub(DataFee, StoreFee)
- }
- case oSLOAD:
- fee.Add(fee, StoreFee)
- case oEXTRO, oBALANCE:
- fee.Add(fee, ExtroFee)
- case oSHA256, oRIPEMD160, oECMUL, oECADD, oECSIGN, oECRECOVER, oECVALID:
- fee.Add(fee, CryptoFee)
- case oMKTX:
- fee.Add(fee, ContractFee)
+ if ethutil.Config.Debug {
+ ethutil.Config.Log.Debugf("%-3d %-4s", pc, op.String())
}
- tf := new(big.Int).Add(fee, fee2)
- if contract.Amount.Cmp(tf) < 0 {
- fmt.Println("Insufficient fees to continue running the contract", tf, contract.Amount)
- break
- }
- // Add the fee to the total fee. It's subtracted when we're done looping
- totalFee.Add(totalFee, tf)
+ // TODO Get each instruction cost properly
+ fee := new(big.Int)
+ fee.Add(fee, big.NewInt(1000))
- if ethutil.Config.Debug {
- ethutil.Config.Log.Debugf("%-3d %-4s", pc, op.String())
+ if closure.Gas.Cmp(fee) < 0 {
+ return closure.Return(nil)
}
switch op {
- case oSTOP:
- fmt.Println("")
- break out
+ case oLOG:
+ stack.Print()
+ mem.Print()
+ case oSTOP: // Stop the closure
+ return closure.Return(nil)
+
+ // 0x20 range
case oADD:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
// (x + y) % 2 ** 256
base.Add(x, y)
base.Mod(base, Pow256)
// Pop result back on the stack
- vm.stack.Push(base)
+ stack.Push(base)
case oSUB:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
// (x - y) % 2 ** 256
base.Sub(x, y)
base.Mod(base, Pow256)
// Pop result back on the stack
- vm.stack.Push(base)
+ stack.Push(base)
case oMUL:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
// (x * y) % 2 ** 256
base.Mul(x, y)
base.Mod(base, Pow256)
// Pop result back on the stack
- vm.stack.Push(base)
+ stack.Push(base)
case oDIV:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
// floor(x / y)
base.Div(x, y)
// Pop result back on the stack
- vm.stack.Push(base)
+ stack.Push(base)
case oSDIV:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
// n > 2**255
if x.Cmp(Pow256) > 0 {
x.Sub(Pow256, x)
@@ -147,13 +127,13 @@ out:
z.Sub(Pow256, z)
}
// Push result on to the stack
- vm.stack.Push(z)
+ stack.Push(z)
case oMOD:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
base.Mod(x, y)
- vm.stack.Push(base)
+ stack.Push(base)
case oSMOD:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
// n > 2**255
if x.Cmp(Pow256) > 0 {
x.Sub(Pow256, x)
@@ -167,250 +147,189 @@ out:
z.Sub(Pow256, z)
}
// Push result on to the stack
- vm.stack.Push(z)
+ stack.Push(z)
case oEXP:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
base.Exp(x, y, Pow256)
- vm.stack.Push(base)
+ stack.Push(base)
case oNEG:
- base.Sub(Pow256, vm.stack.Pop())
- vm.stack.Push(base)
+ base.Sub(Pow256, stack.Pop())
+ stack.Push(base)
case oLT:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
// x < y
if x.Cmp(y) < 0 {
- vm.stack.Push(ethutil.BigTrue)
- } else {
- vm.stack.Push(ethutil.BigFalse)
- }
- case oLE:
- x, y := vm.stack.Popn()
- // x <= y
- if x.Cmp(y) < 1 {
- vm.stack.Push(ethutil.BigTrue)
+ stack.Push(ethutil.BigTrue)
} else {
- vm.stack.Push(ethutil.BigFalse)
+ stack.Push(ethutil.BigFalse)
}
case oGT:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
// x > y
if x.Cmp(y) > 0 {
- vm.stack.Push(ethutil.BigTrue)
- } else {
- vm.stack.Push(ethutil.BigFalse)
- }
- case oGE:
- x, y := vm.stack.Popn()
- // x >= y
- if x.Cmp(y) > -1 {
- vm.stack.Push(ethutil.BigTrue)
+ stack.Push(ethutil.BigTrue)
} else {
- vm.stack.Push(ethutil.BigFalse)
+ stack.Push(ethutil.BigFalse)
}
case oNOT:
- x, y := vm.stack.Popn()
+ x, y := stack.Popn()
// x != y
if x.Cmp(y) != 0 {
- vm.stack.Push(ethutil.BigTrue)
+ stack.Push(ethutil.BigTrue)
} else {
- vm.stack.Push(ethutil.BigFalse)
+ stack.Push(ethutil.BigFalse)
}
- case oMYADDRESS:
- vm.stack.Push(ethutil.BigD(addr))
- case oTXSENDER:
- vm.stack.Push(ethutil.BigD(vars.sender))
- case oTXVALUE:
- vm.stack.Push(vars.txValue)
- case oTXDATAN:
- vm.stack.Push(big.NewInt(int64(len(vars.txData))))
- case oTXDATA:
- v := vm.stack.Pop()
- // v >= len(data)
- if v.Cmp(big.NewInt(int64(len(vars.txData)))) >= 0 {
- vm.stack.Push(ethutil.Big("0"))
- } else {
- vm.stack.Push(ethutil.Big(vars.txData[v.Uint64()]))
- }
- case oBLK_PREVHASH:
- vm.stack.Push(ethutil.BigD(vars.prevHash))
- case oBLK_COINBASE:
- vm.stack.Push(ethutil.BigD(vars.coinbase))
- case oBLK_TIMESTAMP:
- vm.stack.Push(big.NewInt(vars.time))
- case oBLK_NUMBER:
- vm.stack.Push(big.NewInt(int64(vars.blockNumber)))
- case oBLK_DIFFICULTY:
- vm.stack.Push(vars.diff)
- case oBASEFEE:
- // e = 10^21
- e := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(21), big.NewInt(0))
- d := new(big.Rat)
- d.SetInt(vars.diff)
- c := new(big.Rat)
- c.SetFloat64(0.5)
- // d = diff / 0.5
- d.Quo(d, c)
- // base = floor(d)
- base.Div(d.Num(), d.Denom())
- x := new(big.Int)
- x.Div(e, base)
+ // 0x10 range
+ case oAND:
+ case oOR:
+ case oXOR:
+ case oBYTE:
- // x = floor(10^21 / floor(diff^0.5))
- vm.stack.Push(x)
- case oSHA256, oSHA3, oRIPEMD160:
- // This is probably save
- // ceil(pop / 32)
- length := int(math.Ceil(float64(vm.stack.Pop().Uint64()) / 32.0))
- // New buffer which will contain the concatenated popped items
- data := new(bytes.Buffer)
- for i := 0; i < length; i++ {
- // Encode the number to bytes and have it 32bytes long
- num := ethutil.NumberToBytes(vm.stack.Pop().Bytes(), 256)
- data.WriteString(string(num))
- }
+ // 0x20 range
+ case oSHA3:
- if op == oSHA256 {
- vm.stack.Push(base.SetBytes(ethutil.Sha256Bin(data.Bytes())))
- } else if op == oSHA3 {
- vm.stack.Push(base.SetBytes(ethutil.Sha3Bin(data.Bytes())))
- } else {
- vm.stack.Push(base.SetBytes(ethutil.Ripemd160(data.Bytes())))
- }
- case oECMUL:
- y := vm.stack.Pop()
- x := vm.stack.Pop()
- //n := vm.stack.Pop()
+ // 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 oCALLDATA:
+ offset := stack.Pop()
+ mem.Set(offset.Int64(), int64(len(closure.Args)), closure.Args)
+ case oCALLDATASIZE:
+ stack.Push(big.NewInt(int64(len(closure.Args))))
+ case oGASPRICE:
+ // TODO
- //if ethutil.Big(x).Cmp(ethutil.Big(y)) {
- data := new(bytes.Buffer)
- data.WriteString(x.String())
- data.WriteString(y.String())
- if secp256k1.VerifyPubkeyValidity(data.Bytes()) == 1 {
- // TODO
- } else {
- // Invalid, push infinity
- vm.stack.Push(ethutil.Big("0"))
- vm.stack.Push(ethutil.Big("0"))
- }
- //} else {
- // // Invalid, push infinity
- // vm.stack.Push("0")
- // vm.stack.Push("0")
- //}
+ // 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
- case oECADD:
- case oECSIGN:
- case oECRECOVER:
- case oECVALID:
- case oPUSH:
- pc++
- vm.stack.Push(contract.GetMem(pc).BigInt())
+ // 0x50 range
+ case oPUSH: // Push PC+1 on to the stack
+ pc.Add(pc, ethutil.Big1)
+
+ val := closure.GetMem(pc).BigInt()
+ stack.Push(val)
case oPOP:
- // Pop current value of the stack
- vm.stack.Pop()
+ stack.Pop()
case oDUP:
- // Dup top stack
- x := vm.stack.Pop()
- vm.stack.Push(x)
- vm.stack.Push(x)
+ stack.Push(stack.Peek())
case oSWAP:
- // Swap two top most values
- x, y := vm.stack.Popn()
- vm.stack.Push(y)
- vm.stack.Push(x)
+ x, y := stack.Popn()
+ stack.Push(y)
+ stack.Push(x)
case oMLOAD:
- x := vm.stack.Pop()
- vm.stack.Push(vm.mem[x.String()])
- case oMSTORE:
- x, y := vm.stack.Popn()
- vm.mem[x.String()] = y
+ 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
+ // Pop value of the stack
+ val, mStart := stack.Popn()
+ mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(val, 256))
+ case oMSTORE8:
+ val, mStart := stack.Popn()
+ base.And(val, new(big.Int).SetInt64(0xff))
+ mem.Set(mStart.Int64(), 32, ethutil.BigToBytes(base, 256))
case oSLOAD:
- // Load the value in storage and push it on the stack
- x := vm.stack.Pop()
- // decode the object as a big integer
- decoder := contract.Addr(x.Bytes())
- if !decoder.IsNil() {
- vm.stack.Push(decoder.BigInt())
- } else {
- vm.stack.Push(ethutil.BigFalse)
- }
+ loc := stack.Pop()
+ val := closure.GetMem(loc)
+ stack.Push(val.BigInt())
case oSSTORE:
- // Store Y at index X
- y, x := vm.stack.Popn()
- addr := ethutil.BigToBytes(x, 256)
- fmt.Printf(" => %x (%v) @ %v", y.Bytes(), y, ethutil.BigD(addr))
- contract.SetAddr(addr, y)
- //contract.State().Update(string(idx), string(y))
- case oJMP:
- x := int(vm.stack.Pop().Uint64())
- // Set pc to x - 1 (minus one so the incrementing at the end won't effect it)
- pc = x
- pc--
- case oJMPI:
- x := vm.stack.Pop()
- // Set pc to x if it's non zero
- if x.Cmp(ethutil.BigFalse) != 0 {
- pc = int(x.Uint64())
- pc--
+ val, loc := stack.Popn()
+ closure.SetMem(loc, ethutil.NewValue(val))
+ case oJUMP:
+ pc = stack.Pop()
+ case oJUMPI:
+ pos, cond := stack.Popn()
+ if cond.Cmp(big.NewInt(0)) > 0 {
+ pc = pos
}
- case oIND:
- vm.stack.Push(big.NewInt(int64(pc)))
- case oEXTRO:
- memAddr := vm.stack.Pop()
- contractAddr := vm.stack.Pop().Bytes()
+ case oPC:
+ stack.Push(pc)
+ case oMSIZE:
+ stack.Push(big.NewInt(int64(mem.Len())))
+ // 0x60 range
+ case oCALL:
+ // Pop return size and offset
+ retSize, retOffset := stack.Popn()
+ // Pop input size and offset
+ inSize, inOffset := stack.Popn()
+ // Get the arguments from the memory
+ args := mem.Get(inOffset.Int64(), inSize.Int64())
+ // Pop gas and value of the stack.
+ gas, value := stack.Popn()
+ // Closure addr
+ addr := stack.Pop()
+ // Fetch the contract which will serve as the closure body
+ contract := vm.state.GetContract(addr.Bytes())
+ // Create a new callable closure
+ closure := NewClosure(closure, contract, vm.state, gas, value)
+ // Executer the closure and get the return value (if any)
+ ret := closure.Call(vm, args)
- // Push the contract's memory on to the stack
- vm.stack.Push(contractMemory(state, contractAddr, memAddr))
- case oBALANCE:
- // Pushes the balance of the popped value on to the stack
- account := state.GetAccount(vm.stack.Pop().Bytes())
- vm.stack.Push(account.Amount)
- case oMKTX:
- addr, value := vm.stack.Popn()
- from, length := vm.stack.Popn()
+ mem.Set(retOffset.Int64(), retSize.Int64(), ret)
+ case oRETURN:
+ size, offset := stack.Popn()
+ ret := mem.Get(offset.Int64(), size.Int64())
- makeInlineTx(addr.Bytes(), value, from, length, contract, state)
+ return closure.Return(ret)
case oSUICIDE:
- recAddr := vm.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), "")
+ /*
+ 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
+ ethutil.Config.Log.Debugf("(%d) => %x\n", deletedMemory, recAddr)
+ break out
+ */
default:
- fmt.Printf("Invalid OPCODE: %x\n", op)
+ ethutil.Config.Log.Debugln("Invalid opcode", op)
}
- ethutil.Config.Log.Debugln("")
- //vm.stack.Print()
- pc++
- }
- state.UpdateContract(addr, contract)
+ pc.Add(pc, ethutil.Big1)
+ }
}
func makeInlineTx(addr []byte, value, from, length *big.Int, contract *Contract, state *State) {
ethutil.Config.Log.Debugf(" => creating inline tx %x %v %v %v", addr, value, from, length)
- j := 0
+ j := int64(0)
dataItems := make([]string, int(length.Uint64()))
- for i := from.Uint64(); i < length.Uint64(); i++ {
- dataItems[j] = contract.GetMem(j).Str()
+ for i := from.Int64(); i < length.Int64(); i++ {
+ dataItems[j] = contract.GetMem(big.NewInt(j)).Str()
j++
}
tx := NewTransaction(addr, value, dataItems)
if tx.IsContract() {
contract := MakeContract(tx, state)
- state.UpdateContract(tx.Hash()[12:], contract)
+ state.UpdateContract(contract)
} else {
account := state.GetAccount(tx.Recipient)
account.Amount.Add(account.Amount, tx.Value)
diff --git a/ethchain/vm_test.go b/ethchain/vm_test.go
index 6ceb0ff15..047531e09 100644
--- a/ethchain/vm_test.go
+++ b/ethchain/vm_test.go
@@ -1,13 +1,15 @@
package ethchain
import (
- "fmt"
+ "bytes"
"github.com/ethereum/eth-go/ethdb"
"github.com/ethereum/eth-go/ethutil"
"math/big"
"testing"
)
+/*
+
func TestRun(t *testing.T) {
InitFees()
@@ -104,3 +106,69 @@ func TestRun2(t *testing.T) {
txData: tx.Data,
})
}
+*/
+
+// XXX Full stack test
+func TestRun3(t *testing.T) {
+ ethutil.ReadConfig("")
+
+ db, _ := ethdb.NewMemDatabase()
+ state := NewState(ethutil.NewTrie(db, ""))
+
+ script := Compile([]string{
+ "PUSH", "300",
+ "PUSH", "0",
+ "MSTORE",
+
+ "PUSH", "32",
+ "CALLDATA",
+
+ "PUSH", "64",
+ "PUSH", "0",
+ "RETURN",
+ })
+ tx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), script)
+ addr := tx.Hash()[12:]
+ contract := MakeContract(tx, state)
+ state.UpdateContract(contract)
+
+ callerScript := ethutil.Compile(
+ "PUSH", 1337, // Argument
+ "PUSH", 65, // argument mem offset
+ "MSTORE",
+ "PUSH", 64, // ret size
+ "PUSH", 0, // ret offset
+
+ "PUSH", 32, // arg size
+ "PUSH", 65, // arg offset
+ "PUSH", 1000, /// Gas
+ "PUSH", 0, /// value
+ "PUSH", addr, // Sender
+ "CALL",
+ "PUSH", 64,
+ "PUSH", 0,
+ "RETURN",
+ )
+ callerTx := NewTransaction(ContractAddr, ethutil.Big("100000000000000000000000000000000000000000000000000"), callerScript)
+
+ // Contract addr as test address
+ account := NewAccount(ContractAddr, big.NewInt(10000000))
+ callerClosure := NewClosure(account, MakeContract(callerTx, state), state, big.NewInt(1000000000), new(big.Int))
+
+ vm := NewVm(state, RuntimeVars{
+ origin: account.Address(),
+ blockNumber: 1,
+ prevHash: ethutil.FromHex("5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6"),
+ coinbase: ethutil.FromHex("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"),
+ time: 1,
+ diff: big.NewInt(256),
+ // XXX Tx data? Could be just an argument to the closure instead
+ txData: nil,
+ })
+ ret := callerClosure.Call(vm, nil)
+
+ exp := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 57}
+ if bytes.Compare(ret, exp) != 0 {
+ t.Errorf("expected return value to be %v, got %v", exp, ret)
+ }
+}