Compare commits

...

11 commits

Author SHA1 Message Date
Anna Shaleva
8790602f69 nns: ensure records with the same type are not repeated
Port https://github.com/nspcc-dev/neofs-contract/pull/170.
2022-09-09 19:36:16 +03:00
Anna Shaleva
c9050cef4b nns: allow multiple records of the same type
Except for the CNAME records. Port
6ea4573ef8
and
f4762c1b56.
2022-09-09 19:36:13 +03:00
Anna Shaleva
c296f8804c nns: add test for getAllRecords 2022-09-09 19:35:54 +03:00
Anna Shaleva
4543de0923 *: update basic test chain
Apply new NNS rules.
2022-09-08 14:19:39 +03:00
Anna Shaleva
d77b35c385 nns: add admin to properties
See 14f43ba8cf/src/NameService/NameService.cs (L69).
2022-09-08 14:19:39 +03:00
Anna Shaleva
225152f2d7 nns: allow to resolve FQDN
Port 4041924a75.
2022-09-08 14:19:39 +03:00
Anna Shaleva
baf24d1c66 nns: check domain expiration for read functions
Port 432c02a369.
2022-09-08 14:19:39 +03:00
Anna Shaleva
017a6b9bc1 nns: require admin signature for subdomain registration
Port
14fc086291.
2022-09-08 14:19:39 +03:00
Anna Shaleva
5cb2a1219c nns: replace root with TLD
Port
4b86891d57.
2022-09-08 14:19:39 +03:00
Anna Shaleva
c11481b119 nns: allow hyphen in domain names
Port https://github.com/nspcc-dev/neofs-contract/pull/183.
2022-09-08 14:19:39 +03:00
Anna Shaleva
bd3722041a nns: adjust maxDomainNameFragmentLength
Port https://github.com/nspcc-dev/neofs-contract/pull/238.
2022-09-08 14:19:39 +03:00
7 changed files with 448 additions and 184 deletions

View file

@ -47,14 +47,17 @@ const (
maxRegisterPrice = 1_0000_0000_0000
// maxRootLength is the maximum domain root length.
maxRootLength = 16
// maxDomainNameFragmentLength is the maximum length of the domain name fragment.
maxDomainNameFragmentLength = 62
// maxDomainNameFragmentLength is the maximum length of the domain name fragment
maxDomainNameFragmentLength = 63
// minDomainNameLength is minimum domain length.
minDomainNameLength = 3
// maxDomainNameLength is maximum domain length.
maxDomainNameLength = 255
// maxTXTRecordLength is the maximum length of the TXT domain record.
maxTXTRecordLength = 255
// maxRecordID is the maximum value of record ID (the upper bound for the number
// of records with the same type).
maxRecordID = 255
)
// Other constants.
@ -70,6 +73,7 @@ type RecordState struct {
Name string
Type RecordType
Data string
ID byte
}
// Update updates NameService contract.
@ -118,6 +122,7 @@ func Properties(tokenID []byte) map[string]interface{} {
return map[string]interface{}{
"name": ns.Name,
"expiration": ns.Expiration,
"admin": ns.Admin,
}
}
@ -179,22 +184,6 @@ func Transfer(to interop.Hash160, tokenID []byte, data interface{}) bool {
return true
}
// AddRoot registers new root.
func AddRoot(root string) {
checkCommittee()
if !checkFragment(root, true) {
panic("invalid root format")
}
var (
ctx = storage.GetContext()
rootKey = append([]byte{prefixRoot}, []byte(root)...)
)
if storage.Get(ctx, rootKey) != nil {
panic("root already exists")
}
storage.Put(ctx, rootKey, 0)
}
// Roots returns iterator over a set of NameService roots.
func Roots() iterator.Iterator {
ctx := storage.GetReadOnlyContext()
@ -224,15 +213,36 @@ func IsAvailable(name string) bool {
panic("invalid domain name format")
}
ctx := storage.GetReadOnlyContext()
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[1])...)) == nil {
panic("root not found")
l := len(fragments)
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil {
if l != 1 {
panic("TLD not found")
}
return true
}
return parentExpired(ctx, 0, fragments)
}
// parentExpired returns true if any domain from fragments doesn't exist or expired.
// first denotes the deepest subdomain to check.
func parentExpired(ctx storage.Context, first int, fragments []string) bool {
now := runtime.GetTime()
last := len(fragments) - 1
name := fragments[last]
for i := last; i >= first; i-- {
if i != last {
name = fragments[i] + "." + name
}
nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...))
if nsBytes == nil {
return true
}
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
return runtime.GetTime() >= ns.Expiration
if now >= ns.Expiration {
return true
}
}
return false
}
// Register registers new domain with the specified owner and name if it's available.
@ -241,9 +251,27 @@ func Register(name string, owner interop.Hash160) bool {
if fragments == nil {
panic("invalid domain name format")
}
l := len(fragments)
tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...)
ctx := storage.GetContext()
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[1])...)) == nil {
panic("root not found")
tldBytes := storage.Get(ctx, tldKey)
if l == 1 {
checkCommittee()
if tldBytes != nil {
panic("TLD already exists")
}
storage.Put(ctx, tldKey, 0)
} else {
if tldBytes == nil {
panic("TLD not found")
}
if parentExpired(ctx, 1, fragments) {
panic("one of the parent domains has expired")
}
parentKey := getTokenKey([]byte(fragments[1]))
nsBytes := storage.Get(ctx, append([]byte{prefixName}, parentKey...))
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
ns.checkAdmin()
}
if !isValid(owner) {
@ -313,8 +341,43 @@ func SetAdmin(name string, admin interop.Hash160) {
putNameState(ctx, ns)
}
// SetRecord adds new record of the specified type to the provided domain.
func SetRecord(name string, typ RecordType, data string) {
// SetRecord updates record of the specified type and ID.
func SetRecord(name string, typ RecordType, id byte, data string) {
ctx := storage.GetContext()
tokenID := checkRecord(ctx, name, typ, data)
recordKey := getRecordKey(tokenID, name, typ, id)
recBytes := storage.Get(ctx, recordKey)
if recBytes == nil {
panic("unknown record")
}
putRecord(ctx, tokenID, name, typ, id, data)
}
// AddRecord adds new record of the specified type to the provided domain.
func AddRecord(name string, typ RecordType, data string) {
ctx := storage.GetContext()
tokenID := checkRecord(ctx, name, typ, data)
recordsPrefix := getRecordsByTypePrefix(tokenID, name, typ)
var id byte
records := storage.Find(ctx, recordsPrefix, storage.ValuesOnly|storage.DeserializeValues)
for iterator.Next(records) {
r := iterator.Value(records).(RecordState)
if r.Name == name && r.Type == typ && r.Data == data {
panic("record already exists")
}
id++
}
if id > maxRecordID {
panic("maximum number of records reached")
}
if typ == CNAME && id != 0 {
panic("multiple CNAME records")
}
putRecord(ctx, tokenID, name, typ, id, data)
}
// checkRecord performs record validness check and returns token ID.
func checkRecord(ctx storage.Context, name string, typ RecordType, data string) []byte {
tokenID := []byte(tokenIDFromName(name))
var ok bool
switch typ {
@ -332,44 +395,46 @@ func SetRecord(name string, typ RecordType, data string) {
if !ok {
panic("invalid record data")
}
ctx := storage.GetContext()
ns := getNameState(ctx, tokenID)
ns.checkAdmin()
putRecord(ctx, tokenID, name, typ, data)
return tokenID
}
// GetRecord returns domain record of the specified type if it exists or an empty
// string if not.
func GetRecord(name string, typ RecordType) string {
// GetRecords returns domain records of the specified type if they exist or an empty
// array if not.
func GetRecords(name string, typ RecordType) []string {
tokenID := []byte(tokenIDFromName(name))
ctx := storage.GetReadOnlyContext()
_ = getNameState(ctx, tokenID) // ensure not expired
return getRecord(ctx, tokenID, name, typ)
return getRecordsByType(ctx, tokenID, name, typ)
}
// DeleteRecord removes domain record with the specified type.
func DeleteRecord(name string, typ RecordType) {
// DeleteRecords removes all domain records with the specified type.
func DeleteRecords(name string, typ RecordType) {
tokenID := []byte(tokenIDFromName(name))
ctx := storage.GetContext()
ns := getNameState(ctx, tokenID)
ns.checkAdmin()
recordKey := getRecordKey(tokenID, name, typ)
storage.Delete(ctx, recordKey)
recordsPrefix := getRecordsByTypePrefix(tokenID, name, typ)
records := storage.Find(ctx, recordsPrefix, storage.KeysOnly)
for iterator.Next(records) {
key := iterator.Value(records).(string)
storage.Delete(ctx, key)
}
}
// Resolve resolves given name (not more then three redirects are allowed).
func Resolve(name string, typ RecordType) string {
// Resolve resolves given name (not more than three redirects are allowed) to a set
// of domain records.
func Resolve(name string, typ RecordType) []string {
ctx := storage.GetReadOnlyContext()
return resolve(ctx, name, typ, 2)
res := []string{}
return resolve(ctx, res, name, typ, 2)
}
// GetAllRecords returns an Iterator with RecordState items for given name.
func GetAllRecords(name string) iterator.Iterator {
tokenID := []byte(tokenIDFromName(name))
ctx := storage.GetReadOnlyContext()
_ = getNameState(ctx, tokenID) // ensure not expired
recordsKey := getRecordsKey(tokenID, name)
return storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues)
return getAllRecords(ctx, name)
}
// updateBalance updates account's balance and account's tokens.
@ -425,7 +490,12 @@ func getTokenKey(tokenID []byte) []byte {
// getNameState returns domain name state by the specified tokenID.
func getNameState(ctx storage.Context, tokenID []byte) NameState {
tokenKey := getTokenKey(tokenID)
return getNameStateWithKey(ctx, tokenKey)
ns := getNameStateWithKey(ctx, tokenKey)
fragments := std.StringSplit(string(tokenID), ".")
if parentExpired(ctx, 1, fragments) {
panic("parent domain has expired")
}
return ns
}
// getNameStateWithKey returns domain name state by the specified token key.
@ -453,41 +523,53 @@ func putNameStateWithKey(ctx storage.Context, tokenKey []byte, ns NameState) {
storage.Put(ctx, nameKey, nsBytes)
}
// getRecord returns domain record.
func getRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType) string {
recordKey := getRecordKey(tokenId, name, typ)
recBytes := storage.Get(ctx, recordKey)
if recBytes == nil {
return recBytes.(string) // A hack to actually return NULL.
// getRecordsByType returns domain records of the specified type or an empty array if no records found.
func getRecordsByType(ctx storage.Context, tokenId []byte, name string, typ RecordType) []string {
recordsPrefix := getRecordsByTypePrefix(tokenId, name, typ)
records := storage.Find(ctx, recordsPrefix, storage.ValuesOnly|storage.DeserializeValues)
res := []string{} // return empty slice if no records was found.
for iterator.Next(records) {
r := iterator.Value(records).(RecordState)
if r.Type == typ {
res = append(res, r.Data)
}
record := std.Deserialize(recBytes.([]byte)).(RecordState)
return record.Data
}
return res
}
// putRecord stores domain record.
func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, record string) {
recordKey := getRecordKey(tokenId, name, typ)
// putRecord puts the specified record to the contract storage without any additional checks.
func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, id byte, data string) {
recordKey := getRecordKey(tokenId, name, typ, id)
rs := RecordState{
Name: name,
Type: typ,
Data: record,
Data: data,
ID: id,
}
recBytes := std.Serialize(rs)
storage.Put(ctx, recordKey, recBytes)
}
// getRecordsKey returns prefix used to store domain records of different types.
func getRecordsKey(tokenId []byte, name string) []byte {
recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...)
return append(recordKey, getTokenKey([]byte(name))...)
// getRecordKey returns key used to store domain record with the specified type and ID.
// This key always have a single corresponding value.
func getRecordKey(tokenId []byte, name string, typ RecordType, id byte) []byte {
prefix := getRecordsByTypePrefix(tokenId, name, typ)
return append(prefix, id)
}
// getRecordKey returns key used to store domain records.
func getRecordKey(tokenId []byte, name string, typ RecordType) []byte {
recordKey := getRecordsKey(tokenId, name)
// getRecordsByTypePrefix returns prefix used to store domain records with the
// specified type of different IDs.
func getRecordsByTypePrefix(tokenId []byte, name string, typ RecordType) []byte {
recordKey := getRecordsPrefix(tokenId, name)
return append(recordKey, []byte{byte(typ)}...)
}
// getRecordsPrefix returns prefix used to store domain records of different types.
func getRecordsPrefix(tokenId []byte, name string) []byte {
recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...)
return append(recordKey, getTokenKey([]byte(name))...)
}
// isValid returns true if the provided address is a valid Uint160.
func isValid(address interop.Hash160) bool {
return address != nil && len(address) == 20
@ -507,6 +589,8 @@ func checkCommittee() {
}
// checkFragment validates root or a part of domain name.
// 1. Root domain must start with a letter.
// 2. All other fragments must start and end in a letter or a digit.
func checkFragment(v string, isRoot bool) bool {
maxLength := maxDomainNameFragmentLength
if isRoot {
@ -525,12 +609,12 @@ func checkFragment(v string, isRoot bool) bool {
return false
}
}
for i := 1; i < len(v); i++ {
if !isAlNum(v[i]) {
for i := 1; i < len(v)-1; i++ {
if v[i] != '-' && !isAlNum(v[i]) {
return false
}
}
return true
return isAlNum(v[len(v)-1])
}
// isAlNum checks whether provided char is a lowercase letter or a number.
@ -546,9 +630,6 @@ func splitAndCheck(name string, allowMultipleFragments bool) []string {
}
fragments := std.StringSplit(name, ".")
l = len(fragments)
if l < 2 {
return nil
}
if l > 2 && !allowMultipleFragments {
return nil
}
@ -677,42 +758,51 @@ func tokenIDFromName(name string) string {
panic("invalid domain name format")
}
l := len(fragments)
if l == 1 {
return name
}
return name[len(name)-(len(fragments[l-1])+len(fragments[l-2])+1):]
}
// resolve resolves provided name using record with the specified type and given
// maximum redirections constraint.
func resolve(ctx storage.Context, name string, typ RecordType, redirect int) string {
func resolve(ctx storage.Context, res []string, name string, typ RecordType, redirect int) []string {
if redirect < 0 {
panic("invalid redirect")
}
records := getRecords(ctx, name)
if len(name) == 0 {
panic("invalid name")
}
if name[len(name)-1] == '.' {
name = name[:len(name)-1]
}
records := getAllRecords(ctx, name)
cname := ""
for iterator.Next(records) {
r := iterator.Value(records).(struct {
key string
rs RecordState
})
value := r.rs.Data
rTyp := r.key[len(r.key)-1]
if rTyp == byte(typ) {
return value
r := iterator.Value(records).(RecordState)
if r.Type == typ {
res = append(res, r.Data)
}
if rTyp == byte(CNAME) {
cname = value
if r.Type == CNAME {
cname = r.Data
}
}
if cname == "" {
return string([]byte(nil))
if cname == "" || typ == CNAME {
return res
}
return resolve(ctx, cname, typ, redirect-1)
// TODO: the line below must be removed from the neofs nns:
// res = append(res, cname)
// @roman-khimov, it is done in a separate commit in neofs-contracts repo, is it OK?
return resolve(ctx, res, cname, typ, redirect-1)
}
// getRecords returns iterator over the set of records corresponded with the
// specified name.
func getRecords(ctx storage.Context, name string) iterator.Iterator {
// getAllRecords returns iterator over the set of records corresponded with the
// specified name. Records returned are of different types and/or different IDs.
// No keys are returned.
func getAllRecords(ctx storage.Context, name string) iterator.Iterator {
tokenID := []byte(tokenIDFromName(name))
_ = getNameState(ctx, tokenID)
recordsKey := getRecordsKey(tokenID, name)
return storage.Find(ctx, recordsKey, storage.DeserializeValues)
_ = getNameState(ctx, tokenID) // ensure not expired.
recordsPrefix := getRecordsPrefix(tokenID, name)
return storage.Find(ctx, recordsPrefix, storage.ValuesOnly|storage.DeserializeValues)
}

View file

@ -2,7 +2,7 @@ name: "NameService"
sourceurl: https://github.com/nspcc-dev/neo-go/
supportedstandards: ["NEP-11"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecord",
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecords",
"resolve", "getAllRecords"]
events:
- name: Transfer

View file

@ -1,6 +1,8 @@
package nns_test
import (
"math/big"
"strconv"
"strings"
"testing"
@ -9,6 +11,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"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"
"github.com/stretchr/testify/require"
@ -70,21 +73,21 @@ func TestNonfungible(t *testing.T) {
c.Invoke(t, 0, "totalSupply")
}
func TestAddRoot(t *testing.T) {
func TestRegisterTLD(t *testing.T) {
c := newNSClient(t)
t.Run("invalid format", func(t *testing.T) {
c.InvokeFail(t, "invalid root format", "addRoot", "")
c.InvokeFail(t, "invalid domain name format", "register", "", c.CommitteeHash)
})
t.Run("not signed by committee", func(t *testing.T) {
acc := c.NewAccount(t)
c := c.WithSigners(acc)
c.InvokeFail(t, "not witnessed by committee", "addRoot", "some")
c.InvokeFail(t, "not witnessed by committee", "register", "some", c.CommitteeHash)
})
c.Invoke(t, stackitem.Null{}, "addRoot", "some")
c.Invoke(t, true, "register", "some", c.CommitteeHash)
t.Run("already exists", func(t *testing.T) {
c.InvokeFail(t, "already exists", "addRoot", "some")
c.InvokeFail(t, "TLD already exists", "register", "some", c.CommitteeHash)
})
}
@ -95,19 +98,20 @@ func TestExpiration(t *testing.T) {
acc := e.NewAccount(t)
cAcc := c.WithSigners(acc)
cAccCommittee := c.WithSigners(acc, c.Committee) // acc + committee signers for ".com"'s subdomains registration
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cAccCommittee.Invoke(t, true, "register", "first.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "first.com", int64(nns.TXT), "sometext")
b1 := e.TopBlock(t)
tx := cAcc.PrepareInvoke(t, "register", "second.com", acc.ScriptHash())
tx := cAccCommittee.PrepareInvoke(t, "register", "second.com", acc.ScriptHash())
b2 := e.NewUnsignedBlock(t, tx)
b2.Index = b1.Index + 1
b2.PrevHash = b1.Hash()
b2.Timestamp = b1.Timestamp + 10000
require.NoError(t, bc.AddBlock(e.SignBlock(b2)))
e.CheckHalt(t, tx.Hash())
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true))
tx = cAcc.PrepareInvoke(t, "isAvailable", "first.com")
b3 := e.NewUnsignedBlock(t, tx)
@ -115,7 +119,7 @@ func TestExpiration(t *testing.T) {
b3.PrevHash = b2.Hash()
b3.Timestamp = b1.Timestamp + (millisecondsInYear + 1)
require.NoError(t, bc.AddBlock(e.SignBlock(b3)))
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true))
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true)) // "first.com" has been expired
tx = cAcc.PrepareInvoke(t, "isAvailable", "second.com")
b4 := e.NewUnsignedBlock(t, tx)
@ -123,9 +127,9 @@ func TestExpiration(t *testing.T) {
b4.PrevHash = b3.Hash()
b4.Timestamp = b3.Timestamp + 1000
require.NoError(t, bc.AddBlock(e.SignBlock(b4)))
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(false))
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true)) // TLD "com" has been expired
tx = cAcc.PrepareInvoke(t, "getRecord", "first.com", int64(nns.TXT))
tx = cAcc.PrepareInvoke(t, "getRecords", "first.com", int64(nns.TXT))
b5 := e.NewUnsignedBlock(t, tx)
b5.Index = b4.Index + 1
b5.PrevHash = b4.Hash()
@ -133,20 +137,27 @@ func TestExpiration(t *testing.T) {
require.NoError(t, bc.AddBlock(e.SignBlock(b5)))
e.CheckFault(t, tx.Hash(), "name has expired")
cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash()) // Re-register.
cAcc.Invoke(t, stackitem.Null{}, "resolve", "first.com", int64(nns.TXT))
// TODO: According to the new code, we can't re-register expired "com" TLD, because it's already registered; at the
// same time we can't renew it because it's already expired. We likely need to change this logic in the contract and
// after that uncomment the lines below.
// c.Invoke(t, true, "renew", "com")
// cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash()) // Re-register.
// cAcc.Invoke(t, stackitem.Null{}, "resolve", "first.com", int64(nns.TXT))
}
const millisecondsInYear = 365 * 24 * 3600 * 1000
const (
millisecondsInYear = 365 * 24 * 3600 * 1000
maxDomainNameFragmentLength = 63
)
func TestRegisterAndRenew(t *testing.T) {
c := newNSClient(t)
e := c.Executor
c.InvokeFail(t, "root not found", "isAvailable", "neo.com")
c.Invoke(t, stackitem.Null{}, "addRoot", "org")
c.InvokeFail(t, "root not found", "isAvailable", "neo.com")
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com")
c.Invoke(t, true, "register", "org", c.CommitteeHash)
c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
c.Invoke(t, true, "isAvailable", "neo.com")
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash)
c.InvokeFail(t, "invalid domain name format", "register", "docs.neo.org", e.CommitteeHash)
@ -154,20 +165,34 @@ func TestRegisterAndRenew(t *testing.T) {
c.InvokeFail(t, "invalid domain name format", "register", "neo.com\n", e.CommitteeHash)
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash)
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceDomainPrice, "register", "neo.com", e.CommitteeHash)
var maxLenFragment string
for i := 0; i < maxDomainNameFragmentLength; i++ {
maxLenFragment += "q"
}
c.Invoke(t, true, "isAvailable", maxLenFragment+".com")
c.Invoke(t, true, "register", maxLenFragment+".com", e.CommitteeHash)
c.InvokeFail(t, "invalid domain name format", "register", maxLenFragment+"q.com", e.CommitteeHash)
c.Invoke(t, true, "isAvailable", "neo.com")
c.Invoke(t, 0, "balanceOf", e.CommitteeHash)
c.Invoke(t, 3, "balanceOf", e.CommitteeHash) // org, com, qqq...qqq.com
c.Invoke(t, true, "register", "neo.com", e.CommitteeHash)
topBlock := e.TopBlock(t)
expectedExpiration := topBlock.Timestamp + millisecondsInYear
c.Invoke(t, false, "register", "neo.com", e.CommitteeHash)
c.Invoke(t, false, "isAvailable", "neo.com")
t.Run("domain names with hyphen", func(t *testing.T) {
c.InvokeFail(t, "invalid domain name format", "register", "-testdomain.com", e.CommitteeHash)
c.InvokeFail(t, "invalid domain name format", "register", "testdomain-.com", e.CommitteeHash)
c.Invoke(t, true, "register", "test-domain.com", e.CommitteeHash)
})
props := stackitem.NewMap()
props.Add(stackitem.Make("name"), stackitem.Make("neo.com"))
props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration))
props.Add(stackitem.Make("admin"), stackitem.Null{}) // no admin was set
c.Invoke(t, props, "properties", "neo.com")
c.Invoke(t, 1, "balanceOf", e.CommitteeHash)
c.Invoke(t, 5, "balanceOf", e.CommitteeHash) // org, com, qqq...qqq.com, neo.com, test-domain.com
c.Invoke(t, e.CommitteeHash.BytesBE(), "ownerOf", []byte("neo.com"))
t.Run("invalid token ID", func(t *testing.T) {
@ -185,42 +210,59 @@ func TestRegisterAndRenew(t *testing.T) {
c.Invoke(t, props, "properties", "neo.com")
}
func TestSetGetRecord(t *testing.T) {
func TestSetAddGetRecord(t *testing.T) {
c := newNSClient(t)
e := c.Executor
acc := e.NewAccount(t)
cAcc := c.WithSigners(acc)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
t.Run("set before register", func(t *testing.T) {
c.InvokeFail(t, "token not found", "setRecord", "neo.com", int64(nns.TXT), "sometext")
c.InvokeFail(t, "token not found", "addRecord", "neo.com", int64(nns.TXT), "sometext")
})
c.Invoke(t, true, "register", "neo.com", e.CommitteeHash)
t.Run("invalid parameters", func(t *testing.T) {
c.InvokeFail(t, "unsupported record type", "setRecord", "neo.com", int64(0xFF), "1.2.3.4")
c.InvokeFail(t, "invalid record", "setRecord", "neo.com", int64(nns.A), "not.an.ip.address")
c.InvokeFail(t, "unsupported record type", "addRecord", "neo.com", int64(0xFF), "1.2.3.4")
c.InvokeFail(t, "invalid record", "addRecord", "neo.com", int64(nns.A), "not.an.ip.address")
})
t.Run("invalid witness", func(t *testing.T) {
cAcc.InvokeFail(t, "not witnessed by admin", "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
cAcc.InvokeFail(t, "not witnessed by admin", "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
})
c.Invoke(t, stackitem.Null{}, "getRecord", "neo.com", int64(nns.A))
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A))
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A))
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.AAAA), "2001:0201:1f1f:0000:0000:0100:11a0:11df")
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "nspcc.ru")
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
c.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "getRecords", "neo.com", int64(nns.A))
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "getRecords", "neo.com", int64(nns.A))
c.InvokeFail(t, "record already exists", "addRecord", "neo.com", int64(nns.A), "1.2.3.4") // Duplicating record.
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "getRecords", "neo.com", int64(nns.A))
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.AAAA), "2001:0201:1f1f:0000:0000:0100:11a0:11df")
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.CNAME), "nspcc.ru")
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext")
// Add multiple records and update some of them.
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext1")
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext2")
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext3")
c.Invoke(t, stackitem.NewArray([]stackitem.Item{
stackitem.Make("sometext"),
stackitem.Make("sometext1"),
stackitem.Make("sometext2"),
stackitem.Make("sometext3"),
}), "getRecords", "neo.com", int64(nns.TXT))
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), 2, "sometext22")
c.Invoke(t, stackitem.NewArray([]stackitem.Item{
stackitem.Make("sometext"),
stackitem.Make("sometext1"),
stackitem.Make("sometext22"),
stackitem.Make("sometext3"),
}), "getRecords", "neo.com", int64(nns.TXT))
// Delete record.
t.Run("invalid witness", func(t *testing.T) {
cAcc.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.CNAME))
cAcc.InvokeFail(t, "not witnessed by admin", "deleteRecords", "neo.com", int64(nns.CNAME))
})
c.Invoke(t, "nspcc.ru", "getRecord", "neo.com", int64(nns.CNAME))
c.Invoke(t, stackitem.Null{}, "deleteRecord", "neo.com", int64(nns.CNAME))
c.Invoke(t, stackitem.Null{}, "getRecord", "neo.com", int64(nns.CNAME))
c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A))
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("nspcc.ru")}), "getRecords", "neo.com", int64(nns.CNAME))
c.Invoke(t, stackitem.Null{}, "deleteRecords", "neo.com", int64(nns.CNAME))
c.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "getRecords", "neo.com", int64(nns.CNAME))
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "getRecords", "neo.com", int64(nns.A))
t.Run("SetRecord_compatibility", func(t *testing.T) {
// tests are got from the NNS C# implementation and changed accordingly to non-native implementation behavior
@ -280,9 +322,10 @@ func TestSetGetRecord(t *testing.T) {
args := []interface{}{"neo.com", int64(testCase.Type), testCase.Name}
t.Run(testCase.Name, func(t *testing.T) {
if testCase.ShouldFail {
c.InvokeFail(t, "", "setRecord", args...)
c.InvokeFail(t, "", "addRecord", args...)
} else {
c.Invoke(t, stackitem.Null{}, "setRecord", args...)
c.Invoke(t, stackitem.Null{}, "addRecord", args...)
c.Invoke(t, stackitem.Null{}, "deleteRecords", "neo.com", int64(testCase.Type)) // clear records after test to avoid duplicating records.
}
})
}
@ -295,14 +338,17 @@ func TestSetAdmin(t *testing.T) {
owner := e.NewAccount(t)
cOwner := c.WithSigners(owner)
cOwnerCommittee := c.WithSigners(owner, c.Committee)
admin := e.NewAccount(t)
cAdmin := c.WithSigners(admin)
guest := e.NewAccount(t)
cGuest := c.WithSigners(guest)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cOwner.Invoke(t, true, "register", "neo.com", owner.ScriptHash())
cOwner.InvokeFail(t, "not witnessed by admin", "register", "neo.com", owner.ScriptHash()) // admin is committee
cOwnerCommittee.Invoke(t, true, "register", "neo.com", owner.ScriptHash())
expectedExpiration := e.TopBlock(t).Timestamp + millisecondsInYear
cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.ScriptHash())
// Must be witnessed by both owner and admin.
@ -310,17 +356,22 @@ func TestSetAdmin(t *testing.T) {
cAdmin.InvokeFail(t, "not witnessed by owner", "setAdmin", "neo.com", admin.ScriptHash())
cc := c.WithSigners(owner, admin)
cc.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", admin.ScriptHash())
props := stackitem.NewMap()
props.Add(stackitem.Make("name"), stackitem.Make("neo.com"))
props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration))
props.Add(stackitem.Make("admin"), stackitem.Make(admin.ScriptHash().BytesBE()))
c.Invoke(t, props, "properties", "neo.com")
t.Run("set and delete by admin", func(t *testing.T) {
cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
cGuest.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.TXT))
cAdmin.Invoke(t, stackitem.Null{}, "deleteRecord", "neo.com", int64(nns.TXT))
cAdmin.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext")
cGuest.InvokeFail(t, "not witnessed by admin", "deleteRecords", "neo.com", int64(nns.TXT))
cAdmin.Invoke(t, stackitem.Null{}, "deleteRecords", "neo.com", int64(nns.TXT))
})
t.Run("set admin to null", func(t *testing.T) {
cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
cAdmin.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "sometext")
cOwner.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", nil)
cAdmin.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.TXT))
cAdmin.InvokeFail(t, "not witnessed by admin", "deleteRecords", "neo.com", int64(nns.TXT))
})
}
@ -330,16 +381,17 @@ func TestTransfer(t *testing.T) {
from := e.NewAccount(t)
cFrom := c.WithSigners(from)
cFromCommittee := c.WithSigners(from, c.Committee)
to := e.NewAccount(t)
cTo := c.WithSigners(to)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
cFrom.Invoke(t, true, "register", "neo.com", from.ScriptHash())
cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cFromCommittee.Invoke(t, true, "register", "neo.com", from.ScriptHash())
cFrom.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
cFrom.InvokeFail(t, "token not found", "transfer", to.ScriptHash(), "not.exists", nil)
c.Invoke(t, false, "transfer", to.ScriptHash(), "neo.com", nil)
cFrom.Invoke(t, true, "transfer", to.ScriptHash(), "neo.com", nil)
cFrom.Invoke(t, 1, "totalSupply")
cFrom.Invoke(t, 2, "totalSupply") // com, neo.com
cFrom.Invoke(t, to.ScriptHash().BytesBE(), "ownerOf", "neo.com")
// without onNEP11Transfer
@ -358,7 +410,7 @@ func TestTransfer(t *testing.T) {
&compiler.Options{Name: "foo"})
e.DeployContract(t, ctr, nil)
cTo.Invoke(t, true, "transfer", ctr.Hash, []byte("neo.com"), nil)
cFrom.Invoke(t, 1, "totalSupply")
cFrom.Invoke(t, 2, "totalSupply") // com, neo.com
cFrom.Invoke(t, ctr.Hash.BytesBE(), "ownerOf", []byte("neo.com"))
}
@ -367,21 +419,22 @@ func TestTokensOf(t *testing.T) {
e := c.Executor
acc1 := e.NewAccount(t)
cAcc1 := c.WithSigners(acc1)
cAcc1Committee := c.WithSigners(acc1, c.Committee)
acc2 := e.NewAccount(t)
cAcc2 := c.WithSigners(acc2)
cAcc2Committee := c.WithSigners(acc2, c.Committee)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
cAcc1.Invoke(t, true, "register", "neo.com", acc1.ScriptHash())
cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash())
tld := []byte("com")
c.Invoke(t, true, "register", tld, c.CommitteeHash)
cAcc1Committee.Invoke(t, true, "register", "neo.com", acc1.ScriptHash())
cAcc2Committee.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash())
testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
testTokensOf(t, c, [][]byte{[]byte("neo.com"), []byte("nspcc.com")})
testTokensOf(t, c, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still
testTokensOf(t, c, tld, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
testTokensOf(t, c, tld, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
testTokensOf(t, c, tld, [][]byte{[]byte("neo.com"), []byte("nspcc.com")})
testTokensOf(t, c, tld, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still
}
func testTokensOf(t *testing.T, c *neotest.ContractInvoker, result [][]byte, args ...interface{}) {
func testTokensOf(t *testing.T, c *neotest.ContractInvoker, tld []byte, result [][]byte, args ...interface{}) {
method := "tokensOf"
if len(args) == 0 {
method = "tokens"
@ -399,7 +452,12 @@ func testTokensOf(t *testing.T, c *neotest.ContractInvoker, result [][]byte, arg
require.Equal(t, result[i], iter.Value().Value())
arr = append(arr, stackitem.Make(result[i]))
}
if method == "tokens" {
require.True(t, iter.Next())
require.Equal(t, tld, iter.Value().Value())
} else {
require.False(t, iter.Next())
}
}
func TestResolve(t *testing.T) {
@ -408,22 +466,117 @@ func TestResolve(t *testing.T) {
acc := e.NewAccount(t)
cAcc := c.WithSigners(acc)
cAccCommittee := c.WithSigners(acc, c.Committee)
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
cAcc.Invoke(t, true, "register", "neo.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com")
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.CNAME), "alias.com")
cAcc.Invoke(t, true, "register", "alias.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "alias.com", int64(nns.TXT), "sometxt")
cAccCommittee.Invoke(t, true, "register", "alias.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias.com", int64(nns.TXT), "sometxt from alias1")
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias.com", int64(nns.CNAME), "alias2.com")
c.Invoke(t, "1.2.3.4", "resolve", "neo.com", int64(nns.A))
c.Invoke(t, "alias.com", "resolve", "neo.com", int64(nns.CNAME))
c.Invoke(t, "sometxt", "resolve", "neo.com", int64(nns.TXT))
c.Invoke(t, stackitem.Null{}, "resolve", "neo.com", int64(nns.AAAA))
cAccCommittee.Invoke(t, true, "register", "alias2.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias2.com", int64(nns.TXT), "sometxt from alias2")
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "resolve", "neo.com", int64(nns.A))
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "resolve", "neo.com.", int64(nns.A))
c.InvokeFail(t, "invalid domain name format", "resolve", "neo.com..", int64(nns.A))
// Check CNAME is properly resolved and is not included into the result.
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("sometxt from alias1"), stackitem.Make("sometxt from alias2")}), "resolve", "neo.com", int64(nns.TXT))
// Check CNAME is included into the result and is not resolved.
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("alias.com")}), "resolve", "neo.com", int64(nns.CNAME))
// Empty result.
c.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "resolve", "neo.com", int64(nns.AAAA))
}
func TestGetAllRecords(t *testing.T) {
c := newNSClient(t)
e := c.Executor
acc := e.NewAccount(t)
cAcc := c.WithSigners(acc)
cAccCommittee := c.WithSigners(acc, c.Committee)
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.CNAME), "alias.com")
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), "bla0")
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), 0, "bla1") // overwrite
// Add some arbitrary data.
cAccCommittee.Invoke(t, true, "register", "alias.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias.com", int64(nns.TXT), "sometxt")
script, err := smartcontract.CreateCallAndUnwrapIteratorScript(c.Hash, "getAllRecords", 10, "neo.com")
require.NoError(t, err)
h := e.InvokeScript(t, script, []neotest.Signer{acc})
e.CheckHalt(t, h, stackitem.NewArray([]stackitem.Item{
stackitem.NewStruct([]stackitem.Item{
stackitem.NewByteArray([]byte("neo.com")),
stackitem.Make(nns.A),
stackitem.NewByteArray([]byte("1.2.3.4")),
stackitem.NewBigInteger(big.NewInt(0)),
}),
stackitem.NewStruct([]stackitem.Item{
stackitem.NewByteArray([]byte("neo.com")),
stackitem.Make(nns.CNAME),
stackitem.NewByteArray([]byte("alias.com")),
stackitem.NewBigInteger(big.NewInt(0)),
}),
stackitem.NewStruct([]stackitem.Item{
stackitem.NewByteArray([]byte("neo.com")),
stackitem.Make(nns.TXT),
stackitem.NewByteArray([]byte("bla1")),
stackitem.NewBigInteger(big.NewInt(0)),
}),
}))
}
func TestGetRecords(t *testing.T) {
c := newNSClient(t)
e := c.Executor
acc := e.NewAccount(t)
cAcc := c.WithSigners(acc)
cAccCommittee := c.WithSigners(acc, c.Committee)
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.CNAME), "alias.com")
// Add some arbitrary data.
cAccCommittee.Invoke(t, true, "register", "alias.com", acc.ScriptHash())
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias.com", int64(nns.TXT), "sometxt")
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "getRecords", "neo.com", int64(nns.A))
// Check empty result of `getRecords`.
c.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "getRecords", "neo.com", int64(nns.AAAA))
}
func TestNNSAddRecord(t *testing.T) {
c := newNSClient(t)
cAccCommittee := c.WithSigners(c.Committee)
c.Invoke(t, true, "register", "com", c.CommitteeHash)
cAccCommittee.Invoke(t, true, "register", "neo.com", c.CommitteeHash)
for i := 0; i <= maxRecordID+1; i++ {
if i == maxRecordID+1 {
c.InvokeFail(t, "maximum number of records reached", "addRecord", "neo.com", int64(nns.TXT), strconv.Itoa(i))
} else {
c.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.TXT), strconv.Itoa(i))
}
}
}
const (
defaultNameServiceDomainPrice = 10_0000_0000
defaultNameServiceSysfee = 6000_0000
maxRecordID = 255
)

View file

@ -158,16 +158,17 @@ func Init(t *testing.T, rootpath string, e *neotest.Executor) {
_, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, nsConfigPath, 4) // block #11
nsCommitteeInvoker := e.CommitteeInvoker(nsHash)
nsPriv0Invoker := e.NewInvoker(nsHash, acc0)
nsPriv0CommitteeInvoker := e.NewInvoker(nsHash, acc0, e.Committee)
// Block #12: transfer funds to committee for further NS record registration.
gasValidatorInvoker.Invoke(t, true, "transfer",
e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12
// Block #13: add `.com` root to NNS.
nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "com") // block #13
nsCommitteeInvoker.Invoke(t, true, "register", "com", nsCommitteeInvoker.CommitteeHash) // block #13
// Block #14: register `neo.com` via NNS.
registerTxH := nsPriv0Invoker.Invoke(t, true, "register",
registerTxH := nsPriv0CommitteeInvoker.Invoke(t, true, "register",
"neo.com", priv0ScriptHash) // block #14
res := e.GetTxExecResult(t, registerTxH)
require.Equal(t, 1, len(res.Events)) // transfer
@ -176,7 +177,7 @@ func Init(t *testing.T, rootpath string, e *neotest.Executor) {
t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID))
// Block #15: set A record type with priv0 owner via NNS.
nsPriv0Invoker.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15
nsPriv0Invoker.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15
// Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call
txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", "newtestvalue") // tx1

View file

@ -1370,7 +1370,7 @@ func TestClient_NEP11_ND(t *testing.T) {
t.Run("TotalSupply", func(t *testing.T) {
s, err := n11.TotalSupply()
require.NoError(t, err)
require.EqualValues(t, big.NewInt(1), s) // the only `neo.com` of acc0
require.EqualValues(t, big.NewInt(2), s) // `neo.com` of acc0 and TLD `com` of committee
})
t.Run("Symbol", func(t *testing.T) {
sym, err := n11.Symbol()
@ -1403,14 +1403,14 @@ func TestClient_NEP11_ND(t *testing.T) {
require.NoError(t, err)
items, err := iter.Next(config.DefaultMaxIteratorResultItems)
require.NoError(t, err)
require.Equal(t, 1, len(items))
require.Equal(t, [][]byte{[]byte("neo.com")}, items)
require.Equal(t, 2, len(items))
require.Equal(t, [][]byte{[]byte("neo.com"), []byte("com")}, items)
require.NoError(t, iter.Terminate())
})
t.Run("TokensExpanded", func(t *testing.T) {
items, err := n11.TokensExpanded(config.DefaultMaxIteratorResultItems)
require.NoError(t, err)
require.Equal(t, [][]byte{[]byte("neo.com")}, items)
require.Equal(t, [][]byte{[]byte("neo.com"), []byte("com")}, items)
})
t.Run("Properties", func(t *testing.T) {
p, err := n11.Properties([]byte("neo.com"))
@ -1421,6 +1421,7 @@ func TestClient_NEP11_ND(t *testing.T) {
expected := stackitem.NewMap()
expected.Add(stackitem.Make([]byte("name")), stackitem.Make([]byte("neo.com")))
expected.Add(stackitem.Make([]byte("expiration")), stackitem.Make(blockRegisterDomain.Timestamp+365*24*3600*1000)) // expiration formula
expected.Add(stackitem.Make([]byte("admin")), stackitem.Null{})
require.EqualValues(t, expected, p)
})
t.Run("Transfer", func(t *testing.T) {

View file

@ -74,12 +74,12 @@ const (
verifyContractHash = "06ed5314c2e4cb103029a60b86d46afa2fb8f67c"
verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A="
verifyWithArgsContractHash = "0dce75f52adb1a4c5c6eaa6a34eb26db2e5b3781"
nnsContractHash = "bdbfe1a280a0e23ca5b569c8f5845169bd93cb06"
nnsContractHash = "cb93bcab0d6d435b61fa96a3bbce3b6f043968b5"
nnsToken1ID = "6e656f2e636f6d"
nfsoContractHash = "0e15ca0df00669a2cd5dcb03bfd3e2b3849c2969"
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
block20StateRootLE = "f1380226a217b5e35ea968d42c50e20b9af7ab83b91416c8fb85536c61004332"
block20StateRootLE = "7f80c7e265a44faa7374953d4d5059d21b34e65e06a7695d57ca8c59cc9a36fa"
storageContractHash = "ebc0c16a76c808cd4dde6bcc063f09e45e331ec7"
)
@ -287,6 +287,7 @@ var rpcTestCases = map[string][]rpcTestCase{
return &map[string]interface{}{
"name": "neo.com",
"expiration": "lhbLRl0B",
"admin": nil, // no admin was set
}
},
},
@ -935,7 +936,7 @@ var rpcTestCases = map[string][]rpcTestCase{
chg := []dboper.Operation{{
State: "Changed",
Key: []byte{0xfa, 0xff, 0xff, 0xff, 0xb},
Value: []byte{0xf6, 0x8b, 0x4e, 0x9d, 0x51, 0x79, 0x12},
Value: []byte{0x6e, 0xaf, 0xba, 0x5e, 0x51, 0x79, 0x12},
}, {
State: "Added",
Key: []byte{0xfb, 0xff, 0xff, 0xff, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb},
@ -947,7 +948,7 @@ var rpcTestCases = map[string][]rpcTestCase{
}, {
State: "Changed",
Key: []byte{0xfa, 0xff, 0xff, 0xff, 0x14, 0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x8, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2},
Value: []byte{0x41, 0x01, 0x21, 0x05, 0xe4, 0x74, 0xef, 0xdb, 0x08},
Value: []byte{0x41, 0x01, 0x21, 0x05, 0xda, 0xb5, 0x8c, 0xda, 0x08},
}}
// Can be returned in any order.
assert.ElementsMatch(t, chg, res.Diagnostics.Changes)
@ -963,7 +964,7 @@ var rpcTestCases = map[string][]rpcTestCase{
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
return &result.Invoke{
State: "HALT",
GasConsumed: 15928320,
GasConsumed: 22192980,
Script: script,
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
Notifications: []state.NotificationEvent{},
@ -975,6 +976,15 @@ var rpcTestCases = map[string][]rpcTestCase{
{
Current: nnsHash,
Calls: []*invocations.Tree{
{
Current: stdHash,
},
{
Current: cryptoHash,
},
{
Current: stdHash,
},
{
Current: stdHash,
},
@ -1078,7 +1088,7 @@ var rpcTestCases = map[string][]rpcTestCase{
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
return &result.Invoke{
State: "HALT",
GasConsumed: 15928320,
GasConsumed: 22192980,
Script: script,
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
Notifications: []state.NotificationEvent{},
@ -1090,6 +1100,15 @@ var rpcTestCases = map[string][]rpcTestCase{
{
Current: nnsHash,
Calls: []*invocations.Tree{
{
Current: stdHash,
},
{
Current: cryptoHash,
},
{
Current: stdHash,
},
{
Current: stdHash,
},
@ -2717,7 +2736,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
},
{
Asset: e.chain.UtilityTokenHash(),
Amount: "37099660700",
Amount: "37076412050",
LastUpdated: 22,
Decimals: 8,
Name: "GasToken",

Binary file not shown.