commit
cb8261f2ff
|
@ -230,7 +230,7 @@ func TestWSCallWait(t *testing.T) {
|
||||||
|
|
||||||
// susbscribe to the new contract
|
// susbscribe to the new contract
|
||||||
amt = uint64(10001)
|
amt = uint64(10001)
|
||||||
eid2 := types.EventStringAccReceive(contractAddr)
|
eid2 := types.EventStringAccOutput(contractAddr)
|
||||||
subscribe(t, con, eid2)
|
subscribe(t, con, eid2)
|
||||||
defer func() {
|
defer func() {
|
||||||
unsubscribe(t, con, eid2)
|
unsubscribe(t, con, eid2)
|
||||||
|
@ -254,7 +254,7 @@ func TestWSCallNoWait(t *testing.T) {
|
||||||
|
|
||||||
// susbscribe to the new contract
|
// susbscribe to the new contract
|
||||||
amt = uint64(10001)
|
amt = uint64(10001)
|
||||||
eid := types.EventStringAccReceive(contractAddr)
|
eid := types.EventStringAccOutput(contractAddr)
|
||||||
subscribe(t, con, eid)
|
subscribe(t, con, eid)
|
||||||
defer func() {
|
defer func() {
|
||||||
unsubscribe(t, con, eid)
|
unsubscribe(t, con, eid)
|
||||||
|
@ -284,16 +284,20 @@ func TestWSCallCall(t *testing.T) {
|
||||||
// susbscribe to the new contracts
|
// susbscribe to the new contracts
|
||||||
amt = uint64(10001)
|
amt = uint64(10001)
|
||||||
eid1 := types.EventStringAccReceive(contractAddr1)
|
eid1 := types.EventStringAccReceive(contractAddr1)
|
||||||
eid2 := types.EventStringAccReceive(contractAddr2)
|
|
||||||
subscribe(t, con, eid1)
|
subscribe(t, con, eid1)
|
||||||
subscribe(t, con, eid2)
|
|
||||||
defer func() {
|
defer func() {
|
||||||
unsubscribe(t, con, eid1)
|
unsubscribe(t, con, eid1)
|
||||||
unsubscribe(t, con, eid2)
|
|
||||||
con.Close()
|
con.Close()
|
||||||
}()
|
}()
|
||||||
// call contract2, which should call contract1, and wait for ev1
|
// 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
|
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() {
|
waitForEvent(t, con, eid1, true, func() {
|
||||||
tx, _ := broadcastTx(t, "JSONRPC", userByteAddr, contractAddr2, data, userBytePriv, amt, 1000, 1000)
|
tx, _ := broadcastTx(t, "JSONRPC", userByteAddr, contractAddr2, data, userBytePriv, amt, 1000, 1000)
|
||||||
*txid = account.HashSignBytes(tx)
|
*txid = account.HashSignBytes(tx)
|
||||||
|
|
|
@ -130,7 +130,6 @@ func unmarshalValidateCallCall(origin, returnCode []byte, txid *[]byte) func(str
|
||||||
if bytes.Compare(response.Data.TxId, *txid) != 0 {
|
if bytes.Compare(response.Data.TxId, *txid) != 0 {
|
||||||
return fmt.Errorf("TxIds do not match up! Got %x, expected %x", response.Data.TxId, *txid)
|
return fmt.Errorf("TxIds do not match up! Got %x, expected %x", response.Data.TxId, *txid)
|
||||||
}
|
}
|
||||||
// calldata := response.Data.CallData
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -403,11 +403,21 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea
|
||||||
// Maybe create a new callee account if
|
// Maybe create a new callee account if
|
||||||
// this transaction is creating a new contract.
|
// this transaction is creating a new contract.
|
||||||
if !createAccount {
|
if !createAccount {
|
||||||
if outAcc == nil {
|
if outAcc == nil || len(outAcc.Code) == 0 {
|
||||||
// take fees (sorry pal)
|
// 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
|
inAcc.Balance -= tx.Fee
|
||||||
blockCache.UpdateAccount(inAcc)
|
blockCache.UpdateAccount(inAcc)
|
||||||
|
if outAcc == nil {
|
||||||
log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
|
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
|
return types.ErrTxInvalidAddress
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -452,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
|
// a separate event will be fired from vm for each additional call
|
||||||
if evc != nil {
|
if evc != nil {
|
||||||
evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventMsgCallTx{tx, ret, exception})
|
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 {
|
} else {
|
||||||
// The mempool does not call txs until
|
// The mempool does not call txs until
|
||||||
|
|
|
@ -14,20 +14,11 @@ type FakeAppState struct {
|
||||||
|
|
||||||
func (fas *FakeAppState) GetAccount(addr Word256) *Account {
|
func (fas *FakeAppState) GetAccount(addr Word256) *Account {
|
||||||
account := fas.accounts[addr.String()]
|
account := fas.accounts[addr.String()]
|
||||||
if account != nil {
|
|
||||||
return account
|
return account
|
||||||
} else {
|
|
||||||
panic(Fmt("Invalid account addr: %X", addr))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fas *FakeAppState) UpdateAccount(account *Account) {
|
func (fas *FakeAppState) UpdateAccount(account *Account) {
|
||||||
_, ok := fas.accounts[account.Address.String()]
|
fas.accounts[account.Address.String()] = account
|
||||||
if !ok {
|
|
||||||
panic(Fmt("Invalid account addr: %X", account.Address))
|
|
||||||
} else {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fas *FakeAppState) RemoveAccount(account *Account) {
|
func (fas *FakeAppState) RemoveAccount(account *Account) {
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/tendermint/tendermint/common"
|
. "github.com/tendermint/tendermint/common"
|
||||||
|
"github.com/tendermint/tendermint/events"
|
||||||
|
"github.com/tendermint/tendermint/types"
|
||||||
. "github.com/tendermint/tendermint/vm"
|
. "github.com/tendermint/tendermint/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,6 +37,7 @@ func makeBytes(n int) []byte {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs a basic loop
|
||||||
func TestVM(t *testing.T) {
|
func TestVM(t *testing.T) {
|
||||||
ourVm := NewVM(newAppState(), newParams(), Zero256, nil)
|
ourVm := NewVM(newAppState(), newParams(), Zero256, nil)
|
||||||
|
|
||||||
|
@ -46,8 +49,8 @@ func TestVM(t *testing.T) {
|
||||||
Address: Uint64ToWord256(101),
|
Address: Uint64ToWord256(101),
|
||||||
}
|
}
|
||||||
|
|
||||||
var gas uint64 = 1000
|
var gas uint64 = 100000
|
||||||
N := []byte{0xff, 0xff}
|
N := []byte{0x0f, 0x0f}
|
||||||
// Loop N times
|
// Loop N times
|
||||||
code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)}
|
code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)}
|
||||||
for i := 0; i < len(N); i++ {
|
for i := 0; i < len(N); i++ {
|
||||||
|
@ -58,8 +61,12 @@ func TestVM(t *testing.T) {
|
||||||
output, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas)
|
output, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas)
|
||||||
fmt.Printf("Output: %v Error: %v\n", output, err)
|
fmt.Printf("Output: %v Error: %v\n", output, err)
|
||||||
fmt.Println("Call took:", time.Since(start))
|
fmt.Println("Call took:", time.Since(start))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests the code for a subcurrency contract compiled by serpent
|
||||||
func TestSubcurrency(t *testing.T) {
|
func TestSubcurrency(t *testing.T) {
|
||||||
st := newAppState()
|
st := newAppState()
|
||||||
// Create accounts
|
// Create accounts
|
||||||
|
@ -86,7 +93,103 @@ func TestSubcurrency(t *testing.T) {
|
||||||
data, _ := hex.DecodeString("693200CE0000000000000000000000004B4363CDE27C2EB05E66357DB05BC5C88F850C1A0000000000000000000000000000000000000000000000000000000000000005")
|
data, _ := hex.DecodeString("693200CE0000000000000000000000004B4363CDE27C2EB05E66357DB05BC5C88F850C1A0000000000000000000000000000000000000000000000000000000000000005")
|
||||||
output, err := ourVm.Call(account1, account2, code, data, 0, &gas)
|
output, err := ourVm.Call(account1, account2, code, data, 0, &gas)
|
||||||
fmt.Printf("Output: %v Error: %v\n", output, err)
|
fmt.Printf("Output: %v Error: %v\n", output, err)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
}
|
||||||
|
account3 := &Account{
|
||||||
|
Address: Uint64ToWord256(102),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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}...)
|
||||||
|
return contractCode
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
68
vm/vm.go
68
vm/vm.go
|
@ -71,38 +71,40 @@ func (vm *VM) SetFireable(evc events.Fireable) {
|
||||||
// CONTRACT appState is aware of caller and callee, so we can just mutate them.
|
// 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.
|
// value: To be transferred from caller to callee. Refunded upon error.
|
||||||
// gas: Available gas. No refunds for gas.
|
// 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) {
|
func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) {
|
||||||
|
|
||||||
if len(code) == 0 {
|
exception := new(string)
|
||||||
panic("Call() requires code")
|
defer func() {
|
||||||
}
|
if vm.evc != nil {
|
||||||
|
|
||||||
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 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 {
|
|
||||||
vm.evc.FireEvent(types.EventStringAccReceive(callee.Address.Postfix(20)), types.EventMsgCall{
|
vm.evc.FireEvent(types.EventStringAccReceive(callee.Address.Postfix(20)), types.EventMsgCall{
|
||||||
&types.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas},
|
&types.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas},
|
||||||
vm.origin.Postfix(20),
|
vm.origin.Postfix(20),
|
||||||
vm.txid,
|
vm.txid,
|
||||||
output,
|
output,
|
||||||
exception,
|
*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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -622,7 +624,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
|
||||||
stack.Push(res)
|
stack.Push(res)
|
||||||
pc += a
|
pc += a
|
||||||
dbg.Printf(" => 0x%X\n", res)
|
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:
|
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
|
||||||
n := int(op - DUP1 + 1)
|
n := int(op - DUP1 + 1)
|
||||||
|
@ -633,7 +635,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
|
||||||
n := int(op - SWAP1 + 2)
|
n := int(op - SWAP1 + 2)
|
||||||
stack.Swap(n)
|
stack.Swap(n)
|
||||||
dbg.Printf(" => [%d] %X\n", n, stack.Peek())
|
dbg.Printf(" => [%d] %X\n", n, stack.Peek())
|
||||||
stack.Print(10)
|
//stack.Print(10)
|
||||||
|
|
||||||
case LOG0, LOG1, LOG2, LOG3, LOG4:
|
case LOG0, LOG1, LOG2, LOG3, LOG4:
|
||||||
n := int(op - LOG0)
|
n := int(op - LOG0)
|
||||||
|
@ -713,18 +715,32 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
|
||||||
return nil, firstErr(err, ErrInsufficientGas)
|
return nil, firstErr(err, ErrInsufficientGas)
|
||||||
}
|
}
|
||||||
acc := vm.appState.GetAccount(addr)
|
acc := vm.appState.GetAccount(addr)
|
||||||
|
// 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 {
|
if acc == nil {
|
||||||
return nil, firstErr(err, ErrUnknownAddress)
|
return nil, firstErr(err, ErrUnknownAddress)
|
||||||
}
|
}
|
||||||
if op == CALLCODE {
|
|
||||||
ret, err = vm.Call(callee, callee, acc.Code, args, value, gas)
|
ret, err = vm.Call(callee, callee, acc.Code, args, value, gas)
|
||||||
} else {
|
} 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)
|
ret, err = vm.Call(callee, acc, acc.Code, args, value, gas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push result
|
// Push result
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
dbg.Printf("error on call: %s", err.Error())
|
||||||
|
// TODO: fire event
|
||||||
stack.Push(Zero256)
|
stack.Push(Zero256)
|
||||||
} else {
|
} else {
|
||||||
stack.Push(One256)
|
stack.Push(One256)
|
||||||
|
|
Loading…
Reference in New Issue