Compare commits
16 commits
Author | SHA1 | Date | |
---|---|---|---|
|
14c9858df2 | ||
|
d8e0a02a86 | ||
|
d5b1c0e429 | ||
|
db9cea5ecc | ||
|
ea934b8e30 | ||
|
8790602f69 | ||
|
c9050cef4b | ||
|
c296f8804c | ||
|
4543de0923 | ||
|
d77b35c385 | ||
|
225152f2d7 | ||
|
baf24d1c66 | ||
|
017a6b9bc1 | ||
|
5cb2a1219c | ||
|
c11481b119 | ||
|
bd3722041a |
8 changed files with 665 additions and 243 deletions
|
@ -47,22 +47,27 @@ const (
|
||||||
maxRegisterPrice = 1_0000_0000_0000
|
maxRegisterPrice = 1_0000_0000_0000
|
||||||
// maxRootLength is the maximum domain root length.
|
// maxRootLength is the maximum domain root length.
|
||||||
maxRootLength = 16
|
maxRootLength = 16
|
||||||
// maxDomainNameFragmentLength is the maximum length of the domain name fragment.
|
// maxDomainNameFragmentLength is the maximum length of the domain name fragment
|
||||||
maxDomainNameFragmentLength = 62
|
maxDomainNameFragmentLength = 63
|
||||||
// minDomainNameLength is minimum domain length.
|
// minDomainNameLength is minimum domain length.
|
||||||
minDomainNameLength = 3
|
minDomainNameLength = 3
|
||||||
// maxDomainNameLength is maximum domain length.
|
// maxDomainNameLength is maximum domain length.
|
||||||
maxDomainNameLength = 255
|
maxDomainNameLength = 255
|
||||||
// maxTXTRecordLength is the maximum length of the TXT domain record.
|
// maxTXTRecordLength is the maximum length of the TXT domain record.
|
||||||
maxTXTRecordLength = 255
|
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.
|
// Other constants.
|
||||||
const (
|
const (
|
||||||
// defaultRegisterPrice is the default price for new domain registration.
|
// defaultRegisterPrice is the default price for new domain registration.
|
||||||
defaultRegisterPrice = 10_0000_0000
|
defaultRegisterPrice = 10_0000_0000
|
||||||
|
// millisecondsInSecond is the amount of milliseconds per second.
|
||||||
|
millisecondsInSecond = 1000
|
||||||
// millisecondsInYear is amount of milliseconds per year.
|
// millisecondsInYear is amount of milliseconds per year.
|
||||||
millisecondsInYear = 365 * 24 * 3600 * 1000
|
millisecondsInYear = 365 * 24 * 3600 * millisecondsInSecond
|
||||||
)
|
)
|
||||||
|
|
||||||
// RecordState is a type that registered entities are saved to.
|
// RecordState is a type that registered entities are saved to.
|
||||||
|
@ -70,6 +75,7 @@ type RecordState struct {
|
||||||
Name string
|
Name string
|
||||||
Type RecordType
|
Type RecordType
|
||||||
Data string
|
Data string
|
||||||
|
ID byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates NameService contract.
|
// Update updates NameService contract.
|
||||||
|
@ -118,6 +124,7 @@ func Properties(tokenID []byte) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"name": ns.Name,
|
"name": ns.Name,
|
||||||
"expiration": ns.Expiration,
|
"expiration": ns.Expiration,
|
||||||
|
"admin": ns.Admin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,22 +186,6 @@ func Transfer(to interop.Hash160, tokenID []byte, data interface{}) bool {
|
||||||
return true
|
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.
|
// Roots returns iterator over a set of NameService roots.
|
||||||
func Roots() iterator.Iterator {
|
func Roots() iterator.Iterator {
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
@ -219,31 +210,94 @@ func GetPrice() int {
|
||||||
|
|
||||||
// IsAvailable checks whether provided domain name is available.
|
// IsAvailable checks whether provided domain name is available.
|
||||||
func IsAvailable(name string) bool {
|
func IsAvailable(name string) bool {
|
||||||
fragments := splitAndCheck(name, false)
|
fragments := splitAndCheck(name, true)
|
||||||
if fragments == nil {
|
if fragments == nil {
|
||||||
panic("invalid domain name format")
|
panic("invalid domain name format")
|
||||||
}
|
}
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[1])...)) == nil {
|
l := len(fragments)
|
||||||
panic("root not found")
|
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil {
|
||||||
|
if l != 1 {
|
||||||
|
panic("TLD not found")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !parentExpired(ctx, 0, fragments) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(getParentConflictingRecord(ctx, name, fragments)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPrentConflictingRecord returns record of '*.name' format if they are presented.
|
||||||
|
// These records conflict with domain name to be registered.
|
||||||
|
func getParentConflictingRecord(ctx storage.Context, name string, fragments []string) string {
|
||||||
|
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:]))
|
||||||
|
parentRecKey := append([]byte{prefixRecord}, parentKey...)
|
||||||
|
it := storage.Find(ctx, parentRecKey, storage.ValuesOnly|storage.DeserializeValues)
|
||||||
|
suffix := []byte(name)
|
||||||
|
for iterator.Next(it) {
|
||||||
|
r := iterator.Value(it).(RecordState)
|
||||||
|
ind := std.MemorySearchLastIndex([]byte(r.Name), suffix, len(r.Name))
|
||||||
|
if ind > 0 && ind+len(suffix) == len(r.Name) {
|
||||||
|
return r.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))...))
|
nsBytes := storage.Get(ctx, append([]byte{prefixName}, getTokenKey([]byte(name))...))
|
||||||
if nsBytes == nil {
|
if nsBytes == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
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.
|
// Register registers new domain with the specified owner and name if it's available.
|
||||||
func Register(name string, owner interop.Hash160) bool {
|
func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
|
||||||
fragments := splitAndCheck(name, false)
|
fragments := splitAndCheck(name, true)
|
||||||
if fragments == nil {
|
if fragments == nil {
|
||||||
panic("invalid domain name format")
|
panic("invalid domain name format")
|
||||||
}
|
}
|
||||||
|
l := len(fragments)
|
||||||
|
tldKey := append([]byte{prefixRoot}, []byte(fragments[l-1])...)
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[1])...)) == nil {
|
tldBytes := storage.Get(ctx, tldKey)
|
||||||
panic("root not found")
|
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 is not registered")
|
||||||
|
}
|
||||||
|
parentKey := getTokenKey([]byte(name[len(fragments[0])+1:]))
|
||||||
|
nsBytes := storage.Get(ctx, append([]byte{prefixName}, parentKey...))
|
||||||
|
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||||
|
ns.checkAdmin()
|
||||||
|
|
||||||
|
if conflict := getParentConflictingRecord(ctx, name, fragments); len(conflict) != 0 {
|
||||||
|
panic("parent domain has conflicting records: " + conflict)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isValid(owner) {
|
if !isValid(owner) {
|
||||||
|
@ -275,14 +329,62 @@ func Register(name string, owner interop.Hash160) bool {
|
||||||
ns := NameState{
|
ns := NameState{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
Name: name,
|
Name: name,
|
||||||
Expiration: runtime.GetTime() + millisecondsInYear,
|
Expiration: runtime.GetTime() + expire*millisecondsInSecond,
|
||||||
}
|
}
|
||||||
putNameStateWithKey(ctx, tokenKey, ns)
|
putNameStateWithKey(ctx, tokenKey, ns)
|
||||||
|
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
|
||||||
updateBalance(ctx, []byte(name), owner, +1)
|
updateBalance(ctx, []byte(name), owner, +1)
|
||||||
postTransfer(oldOwner, owner, []byte(name), nil)
|
postTransfer(oldOwner, owner, []byte(name), nil)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateSOA updates soa record.
|
||||||
|
func UpdateSOA(name, email string, refresh, retry, expire, ttl int) {
|
||||||
|
if len(name) > maxDomainNameLength {
|
||||||
|
panic("invalid domain name format")
|
||||||
|
}
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
ns := getNameState(ctx, []byte(name))
|
||||||
|
ns.checkAdmin()
|
||||||
|
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// putSoaRecord stores SOA domain record.
|
||||||
|
func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expire, ttl int) {
|
||||||
|
data := name + " " + email + " " +
|
||||||
|
std.Itoa(runtime.GetTime(), 10) + " " +
|
||||||
|
std.Itoa(refresh, 10) + " " +
|
||||||
|
std.Itoa(retry, 10) + " " +
|
||||||
|
std.Itoa(expire, 10) + " " +
|
||||||
|
std.Itoa(ttl, 10)
|
||||||
|
tokenId := []byte(tokenIDFromName(ctx, name))
|
||||||
|
putRecord(ctx, tokenId, name, SOA, 0, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateSoaSerial updates serial of the corresponding SOA domain record.
|
||||||
|
func updateSoaSerial(ctx storage.Context, tokenId []byte) {
|
||||||
|
recordKey := getRecordKey(tokenId, string(tokenId), SOA, 0)
|
||||||
|
|
||||||
|
recBytes := storage.Get(ctx, recordKey)
|
||||||
|
if recBytes == nil {
|
||||||
|
panic("SOA record not found")
|
||||||
|
}
|
||||||
|
rec := std.Deserialize(recBytes.([]byte)).(RecordState)
|
||||||
|
|
||||||
|
split := std.StringSplitNonEmpty(rec.Data, " ")
|
||||||
|
if len(split) != 7 {
|
||||||
|
panic("corrupted SOA record format")
|
||||||
|
}
|
||||||
|
split[2] = std.Itoa(runtime.GetTime(), 10) // update serial
|
||||||
|
rec.Data = split[0] + " " + split[1] + " " +
|
||||||
|
split[2] + " " + split[3] + " " +
|
||||||
|
split[4] + " " + split[5] + " " +
|
||||||
|
split[6]
|
||||||
|
|
||||||
|
recBytes = std.Serialize(rec)
|
||||||
|
storage.Put(ctx, recordKey, recBytes)
|
||||||
|
}
|
||||||
|
|
||||||
// Renew increases domain expiration date.
|
// Renew increases domain expiration date.
|
||||||
func Renew(name string) int {
|
func Renew(name string) int {
|
||||||
if len(name) > maxDomainNameLength {
|
if len(name) > maxDomainNameLength {
|
||||||
|
@ -313,9 +415,46 @@ func SetAdmin(name string, admin interop.Hash160) {
|
||||||
putNameState(ctx, ns)
|
putNameState(ctx, ns)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRecord adds new record of the specified type to the provided domain.
|
// SetRecord updates record of the specified type and ID.
|
||||||
func SetRecord(name string, typ RecordType, data string) {
|
func SetRecord(name string, typ RecordType, id byte, data string) {
|
||||||
tokenID := []byte(tokenIDFromName(name))
|
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)
|
||||||
|
updateSoaSerial(ctx, tokenID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
updateSoaSerial(ctx, tokenID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkRecord performs record validness check and returns token ID.
|
||||||
|
func checkRecord(ctx storage.Context, name string, typ RecordType, data string) []byte {
|
||||||
|
tokenID := []byte(tokenIDFromName(ctx, name))
|
||||||
var ok bool
|
var ok bool
|
||||||
switch typ {
|
switch typ {
|
||||||
case A:
|
case A:
|
||||||
|
@ -332,44 +471,50 @@ func SetRecord(name string, typ RecordType, data string) {
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("invalid record data")
|
panic("invalid record data")
|
||||||
}
|
}
|
||||||
ctx := storage.GetContext()
|
|
||||||
ns := getNameState(ctx, tokenID)
|
ns := getNameState(ctx, tokenID)
|
||||||
ns.checkAdmin()
|
ns.checkAdmin()
|
||||||
putRecord(ctx, tokenID, name, typ, data)
|
return tokenID
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRecord returns domain record of the specified type if it exists or an empty
|
// GetRecords returns domain records of the specified type if they exist or an empty
|
||||||
// string if not.
|
// array if not.
|
||||||
func GetRecord(name string, typ RecordType) string {
|
func GetRecords(name string, typ RecordType) []string {
|
||||||
tokenID := []byte(tokenIDFromName(name))
|
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
tokenID := []byte(tokenIDFromName(ctx, name))
|
||||||
_ = getNameState(ctx, tokenID) // ensure not expired
|
_ = 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.
|
// DeleteRecords removes all domain records with the specified type.
|
||||||
func DeleteRecord(name string, typ RecordType) {
|
func DeleteRecords(name string, typ RecordType) {
|
||||||
tokenID := []byte(tokenIDFromName(name))
|
if typ == SOA {
|
||||||
|
panic("forbidden to delete SOA record")
|
||||||
|
}
|
||||||
ctx := storage.GetContext()
|
ctx := storage.GetContext()
|
||||||
|
tokenID := []byte(tokenIDFromName(ctx, name))
|
||||||
ns := getNameState(ctx, tokenID)
|
ns := getNameState(ctx, tokenID)
|
||||||
ns.checkAdmin()
|
ns.checkAdmin()
|
||||||
recordKey := getRecordKey(tokenID, name, typ)
|
recordsPrefix := getRecordsByTypePrefix(tokenID, name, typ)
|
||||||
storage.Delete(ctx, recordKey)
|
records := storage.Find(ctx, recordsPrefix, storage.KeysOnly)
|
||||||
|
for iterator.Next(records) {
|
||||||
|
key := iterator.Value(records).(string)
|
||||||
|
storage.Delete(ctx, key)
|
||||||
|
}
|
||||||
|
updateSoaSerial(ctx, tokenID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve resolves given name (not more then three redirects are allowed).
|
// Resolve resolves given name (not more than three redirects are allowed) to a set
|
||||||
func Resolve(name string, typ RecordType) string {
|
// of domain records.
|
||||||
|
func Resolve(name string, typ RecordType) []string {
|
||||||
ctx := storage.GetReadOnlyContext()
|
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.
|
// GetAllRecords returns an Iterator with RecordState items for given name.
|
||||||
func GetAllRecords(name string) iterator.Iterator {
|
func GetAllRecords(name string) iterator.Iterator {
|
||||||
tokenID := []byte(tokenIDFromName(name))
|
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
_ = getNameState(ctx, tokenID) // ensure not expired
|
return getAllRecords(ctx, name)
|
||||||
recordsKey := getRecordsKey(tokenID, name)
|
|
||||||
return storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateBalance updates account's balance and account's tokens.
|
// updateBalance updates account's balance and account's tokens.
|
||||||
|
@ -425,12 +570,17 @@ func getTokenKey(tokenID []byte) []byte {
|
||||||
// getNameState returns domain name state by the specified tokenID.
|
// getNameState returns domain name state by the specified tokenID.
|
||||||
func getNameState(ctx storage.Context, tokenID []byte) NameState {
|
func getNameState(ctx storage.Context, tokenID []byte) NameState {
|
||||||
tokenKey := getTokenKey(tokenID)
|
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.
|
// getNameStateWithKey returns domain name state by the specified token key.
|
||||||
func getNameStateWithKey(ctx storage.Context, tokenKey []byte) NameState {
|
func getNameStateWithKey(ctx storage.Context, tokenKey []byte) NameState {
|
||||||
nameKey := append([]byte{prefixName}, tokenKey...)
|
nameKey := getNameStateKey(tokenKey)
|
||||||
nsBytes := storage.Get(ctx, nameKey)
|
nsBytes := storage.Get(ctx, nameKey)
|
||||||
if nsBytes == nil {
|
if nsBytes == nil {
|
||||||
panic("token not found")
|
panic("token not found")
|
||||||
|
@ -440,6 +590,11 @@ func getNameStateWithKey(ctx storage.Context, tokenKey []byte) NameState {
|
||||||
return ns
|
return ns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getNameStateKey returns NameState key for the provided token key.
|
||||||
|
func getNameStateKey(tokenKey []byte) []byte {
|
||||||
|
return append([]byte{prefixName}, tokenKey...)
|
||||||
|
}
|
||||||
|
|
||||||
// putNameState stores domain name state.
|
// putNameState stores domain name state.
|
||||||
func putNameState(ctx storage.Context, ns NameState) {
|
func putNameState(ctx storage.Context, ns NameState) {
|
||||||
tokenKey := getTokenKey([]byte(ns.Name))
|
tokenKey := getTokenKey([]byte(ns.Name))
|
||||||
|
@ -453,41 +608,53 @@ func putNameStateWithKey(ctx storage.Context, tokenKey []byte, ns NameState) {
|
||||||
storage.Put(ctx, nameKey, nsBytes)
|
storage.Put(ctx, nameKey, nsBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRecord returns domain record.
|
// getRecordsByType returns domain records of the specified type or an empty array if no records found.
|
||||||
func getRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType) string {
|
func getRecordsByType(ctx storage.Context, tokenId []byte, name string, typ RecordType) []string {
|
||||||
recordKey := getRecordKey(tokenId, name, typ)
|
recordsPrefix := getRecordsByTypePrefix(tokenId, name, typ)
|
||||||
recBytes := storage.Get(ctx, recordKey)
|
records := storage.Find(ctx, recordsPrefix, storage.ValuesOnly|storage.DeserializeValues)
|
||||||
if recBytes == nil {
|
res := []string{} // return empty slice if no records was found.
|
||||||
return recBytes.(string) // A hack to actually return NULL.
|
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.
|
// putRecord puts the specified record to the contract storage without any additional checks.
|
||||||
func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, record string) {
|
func putRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType, id byte, data string) {
|
||||||
recordKey := getRecordKey(tokenId, name, typ)
|
recordKey := getRecordKey(tokenId, name, typ, id)
|
||||||
rs := RecordState{
|
rs := RecordState{
|
||||||
Name: name,
|
Name: name,
|
||||||
Type: typ,
|
Type: typ,
|
||||||
Data: record,
|
Data: data,
|
||||||
|
ID: id,
|
||||||
}
|
}
|
||||||
recBytes := std.Serialize(rs)
|
recBytes := std.Serialize(rs)
|
||||||
storage.Put(ctx, recordKey, recBytes)
|
storage.Put(ctx, recordKey, recBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRecordsKey returns prefix used to store domain records of different types.
|
// getRecordKey returns key used to store domain record with the specified type and ID.
|
||||||
func getRecordsKey(tokenId []byte, name string) []byte {
|
// This key always have a single corresponding value.
|
||||||
recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...)
|
func getRecordKey(tokenId []byte, name string, typ RecordType, id byte) []byte {
|
||||||
return append(recordKey, getTokenKey([]byte(name))...)
|
prefix := getRecordsByTypePrefix(tokenId, name, typ)
|
||||||
|
return append(prefix, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRecordKey returns key used to store domain records.
|
// getRecordsByTypePrefix returns prefix used to store domain records with the
|
||||||
func getRecordKey(tokenId []byte, name string, typ RecordType) []byte {
|
// specified type of different IDs.
|
||||||
recordKey := getRecordsKey(tokenId, name)
|
func getRecordsByTypePrefix(tokenId []byte, name string, typ RecordType) []byte {
|
||||||
|
recordKey := getRecordsPrefix(tokenId, name)
|
||||||
return append(recordKey, []byte{byte(typ)}...)
|
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.
|
// isValid returns true if the provided address is a valid Uint160.
|
||||||
func isValid(address interop.Hash160) bool {
|
func isValid(address interop.Hash160) bool {
|
||||||
return address != nil && len(address) == 20
|
return address != nil && len(address) == 20
|
||||||
|
@ -507,6 +674,8 @@ func checkCommittee() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkFragment validates root or a part of domain name.
|
// 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 {
|
func checkFragment(v string, isRoot bool) bool {
|
||||||
maxLength := maxDomainNameFragmentLength
|
maxLength := maxDomainNameFragmentLength
|
||||||
if isRoot {
|
if isRoot {
|
||||||
|
@ -525,12 +694,12 @@ func checkFragment(v string, isRoot bool) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 1; i < len(v); i++ {
|
for i := 1; i < len(v)-1; i++ {
|
||||||
if !isAlNum(v[i]) {
|
if v[i] != '-' && !isAlNum(v[i]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return isAlNum(v[len(v)-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// isAlNum checks whether provided char is a lowercase letter or a number.
|
// isAlNum checks whether provided char is a lowercase letter or a number.
|
||||||
|
@ -546,9 +715,6 @@ func splitAndCheck(name string, allowMultipleFragments bool) []string {
|
||||||
}
|
}
|
||||||
fragments := std.StringSplit(name, ".")
|
fragments := std.StringSplit(name, ".")
|
||||||
l = len(fragments)
|
l = len(fragments)
|
||||||
if l < 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if l > 2 && !allowMultipleFragments {
|
if l > 2 && !allowMultipleFragments {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -671,48 +837,66 @@ func checkIPv6(data string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// tokenIDFromName returns token ID (domain.root) from provided name.
|
// tokenIDFromName returns token ID (domain.root) from provided name.
|
||||||
func tokenIDFromName(name string) string {
|
func tokenIDFromName(ctx storage.Context, name string) string {
|
||||||
fragments := splitAndCheck(name, true)
|
fragments := splitAndCheck(name, true)
|
||||||
if fragments == nil {
|
if fragments == nil {
|
||||||
panic("invalid domain name format")
|
panic("invalid domain name format")
|
||||||
}
|
}
|
||||||
l := len(fragments)
|
sum := 0
|
||||||
return name[len(name)-(len(fragments[l-1])+len(fragments[l-2])+1):]
|
for i := 0; i < len(fragments)-1; i++ {
|
||||||
|
tokenKey := getTokenKey([]byte(name[sum:]))
|
||||||
|
nameKey := getNameStateKey(tokenKey)
|
||||||
|
nsBytes := storage.Get(ctx, nameKey)
|
||||||
|
if nsBytes != nil {
|
||||||
|
ns := std.Deserialize(nsBytes.([]byte)).(NameState)
|
||||||
|
if runtime.GetTime() < ns.Expiration {
|
||||||
|
return name[sum:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sum += len(fragments[i]) + 1
|
||||||
|
}
|
||||||
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve resolves provided name using record with the specified type and given
|
// resolve resolves provided name using record with the specified type and given
|
||||||
// maximum redirections constraint.
|
// 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 {
|
if redirect < 0 {
|
||||||
panic("invalid redirect")
|
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 := ""
|
cname := ""
|
||||||
for iterator.Next(records) {
|
for iterator.Next(records) {
|
||||||
r := iterator.Value(records).(struct {
|
r := iterator.Value(records).(RecordState)
|
||||||
key string
|
if r.Type == typ {
|
||||||
rs RecordState
|
res = append(res, r.Data)
|
||||||
})
|
|
||||||
value := r.rs.Data
|
|
||||||
rTyp := r.key[len(r.key)-1]
|
|
||||||
if rTyp == byte(typ) {
|
|
||||||
return value
|
|
||||||
}
|
}
|
||||||
if rTyp == byte(CNAME) {
|
if r.Type == CNAME {
|
||||||
cname = value
|
cname = r.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cname == "" {
|
if cname == "" || typ == CNAME {
|
||||||
return string([]byte(nil))
|
return res
|
||||||
}
|
|
||||||
return resolve(ctx, cname, typ, redirect-1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRecords returns iterator over the set of records corresponded with the
|
// TODO: the line below must be removed from the neofs nns:
|
||||||
// specified name.
|
// res = append(res, cname)
|
||||||
func getRecords(ctx storage.Context, name string) iterator.Iterator {
|
// @roman-khimov, it is done in a separate commit in neofs-contracts repo, is it OK?
|
||||||
tokenID := []byte(tokenIDFromName(name))
|
return resolve(ctx, res, cname, typ, redirect-1)
|
||||||
_ = getNameState(ctx, tokenID)
|
}
|
||||||
recordsKey := getRecordsKey(tokenID, name)
|
|
||||||
return storage.Find(ctx, recordsKey, storage.DeserializeValues)
|
// 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(ctx, name))
|
||||||
|
_ = getNameState(ctx, tokenID) // ensure not expired.
|
||||||
|
recordsPrefix := getRecordsPrefix(tokenID, name)
|
||||||
|
return storage.Find(ctx, recordsPrefix, storage.ValuesOnly|storage.DeserializeValues)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ name: "NameService"
|
||||||
sourceurl: https://github.com/nspcc-dev/neo-go/
|
sourceurl: https://github.com/nspcc-dev/neo-go/
|
||||||
supportedstandards: ["NEP-11"]
|
supportedstandards: ["NEP-11"]
|
||||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
|
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
|
||||||
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecord",
|
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecords",
|
||||||
"resolve", "getAllRecords"]
|
"resolve", "getAllRecords"]
|
||||||
events:
|
events:
|
||||||
- name: Transfer
|
- name: Transfer
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package nns_test
|
package nns_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -9,18 +11,30 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
"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"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
"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/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newNSClient(t *testing.T) *neotest.ContractInvoker {
|
const (
|
||||||
|
millisecondsInYear = 365 * 24 * 3600 * 1000
|
||||||
|
maxDomainNameFragmentLength = 63
|
||||||
|
)
|
||||||
|
|
||||||
|
func newNSClient(t *testing.T, registerComTLD bool) *neotest.ContractInvoker {
|
||||||
bc, acc := chain.NewSingle(t)
|
bc, acc := chain.NewSingle(t)
|
||||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||||
c := neotest.CompileFile(t, e.CommitteeHash, ".", "nns.yml")
|
ctr := neotest.CompileFile(t, e.CommitteeHash, ".", "nns.yml")
|
||||||
e.DeployContract(t, c, nil)
|
e.DeployContract(t, ctr, nil)
|
||||||
|
|
||||||
return e.CommitteeInvoker(c.Hash)
|
c := e.CommitteeInvoker(ctr.Hash)
|
||||||
|
if registerComTLD {
|
||||||
|
// Set expiration big enough to pass all tests.
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
c.Invoke(t, true, "register", "com", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNameService_Price(t *testing.T) {
|
func TestNameService_Price(t *testing.T) {
|
||||||
|
@ -29,7 +43,7 @@ func TestNameService_Price(t *testing.T) {
|
||||||
maxPrice = int64(10000_00000000)
|
maxPrice = int64(10000_00000000)
|
||||||
)
|
)
|
||||||
|
|
||||||
c := newNSClient(t)
|
c := newNSClient(t, false)
|
||||||
|
|
||||||
t.Run("set, not signed by committee", func(t *testing.T) {
|
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||||
acc := c.NewAccount(t)
|
acc := c.NewAccount(t)
|
||||||
|
@ -62,7 +76,7 @@ func TestNameService_Price(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNonfungible(t *testing.T) {
|
func TestNonfungible(t *testing.T) {
|
||||||
c := newNSClient(t)
|
c := newNSClient(t, false)
|
||||||
|
|
||||||
c.Signers = []neotest.Signer{c.NewAccount(t)}
|
c.Signers = []neotest.Signer{c.NewAccount(t)}
|
||||||
c.Invoke(t, "NNS", "symbol")
|
c.Invoke(t, "NNS", "symbol")
|
||||||
|
@ -70,104 +84,109 @@ func TestNonfungible(t *testing.T) {
|
||||||
c.Invoke(t, 0, "totalSupply")
|
c.Invoke(t, 0, "totalSupply")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddRoot(t *testing.T) {
|
func TestRegisterTLD(t *testing.T) {
|
||||||
c := newNSClient(t)
|
c := newNSClient(t, false)
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
t.Run("invalid format", func(t *testing.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, mail, refresh, retry, expire, ttl)
|
||||||
})
|
})
|
||||||
t.Run("not signed by committee", func(t *testing.T) {
|
t.Run("not signed by committee", func(t *testing.T) {
|
||||||
acc := c.NewAccount(t)
|
acc := c.NewAccount(t)
|
||||||
c := c.WithSigners(acc)
|
c := c.WithSigners(acc)
|
||||||
c.InvokeFail(t, "not witnessed by committee", "addRoot", "some")
|
c.InvokeFail(t, "not witnessed by committee", "register", "some", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "some")
|
c.Invoke(t, true, "register", "some", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
t.Run("already exists", func(t *testing.T) {
|
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, mail, refresh, retry, expire, ttl)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExpiration(t *testing.T) {
|
func TestExpiration(t *testing.T) {
|
||||||
c := newNSClient(t)
|
c := newNSClient(t, true)
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
bc := e.Chain
|
bc := e.Chain
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
acc := e.NewAccount(t)
|
acc := e.NewAccount(t)
|
||||||
cAcc := c.WithSigners(acc)
|
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")
|
cAccCommittee.Invoke(t, true, "register", "first.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash())
|
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "first.com", int64(nns.TXT), "sometext")
|
||||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext")
|
|
||||||
b1 := e.TopBlock(t)
|
b1 := e.TopBlock(t)
|
||||||
|
|
||||||
tx := cAcc.PrepareInvoke(t, "register", "second.com", acc.ScriptHash())
|
tx := cAccCommittee.PrepareInvoke(t, "register", "second.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
b2 := e.NewUnsignedBlock(t, tx)
|
b2 := e.NewUnsignedBlock(t, tx)
|
||||||
b2.Index = b1.Index + 1
|
b2.Index = b1.Index + 1
|
||||||
b2.PrevHash = b1.Hash()
|
b2.PrevHash = b1.Hash()
|
||||||
b2.Timestamp = b1.Timestamp + 10000
|
b2.Timestamp = b1.Timestamp + 10000
|
||||||
require.NoError(t, bc.AddBlock(e.SignBlock(b2)))
|
require.NoError(t, bc.AddBlock(e.SignBlock(b2)))
|
||||||
e.CheckHalt(t, tx.Hash())
|
|
||||||
|
|
||||||
tx = cAcc.PrepareInvoke(t, "isAvailable", "first.com")
|
|
||||||
b3 := e.NewUnsignedBlock(t, tx)
|
|
||||||
b3.Index = b2.Index + 1
|
|
||||||
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))
|
||||||
|
|
||||||
tx = cAcc.PrepareInvoke(t, "isAvailable", "second.com")
|
b3 := e.NewUnsignedBlock(t)
|
||||||
b4 := e.NewUnsignedBlock(t, tx)
|
b3.Index = b2.Index + 1
|
||||||
b4.Index = b3.Index + 1
|
b3.PrevHash = b2.Hash()
|
||||||
b4.PrevHash = b3.Hash()
|
b3.Timestamp = b1.Timestamp + (uint64(expire) * 1000)
|
||||||
b4.Timestamp = b3.Timestamp + 1000
|
require.NoError(t, bc.AddBlock(e.SignBlock(b3)))
|
||||||
require.NoError(t, bc.AddBlock(e.SignBlock(b4)))
|
|
||||||
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(false))
|
|
||||||
|
|
||||||
tx = cAcc.PrepareInvoke(t, "getRecord", "first.com", int64(nns.TXT))
|
cAcc.Invoke(t, true, "isAvailable", "first.com") // "first.com" has been expired
|
||||||
b5 := e.NewUnsignedBlock(t, tx)
|
cAcc.Invoke(t, true, "isAvailable", "second.com") // TLD "com" has been expired
|
||||||
b5.Index = b4.Index + 1
|
cAcc.InvokeFail(t, "name has expired", "getRecords", "first.com", int64(nns.TXT))
|
||||||
b5.PrevHash = b4.Hash()
|
|
||||||
b5.Timestamp = b4.Timestamp + 1000
|
|
||||||
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.
|
// TODO: According to the new code, we can't re-register expired "com" TLD, because it's already registered; at the
|
||||||
cAcc.Invoke(t, stackitem.Null{}, "resolve", "first.com", int64(nns.TXT))
|
// 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
|
|
||||||
|
|
||||||
func TestRegisterAndRenew(t *testing.T) {
|
func TestRegisterAndRenew(t *testing.T) {
|
||||||
c := newNSClient(t)
|
c := newNSClient(t, false)
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
c.InvokeFail(t, "root not found", "isAvailable", "neo.com")
|
c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com")
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "org")
|
c.Invoke(t, true, "register", "org", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
c.InvokeFail(t, "root not found", "isAvailable", "neo.com")
|
c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com")
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
c.Invoke(t, true, "register", "com", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
c.Invoke(t, true, "isAvailable", "neo.com")
|
c.Invoke(t, true, "isAvailable", "neo.com")
|
||||||
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash)
|
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
c.InvokeFail(t, "invalid domain name format", "register", "docs.neo.org", e.CommitteeHash)
|
c.InvokeFail(t, "one of the parent domains is not registered", "register", "docs.neo.org", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
c.InvokeFail(t, "invalid domain name format", "register", "\nneo.com'", e.CommitteeHash)
|
c.InvokeFail(t, "invalid domain name format", "register", "\nneo.com'", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
c.InvokeFail(t, "invalid domain name format", "register", "neo.com\n", e.CommitteeHash)
|
c.InvokeFail(t, "invalid domain name format", "register", "neo.com\n", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash)
|
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceDomainPrice, "register", "neo.com", e.CommitteeHash)
|
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceDomainPrice, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
|
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, mail, refresh, retry, expire, ttl)
|
||||||
|
c.InvokeFail(t, "invalid domain name format", "register", maxLenFragment+"q.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
|
|
||||||
c.Invoke(t, true, "isAvailable", "neo.com")
|
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)
|
c.Invoke(t, true, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
topBlock := e.TopBlock(t)
|
topBlock := e.TopBlock(t)
|
||||||
expectedExpiration := topBlock.Timestamp + millisecondsInYear
|
expectedExpiration := topBlock.Timestamp + uint64(expire*1000)
|
||||||
c.Invoke(t, false, "register", "neo.com", e.CommitteeHash)
|
c.Invoke(t, false, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
c.Invoke(t, false, "isAvailable", "neo.com")
|
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, mail, refresh, retry, expire, ttl)
|
||||||
|
c.InvokeFail(t, "invalid domain name format", "register", "testdomain-.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
|
c.Invoke(t, true, "register", "test-domain.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
|
})
|
||||||
|
|
||||||
props := stackitem.NewMap()
|
props := stackitem.NewMap()
|
||||||
props.Add(stackitem.Make("name"), stackitem.Make("neo.com"))
|
props.Add(stackitem.Make("name"), stackitem.Make("neo.com"))
|
||||||
props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration))
|
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, 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"))
|
c.Invoke(t, e.CommitteeHash.BytesBE(), "ownerOf", []byte("neo.com"))
|
||||||
|
|
||||||
t.Run("invalid token ID", func(t *testing.T) {
|
t.Run("invalid token ID", func(t *testing.T) {
|
||||||
|
@ -185,42 +204,59 @@ func TestRegisterAndRenew(t *testing.T) {
|
||||||
c.Invoke(t, props, "properties", "neo.com")
|
c.Invoke(t, props, "properties", "neo.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetGetRecord(t *testing.T) {
|
func TestSetAddGetRecord(t *testing.T) {
|
||||||
c := newNSClient(t)
|
c := newNSClient(t, true)
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
acc := e.NewAccount(t)
|
acc := e.NewAccount(t)
|
||||||
cAcc := c.WithSigners(acc)
|
cAcc := c.WithSigners(acc)
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
|
||||||
|
|
||||||
t.Run("set before register", func(t *testing.T) {
|
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)
|
c.Invoke(t, true, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
t.Run("invalid parameters", func(t *testing.T) {
|
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, "unsupported record type", "addRecord", "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, "invalid record", "addRecord", "neo.com", int64(nns.A), "not.an.ip.address")
|
||||||
})
|
})
|
||||||
t.Run("invalid witness", func(t *testing.T) {
|
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.NewArray([]stackitem.Item{}), "getRecords", "neo.com", int64(nns.A))
|
||||||
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
c.Invoke(t, stackitem.Null{}, "addRecord", "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.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "getRecords", "neo.com", int64(nns.A))
|
||||||
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
c.InvokeFail(t, "record already exists", "addRecord", "neo.com", int64(nns.A), "1.2.3.4") // Duplicating record.
|
||||||
c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A))
|
c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4")}), "getRecords", "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{}, "addRecord", "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{}, "addRecord", "neo.com", int64(nns.CNAME), "nspcc.ru")
|
||||||
c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
|
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.
|
// Delete record.
|
||||||
t.Run("invalid witness", func(t *testing.T) {
|
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.NewArray([]stackitem.Item{stackitem.Make("nspcc.ru")}), "getRecords", "neo.com", int64(nns.CNAME))
|
||||||
c.Invoke(t, stackitem.Null{}, "deleteRecord", "neo.com", int64(nns.CNAME))
|
c.Invoke(t, stackitem.Null{}, "deleteRecords", "neo.com", int64(nns.CNAME))
|
||||||
c.Invoke(t, stackitem.Null{}, "getRecord", "neo.com", int64(nns.CNAME))
|
c.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "getRecords", "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("1.2.3.4")}), "getRecords", "neo.com", int64(nns.A))
|
||||||
|
|
||||||
t.Run("SetRecord_compatibility", func(t *testing.T) {
|
t.Run("SetRecord_compatibility", func(t *testing.T) {
|
||||||
// tests are got from the NNS C# implementation and changed accordingly to non-native implementation behavior
|
// tests are got from the NNS C# implementation and changed accordingly to non-native implementation behavior
|
||||||
|
@ -280,9 +316,10 @@ func TestSetGetRecord(t *testing.T) {
|
||||||
args := []interface{}{"neo.com", int64(testCase.Type), testCase.Name}
|
args := []interface{}{"neo.com", int64(testCase.Type), testCase.Name}
|
||||||
t.Run(testCase.Name, func(t *testing.T) {
|
t.Run(testCase.Name, func(t *testing.T) {
|
||||||
if testCase.ShouldFail {
|
if testCase.ShouldFail {
|
||||||
c.InvokeFail(t, "", "setRecord", args...)
|
c.InvokeFail(t, "", "addRecord", args...)
|
||||||
} else {
|
} 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.
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -290,19 +327,21 @@ func TestSetGetRecord(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetAdmin(t *testing.T) {
|
func TestSetAdmin(t *testing.T) {
|
||||||
c := newNSClient(t)
|
c := newNSClient(t, true)
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
owner := e.NewAccount(t)
|
owner := e.NewAccount(t)
|
||||||
cOwner := c.WithSigners(owner)
|
cOwner := c.WithSigners(owner)
|
||||||
|
cOwnerCommittee := c.WithSigners(owner, c.Committee)
|
||||||
admin := e.NewAccount(t)
|
admin := e.NewAccount(t)
|
||||||
cAdmin := c.WithSigners(admin)
|
cAdmin := c.WithSigners(admin)
|
||||||
guest := e.NewAccount(t)
|
guest := e.NewAccount(t)
|
||||||
cGuest := c.WithSigners(guest)
|
cGuest := c.WithSigners(guest)
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
cOwner.InvokeFail(t, "not witnessed by admin", "register", "neo.com", owner.ScriptHash(), mail, refresh, retry, expire, ttl) // admin is committee
|
||||||
|
cOwnerCommittee.Invoke(t, true, "register", "neo.com", owner.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
cOwner.Invoke(t, true, "register", "neo.com", owner.ScriptHash())
|
expectedExpiration := e.TopBlock(t).Timestamp + uint64(expire)*1000
|
||||||
cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.ScriptHash())
|
cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.ScriptHash())
|
||||||
|
|
||||||
// Must be witnessed by both owner and admin.
|
// Must be witnessed by both owner and admin.
|
||||||
|
@ -310,36 +349,42 @@ func TestSetAdmin(t *testing.T) {
|
||||||
cAdmin.InvokeFail(t, "not witnessed by owner", "setAdmin", "neo.com", admin.ScriptHash())
|
cAdmin.InvokeFail(t, "not witnessed by owner", "setAdmin", "neo.com", admin.ScriptHash())
|
||||||
cc := c.WithSigners(owner, admin)
|
cc := c.WithSigners(owner, admin)
|
||||||
cc.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", admin.ScriptHash())
|
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) {
|
t.Run("set and delete by admin", 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")
|
||||||
cGuest.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.TXT))
|
cGuest.InvokeFail(t, "not witnessed by admin", "deleteRecords", "neo.com", int64(nns.TXT))
|
||||||
cAdmin.Invoke(t, stackitem.Null{}, "deleteRecord", "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) {
|
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)
|
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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTransfer(t *testing.T) {
|
func TestTransfer(t *testing.T) {
|
||||||
c := newNSClient(t)
|
c := newNSClient(t, true)
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
from := e.NewAccount(t)
|
from := e.NewAccount(t)
|
||||||
cFrom := c.WithSigners(from)
|
cFrom := c.WithSigners(from)
|
||||||
|
cFromCommittee := c.WithSigners(from, c.Committee)
|
||||||
to := e.NewAccount(t)
|
to := e.NewAccount(t)
|
||||||
cTo := c.WithSigners(to)
|
cTo := c.WithSigners(to)
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
cFromCommittee.Invoke(t, true, "register", "neo.com", from.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
cFrom.Invoke(t, true, "register", "neo.com", from.ScriptHash())
|
cFrom.Invoke(t, stackitem.Null{}, "addRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||||
cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
|
||||||
cFrom.InvokeFail(t, "token not found", "transfer", to.ScriptHash(), "not.exists", nil)
|
cFrom.InvokeFail(t, "token not found", "transfer", to.ScriptHash(), "not.exists", nil)
|
||||||
c.Invoke(t, false, "transfer", to.ScriptHash(), "neo.com", nil)
|
c.Invoke(t, false, "transfer", to.ScriptHash(), "neo.com", nil)
|
||||||
cFrom.Invoke(t, true, "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")
|
cFrom.Invoke(t, to.ScriptHash().BytesBE(), "ownerOf", "neo.com")
|
||||||
|
|
||||||
// without onNEP11Transfer
|
// without onNEP11Transfer
|
||||||
|
@ -358,30 +403,32 @@ func TestTransfer(t *testing.T) {
|
||||||
&compiler.Options{Name: "foo"})
|
&compiler.Options{Name: "foo"})
|
||||||
e.DeployContract(t, ctr, nil)
|
e.DeployContract(t, ctr, nil)
|
||||||
cTo.Invoke(t, true, "transfer", ctr.Hash, []byte("neo.com"), 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"))
|
cFrom.Invoke(t, ctr.Hash.BytesBE(), "ownerOf", []byte("neo.com"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTokensOf(t *testing.T) {
|
func TestTokensOf(t *testing.T) {
|
||||||
c := newNSClient(t)
|
c := newNSClient(t, false)
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
acc1 := e.NewAccount(t)
|
acc1 := e.NewAccount(t)
|
||||||
cAcc1 := c.WithSigners(acc1)
|
cAcc1Committee := c.WithSigners(acc1, c.Committee)
|
||||||
acc2 := e.NewAccount(t)
|
acc2 := e.NewAccount(t)
|
||||||
cAcc2 := c.WithSigners(acc2)
|
cAcc2Committee := c.WithSigners(acc2, c.Committee)
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
tld := []byte("com")
|
||||||
cAcc1.Invoke(t, true, "register", "neo.com", acc1.ScriptHash())
|
c.Invoke(t, true, "register", tld, c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash())
|
cAcc1Committee.Invoke(t, true, "register", "neo.com", acc1.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
|
cAcc2Committee.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
|
|
||||||
testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
|
testTokensOf(t, c, tld, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
|
||||||
testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
|
testTokensOf(t, c, tld, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
|
||||||
testTokensOf(t, c, [][]byte{[]byte("neo.com"), []byte("nspcc.com")})
|
testTokensOf(t, c, tld, [][]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{}, 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"
|
method := "tokensOf"
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
method = "tokens"
|
method = "tokens"
|
||||||
|
@ -399,31 +446,195 @@ func testTokensOf(t *testing.T, c *neotest.ContractInvoker, result [][]byte, arg
|
||||||
require.Equal(t, result[i], iter.Value().Value())
|
require.Equal(t, result[i], iter.Value().Value())
|
||||||
arr = append(arr, stackitem.Make(result[i]))
|
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())
|
require.False(t, iter.Next())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolve(t *testing.T) {
|
func TestResolve(t *testing.T) {
|
||||||
c := newNSClient(t)
|
c := newNSClient(t, true)
|
||||||
e := c.Executor
|
e := c.Executor
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
acc := e.NewAccount(t)
|
acc := e.NewAccount(t)
|
||||||
cAcc := c.WithSigners(acc)
|
cAcc := c.WithSigners(acc)
|
||||||
|
cAccCommittee := c.WithSigners(acc, c.Committee)
|
||||||
|
|
||||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
cAcc.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{}, "setRecord", "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{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com")
|
|
||||||
|
|
||||||
cAcc.Invoke(t, true, "register", "alias.com", acc.ScriptHash())
|
cAccCommittee.Invoke(t, true, "register", "alias.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "alias.com", int64(nns.TXT), "sometxt")
|
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))
|
cAccCommittee.Invoke(t, true, "register", "alias2.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
c.Invoke(t, "alias.com", "resolve", "neo.com", int64(nns.CNAME))
|
cAcc.Invoke(t, stackitem.Null{}, "addRecord", "alias2.com", int64(nns.TXT), "sometxt from alias2")
|
||||||
c.Invoke(t, "sometxt", "resolve", "neo.com", int64(nns.TXT))
|
|
||||||
c.Invoke(t, stackitem.Null{}, "resolve", "neo.com", int64(nns.AAAA))
|
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, true)
|
||||||
|
e := c.Executor
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
cAcc := c.WithSigners(acc)
|
||||||
|
cAccCommittee := c.WithSigners(acc, c.Committee)
|
||||||
|
|
||||||
|
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
|
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
|
||||||
|
time := e.TopBlock(t).Timestamp
|
||||||
|
|
||||||
|
// Add some arbitrary data.
|
||||||
|
cAccCommittee.Invoke(t, true, "register", "alias.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
|
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.SOA),
|
||||||
|
stackitem.NewBuffer([]byte("neo.com" + " " + mail + " " +
|
||||||
|
strconv.Itoa(int(time)) + " " + strconv.Itoa(int(refresh)) + " " +
|
||||||
|
strconv.Itoa(int(retry)) + " " + strconv.Itoa(int(expire)) + " " +
|
||||||
|
strconv.Itoa(int(ttl)))),
|
||||||
|
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, true)
|
||||||
|
e := c.Executor
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
cAcc := c.WithSigners(acc)
|
||||||
|
cAccCommittee := c.WithSigners(acc, c.Committee)
|
||||||
|
|
||||||
|
cAccCommittee.Invoke(t, true, "register", "neo.com", acc.ScriptHash(), mail, refresh, retry, expire, ttl)
|
||||||
|
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(), mail, refresh, retry, expire, ttl)
|
||||||
|
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, true)
|
||||||
|
cAccCommittee := c.WithSigners(c.Committee)
|
||||||
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
|
||||||
|
cAccCommittee.Invoke(t, true, "register", "neo.com", c.CommitteeHash, mail, refresh, retry, expire, ttl)
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNNSRegisterArbitraryLevelDomain(t *testing.T) {
|
||||||
|
c := newNSClient(t, true)
|
||||||
|
|
||||||
|
newArgs := func(domain string, account neotest.Signer) []interface{} {
|
||||||
|
return []interface{}{
|
||||||
|
domain, account.ScriptHash(), "doesnt@matter.com",
|
||||||
|
int64(101), int64(102), int64(103), int64(104),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc := c.NewAccount(t)
|
||||||
|
cBoth := c.WithSigners(c.Committee, acc)
|
||||||
|
args := newArgs("neo.com", acc)
|
||||||
|
cBoth.Invoke(t, true, "register", args...)
|
||||||
|
|
||||||
|
c1 := c.WithSigners(acc)
|
||||||
|
// parent domain is missing
|
||||||
|
args[0] = "testnet.fs.neo.com"
|
||||||
|
c1.InvokeFail(t, "one of the parent domains is not registered", "register", args...)
|
||||||
|
|
||||||
|
args[0] = "fs.neo.com"
|
||||||
|
c1.Invoke(t, true, "register", args...)
|
||||||
|
|
||||||
|
args[0] = "testnet.fs.neo.com"
|
||||||
|
c1.Invoke(t, true, "register", args...)
|
||||||
|
|
||||||
|
acc2 := c.NewAccount(t)
|
||||||
|
c2 := c.WithSigners(c.Committee, acc2)
|
||||||
|
args = newArgs("mainnet.fs.neo.com", acc2)
|
||||||
|
c2.InvokeFail(t, "not witnessed by admin", "register", args...)
|
||||||
|
|
||||||
|
c1.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"something.mainnet.fs.neo.com", int64(nns.A), "1.2.3.4")
|
||||||
|
c1.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"another.fs.neo.com", int64(nns.A), "4.3.2.1")
|
||||||
|
|
||||||
|
c2 = c.WithSigners(acc, acc2)
|
||||||
|
c2.Invoke(t, stackitem.NewBool(false), "isAvailable", "mainnet.fs.neo.com")
|
||||||
|
c2.InvokeFail(t, "parent domain has conflicting records: something.mainnet.fs.neo.com",
|
||||||
|
"register", args...)
|
||||||
|
|
||||||
|
c1.Invoke(t, stackitem.Null{}, "deleteRecords",
|
||||||
|
"something.mainnet.fs.neo.com", int64(nns.A))
|
||||||
|
c2.Invoke(t, stackitem.NewBool(true), "isAvailable", "mainnet.fs.neo.com")
|
||||||
|
c2.Invoke(t, true, "register", args...)
|
||||||
|
|
||||||
|
c2 = c.WithSigners(acc2)
|
||||||
|
c2.Invoke(t, stackitem.Null{}, "addRecord",
|
||||||
|
"cdn.mainnet.fs.neo.com", int64(nns.A), "166.15.14.13")
|
||||||
|
result := stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray([]byte("166.15.14.13")),
|
||||||
|
})
|
||||||
|
c2.Invoke(t, result, "resolve", "cdn.mainnet.fs.neo.com", int64(nns.A))
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultNameServiceDomainPrice = 10_0000_0000
|
defaultNameServiceDomainPrice = 10_0000_0000
|
||||||
defaultNameServiceSysfee = 6000_0000
|
defaultNameServiceSysfee = 6000_0000
|
||||||
|
maxRecordID = 255
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,8 @@ const (
|
||||||
A RecordType = 1
|
A RecordType = 1
|
||||||
// CNAME represents canonical name record type.
|
// CNAME represents canonical name record type.
|
||||||
CNAME RecordType = 5
|
CNAME RecordType = 5
|
||||||
|
// SOA represents start of authority record type.
|
||||||
|
SOA RecordType = 6
|
||||||
// TXT represents text record type.
|
// TXT represents text record type.
|
||||||
TXT RecordType = 16
|
TXT RecordType = 16
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,7 +21,10 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const neoAmount = 99999000
|
const (
|
||||||
|
neoAmount = 99999000
|
||||||
|
millisecondsInYear = 365 * 24 * 3600 * 1000
|
||||||
|
)
|
||||||
|
|
||||||
// Init pushes some predefined set of transactions into the given chain, it needs a path to
|
// Init pushes some predefined set of transactions into the given chain, it needs a path to
|
||||||
// the root project directory.
|
// the root project directory.
|
||||||
|
@ -158,17 +161,19 @@ func Init(t *testing.T, rootpath string, e *neotest.Executor) {
|
||||||
_, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, nsConfigPath, 4) // block #11
|
_, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, nsConfigPath, 4) // block #11
|
||||||
nsCommitteeInvoker := e.CommitteeInvoker(nsHash)
|
nsCommitteeInvoker := e.CommitteeInvoker(nsHash)
|
||||||
nsPriv0Invoker := e.NewInvoker(nsHash, acc0)
|
nsPriv0Invoker := e.NewInvoker(nsHash, acc0)
|
||||||
|
nsPriv0CommitteeInvoker := e.NewInvoker(nsHash, acc0, e.Committee)
|
||||||
|
|
||||||
// Block #12: transfer funds to committee for further NS record registration.
|
// Block #12: transfer funds to committee for further NS record registration.
|
||||||
gasValidatorInvoker.Invoke(t, true, "transfer",
|
gasValidatorInvoker.Invoke(t, true, "transfer",
|
||||||
e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12
|
e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12
|
||||||
|
|
||||||
// Block #13: add `.com` root to NNS.
|
// Block #13: add `.com` root to NNS.
|
||||||
nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "com") // block #13
|
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
|
||||||
|
nsCommitteeInvoker.Invoke(t, true, "register", "com", nsCommitteeInvoker.CommitteeHash, mail, refresh, retry, expire, ttl) // block #13
|
||||||
|
|
||||||
// Block #14: register `neo.com` via NNS.
|
// Block #14: register `neo.com` via NNS.
|
||||||
registerTxH := nsPriv0Invoker.Invoke(t, true, "register",
|
registerTxH := nsPriv0CommitteeInvoker.Invoke(t, true, "register",
|
||||||
"neo.com", priv0ScriptHash) // block #14
|
"neo.com", priv0ScriptHash, mail, refresh, retry, expire, ttl) // block #14
|
||||||
res := e.GetTxExecResult(t, registerTxH)
|
res := e.GetTxExecResult(t, registerTxH)
|
||||||
require.Equal(t, 1, len(res.Events)) // transfer
|
require.Equal(t, 1, len(res.Events)) // transfer
|
||||||
tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes()
|
tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes()
|
||||||
|
@ -176,7 +181,7 @@ func Init(t *testing.T, rootpath string, e *neotest.Executor) {
|
||||||
t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID))
|
t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID))
|
||||||
|
|
||||||
// Block #15: set A record type with priv0 owner via NNS.
|
// 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
|
// 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
|
txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", "newtestvalue") // tx1
|
||||||
|
|
|
@ -1370,7 +1370,7 @@ func TestClient_NEP11_ND(t *testing.T) {
|
||||||
t.Run("TotalSupply", func(t *testing.T) {
|
t.Run("TotalSupply", func(t *testing.T) {
|
||||||
s, err := n11.TotalSupply()
|
s, err := n11.TotalSupply()
|
||||||
require.NoError(t, err)
|
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) {
|
t.Run("Symbol", func(t *testing.T) {
|
||||||
sym, err := n11.Symbol()
|
sym, err := n11.Symbol()
|
||||||
|
@ -1403,14 +1403,14 @@ func TestClient_NEP11_ND(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
items, err := iter.Next(config.DefaultMaxIteratorResultItems)
|
items, err := iter.Next(config.DefaultMaxIteratorResultItems)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(items))
|
require.Equal(t, 2, len(items))
|
||||||
require.Equal(t, [][]byte{[]byte("neo.com")}, items)
|
require.Equal(t, [][]byte{[]byte("neo.com"), []byte("com")}, items)
|
||||||
require.NoError(t, iter.Terminate())
|
require.NoError(t, iter.Terminate())
|
||||||
})
|
})
|
||||||
t.Run("TokensExpanded", func(t *testing.T) {
|
t.Run("TokensExpanded", func(t *testing.T) {
|
||||||
items, err := n11.TokensExpanded(config.DefaultMaxIteratorResultItems)
|
items, err := n11.TokensExpanded(config.DefaultMaxIteratorResultItems)
|
||||||
require.NoError(t, err)
|
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) {
|
t.Run("Properties", func(t *testing.T) {
|
||||||
p, err := n11.Properties([]byte("neo.com"))
|
p, err := n11.Properties([]byte("neo.com"))
|
||||||
|
@ -1421,6 +1421,7 @@ func TestClient_NEP11_ND(t *testing.T) {
|
||||||
expected := stackitem.NewMap()
|
expected := stackitem.NewMap()
|
||||||
expected.Add(stackitem.Make([]byte("name")), stackitem.Make([]byte("neo.com")))
|
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("expiration")), stackitem.Make(blockRegisterDomain.Timestamp+365*24*3600*1000)) // expiration formula
|
||||||
|
expected.Add(stackitem.Make([]byte("admin")), stackitem.Null{})
|
||||||
require.EqualValues(t, expected, p)
|
require.EqualValues(t, expected, p)
|
||||||
})
|
})
|
||||||
t.Run("Transfer", func(t *testing.T) {
|
t.Run("Transfer", func(t *testing.T) {
|
||||||
|
|
|
@ -74,12 +74,12 @@ const (
|
||||||
verifyContractHash = "06ed5314c2e4cb103029a60b86d46afa2fb8f67c"
|
verifyContractHash = "06ed5314c2e4cb103029a60b86d46afa2fb8f67c"
|
||||||
verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A="
|
verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A="
|
||||||
verifyWithArgsContractHash = "0dce75f52adb1a4c5c6eaa6a34eb26db2e5b3781"
|
verifyWithArgsContractHash = "0dce75f52adb1a4c5c6eaa6a34eb26db2e5b3781"
|
||||||
nnsContractHash = "bdbfe1a280a0e23ca5b569c8f5845169bd93cb06"
|
nnsContractHash = "cb93bcab0d6d435b61fa96a3bbce3b6f043968b5"
|
||||||
nnsToken1ID = "6e656f2e636f6d"
|
nnsToken1ID = "6e656f2e636f6d"
|
||||||
nfsoContractHash = "0e15ca0df00669a2cd5dcb03bfd3e2b3849c2969"
|
nfsoContractHash = "0e15ca0df00669a2cd5dcb03bfd3e2b3849c2969"
|
||||||
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
||||||
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
||||||
block20StateRootLE = "f1380226a217b5e35ea968d42c50e20b9af7ab83b91416c8fb85536c61004332"
|
block20StateRootLE = "7f80c7e265a44faa7374953d4d5059d21b34e65e06a7695d57ca8c59cc9a36fa"
|
||||||
storageContractHash = "ebc0c16a76c808cd4dde6bcc063f09e45e331ec7"
|
storageContractHash = "ebc0c16a76c808cd4dde6bcc063f09e45e331ec7"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -287,6 +287,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
return &map[string]interface{}{
|
return &map[string]interface{}{
|
||||||
"name": "neo.com",
|
"name": "neo.com",
|
||||||
"expiration": "lhbLRl0B",
|
"expiration": "lhbLRl0B",
|
||||||
|
"admin": nil, // no admin was set
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -935,7 +936,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
chg := []dboper.Operation{{
|
chg := []dboper.Operation{{
|
||||||
State: "Changed",
|
State: "Changed",
|
||||||
Key: []byte{0xfa, 0xff, 0xff, 0xff, 0xb},
|
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",
|
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},
|
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",
|
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},
|
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.
|
// Can be returned in any order.
|
||||||
assert.ElementsMatch(t, chg, res.Diagnostics.Changes)
|
assert.ElementsMatch(t, chg, res.Diagnostics.Changes)
|
||||||
|
@ -963,7 +964,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
|
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
|
||||||
return &result.Invoke{
|
return &result.Invoke{
|
||||||
State: "HALT",
|
State: "HALT",
|
||||||
GasConsumed: 15928320,
|
GasConsumed: 22192980,
|
||||||
Script: script,
|
Script: script,
|
||||||
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
|
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
|
||||||
Notifications: []state.NotificationEvent{},
|
Notifications: []state.NotificationEvent{},
|
||||||
|
@ -975,6 +976,15 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
{
|
{
|
||||||
Current: nnsHash,
|
Current: nnsHash,
|
||||||
Calls: []*invocations.Tree{
|
Calls: []*invocations.Tree{
|
||||||
|
{
|
||||||
|
Current: stdHash,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Current: cryptoHash,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Current: stdHash,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Current: stdHash,
|
Current: stdHash,
|
||||||
},
|
},
|
||||||
|
@ -1078,7 +1088,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
|
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
|
||||||
return &result.Invoke{
|
return &result.Invoke{
|
||||||
State: "HALT",
|
State: "HALT",
|
||||||
GasConsumed: 15928320,
|
GasConsumed: 22192980,
|
||||||
Script: script,
|
Script: script,
|
||||||
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
|
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
|
||||||
Notifications: []state.NotificationEvent{},
|
Notifications: []state.NotificationEvent{},
|
||||||
|
@ -1090,6 +1100,15 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
{
|
{
|
||||||
Current: nnsHash,
|
Current: nnsHash,
|
||||||
Calls: []*invocations.Tree{
|
Calls: []*invocations.Tree{
|
||||||
|
{
|
||||||
|
Current: stdHash,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Current: cryptoHash,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Current: stdHash,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Current: stdHash,
|
Current: stdHash,
|
||||||
},
|
},
|
||||||
|
@ -2717,7 +2736,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Asset: e.chain.UtilityTokenHash(),
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
Amount: "37099660700",
|
Amount: "37076412050",
|
||||||
LastUpdated: 22,
|
LastUpdated: 22,
|
||||||
Decimals: 8,
|
Decimals: 8,
|
||||||
Name: "GasToken",
|
Name: "GasToken",
|
||||||
|
|
BIN
pkg/services/rpcsrv/testdata/testblocks.acc
vendored
BIN
pkg/services/rpcsrv/testdata/testblocks.acc
vendored
Binary file not shown.
Loading…
Reference in a new issue