// 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) { exception := new(string) defer func() { 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), vm.txid, output, *exception, }) } }() if err = transfer(caller, callee, value); err != nil { *exception = err.Error() return } 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() err := transfer(callee, caller, value) if err != nil { panic("Could not return value to caller") } } } return }
// 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 "" }
// create two contracts, one of which calls the other func TestWSCallCall(t *testing.T) { con := newWSCon(t) amt := uint64(10000) code, _, returnVal := simpleContract() txid := new([]byte) // deploy the two contracts _, receipt := broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) contractAddr1 := receipt.ContractAddr code, _, _ = simpleCallContract(contractAddr1) _, receipt = broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) contractAddr2 := receipt.ContractAddr // susbscribe to the new contracts amt = uint64(10001) eid1 := types.EventStringAccReceive(contractAddr1) subscribe(t, con, eid1) defer func() { unsubscribe(t, con, eid1) 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) }, unmarshalValidateCallCall(userByteAddr, returnVal, txid)) }