From 1a1a19da7dc924c35873ce17caa3963b13ee136b Mon Sep 17 00:00:00 2001 From: Anthony De Meulemeester Date: Sun, 4 Mar 2018 14:56:49 +0100 Subject: [PATCH] Smartcontract (#39) * deleted transfer_output added asset type and transaction result to core * removed writing 0x00 when buffer length is 0 * Refactored emit into VM package + moved tx to own package. * implemented transaction along with claimTransaction. * refactored naming of transaction + added decode address for uint160 types * removed unnecessary folder and files. * transaction/smartcontract logic * bumped version 0.24.0 --- Gopkg.lock | 26 +++- Gopkg.toml | 4 + VERSION | 2 +- pkg/core/asset_type.go | 18 +++ pkg/core/block.go | 19 +-- pkg/core/block_test.go | 45 ++---- pkg/core/blockchain_test.go | 7 +- pkg/core/header_test.go | 4 +- pkg/core/transaction.go | 35 ----- pkg/core/transaction/attr_usage.go | 49 +++++++ pkg/core/transaction/attribute.go | 79 ++++++++++ pkg/core/transaction/claim.go | 38 +++++ pkg/core/transaction/input.go | 36 +++++ pkg/core/transaction/invocation.go | 54 +++++++ pkg/core/transaction/miner.go | 22 +++ pkg/core/transaction/output.go | 51 +++++++ pkg/core/transaction/transaction.go | 178 +++++++++++++++++++++++ pkg/core/transaction/transaction_test.go | 81 +++++++++++ pkg/core/transaction/type.go | 50 +++++++ pkg/core/{ => transaction}/witness.go | 15 +- pkg/core/transaction_type.go | 28 ---- pkg/network/payload/headers_test.go | 7 +- pkg/network/payload/payload.go | 4 +- pkg/smartcontract/.keep | 0 pkg/smartcontract/contract.go | 3 +- pkg/smartcontract/param_context.go | 52 +++++++ pkg/smartcontract/types/block.go | 9 -- pkg/util/fixed8.go | 31 ++++ pkg/util/uint160.go | 19 +++ pkg/util/uint160_test.go | 21 +++ pkg/util/uint256_test.go | 8 +- pkg/vm/compiler/emit.go | 5 - pkg/vm/opcode.go | 2 +- pkg/vm/script_builder.go | 149 +++++++++++++++++++ pkg/vm/script_builder_test.go | 64 ++++++++ pkg/wallet/transfer_output.go | 21 --- 36 files changed, 1066 insertions(+), 170 deletions(-) create mode 100644 pkg/core/asset_type.go delete mode 100644 pkg/core/transaction.go create mode 100644 pkg/core/transaction/attr_usage.go create mode 100644 pkg/core/transaction/attribute.go create mode 100644 pkg/core/transaction/claim.go create mode 100644 pkg/core/transaction/input.go create mode 100644 pkg/core/transaction/invocation.go create mode 100644 pkg/core/transaction/miner.go create mode 100644 pkg/core/transaction/output.go create mode 100644 pkg/core/transaction/transaction.go create mode 100644 pkg/core/transaction/transaction_test.go create mode 100644 pkg/core/transaction/type.go rename pkg/core/{ => transaction}/witness.go (76%) delete mode 100644 pkg/core/transaction_type.go delete mode 100644 pkg/smartcontract/.keep create mode 100644 pkg/smartcontract/param_context.go delete mode 100644 pkg/smartcontract/types/block.go create mode 100644 pkg/util/fixed8.go create mode 100644 pkg/vm/script_builder.go create mode 100644 pkg/vm/script_builder_test.go delete mode 100644 pkg/wallet/transfer_output.go diff --git a/Gopkg.lock b/Gopkg.lock index 4da50e70d..748c10078 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,18 +1,42 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + branch = "master" + name = "github.com/anthdm/neo-go" + packages = ["pkg/util"] + revision = "da01cdae5c15dcf7d6a046b27d2710c95e2cc66a" + [[projects]] branch = "master" name = "github.com/anthdm/rfc6979" packages = ["."] revision = "6a90f24967ebb1aa57b22f74a13dbb3faad8cf3d" +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + [[projects]] branch = "master" name = "github.com/golang/snappy" packages = ["."] revision = "553a641470496b2327abcac10b36396bd98e45c9" +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + name = "github.com/stretchr/testify" + packages = ["assert"] + revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" + version = "v1.2.1" + [[projects]] branch = "master" name = "github.com/syndtr/goleveldb" @@ -74,6 +98,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "74c6b0a11057b8c0fd68342f85e010e323cda6407bec2a889f161b5890928aaf" + inputs-digest = "069a738aa1487766b26f9efb8103d2ce0526d43c83049cb5b792f0edf91568de" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index bd0ec9ce2..e02ed3b24 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -47,3 +47,7 @@ [[constraint]] name = "golang.org/x/text" version = "0.3.0" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.2.1" diff --git a/VERSION b/VERSION index ca222b7cf..2094a100c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.23.0 +0.24.0 diff --git a/pkg/core/asset_type.go b/pkg/core/asset_type.go new file mode 100644 index 000000000..052930267 --- /dev/null +++ b/pkg/core/asset_type.go @@ -0,0 +1,18 @@ +package core + +// AssetType represent a NEO asset type +type AssetType uint8 + +// Valid asset types. +const ( + CreditFlag AssetType = 0x40 + DutyFlag AssetType = 0x80 + + GoverningToken AssetType = 0x00 + UtilityToken AssetType = 0x01 + Currency AssetType = 0x08 + + Share = DutyFlag | 0x10 + Invoice = DutyFlag | 0x18 + Token = CreditFlag | 0x20 +) diff --git a/pkg/core/block.go b/pkg/core/block.go index ed1739cd0..fcdcecd59 100644 --- a/pkg/core/block.go +++ b/pkg/core/block.go @@ -8,6 +8,7 @@ import ( "io" "log" + "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/util" ) @@ -26,12 +27,12 @@ type BlockBase struct { Index uint32 // Random number also called nonce ConsensusData uint64 - // contract addresss of the next miner + // Contract addresss of the next miner NextConsensus util.Uint160 // fixed to 1 _ uint8 // padding // Script used to validate the block - Script *Witness + Script *transaction.Witness } // DecodeBinary implements the payload interface. @@ -66,11 +67,11 @@ func (b *BlockBase) DecodeBinary(r io.Reader) error { return fmt.Errorf("format error: padding must equal 1 got %d", padding) } - b.Script = &Witness{} + b.Script = &transaction.Witness{} return b.Script.DecodeBinary(r) } -// Hash return the hash of the block. +// Hash returns the hash of the block. // When calculating the hash value of the block, instead of calculating the entire block, // only first seven fields in the block head will be calculated, which are // version, PrevBlock, MerkleRoot, timestamp, and height, the nonce, NextMiner. @@ -158,7 +159,7 @@ func (h *Header) EncodeBinary(w io.Writer) error { type Block struct { BlockBase // transaction list - Transactions []*Transaction + Transactions []*transaction.Transaction } // Header returns a pointer to the head of the block (BlockHead). @@ -171,13 +172,13 @@ func (b *Block) Header() *Header { // Verify the integrity of the block. func (b *Block) Verify(full bool) bool { // The first TX has to be a miner transaction. - if b.Transactions[0].Type != MinerTX { + if b.Transactions[0].Type != transaction.MinerType { return false } // If the first TX is a minerTX then all others cant. for _, tx := range b.Transactions[1:] { - if tx.Type == MinerTX { + if tx.Type == transaction.MinerType { return false } } @@ -202,9 +203,9 @@ func (b *Block) DecodeBinary(r io.Reader) error { } lentx := util.ReadVarUint(r) - b.Transactions = make([]*Transaction, lentx) + b.Transactions = make([]*transaction.Transaction, lentx) for i := 0; i < int(lentx); i++ { - tx := &Transaction{} + tx := &transaction.Transaction{} if err := tx.DecodeBinary(r); err != nil { return err } diff --git a/pkg/core/block_test.go b/pkg/core/block_test.go index 68850ee19..0fb6d1098 100644 --- a/pkg/core/block_test.go +++ b/pkg/core/block_test.go @@ -6,33 +6,10 @@ import ( "encoding/hex" "testing" + "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/util" ) -func TestGenisis(t *testing.T) { - var ( - rawBlock = "000000000000000000000000000000000000000000000000000000000000000000000000845c34e7c1aed302b1718e914da0c42bf47c476ac4d89671f278d8ab6d27aa3d65fc8857000000001dac2b7c00000000be48d3a3f5d10013ab9ffee489706078714f1ea2010001510400001dac2b7c00000000400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000400001445b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e5b881227d2c7b226c616e67223a22656e222c226e616d65223a22416e74436f696e227d5d0000c16ff286230008009f7fd096d37ed2c0e3f7f0cfc924beef4ffceb680000000001000000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50000c16ff2862300be48d3a3f5d10013ab9ffee489706078714f1ea201000151" - //rawBlockHash = "996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099" - ) - - rawBlockBytes, err := hex.DecodeString(rawBlock) - if err != nil { - t.Fatal(err) - } - - block := &Block{} - if err := block.DecodeBinary(bytes.NewReader(rawBlockBytes)); err != nil { - t.Fatal(err) - } - - hash, err := block.Hash() - if err != nil { - t.Fatal(err) - } - - t.Log(hash) -} - func TestDecodeBlock(t *testing.T) { var ( rawBlock = "00000000b7def681f0080262aa293071c53b41fc3146b196067243700b68acd059734fd19543108bf9ddc738cbee2ed1160f153aa0d057f062de0aa3cbb64ba88735c23d43667e59543f050095df82b02e324c5ff3812db982f3b0089a21a278988efeec6a027b2501fd450140113ac66657c2f544e8ad13905fcb2ebaadfef9502cbefb07960fbe56df098814c223dcdd3d0efa0b43a9459e654d948516dcbd8b370f50fbecfb8b411d48051a408500ce85591e516525db24065411f6a88f43de90fa9c167c2e6f5af43bc84e65e5a4bb174bc83a19b6965ff10f476b1b151ae15439a985f33916abc6822b0bb140f4aae522ffaea229987a10d01beec826c3b9a189fe02aa82680581b78f3df0ea4d3f93ca8ea35ffc90f15f7db9017f92fafd9380d9ba3237973cf4313cf626fc40e30e50e3588bd047b39f478b59323868cd50c7ab54355d8245bf0f1988d37528f9bbfc68110cf917debbdbf1f4bdd02cdcccdc3269fdf18a6c727ee54b6934d840e43918dd1ec6123550ec37a513e72b34b2c2a3baa510dec3037cbef2fa9f6ed1e7ccd1f3f6e19d4ce2c0919af55249a970c2685217f75a5589cf9e54dff8449af155210209e7fd41dfb5c2f8dc72eb30358ac100ea8c72da18847befe06eade68cebfcb9210327da12b5c40200e9f65569476bbff2218da4f32548ff43b6387ec1416a231ee821034ff5ceeac41acf22cd5ed2da17a6df4dd8358fcb2bfb1a43208ad0feaab2746b21026ce35b29147ad09e4afe4ec4a7319095f08198fa8babbe3c56e970b143528d2221038dddc06ce687677a53d54f096d2591ba2302068cf123c1f2d75c2dddc542557921039dafd8571a641058ccc832c5e2111ea39b09c0bde36050914384f7a48bce9bf92102d02b1873a0863cd042cc717da31cea0d7cf9db32b74d4c72c01b0011503e2e2257ae01000095df82b000000000" @@ -82,7 +59,7 @@ func newBlockBase() BlockBase { Index: 1, ConsensusData: 1111, NextConsensus: util.Uint160{}, - Script: &Witness{ + Script: &transaction.Witness{ VerificationScript: []byte{0x0}, InvocationScript: []byte{0x1}, }, @@ -104,9 +81,9 @@ func TestHashBlockEqualsHashHeader(t *testing.T) { func TestBlockVerify(t *testing.T) { block := &Block{ BlockBase: newBlockBase(), - Transactions: []*Transaction{ - {Type: MinerTX}, - {Type: IssueTX}, + Transactions: []*transaction.Transaction{ + {Type: transaction.MinerType}, + {Type: transaction.IssueType}, }, } @@ -114,18 +91,18 @@ func TestBlockVerify(t *testing.T) { t.Fatal("block should be verified") } - block.Transactions = []*Transaction{ - {Type: IssueTX}, - {Type: MinerTX}, + block.Transactions = []*transaction.Transaction{ + {Type: transaction.IssueType}, + {Type: transaction.MinerType}, } if block.Verify(false) { t.Fatal("block should not by verified") } - block.Transactions = []*Transaction{ - {Type: MinerTX}, - {Type: MinerTX}, + block.Transactions = []*transaction.Transaction{ + {Type: transaction.MinerType}, + {Type: transaction.MinerType}, } if block.Verify(false) { diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 6ac7cfda8..6cd69bdcb 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -5,6 +5,7 @@ import ( "os" "testing" + "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/util" ) @@ -31,9 +32,9 @@ func TestAddHeaders(t *testing.T) { startHash, _ := util.Uint256DecodeString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099") bc := NewBlockchain(NewMemoryStore(), log.New(os.Stdout, "", 0), startHash) - h1 := &Header{BlockBase: BlockBase{Version: 0, Index: 1, Script: &Witness{}}} - h2 := &Header{BlockBase: BlockBase{Version: 0, Index: 2, Script: &Witness{}}} - h3 := &Header{BlockBase: BlockBase{Version: 0, Index: 3, Script: &Witness{}}} + h1 := &Header{BlockBase: BlockBase{Version: 0, Index: 1, Script: &transaction.Witness{}}} + h2 := &Header{BlockBase: BlockBase{Version: 0, Index: 2, Script: &transaction.Witness{}}} + h3 := &Header{BlockBase: BlockBase{Version: 0, Index: 3, Script: &transaction.Witness{}}} if err := bc.AddHeaders(h1, h2, h3); err != nil { t.Fatal(err) diff --git a/pkg/core/header_test.go b/pkg/core/header_test.go index bafd2d3e9..fd2812767 100644 --- a/pkg/core/header_test.go +++ b/pkg/core/header_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/util" ) @@ -18,7 +19,7 @@ func TestHeaderEncodeDecode(t *testing.T) { Index: 3445, ConsensusData: 394949, NextConsensus: util.Uint160{}, - Script: &Witness{ + Script: &transaction.Witness{ InvocationScript: []byte{0x10}, VerificationScript: []byte{0x11}, }, @@ -51,7 +52,6 @@ func TestHeaderEncodeDecode(t *testing.T) { if !header.NextConsensus.Equals(headerDecode.NextConsensus) { t.Fatalf("expected both next consensus fields to be equal") } - if bytes.Compare(header.Script.InvocationScript, headerDecode.Script.InvocationScript) != 0 { t.Fatalf("expected equal invocation scripts %v and %v", header.Script.InvocationScript, headerDecode.Script.InvocationScript) } diff --git a/pkg/core/transaction.go b/pkg/core/transaction.go deleted file mode 100644 index bc432bca1..000000000 --- a/pkg/core/transaction.go +++ /dev/null @@ -1,35 +0,0 @@ -package core - -import ( - "encoding/binary" - "io" -) - -// Transaction is a process recorded in the NEO system. -type Transaction struct { - Type TransactionType -} - -// All processes in NEO system are recorded in transactions. -// There are several types of transactions. -const ( - MinerTX TransactionType = 0x00 - IssueTX = 0x01 - ClaimTX = 0x02 - EnrollmentTX = 0x20 - VotingTX = 0x24 - RegisterTX = 0x40 - ContractTX = 0x80 - AgencyTX = 0xb0 -) - -// DecodeBinary implements the payload interface. -func (t Transaction) DecodeBinary(r io.Reader) error { - err := binary.Read(r, binary.LittleEndian, &t.Type) - return err -} - -// EncodeBinary implements the payload interface. -func (t Transaction) EncodeBinary(w io.Writer) error { - return nil -} diff --git a/pkg/core/transaction/attr_usage.go b/pkg/core/transaction/attr_usage.go new file mode 100644 index 000000000..183caac41 --- /dev/null +++ b/pkg/core/transaction/attr_usage.go @@ -0,0 +1,49 @@ +package transaction + +// AttrUsage represents the purpose of the attribute. +type AttrUsage uint8 + +// List of valid attribute usages. +const ( + ContractHash AttrUsage = 0x00 + ECDH02 AttrUsage = 0x02 + ECDH03 AttrUsage = 0x03 + Script AttrUsage = 0x20 + Vote AttrUsage = 0x30 + CertUrl AttrUsage = 0x80 + DescriptionUrl AttrUsage = 0x81 + Description AttrUsage = 0x90 + + Hash1 AttrUsage = 0xa1 + Hash2 AttrUsage = 0xa2 + Hash3 AttrUsage = 0xa3 + Hash4 AttrUsage = 0xa4 + Hash5 AttrUsage = 0xa5 + Hash6 AttrUsage = 0xa6 + Hash7 AttrUsage = 0xa7 + Hash8 AttrUsage = 0xa8 + Hash9 AttrUsage = 0xa9 + Hash10 AttrUsage = 0xaa + Hash11 AttrUsage = 0xab + Hash12 AttrUsage = 0xac + Hash13 AttrUsage = 0xad + Hash14 AttrUsage = 0xae + Hash15 AttrUsage = 0xaf + + Remark AttrUsage = 0xf0 + Remark1 AttrUsage = 0xf1 + Remark2 AttrUsage = 0xf2 + Remark3 AttrUsage = 0xf3 + Remark4 AttrUsage = 0xf4 + Remark5 AttrUsage = 0xf5 + Remark6 AttrUsage = 0xf6 + Remark7 AttrUsage = 0xf7 + Remark8 AttrUsage = 0xf8 + Remark9 AttrUsage = 0xf9 + Remark10 AttrUsage = 0xfa + Remark11 AttrUsage = 0xfb + Remark12 AttrUsage = 0xfc + Remark13 AttrUsage = 0xfd + Remark14 AttrUsage = 0xfe + Remark15 AttrUsage = 0xf +) diff --git a/pkg/core/transaction/attribute.go b/pkg/core/transaction/attribute.go new file mode 100644 index 000000000..930a98100 --- /dev/null +++ b/pkg/core/transaction/attribute.go @@ -0,0 +1,79 @@ +package transaction + +import ( + "encoding/binary" + "errors" + "io" + + "github.com/CityOfZion/neo-go/pkg/util" +) + +// Attribute represents a Transaction attribute. +type Attribute struct { + Usage AttrUsage + Data []byte +} + +// DecodeBinary implements the Payloader interface. +func (attr *Attribute) DecodeBinary(r io.Reader) error { + if err := binary.Read(r, binary.LittleEndian, &attr.Usage); err != nil { + return err + } + if attr.Usage == ContractHash || + attr.Usage == Vote || + (attr.Usage >= Hash1 && attr.Usage <= Hash15) { + attr.Data = make([]byte, 32) + return binary.Read(r, binary.LittleEndian, attr.Data) + } + if attr.Usage == ECDH02 || attr.Usage == ECDH03 { + attr.Data = make([]byte, 33) + attr.Data[0] = byte(attr.Usage) + return binary.Read(r, binary.LittleEndian, attr.Data[1:]) + } + if attr.Usage == Script { + attr.Data = make([]byte, 20) + return binary.Read(r, binary.LittleEndian, attr.Data) + } + if attr.Usage == DescriptionUrl { + attr.Data = make([]byte, 1) + return binary.Read(r, binary.LittleEndian, attr.Data) + } + if attr.Usage == Description || attr.Usage >= Remark { + lenData := util.ReadVarUint(r) + attr.Data = make([]byte, lenData) + return binary.Read(r, binary.LittleEndian, attr.Data) + } + return errors.New("format error in decoding transaction attribute") +} + +// EncodeBinary implements the Payload interface. +func (attr *Attribute) EncodeBinary(w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, &attr.Usage); err != nil { + return err + } + if attr.Usage == ContractHash || + attr.Usage == Vote || + (attr.Usage >= Hash1 && attr.Usage <= Hash15) { + return binary.Write(w, binary.LittleEndian, attr.Data) + } + if attr.Usage == ECDH02 || attr.Usage == ECDH03 { + attr.Data[0] = byte(attr.Usage) + return binary.Write(w, binary.LittleEndian, attr.Data[1:33]) + } + if attr.Usage == Script { + return binary.Write(w, binary.LittleEndian, attr.Data) + } + if attr.Usage == DescriptionUrl { + if err := util.WriteVarUint(w, uint64(len(attr.Data))); err != nil { + return err + } + return binary.Write(w, binary.LittleEndian, attr.Data) + } + if attr.Usage == Description || attr.Usage >= Remark { + if err := util.WriteVarUint(w, uint64(len(attr.Data))); err != nil { + return err + } + return binary.Write(w, binary.LittleEndian, attr.Data) + } + return errors.New("format error in encoding transaction attribute") +} diff --git a/pkg/core/transaction/claim.go b/pkg/core/transaction/claim.go new file mode 100644 index 000000000..b620ae297 --- /dev/null +++ b/pkg/core/transaction/claim.go @@ -0,0 +1,38 @@ +package transaction + +import ( + "io" + + "github.com/CityOfZion/neo-go/pkg/util" +) + +// ClaimTX represents a claim transaction. +type ClaimTX struct { + Claims []*Input +} + +// DecodeBinary implements the Payload interface. +func (tx *ClaimTX) DecodeBinary(r io.Reader) error { + lenClaims := util.ReadVarUint(r) + tx.Claims = make([]*Input, lenClaims) + for i := 0; i < int(lenClaims); i++ { + tx.Claims[i] = &Input{} + if err := tx.Claims[i].DecodeBinary(r); err != nil { + return err + } + } + return nil +} + +// EncodeBinary implements the Payload interface. +func (tx *ClaimTX) EncodeBinary(w io.Writer) error { + if err := util.WriteVarUint(w, uint64(len(tx.Claims))); err != nil { + return err + } + for _, claim := range tx.Claims { + if err := claim.EncodeBinary(w); err != nil { + return err + } + } + return nil +} diff --git a/pkg/core/transaction/input.go b/pkg/core/transaction/input.go new file mode 100644 index 000000000..8cee4645a --- /dev/null +++ b/pkg/core/transaction/input.go @@ -0,0 +1,36 @@ +package transaction + +import ( + "encoding/binary" + "io" + + "github.com/anthdm/neo-go/pkg/util" +) + +// Input represents a Transaction input. +type Input struct { + // The hash of the previous transaction. + PrevHash util.Uint256 + + // The index of the previous transaction. + PrevIndex uint16 +} + +// DecodeBinary implements the Payload interface. +func (in *Input) DecodeBinary(r io.Reader) error { + if err := binary.Read(r, binary.LittleEndian, &in.PrevHash); err != nil { + return err + } + return binary.Read(r, binary.LittleEndian, &in.PrevIndex) +} + +// EncodeBinary implements the Payload interface. +func (in *Input) EncodeBinary(w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, in.PrevHash); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, in.PrevIndex); err != nil { + return err + } + return nil +} diff --git a/pkg/core/transaction/invocation.go b/pkg/core/transaction/invocation.go new file mode 100644 index 000000000..216d141fb --- /dev/null +++ b/pkg/core/transaction/invocation.go @@ -0,0 +1,54 @@ +package transaction + +import ( + "encoding/binary" + "io" + + "github.com/CityOfZion/neo-go/pkg/util" +) + +// InvocationTX represents a invocation transaction and is used to +// deploy smart contract to the NEO blockchain. +type InvocationTX struct { + // Script output of the smart contract. + Script []byte + + // Gas cost of the smart contract. + Gas util.Fixed8 +} + +// NewInvocationTX returns a new invocation transaction. +func NewInvocationTX(script []byte) *Transaction { + return &Transaction{ + Type: InvocationType, + Version: 1, + Data: &InvocationTX{ + Script: script, + }, + Attributes: []*Attribute{}, + Inputs: []*Input{}, + Outputs: []*Output{}, + Scripts: []*Witness{}, + } +} + +// DecodeBinary implements the Payload interface. +func (tx *InvocationTX) DecodeBinary(r io.Reader) error { + lenScript := util.ReadVarUint(r) + tx.Script = make([]byte, lenScript) + if err := binary.Read(r, binary.LittleEndian, tx.Script); err != nil { + return err + } + return binary.Read(r, binary.LittleEndian, &tx.Gas) +} + +// EncodeBinary implements the Payload interface. +func (tx *InvocationTX) EncodeBinary(w io.Writer) error { + if err := util.WriteVarUint(w, uint64(len(tx.Script))); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, tx.Script); err != nil { + return err + } + return binary.Write(w, binary.LittleEndian, tx.Gas) +} diff --git a/pkg/core/transaction/miner.go b/pkg/core/transaction/miner.go new file mode 100644 index 000000000..af7284b93 --- /dev/null +++ b/pkg/core/transaction/miner.go @@ -0,0 +1,22 @@ +package transaction + +import ( + "encoding/binary" + "io" +) + +// MinerTX represents a miner transaction. +type MinerTX struct { + // Random number/identifier + Nonce uint32 +} + +// DecodeBinary implements the Payload interface. +func (tx *MinerTX) DecodeBinary(r io.Reader) error { + return binary.Read(r, binary.LittleEndian, &tx.Nonce) +} + +// EncodeBinary implements the Payload interface. +func (tx *MinerTX) EncodeBinary(w io.Writer) error { + return binary.Write(w, binary.LittleEndian, tx.Nonce) +} diff --git a/pkg/core/transaction/output.go b/pkg/core/transaction/output.go new file mode 100644 index 000000000..05716b9d2 --- /dev/null +++ b/pkg/core/transaction/output.go @@ -0,0 +1,51 @@ +package transaction + +import ( + "encoding/binary" + "io" + + "github.com/CityOfZion/neo-go/pkg/util" +) + +// Output represents a Transaction output. +type Output struct { + // The NEO asset id used in the transaction. + AssetID util.Uint256 + + // Amount of AssetType send or received. + Amount util.Fixed8 + + // The address of the remittee. + ScriptHash util.Uint160 +} + +// NewOutput returns a new transaction output. +func NewOutput(assetID util.Uint256, amount util.Fixed8, scriptHash util.Uint160) *Output { + return &Output{ + AssetID: assetID, + Amount: amount, + ScriptHash: scriptHash, + } +} + +// DecodeBinary implements the Payload interface. +func (out *Output) DecodeBinary(r io.Reader) error { + if err := binary.Read(r, binary.LittleEndian, &out.AssetID); err != nil { + return err + } + if err := binary.Read(r, binary.LittleEndian, &out.Amount); err != nil { + return err + } + return binary.Read(r, binary.LittleEndian, &out.ScriptHash) +} + +// EncodeBinary implements the Payload interface. +func (out *Output) EncodeBinary(w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, out.AssetID); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, out.Amount); err != nil { + return err + } + return binary.Write(w, binary.LittleEndian, out.ScriptHash) +} diff --git a/pkg/core/transaction/transaction.go b/pkg/core/transaction/transaction.go new file mode 100644 index 000000000..deeab0527 --- /dev/null +++ b/pkg/core/transaction/transaction.go @@ -0,0 +1,178 @@ +package transaction + +import ( + "encoding/binary" + "io" + + "github.com/CityOfZion/neo-go/pkg/util" +) + +// Transaction is a process recorded in the NEO blockchain. +type Transaction struct { + // The type of the transaction. + Type TransactionType + + // The trading version which is currently 0. + Version uint8 + + // Data specific to the type of the transaction. + // This is always a pointer to a Transaction. + Data interface{} + + // Transaction attributes. + Attributes []*Attribute + + // The inputs of the transaction. + Inputs []*Input + + // The outputs of the transaction. + Outputs []*Output + + // The scripts that comes with this transaction. + // Scripts exist out of the verification script + // and invocation script. + Scripts []*Witness +} + +// AddOutput adds the given output to the transaction outputs. +func (t *Transaction) AddOutput(out *Output) { + t.Outputs = append(t.Outputs, out) +} + +// AddInput adds the given input to the transaction inputs. +func (t *Transaction) AddInput(in *Input) { + t.Inputs = append(t.Inputs, in) +} + +// DecodeBinary implements the payload interface. +func (t *Transaction) DecodeBinary(r io.Reader) error { + if err := binary.Read(r, binary.LittleEndian, &t.Type); err != nil { + return err + } + if err := binary.Read(r, binary.LittleEndian, &t.Version); err != nil { + return err + } + if err := t.decodeData(r); err != nil { + return err + } + + lenAttrs := util.ReadVarUint(r) + t.Attributes = make([]*Attribute, lenAttrs) + for i := 0; i < int(lenAttrs); i++ { + t.Attributes[i] = &Attribute{} + if err := t.Attributes[i].DecodeBinary(r); err != nil { + return err + } + } + + lenInputs := util.ReadVarUint(r) + t.Inputs = make([]*Input, lenInputs) + for i := 0; i < int(lenInputs); i++ { + t.Inputs[i] = &Input{} + if err := t.Inputs[i].DecodeBinary(r); err != nil { + return err + } + } + + lenOutputs := util.ReadVarUint(r) + t.Outputs = make([]*Output, lenOutputs) + for i := 0; i < int(lenOutputs); i++ { + t.Outputs[i] = &Output{} + if err := t.Outputs[i].DecodeBinary(r); err != nil { + return err + } + } + + lenScripts := util.ReadVarUint(r) + t.Scripts = make([]*Witness, lenScripts) + for i := 0; i < int(lenScripts); i++ { + t.Scripts[i] = &Witness{} + if err := t.Scripts[i].DecodeBinary(r); err != nil { + return err + } + } + + return nil +} + +func (t *Transaction) decodeData(r io.Reader) error { + switch t.Type { + case InvocationType: + t.Data = &InvocationTX{} + return t.Data.(*InvocationTX).DecodeBinary(r) + case MinerType: + t.Data = &MinerTX{} + return t.Data.(*MinerTX).DecodeBinary(r) + case ClaimType: + t.Data = &ClaimTX{} + return t.Data.(*ClaimTX).DecodeBinary(r) + } + return nil +} + +// EncodeBinary implements the payload interface. +func (t *Transaction) EncodeBinary(w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, t.Type); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, t.Version); err != nil { + return err + } + if err := t.encodeData(w); err != nil { + return err + } + + // Attributes + if err := util.WriteVarUint(w, uint64(len(t.Attributes))); err != nil { + return err + } + for _, attr := range t.Attributes { + if err := attr.EncodeBinary(w); err != nil { + return err + } + } + + // Inputs + if err := util.WriteVarUint(w, uint64(len(t.Inputs))); err != nil { + return err + } + for _, in := range t.Inputs { + if err := in.EncodeBinary(w); err != nil { + return err + } + } + + // Outputs + if err := util.WriteVarUint(w, uint64(len(t.Outputs))); err != nil { + return err + } + for _, out := range t.Outputs { + if err := out.EncodeBinary(w); err != nil { + return err + } + } + + // Scripts + if err := util.WriteVarUint(w, uint64(len(t.Scripts))); err != nil { + return err + } + for _, s := range t.Scripts { + if err := s.EncodeBinary(w); err != nil { + return err + } + } + + return nil +} + +func (t *Transaction) encodeData(w io.Writer) error { + switch t.Type { + case InvocationType: + return t.Data.(*InvocationTX).EncodeBinary(w) + case MinerType: + return t.Data.(*MinerTX).EncodeBinary(w) + case ClaimType: + return t.Data.(*ClaimTX).EncodeBinary(w) + } + return nil +} diff --git a/pkg/core/transaction/transaction_test.go b/pkg/core/transaction/transaction_test.go new file mode 100644 index 000000000..d4e65826a --- /dev/null +++ b/pkg/core/transaction/transaction_test.go @@ -0,0 +1,81 @@ +package transaction + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/CityOfZion/neo-go/pkg/util" + "github.com/stretchr/testify/assert" +) + +// Source of this TX: https://neotracker.io/tx/2c6a45547b3898318e400e541628990a07acb00f3b9a15a8e966ae49525304da +var rawTXClaim = "020004bc67ba325d6412ff4c55b10f7e9afb54bbb2228d201b37363c3d697ac7c198f70300591cd454d7318d2087c0196abfbbd1573230380672f0f0cd004dcb4857e58cbd010031bcfbed573f5318437e95edd603922a4455ff3326a979fdd1c149a84c4cb0290000b51eb6159c58cac4fe23d90e292ad2bcb7002b0da2c474e81e1889c0649d2c490000000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c603b555f00000000005d9de59d99c0d1f6ed1496444473f4a0b538302f014140456349cec43053009accdb7781b0799c6b591c812768804ab0a0b56b5eae7a97694227fcd33e70899c075848b2cee8fae733faac6865b484d3f7df8949e2aadb232103945fae1ed3c31d778f149192b76734fcc951b400ba3598faa81ff92ebe477eacac" + +func TestDecodeEncodeClaimTX(t *testing.T) { + b, err := hex.DecodeString(rawTXClaim) + if err != nil { + t.Fatal(err) + } + tx := &Transaction{} + if err := tx.DecodeBinary(bytes.NewReader(b)); err != nil { + t.Fatal(err) + } + assert.Equal(t, tx.Type, ClaimType) + assert.IsType(t, tx.Data, &ClaimTX{}) + claimTX := tx.Data.(*ClaimTX) + assert.Equal(t, 4, len(claimTX.Claims)) + assert.Equal(t, 0, len(tx.Attributes)) + assert.Equal(t, 0, len(tx.Inputs)) + assert.Equal(t, 1, len(tx.Outputs)) + assert.Equal(t, "AQJseD8iBmCD4sgfHRhMahmoi9zvopG6yz", tx.Outputs[0].ScriptHash.Address()) + assert.Equal(t, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", tx.Outputs[0].AssetID.String()) + assert.Equal(t, tx.Outputs[0].Amount.String(), "0.06247739") + invoc := "40456349cec43053009accdb7781b0799c6b591c812768804ab0a0b56b5eae7a97694227fcd33e70899c075848b2cee8fae733faac6865b484d3f7df8949e2aadb" + verif := "2103945fae1ed3c31d778f149192b76734fcc951b400ba3598faa81ff92ebe477eacac" + assert.Equal(t, 1, len(tx.Scripts)) + assert.Equal(t, invoc, hex.EncodeToString(tx.Scripts[0].InvocationScript)) + assert.Equal(t, verif, hex.EncodeToString(tx.Scripts[0].VerificationScript)) + + buf := new(bytes.Buffer) + if err := tx.EncodeBinary(buf); err != nil { + t.Fatal(err) + } + assert.Equal(t, rawTXClaim, hex.EncodeToString(buf.Bytes())) +} + +// Source of this TX: https://neotracker.io/tx/fe4b3af60677204c57e573a57bdc97bc5059b05ad85b1474f84431f88d910f64 +var rawTXInvocation = "d101590400b33f7114839c33710da24cf8e7d536b8d244f3991cf565c8146063795d3b9b3cd55aef026eae992b91063db0db53c1087472616e7366657267c5cc1cb5392019e2cc4e6d6b5ea54c8d4b6d11acf166cb072961424c54f6000000000000000001206063795d3b9b3cd55aef026eae992b91063db0db0000014140c6a131c55ca38995402dff8e92ac55d89cbed4b98dfebbcb01acbc01bd78fa2ce2061be921b8999a9ab79c2958875bccfafe7ce1bbbaf1f56580815ea3a4feed232102d41ddce2c97be4c9aa571b8a32cbc305aa29afffbcae71b0ef568db0e93929aaac" + +func TestDecodeEncodeInvocationTX(t *testing.T) { + b, err := hex.DecodeString(rawTXInvocation) + if err != nil { + t.Fatal(err) + } + tx := &Transaction{} + if err := tx.DecodeBinary(bytes.NewReader(b)); err != nil { + t.Fatal(err) + } + assert.Equal(t, tx.Type, InvocationType) + assert.IsType(t, tx.Data, &InvocationTX{}) + + invocTX := tx.Data.(*InvocationTX) + script := "0400b33f7114839c33710da24cf8e7d536b8d244f3991cf565c8146063795d3b9b3cd55aef026eae992b91063db0db53c1087472616e7366657267c5cc1cb5392019e2cc4e6d6b5ea54c8d4b6d11acf166cb072961424c54f6" + assert.Equal(t, script, hex.EncodeToString(invocTX.Script)) + assert.Equal(t, util.Fixed8(0), invocTX.Gas) + + assert.Equal(t, 1, len(tx.Attributes)) + assert.Equal(t, 0, len(tx.Inputs)) + assert.Equal(t, 0, len(tx.Outputs)) + invoc := "40c6a131c55ca38995402dff8e92ac55d89cbed4b98dfebbcb01acbc01bd78fa2ce2061be921b8999a9ab79c2958875bccfafe7ce1bbbaf1f56580815ea3a4feed" + verif := "2102d41ddce2c97be4c9aa571b8a32cbc305aa29afffbcae71b0ef568db0e93929aaac" + assert.Equal(t, 1, len(tx.Scripts)) + assert.Equal(t, invoc, hex.EncodeToString(tx.Scripts[0].InvocationScript)) + assert.Equal(t, verif, hex.EncodeToString(tx.Scripts[0].VerificationScript)) + + buf := new(bytes.Buffer) + if err := tx.EncodeBinary(buf); err != nil { + t.Fatal(err) + } + assert.Equal(t, rawTXInvocation, hex.EncodeToString(buf.Bytes())) +} diff --git a/pkg/core/transaction/type.go b/pkg/core/transaction/type.go new file mode 100644 index 000000000..fbf95a608 --- /dev/null +++ b/pkg/core/transaction/type.go @@ -0,0 +1,50 @@ +package transaction + +// TransactionType is the type of a transaction. +type TransactionType uint8 + +// All processes in NEO system are recorded in transactions. +// There are several types of transactions. +const ( + MinerType TransactionType = 0x00 + IssueType TransactionType = 0x01 + ClaimType TransactionType = 0x02 + EnrollmentType TransactionType = 0x20 + VotingType TransactionType = 0x24 + RegisterType TransactionType = 0x40 + ContractType TransactionType = 0x80 + StateType TransactionType = 0x90 + AgencyType TransactionType = 0xb0 + PublishType TransactionType = 0xd0 + InvocationType TransactionType = 0xd1 +) + +// String implements the stringer interface. +func (t TransactionType) String() string { + switch t { + case MinerType: + return "miner transaction" + case IssueType: + return "issue transaction" + case ClaimType: + return "claim transaction" + case EnrollmentType: + return "enrollment transaction" + case VotingType: + return "voting transaction" + case RegisterType: + return "register transaction" + case ContractType: + return "contract transaction" + case StateType: + return "state transaction" + case AgencyType: + return "agency transaction" + case PublishType: + return "publish transaction" + case InvocationType: + return "invocation transaction" + default: + return "" + } +} diff --git a/pkg/core/witness.go b/pkg/core/transaction/witness.go similarity index 76% rename from pkg/core/witness.go rename to pkg/core/transaction/witness.go index b59d80d7c..eabedea32 100644 --- a/pkg/core/witness.go +++ b/pkg/core/transaction/witness.go @@ -1,4 +1,4 @@ -package core +package transaction import ( "encoding/binary" @@ -7,7 +7,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/util" ) -// Witness ... +// Witness contains 2 scripts. type Witness struct { InvocationScript []byte VerificationScript []byte @@ -17,17 +17,12 @@ type Witness struct { func (wit *Witness) DecodeBinary(r io.Reader) error { lenb := util.ReadVarUint(r) wit.InvocationScript = make([]byte, lenb) - if err := binary.Read(r, binary.LittleEndian, &wit.InvocationScript); err != nil { - panic(err) + if err := binary.Read(r, binary.LittleEndian, wit.InvocationScript); err != nil { + return err } - lenb = util.ReadVarUint(r) wit.VerificationScript = make([]byte, lenb) - if err := binary.Read(r, binary.LittleEndian, &wit.VerificationScript); err != nil { - panic(err) - } - - return nil + return binary.Read(r, binary.LittleEndian, wit.VerificationScript) } // EncodeBinary implements the payload interface. diff --git a/pkg/core/transaction_type.go b/pkg/core/transaction_type.go deleted file mode 100644 index 3aafe0166..000000000 --- a/pkg/core/transaction_type.go +++ /dev/null @@ -1,28 +0,0 @@ -package core - -// TransactionType is the type of a transaction. -type TransactionType uint8 - -// String implements the stringer interface. -func (t TransactionType) String() string { - switch t { - case MinerTX: - return "miner transaction" - case IssueTX: - return "issue transaction" - case ClaimTX: - return "claim transaction" - case EnrollmentTX: - return "enrollment transaction" - case VotingTX: - return "voting transaction" - case RegisterTX: - return "register transaction" - case ContractTX: - return "contract transaction" - case AgencyTX: - return "agency transaction" - default: - return "" - } -} diff --git a/pkg/network/payload/headers_test.go b/pkg/network/payload/headers_test.go index d9918316b..a2848f984 100644 --- a/pkg/network/payload/headers_test.go +++ b/pkg/network/payload/headers_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/CityOfZion/neo-go/pkg/core" + "github.com/CityOfZion/neo-go/pkg/core/transaction" ) func TestHeadersEncodeDecode(t *testing.T) { @@ -14,7 +15,7 @@ func TestHeadersEncodeDecode(t *testing.T) { BlockBase: core.BlockBase{ Version: 0, Index: 1, - Script: &core.Witness{ + Script: &transaction.Witness{ InvocationScript: []byte{0x0}, VerificationScript: []byte{0x1}, }, @@ -23,7 +24,7 @@ func TestHeadersEncodeDecode(t *testing.T) { BlockBase: core.BlockBase{ Version: 0, Index: 2, - Script: &core.Witness{ + Script: &transaction.Witness{ InvocationScript: []byte{0x0}, VerificationScript: []byte{0x1}, }, @@ -32,7 +33,7 @@ func TestHeadersEncodeDecode(t *testing.T) { BlockBase: core.BlockBase{ Version: 0, Index: 3, - Script: &core.Witness{ + Script: &transaction.Witness{ InvocationScript: []byte{0x0}, VerificationScript: []byte{0x1}, }, diff --git a/pkg/network/payload/payload.go b/pkg/network/payload/payload.go index 90d5b0d74..90c489c43 100644 --- a/pkg/network/payload/payload.go +++ b/pkg/network/payload/payload.go @@ -4,6 +4,6 @@ import "io" // Payload is anything that can be binary encoded/decoded. type Payload interface { - EncodeBinary(io.Writer) error - DecodeBinary(io.Reader) error + EncodeBinary(io.Writer) error + DecodeBinary(io.Reader) error } diff --git a/pkg/smartcontract/.keep b/pkg/smartcontract/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/smartcontract/contract.go b/pkg/smartcontract/contract.go index 2b13035c5..526ea36fb 100644 --- a/pkg/smartcontract/contract.go +++ b/pkg/smartcontract/contract.go @@ -1,5 +1,4 @@ package smartcontract // Contract represents a NEO smartcontract. -type Contract struct { -} +type Contract struct{} diff --git a/pkg/smartcontract/param_context.go b/pkg/smartcontract/param_context.go new file mode 100644 index 000000000..22ce22dc2 --- /dev/null +++ b/pkg/smartcontract/param_context.go @@ -0,0 +1,52 @@ +package smartcontract + +import "github.com/anthdm/neo-go/pkg/util" + +// ParamType represent the Type of the contract parameter +type ParamType int + +// A list of supported smart contract parameter types. +const ( + SignatureType ParamType = iota + BoolType + IntegerType + Hash160Type + Hash256Type + ByteArrayType + PublicKeyType + StringType + ArrayType +) + +// Parameter represents a smart contract parameter. +type Parameter struct { + // Type of the parameter + Type ParamType + // The actual value of the parameter. + Value interface{} +} + +// NewParameter returns a Parameter with proper initialized Value +// of the given ParamType. +func NewParameter(t ParamType) Parameter { + return Parameter{ + Type: t, + Value: nil, + } +} + +// ContextItem represents a transaction context item. +type ContextItem struct { + Script util.Uint160 + Parameters []Parameter + Signatures []Signature +} + +// Signature represents a transaction signature. +type Signature struct { + Data []byte + PublicKey []byte +} + +// ParameterContext holds the parameter context. +type ParameterContext struct{} diff --git a/pkg/smartcontract/types/block.go b/pkg/smartcontract/types/block.go deleted file mode 100644 index 928449db6..000000000 --- a/pkg/smartcontract/types/block.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -// Block represents a block in the blockchain. -type Block struct{} - -// Index returns the height of the block. -func (b Block) Index() int { - return 0 -} diff --git a/pkg/util/fixed8.go b/pkg/util/fixed8.go new file mode 100644 index 000000000..22dd8429f --- /dev/null +++ b/pkg/util/fixed8.go @@ -0,0 +1,31 @@ +package util + +import ( + "bytes" + "strconv" +) + +// Fixed8 represents a fixed-point number with precision 10^-8. +type Fixed8 int64 + +// String implements the Stringer interface. +func (f Fixed8) String() string { + buf := new(bytes.Buffer) + val := int64(f) + if val < 0 { + buf.WriteRune('-') + val = -val + } + str := strconv.FormatInt(val/100000000, 10) + buf.WriteString(str) + val %= 100000000 + if val > 0 { + buf.WriteRune('.') + str = strconv.FormatInt(val, 10) + for i := len(str); i < 8; i++ { + buf.WriteRune('0') + } + buf.WriteString(str) + } + return buf.String() +} diff --git a/pkg/util/uint160.go b/pkg/util/uint160.go index f6bd0b9a5..800671825 100644 --- a/pkg/util/uint160.go +++ b/pkg/util/uint160.go @@ -3,6 +3,8 @@ package util import ( "encoding/hex" "fmt" + + "github.com/CityOfZion/neo-go/pkg/crypto" ) const uint160Size = 20 @@ -22,6 +24,16 @@ func Uint160DecodeString(s string) (u Uint160, err error) { return Uint160DecodeBytes(b) } +// Uint160DecodeAddress attempts to decode the given NEO address string +// into an Uint160. +func Uint160DecodeAddress(s string) (u Uint160, err error) { + b, err := crypto.Base58CheckDecode(s) + if err != nil { + return u, err + } + return Uint160DecodeBytes(b[1:21]) +} + // Uint160DecodeBytes attempts to decode the given bytes into an Uint160. func Uint160DecodeBytes(b []byte) (u Uint160, err error) { if len(b) != uint160Size { @@ -42,6 +54,13 @@ func (u Uint160) Bytes() []byte { return b } +// Address returns the NEO address representation of u. +func (u Uint160) Address() string { + // Dont forget to prepend the Address version 0x17 (23) A + b := append([]byte{0x17}, u.Bytes()...) + return crypto.Base58CheckEncode(b) +} + // String implements the stringer interface. func (u Uint160) String() string { return hex.EncodeToString(u.Bytes()) diff --git a/pkg/util/uint160_test.go b/pkg/util/uint160_test.go index c7d868219..145533e7c 100644 --- a/pkg/util/uint160_test.go +++ b/pkg/util/uint160_test.go @@ -1 +1,22 @@ package util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUint160FromToAddress(t *testing.T) { + addrs := []string{ + "AMLr1CpPQtbEdiJdriX1HpRNMZUwbU2Huj", + "AKtwd3DRXj3nL5kHMUoNsdnsCEVjnuuTFF", + "AMxkaxFVG8Q1BhnB4fjTA5ZmUTEnnTMJMa", + } + for _, addr := range addrs { + val, err := Uint160DecodeAddress(addr) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, addr, val.Address()) + } +} diff --git a/pkg/util/uint256_test.go b/pkg/util/uint256_test.go index 1a965c025..ae6b8a438 100644 --- a/pkg/util/uint256_test.go +++ b/pkg/util/uint256_test.go @@ -3,6 +3,8 @@ package util import ( "encoding/hex" "testing" + + "github.com/stretchr/testify/assert" ) func TestUint256DecodeString(t *testing.T) { @@ -11,7 +13,7 @@ func TestUint256DecodeString(t *testing.T) { if err != nil { t.Fatal(err) } - t.Log(val) + assert.Equal(t, hexStr, val.String()) } func TestUint256DecodeBytes(t *testing.T) { @@ -24,9 +26,7 @@ func TestUint256DecodeBytes(t *testing.T) { if err != nil { t.Fatal(err) } - if val.String() != hexStr { - t.Fatalf("expected %s and %s to be equal", val, hexStr) - } + assert.Equal(t, hexStr, val.String()) } func TestUInt256Equals(t *testing.T) { diff --git a/pkg/vm/compiler/emit.go b/pkg/vm/compiler/emit.go index 8d04156f2..4b25f975b 100644 --- a/pkg/vm/compiler/emit.go +++ b/pkg/vm/compiler/emit.go @@ -57,11 +57,6 @@ func emitBytes(w *bytes.Buffer, b []byte) error { n = len(b) ) - if n == 0 { - // The VM expects a pushf (0x00). - // Empty strings on the stack for example. - return emitOpcode(w, vm.Opushf) - } if n <= int(vm.Opushbytes75) { return emit(w, vm.Opcode(n), b) } else if n < 0x100 { diff --git a/pkg/vm/opcode.go b/pkg/vm/opcode.go index 724df2616..f220ccbc6 100644 --- a/pkg/vm/opcode.go +++ b/pkg/vm/opcode.go @@ -39,7 +39,7 @@ const ( Ojmpifnot Opcode = 0x64 Ocall Opcode = 0x65 Oret Opcode = 0x66 - Opcall Opcode = 0x67 + Oappcall Opcode = 0x67 Osyscall Opcode = 0x68 Otailcall Opcode = 0x69 diff --git a/pkg/vm/script_builder.go b/pkg/vm/script_builder.go new file mode 100644 index 000000000..3d05cb30f --- /dev/null +++ b/pkg/vm/script_builder.go @@ -0,0 +1,149 @@ +package vm + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math/big" + + "github.com/CityOfZion/neo-go/pkg/util" +) + +// Emit a VM Opcode with data to the given buffer. +func Emit(w *bytes.Buffer, op Opcode, b []byte) error { + if err := w.WriteByte(byte(op)); err != nil { + return err + } + _, err := w.Write(b) + return err +} + +// EmitOpcode emits a single VM Opcode the given buffer. +func EmitOpcode(w *bytes.Buffer, op Opcode) error { + return w.WriteByte(byte(op)) +} + +// EmitBool emits a bool type the given buffer. +func EmitBool(w *bytes.Buffer, ok bool) error { + if ok { + return EmitOpcode(w, Opusht) + } + return EmitOpcode(w, Opushf) +} + +// EmitInt emits a int type to the given buffer. +func EmitInt(w *bytes.Buffer, i int64) error { + if i == -1 { + return EmitOpcode(w, Opushm1) + } + if i == 0 { + return EmitOpcode(w, Opushf) + } + if i > 0 && i < 16 { + val := Opcode((int(Opush1) - 1 + int(i))) + return EmitOpcode(w, val) + } + + bInt := big.NewInt(i) + val := util.ArrayReverse(bInt.Bytes()) + return EmitBytes(w, val) +} + +// EmitString emits a string to the given buffer. +func EmitString(w *bytes.Buffer, s string) error { + return EmitBytes(w, []byte(s)) +} + +// EmitBytes emits a byte array the given buffer. +func EmitBytes(w *bytes.Buffer, b []byte) error { + var ( + err error + n = len(b) + ) + + if n <= int(Opushbytes75) { + return Emit(w, Opcode(n), b) + } else if n < 0x100 { + err = Emit(w, Opushdata1, []byte{byte(n)}) + } else if n < 0x10000 { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(n)) + err = Emit(w, Opushdata2, buf) + } else { + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, uint32(n)) + err = Emit(w, Opushdata4, buf) + } + if err != nil { + return err + } + _, err = w.Write(b) + return err +} + +// EmitSyscall emits the syscall API to the given buffer. +// Syscall API string cannot be 0. +func EmitSyscall(w *bytes.Buffer, api string) error { + if len(api) == 0 { + return errors.New("syscall api cannot be of length 0") + } + buf := make([]byte, len(api)+1) + buf[0] = byte(len(api)) + copy(buf[1:len(buf)], []byte(api)) + return Emit(w, Osyscall, buf) +} + +// EmitCall emits a call Opcode with label to the given buffer. +func EmitCall(w *bytes.Buffer, op Opcode, label int16) error { + return EmitJmp(w, op, label) +} + +// EmitJmp emits a jump Opcode along with label to the given buffer. +func EmitJmp(w *bytes.Buffer, op Opcode, label int16) error { + if !isOpcodeJmp(op) { + return fmt.Errorf("opcode %s is not a jump or call type", op) + } + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(label)) + return Emit(w, op, buf) +} + +// EmitAppCall emits an appcall, if tailCall is true, tailCall opcode will be +// emitted instead. +func EmitAppCall(w *bytes.Buffer, scriptHash util.Uint160, tailCall bool) error { + op := Oappcall + if tailCall { + op = Otailcall + } + return Emit(w, op, scriptHash.Bytes()) +} + +// EmitAppCallWithOperationAndData emits an appcall with the given operation and data. +func EmitAppCallWithOperationAndData(w *bytes.Buffer, scriptHash util.Uint160, operation string, data []byte) error { + if err := EmitBytes(w, data); err != nil { + return err + } + if err := EmitString(w, operation); err != nil { + return err + } + return EmitAppCall(w, scriptHash, false) +} + +// EmitAppCallWithOperation emits an appcall with the given operation. +func EmitAppCallWithOperation(w *bytes.Buffer, scriptHash util.Uint160, operation string) error { + if err := EmitBool(w, false); err != nil { + return err + } + if err := EmitString(w, operation); err != nil { + return err + } + return EmitAppCall(w, scriptHash, false) +} + +func isOpcodeJmp(op Opcode) bool { + if op == Ojmp || op == Ojmpifnot || op == Ojmpif || op == Ocall { + return true + } + return false +} diff --git a/pkg/vm/script_builder_test.go b/pkg/vm/script_builder_test.go new file mode 100644 index 000000000..2c127636c --- /dev/null +++ b/pkg/vm/script_builder_test.go @@ -0,0 +1,64 @@ +package vm + +import ( + "bytes" + "encoding/binary" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEmitInt(t *testing.T) { + buf := new(bytes.Buffer) + EmitInt(buf, 10) + assert.Equal(t, Opcode(buf.Bytes()[0]), Opush10) + buf.Reset() + EmitInt(buf, 100) + assert.Equal(t, buf.Bytes()[0], uint8(1)) + assert.Equal(t, buf.Bytes()[1], uint8(100)) + buf.Reset() + EmitInt(buf, 1000) + assert.Equal(t, buf.Bytes()[0], uint8(2)) + assert.Equal(t, buf.Bytes()[1:3], []byte{0xe8, 0x03}) +} + +func TestEmitBool(t *testing.T) { + buf := new(bytes.Buffer) + EmitBool(buf, true) + EmitBool(buf, false) + assert.Equal(t, Opcode(buf.Bytes()[0]), Opush1) + assert.Equal(t, Opcode(buf.Bytes()[1]), Opush0) +} + +func TestEmitString(t *testing.T) { + buf := new(bytes.Buffer) + str := "City Of Zion" + EmitString(buf, str) + assert.Equal(t, buf.Len(), len(str)+1) + assert.Equal(t, buf.Bytes()[1:], []byte(str)) +} + +func TestEmitSyscall(t *testing.T) { + syscalls := []string{ + "Neo.Runtime.Log", + "Neo.Runtime.Notify", + "Neo.Runtime.Whatever", + } + + buf := new(bytes.Buffer) + for _, syscall := range syscalls { + EmitSyscall(buf, syscall) + assert.Equal(t, Opcode(buf.Bytes()[0]), Osyscall) + assert.Equal(t, buf.Bytes()[1], uint8(len(syscall))) + assert.Equal(t, buf.Bytes()[2:], []byte(syscall)) + buf.Reset() + } +} + +func TestEmitCall(t *testing.T) { + buf := new(bytes.Buffer) + EmitCall(buf, Ojmp, 100) + assert.Equal(t, Opcode(buf.Bytes()[0]), Ojmp) + label := binary.LittleEndian.Uint16(buf.Bytes()[1:3]) + assert.Equal(t, label, uint16(100)) +} diff --git a/pkg/wallet/transfer_output.go b/pkg/wallet/transfer_output.go deleted file mode 100644 index 0e463f0ce..000000000 --- a/pkg/wallet/transfer_output.go +++ /dev/null @@ -1,21 +0,0 @@ -package wallet - -import ( - "math/big" - - "github.com/CityOfZion/neo-go/pkg/util" -) - -// TransferOutput respresents the output of a transaction. -type TransferOutput struct { - // The asset identifier. This should be of type Uint256. - // NEO governing token: c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b - // NEO gas: 602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7 - AssetID util.Uint256 - - // Value of the transfer - Value *big.Int - - // ScriptHash of the transfer. - ScriptHash util.Uint160 -}