diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/permissions_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/permissions_test.go index 580a3cc023d1451f9d23b92a38c3044cf8bbf6df..a5f78963f214d13bd5946fd7581f46e111b52688 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/state/permissions_test.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/state/permissions_test.go @@ -16,6 +16,7 @@ import ( . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/state/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" vm "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm" + . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes" ) /* @@ -1242,17 +1243,17 @@ func snativeRoleTestInputTx(name string, user *acm.PrivAccount, role string) (sn func callContractCode(contractAddr []byte) []byte { // calldatacopy into mem and use as input to call memOff, inputOff := byte(0x0), byte(0x0) - contractCode := []byte{0x36, 0x60, inputOff, 0x60, memOff, 0x37} - - gas1, gas2 := byte(0x1), byte(0x1) value := byte(0x1) inOff := byte(0x0) retOff, retSize := byte(0x0), byte(0x20) + // this is the code we want to run (call a contract and return) - contractCode = append(contractCode, []byte{0x60, retSize, 0x60, retOff, 0x36, 0x60, inOff, 0x60, value, 0x73}...) - contractCode = append(contractCode, contractAddr...) - contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...) - return contractCode + return Bytecode(CALLDATASIZE, PUSH1, inputOff, PUSH1, memOff, + CALLDATACOPY, PUSH1, retSize, PUSH1, retOff, CALLDATASIZE, PUSH1, inOff, + PUSH1, value, PUSH20, contractAddr, + // Zeno loves us - call with half of the available gas each time we CALL + PUSH1, 2, GAS, DIV, CALL, + PUSH1, 32, PUSH1, 0, RETURN) } // convenience function for contract that is a factory for the code that comes as call data diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes/opcodes.go similarity index 81% rename from Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes.go rename to Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes/opcodes.go index 5ebd4807252a273b665359ffe6925e6f0fe7455d..284278396bdda627f41b2449306c749eb4dfcb8d 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes/opcodes.go @@ -354,3 +354,51 @@ func AnalyzeJumpDests(code []byte) (dests *set.Set) { } return } + +// Convenience function to allow us to mix bytes, ints, and OpCodes that +// represent bytes in an EVM assembly code to make assembly more readable. +// Also allows us to splice together assembly +// fragments because any []byte arguments are flattened in the result. +func Bytecode(bytelikes ...interface{}) []byte { + bytes := make([]byte, len(bytelikes)) + for i, bytelike := range bytelikes { + switch b := bytelike.(type) { + case byte: + bytes[i] = b + case OpCode: + bytes[i] = byte(b) + case int: + bytes[i] = byte(b) + if int(bytes[i]) != b { + panic(fmt.Sprintf("The int %v does not fit inside a byte", b)) + } + case int64: + bytes[i] = byte(b) + if int64(bytes[i]) != b { + panic(fmt.Sprintf("The int64 %v does not fit inside a byte", b)) + } + case []byte: + // splice + return Concat(bytes[:i], b, Bytecode(bytelikes[i+1:]...)) + default: + panic("Only byte-like codes (and []byte sequences) can be used to form bytecode") + } + } + return bytes +} + +func Concat(bss ...[]byte) []byte { + offset := 0 + for _, bs := range bss { + offset += len(bs) + } + bytes := make([]byte, offset) + offset = 0 + for _, bs := range bss { + for i, b := range bs { + bytes[offset+i] = b + } + offset += len(bs) + } + return bytes +} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/log_event_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/log_event_test.go index c0fca12dd1ab36e51dfc34d47e340a570e81119f..0f09d9c308c44b338dc9eb8bb3150c2acf7b8e57 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/log_event_test.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/log_event_test.go @@ -9,6 +9,7 @@ import ( "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm" + . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes" ) var expectedData = []byte{0x10} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go index 900a3ff4dded1fc950dc8a62c05cbf36735e1122..f90fbf0820791fb4a6352218c59164cd925146bf 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/test/vm_test.go @@ -8,13 +8,21 @@ import ( "testing" "time" + "errors" + . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/common" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm" + . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes" + "github.com/stretchr/testify/assert" ) +func init() { + SetDebug(true) +} + func newAppState() *FakeAppState { fas := &FakeAppState{ accounts: make(map[string]*Account), @@ -154,65 +162,164 @@ func TestSendCall(t *testing.T) { //---------------------------------------------- // account2 has insufficient balance, should fail - fmt.Println("Should fail with insufficient balance") - - exception := runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000) - if exception == "" { - t.Fatal("Expected exception") - } + _, err := runVMWaitError(ourVm, account1, account2, addr, contractCode, 1000) + assert.Error(t, err, "Expected insufficient balance error") //---------------------------------------------- // 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) - } + _, err = runVMWaitError(ourVm, account1, account2, addr, contractCode, 1000) + assert.NoError(t, err, "Should have sufficient balance") //---------------------------------------------- // insufficient gas, should fail - fmt.Println("Should fail with insufficient gas") account2.Balance = 100000 - exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 100) - if exception == "" { - t.Fatal("Expected exception") + _, err = runVMWaitError(ourVm, account1, account2, addr, contractCode, 100) + assert.Error(t, err, "Expected insufficient gas error") +} + +// This test was introduced to cover an issues exposed in our handling of the +// gas limit passed from caller to callee on various forms of CALL +// this ticket gives some background: https://github.com/eris-ltd/eris-pm/issues/212 +// The idea of this test is to implement a simple DelegateCall in EVM code +// We first run the DELEGATECALL with _just_ enough gas expecting a simple return, +// and then run it with 1 gas unit less, expecting a failure +func TestDelegateCallGas(t *testing.T) { + appState := newAppState() + ourVm := NewVM(appState, newParams(), Zero256, nil) + + inOff := 0 + inSize := 0 // no call data + retOff := 0 + retSize := 32 + calleeReturnValue := int64(20) + + // DELEGATECALL(retSize, refOffset, inSize, inOffset, addr, gasLimit) + // 6 pops + delegateCallCost := GasStackOp * 6 + // 1 push + gasCost := GasStackOp + // 2 pops, 1 push + subCost := GasStackOp * 3 + pushCost := GasStackOp + + costBetweenGasAndDelegateCall := gasCost + subCost + delegateCallCost + pushCost + + // Do a simple operation using 1 gas unit + calleeAccount, calleeAddress := makeAccountWithCode(appState, "callee", + Bytecode(PUSH1, calleeReturnValue, return1())) + + // Here we split up the caller code so we can make a DELEGATE call with + // different amounts of gas. The value we sandwich in the middle is the amount + // we subtract from the available gas (that the caller has available), so: + // code := Bytecode(callerCodePrefix, <amount to subtract from GAS> , callerCodeSuffix) + // gives us the code to make the call + callerCodePrefix := Bytecode(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, + PUSH1, inOff, PUSH20, calleeAddress, PUSH1) + callerCodeSuffix := Bytecode(GAS, SUB, DELEGATECALL, returnWord()) + + // Perform a delegate call + callerAccount, _ := makeAccountWithCode(appState, "caller", + Bytecode(callerCodePrefix, + // Give just enough gas to make the DELEGATECALL + costBetweenGasAndDelegateCall, + callerCodeSuffix)) + + // Should pass + output, err := runVMWaitError(ourVm, callerAccount, calleeAccount, calleeAddress, + callerAccount.Code, 100) + assert.NoError(t, err, "Should have sufficient funds for call") + assert.Equal(t, Int64ToWord256(calleeReturnValue).Bytes(), output) + + callerAccount.Code = Bytecode(callerCodePrefix, + // Shouldn't be enough gas to make call + costBetweenGasAndDelegateCall-1, + callerCodeSuffix) + + // Should fail + _, err = runVMWaitError(ourVm, callerAccount, calleeAccount, calleeAddress, + callerAccount.Code, 100) + assert.Error(t, err, "Should have insufficient funds for call") +} + +// Store the top element of the stack (which is a 32-byte word) in memory +// and return it. Useful for a simple return value. +func return1() []byte { + return Bytecode(PUSH1, 0, MSTORE, returnWord()) +} + +func returnWord() []byte { + // PUSH1 => return size, PUSH1 => return offset, RETURN + return Bytecode(PUSH1, 32, PUSH1, 0, RETURN) +} + +func makeAccountWithCode(appState AppState, name string, + code []byte) (*Account, []byte) { + account := &Account{ + Address: LeftPadWord256([]byte(name)), + Balance: 9999999, + Code: code, + Nonce: 0, + } + account.Code = code + appState.UpdateAccount(account) + // Sanity check + address := new([20]byte) + for i, b := range account.Address.Postfix(20) { + address[i] = b + } + return account, address[:] +} + +// Subscribes to an AccCall, runs the vm, returns the output any direct exception +// and then waits for any exceptions transmitted by EventData in the AccCall +// event (in the case of no direct error from call we will block waiting for +// at least 1 AccCall event) +func runVMWaitError(ourVm *VM, caller, callee *Account, subscribeAddr, + contractCode []byte, gas int64) (output []byte, err error) { + eventCh := make(chan types.EventData) + output, err = runVM(eventCh, ourVm, caller, callee, subscribeAddr, + contractCode, gas) + if err != nil { + return + } + msg := <-eventCh + var errString string + switch ev := msg.(type) { + case types.EventDataTx: + errString = ev.Exception + case types.EventDataCall: + errString = ev.Exception + } + + if errString != "" { + err = errors.New(errString) } + return } -// subscribes to an AccCall, runs the vm, returns the exception -func runVMWaitEvents(t *testing.T, ourVm *VM, caller, callee *Account, subscribeAddr, contractCode []byte, gas int64) string { +// Subscribes to an AccCall, runs the vm, returns the output and any direct +// exception +func runVM(eventCh chan types.EventData, ourVm *VM, caller, callee *Account, + subscribeAddr, contractCode []byte, gas int64) ([]byte, error) { + // we need to catch the event from the CALL to check for exceptions evsw := events.NewEventSwitch() evsw.Start() - ch := make(chan interface{}) fmt.Printf("subscribe to %x\n", subscribeAddr) - evsw.AddListenerForEvent("test", types.EventStringAccCall(subscribeAddr), func(msg types.EventData) { - ch <- msg - }) + evsw.AddListenerForEvent("test", types.EventStringAccCall(subscribeAddr), + func(msg types.EventData) { + eventCh <- 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.EventDataTx: - return ev.Exception - case types.EventDataCall: - return ev.Exception - case string: - return ev - } - return "" + 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)) + go func() { evc.Flush() }() + return output, err } // this is code to call another contract (hardcoded as addr) @@ -222,17 +329,42 @@ func callContractCode(addr []byte) []byte { 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) + return Bytecode(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, + inOff, PUSH1, value, PUSH20, addr, PUSH2, gas1, gas2, CALL, PUSH1, retSize, + PUSH1, retOff, RETURN) +} + +func TestBytecode(t *testing.T) { + assert.Equal(t, + Bytecode(1, 2, 3, 4, 5, 6), + Bytecode(1, 2, 3, Bytecode(4, 5, 6))) + assert.Equal(t, + Bytecode(1, 2, 3, 4, 5, 6, 7, 8), + Bytecode(1, 2, 3, Bytecode(4, Bytecode(5), 6), 7, 8)) + assert.Equal(t, + Bytecode(PUSH1, 2), + Bytecode(byte(PUSH1), 0x02)) + assert.Equal(t, + []byte{}, + Bytecode(Bytecode(Bytecode()))) + + contractAccount := &Account{Address: Int64ToWord256(102)} + addr := contractAccount.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) + contractCodeBytecode := Bytecode(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, + inOff, PUSH1, value, PUSH20, addr, PUSH2, gas1, gas2, CALL, PUSH1, retSize, + PUSH1, retOff, 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 + assert.Equal(t, contractCode, contractCodeBytecode) } -/* - // infinite loop - code := []byte{0x5B, 0x60, 0x00, 0x56} - // mstore - code := []byte{0x60, 0x00, 0x60, 0x20} - // mstore, mload - code := []byte{0x60, 0x01, 0x60, 0x20, 0x52, 0x60, 0x20, 0x51} -*/ +func TestConcat(t *testing.T) { + assert.Equal(t, + []byte{0x01, 0x02, 0x03, 0x04}, + Concat([]byte{0x01, 0x02}, []byte{0x03, 0x04})) +} diff --git a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go index f79a3d28734bc69ceaa0a8139d52fb2ad1b5d633..0cec78a249c48539b88566c722bd7a27cf0b3056 100644 --- a/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go +++ b/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/vm.go @@ -10,6 +10,7 @@ import ( "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/events" ptypes "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/permission/types" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types" + . "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/opcodes" "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm/sha3" ) @@ -17,7 +18,7 @@ var ( ErrUnknownAddress = errors.New("Unknown address") ErrInsufficientBalance = errors.New("Insufficient balance") ErrInvalidJumpDest = errors.New("Invalid jump dest") - ErrInsufficientGas = errors.New("Insuffient gas") + ErrInsufficientGas = errors.New("Insufficient gas") ErrMemoryOutOfBounds = errors.New("Memory out of bounds") ErrCodeOutOfBounds = errors.New("Code out of bounds") ErrInputOutOfBounds = errors.New("Input out of bounds") @@ -194,7 +195,6 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas ) for { - // Use BaseOp gas. if useGasNegative(gas, GasBaseOp, &err) { return nil, err @@ -816,7 +816,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas exception = err.Error() } // NOTE: these fire call events and not particular events for eg name reg or permissions - vm.fireCallEvent(&exception, &ret, callee, &Account{Address: addr}, args, value, gas) + vm.fireCallEvent(&exception, &ret, callee, &Account{Address: addr}, args, value, &gasLimit) } else { // EVM contract if useGasNegative(gas, GasGetAccount, &err) { @@ -831,12 +831,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if acc == nil { return nil, firstErr(err, ErrUnknownAddress) } - ret, err = vm.Call(callee, callee, acc.Code, args, value, gas) + ret, err = vm.Call(callee, callee, acc.Code, args, value, &gasLimit) } else if op == DELEGATECALL { if acc == nil { return nil, firstErr(err, ErrUnknownAddress) } - ret, err = vm.DelegateCall(caller, callee, acc.Code, args, value, gas) + ret, err = vm.DelegateCall(caller, callee, acc.Code, args, value, &gasLimit) } else { // nil account means we're sending funds to a new account if acc == nil { @@ -847,7 +847,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } // add account to the tx cache 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, &gasLimit) } } @@ -906,7 +906,6 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } pc++ - } }