From 414172c8d7e3978d202285a9e49499af05c9f70a Mon Sep 17 00:00:00 2001 From: Apisit Toompakdee Date: Wed, 17 Oct 2018 17:51:39 +0900 Subject: [PATCH] several things... --- neoutils/deploy_test.go | 64 +++++ neoutils/deployment.go | 135 +++++++++++ neoutils/mobile.go | 22 ++ neoutils/mobile_test.go | 66 ++++- neoutils/multisig.go | 18 +- neoutils/multisig_test.go | 15 +- neoutils/native_asset.go | 4 - neoutils/native_asset_test.go | 20 +- neoutils/neowallet_test.go | 14 ++ neoutils/nep5.go | 2 +- neoutils/nep5_test.go | 10 +- neoutils/nns_test.go | 59 +++++ neoutils/ont.go | 3 +- neoutils/ont_test.go | 32 ++- neoutils/serializer.go | 107 +++++++++ neoutils/serializer_test.go | 24 ++ neoutils/smartcontract/param.go | 26 ++ neoutils/smartcontract/property.go | 10 + neoutils/smartcontract/scriptbuilder.go | 240 ++++++++++++++++++- neoutils/smartcontract/scriptbuilder_test.go | 8 + neoutils/smartcontract/transaction.go | 38 ++- neoutils/smartcontract_test.go | 230 +++++++++++++++++- neoutils/utils_test.go | 12 +- neoutils/version.go | 8 +- neoutils/wallet_test.go | 1 + 25 files changed, 1088 insertions(+), 80 deletions(-) create mode 100644 neoutils/deploy_test.go create mode 100644 neoutils/deployment.go create mode 100644 neoutils/nns_test.go create mode 100644 neoutils/serializer.go create mode 100644 neoutils/serializer_test.go create mode 100644 neoutils/smartcontract/param.go create mode 100644 neoutils/smartcontract/property.go create mode 100644 neoutils/wallet_test.go diff --git a/neoutils/deploy_test.go b/neoutils/deploy_test.go new file mode 100644 index 0000000..b3011bb --- /dev/null +++ b/neoutils/deploy_test.go @@ -0,0 +1,64 @@ +package neoutils_test + +import ( + "bytes" + "log" + "testing" + + "github.com/o3labs/neo-utils/neoutils" + "github.com/o3labs/neo-utils/neoutils/smartcontract" +) + +func TestDeploy(t *testing.T) { + + contract := neoutils.SmartContractInfo{ + AVMHEX: "54c56b6c766b00527ac46c766b51527ac46168164e656f2e52756e74696d652e47657454726967676572635e0061149ba59c6b61d40aecf939c72cf1b6d21bba346dc2c00114907c907c9e63380061149ba59c6b61d40aecf939c72cf1b6d21bba346dc26168184e656f2e52756e74696d652e436865636b5769746e657373616c756600616c75666168164e656f2e52756e74696d652e4765745472696767657260907c907c9e630b016c766b00c3066465706c6f7987640b0061652501616c75666c766b00c30b746f74616c537570706c7987640b0061655b02616c75666c766b00c3046e616d6587640b006165c900616c75666c766b00c30673796d626f6c87640b006165c700616c75666c766b00c3087472616e7366657287644a006c766b51c3c0539c63080000616c75666c766b51c300c36c766b51c351c36c766b52527ac46c766b51c352c36c766b53527ac46c766b52c36c766b53c3615272651302616c75666c766b00c30962616c616e63654f66876422006c766b51c3c0519c63080000616c75666c766b51c300c36165d103616c75666c766b00c308646563696d616c7387640b0061652d00616c756600616c756600c56b0e5374656d2043656c6c20436f696e616c756600c56b03534343616c756600c56b58616c756600c56b6114f106089975d5964c41b5c80136814257665312326168184e656f2e52756e74696d652e436865636b5769746e65737363080000616c75666168164e656f2e53746f726167652e476574436f6e746578740b746f74616c537570706c79617c680f4e656f2e53746f726167652e476574c064080000616c75666168164e656f2e53746f726167652e476574436f6e7465787461149ba59c6b61d40aecf939c72cf1b6d21bba346dc2080000b2d3595bf006615272680f4e656f2e53746f726167652e5075746168164e656f2e53746f726167652e476574436f6e746578740b746f74616c537570706c79080000b2d3595bf006615272680f4e656f2e53746f726167652e507574610061149ba59c6b61d40aecf939c72cf1b6d21bba346dc2080000b2d3595bf006615272087472616e7366657254c168124e656f2e52756e74696d652e4e6f7469667951616c756600c56b6168164e656f2e53746f726167652e476574436f6e746578740b746f74616c537570706c79617c680f4e656f2e53746f726167652e476574616c756655c56b6c766b00527ac46c766b51527ac46c766b52527ac46c766b52c300a164080000616c75666c766b00c36168184e656f2e52756e74696d652e436865636b5769746e65737363080000616c75666c766b51c3c001149c63080000616c75666168164e656f2e53746f726167652e476574436f6e746578746c766b00c3617c680f4e656f2e53746f726167652e4765746c766b53527ac46c766b53c36c766b52c39f64080000616c75666c766b00c36c766b51c3907c907c9e63080051616c75666c766b53c36c766b52c39c643a006168164e656f2e53746f726167652e476574436f6e746578746c766b00c3617c68124e656f2e53746f726167652e44656c6574656240006168164e656f2e53746f726167652e476574436f6e746578746c766b00c36c766b53c36c766b52c394615272680f4e656f2e53746f726167652e5075746168164e656f2e53746f726167652e476574436f6e746578746c766b51c3617c680f4e656f2e53746f726167652e4765746c766b54527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b51c36c766b54c36c766b52c393615272680f4e656f2e53746f726167652e507574616c766b00c36c766b51c36c766b52c3615272087472616e7366657254c168124e656f2e52756e74696d652e4e6f7469667951616c756651c56b6c766b00527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b00c3617c680f4e656f2e53746f726167652e476574616c7566", + Name: "Stem Cell Coin", + Version: "1.0", + Author: "MTA Intec PTE LTD", + Email: "info@stemcell-pj.com", + Description: "SCC Token - https://www.stemcell-pj.net/", + InputTypes: []smartcontract.ParameterType{smartcontract.String, smartcontract.Array}, + ReturnType: smartcontract.ByteArray, + Properties: smartcontract.HasStorage + smartcontract.Payable, + } + + log.Printf("sc hash %v", contract.GetScriptHash()) + + asset := smartcontract.GAS + amount := float64(490) + + encryptedKey := "" + passphrase := "" + wif, _ := neoutils.NEP2Decrypt(encryptedKey, passphrase) + + privateNetwallet, err := neoutils.GenerateFromWIF(wif) + if err != nil { + log.Printf("%v", err) + t.Fail() + return + } + log.Printf("wallet address %v", privateNetwallet.Address) + + unspent, err := utxo("main", privateNetwallet.Address) + log.Printf("unspent %+v", unspent) + if err != nil { + log.Printf("error %v", err) + t.Fail() + return + } + attributes := map[smartcontract.TransactionAttribute][]byte{} + tx, err := neoutils.DeploySmartContractScript(contract, *privateNetwallet, asset, amount, unspent, attributes) + if err != nil { + log.Printf("error %v", err) + return + } + log.Printf("tx %x", tx) + +} + +func TestVarInt(t *testing.T) { + buff := new(bytes.Buffer) + neoutils.WriteVarUint(buff, uint64(286)) + log.Printf("%x", buff.Bytes()) +} diff --git a/neoutils/deployment.go b/neoutils/deployment.go new file mode 100644 index 0000000..cdc8eb6 --- /dev/null +++ b/neoutils/deployment.go @@ -0,0 +1,135 @@ +package neoutils + +import ( + "bytes" + "encoding/binary" + "io" + "log" + + "github.com/o3labs/neo-utils/neoutils/smartcontract" +) + +func WriteVarUint(w io.Writer, val uint64) error { + if val < 0xfd { + binary.Write(w, binary.LittleEndian, uint8(val)) + return nil + } + if val < 0xFFFF { + binary.Write(w, binary.LittleEndian, byte(0xfd)) + binary.Write(w, binary.LittleEndian, uint16(val)) + return nil + } + if val < 0xFFFFFFFF { + binary.Write(w, binary.LittleEndian, byte(0xfe)) + binary.Write(w, binary.LittleEndian, uint32(val)) + return nil + } + + binary.Write(w, binary.LittleEndian, byte(0xff)) + binary.Write(w, binary.LittleEndian, val) + + return nil +} + +type SmartContractInfo struct { + AVMHEX string + Name string + Version string + Author string + Email string + Description string + Properties smartcontract.Properties + InputTypes []smartcontract.ParameterType + ReturnType smartcontract.ParameterType +} + +func (s *SmartContractInfo) GetScriptHash() string { + address := VMCodeToNEOAddress(hex2bytes(s.AVMHEX)) + scripthash := NEOAddressToScriptHashWithEndian(address, binary.BigEndian) + return scripthash +} + +func (s *SmartContractInfo) Serialize() []byte { + + params := []byte{} + for _, p := range s.InputTypes { + params = append(params, p.Byte()) + } + + scriptBuilder := smartcontract.NewScriptBuilder() + scriptBuilder.Push([]byte(s.Description)) + scriptBuilder.Push([]byte(s.Email)) + scriptBuilder.Push([]byte(s.Author)) + scriptBuilder.Push([]byte(s.Version)) + scriptBuilder.Push([]byte(s.Name)) + scriptBuilder.Push(int(s.Properties)) + scriptBuilder.Push([]byte{s.ReturnType.Byte()}) + scriptBuilder.Push(params) + scriptBuilder.PushVarData(hex2bytes(s.AVMHEX)) + scriptBuilder.PushSysCall("Neo.Contract.Create") + + b := scriptBuilder.ToBytes() + buff := new(bytes.Buffer) + WriteVarUint(buff, uint64(len(b))) + endPayload := []byte{} + endPayload = append(endPayload, buff.Bytes()...) + endPayload = append(endPayload, b...) + return endPayload +} + +func DeploySmartContractScript(contractInfo SmartContractInfo, wallet Wallet, asset smartcontract.NativeAsset, amount float64, unspent smartcontract.Unspent, attributes map[smartcontract.TransactionAttribute][]byte) ([]byte, error) { + + tx := smartcontract.NewInvocationTransactionPayable() + + tx.Data = contractInfo.Serialize() + tx.GAS = uint64(490) + + amountToSend := amount + assetToSend := asset + + networkFee := smartcontract.NetworkFeeAmount(0) + + txInputs, err := smartcontract.NewScriptBuilder().GenerateTransactionInput(unspent, assetToSend, amountToSend, networkFee) + if err != nil { + return nil, err + } + tx.Inputs = txInputs + + txAttributes, err := smartcontract.NewScriptBuilder().GenerateTransactionAttributes(attributes) + if err != nil { + return nil, err + } + tx.Attributes = txAttributes + + //when deploy smart contract, you don't actually send asset to another address + //so the receiver is the same address + sender := smartcontract.ParseNEOAddress(wallet.Address) + receiver := smartcontract.ParseNEOAddress(wallet.Address) + txOutputs, err := smartcontract.NewScriptBuilder().GenerateTransactionOutputPayableGAS(sender, receiver, unspent, assetToSend, amount, networkFee, float64(tx.GAS)) + if err != nil { + return nil, err + } + + tx.Outputs = txOutputs + + //begin signing process and invocation script + privateKeyInHex := bytesToHex(wallet.PrivateKey) + signedData, err := Sign(tx.ToBytes(), privateKeyInHex) + if err != nil { + return nil, err + } + + signature := smartcontract.TransactionSignature{ + SignedData: signedData, + PublicKey: wallet.PublicKey, + } + + scripts := []interface{}{signature} + txScripts := smartcontract.NewScriptBuilder().GenerateVerificationScripts(scripts) + tx.Script = txScripts + //end signing process + + log.Printf("txid = %v", tx.ToTXID()) + + return tx.ToBytes(), nil +} diff --git a/neoutils/mobile.go b/neoutils/mobile.go index a3d8f65..2f4f9f5 100644 --- a/neoutils/mobile.go +++ b/neoutils/mobile.go @@ -1,8 +1,11 @@ package neoutils import ( + "bytes" + "encoding/binary" "encoding/json" "fmt" + "log" "strconv" "strings" @@ -122,3 +125,22 @@ func GenerateNEP6FromEncryptedKey(walletName, addressLabel, address, encryptedKe } return string(b) } + +func SerializeTX(jsonString string) []byte { + tx := NeonJSTransaction{} + json.Unmarshal([]byte(jsonString), &tx) + log.Printf("%+v", tx) + final := NeonJSTXSerializer(tx) + return final +} + +func NEOAddresstoScriptHashBigEndian(neoAddress string) string { + return NEOAddressToScriptHashWithEndian(neoAddress, binary.BigEndian) +} + +func GetVarUInt(value int64) []byte { + buff := new(bytes.Buffer) + WriteVarUint(buff, uint64(value)) + + return buff.Bytes() +} diff --git a/neoutils/mobile_test.go b/neoutils/mobile_test.go index 8a7738e..f7a1936 100644 --- a/neoutils/mobile_test.go +++ b/neoutils/mobile_test.go @@ -10,19 +10,27 @@ import ( ) func TestMintTokensFromMobile(t *testing.T) { - scriptHash := "0x3e390ae61acb6713389c8fbbd47d1d69c32655a3" + scriptHash := "9121e89e8a0849857262d67c8408601b5e8e0524" + // encryptedKey := "" + // passphrase := "" + // wif, _ := neoutils.NEP2Decrypt(encryptedKey, passphrase) wif := "" - wallet, _ := neoutils.GenerateFromWIF(wif) - log.Printf("address = %v\n address hash = %x", wallet.Address, wallet.HashedSignature) + wallet, err := neoutils.GenerateFromWIF(wif) + if err != nil { + log.Printf("%v", err) + t.Fail() + return + } - neo := string(smartcontract.NEO) - // gas := string(smartcontract.GAS) - amount := float64(2) - remark := "O3XMOONLIGHT2" - network := "test" + log.Printf("address = %v\n address hash = %x", wallet.Address, wallet.HashedSignature) + // neo := string(smartcontract.NEO) + gas := string(smartcontract.GAS) + amount := float64(1) + remark := "FIRST! APISIT FROM O3 :D" + network := "main" networkFeeAmountInGAS := float64(0) - tx, err := neoutils.MintTokensRawTransactionMobile(network, scriptHash, wif, neo, amount, remark, networkFeeAmountInGAS) + tx, err := neoutils.MintTokensRawTransactionMobile(network, scriptHash, wif, gas, amount, remark, networkFeeAmountInGAS) if err != nil { log.Printf("%v", err) t.Fail() @@ -48,3 +56,43 @@ func TestNEP6MobileMethod(t *testing.T) { log.Printf("%+v", nep6Wallet) } + +func TestSerializeTX(t *testing.T) { + data := ` +{ + "sha256": "ab1ad3efa1bf2fca51219b73c676dadaf9f446b81acd72f2557fecb7a8e7d243", + "type": 209, + "attributes": [{ + "usage": 32, + "data": "4d17abe11020df91ce627af28c03c9c0cfb2a6c4" + }], + "scripts": [], + "gas": 0, + "version": 1, + "hash": "a67d3f9314383c4f7234cc9c8b7cf50602f91bf908bf6496a0c81bdc37fac7da", + "inputs": [{ + "prevIndex": 0, + "prevHash": "d21043bb53d70a4762ebad4fcd55fb00528f4898d97cbe4182aef5b91139ec60" + }, { + "prevIndex": 6, + "prevHash": "6005967b1f6697d03cf241995fd4b2e71e56945ce0e4f815033700b993150c15" + }], + "outputs": [{ + "assetId": "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", + "scriptHash": "e707714512577b42f9a011f8b870625429f93573", + "value": 1e-08 + }], + "script": "0800e1f505000000001432e125258b7db0a0dffde5bd03b2b859253538ab144d17abe11020df91ce627af28c03c9c0cfb2a6c453c1076465706f73697467823b63e7c70a795a7615a38d1ba67d9e54c195a1" +} +` + final := neoutils.SerializeTX(data) + log.Printf("%x", final) +} + +func TestNEOAddresstoScriptHashBigEndian(t *testing.T) { + log.Printf("%v", neoutils.NEOAddresstoScriptHashBigEndian("AQV8FNNi2o7EtMNn4etWBYx1cqBREAifgE")) +} + +func TestGetVarUInt(t *testing.T) { + log.Printf("%x", neoutils.GetVarUInt(286)) +} diff --git a/neoutils/multisig.go b/neoutils/multisig.go index 2bbe695..7918db4 100644 --- a/neoutils/multisig.go +++ b/neoutils/multisig.go @@ -10,25 +10,29 @@ import ( ) type MultiSigInterface interface { - CreateMultiSigRedeemScript(numerOfRequiredSignature int, publicKeys [][]byte) ([]byte, error) + CreateMultiSigRedeemScript() ([]byte, error) } -type MultiSig struct{} +type MultiSig struct { + NumberOfRequiredSignatures int + PublicKeys [][]byte +} var _ MultiSigInterface = (*MultiSig)(nil) -func (m *MultiSig) CreateMultiSigRedeemScript(numerOfRequiredSignature int, publicKeys [][]byte) ([]byte, error) { - numberOfPublicKeys := len(publicKeys) +func (m *MultiSig) CreateMultiSigRedeemScript() ([]byte, error) { + + numberOfPublicKeys := len(m.PublicKeys) if numberOfPublicKeys <= 1 { return nil, fmt.Errorf("Number of required Signature must be more than one") } - if numerOfRequiredSignature > numberOfPublicKeys { + if m.NumberOfRequiredSignatures > numberOfPublicKeys { return nil, fmt.Errorf("Number of required Signature is more than public keys provided.") } //sort public key keys := []btckey.PublicKey{} - for _, pb := range publicKeys { + for _, pb := range m.PublicKeys { publicKey := btckey.PublicKey{} publicKey.FromBytes(pb) keys = append(keys, publicKey) @@ -38,7 +42,7 @@ func (m *MultiSig) CreateMultiSigRedeemScript(numerOfRequiredSignature int, publ sort.SliceStable(keys, func(i, j int) bool { return keys[i].Point.X.Cmp(keys[j].Point.X) == -1 }) sb := smartcontract.NewScriptBuilder() - sb.Push(numerOfRequiredSignature) + sb.Push(m.NumberOfRequiredSignatures) for _, publicKey := range keys { sb.Push(publicKey.ToBytes()) } diff --git a/neoutils/multisig_test.go b/neoutils/multisig_test.go index 42c7dc0..3e35a01 100644 --- a/neoutils/multisig_test.go +++ b/neoutils/multisig_test.go @@ -10,17 +10,20 @@ import ( ) func TestGenerateMultiSigAddress(t *testing.T) { - - pb1 := "02e77ff280db51ef3638009f11947c544ed094d4e5f2d96a9e654dc817bc3a8986" - pb2 := "024da93f9a66981e499b36ce763e57fd89a47a052e86d40b42f81708c40fe9eff0" - require := 2 + // 1/2 + pb1 := "024e543aee592c4dd2361f8e02b4275e18eb665bcfb1c4b6c09bc6aed125b2f13c" + pb2 := "030adab68b3eeb02734f65b8ced64f023e70c15bcdfae94c3e74b9d647ddf9c976" + require := 1 pubKeys := [][]byte{} pubKeys = append(pubKeys, neoutils.HexTobytes(pb1)) pubKeys = append(pubKeys, neoutils.HexTobytes(pb2)) - multisign := neoutils.MultiSig{} - vmCode, err := multisign.CreateMultiSigRedeemScript(require, pubKeys) + multisign := neoutils.MultiSig{ + NumberOfRequiredSignatures: require, + PublicKeys: pubKeys, + } + vmCode, err := multisign.CreateMultiSigRedeemScript() if err != nil { log.Printf("%v", err) } diff --git a/neoutils/native_asset.go b/neoutils/native_asset.go index 0bc4c60..12d47ef 100644 --- a/neoutils/native_asset.go +++ b/neoutils/native_asset.go @@ -41,10 +41,6 @@ func (n *NativeAsset) SendNativeAssetRawTransaction(wallet Wallet, asset smartco SignedData: signedData, PublicKey: wallet.PublicKey, } - // try to verify it - // hash := sha256.Sum256(tx.ToBytes()) - // valid := Verify(wallet.PublicKey, signedData, hash[:]) - // log.Printf("verify tx %v", valid) scripts := []interface{}{signature} txScripts := smartcontract.NewScriptBuilder().GenerateVerificationScripts(scripts) diff --git a/neoutils/native_asset_test.go b/neoutils/native_asset_test.go index c72689a..efb69fe 100644 --- a/neoutils/native_asset_test.go +++ b/neoutils/native_asset_test.go @@ -89,9 +89,9 @@ func TestSendingGAS(t *testing.T) { t.Fail() return } - asset := smartcontract.GAS - amount := float64(0.1) - toAddress := "AKo8k27H5nCG8MwSirmnraH6uUG6fQQVC2" //this is multi signature adddress 3/2 + asset := smartcontract.NEO + amount := float64(3) + toAddress := "ANoW2zD8HmhbWJAjL4yKJWCZcF2WFb1ire" //this is multi signature adddress 3/2 to := smartcontract.ParseNEOAddress(toAddress) // remark := "O3TX" attributes := map[smartcontract.TransactionAttribute][]byte{} @@ -143,17 +143,17 @@ func TestSendingNEO(t *testing.T) { } func TestSendingGASFromMultiSig(t *testing.T) { - fromAddress := "AKo8k27H5nCG8MwSirmnraH6uUG6fQQVC2" //this is multi signature adddress 3/2 + fromAddress := "AFrFrNjKKLc6vEztHeDhNmqpdHuciKzBqt" //this is multi signature adddress 3/2 unspent, err := utxoFromO3Platform("test", fromAddress) if err != nil { log.Printf("error %v", err) t.Fail() return } - asset := smartcontract.GAS - amount := float64(0.1) + asset := smartcontract.NEO + amount := float64(1) - toAddress := "AKo8k27H5nCG8MwSirmnraH6uUG6fQQVC2" + toAddress := "ANovQs3YXipL4HxRmj4D62YLCLEGsK7iDG" to := smartcontract.ParseNEOAddress(toAddress) attributes := map[smartcontract.TransactionAttribute][]byte{} @@ -170,9 +170,9 @@ func TestSendingGASFromMultiSig(t *testing.T) { log.Printf("raw %x\n", rawtx) wallet1, _ := neoutils.GenerateFromWIF("") - wallet2, _ := neoutils.GenerateFromWIF("") + // wallet2, _ := neoutils.GenerateFromWIF("") - wallets := []*neoutils.Wallet{wallet1, wallet2} + wallets := []*neoutils.Wallet{wallet1} signatures := []smartcontract.TransactionSignature{} @@ -205,7 +205,7 @@ func TestSendingGASFromMultiSig(t *testing.T) { endPayload = append(endPayload, rawtx...) endPayload = append(endPayload, verificationScripts...) - redeemScript := "5221024da93f9a66981e499b36ce763e57fd89a47a052e86d40b42f81708c40fe9eff02102e77ff280db51ef3638009f11947c544ed094d4e5f2d96a9e654dc817bc3a898652ae" + redeemScript := "5121030adab68b3eeb02734f65b8ced64f023e70c15bcdfae94c3e74b9d647ddf9c97621024e543aee592c4dd2361f8e02b4275e18eb665bcfb1c4b6c09bc6aed125b2f13c52ae" b := neoutils.HexTobytes(redeemScript) length := len(b) log.Printf("%x%x%v", endPayload, length, redeemScript) diff --git a/neoutils/neowallet_test.go b/neoutils/neowallet_test.go index 14d484f..5f0ed41 100644 --- a/neoutils/neowallet_test.go +++ b/neoutils/neowallet_test.go @@ -93,3 +93,17 @@ func TestRecoverFromString(t *testing.T) { } fmt.Printf("%v", recovered) } + +func TestSign(t *testing.T) { + wif := "" + + wallet, _ := neoutils.GenerateFromWIF(wif) + log.Printf("%x", wallet.PrivateKey) + key := wallet.PrivateKey + keyString := neoutils.BytesToHex(key) + data := "abc" + b := neoutils.HexTobytes(data) + // log.Printf("%v", keyString) + result, _ := neoutils.Sign(b, keyString) + log.Printf("result %x", result) +} diff --git a/neoutils/nep5.go b/neoutils/nep5.go index b4e1cf1..77d9e35 100644 --- a/neoutils/nep5.go +++ b/neoutils/nep5.go @@ -120,7 +120,7 @@ func (n *NEP5) TransferNEP5RawTransaction(wallet Wallet, toAddress smartcontract func (n *NEP5) MintTokensRawTransaction(wallet Wallet, assetToSend smartcontract.NativeAsset, amount float64, unspent smartcontract.Unspent, remark string) ([]byte, string, error) { - needVerification := true + needVerification := false operation := "mintTokens" args := []interface{}{} attributes := map[smartcontract.TransactionAttribute][]byte{} diff --git a/neoutils/nep5_test.go b/neoutils/nep5_test.go index e2187c6..7a7ccb5 100644 --- a/neoutils/nep5_test.go +++ b/neoutils/nep5_test.go @@ -14,8 +14,8 @@ import ( ) func TestMintTokens(t *testing.T) { - scripthash := "55d8d97603701a34f1bda8c30777c8c04deefe55" - fee := smartcontract.NetworkFeeAmount(0.001) + scripthash := "" + fee := smartcontract.NetworkFeeAmount(0) nep5 := neoutils.UseNEP5WithNetworkFee(scripthash, fee) wif := "" @@ -27,7 +27,7 @@ func TestMintTokens(t *testing.T) { } unspent := smartcontract.Unspent{} - remark := "O3TX" + remark := "APISIT FROM O3 IS HERE." asset := smartcontract.NEO amount := float64(10) @@ -108,8 +108,8 @@ func utxo(network string, address string) (smartcontract.Unspent, error) { func TestTransferNEP5PrivateNet(t *testing.T) { //this is NNC token - scripthash := "fc732edee1efdf968c23c20a9628eaa5a6ccb934" - fee := smartcontract.NetworkFeeAmount(0.00000001) + scripthash := "0xe8fe7fbaf639722e577a1961b9cc1d43572ed6c3" + fee := smartcontract.NetworkFeeAmount(0) nep5 := neoutils.UseNEP5WithNetworkFee(scripthash, fee) wif := "" diff --git a/neoutils/nns_test.go b/neoutils/nns_test.go new file mode 100644 index 0000000..143112a --- /dev/null +++ b/neoutils/nns_test.go @@ -0,0 +1,59 @@ +package neoutils_test + +import ( + "crypto/sha256" + "log" + "testing" + + "github.com/o3labs/neo-utils/neoutils" + "github.com/o3labs/neo-utils/neoutils/smartcontract" +) + +func StringToNNSNameHash(v string) []byte { + hash := sha256.Sum256([]byte(v)) + + return neoutils.ReverseBytes(hash[:]) +} + +func NameHash(domain string, subdomain string) []byte { + root := sha256.Sum256([]byte(domain)) + sub := sha256.Sum256([]byte(subdomain)) + full := append(sub[:], root[:]...) + hash := sha256.Sum256(full) + return hash[:] +} + +func TestNNSHash(t *testing.T) { + log.Printf("%x", StringToNNSNameHash("neo")) +} + +// o3.neo = 231420791e6cfa9b6eee03f497cf0c24589435a92ab7b7b232a5951088afa3d6 + +func TestNameHash(t *testing.T) { + // expected := "b8e55096b5871c43375881259c5664a366b67671e9ede571d369aee4a5188bc0" + + result := NameHash("neo", "o3") + log.Printf("%x", neoutils.ReverseBytes(result)) +} +func TestNNSResolver(t *testing.T) { + //0020d86aa99be2f6689dc767c13be2f6a40098511b3c039d4266228656d79d1c64c6 + //046164647253c1077265736f6c766567c72871904920c0d977326620e4754a6c11878334 + + //0020d86aa99be2f6689dc767c13be2f6a40098511b3c039d4266228656d79d1c64c6 + //046164647253c1077265736f6c766567c72871904920c0d977326620e4754a6c11878334 + // + namehash := NameHash("neo", "o3") + log.Printf("namehash = %x", namehash) + scriptHash, err := smartcontract.NewScriptHash("348387116c4a75e420663277d9c02049907128c7") + if err != nil { + log.Printf("err = %v", err) + t.Fail() + return + } + // json := fmt.Sprintf("%x", result) + args := []interface{}{[]byte("addr"), namehash, []byte("")} + s := smartcontract.NewScriptBuilder() + s.GenerateContractInvocationScript(scriptHash, "resolve", args) + + log.Printf("%x", s.ToBytes()) +} diff --git a/neoutils/ont.go b/neoutils/ont.go index 9d9d671..182d742 100644 --- a/neoutils/ont.go +++ b/neoutils/ont.go @@ -2,6 +2,7 @@ package neoutils import ( "fmt" + "log" "github.com/o3labs/ont-mobile/ontmobile" ) @@ -11,7 +12,7 @@ func OntologyTransfer(endpoint string, gasPrice int, gasLimit int, wif string, a if err != nil { return "", err } - + log.Printf("raw = %x", raw.Data) txid, err := ontmobile.SendRawTransaction(endpoint, fmt.Sprintf("%x", raw.Data)) if err != nil { return "", err diff --git a/neoutils/ont_test.go b/neoutils/ont_test.go index b6adebf..3f4f898 100644 --- a/neoutils/ont_test.go +++ b/neoutils/ont_test.go @@ -2,25 +2,33 @@ package neoutils_test import ( "log" + "math" "testing" "github.com/o3labs/neo-utils/neoutils" ) func TestONTTransfer(t *testing.T) { - endpoint := "http://dappnode2.ont.io:20336" - wif := "" - asset := "ont" - to := "AcydXy1MvrzaT8qD3Qe4B8mqEoinTvRy8U" - amount := float64(2) - gasPrice := int(500) - gasLimit := int(20000) - txid, err := neoutils.OntologyTransfer(endpoint, gasPrice, gasLimit, wif, asset, to, amount) - if err != nil { - log.Printf("err %v", err) - return + + for i := 1; i <= 100; i++ { + + endpoint := "http://polaris1.ont.io:20336" + //pass := "" + //wif, _ := neoutils.NEP2Decrypt("", pass) + wif := "" + asset := "ong" + to := "AcWfHYbPDt4ysz7s5WQtkGvcFsfTsM6anm" + + amount := float64(float64(i) / math.Pow10(9)) + gasPrice := int(500) + gasLimit := int(20000) + txid, err := neoutils.OntologyTransfer(endpoint, gasPrice, gasLimit, wif, asset, to, amount) + if err != nil { + log.Printf("err %v", err) + return + } + log.Printf("tx id =%v", txid) } - log.Printf("tx id =%v", txid) } func TestClaimONG(t *testing.T) { diff --git a/neoutils/serializer.go b/neoutils/serializer.go new file mode 100644 index 0000000..0d0a94f --- /dev/null +++ b/neoutils/serializer.go @@ -0,0 +1,107 @@ +package neoutils + +import ( + "log" + "strconv" + + "github.com/o3labs/neo-utils/neoutils/smartcontract" +) + +type NeonJSTransaction struct { + Sha256 string `json:"sha256"` + Hash string `json:"hash"` + Inputs []struct { + PrevIndex int `json:"prevIndex"` + PrevHash string `json:"prevHash"` + } `json:"inputs"` + Outputs []struct { + AssetID string `json:"assetId"` + ScriptHash string `json:"scriptHash"` + Value interface{} `json:"value"` + } `json:"outputs"` + Script string `json:"script"` + Version int `json:"version"` + Type int `json:"type"` + Attributes []struct { + Usage int `json:"usage"` + Data string `json:"data"` + } `json:"attributes"` + Scripts []interface{} `json:"scripts"` + Gas int `json:"gas"` +} + +func NeonJSTXSerializer(tx NeonJSTransaction) []byte { + + hexType := strconv.FormatInt(int64(tx.Type), 16) + + transaction := smartcontract.NewTransactionWithType(hex2bytes(hexType)[0], tx.Version) + //inputs + inputs := []smartcontract.UTXO{} + for _, v := range tx.Inputs { + input := smartcontract.UTXO{ + Index: v.PrevIndex, + TXID: v.PrevHash, + } + inputs = append(inputs, input) + } + + inputBuilder := smartcontract.NewScriptBuilder() + inputBuilder.PushLength(len(inputs)) + for _, input := range inputs { + inputBuilder.Push(input) + } + transaction.Inputs = inputBuilder.ToBytes() + //end inputs + + //output + outputs := []smartcontract.TransactionOutput{} + for _, v := range tx.Outputs { + address := smartcontract.NEOAddressFromScriptHash(HexTobytes(v.ScriptHash)) + valueFloat := float64(0) + switch a := v.Value.(type) { + case float64: + valueFloat = a + case string: + valueFloat, _ = strconv.ParseFloat(a, 64) + } + + output := smartcontract.TransactionOutput{ + Asset: smartcontract.NativeAssets[v.AssetID], + Value: int64(smartcontract.RoundFixed8(valueFloat) * float64(100000000)), + Address: address, + } + outputs = append(outputs, output) + } + + outputBuilder := smartcontract.NewScriptBuilder() + + outputBuilder.PushLength(len(outputs)) + for _, output := range outputs { + outputBuilder.Push(output) + } + transaction.Outputs = outputBuilder.ToBytes() + //end outputs + + //attributes + attributes := map[smartcontract.TransactionAttribute][]byte{} + for _, v := range tx.Attributes { + usageHex := strconv.FormatInt(int64(v.Usage), 16) + attr := smartcontract.TransactionAttribute(HexTobytes(usageHex)[0]) + attributes[attr] = hex2bytes(v.Data) + } + + txAttributes, err := smartcontract.NewScriptBuilder().GenerateTransactionAttributes(attributes) + + if err == nil { + transaction.Attributes = txAttributes + } + //end attributes + scriptBytes := HexTobytes(tx.Script) + + transaction.Data = append([]byte{byte(len(scriptBytes))}, scriptBytes...) + transaction.GAS = uint64(tx.Gas) + + log.Printf("tx id %v", transaction.ToTXID()) + + return transaction.ToBytes() +} diff --git a/neoutils/serializer_test.go b/neoutils/serializer_test.go new file mode 100644 index 0000000..50d9e4e --- /dev/null +++ b/neoutils/serializer_test.go @@ -0,0 +1,24 @@ +package neoutils_test + +import ( + "encoding/json" + "log" + "testing" + + "github.com/o3labs/neo-utils/neoutils" +) + +func TestSerialize(t *testing.T) { + + data := ` +{"sha256":"accb0534cb4c4d9b8594189d31e759ab96ae7488dc90e52a443b44bb2e2b2493","hash":"ef249c579898e3adaee6f4c5df8117cc08b8a2832bdd5978beeb859cef6620c9","inputs":[{"prevIndex":0,"prevHash":"3a963116b572a466819c05bee74782902a51fd9b83be99f25d9edc5b7891049a"},{"prevIndex":18,"prevHash":"b7e77a70481edc1d5156a182d358aac53da51b9f1653683ae7bcb811b779c759"}],"outputs":[{"assetId":"602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7","scriptHash":"e707714512577b42f9a011f8b870625429f93573","value":1e-08}],"script":"0800ca9a3b000000001432e125258b7db0a0dffde5bd03b2b859253538ab144d2c053c1a5911be6253b3cc1a68397feb3f647053c1076465706f73697467823b63e7c70a795a7615a38d1ba67d9e54c195a1","version":1,"type":209,"attributes":[{"usage":32,"data":"4d2c053c1a5911be6253b3cc1a68397feb3f6470"}],"scripts":[],"gas":0} +` + tx := neoutils.NeonJSTransaction{} + json.Unmarshal([]byte(data), &tx) + + final := neoutils.NeonJSTXSerializer(tx) + log.Printf("%x", final) + + w := WalletForSwitcheoTestNet() + log.Printf("%v", w.Address) +} diff --git a/neoutils/smartcontract/param.go b/neoutils/smartcontract/param.go new file mode 100644 index 0000000..a97e1d5 --- /dev/null +++ b/neoutils/smartcontract/param.go @@ -0,0 +1,26 @@ +package smartcontract + +type ParameterType byte + +const ( + Signature ParameterType = 0x00 + Boolean ParameterType = 0x01 + + Integer ParameterType = 0x02 + + Hash160 ParameterType = 0x03 + + Hash256 ParameterType = 0x04 + + ByteArray ParameterType = 0x05 + PublicKey ParameterType = 0x06 + String ParameterType = 0x07 + + Array ParameterType = 0x10 + InteropInterface ParameterType = 0xf0 + Void ParameterType = 0xff +) + +func (p ParameterType) Byte() byte { + return byte(p) +} diff --git a/neoutils/smartcontract/property.go b/neoutils/smartcontract/property.go new file mode 100644 index 0000000..53f9432 --- /dev/null +++ b/neoutils/smartcontract/property.go @@ -0,0 +1,10 @@ +package smartcontract + +type Properties int + +const ( + NoProperty Properties = 0 + HasStorage Properties = 1 << 0 + HasDynamicInvoke Properties = 1 << 1 + Payable Properties = 1 << 2 +) diff --git a/neoutils/smartcontract/scriptbuilder.go b/neoutils/smartcontract/scriptbuilder.go index 95fc025..aa07f70 100644 --- a/neoutils/smartcontract/scriptbuilder.go +++ b/neoutils/smartcontract/scriptbuilder.go @@ -50,7 +50,8 @@ type ScriptBuilderInterface interface { //this is to send the UTXO of asset that will be used in TransactionOutput GenerateTransactionInput(unspent Unspent, assetToSend NativeAsset, amountToSend float64, networkFeeAmount NetworkFeeAmount) ([]byte, error) GenerateTransactionOutput(sender NEOAddress, receiver NEOAddress, unspent Unspent, assetToSend NativeAsset, amountToSend float64, networkFeeAmount NetworkFeeAmount) ([]byte, error) - + GenerateTransactionOutputPayableGAS(sender NEOAddress, receiver NEOAddress, unspent Unspent, assetToSend NativeAsset, amountToSend float64, networkFeeAmount NetworkFeeAmount, payableGAS float64) ([]byte, error) + EmptyOutput() []byte GenerateVerificationScripts(signatures []interface{}) []byte GenerateVerificationScriptsMultiSig(signatures []TransactionSignature) []byte @@ -62,13 +63,15 @@ type ScriptBuilderInterface interface { //public method to wrap pushData Push(data interface{}) error + PushVarData(data []byte) PushOpCode(opcode OpCode) + PushSysCall(command string) ToScriptHash() []byte //UInt160 pushInt(value int) error pushData(data interface{}) error - pushLength(count int) + PushLength(count int) } func NewScriptBuilder() ScriptBuilderInterface { @@ -105,6 +108,7 @@ func (s ScriptBuilder) FullHexString() string { func (s *ScriptBuilder) PushOpCode(opcode OpCode) { s.RawBytes = append(s.RawBytes, byte(opcode)) } + func (s *ScriptBuilder) pushInt8bytes(value int) error { num := make([]byte, 8) binary.LittleEndian.PutUint64(num, uint64(value)) @@ -133,7 +137,7 @@ func (s *ScriptBuilder) pushInt(value int) error { return nil } -func (s *ScriptBuilder) pushLength(count int) { +func (s *ScriptBuilder) PushLength(count int) { if count == 0 { s.RawBytes = append(s.RawBytes, 0x00) return @@ -145,10 +149,16 @@ func (s *ScriptBuilder) pushLength(count int) { } func (s *ScriptBuilder) pushHexString(hexString string) error { + + if len(hexString) == 0 { + s.RawBytes = append(s.RawBytes, 0) + return nil + } b, err := hex.DecodeString(hexString) if err != nil { return err } + count := len(b) countBytes := make([]byte, 8) binary.LittleEndian.PutUint64(countBytes, uint64(count)) @@ -173,9 +183,40 @@ func (s *ScriptBuilder) pushHexString(hexString string) error { return nil } +func (s *ScriptBuilder) PushVarData(b []byte) { + + length := len(b) + + countBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(countBytes, uint64(length)) + trimmedCountByte := bytes.TrimRight(countBytes, "\x00") + + if length < int(PUSHBYTES75) { + s.RawBytes = append(s.RawBytes, trimmedCountByte...) + s.RawBytes = append(s.RawBytes, b...) + } else if length < 0x100 { + s.PushOpCode(PUSHDATA1) + s.RawBytes = append(s.RawBytes, trimmedCountByte...) + s.RawBytes = append(s.RawBytes, b...) + } else if length < 0x10000 { + s.PushOpCode(PUSHDATA2) + s.RawBytes = append(s.RawBytes, trimmedCountByte...) + s.RawBytes = append(s.RawBytes, b...) + } else { + s.PushOpCode(PUSHDATA4) + s.RawBytes = append(s.RawBytes, trimmedCountByte...) + s.RawBytes = append(s.RawBytes, b...) + } + +} + func (s *ScriptBuilder) Push(data interface{}) error { return s.pushData(data) } +func (s *ScriptBuilder) PushSysCall(command string) { + s.PushOpCode(SYSCALL) + s.pushData([]byte(command)) +} func (s *ScriptBuilder) pushData(data interface{}) error { switch e := data.(type) { @@ -192,7 +233,7 @@ func (s *ScriptBuilder) pushData(data interface{}) error { b := []byte{} b = append(b, uintToBytes(uint(signatureLength))...) b = append(b, e.SignedData...) - s.pushLength(len(b)) //this should be 0x41 + s.PushLength(len(b)) //this should be 0x41 s.RawBytes = append(s.RawBytes, b...) s.RawBytes = append(s.RawBytes, 0x23) //0x23 = 35 this is the length of the next [publickey.length(2)]+[publickey(33)]] //this part is for verification script @@ -325,13 +366,19 @@ func (s *ScriptBuilder) EmptyTransactionAttributes() []byte { return s.ToBytes() } +func (s *ScriptBuilder) EmptyOutput() []byte { + s.PushLength(0) + return s.ToBytes() +} + func (s *ScriptBuilder) GenerateTransactionAttributes(attributes map[TransactionAttribute][]byte) ([]byte, error) { count := len(attributes) - s.pushLength(count) //number of transaction attributes + s.PushLength(count) //number of transaction attributes // N x transaction attribute //transaction attribute = TransactionAttribute + data.length + data for k, v := range attributes { + s.pushData(k) //transaction attribute usage if k == Script { //if it's a Script field, we just need to put it as is in a little endian bytes @@ -349,7 +396,7 @@ func (s *ScriptBuilder) GenerateTransactionInput(unspent Unspent, assetToSend Na //empty unspent if (len(unspent.Assets) == 0 || amountToSend == 0) && float64(networkFeeAmount) == 0 { - s.pushLength(0) + s.PushLength(0) return s.ToBytes(), nil } sendingAsset := unspent.Assets[assetToSend] @@ -405,7 +452,7 @@ func (s *ScriptBuilder) GenerateTransactionInput(unspent Unspent, assetToSend Na //end fee input part } - s.pushLength(count) + s.PushLength(count) for _, v := range inputs { //push utxo data s.pushData(v) @@ -420,7 +467,7 @@ func (s *ScriptBuilder) GenerateTransactionOutput(sender NEOAddress, receiver NE //empty unspent if (len(unspent.Assets) == 0 || amountToSend == 0) && float64(networkFeeAmount) == 0 { log.Printf("asset less ending") - s.pushLength(0) + s.PushLength(0) return s.ToBytes(), nil } @@ -501,6 +548,175 @@ func (s *ScriptBuilder) GenerateTransactionOutput(sender NEOAddress, receiver NE } + //if set network fee is more than 0 + //add more output for fee + if needAnotherAssetForFee == true { + + gasBalanceForFee := unspent.Assets[GAS] + gasBalanceForFee.SortMinFirst() + if float64(feeAmount) > gasBalanceForFee.TotalAmount() { + return nil, fmt.Errorf("you don't have enough balance for network fee.") + } + runningFeeAmount := float64(0) + feeIndex := 0 + for runningFeeAmount < float64(feeAmount) { + addingUTXO := gasBalanceForFee.UTXOs[feeIndex] + inputs = append(inputs, addingUTXO) + runningFeeAmount += addingUTXO.Value + feeIndex += 1 + count += 1 + } + + // To allow user to set network fee is to make send GAS back to yourself + // minus the amount of gas that you want it to be network fee + // for example + // GAS balance = 10 + // sending back amount = 9 + // this will make network fee = 1 + + returningAmount := runningFeeAmount - float64(feeAmount) + //so if the input and the fee is the exact match we don't need to have the output + //sending output with value = 0 will not work + if returningAmount > 0 { + returningOutput := TransactionOutput{ + Asset: GAS, + Value: int64(RoundFixed8(returningAmount) * float64(100000000)), + Address: sender, + } + list = append(list, returningOutput) + } + + } + + //number of outputs + s.PushLength(len(list)) + for _, v := range list { + s.pushData(v) + } + + return s.ToBytes(), nil +} + +func (s *ScriptBuilder) GenerateTransactionOutputPayableGAS(sender NEOAddress, receiver NEOAddress, unspent Unspent, assetToSend NativeAsset, amountToSend float64, networkFeeAmount NetworkFeeAmount, payableGAS float64) ([]byte, error) { + + //output = [output_count] + [assetID(32)] + [amount(8)] + [sender_scripthash(20)] = 60 x output_count bytes + //empty unspent + if (len(unspent.Assets) == 0 || amountToSend == 0) && float64(networkFeeAmount) == 0 { + log.Printf("asset less ending") + s.PushLength(0) + return s.ToBytes(), nil + } + + sendingAsset := unspent.Assets[assetToSend] + if sendingAsset == nil { + return nil, fmt.Errorf("Asset %v not found in UTXO", assetToSend) + } + + //network fee + sys fee + feeAmount := payableGAS + float64(networkFeeAmount) + + //if assetToSend is NEO and fee amount is more than zero + needAnotherAssetForFee := false + if assetToSend == NEO && feeAmount > 0 { + needAnotherAssetForFee = true + } + + if amountToSend == 0 && float64(feeAmount) > 0 { + needAnotherAssetForFee = true + } + + if amountToSend > sendingAsset.TotalAmount() { + return nil, fmt.Errorf("you don't have enough balance. Sending %v but only have %v", amountToSend, sendingAsset.TotalAmount()) + } + //sort min first + + sendingAsset.SortMinFirst() + //we need to know the total amount of the input so we can calculate the output + + utxoSumAmount := float64(0) + index := 0 + count := 0 + inputs := []UTXO{} + for utxoSumAmount < amountToSend { + addingUTXO := sendingAsset.UTXOs[index] + inputs = append(inputs, addingUTXO) + utxoSumAmount += addingUTXO.Value + index += 1 + count += 1 + } + totalAmountInInputs := utxoSumAmount + + if totalAmountInInputs < payableGAS { + return nil, fmt.Errorf("you don't have enough balance. Sending %v GAS but only have %v", amountToSend, sendingAsset.TotalAmount()) + } + + changeAmount := totalAmountInInputs - feeAmount //fee is network fee + payable gas + //if the input matches exactly what user is paying then we don't need output + if changeAmount == 0 { + //empty output + s.PushLength(0) + return s.ToBytes(), nil + } + + list := []TransactionOutput{} + + if changeAmount > 0 { + out := TransactionOutput{ + Asset: assetToSend, + Value: int64(RoundFixed8(changeAmount) * float64(100000000)), + Address: receiver, + } + list = append(list, out) + //number of outputs + s.PushLength(len(list)) + for _, v := range list { + s.pushData(v) + } + log.Printf("output %+v (%v)", list, len(list)) + + return s.ToBytes(), nil + } + + //if the total amount of inputs is over amountToSend + //we need to send the rest back to the sending address + + needTwoOutputTransaction := totalAmountInInputs > amountToSend + + if needTwoOutputTransaction { + //first output is the amount to send to the receiver + sendingOutput := TransactionOutput{ + Asset: assetToSend, + Value: int64(RoundFixed8(amountToSend) * float64(100000000)), + Address: receiver, + } + list = append(list, sendingOutput) + + //second output is the returning amount you will be sending back to yourself. + returningAmount := totalAmountInInputs - amountToSend + + //so if we don't need another asset input and fee is more than 0 + //we then make returningAmount = returningAmount - fee + if needAnotherAssetForFee == false && float64(feeAmount) > 0 { + returningAmount -= float64(feeAmount) + } + //return the left over to sender + returningOutput := TransactionOutput{ + Asset: assetToSend, + Value: int64(RoundFixed8(returningAmount) * float64(100000000)), + Address: sender, + } + list = append(list, returningOutput) + } else if amountToSend > 0 && needTwoOutputTransaction == false { + + out := TransactionOutput{ + Asset: assetToSend, + Value: int64(RoundFixed8(amountToSend) * float64(100000000)), + Address: receiver, + } + list = append(list, out) + + } + //if set network fee is more than 0 //add more output for fee if needAnotherAssetForFee == true { @@ -543,7 +759,7 @@ func (s *ScriptBuilder) GenerateTransactionOutput(sender NEOAddress, receiver NE log.Printf("output %+v (%v)", list, len(list)) //number of outputs - s.pushLength(len(list)) + s.PushLength(len(list)) for _, v := range list { s.pushData(v) } @@ -558,7 +774,7 @@ func (s *ScriptBuilder) GenerateVerificationScripts(scripts []interface{}) []byt return nil } - s.pushLength(numberOfScripts) + s.PushLength(numberOfScripts) for _, script := range scripts { switch e := script.(type) { case TransactionSignature: @@ -574,7 +790,7 @@ func (s *ScriptBuilder) GenerateVerificationScripts(scripts []interface{}) []byt func (s *ScriptBuilder) GenerateVerificationScriptsMultiSig(signatures []TransactionSignature) []byte { - s.pushLength(1) + s.PushLength(1) list := []struct { TransactionSignature @@ -610,7 +826,7 @@ func (s *ScriptBuilder) GenerateVerificationScriptsMultiSig(signatures []Transac } //push length of signed data - s.pushLength(len(all)) + s.PushLength(len(all)) s.RawBytes = append(s.RawBytes, all...) return s.ToBytes() } diff --git a/neoutils/smartcontract/scriptbuilder_test.go b/neoutils/smartcontract/scriptbuilder_test.go index fd539f0..56cfe93 100644 --- a/neoutils/smartcontract/scriptbuilder_test.go +++ b/neoutils/smartcontract/scriptbuilder_test.go @@ -40,6 +40,14 @@ func TestGenerateInvokeScript(t *testing.T) { log.Printf("%x", s.ToBytes()) } +func TestAdd(t *testing.T) { + s := smartcontract.NewScriptBuilder() + s.PushOpCode(smartcontract.PUSH1) + s.PushOpCode(smartcontract.PUSH3) + s.PushOpCode(smartcontract.ADD) + log.Printf("%x", s.ToBytes()) +} + func TestRoundFixed8(t *testing.T) { inputs := float64(0.00119) fee := float64(0.0011) diff --git a/neoutils/smartcontract/transaction.go b/neoutils/smartcontract/transaction.go index be38e9d..0f1c005 100644 --- a/neoutils/smartcontract/transaction.go +++ b/neoutils/smartcontract/transaction.go @@ -2,6 +2,7 @@ package smartcontract import ( "crypto/sha256" + "encoding/binary" "fmt" ) @@ -14,6 +15,7 @@ type Transaction struct { Outputs []byte //scripts contains two parts, Invocation script and Verification script Script []byte + GAS uint64 //only for version 1 } type TransactionOutput struct { @@ -27,6 +29,13 @@ func (t *Transaction) ToBytes() []byte { payload = append(payload, byte(t.Type)) payload = append(payload, byte(t.Version)) payload = append(payload, t.Data...) + + if t.Version >= NEOTradingVersionPayableGAS { + gasInBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(gasInBytes, uint64(t.GAS*uint64(100000000))) + payload = append(payload, gasInBytes...) + } + payload = append(payload, t.Attributes...) payload = append(payload, t.Inputs...) payload = append(payload, t.Outputs...) @@ -42,6 +51,12 @@ func (t *Transaction) ToHash256() []byte { payload = append(payload, byte(t.Type)) payload = append(payload, byte(t.Version)) payload = append(payload, t.Data...) + + if t.Version >= NEOTradingVersionPayableGAS { + gasInBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(gasInBytes, uint64(t.GAS*uint64(100000000))) + payload = append(payload, gasInBytes...) + } payload = append(payload, t.Attributes...) payload = append(payload, t.Inputs...) payload = append(payload, t.Outputs...) @@ -56,9 +71,6 @@ func (t *Transaction) ToTXID() string { return fmt.Sprintf("%x", reverseBytes(t.ToHash256())) } -//version is 0 currently -//it needs to change to 1 eventually to support pay gas to run smart contract -//https://github.com/neo-project/neo/blob/11d8db11568d9eadeeb86c5b8c21a1d3937e0912/neo/Core/InvocationTransaction.cs#L23 func NewInvocationTransaction() Transaction { return Transaction{ Type: InvocationTransaction, @@ -66,9 +78,29 @@ func NewInvocationTransaction() Transaction { } } +func NewInvocationTransactionPayable() Transaction { + return Transaction{ + Type: InvocationTransaction, + Version: NEOTradingVersionPayableGAS, //version 1 this will allow paying GAS + } +} + func NewContractTransaction() Transaction { return Transaction{ Type: ContractTransaction, Version: NEOTradingVersion, } } + +func NewTransactionWithType(txType byte, version int) Transaction { + if txType == byte(InvocationTransaction) { + return Transaction{ + Type: InvocationTransaction, + Version: TradingVersion(version), + } + } + return Transaction{ + Type: InvocationTransaction, + Version: NEOTradingVersionPayableGAS, //version 1 this will allow paying GAS + } +} diff --git a/neoutils/smartcontract_test.go b/neoutils/smartcontract_test.go index d51c852..b47f193 100644 --- a/neoutils/smartcontract_test.go +++ b/neoutils/smartcontract_test.go @@ -141,7 +141,11 @@ func TestGenerateInvokeTransferNEP5Token(t *testing.T) { } func TestCallDeployFunction(t *testing.T) { - wif := "" + + encryptedKey := "" + passphrase := "" + wif, err := neoutils.NEP2Decrypt(encryptedKey, passphrase) + privateNetwallet, err := neoutils.GenerateFromWIF(wif) if err != nil { log.Printf("%v", err) @@ -150,13 +154,13 @@ func TestCallDeployFunction(t *testing.T) { unspent := smartcontract.Unspent{} - sc := neoutils.UseSmartContract("0x7cd338644833db2fd8824c410e364890d179e6f8") + sc := neoutils.UseSmartContract("323571cfc42a40d48d64832a7da594039fbac76a") args := []interface{}{} attributes := map[smartcontract.TransactionAttribute][]byte{} addressScriptHash := neoutils.NEOAddressToScriptHashWithEndian(privateNetwallet.Address, binary.LittleEndian) b, _ := hex.DecodeString(addressScriptHash) attributes[smartcontract.Script] = []byte(b) - attributes[smartcontract.Remark1] = []byte(fmt.Sprintf("O3TXAPT%v", time.Now().Unix())) + attributes[smartcontract.Remark1] = []byte(fmt.Sprintf("O3TXSCC%v", time.Now().Unix())) tx, err := sc.GenerateInvokeFunctionRawTransaction(*privateNetwallet, unspent, attributes, "deploy", args) if err != nil { @@ -165,3 +169,223 @@ func TestCallDeployFunction(t *testing.T) { } log.Printf("%x", tx) } + +func TestRefund1stCGAS(t *testing.T) { + wif := "" + wallet, _ := neoutils.GenerateFromWIF(wif) + + refundValue := float64(1) + + cgas, _ := smartcontract.NewScriptHash("9121e89e8a0849857262d67c8408601b5e8e0524") + + // unspent, _ := utxo("test", wallet.Address) + + unspent := smartcontract.Unspent{} + unspent.Assets = map[smartcontract.NativeAsset]*smartcontract.Balance{} + + //any utxo from SGAS address that is not marked as refund. + txid := "0x472eadfd5fc2b726d07b78a83887a5f9ea00eafe1bc7dcc899dcbed21c9c99af" + gasBalance := smartcontract.Balance{ + Amount: float64(0) / float64(100000000), + UTXOs: []smartcontract.UTXO{}, + } + + gasTX1 := smartcontract.UTXO{ + Index: 0, + TXID: txid, + Value: 1, + } + gasBalance.UTXOs = append(gasBalance.UTXOs, gasTX1) + unspent.Assets[smartcontract.GAS] = &gasBalance + + from := smartcontract.ParseNEOAddress(wallet.Address) + args := []interface{}{from} + + //New invocation transaction struct and fill with all necessary data + tx := smartcontract.NewInvocationTransaction() + + txData := smartcontract.NewScriptBuilder().GenerateContractInvocationData(cgas, "refund", args) + tx.Data = txData + + //basically sending GAS to myself + amountToSend := refundValue + assetToSend := smartcontract.GAS + + networkFee := smartcontract.NetworkFeeAmount(0) + + txInputs, err := smartcontract.NewScriptBuilder().GenerateTransactionInput(unspent, assetToSend, amountToSend, networkFee) + if err != nil { + return + } + tx.Inputs = txInputs + log.Printf("input %x", txInputs) + //this is a MUST + sender := smartcontract.ParseNEOAddress("AK4LdT5ZXR9DQZjfk5X6Xy79mE8ad8jKAW") + receiver := smartcontract.ParseNEOAddress("AK4LdT5ZXR9DQZjfk5X6Xy79mE8ad8jKAW") + log.Printf("receiver %v", receiver.ToString()) + txOutputs, err := smartcontract.NewScriptBuilder().GenerateTransactionOutput(sender, receiver, unspent, assetToSend, amountToSend, networkFee) + if err != nil { + return + } + + tx.Outputs = txOutputs + + attributes := map[smartcontract.TransactionAttribute][]byte{} + // attributes[smartcontract.Remark1] = []byte(remark) + attributes[smartcontract.Script] = neoutils.HexTobytes(neoutils.NEOAddressToScriptHashWithEndian(wallet.Address, binary.LittleEndian)) + + //generate transaction outputs + txAttributes, err := smartcontract.NewScriptBuilder().GenerateTransactionAttributes(attributes) + if err != nil { + return + } + //transaction attributes + tx.Attributes = txAttributes + + //begin signing process and invocation script + privateKeyInHex := neoutils.BytesToHex(wallet.PrivateKey) + signedData, err := neoutils.Sign(tx.ToBytes(), privateKeyInHex) + if err != nil { + return + } + + signature := smartcontract.TransactionSignature{ + SignedData: signedData, + PublicKey: wallet.PublicKey, + } + + scripts := []interface{}{signature} + + //this empty verification script is needed in order to make it triggers Verification part + //and use Script field in Transaction attribute + emptyVerificationScript := smartcontract.TransactionValidationScript{ + StackScript: []byte{0x00, 0x00}, + RedeemScript: nil, + } + + //basically we need to sort in descending order for address and script hash + scriptHashInt := neoutils.ConvertByteArrayToBigInt(fmt.Sprintf("%x", cgas)) + addressInt := neoutils.ConvertByteArrayToBigInt(fmt.Sprintf("%x", wallet.HashedSignature)) + //https://godoc.org/math/big#Int.Cmp + //if scripthash int is grether than address int + if scriptHashInt.Cmp(addressInt) == 1 { + scripts = append(scripts, emptyVerificationScript) + } else { + scripts = append([]interface{}{emptyVerificationScript}, scripts...) + } + + txScripts := smartcontract.NewScriptBuilder().GenerateVerificationScripts(scripts) + //assign scripts to the tx + tx.Script = txScripts + + log.Printf("txid = %v", tx.ToTXID()) + log.Printf("endPayload = %x", tx.ToBytes()) +} + +func TestRefund2ndStep(t *testing.T) { + wif := "" + wallet, _ := neoutils.GenerateFromWIF(wif) + + refundValue := float64(1) + + cgas, _ := smartcontract.NewScriptHash("9121e89e8a0849857262d67c8408601b5e8e0524") + + unspent := smartcontract.Unspent{} + unspent.Assets = map[smartcontract.NativeAsset]*smartcontract.Balance{} + txid := "0x9296fef6b14f85eb29155639ab3cf46edd6fcc529177b7259bfeb2a932278238" + gasBalance := smartcontract.Balance{ + Amount: float64(0) / float64(100000000), + UTXOs: []smartcontract.UTXO{}, + } + + gasTX1 := smartcontract.UTXO{ + Index: 0, + TXID: txid, + Value: 1, + } + gasBalance.UTXOs = append(gasBalance.UTXOs, gasTX1) + unspent.Assets[smartcontract.GAS] = &gasBalance + + from := smartcontract.ParseNEOAddress(wallet.Address) + args := []interface{}{from} + + //New invocation transaction struct and fill with all necessary data + tx := smartcontract.NewInvocationTransaction() + + txData := smartcontract.NewScriptBuilder().GenerateContractInvocationData(cgas, "refund", args) + tx.Data = txData + + //basically sending GAS to myself + amountToSend := refundValue + assetToSend := smartcontract.GAS + + networkFee := smartcontract.NetworkFeeAmount(0) + + txInputs, err := smartcontract.NewScriptBuilder().GenerateTransactionInput(unspent, assetToSend, amountToSend, networkFee) + if err != nil { + return + } + tx.Inputs = txInputs + log.Printf("input %x", txInputs) + + sender := smartcontract.ParseNEOAddress("AK4LdT5ZXR9DQZjfk5X6Xy79mE8ad8jKAW") + receiver := smartcontract.ParseNEOAddress(wallet.Address) + log.Printf("receiver %v", receiver.ToString()) + txOutputs, err := smartcontract.NewScriptBuilder().GenerateTransactionOutput(sender, receiver, unspent, assetToSend, amountToSend, networkFee) + if err != nil { + return + } + + tx.Outputs = txOutputs + + attributes := map[smartcontract.TransactionAttribute][]byte{} + // attributes[smartcontract.Remark1] = []byte(remark) + attributes[smartcontract.Script] = neoutils.HexTobytes(neoutils.NEOAddressToScriptHashWithEndian(wallet.Address, binary.LittleEndian)) + + //generate transaction outputs + txAttributes, err := smartcontract.NewScriptBuilder().GenerateTransactionAttributes(attributes) + if err != nil { + return + } + //transaction attributes + tx.Attributes = txAttributes + + //begin signing process and invocation script + privateKeyInHex := neoutils.BytesToHex(wallet.PrivateKey) + signedData, err := neoutils.Sign(tx.ToBytes(), privateKeyInHex) + if err != nil { + return + } + + signature := smartcontract.TransactionSignature{ + SignedData: signedData, + PublicKey: wallet.PublicKey, + } + + scripts := []interface{}{signature} + + //this empty verification script is needed in order to make it triggers Verification part + emptyVerificationScript := smartcontract.TransactionValidationScript{ + StackScript: []byte{0x00, 0x00}, + RedeemScript: nil, + } + + //basically we need to sort in descending order for address and script hash + scriptHashInt := neoutils.ConvertByteArrayToBigInt(fmt.Sprintf("%x", cgas)) + addressInt := neoutils.ConvertByteArrayToBigInt(fmt.Sprintf("%x", wallet.HashedSignature)) + //https://godoc.org/math/big#Int.Cmp + //if scripthash int is grether than address int + if scriptHashInt.Cmp(addressInt) == 1 { + scripts = append(scripts, emptyVerificationScript) + } else { + scripts = append([]interface{}{emptyVerificationScript}, scripts...) + } + + txScripts := smartcontract.NewScriptBuilder().GenerateVerificationScripts(scripts) + //assign scripts to the tx + tx.Script = txScripts + + log.Printf("txid = %v", tx.ToTXID()) + log.Printf("endPayload = %x", tx.ToBytes()) + +} diff --git a/neoutils/utils_test.go b/neoutils/utils_test.go index 0d8024a..b75b654 100644 --- a/neoutils/utils_test.go +++ b/neoutils/utils_test.go @@ -24,12 +24,12 @@ func TestScriptHashToNEOAddress(t *testing.T) { } func TestSmartContractScripthashToAddress(t *testing.T) { - address := ScriptHashToNEOAddress("fb5f6ac2a3b8396f8eafa5ac5c8f28ffcd247fc4") + address := ScriptHashToNEOAddress("9121e89e8a0849857262d67c8408601b5e8e0524") log.Printf("%v", address) } func TestNEOAddressToScriptHash(t *testing.T) { - hash := NEOAddressToScriptHashWithEndian("ASi48wqdF9avm91pWwdphcAmaDJQkPNdNt", binary.LittleEndian) + hash := NEOAddressToScriptHashWithEndian("AQV8FNNi2o7EtMNn4etWBYx1cqBREAifgE", binary.LittleEndian) b, _ := hex.DecodeString(hash) log.Printf("\nlittle endian %v \nbig endian %x", hash, ReverseBytes(b)) } @@ -49,7 +49,7 @@ func TestValidateNEOAddressInvalidAddress(t *testing.T) { } func TestConverting(t *testing.T) { - hexByteArray := "c02709" //500000000000000000 + hexByteArray := "80778e06" //500000000000000000 //hex := "005c7c875e" = 405991873536 value := ConvertByteArrayToBigInt(hexByteArray) vvv := float64(value.Int64()) / float64(math.Pow10(8)) @@ -57,7 +57,7 @@ func TestConverting(t *testing.T) { } func TestParseNEP9(t *testing.T) { - uri := "neo:AeNkbJdiMx49kBStQdDih7BzfDwyTNVRfb?assetID=602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7&amount=0.11&description=for%20a%20coffee" + uri := "neo:AeNkbJdiMx49kBStQdDih7BzfDwyTNVRfb?asset=602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7&amount=0.11&description=for%20a%20coffee" nep9, err := ParseNEP9URI(uri) if err != nil { log.Printf("%v", err) @@ -68,14 +68,14 @@ func TestParseNEP9(t *testing.T) { } func TestReverse(t *testing.T) { - b := HexTobytes("50591a2f81a506786a39d9aeb4d7ee935a284f95") + b := HexTobytes("73ef176d9f12809e64363b2b5f4553abecca7aae157327f190323cfa0e42c815") log.Printf("%x", ReverseBytes(b)) } func TestHash160(t *testing.T) { address := "AJShjraX4iMJjwVt8WYYzZyGvDMxw6Xfbe" b := Hash160([]byte(address)) - log.Printf("%x", b) + log.Printf("%v", b) } func TestHash256(t *testing.T) { diff --git a/neoutils/version.go b/neoutils/version.go index 5dc0f25..08b7371 100644 --- a/neoutils/version.go +++ b/neoutils/version.go @@ -1,11 +1,17 @@ package neoutils const ( - VERSION = "1.2.2" + VERSION = "1.3.0" ) //RELEASE NOTES +// V.1.3.0 +// - Sample Deploy Smart Contract without syncing +// - Fixed add empty string as a argument when invoke script +// - Sample NNS invoke script +// - Sample Multisignature address + // V.1.2.2 // - Allow sending NEP5 with fee diff --git a/neoutils/wallet_test.go b/neoutils/wallet_test.go new file mode 100644 index 0000000..4f41e85 --- /dev/null +++ b/neoutils/wallet_test.go @@ -0,0 +1 @@ +package neoutils_test