From 0dbf17653af9d5dbf2c09a8bddf85ca3e91ab571 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 15 May 2015 15:30:24 -0400 Subject: [PATCH] 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)