diff --git a/vm/test/fake_app_state.go b/vm/test/fake_app_state.go index caee79dfd..6f703d725 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 fd0374411..f6703e7a8 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), + } + + // account1 will call account2 which will trigger CALL opcode to account3 - addr := account1.Address.Postfix(20) + 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 43d864541..4c0d9e966 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) } }