Repository: xuperchain/xuperunion (go)

Issue:
Manual change of the contract response can still be executed successfully.
**Brief of the issue**
Describe here

**Your environment**
XuperUnion and OS/Go/gcc version etc.

**Repo steps**
step1
I can get the preexe_result through preexe function.
step2
And then,I change the response of the contract that there is the amount of utxoOutput.
step3
I make a new tx and  post into the net.
While it can be invoked successfully.

**Expected behaviours**
Describe what you expected to happen.
I think that it is not the contract result, it can not be successful.
**What actually happened**
Describe what actually happened. Include the full traceback if there was an exception.

**Additional information**
If possible, include a minimal, complete, and verifiable example to help us identify the issue. This also helps check that the issue is not with your own code.


Pull request:
fix contract transfer response can be changed
## Description

What is the purpose of the change?
Now, manual change of the contract transfer response can still be executed successfully.
It's need to be fixed. 

Fixes #591 

## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)

## Brief of your solution
The contract transfer utxo result  must in `tx.Inputs`  and  `tx.Outpus`.


Patch:
diff --git a/xmodel/env.go b/xmodel/env.go
index fce1071..0b92ee6 100644
--- a/xmodel/env.go
+++ b/xmodel/env.go
@@ -1,15 +1,14 @@
 package xmodel
 
 import (
+	"errors"
 	"fmt"
-
 	"github.com/xuperchain/xuperunion/pb"
 	xmodel_pb "github.com/xuperchain/xuperunion/xmodel/pb"
 )
 
 // Env data structure for read/write sets environment
 type Env struct {
-	inputs     []*xmodel_pb.VersionedData
 	outputs    []*xmodel_pb.PureData
 	modelCache *XMCache
 }
@@ -35,12 +34,15 @@ func (s *XModel) PrepareEnv(tx *pb.Transaction) (*Env, error) {
 	for _, txOut := range tx.TxOutputsExt {
 		outputs = append(outputs, &xmodel_pb.PureData{Bucket: txOut.Bucket, Key: txOut.Key, Value: txOut.Value})
 	}
-	utxoInputs, err := ParseContractUtxoInputs(tx)
+	utxoInputs, utxoOutputs, err := ParseContractUtxo(tx)
 	if err != nil {
 		return nil, err
 	}
+	if ok := IsContractUtxoEffective(utxoInputs, utxoOutputs, tx); !ok {
+		s.logger.Warn("PrepareEnv CheckConUtxoEffective error")
+		return nil, errors.New("PrepareEnv CheckConUtxoEffective error")
+	}
 	env.modelCache = NewXModelCacheWithInputs(inputs, utxoInputs)
-	env.inputs = inputs
 	env.outputs = outputs
 	s.logger.Trace("PrepareEnv done!", "env", env)
 	return env, nil
diff --git a/xmodel/utxo_cache.go b/xmodel/utxo_cache.go
index 44ef9ac..1bc3e8e 100644
--- a/xmodel/utxo_cache.go
+++ b/xmodel/utxo_cache.go
@@ -82,7 +82,6 @@ func (u *UtxoCache) Transfer(from, to string, amount *big.Int) error {
 			ToAddr: []byte(from),
 		})
 	}
-
 	return nil
 }
 
diff --git a/xmodel/xmodel_cache.go b/xmodel/xmodel_cache.go
index e654e36..5abef46 100644
--- a/xmodel/xmodel_cache.go
+++ b/xmodel/xmodel_cache.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"errors"
 	"math/big"
+	"strconv"
 
 	"github.com/syndtr/goleveldb/leveldb/comparer"
 	"github.com/syndtr/goleveldb/leveldb/util"
@@ -363,3 +364,55 @@ func ParseContractUtxo(tx *pb.Transaction) ([]*pb.TxInput, []*pb.TxOutput, error
 	}
 	return utxoInputs, utxoOutputs, nil
 }
+
+func makeInputsMap(txInputs []*pb.TxInput) map[string]bool {
+	res := map[string]bool{}
+	if len(txInputs) == 0 {
+		return nil
+	}
+	for _, v := range txInputs {
+		utxoKey := string(v.GetRefTxid()) + strconv.FormatInt(int64(v.GetRefOffset()), 10)
+		res[utxoKey] = true
+	}
+	return res
+}
+
+func isSubOutputs(contractOutputs, txOutputs []*pb.TxOutput) bool {
+	markedOutput := map[string]int{}
+	for _, v := range txOutputs {
+		key := string(v.GetAmount()) + string(v.GetToAddr())
+		markedOutput[key]++
+	}
+
+	for _, v := range contractOutputs {
+		key := string(v.GetAmount()) + string(v.GetToAddr())
+		if val, ok := markedOutput[key]; !ok {
+			return false
+		} else if val < 1 {
+			return false
+		} else {
+			markedOutput[key] = val - 1
+		}
+	}
+	return true
+}
+
+// IsContractUtxoEffective check if contract utxo in tx utxo
+func IsContractUtxoEffective(contractTxInputs []*pb.TxInput, contractTxOutputs []*pb.TxOutput, tx *pb.Transaction) bool {
+	if len(contractTxInputs) > len(tx.GetTxInputs()) || len(contractTxOutputs) > len(tx.GetTxOutputs()) {
+		return false
+	}
+
+	contractTxInputsMap := makeInputsMap(contractTxInputs)
+	txInputsMap := makeInputsMap(tx.GetTxInputs())
+	for k := range contractTxInputsMap {
+		if !(txInputsMap[k]) {
+			return false
+		}
+	}
+
+	if !isSubOutputs(contractTxOutputs, tx.GetTxOutputs()) {
+		return false
+	}
+	return true
+}


Test patch:
diff --git a/xmodel/xmodel_cache_test.go b/xmodel/xmodel_cache_test.go
index c04b52c..d7c92c4 100755
--- a/xmodel/xmodel_cache_test.go
+++ b/xmodel/xmodel_cache_test.go
@@ -233,3 +233,239 @@ func TestXModelCacheGet(t *testing.T) {
 		t.Error("Iterator error", keys2)
 	}
 }
+
+func TestIsContractUtxoEffective(t *testing.T) {
+	testCases := map[string]struct {
+		conTxInputs  []*pb.TxInput
+		conTxOutputs []*pb.TxOutput
+		tx           *pb.Transaction
+		res          bool
+	}{
+		"test check ok": {
+			conTxInputs: []*pb.TxInput{
+				&pb.TxInput{
+					RefTxid:   []byte("txInput1_RefTxid"),
+					RefOffset: 1,
+					FromAddr:  []byte("txInput1_FromAddr"),
+				},
+				&pb.TxInput{
+					RefTxid:   []byte("txInput2_RefTxid"),
+					RefOffset: 2,
+					FromAddr:  []byte("txInput2_FromAddr"),
+				},
+				&pb.TxInput{
+					RefTxid:   []byte("txInput3_RefTxid"),
+					RefOffset: 3,
+					FromAddr:  []byte("txInput3_FromAddr"),
+				},
+			},
+			conTxOutputs: []*pb.TxOutput{
+				&pb.TxOutput{
+					Amount: []byte("txOutput1_Amount"),
+					ToAddr: []byte("txOutput1_ToAddr"),
+				},
+				&pb.TxOutput{
+					Amount: []byte("txOutput2_Amount"),
+					ToAddr: []byte("txOutput2_ToAddr"),
+				},
+				&pb.TxOutput{
+					Amount: []byte("txOutput2_Amount"),
+					ToAddr: []byte("txOutput2_ToAddr"),
+				},
+			},
+			tx: &pb.Transaction{
+				TxInputs: []*pb.TxInput{
+					&pb.TxInput{
+						RefTxid:   []byte("txInput1_RefTxid"),
+						RefOffset: 1,
+						FromAddr:  []byte("txInput1_FromAddr"),
+					},
+					&pb.TxInput{
+						RefTxid:   []byte("txInput2_RefTxid"),
+						RefOffset: 2,
+						FromAddr:  []byte("txInput2_FromAddr"),
+					},
+					&pb.TxInput{
+						RefTxid:   []byte("txInput3_RefTxid"),
+						RefOffset: 3,
+						FromAddr:  []byte("txInput3_FromAddr"),
+					},
+					&pb.TxInput{
+						RefTxid:   []byte("txInput4_RefTxid"),
+						RefOffset: 4,
+						FromAddr:  []byte("txInput4_FromAddr"),
+					},
+				},
+				TxOutputs: []*pb.TxOutput{
+					&pb.TxOutput{
+						Amount: []byte("txOutput1_Amount"),
+						ToAddr: []byte("txOutput1_ToAddr"),
+					},
+					&pb.TxOutput{
+						Amount: []byte("txOutput2_Amount"),
+						ToAddr: []byte("txOutput2_ToAddr"),
+					},
+					&pb.TxOutput{
+						Amount: []byte("txOutput2_Amount"),
+						ToAddr: []byte("txOutput2_ToAddr"),
+					},
+					&pb.TxOutput{
+						Amount: []byte("txOutput3_Amount"),
+						ToAddr: []byte("txOutput3_ToAddr"),
+					},
+				},
+			},
+			res: true,
+		},
+		"test check failed1": {
+			conTxInputs: []*pb.TxInput{
+				&pb.TxInput{
+					RefTxid:   []byte("txInput1_RefTxid"),
+					RefOffset: 1,
+					FromAddr:  []byte("txInput1_FromAddr"),
+				},
+				&pb.TxInput{
+					RefTxid:   []byte("txInput2_RefTxid"),
+					RefOffset: 2,
+					FromAddr:  []byte("txInput2_FromAddr"),
+				},
+				&pb.TxInput{
+					RefTxid:   []byte("txInput3_RefTxid"),
+					RefOffset: 3,
+					FromAddr:  []byte("txInput3_FromAddr"),
+				},
+			},
+			conTxOutputs: []*pb.TxOutput{
+				&pb.TxOutput{
+					Amount: []byte("txOutput1_Amount"),
+					ToAddr: []byte("txOutput1_ToAddr"),
+				},
+				&pb.TxOutput{
+					Amount: []byte("txOutput2_Amount"),
+					ToAddr: []byte("txOutput2_ToAddr"),
+				},
+				&pb.TxOutput{
+					Amount: []byte("txOutput2_Amount"),
+					ToAddr: []byte("txOutput2_ToAddr"),
+				},
+			},
+			tx: &pb.Transaction{
+				TxInputs: []*pb.TxInput{
+					&pb.TxInput{
+						RefTxid:   []byte("txInput1_RefTxid"),
+						RefOffset: 1,
+						FromAddr:  []byte("txInput1_FromAddr"),
+					},
+					&pb.TxInput{
+						RefTxid:   []byte("txInput2_RefTxid"),
+						RefOffset: 2,
+						FromAddr:  []byte("txInput2_FromAddr"),
+					},
+					&pb.TxInput{
+						RefTxid:   []byte("txInput3_RefTxid"),
+						RefOffset: 3,
+						FromAddr:  []byte("txInput3_FromAddr"),
+					},
+					&pb.TxInput{
+						RefTxid:   []byte("txInput4_RefTxid"),
+						RefOffset: 4,
+						FromAddr:  []byte("txInput4_FromAddr"),
+					},
+				},
+				TxOutputs: []*pb.TxOutput{
+					&pb.TxOutput{
+						Amount: []byte("txOutput1_Amount"),
+						ToAddr: []byte("txOutput1_ToAddr"),
+					},
+					&pb.TxOutput{
+						Amount: []byte("txOutput2_Amount"),
+						ToAddr: []byte("txOutput2_ToAddr"),
+					},
+					&pb.TxOutput{
+						Amount: []byte("txOutput3_Amount"),
+						ToAddr: []byte("txOutput3_ToAddr"),
+					},
+				},
+			},
+			res: false,
+		},
+		"test check failed2": {
+			conTxInputs: []*pb.TxInput{
+				&pb.TxInput{
+					RefTxid:   []byte("txInput1_RefTxid"),
+					RefOffset: 1,
+					FromAddr:  []byte("txInput1_FromAddr"),
+				},
+				&pb.TxInput{
+					RefTxid:   []byte("txInput2_RefTxid"),
+					RefOffset: 2,
+					FromAddr:  []byte("txInput2_FromAddr"),
+				},
+				&pb.TxInput{
+					RefTxid:   []byte("txInput3_RefTxid"),
+					RefOffset: 3,
+					FromAddr:  []byte("txInput3_FromAddr"),
+				},
+			},
+			conTxOutputs: []*pb.TxOutput{
+				&pb.TxOutput{
+					Amount: []byte("txOutput1_Amount"),
+					ToAddr: []byte("txOutput1_ToAddr"),
+				},
+				&pb.TxOutput{
+					Amount: []byte("txOutput2_Amount"),
+					ToAddr: []byte("txOutput2_ToAddr"),
+				},
+				&pb.TxOutput{
+					Amount: []byte("txOutput4_Amount"),
+					ToAddr: []byte("txOutput4_ToAddr"),
+				},
+			},
+			tx: &pb.Transaction{
+				TxInputs: []*pb.TxInput{
+					&pb.TxInput{
+						RefTxid:   []byte("txInput1_RefTxid"),
+						RefOffset: 1,
+						FromAddr:  []byte("txInput1_FromAddr"),
+					},
+					&pb.TxInput{
+						RefTxid:   []byte("txInput2_RefTxid"),
+						RefOffset: 2,
+						FromAddr:  []byte("txInput2_FromAddr"),
+					},
+					&pb.TxInput{
+						RefTxid:   []byte("txInput3_RefTxid"),
+						RefOffset: 3,
+						FromAddr:  []byte("txInput3_FromAddr"),
+					},
+					&pb.TxInput{
+						RefTxid:   []byte("txInput4_RefTxid"),
+						RefOffset: 4,
+						FromAddr:  []byte("txInput4_FromAddr"),
+					},
+				},
+				TxOutputs: []*pb.TxOutput{
+					&pb.TxOutput{
+						Amount: []byte("txOutput1_Amount"),
+						ToAddr: []byte("txOutput1_ToAddr"),
+					},
+					&pb.TxOutput{
+						Amount: []byte("txOutput2_Amount"),
+						ToAddr: []byte("txOutput2_ToAddr"),
+					},
+					&pb.TxOutput{
+						Amount: []byte("txOutput3_Amount"),
+						ToAddr: []byte("txOutput3_ToAddr"),
+					},
+				},
+			},
+			res: false,
+		},
+	}
+	for k, v := range testCases {
+		res := IsContractUtxoEffective(v.conTxInputs, v.conTxOutputs, v.tx)
+		if res != v.res {
+			t.Error("TestIsConUtxoEffective failed case=", k, "expect=", v.res, "actual=", res)
+		}
+	}
+}