From 75e88969cbdfe670643c0ada0f2552fe5edba198 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 14 May 2015 20:58:24 -0400 Subject: [PATCH 1/5] failing test for send funds from contract --- vm/test/fake_app_state.go | 7 +------ vm/test/vm_test.go | 34 ++++++++++++++++++++++++++++++++++ vm/vm.go | 4 ++-- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/vm/test/fake_app_state.go b/vm/test/fake_app_state.go index 02e1389d..caee79df 100644 --- a/vm/test/fake_app_state.go +++ b/vm/test/fake_app_state.go @@ -22,12 +22,7 @@ func (fas *FakeAppState) GetAccount(addr Word256) *Account { } func (fas *FakeAppState) UpdateAccount(account *Account) { - _, ok := fas.accounts[account.Address.String()] - if !ok { - panic(Fmt("Invalid account addr: %X", account.Address)) - } else { - // Nothing to do - } + fas.accounts[account.Address.String()] = account } func (fas *FakeAppState) RemoveAccount(account *Account) { diff --git a/vm/test/vm_test.go b/vm/test/vm_test.go index ef9d8834..fd037441 100644 --- a/vm/test/vm_test.go +++ b/vm/test/vm_test.go @@ -35,6 +35,7 @@ func makeBytes(n int) []byte { return b } +// Runs a basic loop func TestVM(t *testing.T) { ourVm := NewVM(newAppState(), newParams(), Zero256, nil) @@ -60,6 +61,7 @@ func TestVM(t *testing.T) { fmt.Println("Call took:", time.Since(start)) } +// Tests the code for a subcurrency contract compiled by serpent func TestSubcurrency(t *testing.T) { st := newAppState() // Create accounts @@ -89,6 +91,38 @@ func TestSubcurrency(t *testing.T) { } +// Test sending tokens from a contract to another account +func TestSendCall(t *testing.T) { + fakeAppState := newAppState() + ourVm := NewVM(fakeAppState, newParams(), Zero256, nil) + + // Create accounts + account1 := &Account{ + Address: Uint64ToWord256(100), + } + account2 := &Account{ + Address: Uint64ToWord256(101), + } + fakeAppState.UpdateAccount(account1) + fakeAppState.UpdateAccount(account2) + + addr := account1.Address.Postfix(20) + gas1, gas2 := byte(0x1), byte(0x1) + value := byte(0x69) + inOff, inSize := byte(0x0), byte(0x0) // no call data + retOff, retSize := byte(0x0), byte(0x20) + // this is the code we want to run (send funds to an account and return) + contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, 0x60, value, 0x73} + contractCode = append(contractCode, addr...) + contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...) + + var gas uint64 = 1000 + start := time.Now() + output, err := ourVm.Call(account1, account2, contractCode, []byte{}, 0, &gas) + fmt.Printf("Output: %v Error: %v\n", output, err) + fmt.Println("Call took:", time.Since(start)) +} + /* // infinite loop code := []byte{0x5B, 0x60, 0x00, 0x56} diff --git a/vm/vm.go b/vm/vm.go index f9ad0b1c..43d86454 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -622,7 +622,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga stack.Push(res) pc += a dbg.Printf(" => 0x%X\n", res) - stack.Print(10) + //stack.Print(10) case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: n := int(op - DUP1 + 1) @@ -633,7 +633,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga n := int(op - SWAP1 + 2) stack.Swap(n) dbg.Printf(" => [%d] %X\n", n, stack.Peek()) - stack.Print(10) + //stack.Print(10) case LOG0, LOG1, LOG2, LOG3, LOG4: n := int(op - LOG0) From 7b1fc780aa423014855981864f604c98f57a3b55 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 14 May 2015 20:23:36 -0400 Subject: [PATCH 2/5] fix send funds from contract --- vm/test/fake_app_state.go | 6 +----- vm/test/vm_test.go | 12 +++++++++--- vm/vm.go | 40 +++++++++++++++++++++++++-------------- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/vm/test/fake_app_state.go b/vm/test/fake_app_state.go index caee79df..6f703d72 100644 --- a/vm/test/fake_app_state.go +++ b/vm/test/fake_app_state.go @@ -14,11 +14,7 @@ type FakeAppState struct { func (fas *FakeAppState) GetAccount(addr Word256) *Account { account := fas.accounts[addr.String()] - if account != nil { - return account - } else { - panic(Fmt("Invalid account addr: %X", addr)) - } + return account } func (fas *FakeAppState) UpdateAccount(account *Account) { diff --git a/vm/test/vm_test.go b/vm/test/vm_test.go index fd037441..f6703e7a 100644 --- a/vm/test/vm_test.go +++ b/vm/test/vm_test.go @@ -103,10 +103,13 @@ func TestSendCall(t *testing.T) { account2 := &Account{ Address: Uint64ToWord256(101), } - fakeAppState.UpdateAccount(account1) - fakeAppState.UpdateAccount(account2) + account3 := &Account{ + Address: Uint64ToWord256(102), + } - addr := account1.Address.Postfix(20) + // account1 will call account2 which will trigger CALL opcode to account3 + + addr := account3.Address.Postfix(20) gas1, gas2 := byte(0x1), byte(0x1) value := byte(0x69) inOff, inSize := byte(0x0), byte(0x0) // no call data @@ -121,6 +124,9 @@ func TestSendCall(t *testing.T) { output, err := ourVm.Call(account1, account2, contractCode, []byte{}, 0, &gas) fmt.Printf("Output: %v Error: %v\n", output, err) fmt.Println("Call took:", time.Since(start)) + if err != nil { + t.Fatal(err) + } } /* diff --git a/vm/vm.go b/vm/vm.go index 43d86454..4c0d9e96 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -71,27 +71,27 @@ func (vm *VM) SetFireable(evc events.Fireable) { // CONTRACT appState is aware of caller and callee, so we can just mutate them. // value: To be transferred from caller to callee. Refunded upon error. // gas: Available gas. No refunds for gas. +// code: May be nil, since the CALL opcode may be used to send value from contracts to accounts func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { - if len(code) == 0 { - panic("Call() requires code") - } - if err = transfer(caller, callee, value); err != nil { return } - vm.callDepth += 1 - output, err = vm.call(caller, callee, code, input, value, gas) - vm.callDepth -= 1 exception := "" - if err != nil { - exception = err.Error() - err := transfer(callee, caller, value) + if len(code) > 0 { + vm.callDepth += 1 + output, err = vm.call(caller, callee, code, input, value, gas) + vm.callDepth -= 1 if err != nil { - panic("Could not return value to caller") + exception = err.Error() + err := transfer(callee, caller, value) + if err != nil { + panic("Could not return value to caller") + } } } + // if callDepth is 0 the event is fired from ExecTx (along with the Input event) // otherwise, we fire from here. if vm.callDepth != 0 && vm.evc != nil { @@ -713,12 +713,24 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga return nil, firstErr(err, ErrInsufficientGas) } acc := vm.appState.GetAccount(addr) - if acc == nil { - return nil, firstErr(err, ErrUnknownAddress) - } + // since CALL is used also for sending funds, + // acc may not exist yet. This is an error for + // CALLCODE, but not for CALL, though I don't think + // ethereum actually cares if op == CALLCODE { + if acc == nil { + return nil, firstErr(err, ErrUnknownAddress) + } ret, err = vm.Call(callee, callee, acc.Code, args, value, gas) } else { + if acc == nil { + // if we have not seen the account before, create it + // so we can send funds + acc = &Account{ + Address: addr, + } + vm.appState.UpdateAccount(acc) + } ret, err = vm.Call(callee, acc, acc.Code, args, value, gas) } } From bda9a38544e88c24947aa1f4e5c15b64c7a0621d Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 15 May 2015 14:34:42 -0400 Subject: [PATCH 3/5] CallTx to address with no code fails and costs gas --- state/execution.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/state/execution.go b/state/execution.go index 33eb160a..d1cdc485 100644 --- a/state/execution.go +++ b/state/execution.go @@ -403,11 +403,16 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // Maybe create a new callee account if // this transaction is creating a new contract. if !createAccount { - if outAcc == nil { - // take fees (sorry pal) + if outAcc == nil || len(outAcc.Code) == 0 { + // if you call an account that doesn't exist + // or an account with no code then we take fees (sorry pal) inAcc.Balance -= tx.Fee blockCache.UpdateAccount(inAcc) - log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address)) + if outAcc == nil { + log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address)) + } else { + log.Debug(Fmt("Attempting to call an account (%X) with no code. Deducting fee from caller", tx.Address)) + } return types.ErrTxInvalidAddress } From 0dbf17653af9d5dbf2c09a8bddf85ca3e91ab571 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 15 May 2015 15:30:24 -0400 Subject: [PATCH 4/5] fire event on failed funds transfer in vm --- vm/test/vm_test.go | 89 +++++++++++++++++++++++++++++++++++++++------- vm/vm.go | 33 ++++++++++------- 2 files changed, 96 insertions(+), 26 deletions(-) diff --git a/vm/test/vm_test.go b/vm/test/vm_test.go index f6703e7a..e285a911 100644 --- a/vm/test/vm_test.go +++ b/vm/test/vm_test.go @@ -9,6 +9,8 @@ import ( "time" . "github.com/tendermint/tendermint/common" + "github.com/tendermint/tendermint/events" + "github.com/tendermint/tendermint/types" . "github.com/tendermint/tendermint/vm" ) @@ -47,8 +49,8 @@ func TestVM(t *testing.T) { Address: Uint64ToWord256(101), } - var gas uint64 = 1000 - N := []byte{0xff, 0xff} + var gas uint64 = 100000 + N := []byte{0x0f, 0x0f} // Loop N times code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)} for i := 0; i < len(N); i++ { @@ -59,6 +61,9 @@ func TestVM(t *testing.T) { output, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas) fmt.Printf("Output: %v Error: %v\n", output, err) fmt.Println("Call took:", time.Since(start)) + if err != nil { + t.Fatal(err) + } } // Tests the code for a subcurrency contract compiled by serpent @@ -88,7 +93,9 @@ func TestSubcurrency(t *testing.T) { data, _ := hex.DecodeString("693200CE0000000000000000000000004B4363CDE27C2EB05E66357DB05BC5C88F850C1A0000000000000000000000000000000000000000000000000000000000000005") output, err := ourVm.Call(account1, account2, code, data, 0, &gas) fmt.Printf("Output: %v Error: %v\n", output, err) - + if err != nil { + t.Fatal(err) + } } // Test sending tokens from a contract to another account @@ -108,8 +115,72 @@ func TestSendCall(t *testing.T) { } // account1 will call account2 which will trigger CALL opcode to account3 - addr := account3.Address.Postfix(20) + contractCode := callContractCode(addr) + + //---------------------------------------------- + // account2 has insufficient balance, should fail + + exception := runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000) + if exception == "" { + t.Fatal("Expected exception") + } + + //---------------------------------------------- + // give account2 sufficient balance, should pass + + account2.Balance = 100000 + exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000) + if exception != "" { + t.Fatal("Unexpected exception", exception) + } + + //---------------------------------------------- + // insufficient gas, should fail + + account2.Balance = 100000 + exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 100) + if exception == "" { + t.Fatal("Expected exception") + } +} + +// subscribes to an AccReceive, runs the vm, returns the exception +func runVMWaitEvents(t *testing.T, ourVm *VM, caller, callee *Account, subscribeAddr, contractCode []byte, gas uint64) string { + // we need to catch the event from the CALL to check for exceptions + evsw := new(events.EventSwitch) + evsw.Start() + ch := make(chan interface{}) + fmt.Printf("subscribe to %x\n", subscribeAddr) + evsw.AddListenerForEvent("test", types.EventStringAccReceive(subscribeAddr), func(msg interface{}) { + ch <- msg + }) + evc := events.NewEventCache(evsw) + ourVm.SetFireable(evc) + go func() { + start := time.Now() + output, err := ourVm.Call(caller, callee, contractCode, []byte{}, 0, &gas) + fmt.Printf("Output: %v Error: %v\n", output, err) + fmt.Println("Call took:", time.Since(start)) + if err != nil { + ch <- err.Error() + } + evc.Flush() + }() + msg := <-ch + switch ev := msg.(type) { + case types.EventMsgCallTx: + return ev.Exception + case types.EventMsgCall: + return ev.Exception + case string: + return ev + } + return "" +} + +// this is code to call another contract (hardcoded as addr) +func callContractCode(addr []byte) []byte { gas1, gas2 := byte(0x1), byte(0x1) value := byte(0x69) inOff, inSize := byte(0x0), byte(0x0) // no call data @@ -118,15 +189,7 @@ func TestSendCall(t *testing.T) { contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, 0x60, value, 0x73} contractCode = append(contractCode, addr...) contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...) - - var gas uint64 = 1000 - start := time.Now() - output, err := ourVm.Call(account1, account2, contractCode, []byte{}, 0, &gas) - fmt.Printf("Output: %v Error: %v\n", output, err) - fmt.Println("Call took:", time.Since(start)) - if err != nil { - t.Fatal(err) - } + return contractCode } /* diff --git a/vm/vm.go b/vm/vm.go index 4c0d9e96..96163868 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -74,17 +74,33 @@ func (vm *VM) SetFireable(evc events.Fireable) { // code: May be nil, since the CALL opcode may be used to send value from contracts to accounts func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { + exception := new(string) + defer func() { + // if callDepth is 0 the event is fired from ExecTx (along with the Input event) + // otherwise, we fire from here. + if vm.callDepth != 0 && vm.evc != nil { + fmt.Println("FIRE AWAY!", types.EventStringAccReceive(callee.Address.Postfix(20))) + vm.evc.FireEvent(types.EventStringAccReceive(callee.Address.Postfix(20)), types.EventMsgCall{ + &types.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas}, + vm.origin.Postfix(20), + vm.txid, + output, + *exception, + }) + } + }() + if err = transfer(caller, callee, value); err != nil { + *exception = err.Error() return } - exception := "" if len(code) > 0 { vm.callDepth += 1 output, err = vm.call(caller, callee, code, input, value, gas) vm.callDepth -= 1 if err != nil { - exception = err.Error() + *exception = err.Error() err := transfer(callee, caller, value) if err != nil { panic("Could not return value to caller") @@ -92,17 +108,6 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga } } - // if callDepth is 0 the event is fired from ExecTx (along with the Input event) - // otherwise, we fire from here. - if vm.callDepth != 0 && vm.evc != nil { - vm.evc.FireEvent(types.EventStringAccReceive(callee.Address.Postfix(20)), types.EventMsgCall{ - &types.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas}, - vm.origin.Postfix(20), - vm.txid, - output, - exception, - }) - } return } @@ -737,6 +742,8 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga // Push result if err != nil { + dbg.Printf("error on call: %s", err.Error()) + // TODO: fire event stack.Push(Zero256) } else { stack.Push(One256) From 02f4219079803c2d22e155d1f1d02b7f94ab97d5 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 15 May 2015 16:12:34 -0400 Subject: [PATCH 5/5] cleanup calldepth=0 events logic --- rpc/test/client_ws_test.go | 14 +++++++++----- rpc/test/ws_helpers_test.go | 1 - state/execution.go | 7 ++++++- vm/vm.go | 5 +---- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/rpc/test/client_ws_test.go b/rpc/test/client_ws_test.go index 85cb2cee..c926b612 100644 --- a/rpc/test/client_ws_test.go +++ b/rpc/test/client_ws_test.go @@ -230,7 +230,7 @@ func TestWSCallWait(t *testing.T) { // susbscribe to the new contract amt = uint64(10001) - eid2 := types.EventStringAccReceive(contractAddr) + eid2 := types.EventStringAccOutput(contractAddr) subscribe(t, con, eid2) defer func() { unsubscribe(t, con, eid2) @@ -254,7 +254,7 @@ func TestWSCallNoWait(t *testing.T) { // susbscribe to the new contract amt = uint64(10001) - eid := types.EventStringAccReceive(contractAddr) + eid := types.EventStringAccOutput(contractAddr) subscribe(t, con, eid) defer func() { unsubscribe(t, con, eid) @@ -284,16 +284,20 @@ func TestWSCallCall(t *testing.T) { // susbscribe to the new contracts amt = uint64(10001) eid1 := types.EventStringAccReceive(contractAddr1) - eid2 := types.EventStringAccReceive(contractAddr2) subscribe(t, con, eid1) - subscribe(t, con, eid2) defer func() { unsubscribe(t, con, eid1) - unsubscribe(t, con, eid2) con.Close() }() // call contract2, which should call contract1, and wait for ev1 data := []byte{0x1} // just needs to be non empty for this to be a CallTx + + // let the contract get created first + waitForEvent(t, con, eid1, true, func() { + }, func(eid string, b []byte) error { + return nil + }) + // call it waitForEvent(t, con, eid1, true, func() { tx, _ := broadcastTx(t, "JSONRPC", userByteAddr, contractAddr2, data, userBytePriv, amt, 1000, 1000) *txid = account.HashSignBytes(tx) diff --git a/rpc/test/ws_helpers_test.go b/rpc/test/ws_helpers_test.go index 2eacbfcc..241e7a7b 100644 --- a/rpc/test/ws_helpers_test.go +++ b/rpc/test/ws_helpers_test.go @@ -130,7 +130,6 @@ func unmarshalValidateCallCall(origin, returnCode []byte, txid *[]byte) func(str if bytes.Compare(response.Data.TxId, *txid) != 0 { return fmt.Errorf("TxIds do not match up! Got %x, expected %x", response.Data.TxId, *txid) } - // calldata := response.Data.CallData return nil } } diff --git a/state/execution.go b/state/execution.go index d1cdc485..dabe8016 100644 --- a/state/execution.go +++ b/state/execution.go @@ -406,6 +406,11 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea if outAcc == nil || len(outAcc.Code) == 0 { // if you call an account that doesn't exist // or an account with no code then we take fees (sorry pal) + // NOTE: it's fine to create a contract and call it within one + // block (nonce will prevent re-ordering of those txs) + // but to create with one account and call with another + // you have to wait a block to avoid a re-ordering attack + // that will take your fees inAcc.Balance -= tx.Fee blockCache.UpdateAccount(inAcc) if outAcc == nil { @@ -457,7 +462,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // a separate event will be fired from vm for each additional call if evc != nil { evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventMsgCallTx{tx, ret, exception}) - evc.FireEvent(types.EventStringAccReceive(tx.Address), types.EventMsgCallTx{tx, ret, exception}) + evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventMsgCallTx{tx, ret, exception}) } } else { // The mempool does not call txs until diff --git a/vm/vm.go b/vm/vm.go index 96163868..6a814655 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -76,10 +76,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga exception := new(string) defer func() { - // if callDepth is 0 the event is fired from ExecTx (along with the Input event) - // otherwise, we fire from here. - if vm.callDepth != 0 && vm.evc != nil { - fmt.Println("FIRE AWAY!", types.EventStringAccReceive(callee.Address.Postfix(20))) + if vm.evc != nil { vm.evc.FireEvent(types.EventStringAccReceive(callee.Address.Postfix(20)), types.EventMsgCall{ &types.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas}, vm.origin.Postfix(20),