From 9dc8f6f9dc31d9266ddf4daa2386753d3f598ca1 Mon Sep 17 00:00:00 2001
From: Silas Davis <silas@monax.io>
Date: Fri, 6 Apr 2018 23:57:50 +0100
Subject: [PATCH] Add strange test case

Signed-off-by: Silas Davis <silas@monax.io>
---
 execution/accounts.go               |  1 +
 execution/events/events.go          |  5 ++-
 execution/evm/abi/types.go          | 12 ++++++
 execution/evm/events/events.go      |  1 +
 execution/evm/snative.go            | 10 +----
 execution/evm/vm.go                 |  4 +-
 execution/transactor.go             | 60 ++---------------------------
 rpc/v0/client.go                    | 11 +++++-
 rpc/v0/integration/strange_loop.go  |  3 ++
 rpc/v0/integration/strange_loop.sh  |  5 +++
 rpc/v0/integration/strange_loop.sol | 31 +++++++++++++++
 rpc/v0/integration/v0_test.go       | 25 ++++++++++--
 rpc/v0/methods.go                   | 17 ++++----
 13 files changed, 107 insertions(+), 78 deletions(-)
 create mode 100644 rpc/v0/integration/strange_loop.go
 create mode 100755 rpc/v0/integration/strange_loop.sh
 create mode 100644 rpc/v0/integration/strange_loop.sol

diff --git a/execution/accounts.go b/execution/accounts.go
index 7dc11149..ec9953d4 100644
--- a/execution/accounts.go
+++ b/execution/accounts.go
@@ -59,6 +59,7 @@ func (accs *Accounts) SigningAccountFromPrivateKey(privateKeyBytes []byte) (*Sig
 	if account != nil {
 		account = acm.ConcreteAccount{
 			Address: privateAccount.Address(),
+			PublicKey: privateAccount.PublicKey(),
 		}.Account()
 	}
 	return &SigningAccount{
diff --git a/execution/events/events.go b/execution/events/events.go
index efbe1f24..5315a741 100644
--- a/execution/events/events.go
+++ b/execution/events/events.go
@@ -32,6 +32,10 @@ var sendTxQuery = event.NewQueryBuilder().
 	AndEquals(event.MessageTypeKey, reflect.TypeOf(EventDataTx{}).String()).
 	AndEquals(event.TxTypeKey, reflect.TypeOf(&txs.SendTx{}).String())
 
+var callTxQuery = event.NewQueryBuilder().
+	AndEquals(event.MessageTypeKey, reflect.TypeOf(EventDataTx{}).String()).
+	AndEquals(event.TxTypeKey, reflect.TypeOf(&txs.CallTx{}).String())
+
 type eventDataTx struct {
 	Tx        txs.Wrapper
 	Return    []byte
@@ -60,7 +64,6 @@ func (edTx *EventDataTx) UnmarshalJSON(data []byte) error {
 }
 
 // Publish/Subscribe
-
 func SubscribeAccountOutputSendTx(ctx context.Context, subscribable event.Subscribable, subscriber string,
 	address acm.Address, txHash []byte, ch chan<- *txs.SendTx) error {
 
diff --git a/execution/evm/abi/types.go b/execution/evm/abi/types.go
index 09ac0e4c..0c7f300d 100644
--- a/execution/evm/abi/types.go
+++ b/execution/evm/abi/types.go
@@ -14,6 +14,8 @@
 
 package abi
 
+import "github.com/hyperledger/burrow/execution/evm/sha3"
+
 // Ethereum defines types and calling conventions for the ABI
 // (application binary interface) here: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
 // We make a start of representing them here
@@ -51,3 +53,13 @@ type (
 	Address          [AddressLength]byte
 	FunctionSelector [FunctionSelectorLength]byte
 )
+
+func FunctionID(signature string) FunctionSelector {
+	return FirstFourBytes(sha3.Sha3([]byte(signature)))
+}
+
+func FirstFourBytes(byteSlice []byte) [4]byte {
+	var bs [4]byte
+	copy(bs[:], byteSlice[:4])
+	return bs
+}
diff --git a/execution/evm/events/events.go b/execution/evm/events/events.go
index a9308bb5..719e3354 100644
--- a/execution/evm/events/events.go
+++ b/execution/evm/events/events.go
@@ -99,6 +99,7 @@ func SubscribeLogEvent(ctx context.Context, subscribable event.Subscribable, sub
 }
 
 func PublishAccountCall(publisher event.Publisher, address acm.Address, eventDataCall *EventDataCall) error {
+	fmt.Printf("%v: %v\n", eventDataCall.StackDepth, eventDataCall.Return[31])
 	return event.PublishWithEventID(publisher, EventStringAccountCall(address), eventDataCall,
 		map[string]interface{}{
 			"address":           address,
diff --git a/execution/evm/snative.go b/execution/evm/snative.go
index 34375a0a..1bb3e89c 100644
--- a/execution/evm/snative.go
+++ b/execution/evm/snative.go
@@ -248,7 +248,7 @@ func (contract *SNativeContractDescription) Dispatch(state state.Writer, caller
 			"identifier but arguments are only %v bytes long", len(args))
 	}
 
-	function, err := contract.FunctionByID(firstFourBytes(args))
+	function, err := contract.FunctionByID(abi.FirstFourBytes(args))
 	if err != nil {
 		return nil, err
 	}
@@ -325,7 +325,7 @@ func (function *SNativeFunctionDescription) Signature() string {
 
 // Get function calling identifier FunctionSelector
 func (function *SNativeFunctionDescription) ID() abi.FunctionSelector {
-	return firstFourBytes(sha3.Sha3([]byte(function.Signature())))
+	return abi.FunctionID(function.Signature())
 }
 
 // Get number of function arguments
@@ -566,9 +566,3 @@ func byteFromBool(b bool) byte {
 	}
 	return 0x0
 }
-
-func firstFourBytes(byteSlice []byte) [4]byte {
-	var bs [4]byte
-	copy(bs[:], byteSlice[:4])
-	return bs
-}
diff --git a/execution/evm/vm.go b/execution/evm/vm.go
index 1837b47d..ea29e487 100644
--- a/execution/evm/vm.go
+++ b/execution/evm/vm.go
@@ -156,6 +156,8 @@ func HasPermission(stateWriter state.Writer, acc acm.Account, perm ptypes.PermFl
 }
 
 func (vm *VM) fireCallEvent(exception *string, output *[]byte, callerAddress, calleeAddress acm.Address, input []byte, value uint64, gas *uint64) {
+	ret := make([]byte, len(*output))
+	copy(ret, *output)
 	// fire the post call event (including exception if applicable)
 	if vm.publisher != nil {
 		events.PublishAccountCall(vm.publisher, calleeAddress, &events.EventDataCall{
@@ -169,7 +171,7 @@ func (vm *VM) fireCallEvent(exception *string, output *[]byte, callerAddress, ca
 			Origin:    vm.origin,
 			TxHash:    vm.txHash,
 			StackDepth: vm.stackDepth,
-			Return:    *output,
+			Return:    ret,
 			Exception: *exception,
 		})
 	}
diff --git a/execution/transactor.go b/execution/transactor.go
index 662407ec..0acb2fd3 100644
--- a/execution/transactor.go
+++ b/execution/transactor.go
@@ -166,7 +166,7 @@ func (trans *Transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) {
 }
 
 // Orders calls to BroadcastTx using lock (waits for response from core before releasing)
-func (trans *Transactor) Transact2(inputAccount SequencedAddressableSigner, address *acm.Address, data []byte, gasLimit,
+func (trans *Transactor) Transact(inputAccount SequencedAddressableSigner, address *acm.Address, data []byte, gasLimit,
 	fee uint64) (*txs.Receipt, error) {
 	trans.Lock()
 	defer trans.Unlock()
@@ -203,65 +203,11 @@ func (trans *Transactor) Transact2(inputAccount SequencedAddressableSigner, addr
 	return trans.BroadcastTx(tx)
 }
 
-// Orders calls to BroadcastTx using lock (waits for response from core before releasing)
-func (trans *Transactor) Transact(privKey []byte, address *acm.Address, data []byte, gasLimit,
-	fee uint64) (*txs.Receipt, error) {
-
-	if len(privKey) != 64 {
-		return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey))
-	}
-	trans.Lock()
-	defer trans.Unlock()
-	pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey)
-	if err != nil {
-		return nil, err
-	}
-	acc, err := trans.state.GetAccount(pa.Address())
-	if err != nil {
-		return nil, err
-	}
-	sequence := uint64(1)
-	if acc != nil {
-		sequence = acc.Sequence() + uint64(1)
-	}
-
-	// TODO: [Silas] we should consider revising this method and removing fee, or
-	// possibly adding an amount parameter. It is non-sensical to just be able to
-	// set the fee. Our support of fees in general is questionable since at the
-	// moment all we do is deduct the fee effectively leaking token. It is possible
-	// someone may be using the sending of native token to payable functions but
-	// they can be served by broadcasting a token.
-
-	// We hard-code the amount to be equal to the fee which means the CallTx we
-	// generate transfers 0 value, which is the most sensible default since in
-	// recent solidity compilers the EVM generated will throw an error if value
-	// is transferred to a non-payable function.
-	txInput := &txs.TxInput{
-		Address:   pa.Address(),
-		Amount:    fee,
-		Sequence:  sequence,
-		PublicKey: pa.PublicKey(),
-	}
-	tx := &txs.CallTx{
-		Input:    txInput,
-		Address:  address,
-		GasLimit: gasLimit,
-		Fee:      fee,
-		Data:     data,
-	}
-
-	// Got ourselves a tx.
-	txS, errS := trans.SignTx(tx, []acm.AddressableSigner{pa})
-	if errS != nil {
-		return nil, errS
-	}
-	return trans.BroadcastTx(txS)
-}
 
-func (trans *Transactor) TransactAndHold(privKey []byte, address *acm.Address, data []byte, gasLimit,
+func (trans *Transactor) TransactAndHold(inputAccount SequencedAddressableSigner, address *acm.Address, data []byte, gasLimit,
 	fee uint64) (*evm_events.EventDataCall, error) {
 
-	receipt, err := trans.Transact(privKey, address, data, gasLimit, fee)
+	receipt, err := trans.Transact(inputAccount, address, data, gasLimit, fee)
 	if err != nil {
 		return nil, err
 	}
diff --git a/rpc/v0/client.go b/rpc/v0/client.go
index 8f635e17..351fd130 100644
--- a/rpc/v0/client.go
+++ b/rpc/v0/client.go
@@ -44,6 +44,15 @@ func (vc *V0Client) Transact(param TransactParam) (*txs.Receipt, error) {
 	return receipt, nil
 }
 
+func (vc *V0Client) TransactAndHold2(param TransactParam) (*events.EventDataCall, error) {
+	eventDataCall := new(events.EventDataCall)
+	err := vc.Call(TRANSACT_AND_HOLD+"2", param, eventDataCall)
+	if err != nil {
+		return nil, err
+	}
+	return eventDataCall, nil
+}
+
 func (vc *V0Client) TransactAndHold(param TransactParam) (*events.EventDataCall, error) {
 	eventDataCall := new(events.EventDataCall)
 	err := vc.Call(TRANSACT_AND_HOLD, param, eventDataCall)
@@ -82,7 +91,7 @@ func (vc *V0Client) Call(method string, param interface{}, result interface{}) e
 	if rpcResponse.Error != nil {
 		return rpcResponse.Error
 	}
-	err = json.Unmarshal(rpcResponse.Result, result)
+	vc.codec.DecodeBytes(result, rpcResponse.Result)
 	if err != nil {
 		return err
 	}
diff --git a/rpc/v0/integration/strange_loop.go b/rpc/v0/integration/strange_loop.go
new file mode 100644
index 00000000..f8137df9
--- /dev/null
+++ b/rpc/v0/integration/strange_loop.go
@@ -0,0 +1,3 @@
+package integration
+
+const strangeLoopBytecode = "60606040526017600055602260015560116002556001600360006101000a81548160ff021916908315150217905550341561003957600080fd5b6102c9806100486000396000f300606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ebb384dd14610046575b600080fd5b341561005157600080fd5b61005961006f565b6040518082815260200191505060405180910390f35b60006002549050600360009054906101000a900460ff16156101cf57600154600254121561012e5760026000815480929190600101919050555060025490503073ffffffffffffffffffffffffffffffffffffffff1663ebb384dd6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561011157600080fd5b5af1151561011e57600080fd5b50505060405180519050506101ca565b6000600360006101000a81548160ff02191690831515021790555060025490503073ffffffffffffffffffffffffffffffffffffffff1663ebb384dd6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156101b157600080fd5b5af115156101be57600080fd5b50505060405180519050505b610299565b6000546002541315610273576002600081548092919060019003919050555060025490503073ffffffffffffffffffffffffffffffffffffffff1663ebb384dd6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561025657600080fd5b5af1151561026357600080fd5b5050506040518051905050610298565b6001600360006101000a81548160ff021916908315150217905550600254905061029a565b5b5b905600a165627a7a7230582071446a8de59540361bd59bb4f5a84f884006f53e50c1c89d2bfbdb72f92fd4700029"
diff --git a/rpc/v0/integration/strange_loop.sh b/rpc/v0/integration/strange_loop.sh
new file mode 100755
index 00000000..d0cf04ee
--- /dev/null
+++ b/rpc/v0/integration/strange_loop.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+echo -e "package integration\n\nconst strangeLoopBytecode = \"$(solc --bin rpc/v0/integration/strange_loop.sol | tail -1)\"" > "${script_dir}/strange_loop.go"
diff --git a/rpc/v0/integration/strange_loop.sol b/rpc/v0/integration/strange_loop.sol
new file mode 100644
index 00000000..2a3e07f2
--- /dev/null
+++ b/rpc/v0/integration/strange_loop.sol
@@ -0,0 +1,31 @@
+pragma solidity ^0.4.16;
+
+contract StrangeLoop {
+    int top = 23;
+    int bottom = 34;
+    int depth = 17;
+    bool down = true;
+
+    function UpsieDownsie() public returns (int i) {
+        i = depth;
+        if (down) {
+            if (depth < bottom) {
+                depth++;
+                i = depth;
+                this.UpsieDownsie();
+            } else {
+                down = false;
+                i = depth;
+                this.UpsieDownsie();
+            }
+        } else if (depth > top) {
+            depth--;
+            i = depth;
+            this.UpsieDownsie();
+        } else {
+            down = true;
+            i = depth;
+            return;
+        }
+    }
+}
\ No newline at end of file
diff --git a/rpc/v0/integration/v0_test.go b/rpc/v0/integration/v0_test.go
index 91ef3715..ede0a128 100644
--- a/rpc/v0/integration/v0_test.go
+++ b/rpc/v0/integration/v0_test.go
@@ -20,9 +20,13 @@ package integration
 import (
 	"testing"
 
+	"encoding/hex"
+
+	"github.com/hyperledger/burrow/execution/evm/abi"
 	"github.com/hyperledger/burrow/rpc/v0"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"github.com/hyperledger/burrow/binary"
 )
 
 func TestTransact(t *testing.T) {
@@ -44,13 +48,28 @@ func TestTransact(t *testing.T) {
 func TestTransactAndHold(t *testing.T) {
 	cli := v0.NewV0Client("http://localhost:1337/rpc")
 
-	call, err := cli.TransactAndHold(v0.TransactParam{
+	bc, err := hex.DecodeString(strangeLoopBytecode)
+	require.NoError(t, err)
+	create, err := cli.TransactAndHold(v0.TransactParam{
 		PrivKey:  privateAccounts[0].PrivateKey().RawBytes(),
 		Address:  nil,
-		Data:     []byte{},
+		Data:     bc,
 		Fee:      2,
 		GasLimit: 10000,
 	})
 	require.NoError(t, err)
-	assert.Equal(t, 0, call.StackDepth)
+	assert.Equal(t, 0, create.StackDepth)
+	functionID := abi.FunctionID("UpsieDownsie()")
+	call, err := cli.TransactAndHold2(v0.TransactParam{
+		PrivKey:  privateAccounts[0].PrivateKey().RawBytes(),
+		Address:  create.CallData.Callee.Bytes(),
+		Data:     functionID[:],
+		Fee:      2,
+		GasLimit: 10000,
+	})
+	require.NoError(t, err)
+	depth := binary.Uint64FromWord256(binary.LeftPadWord256(call.Return))
+	// Would give 23 if taken from wrong frame
+	assert.Equal(t, 18, int(depth))
 }
+
diff --git a/rpc/v0/methods.go b/rpc/v0/methods.go
index cfa07587..77848f4a 100644
--- a/rpc/v0/methods.go
+++ b/rpc/v0/methods.go
@@ -229,12 +229,11 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m
 			if err != nil {
 				return nil, rpc.INVALID_PARAMS, err
 			}
-			//inputAccount, err := service.Mempool().SigningAccountFromPrivateKey(param.PrivKey)
-			//if err != nil {
-			//	return nil, rpc.INVALID_PARAMS, err
-			//}
-			//receipt, err := service.Transactor().Transact2(inputAccount, address, param.Data, param.GasLimit, param.Fee)
-			receipt, err := service.Transactor().Transact(param.PrivKey, address, param.Data, param.GasLimit, param.Fee)
+			inputAccount, err := service.Mempool().SigningAccountFromPrivateKey(param.PrivKey)
+			if err != nil {
+				return nil, rpc.INVALID_PARAMS, err
+			}
+			receipt, err := service.Transactor().Transact(inputAccount, address, param.Data, param.GasLimit, param.Fee)
 			if err != nil {
 				return nil, rpc.INTERNAL_ERROR, err
 			}
@@ -250,7 +249,11 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m
 			if err != nil {
 				return nil, rpc.INVALID_PARAMS, err
 			}
-			ce, err := service.Transactor().TransactAndHold(param.PrivKey, address, param.Data, param.GasLimit, param.Fee)
+			inputAccount, err := service.Mempool().SigningAccountFromPrivateKey(param.PrivKey)
+			if err != nil {
+				return nil, rpc.INVALID_PARAMS, err
+			}
+			ce, err := service.Transactor().TransactAndHold(inputAccount, address, param.Data, param.GasLimit, param.Fee)
 			if err != nil {
 				return nil, rpc.INTERNAL_ERROR, err
 			}
-- 
GitLab