Merge pull request #1857 from nspcc-dev/rpc/nep11

rpc: add NEP11 and NNS interfaces to RPC Client
This commit is contained in:
Roman Khimov 2021-03-27 18:27:29 +03:00 committed by GitHub
commit 5b4f6d255f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 657 additions and 229 deletions

View file

@ -9,6 +9,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
@ -77,10 +78,10 @@ func TestRoleManagementRole(t *testing.T) {
}
func TestNameServiceRecordType(t *testing.T) {
require.EqualValues(t, native.RecordTypeA, nameservice.TypeA)
require.EqualValues(t, native.RecordTypeCNAME, nameservice.TypeCNAME)
require.EqualValues(t, native.RecordTypeTXT, nameservice.TypeTXT)
require.EqualValues(t, native.RecordTypeAAAA, nameservice.TypeAAAA)
require.EqualValues(t, nnsrecords.A, nameservice.TypeA)
require.EqualValues(t, nnsrecords.CNAME, nameservice.TypeCNAME)
require.EqualValues(t, nnsrecords.TXT, nameservice.TypeTXT)
require.EqualValues(t, nnsrecords.AAAA, nameservice.TypeAAAA)
}
func TestCryptoLibNamedCurve(t *testing.T) {

View file

@ -23,6 +23,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
@ -484,6 +485,21 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, txDeploy3.Hash())
// register `neo.com` with A record type and priv0 owner via NNS
transferFundsToCommittee(t, bc) // block #11
res, err := invokeContractMethodGeneric(bc, defaultNameServiceSysfee,
bc.contracts.NameService.Hash, "addRoot", true, "com") // block #12
require.NoError(t, err)
checkResult(t, res, stackitem.Null{})
res, err = invokeContractMethodGeneric(bc, native.DefaultDomainPrice+defaultNameServiceSysfee,
bc.contracts.NameService.Hash, "register", acc0, "neo.com", priv0ScriptHash) // block #13
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
res, err = invokeContractMethodGeneric(bc, defaultNameServiceSysfee, bc.contracts.NameService.Hash,
"setRecord", acc0, "neo.com", int64(nnsrecords.A), "1.2.3.4") // block #14
require.NoError(t, err)
checkResult(t, res, stackitem.Null{})
// Compile contract to test `invokescript` RPC call
_, _ = newDeployTx(t, bc, priv0ScriptHash, prefix+"invokescript_contract.go", "ContractForInvokescriptTest", nil)
}

View file

@ -15,6 +15,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
@ -42,17 +43,6 @@ type nameState struct {
Admin util.Uint160
}
// RecordType represents name record type.
type RecordType byte
// Pre-defined record types.
const (
RecordTypeA RecordType = 1
RecordTypeCNAME RecordType = 5
RecordTypeTXT RecordType = 16
RecordTypeAAAA RecordType = 28
)
const (
nameServiceID = -10
@ -448,19 +438,19 @@ func (n *NameService) setRecord(ic *interop.Context, args []stackitem.Item) stac
return stackitem.Null{}
}
func checkName(rt RecordType, name string) {
func checkName(rt nnsrecords.Type, name string) {
var valid bool
switch rt {
case RecordTypeA:
case nnsrecords.A:
// We can't rely on `len(ip) == net.IPv4len` because
// IPv4 can be parsed to mapped representation.
valid = ipv4Regex.MatchString(name) &&
net.ParseIP(name) != nil
case RecordTypeCNAME:
case nnsrecords.CNAME:
valid = matchName(name)
case RecordTypeTXT:
case nnsrecords.TXT:
valid = utf8.RuneCountInString(name) <= 255
case RecordTypeAAAA:
case nnsrecords.AAAA:
valid = ipv6Regex.MatchString(name) &&
net.ParseIP(name) != nil
}
@ -513,7 +503,7 @@ func (n *NameService) resolve(ic *interop.Context, args []stackitem.Item) stacki
return stackitem.NewByteArray([]byte(result))
}
func (n *NameService) resolveInternal(ic *interop.Context, name string, t RecordType, redirect int) (string, bool) {
func (n *NameService) resolveInternal(ic *interop.Context, name string, t nnsrecords.Type, redirect int) (string, bool) {
if redirect < 0 {
panic("invalid redirect")
}
@ -521,26 +511,26 @@ func (n *NameService) resolveInternal(ic *interop.Context, name string, t Record
if data, ok := records[t]; ok {
return data, true
}
data, ok := records[RecordTypeCNAME]
data, ok := records[nnsrecords.CNAME]
if !ok {
return "", false
}
return n.resolveInternal(ic, data, t, redirect-1)
}
func (n *NameService) getRecordsInternal(d dao.DAO, name string) map[RecordType]string {
func (n *NameService) getRecordsInternal(d dao.DAO, name string) map[nnsrecords.Type]string {
domain := toDomain(name)
key := makeRecordKey(domain, name, 0)
key = key[:len(key)-1]
res := make(map[RecordType]string)
res := make(map[nnsrecords.Type]string)
d.Seek(n.ID, key, func(k, v []byte) {
rt := RecordType(k[len(k)-1])
rt := nnsrecords.Type(k[len(k)-1])
res[rt] = string(v)
})
return res
}
func makeRecordKey(domain, name string, rt RecordType) []byte {
func makeRecordKey(domain, name string, rt nnsrecords.Type) []byte {
key := make([]byte, 1+util.Uint160Size+util.Uint160Size+1)
key[0] = prefixRecord
i := 1
@ -647,7 +637,7 @@ func toDomain(name string) string {
return domain
}
func toRecordType(item stackitem.Item) RecordType {
func toRecordType(item stackitem.Item) nnsrecords.Type {
bi, err := item.TryInteger()
if err != nil || !bi.IsInt64() {
panic("invalid record type")
@ -656,8 +646,8 @@ func toRecordType(item stackitem.Item) RecordType {
if val > math.MaxUint8 {
panic("invalid record type")
}
switch rt := RecordType(val); rt {
case RecordTypeA, RecordTypeCNAME, RecordTypeTXT, RecordTypeAAAA:
switch rt := nnsrecords.Type(val); rt {
case nnsrecords.A, nnsrecords.CNAME, nnsrecords.TXT, nnsrecords.AAAA:
return rt
default:
panic("invalid record type")

View file

@ -3,6 +3,7 @@ package native
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard"
"github.com/stretchr/testify/require"
@ -36,45 +37,45 @@ func TestParseDomain(t *testing.T) {
func TestNameService_CheckName(t *testing.T) {
// tests are got from the C# implementation
testCases := []struct {
Type RecordType
Type nnsrecords.Type
Name string
ShouldFail bool
}{
{Type: RecordTypeA, Name: "0.0.0.0"},
{Type: RecordTypeA, Name: "10.10.10.10"},
{Type: RecordTypeA, Name: "255.255.255.255"},
{Type: RecordTypeA, Name: "192.168.1.1"},
{Type: RecordTypeA, Name: "1a", ShouldFail: true},
{Type: RecordTypeA, Name: "256.0.0.0", ShouldFail: true},
{Type: RecordTypeA, Name: "01.01.01.01", ShouldFail: true},
{Type: RecordTypeA, Name: "00.0.0.0", ShouldFail: true},
{Type: RecordTypeA, Name: "0.0.0.-1", ShouldFail: true},
{Type: RecordTypeA, Name: "0.0.0.0.1", ShouldFail: true},
{Type: RecordTypeA, Name: "11111111.11111111.11111111.11111111", ShouldFail: true},
{Type: RecordTypeA, Name: "11111111.11111111.11111111.11111111", ShouldFail: true},
{Type: RecordTypeA, Name: "ff.ff.ff.ff", ShouldFail: true},
{Type: RecordTypeA, Name: "0.0.256", ShouldFail: true},
{Type: RecordTypeA, Name: "0.0.0", ShouldFail: true},
{Type: RecordTypeA, Name: "0.257", ShouldFail: true},
{Type: RecordTypeA, Name: "1.1", ShouldFail: true},
{Type: RecordTypeA, Name: "257", ShouldFail: true},
{Type: RecordTypeA, Name: "1", ShouldFail: true},
{Type: RecordTypeAAAA, Name: "2001:db8::8:800:200c:417a"},
{Type: RecordTypeAAAA, Name: "ff01::101"},
{Type: RecordTypeAAAA, Name: "::1"},
{Type: RecordTypeAAAA, Name: "::"},
{Type: RecordTypeAAAA, Name: "2001:db8:0:0:8:800:200c:417a"},
{Type: RecordTypeAAAA, Name: "ff01:0:0:0:0:0:0:101"},
{Type: RecordTypeAAAA, Name: "0:0:0:0:0:0:0:1"},
{Type: RecordTypeAAAA, Name: "0:0:0:0:0:0:0:0"},
{Type: RecordTypeAAAA, Name: "2001:DB8::8:800:200C:417A", ShouldFail: true},
{Type: RecordTypeAAAA, Name: "FF01::101", ShouldFail: true},
{Type: RecordTypeAAAA, Name: "fF01::101", ShouldFail: true},
{Type: RecordTypeAAAA, Name: "2001:DB8:0:0:8:800:200C:417A", ShouldFail: true},
{Type: RecordTypeAAAA, Name: "FF01:0:0:0:0:0:0:101", ShouldFail: true},
{Type: RecordTypeAAAA, Name: "::ffff:1.01.1.01", ShouldFail: true},
{Type: RecordTypeAAAA, Name: "2001:DB8:0:0:8:800:200C:4Z", ShouldFail: true},
{Type: RecordTypeAAAA, Name: "::13.1.68.3", ShouldFail: true},
{Type: nnsrecords.A, Name: "0.0.0.0"},
{Type: nnsrecords.A, Name: "10.10.10.10"},
{Type: nnsrecords.A, Name: "255.255.255.255"},
{Type: nnsrecords.A, Name: "192.168.1.1"},
{Type: nnsrecords.A, Name: "1a", ShouldFail: true},
{Type: nnsrecords.A, Name: "256.0.0.0", ShouldFail: true},
{Type: nnsrecords.A, Name: "01.01.01.01", ShouldFail: true},
{Type: nnsrecords.A, Name: "00.0.0.0", ShouldFail: true},
{Type: nnsrecords.A, Name: "0.0.0.-1", ShouldFail: true},
{Type: nnsrecords.A, Name: "0.0.0.0.1", ShouldFail: true},
{Type: nnsrecords.A, Name: "11111111.11111111.11111111.11111111", ShouldFail: true},
{Type: nnsrecords.A, Name: "11111111.11111111.11111111.11111111", ShouldFail: true},
{Type: nnsrecords.A, Name: "ff.ff.ff.ff", ShouldFail: true},
{Type: nnsrecords.A, Name: "0.0.256", ShouldFail: true},
{Type: nnsrecords.A, Name: "0.0.0", ShouldFail: true},
{Type: nnsrecords.A, Name: "0.257", ShouldFail: true},
{Type: nnsrecords.A, Name: "1.1", ShouldFail: true},
{Type: nnsrecords.A, Name: "257", ShouldFail: true},
{Type: nnsrecords.A, Name: "1", ShouldFail: true},
{Type: nnsrecords.AAAA, Name: "2001:db8::8:800:200c:417a"},
{Type: nnsrecords.AAAA, Name: "ff01::101"},
{Type: nnsrecords.AAAA, Name: "::1"},
{Type: nnsrecords.AAAA, Name: "::"},
{Type: nnsrecords.AAAA, Name: "2001:db8:0:0:8:800:200c:417a"},
{Type: nnsrecords.AAAA, Name: "ff01:0:0:0:0:0:0:101"},
{Type: nnsrecords.AAAA, Name: "0:0:0:0:0:0:0:1"},
{Type: nnsrecords.AAAA, Name: "0:0:0:0:0:0:0:0"},
{Type: nnsrecords.AAAA, Name: "2001:DB8::8:800:200C:417A", ShouldFail: true},
{Type: nnsrecords.AAAA, Name: "FF01::101", ShouldFail: true},
{Type: nnsrecords.AAAA, Name: "fF01::101", ShouldFail: true},
{Type: nnsrecords.AAAA, Name: "2001:DB8:0:0:8:800:200C:417A", ShouldFail: true},
{Type: nnsrecords.AAAA, Name: "FF01:0:0:0:0:0:0:101", ShouldFail: true},
{Type: nnsrecords.AAAA, Name: "::ffff:1.01.1.01", ShouldFail: true},
{Type: nnsrecords.AAAA, Name: "2001:DB8:0:0:8:800:200C:4Z", ShouldFail: true},
{Type: nnsrecords.AAAA, Name: "::13.1.68.3", ShouldFail: true},
}
for _, testCase := range testCases {
if testCase.ShouldFail {

View file

@ -0,0 +1,12 @@
package nnsrecords
// Type represents name record type.
type Type byte
// Pre-defined record types.
const (
A Type = 1
CNAME Type = 5
TXT Type = 16
AAAA Type = 28
)

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
@ -67,7 +68,7 @@ func TestExpiration(t *testing.T) {
true, "first.com", acc.Contract.ScriptHash())
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc,
"setRecord", stackitem.Null{}, "first.com", int64(native.RecordTypeTXT), "sometext")
"setRecord", stackitem.Null{}, "first.com", int64(nnsrecords.TXT), "sometext")
b1 := bc.topBlock.Load().(*block.Block)
tx, err := prepareContractMethodInvokeGeneric(bc, defaultRegisterSysfee, bc.contracts.NameService.Hash,
@ -108,7 +109,7 @@ func TestExpiration(t *testing.T) {
checkResult(t, &aer[0], stackitem.NewBool(false))
tx, err = prepareContractMethodInvokeGeneric(bc, defaultNameServiceSysfee, bc.contracts.NameService.Hash,
"getRecord", acc, "first.com", int64(native.RecordTypeTXT))
"getRecord", acc, "first.com", int64(nnsrecords.TXT))
require.NoError(t, err)
b5 := newBlockCustom(bc.GetConfig(), func(b *block.Block) {
b.Index = b4.Index + 1
@ -182,36 +183,36 @@ func TestSetGetRecord(t *testing.T) {
testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "com")
t.Run("set before register", func(t *testing.T) {
testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(native.RecordTypeTXT), "sometext")
testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(nnsrecords.TXT), "sometext")
})
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "register",
true, "neo.com", testchain.CommitteeScriptHash())
t.Run("invalid parameters", func(t *testing.T) {
testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(0xFF), "1.2.3.4")
testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(native.RecordTypeA), "not.an.ip.address")
testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(nnsrecords.A), "not.an.ip.address")
})
t.Run("invalid witness", func(t *testing.T) {
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", nil,
"neo.com", int64(native.RecordTypeA), "1.2.3.4")
"neo.com", int64(nnsrecords.A), "1.2.3.4")
})
testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA))
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA), "1.2.3.4")
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA))
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA), "1.2.3.4")
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA))
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeAAAA), "2001:0000:1f1f:0000:0000:0100:11a0:addf")
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME), "nspcc.ru")
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeTXT), "sometext")
testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.A))
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.A), "1.2.3.4")
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(nnsrecords.A))
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.A), "1.2.3.4")
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(nnsrecords.A))
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.AAAA), "2001:0000:1f1f:0000:0000:0100:11a0:addf")
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.CNAME), "nspcc.ru")
testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.TXT), "sometext")
// Delete record.
t.Run("invalid witness", func(t *testing.T) {
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", nil,
"neo.com", int64(native.RecordTypeCNAME))
"neo.com", int64(nnsrecords.CNAME))
})
testNameServiceInvoke(t, bc, "getRecord", "nspcc.ru", "neo.com", int64(native.RecordTypeCNAME))
testNameServiceInvoke(t, bc, "deleteRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME))
testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME))
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA))
testNameServiceInvoke(t, bc, "getRecord", "nspcc.ru", "neo.com", int64(nnsrecords.CNAME))
testNameServiceInvoke(t, bc, "deleteRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.CNAME))
testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.CNAME))
testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(nnsrecords.A))
}
func TestSetAdmin(t *testing.T) {
@ -239,20 +240,20 @@ func TestSetAdmin(t *testing.T) {
t.Run("set and delete by admin", func(t *testing.T) {
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{},
"neo.com", int64(native.RecordTypeTXT), "sometext")
"neo.com", int64(nnsrecords.TXT), "sometext")
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, guest, "deleteRecord", nil,
"neo.com", int64(native.RecordTypeTXT))
"neo.com", int64(nnsrecords.TXT))
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "deleteRecord", stackitem.Null{},
"neo.com", int64(native.RecordTypeTXT))
"neo.com", int64(nnsrecords.TXT))
})
t.Run("set admin to null", func(t *testing.T) {
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{},
"neo.com", int64(native.RecordTypeTXT), "sometext")
"neo.com", int64(nnsrecords.TXT), "sometext")
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, owner, "setAdmin", stackitem.Null{},
"neo.com", nil)
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "deleteRecord", nil,
"neo.com", int64(native.RecordTypeTXT))
"neo.com", int64(nnsrecords.TXT))
})
}
@ -267,7 +268,7 @@ func TestTransfer(t *testing.T) {
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "register",
true, "neo.com", from.PrivateKey().GetScriptHash())
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "setRecord", stackitem.Null{},
"neo.com", int64(native.RecordTypeA), "1.2.3.4")
"neo.com", int64(nnsrecords.A), "1.2.3.4")
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "transfer",
nil, to.Contract.ScriptHash().BytesBE(), []byte("not.exists"))
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "transfer",
@ -355,23 +356,23 @@ func TestResolve(t *testing.T) {
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc, "register",
true, "neo.com", acc.PrivateKey().GetScriptHash())
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{},
"neo.com", int64(native.RecordTypeA), "1.2.3.4")
"neo.com", int64(nnsrecords.A), "1.2.3.4")
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{},
"neo.com", int64(native.RecordTypeCNAME), "alias.com")
"neo.com", int64(nnsrecords.CNAME), "alias.com")
testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc, "register",
true, "alias.com", acc.PrivateKey().GetScriptHash())
testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{},
"alias.com", int64(native.RecordTypeTXT), "sometxt")
"alias.com", int64(nnsrecords.TXT), "sometxt")
testNameServiceInvoke(t, bc, "resolve", "1.2.3.4",
"neo.com", int64(native.RecordTypeA))
"neo.com", int64(nnsrecords.A))
testNameServiceInvoke(t, bc, "resolve", "alias.com",
"neo.com", int64(native.RecordTypeCNAME))
"neo.com", int64(nnsrecords.CNAME))
testNameServiceInvoke(t, bc, "resolve", "sometxt",
"neo.com", int64(native.RecordTypeTXT))
"neo.com", int64(nnsrecords.TXT))
testNameServiceInvoke(t, bc, "resolve", stackitem.Null{},
"neo.com", int64(native.RecordTypeAAAA))
"neo.com", int64(nnsrecords.AAAA))
}
const (

97
pkg/rpc/client/helper.go Normal file
View file

@ -0,0 +1,97 @@
package client
import (
"crypto/elliptic"
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// getInvocationError returns an error in case of bad VM state or empty stack.
func getInvocationError(result *result.Invoke) error {
if result.State != "HALT" {
return fmt.Errorf("invocation failed: %s", result.FaultException)
}
if len(result.Stack) == 0 {
return errors.New("result stack is empty")
}
return nil
}
// topBoolFromStack returns the top boolean value from stack.
func topBoolFromStack(st []stackitem.Item) (bool, error) {
index := len(st) - 1 // top stack element is last in the array
result, ok := st[index].Value().(bool)
if !ok {
return false, fmt.Errorf("invalid stack item type: %s", st[index].Type())
}
return result, nil
}
// topIntFromStack returns the top integer value from stack.
func topIntFromStack(st []stackitem.Item) (int64, error) {
index := len(st) - 1 // top stack element is last in the array
bi, err := st[index].TryInteger()
if err != nil {
return 0, err
}
return bi.Int64(), nil
}
// topPublicKeysFromStack returns the top array of public keys from stack.
func topPublicKeysFromStack(st []stackitem.Item) (keys.PublicKeys, error) {
index := len(st) - 1 // top stack element is last in the array
var (
pks keys.PublicKeys
err error
)
items, ok := st[index].Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("invalid stack item type: %s", st[index].Type())
}
pks = make(keys.PublicKeys, len(items))
for i, item := range items {
val, ok := item.Value().([]byte)
if !ok {
return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type())
}
pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256())
if err != nil {
return nil, err
}
}
return pks, nil
}
// top string from stack returns the top string from stack.
func topStringFromStack(st []stackitem.Item) (string, error) {
index := len(st) - 1 // top stack element is last in the array
bs, err := st[index].TryBytes()
if err != nil {
return "", err
}
return string(bs), nil
}
// topUint160FromStack returns the top util.Uint160 from stack.
func topUint160FromStack(st []stackitem.Item) (util.Uint160, error) {
index := len(st) - 1 // top stack element is last in the array
bs, err := st[index].TryBytes()
if err != nil {
return util.Uint160{}, err
}
return util.Uint160DecodeBytesBE(bs)
}
// topMapFromStack returns the top stackitem.Map from stack.
func topMapFromStack(st []stackitem.Item) (*stackitem.Map, error) {
index := len(st) - 1 // top stack element is last in the array
if t := st[index].Type(); t != stackitem.MapT {
return nil, fmt.Errorf("invalid return stackitem type: %s", t.String())
}
return st[index].(*stackitem.Map), nil
}

View file

@ -3,14 +3,14 @@ package client
// Various non-policy things from native contracts.
import (
"crypto/elliptic"
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// GetOraclePrice invokes `getPrice` method on a native Oracle contract.
@ -66,27 +66,53 @@ func (c *Client) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.Pu
return topPublicKeysFromStack(result.Stack)
}
// topPublicKeysFromStack returns the top array of public keys from stack.
func topPublicKeysFromStack(st []stackitem.Item) (keys.PublicKeys, error) {
index := len(st) - 1 // top stack element is last in the array
var (
pks keys.PublicKeys
err error
)
items, ok := st[index].Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("invalid stack item type: %s", st[index].Type())
// NNSResolve invokes `resolve` method on a native NameService contract.
func (c *Client) NNSResolve(name string, typ nnsrecords.Type) (string, error) {
if typ == nnsrecords.CNAME {
return "", errors.New("can't resolve CNAME record type")
}
pks = make(keys.PublicKeys, len(items))
for i, item := range items {
val, ok := item.Value().([]byte)
if !ok {
return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type())
}
pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256())
if err != nil {
return nil, err
}
rmHash, err := c.GetNativeContractHash(nativenames.NameService)
if err != nil {
return "", fmt.Errorf("failed to get native NameService hash: %w", err)
}
return pks, nil
result, err := c.InvokeFunction(rmHash, "resolve", []smartcontract.Parameter{
{
Type: smartcontract.StringType,
Value: name,
},
{
Type: smartcontract.IntegerType,
Value: int64(typ),
},
}, nil)
if err != nil {
return "", err
}
err = getInvocationError(result)
if err != nil {
return "", fmt.Errorf("`resolve`: %w", err)
}
return topStringFromStack(result.Stack)
}
// NNSIsAvailable invokes `isAvailable` method on a native NameService contract.
func (c *Client) NNSIsAvailable(name string) (bool, error) {
rmHash, err := c.GetNativeContractHash(nativenames.NameService)
if err != nil {
return false, fmt.Errorf("failed to get native NameService hash: %w", err)
}
result, err := c.InvokeFunction(rmHash, "isAvailable", []smartcontract.Parameter{
{
Type: smartcontract.StringType,
Value: name,
},
}, nil)
if err != nil {
return false, err
}
err = getInvocationError(result)
if err != nil {
return false, fmt.Errorf("`isAvailable`: %w", err)
}
return topBoolFromStack(result.Stack)
}

72
pkg/rpc/client/nep.go Normal file
View file

@ -0,0 +1,72 @@
package client
import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// nepDecimals invokes `decimals` NEP* method on a specified contract.
func (c *Client) nepDecimals(tokenHash util.Uint160) (int64, error) {
result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil)
if err != nil {
return 0, err
}
err = getInvocationError(result)
if err != nil {
return 0, err
}
return topIntFromStack(result.Stack)
}
// nepSymbol invokes `symbol` NEP* method on a specified contract.
func (c *Client) nepSymbol(tokenHash util.Uint160) (string, error) {
result, err := c.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil)
if err != nil {
return "", err
}
err = getInvocationError(result)
if err != nil {
return "", err
}
return topStringFromStack(result.Stack)
}
// nepTotalSupply invokes `totalSupply` NEP* method on a specified contract.
func (c *Client) nepTotalSupply(tokenHash util.Uint160) (int64, error) {
result, err := c.InvokeFunction(tokenHash, "totalSupply", []smartcontract.Parameter{}, nil)
if err != nil {
return 0, err
}
err = getInvocationError(result)
if err != nil {
return 0, err
}
return topIntFromStack(result.Stack)
}
// nepBalanceOf invokes `balanceOf` NEP* method on a specified contract.
func (c *Client) nepBalanceOf(tokenHash, acc util.Uint160, tokenID *string) (int64, error) {
params := []smartcontract.Parameter{{
Type: smartcontract.Hash160Type,
Value: acc,
}}
if tokenID != nil {
params = append(params, smartcontract.Parameter{
Type: smartcontract.StringType,
Value: *tokenID,
})
}
result, err := c.InvokeFunction(tokenHash, "balanceOf", params, nil)
if err != nil {
return 0, err
}
err = getInvocationError(result)
if err != nil {
return 0, err
}
return topIntFromStack(result.Stack)
}

165
pkg/rpc/client/nep11.go Normal file
View file

@ -0,0 +1,165 @@
package client
import (
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
// NEP11Decimals invokes `decimals` NEP11 method on a specified contract.
func (c *Client) NEP11Decimals(tokenHash util.Uint160) (int64, error) {
return c.nepDecimals(tokenHash)
}
// NEP11Symbol invokes `symbol` NEP11 method on a specified contract.
func (c *Client) NEP11Symbol(tokenHash util.Uint160) (string, error) {
return c.nepSymbol(tokenHash)
}
// NEP11TotalSupply invokes `totalSupply` NEP11 method on a specified contract.
func (c *Client) NEP11TotalSupply(tokenHash util.Uint160) (int64, error) {
return c.nepTotalSupply(tokenHash)
}
// NEP11BalanceOf invokes `balanceOf` NEP11 method on a specified contract.
func (c *Client) NEP11BalanceOf(tokenHash, owner util.Uint160) (int64, error) {
return c.nepBalanceOf(tokenHash, owner, nil)
}
// TransferNEP11 creates an invocation transaction that invokes 'transfer' method
// on a given token to move the whole NEP11 token with the specified token ID to
// given account and sends it to the network returning just a hash of it.
func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160,
tokenHash util.Uint160, tokenID string, gas int64) (util.Uint256, error) {
if !c.initDone {
return util.Uint256{}, errNetworkNotInitialized
}
tx, err := c.createNEP11TransferTx(acc, tokenHash, gas, to, tokenID)
if err != nil {
return util.Uint256{}, err
}
if err := acc.SignTx(c.GetNetwork(), tx); err != nil {
return util.Uint256{}, fmt.Errorf("can't sign NEP11 transfer tx: %w", err)
}
return c.SendRawTransaction(tx)
}
// createNEP11TransferTx is an internal helper for TransferNEP11 and
// TransferNEP11D which creates an invocation transaction for the
// 'transfer' method of a given contract (token) to move the whole (or the
// specified amount of) NEP11 token with the specified token ID to given account
// and returns it. The returned transaction is not signed.
// `args` for TransferNEP11: to util.Uint160, tokenID string;
// `args` for TransferNEP11D: from, to util.Uint160, amount int64, tokenID string.
func (c *Client) createNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint160,
gas int64, args ...interface{}) (*transaction.Transaction, error) {
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, tokenHash, "transfer", callflag.All, args...)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
if w.Err != nil {
return nil, fmt.Errorf("failed to create NEP11 transfer script: %w", w.Err)
}
from, err := address.StringToUint160(acc.Address)
if err != nil {
return nil, fmt.Errorf("bad account address: %w", err)
}
return c.CreateTxFromScript(w.Bytes(), acc, -1, gas, []SignerAccount{{
Signer: transaction.Signer{
Account: from,
Scopes: transaction.CalledByEntry,
},
Account: acc,
}})
}
// Non-divisible NFT methods section start.
// NEP11NDOwnerOf invokes `ownerOf` non-devisible NEP11 method with the
// specified token ID on a specified contract.
func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID string) (util.Uint160, error) {
result, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{
{
Type: smartcontract.StringType,
Value: tokenID,
},
}, nil)
if err != nil {
return util.Uint160{}, err
}
err = getInvocationError(result)
if err != nil {
return util.Uint160{}, err
}
return topUint160FromStack(result.Stack)
}
// Non-divisible NFT methods section end.
// Divisible NFT methods section start.
// TransferNEP11D creates an invocation transaction that invokes 'transfer'
// method on a given token to move specified amount of divisible NEP11 assets
// (in FixedN format using contract's number of decimals) to given account and
// sends it to the network returning just a hash of it.
func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160,
tokenHash util.Uint160, amount int64, tokenID string, gas int64) (util.Uint256, error) {
if !c.initDone {
return util.Uint256{}, errNetworkNotInitialized
}
from, err := address.StringToUint160(acc.Address)
if err != nil {
return util.Uint256{}, fmt.Errorf("bad account address: %w", err)
}
tx, err := c.createNEP11TransferTx(acc, tokenHash, gas, acc.Address, from, to, amount, tokenID)
if err != nil {
return util.Uint256{}, err
}
if err := acc.SignTx(c.GetNetwork(), tx); err != nil {
return util.Uint256{}, fmt.Errorf("can't sign NEP11 divisible transfer tx: %w", err)
}
return c.SendRawTransaction(tx)
}
// NEP11DBalanceOf invokes `balanceOf` divisible NEP11 method on a
// specified contract.
func (c *Client) NEP11DBalanceOf(tokenHash, owner util.Uint160, tokenID string) (int64, error) {
return c.nepBalanceOf(tokenHash, owner, &tokenID)
}
// Divisible NFT methods section end.
// Optional NFT methods section start.
// NEP11Properties invokes `properties` optional NEP11 method on a
// specified contract.
func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID string) (*stackitem.Map, error) {
result, err := c.InvokeFunction(tokenHash, "properties", []smartcontract.Parameter{{
Type: smartcontract.StringType,
Value: tokenID,
}}, nil)
if err != nil {
return nil, err
}
err = getInvocationError(result)
if err != nil {
return nil, err
}
return topMapFromStack(result.Stack)
}
// Optional NFT methods section end.

View file

@ -1,19 +1,15 @@
package client
import (
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
@ -33,61 +29,22 @@ type SignerAccount struct {
// NEP17Decimals invokes `decimals` NEP17 method on a specified contract.
func (c *Client) NEP17Decimals(tokenHash util.Uint160) (int64, error) {
result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil)
if err != nil {
return 0, err
}
err = getInvocationError(result)
if err != nil {
return 0, fmt.Errorf("failed to get NEP17 decimals: %w", err)
}
return topIntFromStack(result.Stack)
return c.nepDecimals(tokenHash)
}
// NEP17Symbol invokes `symbol` NEP17 method on a specified contract.
func (c *Client) NEP17Symbol(tokenHash util.Uint160) (string, error) {
result, err := c.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil)
if err != nil {
return "", err
}
err = getInvocationError(result)
if err != nil {
return "", fmt.Errorf("failed to get NEP17 symbol: %w", err)
}
return topStringFromStack(result.Stack)
return c.nepSymbol(tokenHash)
}
// NEP17TotalSupply invokes `totalSupply` NEP17 method on a specified contract.
func (c *Client) NEP17TotalSupply(tokenHash util.Uint160) (int64, error) {
result, err := c.InvokeFunction(tokenHash, "totalSupply", []smartcontract.Parameter{}, nil)
if err != nil {
return 0, err
}
err = getInvocationError(result)
if err != nil {
return 0, fmt.Errorf("failed to get NEP17 total supply: %w", err)
}
return topIntFromStack(result.Stack)
return c.nepTotalSupply(tokenHash)
}
// NEP17BalanceOf invokes `balanceOf` NEP17 method on a specified contract.
func (c *Client) NEP17BalanceOf(tokenHash, acc util.Uint160) (int64, error) {
result, err := c.InvokeFunction(tokenHash, "balanceOf", []smartcontract.Parameter{{
Type: smartcontract.Hash160Type,
Value: acc,
}}, nil)
if err != nil {
return 0, err
}
err = getInvocationError(result)
if err != nil {
return 0, fmt.Errorf("failed to get NEP17 balance: %w", err)
}
return topIntFromStack(result.Stack)
return c.nepBalanceOf(tokenHash, acc, nil)
}
// NEP17TokenInfo returns full NEP17 token info.
@ -143,13 +100,9 @@ func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64, reci
if w.Err != nil {
return nil, fmt.Errorf("failed to create transfer script: %w", w.Err)
}
accAddr, err := address.StringToUint160(acc.Address)
if err != nil {
return nil, fmt.Errorf("bad account address: %v", err)
}
return c.CreateTxFromScript(w.Bytes(), acc, -1, gas, []SignerAccount{{
Signer: transaction.Signer{
Account: accAddr,
Account: from,
Scopes: transaction.CalledByEntry,
},
Account: acc,
@ -176,9 +129,6 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee,
sysFee = result.GasConsumed
}
if !c.initDone {
return nil, errNetworkNotInitialized
}
tx := transaction.New(script, sysFee)
tx.Signers = signers
@ -200,6 +150,10 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee,
// using contract's number of decimals) to given account with data specified and
// sends it to the network returning just a hash of it.
func (c *Client) TransferNEP17(acc *wallet.Account, to util.Uint160, token util.Uint160, amount int64, gas int64, data interface{}) (util.Uint256, error) {
if !c.initDone {
return util.Uint256{}, errNetworkNotInitialized
}
tx, err := c.CreateNEP17TransferTx(acc, to, token, amount, gas, data)
if err != nil {
return util.Uint256{}, err
@ -214,6 +168,10 @@ func (c *Client) TransferNEP17(acc *wallet.Account, to util.Uint160, token util.
// MultiTransferNEP17 is similar to TransferNEP17, buf allows to have multiple recipients.
func (c *Client) MultiTransferNEP17(acc *wallet.Account, gas int64, recipients []TransferTarget, data []interface{}) (util.Uint256, error) {
if !c.initDone {
return util.Uint256{}, errNetworkNotInitialized
}
tx, err := c.CreateNEP17MultiTransferTx(acc, gas, recipients, data)
if err != nil {
return util.Uint256{}, err
@ -225,32 +183,3 @@ func (c *Client) MultiTransferNEP17(acc *wallet.Account, gas int64, recipients [
return c.SendRawTransaction(tx)
}
func topIntFromStack(st []stackitem.Item) (int64, error) {
index := len(st) - 1 // top stack element is last in the array
bi, err := st[index].TryInteger()
if err != nil {
return 0, err
}
return bi.Int64(), nil
}
func topStringFromStack(st []stackitem.Item) (string, error) {
index := len(st) - 1 // top stack element is last in the array
bs, err := st[index].TryBytes()
if err != nil {
return "", err
}
return string(bs), nil
}
// getInvocationError returns an error in case of bad VM state or empty stack.
func getInvocationError(result *result.Invoke) error {
if result.State != "HALT" {
return fmt.Errorf("invocation failed: %s", result.FaultException)
}
if len(result.Stack) == 0 {
return errors.New("result stack is empty")
}
return nil
}

View file

@ -6,7 +6,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// GetFeePerByte invokes `getFeePerByte` method on a native Policy contract.
@ -80,13 +79,3 @@ func (c *Client) IsBlocked(hash util.Uint160) (bool, error) {
}
return topBoolFromStack(result.Stack)
}
// topBoolFromStack returns the top boolean value from stack
func topBoolFromStack(st []stackitem.Item) (bool, error) {
index := len(st) - 1 // top stack element is last in the array
result, ok := st[index].Value().(bool)
if !ok {
return false, fmt.Errorf("invalid stack item type: %s", st[index].Type())
}
return result, nil
}

View file

@ -8,6 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -20,6 +21,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
@ -640,3 +642,92 @@ func TestClient_GetNativeContracts(t *testing.T) {
require.NoError(t, err)
require.Equal(t, chain.GetNatives(), cs)
}
func TestClient_NEP11(t *testing.T) {
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
defer chain.Close()
defer rpcSrv.Shutdown()
c, err := client.New(context.Background(), httpSrv.URL, client.Options{})
require.NoError(t, err)
require.NoError(t, c.Init())
h, err := chain.GetNativeContractScriptHash(nativenames.NameService)
require.NoError(t, err)
acc := testchain.PrivateKeyByID(0).GetScriptHash()
t.Run("Decimals", func(t *testing.T) {
d, err := c.NEP11Decimals(h)
require.NoError(t, err)
require.EqualValues(t, 0, d) // non-divisible
})
t.Run("TotalSupply", func(t *testing.T) {
s, err := c.NEP11TotalSupply(h)
require.NoError(t, err)
require.EqualValues(t, 1, s) // the only `neo.com` of acc0
})
t.Run("Symbol", func(t *testing.T) {
sym, err := c.NEP11Symbol(h)
require.NoError(t, err)
require.Equal(t, "NNS", sym)
})
t.Run("BalanceOf", func(t *testing.T) {
b, err := c.NEP11BalanceOf(h, acc)
require.NoError(t, err)
require.EqualValues(t, 1, b)
})
t.Run("OwnerOf", func(t *testing.T) {
b, err := c.NEP11NDOwnerOf(h, "neo.com")
require.NoError(t, err)
require.EqualValues(t, acc, b)
})
t.Run("Properties", func(t *testing.T) {
p, err := c.NEP11Properties(h, "neo.com")
require.NoError(t, err)
blockRegisterDomain, err := chain.GetBlock(chain.GetHeaderHash(13)) // `neo.com` domain was registered in 13th block
require.NoError(t, err)
require.Equal(t, 1, len(blockRegisterDomain.Transactions))
expected := stackitem.NewMap()
expected.Add(stackitem.Make([]byte("name")), stackitem.Make([]byte("neo.com")))
expected.Add(stackitem.Make([]byte("expiration")), stackitem.Make(blockRegisterDomain.Timestamp/1000+365*24*3600)) // expiration formula
require.EqualValues(t, expected, p)
})
t.Run("Transfer", func(t *testing.T) {
_, err := c.TransferNEP11(wallet.NewAccountFromPrivateKey(testchain.PrivateKeyByID(0)), testchain.PrivateKeyByID(1).GetScriptHash(), h, "neo.com", 0)
require.NoError(t, err)
})
}
func TestClient_NNS(t *testing.T) {
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
defer chain.Close()
defer rpcSrv.Shutdown()
c, err := client.New(context.Background(), httpSrv.URL, client.Options{})
require.NoError(t, err)
require.NoError(t, c.Init())
t.Run("NNSIsAvailable, false", func(t *testing.T) {
b, err := c.NNSIsAvailable("neo.com")
require.NoError(t, err)
require.Equal(t, false, b)
})
t.Run("NNSIsAvailable, true", func(t *testing.T) {
b, err := c.NNSIsAvailable("neogo.com")
require.NoError(t, err)
require.Equal(t, true, b)
})
t.Run("NNSResolve, good", func(t *testing.T) {
b, err := c.NNSResolve("neo.com", nnsrecords.A)
require.NoError(t, err)
require.Equal(t, "1.2.3.4", b)
})
t.Run("NNSResolve, bad", func(t *testing.T) {
_, err := c.NNSResolve("neogo.com", nnsrecords.A)
require.Error(t, err)
})
t.Run("NNSResolve, forbidden", func(t *testing.T) {
_, err := c.NNSResolve("neogo.com", nnsrecords.CNAME)
require.Error(t, err)
})
}

View file

@ -60,7 +60,7 @@ type rpcTestCase struct {
}
const testContractHash = "c6ca2347bb84b99807221365c900ec069a265e7c"
const deploymentTxHash = "fdd4f9252cde778010d14e9710efeeb80796fd38d778e9943c1d5bb2dc656c99"
const deploymentTxHash = "26692315f71f4790263160c0f570828be77c6927493ae6657a6e5a6a09229eb9"
const genesisBlockHash = "5b60644c6c6f58faca72c70689d7ed1f40c2e795772bd0de5a88e983ad55080c"
const verifyContractHash = "5bb4bac40e961e334ba7bd36d2496010f67e246e"
@ -654,7 +654,7 @@ var rpcTestCases = map[string][]rpcTestCase{
require.True(t, ok)
expected := result.UnclaimedGas{
Address: testchain.MultisigScriptHash(),
Unclaimed: *big.NewInt(5000),
Unclaimed: *big.NewInt(7000),
}
assert.Equal(t, expected, *actual)
},
@ -1415,7 +1415,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
require.NoErrorf(t, err, "could not parse response: %s", txOut)
assert.Equal(t, *block.Transactions[0], actual.Transaction)
assert.Equal(t, 11, actual.Confirmations)
assert.Equal(t, 15, actual.Confirmations)
assert.Equal(t, TXHash, actual.Transaction.Hash())
})
@ -1533,12 +1533,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
require.NoError(t, json.Unmarshal(res, actual))
checkNep17TransfersAux(t, e, actual, sent, rcvd)
}
t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{6, 7, 8, 9}, []int{1, 2}) })
t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{8, 9, 10, 11}, []int{2, 3}) })
t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) })
t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{3, 4}, []int{0}) })
t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{6}, []int{1}) })
t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{5, 6}, []int{1}) })
t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{7, 8}, []int{2}) })
t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{5, 6}, []int{1}) })
t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{8}, []int{2}) })
t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{7, 8}, []int{2}) })
t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{9, 10}, []int{3}) })
})
}
@ -1639,8 +1639,8 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
},
{
Asset: e.chain.UtilityTokenHash(),
Amount: "68992456820",
LastUpdated: 10,
Amount: "67960042780",
LastUpdated: 14,
}},
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
}
@ -1649,7 +1649,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
}
func checkNep17Transfers(t *testing.T, e *executor, acc interface{}) {
checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, []int{0, 1, 2, 3, 4, 5, 6})
checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, []int{0, 1, 2, 3, 4, 5, 6, 7})
}
func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) {
@ -1658,6 +1658,19 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
rublesHash, err := util.Uint160DecodeStringLE(testContractHash)
require.NoError(t, err)
blockSetRecord, err := e.chain.GetBlock(e.chain.GetHeaderHash(14)) // add type A record to `neo.com` domain via NNS
require.NoError(t, err)
require.Equal(t, 1, len(blockSetRecord.Transactions))
txSetRecord := blockSetRecord.Transactions[0]
blockRegisterDomain, err := e.chain.GetBlock(e.chain.GetHeaderHash(13)) // register `neo.com` domain via NNS
require.NoError(t, err)
require.Equal(t, 1, len(blockRegisterDomain.Transactions))
txRegisterDomain := blockRegisterDomain.Transactions[0]
blockGASBounty2, err := e.chain.GetBlock(e.chain.GetHeaderHash(12)) // size of committee = 6
require.NoError(t, err)
blockDeploy3, err := e.chain.GetBlock(e.chain.GetHeaderHash(10)) // deploy verification_with_args_contract.go
require.NoError(t, err)
require.Equal(t, 1, len(blockDeploy3.Transactions))
@ -1677,7 +1690,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
require.NoError(t, err)
require.Equal(t, 1, len(blockSendRubles.Transactions))
txSendRubles := blockSendRubles.Transactions[0]
blockGASBounty := blockSendRubles // index 6 = size of committee
blockGASBounty1 := blockSendRubles // index 6 = size of committee
blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5))
require.NoError(t, err)
@ -1716,6 +1729,22 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
// duplicate the Server method.
expected := result.NEP17Transfers{
Sent: []result.NEP17Transfer{
{
Timestamp: blockSetRecord.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txSetRecord.SystemFee + txSetRecord.NetworkFee).String(),
Index: 14,
TxHash: blockSetRecord.Hash(),
},
{
Timestamp: blockRegisterDomain.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txRegisterDomain.SystemFee + txRegisterDomain.NetworkFee).String(),
Index: 13,
TxHash: blockRegisterDomain.Hash(),
},
{
Timestamp: blockDeploy3.Timestamp,
Asset: e.chain.UtilityTokenHash(),
@ -1818,13 +1847,22 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
},
Received: []result.NEP17Transfer{
{
Timestamp: blockGASBounty.Timestamp,
Timestamp: blockGASBounty2.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "",
Amount: "50000000",
Index: 12,
NotifyIndex: 0,
TxHash: blockGASBounty2.Hash(),
},
{
Timestamp: blockGASBounty1.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "",
Amount: "50000000",
Index: 6,
NotifyIndex: 0,
TxHash: blockGASBounty.Hash(),
TxHash: blockGASBounty1.Hash(),
},
{
Timestamp: blockReceiveRubles.Timestamp,

Binary file not shown.

View file

@ -94,7 +94,7 @@ func Array(w *io.BinWriter, es ...interface{}) {
Bool(w, e)
default:
if es[i] != nil {
w.Err = errors.New("unsupported type")
w.Err = fmt.Errorf("unsupported type: %v", e)
return
}
Opcodes(w, opcode.PUSHNULL)