package txbuilder import ( "context" "encoding/hex" "encoding/json" "math" "testing" "time" "github.com/davecgh/go-spew/spew" "github.com/vapor/common" "github.com/vapor/consensus" "github.com/vapor/crypto" "github.com/vapor/crypto/ed25519/chainkd" chainjson "github.com/vapor/encoding/json" "github.com/vapor/errors" "github.com/vapor/protocol/bc" "github.com/vapor/protocol/bc/types" "github.com/vapor/protocol/vm/vmutil" "github.com/vapor/testutil" ) type testAction bc.AssetAmount func (t testAction) Build(ctx context.Context, b *TemplateBuilder) error { in := types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *t.AssetId, t.Amount, 0, nil) tplIn := &SigningInstruction{} err := b.AddInput(in, tplIn) if err != nil { return err } return b.AddOutput(types.NewIntraChainOutput(*t.AssetId, t.Amount, []byte("change"))) } func (t testAction) ActionType() string { return "test-action" } func newControlProgramAction(assetAmt bc.AssetAmount, script []byte) *controlProgramAction { return &controlProgramAction{ AssetAmount: assetAmt, Program: script, } } func TestBuildIntra(t *testing.T) { ctx := context.Background() assetID1 := bc.NewAssetID([32]byte{1}) assetID2 := bc.NewAssetID([32]byte{2}) actions := []Action{ newControlProgramAction(bc.AssetAmount{AssetId: &assetID2, Amount: 6}, []byte("dest")), testAction(bc.AssetAmount{AssetId: &assetID1, Amount: 5}), } expiryTime := time.Now().Add(time.Minute) got, err := Build(ctx, nil, actions, expiryTime, 0) if err != nil { t.Fatal(err) } want := &Template{ Transaction: types.NewTx(types.TxData{ Version: 1, Inputs: []*types.TxInput{ types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), assetID1, 5, 0, nil), }, Outputs: []*types.TxOutput{ types.NewIntraChainOutput(assetID2, 6, []byte("dest")), types.NewIntraChainOutput(assetID1, 5, []byte("change")), }, }), SigningInstructions: []*SigningInstruction{{ WitnessComponents: []witnessComponent{}, }}, } if !testutil.DeepEqual(got.Transaction.TxData, want.Transaction.TxData) { t.Errorf("got tx:\n%s\nwant tx:\n%s", spew.Sdump(got.Transaction.TxData), spew.Sdump(want.Transaction.TxData)) } if !testutil.DeepEqual(got.SigningInstructions, want.SigningInstructions) { t.Errorf("got signing instructions:\n\t%#v\nwant signing instructions:\n\t%#v", got.SigningInstructions, want.SigningInstructions) } } func newCrossOutAction(assetAmt bc.AssetAmount, redeemContract []byte) *crossOutAction { address, err := common.NewAddressWitnessPubKeyHash(redeemContract, &consensus.MainNetParams) if err != nil { panic(err) } return &crossOutAction{ AssetAmount: assetAmt, Address: address.String(), } } func TestBuildCrossOut(t *testing.T) { ctx := context.Background() assetID1 := bc.NewAssetID([32]byte{1}) assetID2 := bc.NewAssetID([32]byte{2}) redeemContract := make([]byte, 20) controlProgram := append([]byte{0x00, byte(len(redeemContract))}, redeemContract...) actions := []Action{ newCrossOutAction(bc.AssetAmount{AssetId: &assetID2, Amount: 6}, redeemContract), testAction(bc.AssetAmount{AssetId: &assetID1, Amount: 5}), } expiryTime := time.Now().Add(time.Minute) got, err := Build(ctx, nil, actions, expiryTime, 0) if err != nil { t.Fatal(err) } want := &Template{ Transaction: types.NewTx(types.TxData{ Version: 1, Inputs: []*types.TxInput{ types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), assetID1, 5, 0, nil), }, Outputs: []*types.TxOutput{ types.NewCrossChainOutput(assetID2, 6, controlProgram), types.NewIntraChainOutput(assetID1, 5, []byte("change")), }, }), SigningInstructions: []*SigningInstruction{{ WitnessComponents: []witnessComponent{}, }}, } if !testutil.DeepEqual(got.Transaction.TxData, want.Transaction.TxData) { t.Errorf("got tx:\n%s\nwant tx:\n%s", spew.Sdump(got.Transaction.TxData), spew.Sdump(want.Transaction.TxData)) } if !testutil.DeepEqual(got.SigningInstructions, want.SigningInstructions) { t.Errorf("got signing instructions:\n\t%#v\nwant signing instructions:\n\t%#v", got.SigningInstructions, want.SigningInstructions) } } func mustDecodeHex(str string) []byte { data, err := hex.DecodeString(str) if err != nil { panic(err) } return data } func TestCheckBlankCheck(t *testing.T) { cases := []struct { tx *types.TxData want error }{{ tx: &types.TxData{ Inputs: []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil)}, }, want: ErrBlankCheck, }, { tx: &types.TxData{ Inputs: []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil)}, Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 3, nil)}, }, want: ErrBlankCheck, }, { tx: &types.TxData{ Inputs: []*types.TxInput{ types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil), types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.NewAssetID([32]byte{1}), 5, 0, nil), }, Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 5, nil)}, }, want: ErrBlankCheck, }, { tx: &types.TxData{ Inputs: []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil)}, Outputs: []*types.TxOutput{ types.NewIntraChainOutput(bc.AssetID{}, math.MaxInt64, nil), types.NewIntraChainOutput(bc.AssetID{}, 7, nil), }, }, want: ErrBadAmount, }, { tx: &types.TxData{ Inputs: []*types.TxInput{ types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil), types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, math.MaxInt64, 0, nil), }, }, want: ErrBadAmount, }, { tx: &types.TxData{ Inputs: []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil)}, Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 5, nil)}, }, want: nil, }, { tx: &types.TxData{ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 5, nil)}, }, want: nil, }, { tx: &types.TxData{ Inputs: []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil)}, Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.NewAssetID([32]byte{1}), 5, nil)}, }, want: nil, }} for _, c := range cases { got := checkBlankCheck(c.tx) if errors.Root(got) != c.want { t.Errorf("checkUnsafe(%+v) err = %v want %v", c.tx, errors.Root(got), c.want) } } } func TestCreateTxByUtxo(t *testing.T) { xprv, xpub, err := chainkd.NewXKeys(nil) if err != nil { t.Fatal(err) } pub := xpub.PublicKey() pubHash := crypto.Ripemd160(pub) program, err := vmutil.P2WPKHProgram([]byte(pubHash)) if err != nil { t.Fatal(err) } address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams) if err != nil { t.Fatal(err) } muxID := testutil.MustDecodeHash("1e673900965623ec3305cead5a78dfb68a34599f8bc078460f3f202256c3dfa6") utxo := struct { SourceID bc.Hash AssetID bc.AssetID Amount uint64 SourcePos uint64 ControlProgram []byte Address string }{ SourceID: muxID, AssetID: *consensus.BTMAssetID, Amount: 20000000000, SourcePos: 1, ControlProgram: program, Address: address.EncodeAddress(), } recvProg := mustDecodeHex("00145056532ecd3621c9ce8adde5505c058610b287cf") tx := types.NewTx(types.TxData{ Version: 1, Inputs: []*types.TxInput{ types.NewSpendInput(nil, utxo.SourceID, utxo.AssetID, utxo.Amount, utxo.SourcePos, utxo.ControlProgram), }, Outputs: []*types.TxOutput{ types.NewIntraChainOutput(*consensus.BTMAssetID, 10000000000, recvProg), }, }) tpl := &Template{ Transaction: tx, AllowAdditional: false, } h := tpl.Hash(0).Byte32() sig := xprv.Sign(h[:]) data := []byte(pub) // Test with more signatures than required, in correct order tpl.SigningInstructions = []*SigningInstruction{{ WitnessComponents: []witnessComponent{ &RawTxSigWitness{ Quorum: 1, Sigs: []chainjson.HexBytes{sig}, }, DataWitness(data), }, }} if err = materializeWitnesses(tpl); err != nil { t.Fatal(err) } if !testutil.DeepEqual(tx, tpl.Transaction) { t.Errorf("tx:%v result is equal to want:%v", tx, tpl.Transaction) } } func TestAddContractArgs(t *testing.T) { hexXpub, err := hex.DecodeString("ba76bb52574b3f40315f2c01f1818a9072ced56e9d4b68acbef56a4d0077d08e5e34837963e4cdc54eb251aa34aad01e6ae48b140f6a2743fbb0a0abd9cf8aac") if err != nil { t.Fatal(err) } var xpub chainkd.XPub copy(xpub[:], hexXpub) rawTxSig := RawTxSigArgument{RootXPub: xpub, Path: []chainjson.HexBytes{{1, 1, 0, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 0}}} rawTxSigMsg, err := json.Marshal(rawTxSig) if err != nil { t.Fatal(err) } value, err := hex.DecodeString("7468697320697320612074657374") if err != nil { t.Fatal(err) } data := DataArgument{value} dataMsg, err := json.Marshal(data) if err != nil { t.Fatal(err) } strMsg, err := json.Marshal(StrArgument{"this is a test string"}) if err != nil { t.Fatal(err) } integerMsg, err := json.Marshal(IntegerArgument{100}) if err != nil { t.Fatal(err) } boolMsg, err := json.Marshal(BoolArgument{true}) if err != nil { t.Fatal(err) } cases := []struct { arguments []ContractArgument wantResult error }{ { arguments: []ContractArgument{ { Type: "raw_tx_signature", RawData: rawTxSigMsg, }, }, wantResult: nil, }, { arguments: []ContractArgument{ { Type: "data", RawData: dataMsg, }, }, wantResult: nil, }, { arguments: []ContractArgument{ { Type: "string", RawData: strMsg, }, }, wantResult: nil, }, { arguments: []ContractArgument{ { Type: "integer", RawData: integerMsg, }, }, wantResult: nil, }, { arguments: []ContractArgument{ { Type: "boolean", RawData: boolMsg, }, }, wantResult: nil, }, { arguments: []ContractArgument{ { Type: "raw_tx_signature", RawData: rawTxSigMsg, }, { Type: "data", RawData: dataMsg, }, }, wantResult: nil, }, { arguments: []ContractArgument{ { Type: "data", RawData: dataMsg, }, { Type: "raw_tx_signature", RawData: rawTxSigMsg, }, }, wantResult: nil, }, { arguments: []ContractArgument{ { Type: "raw_tx_signature", RawData: rawTxSigMsg, }, { Type: "data", RawData: dataMsg, }, { Type: "string", RawData: strMsg, }, { Type: "integer", RawData: integerMsg, }, { Type: "boolean", RawData: boolMsg, }, }, wantResult: nil, }, { arguments: []ContractArgument{ { Type: "data", RawData: dataMsg, }, { Type: "err_data", RawData: rawTxSigMsg, }, }, wantResult: ErrBadContractArgType, }, } sigInst := &SigningInstruction{} for _, c := range cases { err := AddContractArgs(sigInst, c.arguments) if err != c.wantResult { t.Fatalf("got result=%v, want result=%v", err, c.wantResult) } } }