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
This commit is contained in:
Anthony De Meulemeester 2018-03-04 14:56:49 +01:00 committed by GitHub
parent 42195b1af4
commit 1a1a19da7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1066 additions and 170 deletions

26
Gopkg.lock generated
View file

@ -1,18 +1,42 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. # 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]] [[projects]]
branch = "master" branch = "master"
name = "github.com/anthdm/rfc6979" name = "github.com/anthdm/rfc6979"
packages = ["."] packages = ["."]
revision = "6a90f24967ebb1aa57b22f74a13dbb3faad8cf3d" revision = "6a90f24967ebb1aa57b22f74a13dbb3faad8cf3d"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/golang/snappy" name = "github.com/golang/snappy"
packages = ["."] packages = ["."]
revision = "553a641470496b2327abcac10b36396bd98e45c9" 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]] [[projects]]
branch = "master" branch = "master"
name = "github.com/syndtr/goleveldb" name = "github.com/syndtr/goleveldb"
@ -74,6 +98,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "74c6b0a11057b8c0fd68342f85e010e323cda6407bec2a889f161b5890928aaf" inputs-digest = "069a738aa1487766b26f9efb8103d2ce0526d43c83049cb5b792f0edf91568de"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View file

@ -47,3 +47,7 @@
[[constraint]] [[constraint]]
name = "golang.org/x/text" name = "golang.org/x/text"
version = "0.3.0" version = "0.3.0"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.1"

View file

@ -1 +1 @@
0.23.0 0.24.0

18
pkg/core/asset_type.go Normal file
View file

@ -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
)

View file

@ -8,6 +8,7 @@ import (
"io" "io"
"log" "log"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
@ -26,12 +27,12 @@ type BlockBase struct {
Index uint32 Index uint32
// Random number also called nonce // Random number also called nonce
ConsensusData uint64 ConsensusData uint64
// contract addresss of the next miner // Contract addresss of the next miner
NextConsensus util.Uint160 NextConsensus util.Uint160
// fixed to 1 // fixed to 1
_ uint8 // padding _ uint8 // padding
// Script used to validate the block // Script used to validate the block
Script *Witness Script *transaction.Witness
} }
// DecodeBinary implements the payload interface. // 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) return fmt.Errorf("format error: padding must equal 1 got %d", padding)
} }
b.Script = &Witness{} b.Script = &transaction.Witness{}
return b.Script.DecodeBinary(r) 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, // 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 // only first seven fields in the block head will be calculated, which are
// version, PrevBlock, MerkleRoot, timestamp, and height, the nonce, NextMiner. // version, PrevBlock, MerkleRoot, timestamp, and height, the nonce, NextMiner.
@ -158,7 +159,7 @@ func (h *Header) EncodeBinary(w io.Writer) error {
type Block struct { type Block struct {
BlockBase BlockBase
// transaction list // transaction list
Transactions []*Transaction Transactions []*transaction.Transaction
} }
// Header returns a pointer to the head of the block (BlockHead). // 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. // Verify the integrity of the block.
func (b *Block) Verify(full bool) bool { func (b *Block) Verify(full bool) bool {
// The first TX has to be a miner transaction. // The first TX has to be a miner transaction.
if b.Transactions[0].Type != MinerTX { if b.Transactions[0].Type != transaction.MinerType {
return false return false
} }
// If the first TX is a minerTX then all others cant. // If the first TX is a minerTX then all others cant.
for _, tx := range b.Transactions[1:] { for _, tx := range b.Transactions[1:] {
if tx.Type == MinerTX { if tx.Type == transaction.MinerType {
return false return false
} }
} }
@ -202,9 +203,9 @@ func (b *Block) DecodeBinary(r io.Reader) error {
} }
lentx := util.ReadVarUint(r) lentx := util.ReadVarUint(r)
b.Transactions = make([]*Transaction, lentx) b.Transactions = make([]*transaction.Transaction, lentx)
for i := 0; i < int(lentx); i++ { for i := 0; i < int(lentx); i++ {
tx := &Transaction{} tx := &transaction.Transaction{}
if err := tx.DecodeBinary(r); err != nil { if err := tx.DecodeBinary(r); err != nil {
return err return err
} }

View file

@ -6,33 +6,10 @@ import (
"encoding/hex" "encoding/hex"
"testing" "testing"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util" "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) { func TestDecodeBlock(t *testing.T) {
var ( var (
rawBlock = "00000000b7def681f0080262aa293071c53b41fc3146b196067243700b68acd059734fd19543108bf9ddc738cbee2ed1160f153aa0d057f062de0aa3cbb64ba88735c23d43667e59543f050095df82b02e324c5ff3812db982f3b0089a21a278988efeec6a027b2501fd450140113ac66657c2f544e8ad13905fcb2ebaadfef9502cbefb07960fbe56df098814c223dcdd3d0efa0b43a9459e654d948516dcbd8b370f50fbecfb8b411d48051a408500ce85591e516525db24065411f6a88f43de90fa9c167c2e6f5af43bc84e65e5a4bb174bc83a19b6965ff10f476b1b151ae15439a985f33916abc6822b0bb140f4aae522ffaea229987a10d01beec826c3b9a189fe02aa82680581b78f3df0ea4d3f93ca8ea35ffc90f15f7db9017f92fafd9380d9ba3237973cf4313cf626fc40e30e50e3588bd047b39f478b59323868cd50c7ab54355d8245bf0f1988d37528f9bbfc68110cf917debbdbf1f4bdd02cdcccdc3269fdf18a6c727ee54b6934d840e43918dd1ec6123550ec37a513e72b34b2c2a3baa510dec3037cbef2fa9f6ed1e7ccd1f3f6e19d4ce2c0919af55249a970c2685217f75a5589cf9e54dff8449af155210209e7fd41dfb5c2f8dc72eb30358ac100ea8c72da18847befe06eade68cebfcb9210327da12b5c40200e9f65569476bbff2218da4f32548ff43b6387ec1416a231ee821034ff5ceeac41acf22cd5ed2da17a6df4dd8358fcb2bfb1a43208ad0feaab2746b21026ce35b29147ad09e4afe4ec4a7319095f08198fa8babbe3c56e970b143528d2221038dddc06ce687677a53d54f096d2591ba2302068cf123c1f2d75c2dddc542557921039dafd8571a641058ccc832c5e2111ea39b09c0bde36050914384f7a48bce9bf92102d02b1873a0863cd042cc717da31cea0d7cf9db32b74d4c72c01b0011503e2e2257ae01000095df82b000000000" rawBlock = "00000000b7def681f0080262aa293071c53b41fc3146b196067243700b68acd059734fd19543108bf9ddc738cbee2ed1160f153aa0d057f062de0aa3cbb64ba88735c23d43667e59543f050095df82b02e324c5ff3812db982f3b0089a21a278988efeec6a027b2501fd450140113ac66657c2f544e8ad13905fcb2ebaadfef9502cbefb07960fbe56df098814c223dcdd3d0efa0b43a9459e654d948516dcbd8b370f50fbecfb8b411d48051a408500ce85591e516525db24065411f6a88f43de90fa9c167c2e6f5af43bc84e65e5a4bb174bc83a19b6965ff10f476b1b151ae15439a985f33916abc6822b0bb140f4aae522ffaea229987a10d01beec826c3b9a189fe02aa82680581b78f3df0ea4d3f93ca8ea35ffc90f15f7db9017f92fafd9380d9ba3237973cf4313cf626fc40e30e50e3588bd047b39f478b59323868cd50c7ab54355d8245bf0f1988d37528f9bbfc68110cf917debbdbf1f4bdd02cdcccdc3269fdf18a6c727ee54b6934d840e43918dd1ec6123550ec37a513e72b34b2c2a3baa510dec3037cbef2fa9f6ed1e7ccd1f3f6e19d4ce2c0919af55249a970c2685217f75a5589cf9e54dff8449af155210209e7fd41dfb5c2f8dc72eb30358ac100ea8c72da18847befe06eade68cebfcb9210327da12b5c40200e9f65569476bbff2218da4f32548ff43b6387ec1416a231ee821034ff5ceeac41acf22cd5ed2da17a6df4dd8358fcb2bfb1a43208ad0feaab2746b21026ce35b29147ad09e4afe4ec4a7319095f08198fa8babbe3c56e970b143528d2221038dddc06ce687677a53d54f096d2591ba2302068cf123c1f2d75c2dddc542557921039dafd8571a641058ccc832c5e2111ea39b09c0bde36050914384f7a48bce9bf92102d02b1873a0863cd042cc717da31cea0d7cf9db32b74d4c72c01b0011503e2e2257ae01000095df82b000000000"
@ -82,7 +59,7 @@ func newBlockBase() BlockBase {
Index: 1, Index: 1,
ConsensusData: 1111, ConsensusData: 1111,
NextConsensus: util.Uint160{}, NextConsensus: util.Uint160{},
Script: &Witness{ Script: &transaction.Witness{
VerificationScript: []byte{0x0}, VerificationScript: []byte{0x0},
InvocationScript: []byte{0x1}, InvocationScript: []byte{0x1},
}, },
@ -104,9 +81,9 @@ func TestHashBlockEqualsHashHeader(t *testing.T) {
func TestBlockVerify(t *testing.T) { func TestBlockVerify(t *testing.T) {
block := &Block{ block := &Block{
BlockBase: newBlockBase(), BlockBase: newBlockBase(),
Transactions: []*Transaction{ Transactions: []*transaction.Transaction{
{Type: MinerTX}, {Type: transaction.MinerType},
{Type: IssueTX}, {Type: transaction.IssueType},
}, },
} }
@ -114,18 +91,18 @@ func TestBlockVerify(t *testing.T) {
t.Fatal("block should be verified") t.Fatal("block should be verified")
} }
block.Transactions = []*Transaction{ block.Transactions = []*transaction.Transaction{
{Type: IssueTX}, {Type: transaction.IssueType},
{Type: MinerTX}, {Type: transaction.MinerType},
} }
if block.Verify(false) { if block.Verify(false) {
t.Fatal("block should not by verified") t.Fatal("block should not by verified")
} }
block.Transactions = []*Transaction{ block.Transactions = []*transaction.Transaction{
{Type: MinerTX}, {Type: transaction.MinerType},
{Type: MinerTX}, {Type: transaction.MinerType},
} }
if block.Verify(false) { if block.Verify(false) {

View file

@ -5,6 +5,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
@ -31,9 +32,9 @@ func TestAddHeaders(t *testing.T) {
startHash, _ := util.Uint256DecodeString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099") startHash, _ := util.Uint256DecodeString("996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099")
bc := NewBlockchain(NewMemoryStore(), log.New(os.Stdout, "", 0), startHash) bc := NewBlockchain(NewMemoryStore(), log.New(os.Stdout, "", 0), startHash)
h1 := &Header{BlockBase: BlockBase{Version: 0, Index: 1, Script: &Witness{}}} h1 := &Header{BlockBase: BlockBase{Version: 0, Index: 1, Script: &transaction.Witness{}}}
h2 := &Header{BlockBase: BlockBase{Version: 0, Index: 2, Script: &Witness{}}} h2 := &Header{BlockBase: BlockBase{Version: 0, Index: 2, Script: &transaction.Witness{}}}
h3 := &Header{BlockBase: BlockBase{Version: 0, Index: 3, Script: &Witness{}}} h3 := &Header{BlockBase: BlockBase{Version: 0, Index: 3, Script: &transaction.Witness{}}}
if err := bc.AddHeaders(h1, h2, h3); err != nil { if err := bc.AddHeaders(h1, h2, h3); err != nil {
t.Fatal(err) t.Fatal(err)

View file

@ -6,6 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
@ -18,7 +19,7 @@ func TestHeaderEncodeDecode(t *testing.T) {
Index: 3445, Index: 3445,
ConsensusData: 394949, ConsensusData: 394949,
NextConsensus: util.Uint160{}, NextConsensus: util.Uint160{},
Script: &Witness{ Script: &transaction.Witness{
InvocationScript: []byte{0x10}, InvocationScript: []byte{0x10},
VerificationScript: []byte{0x11}, VerificationScript: []byte{0x11},
}, },
@ -51,7 +52,6 @@ func TestHeaderEncodeDecode(t *testing.T) {
if !header.NextConsensus.Equals(headerDecode.NextConsensus) { if !header.NextConsensus.Equals(headerDecode.NextConsensus) {
t.Fatalf("expected both next consensus fields to be equal") t.Fatalf("expected both next consensus fields to be equal")
} }
if bytes.Compare(header.Script.InvocationScript, headerDecode.Script.InvocationScript) != 0 { 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) t.Fatalf("expected equal invocation scripts %v and %v", header.Script.InvocationScript, headerDecode.Script.InvocationScript)
} }

View file

@ -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
}

View file

@ -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
)

View file

@ -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")
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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 <Type>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
}

View file

@ -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()))
}

View file

@ -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 ""
}
}

View file

@ -1,4 +1,4 @@
package core package transaction
import ( import (
"encoding/binary" "encoding/binary"
@ -7,7 +7,7 @@ import (
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
// Witness ... // Witness contains 2 scripts.
type Witness struct { type Witness struct {
InvocationScript []byte InvocationScript []byte
VerificationScript []byte VerificationScript []byte
@ -17,17 +17,12 @@ type Witness struct {
func (wit *Witness) DecodeBinary(r io.Reader) error { func (wit *Witness) DecodeBinary(r io.Reader) error {
lenb := util.ReadVarUint(r) lenb := util.ReadVarUint(r)
wit.InvocationScript = make([]byte, lenb) wit.InvocationScript = make([]byte, lenb)
if err := binary.Read(r, binary.LittleEndian, &wit.InvocationScript); err != nil { if err := binary.Read(r, binary.LittleEndian, wit.InvocationScript); err != nil {
panic(err) return err
} }
lenb = util.ReadVarUint(r) lenb = util.ReadVarUint(r)
wit.VerificationScript = make([]byte, lenb) wit.VerificationScript = make([]byte, lenb)
if err := binary.Read(r, binary.LittleEndian, &wit.VerificationScript); err != nil { return binary.Read(r, binary.LittleEndian, wit.VerificationScript)
panic(err)
}
return nil
} }
// EncodeBinary implements the payload interface. // EncodeBinary implements the payload interface.

View file

@ -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 ""
}
}

View file

@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/CityOfZion/neo-go/pkg/core" "github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
) )
func TestHeadersEncodeDecode(t *testing.T) { func TestHeadersEncodeDecode(t *testing.T) {
@ -14,7 +15,7 @@ func TestHeadersEncodeDecode(t *testing.T) {
BlockBase: core.BlockBase{ BlockBase: core.BlockBase{
Version: 0, Version: 0,
Index: 1, Index: 1,
Script: &core.Witness{ Script: &transaction.Witness{
InvocationScript: []byte{0x0}, InvocationScript: []byte{0x0},
VerificationScript: []byte{0x1}, VerificationScript: []byte{0x1},
}, },
@ -23,7 +24,7 @@ func TestHeadersEncodeDecode(t *testing.T) {
BlockBase: core.BlockBase{ BlockBase: core.BlockBase{
Version: 0, Version: 0,
Index: 2, Index: 2,
Script: &core.Witness{ Script: &transaction.Witness{
InvocationScript: []byte{0x0}, InvocationScript: []byte{0x0},
VerificationScript: []byte{0x1}, VerificationScript: []byte{0x1},
}, },
@ -32,7 +33,7 @@ func TestHeadersEncodeDecode(t *testing.T) {
BlockBase: core.BlockBase{ BlockBase: core.BlockBase{
Version: 0, Version: 0,
Index: 3, Index: 3,
Script: &core.Witness{ Script: &transaction.Witness{
InvocationScript: []byte{0x0}, InvocationScript: []byte{0x0},
VerificationScript: []byte{0x1}, VerificationScript: []byte{0x1},
}, },

View file

@ -4,6 +4,6 @@ import "io"
// Payload is anything that can be binary encoded/decoded. // Payload is anything that can be binary encoded/decoded.
type Payload interface { type Payload interface {
EncodeBinary(io.Writer) error EncodeBinary(io.Writer) error
DecodeBinary(io.Reader) error DecodeBinary(io.Reader) error
} }

View file

@ -1,5 +1,4 @@
package smartcontract package smartcontract
// Contract represents a NEO smartcontract. // Contract represents a NEO smartcontract.
type Contract struct { type Contract struct{}
}

View file

@ -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{}

View file

@ -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
}

31
pkg/util/fixed8.go Normal file
View file

@ -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()
}

View file

@ -3,6 +3,8 @@ package util
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/CityOfZion/neo-go/pkg/crypto"
) )
const uint160Size = 20 const uint160Size = 20
@ -22,6 +24,16 @@ func Uint160DecodeString(s string) (u Uint160, err error) {
return Uint160DecodeBytes(b) 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. // Uint160DecodeBytes attempts to decode the given bytes into an Uint160.
func Uint160DecodeBytes(b []byte) (u Uint160, err error) { func Uint160DecodeBytes(b []byte) (u Uint160, err error) {
if len(b) != uint160Size { if len(b) != uint160Size {
@ -42,6 +54,13 @@ func (u Uint160) Bytes() []byte {
return b 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. // String implements the stringer interface.
func (u Uint160) String() string { func (u Uint160) String() string {
return hex.EncodeToString(u.Bytes()) return hex.EncodeToString(u.Bytes())

View file

@ -1 +1,22 @@
package util 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())
}
}

View file

@ -3,6 +3,8 @@ package util
import ( import (
"encoding/hex" "encoding/hex"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestUint256DecodeString(t *testing.T) { func TestUint256DecodeString(t *testing.T) {
@ -11,7 +13,7 @@ func TestUint256DecodeString(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log(val) assert.Equal(t, hexStr, val.String())
} }
func TestUint256DecodeBytes(t *testing.T) { func TestUint256DecodeBytes(t *testing.T) {
@ -24,9 +26,7 @@ func TestUint256DecodeBytes(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if val.String() != hexStr { assert.Equal(t, hexStr, val.String())
t.Fatalf("expected %s and %s to be equal", val, hexStr)
}
} }
func TestUInt256Equals(t *testing.T) { func TestUInt256Equals(t *testing.T) {

View file

@ -57,11 +57,6 @@ func emitBytes(w *bytes.Buffer, b []byte) error {
n = len(b) 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) { if n <= int(vm.Opushbytes75) {
return emit(w, vm.Opcode(n), b) return emit(w, vm.Opcode(n), b)
} else if n < 0x100 { } else if n < 0x100 {

View file

@ -39,7 +39,7 @@ const (
Ojmpifnot Opcode = 0x64 Ojmpifnot Opcode = 0x64
Ocall Opcode = 0x65 Ocall Opcode = 0x65
Oret Opcode = 0x66 Oret Opcode = 0x66
Opcall Opcode = 0x67 Oappcall Opcode = 0x67
Osyscall Opcode = 0x68 Osyscall Opcode = 0x68
Otailcall Opcode = 0x69 Otailcall Opcode = 0x69

149
pkg/vm/script_builder.go Normal file
View file

@ -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
}

View file

@ -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))
}

View file

@ -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
}