From dad079b9ab22c5702e3556996f52b5fcf7174dd4 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 9 Sep 2022 19:38:38 +0300 Subject: [PATCH 01/19] nns: Add `admin` to properties Follow the https://github.com/neo-project/non-native-contracts/blob/14f43ba8cf169323b61c23a3a701ac77d9a4e3eb/src/NameService/NameService.cs#L69. Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 1 + tests/nns_test.go | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 3cc55128..05457dd6 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -202,6 +202,7 @@ func Properties(tokenID []byte) map[string]any { return map[string]any{ "name": ns.Name, "expiration": ns.Expiration, + "admin": ns.Admin, } } diff --git a/tests/nns_test.go b/tests/nns_test.go index a6a56463..87d3a2bd 100644 --- a/tests/nns_test.go +++ b/tests/nns_test.go @@ -274,7 +274,8 @@ func TestExpiration(t *testing.T) { checkProperties := func(t *testing.T, expiration uint64) { expected := stackitem.NewMapWithValue([]stackitem.MapElement{ {Key: stackitem.Make("name"), Value: stackitem.Make("testdomain.com")}, - {Key: stackitem.Make("expiration"), Value: stackitem.Make(expiration)}}) + {Key: stackitem.Make("expiration"), Value: stackitem.Make(expiration)}, + {Key: stackitem.Make("admin"), Value: stackitem.Null{}}}) s, err := c.TestInvoke(t, "properties", "testdomain.com") require.NoError(t, err) require.Equal(t, expected.Value(), s.Top().Item().Value()) @@ -315,6 +316,7 @@ func TestNNSSetAdmin(t *testing.T) { c.Invoke(t, true, "register", "testdomain.com", c.CommitteeHash, "myemail@nspcc.ru", refresh, retry, expire, ttl) + top := c.TopBlock(t) acc := c.NewAccount(t) cAcc := c.WithSigners(acc) @@ -324,6 +326,13 @@ func TestNNSSetAdmin(t *testing.T) { c1 := c.WithSigners(c.Committee, acc) c1.Invoke(t, stackitem.Null{}, "setAdmin", "testdomain.com", acc.ScriptHash()) + expiration := top.Timestamp + uint64(expire*1000) + expectedProps := stackitem.NewMapWithValue([]stackitem.MapElement{ + {Key: stackitem.Make("name"), Value: stackitem.Make("testdomain.com")}, + {Key: stackitem.Make("expiration"), Value: stackitem.Make(expiration)}, + {Key: stackitem.Make("admin"), Value: stackitem.Make(acc.ScriptHash().BytesBE())}}) + cAcc.Invoke(t, expectedProps, "properties", "testdomain.com") + cAcc.Invoke(t, stackitem.Null{}, "addRecord", "testdomain.com", int64(recordtype.TXT), "will be added") } @@ -369,7 +378,8 @@ func TestNNSRenew(t *testing.T) { c1.Invoke(t, ts, "renew", "testdomain.com") expected := stackitem.NewMapWithValue([]stackitem.MapElement{ {Key: stackitem.Make("name"), Value: stackitem.Make("testdomain.com")}, - {Key: stackitem.Make("expiration"), Value: stackitem.Make(ts)}}) + {Key: stackitem.Make("expiration"), Value: stackitem.Make(ts)}, + {Key: stackitem.Make("admin"), Value: stackitem.Null{}}}) cAcc.Invoke(t, expected, "properties", "testdomain.com") } From c7322964ed5795abff64506488c46c38d71074ab Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 12 Sep 2022 10:30:21 +0300 Subject: [PATCH 02/19] nns: Remove unused config file Signed-off-by: Anna Shaleva --- contracts/nns/nns.yml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 contracts/nns/nns.yml diff --git a/contracts/nns/nns.yml b/contracts/nns/nns.yml deleted file mode 100644 index 289793c9..00000000 --- a/contracts/nns/nns.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: "NameService" -supportedstandards: ["NEP-11"] -safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", - "tokens", "properties", "roots", "getPrice", "isAvailable", "getRecord", - "resolve", "getAllRecords"] -events: - - name: Transfer - parameters: - - name: from - type: Hash160 - - name: to - type: Hash160 - - name: amount - type: Integer - - name: tokenId - type: ByteArray -permissions: - - hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd - methods: ["update"] - - methods: ["onNEP11Payment"] From d77cfe557da8a79b24d2c5a5a2b46da169aee5bb Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 9 Sep 2022 19:40:48 +0300 Subject: [PATCH 03/19] nns: Fix typo in the method description Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 05457dd6..fd4691a2 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -590,7 +590,7 @@ func DeleteRecords(name string, typ recordtype.Type) { 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). // The name MUST NOT be a TLD. func Resolve(name string, typ recordtype.Type) []string { fragments := std.StringSplit(name, ".") From 51873d5a20715e6622b373fb2e4a49f634deb417 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 9 Sep 2022 19:40:54 +0300 Subject: [PATCH 04/19] nns: Restrict the maximum number of records with the same type Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 6 ++++++ tests/nns_test.go | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index fd4691a2..7f3dd16f 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -58,6 +58,9 @@ const ( 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. @@ -752,6 +755,9 @@ func addRecord(ctx storage.Context, tokenId []byte, name string, typ recordtype. panic("record already exists") } } + if id > maxRecordID { + panic("maximum number of records reached") + } if typ == recordtype.CNAME && id != 0 { panic("you shouldn't have more than one CNAME record") diff --git a/tests/nns_test.go b/tests/nns_test.go index 87d3a2bd..2c6577ab 100644 --- a/tests/nns_test.go +++ b/tests/nns_test.go @@ -4,6 +4,7 @@ import ( "fmt" "math/big" "path" + "strconv" "strings" "testing" "time" @@ -20,7 +21,10 @@ import ( const nnsPath = "../contracts/nns" -const msPerYear = 365 * 24 * time.Hour / time.Millisecond +const ( + msPerYear = 365 * 24 * time.Hour / time.Millisecond + maxRecordID = 255 // value from the contract. +) func newNNSInvoker(t *testing.T, addRoot bool, tldSet ...string) *neotest.ContractInvoker { e := newExecutor(t) @@ -515,3 +519,19 @@ func TestNNSRoots(t *testing.T) { require.ElementsMatch(t, tlds, res) } + +func TestNNSAddRecord(t *testing.T) { + c := newNNSInvoker(t, true) + + refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) + c.Invoke(t, true, "register", + "testdomain.com", c.CommitteeHash, + "myemail@nspcc.ru", refresh, retry, expire, ttl) + for i := 0; i <= maxRecordID+1; i++ { + if i == maxRecordID+1 { + c.InvokeFail(t, "maximum number of records reached", "addRecord", "testdomain.com", int64(recordtype.TXT), strconv.Itoa(i)) + } else { + c.Invoke(t, stackitem.Null{}, "addRecord", "testdomain.com", int64(recordtype.TXT), strconv.Itoa(i)) + } + } +} From c0b73a68463aa8efc981da3dfcf58aaf1102ec0e Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 9 Sep 2022 19:41:02 +0300 Subject: [PATCH 05/19] nns: Move common code to a separate method Reuse getAllRecords for GetAllRecords. Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 7f3dd16f..d653b1a0 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -613,11 +613,8 @@ func GetAllRecords(name string) iterator.Iterator { panic("token not found") } - tokenID := []byte(tokenIDFromName(name)) ctx := storage.GetReadOnlyContext() - _ = getFragmentedNameState(ctx, tokenID, fragments) // ensure not expired - recordsKey := getRecordsKey(tokenID, name) - return storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues) + return getAllRecords(ctx, name, fragments) } // updateBalance updates account's balance and account's tokens. @@ -1060,7 +1057,7 @@ func resolve(ctx storage.Context, res []string, name string, typ recordtype.Type if name[len(name)-1] == '.' { name = name[:len(name)-1] } - records := getAllRecords(ctx, name) + records := getAllRecords(ctx, name, nil) cname := "" for iterator.Next(records) { r := iterator.Value(records).(RecordState) @@ -1080,10 +1077,11 @@ func resolve(ctx storage.Context, res []string, name string, typ recordtype.Type } // getAllRecords returns iterator over the set of records corresponded with the -// specified name. -func getAllRecords(ctx storage.Context, name string) iterator.Iterator { +// specified name. Optional fragments parameter allows to pass pre-calculated +// elements of the domain name path: if empty, splits name on its own. +func getAllRecords(ctx storage.Context, name string, fragments []string) iterator.Iterator { tokenID := []byte(tokenIDFromName(name)) - _ = getNameState(ctx, tokenID) + _ = getFragmentedNameState(ctx, tokenID, fragments) // ensure not expired recordsKey := getRecordsKey(tokenID, name) return storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues) } From 9db9ace83cf622c1e6fd0fd2849b96b8c41e15ee Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 9 Sep 2022 19:41:21 +0300 Subject: [PATCH 06/19] nns: Fix CNAME resolution rules Do not include CNAME to the resulting list if we're looking for another record type. If it's CNAME than it must be resolved. Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 1 - tests/nns_test.go | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index d653b1a0..0b2cf565 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -1072,7 +1072,6 @@ func resolve(ctx storage.Context, res []string, name string, typ recordtype.Type return res } - res = append(res, cname) return resolve(ctx, res, cname, typ, redirect-1) } diff --git a/tests/nns_test.go b/tests/nns_test.go index 2c6577ab..60c1ca88 100644 --- a/tests/nns_test.go +++ b/tests/nns_test.go @@ -391,17 +391,26 @@ func TestNNSResolve(t *testing.T) { c := newNNSInvoker(t, true) refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) - c.Invoke(t, true, "register", - "test.com", c.CommitteeHash, - "myemail@nspcc.ru", refresh, retry, expire, ttl) + c.Invoke(t, true, "register", "test.com", c.CommitteeHash, "myemail@nspcc.ru", refresh, retry, expire, ttl) + c.Invoke(t, stackitem.Null{}, "addRecord", "test.com", int64(recordtype.TXT), "expected result") + c.Invoke(t, stackitem.Null{}, "addRecord", "test.com", int64(recordtype.CNAME), "alias.com") - c.Invoke(t, stackitem.Null{}, "addRecord", - "test.com", int64(recordtype.TXT), "expected result") + c.Invoke(t, true, "register", "alias.com", c.CommitteeHash, "myemail@nspcc.ru", refresh, retry, expire, ttl) + c.Invoke(t, stackitem.Null{}, "addRecord", "alias.com", int64(recordtype.A), "1.2.3.4") + c.Invoke(t, stackitem.Null{}, "addRecord", "alias.com", int64(recordtype.CNAME), "alias2.com") + + c.Invoke(t, true, "register", "alias2.com", c.CommitteeHash, "myemail@nspcc.ru", refresh, retry, expire, ttl) + c.Invoke(t, stackitem.Null{}, "addRecord", "alias2.com", int64(recordtype.A), "5.6.7.8") records := stackitem.NewArray([]stackitem.Item{stackitem.Make("expected result")}) c.Invoke(t, records, "resolve", "test.com", int64(recordtype.TXT)) c.Invoke(t, records, "resolve", "test.com.", int64(recordtype.TXT)) c.InvokeFail(t, "invalid domain name format", "resolve", "test.com..", int64(recordtype.TXT)) + + // Check CNAME is properly resolved and is not included into the result list. + c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4"), stackitem.Make("5.6.7.8")}), "resolve", "test.com", int64(recordtype.A)) + // And this time it should be properly included without resolution. + c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("alias.com")}), "resolve", "test.com", int64(recordtype.CNAME)) } func TestNNSRegisterAccess(t *testing.T) { From 9f4a35e8de8aeb68a7e81b9f4bffb6330c9b556c Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 13 Sep 2022 12:37:26 +0300 Subject: [PATCH 07/19] nns: Refactor record-related operations code Do not move parts of SetRecord/AddRecord to a separate functions, it makes the contract code more complicated. Also, improve documentation a bit. Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 74 ++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 0b2cf565..e9a7e6dd 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -498,7 +498,7 @@ func SetAdmin(name string, admin interop.Hash160) { putNameState(ctx, ns) } -// SetRecord adds a new record of the specified type to the provided domain. +// SetRecord updates existing domain record with the specified type and ID. // The name MUST NOT be a TLD. func SetRecord(name string, typ recordtype.Type, id byte, data string) { tokenID := []byte(tokenIDFromName(name)) @@ -514,7 +514,12 @@ func SetRecord(name string, typ recordtype.Type, id byte, data string) { ctx := storage.GetContext() ns := getFragmentedNameState(ctx, tokenID, fragments) ns.checkAdmin() - putRecord(ctx, tokenID, name, typ, id, data) + recordKey := getIdRecordKey(tokenID, name, typ, id) + recBytes := storage.Get(ctx, recordKey) + if recBytes == nil { + panic("invalid record id") + } + storeRecord(ctx, recordKey, name, typ, id, data) updateSoaSerial(ctx, tokenID) } @@ -533,8 +538,8 @@ func checkBaseRecords(typ recordtype.Type, data string) bool { } } -// AddRecord adds a new record of the specified type to the provided domain. -// The name MUST NOT be a TLD. +// AddRecord appends domain record to the list of domain records with the specified type +// if it doesn't exist yet. The name MUST NOT be a TLD. func AddRecord(name string, typ recordtype.Type, data string) { tokenID := []byte(tokenIDFromName(name)) if !checkBaseRecords(typ, data) { @@ -549,7 +554,27 @@ func AddRecord(name string, typ recordtype.Type, data string) { ctx := storage.GetContext() ns := getFragmentedNameState(ctx, tokenID, fragments) ns.checkAdmin() - addRecord(ctx, tokenID, name, typ, data) + recordsKey := getRecordsKeyByType(tokenID, name, typ) + var id byte + records := storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues) + for iterator.Next(records) { + id++ + + r := iterator.Value(records).(RecordState) + if r.Name == name && r.Type == typ && r.Data == data { + panic("record already exists") + } + } + if id > maxRecordID { + panic("maximum number of records reached") + } + + if typ == recordtype.CNAME && id != 0 { + panic("you shouldn't have more than one CNAME record") + } + + recordKey := append(recordsKey, id) // the same as getIdRecordKey + storeRecord(ctx, recordKey, name, typ, id, data) updateSoaSerial(ctx, tokenID) } @@ -727,44 +752,7 @@ func getRecordsByType(ctx storage.Context, tokenId []byte, name string, typ reco return result } -// putRecord stores domain record. -func putRecord(ctx storage.Context, tokenId []byte, name string, typ recordtype.Type, id byte, data string) { - recordKey := getIdRecordKey(tokenId, name, typ, id) - recBytes := storage.Get(ctx, recordKey) - if recBytes == nil { - panic("invalid record id") - } - - storeRecord(ctx, recordKey, name, typ, id, data) -} - -// addRecord stores domain record. -func addRecord(ctx storage.Context, tokenId []byte, name string, typ recordtype.Type, data string) { - recordsKey := getRecordsKeyByType(tokenId, name, typ) - - var id byte - records := storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues) - for iterator.Next(records) { - id++ - - r := iterator.Value(records).(RecordState) - if r.Name == name && r.Type == typ && r.Data == data { - panic("record already exists") - } - } - if id > maxRecordID { - panic("maximum number of records reached") - } - - if typ == recordtype.CNAME && id != 0 { - panic("you shouldn't have more than one CNAME record") - } - - recordKey := append(recordsKey, id) // the same as getIdRecordKey - storeRecord(ctx, recordKey, name, typ, id, data) -} - -// storeRecord puts record to storage. +// storeRecord puts record to storage and performs no additional checks. func storeRecord(ctx storage.Context, recordKey []byte, name string, typ recordtype.Type, id byte, data string) { rs := RecordState{ Name: name, From d677c664bded34f95ea123acba375f202e196060 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 29 Jul 2024 16:20:52 +0300 Subject: [PATCH 08/19] nns: use tokenID for setRecord operations a.b.c.d can be an "a.b" record of "c.d" and can be "a" record of "b.c.d", the first one would fail to setRecord currently: at instruction 4287 (THROW): unhandled exception: "parent domain has expired" which is a 5758c841f24b3a0bd9c10f6aa4840f1ec3cf580e regression. Signed-off-by: Roman Khimov --- contracts/nns/contract.go | 2 +- tests/nns_test.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index e9a7e6dd..9a6bec20 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -506,7 +506,7 @@ func SetRecord(name string, typ recordtype.Type, id byte, data string) { panic("invalid record data") } - fragments := std.StringSplit(name, ".") + fragments := std.StringSplit(string(tokenID), ".") if len(fragments) == 1 { panic("token not found") } diff --git a/tests/nns_test.go b/tests/nns_test.go index 60c1ca88..16e3cbbb 100644 --- a/tests/nns_test.go +++ b/tests/nns_test.go @@ -184,6 +184,10 @@ func TestNNSRegisterMulti(t *testing.T) { c1.Invoke(t, stackitem.Null{}, "addRecord", "something.mainnet.fs.neo.com", int64(recordtype.A), "1.2.3.4") + c1.Invoke(t, stackitem.Null{}, "setRecord", + "something.mainnet.fs.neo.com", int64(recordtype.A), 0, "2.3.4.5") + c1.InvokeFail(t, "invalid record id", "setRecord", + "something.mainnet.fs.neo.com", int64(recordtype.A), 1, "2.3.4.5") c1.Invoke(t, stackitem.Null{}, "addRecord", "another.fs.neo.com", int64(recordtype.A), "4.3.2.1") From 725544160c13448b0219ba27e5fbf10e56d07951 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 13 Sep 2022 12:47:29 +0300 Subject: [PATCH 09/19] nns: Move common record checking code to a separate function Don't repeat it each time. Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 43 ++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 9a6bec20..aec6d5c7 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -501,19 +501,8 @@ func SetAdmin(name string, admin interop.Hash160) { // SetRecord updates existing domain record with the specified type and ID. // The name MUST NOT be a TLD. func SetRecord(name string, typ recordtype.Type, id byte, data string) { - tokenID := []byte(tokenIDFromName(name)) - if !checkBaseRecords(typ, data) { - panic("invalid record data") - } - - fragments := std.StringSplit(string(tokenID), ".") - if len(fragments) == 1 { - panic("token not found") - } - ctx := storage.GetContext() - ns := getFragmentedNameState(ctx, tokenID, fragments) - ns.checkAdmin() + tokenID := checkRecord(ctx, name, typ, data) recordKey := getIdRecordKey(tokenID, name, typ, id) recBytes := storage.Get(ctx, recordKey) if recBytes == nil { @@ -523,26 +512,23 @@ func SetRecord(name string, typ recordtype.Type, id byte, data string) { updateSoaSerial(ctx, tokenID) } -func checkBaseRecords(typ recordtype.Type, data string) bool { +// checkRecord performs record validness check and returns token ID. +func checkRecord(ctx storage.Context, name string, typ recordtype.Type, data string) []byte { + tokenID := []byte(tokenIDFromName(name)) + var ok bool switch typ { case recordtype.A: - return checkIPv4(data) + ok = checkIPv4(data) case recordtype.CNAME: - return splitAndCheck(data, true) != nil + ok = splitAndCheck(data, true) != nil case recordtype.TXT: - return len(data) <= maxTXTRecordLength + ok = len(data) <= maxTXTRecordLength case recordtype.AAAA: - return checkIPv6(data) + ok = checkIPv6(data) default: panic("unsupported record type") } -} - -// AddRecord appends domain record to the list of domain records with the specified type -// if it doesn't exist yet. The name MUST NOT be a TLD. -func AddRecord(name string, typ recordtype.Type, data string) { - tokenID := []byte(tokenIDFromName(name)) - if !checkBaseRecords(typ, data) { + if !ok { panic("invalid record data") } @@ -551,9 +537,16 @@ func AddRecord(name string, typ recordtype.Type, data string) { panic("token not found") } - ctx := storage.GetContext() ns := getFragmentedNameState(ctx, tokenID, fragments) ns.checkAdmin() + return tokenID +} + +// AddRecord appends domain record to the list of domain records with the specified type +// if it doesn't exist yet. The name MUST NOT be a TLD. +func AddRecord(name string, typ recordtype.Type, data string) { + ctx := storage.GetContext() + tokenID := checkRecord(ctx, name, typ, data) recordsKey := getRecordsKeyByType(tokenID, name, typ) var id byte records := storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues) From 224936cc3e7a49173b7657adad0cb7764b732f75 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 13 Sep 2022 12:50:04 +0300 Subject: [PATCH 10/19] nns: Accept token ID as an argument for storeRecord It doesn't save VM opcodes, but allows to keep record key creation logic in a single place which prevents the code from bugs appearance. Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index aec6d5c7..31af13e7 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -508,7 +508,7 @@ func SetRecord(name string, typ recordtype.Type, id byte, data string) { if recBytes == nil { panic("invalid record id") } - storeRecord(ctx, recordKey, name, typ, id, data) + storeRecord(ctx, tokenID, name, typ, id, data) updateSoaSerial(ctx, tokenID) } @@ -566,8 +566,7 @@ func AddRecord(name string, typ recordtype.Type, data string) { panic("you shouldn't have more than one CNAME record") } - recordKey := append(recordsKey, id) // the same as getIdRecordKey - storeRecord(ctx, recordKey, name, typ, id, data) + storeRecord(ctx, tokenID, name, typ, id, data) updateSoaSerial(ctx, tokenID) } @@ -746,7 +745,8 @@ func getRecordsByType(ctx storage.Context, tokenId []byte, name string, typ reco } // storeRecord puts record to storage and performs no additional checks. -func storeRecord(ctx storage.Context, recordKey []byte, name string, typ recordtype.Type, id byte, data string) { +func storeRecord(ctx storage.Context, tokenId []byte, name string, typ recordtype.Type, id byte, data string) { + recordKey := getIdRecordKey(tokenId, name, typ, id) rs := RecordState{ Name: name, Type: typ, From 78862e4322fb2b2235a51c4666af94f1fb113a23 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 13 Sep 2022 12:56:40 +0300 Subject: [PATCH 11/19] nns: Reuse storeRecord for storing SOA record Less code repeating. Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 31af13e7..b8e41618 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -759,22 +759,14 @@ func storeRecord(ctx storage.Context, tokenId []byte, name string, typ recordtyp // putSoaRecord stores soa domain record. func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expire, ttl int) { - var id byte tokenId := []byte(tokenIDFromName(name)) - recordKey := getIdRecordKey(tokenId, name, recordtype.SOA, id) - rs := RecordState{ - Name: name, - Type: recordtype.SOA, - ID: id, - Data: name + " " + email + " " + - std.Itoa(runtime.GetTime(), 10) + " " + - std.Itoa(refresh, 10) + " " + - std.Itoa(retry, 10) + " " + - std.Itoa(expire, 10) + " " + - std.Itoa(ttl, 10), - } - recBytes := std.Serialize(rs) - storage.Put(ctx, recordKey, recBytes) + data := name + " " + email + " " + + std.Itoa(runtime.GetTime(), 10) + " " + + std.Itoa(refresh, 10) + " " + + std.Itoa(retry, 10) + " " + + std.Itoa(expire, 10) + " " + + std.Itoa(ttl, 10) + storeRecord(ctx, tokenId, name, recordtype.SOA, 0, data) } // updateSoaSerial stores soa domain record. From c8825a18484286a9189169e7f03d736ed8482335 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 13 Sep 2022 17:24:16 +0300 Subject: [PATCH 12/19] nns: Move token key creation to a separate function Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index b8e41618..8bc2d31b 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -706,7 +706,7 @@ func getFragmentedNameState(ctx storage.Context, tokenID []byte, fragments []str // getNameStateWithKey returns domain name state by the specified token key. func getNameStateWithKey(ctx storage.Context, tokenKey []byte) NameState { - nameKey := append([]byte{prefixName}, tokenKey...) + nameKey := getNameStateKey(tokenKey) nsBytes := storage.Get(ctx, nameKey) if nsBytes == nil { panic("token not found") @@ -716,6 +716,11 @@ func getNameStateWithKey(ctx storage.Context, tokenKey []byte) NameState { 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. func putNameState(ctx storage.Context, ns NameState) { tokenKey := getTokenKey([]byte(ns.Name)) @@ -1005,7 +1010,7 @@ func tokenIDFromName(name string) string { l := len(fragments) - 1 for i := 0; i < l; i++ { tokenKey := getTokenKey([]byte(name[sum:])) - nameKey := append([]byte{prefixName}, tokenKey...) + nameKey := getNameStateKey(tokenKey) nsBytes := storage.Get(ctx, nameKey) if nsBytes != nil { ns := std.Deserialize(nsBytes.([]byte)).(NameState) From 836a5de200786bb86f15376db47a530c4d56c156 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 13 Sep 2022 17:26:45 +0300 Subject: [PATCH 13/19] nns: reuse existing context in tokenIDFromName Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 8bc2d31b..8db3ada9 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -514,7 +514,7 @@ func SetRecord(name string, typ recordtype.Type, id byte, data string) { // checkRecord performs record validness check and returns token ID. func checkRecord(ctx storage.Context, name string, typ recordtype.Type, data string) []byte { - tokenID := []byte(tokenIDFromName(name)) + tokenID := []byte(tokenIDFromName(ctx, name)) var ok bool switch typ { case recordtype.A: @@ -578,8 +578,8 @@ func GetRecords(name string, typ recordtype.Type) []string { panic("token not found") } - tokenID := []byte(tokenIDFromName(name)) ctx := storage.GetReadOnlyContext() + tokenID := []byte(tokenIDFromName(ctx, name)) _ = getFragmentedNameState(ctx, tokenID, fragments) // ensure not expired return getRecordsByType(ctx, tokenID, name, typ) } @@ -591,14 +591,14 @@ func DeleteRecords(name string, typ recordtype.Type) { panic("you cannot delete soa record") } - tokenID := []byte(tokenIDFromName(name)) + ctx := storage.GetContext() + tokenID := []byte(tokenIDFromName(ctx, name)) fragments := std.StringSplit(string(tokenID), ".") if len(fragments) == 1 { panic("token not found") } - ctx := storage.GetContext() ns := getFragmentedNameState(ctx, tokenID, fragments) ns.checkAdmin() recordsKey := getRecordsKeyByType(tokenID, name, typ) @@ -764,7 +764,7 @@ func storeRecord(ctx storage.Context, tokenId []byte, name string, typ recordtyp // putSoaRecord stores soa domain record. func putSoaRecord(ctx storage.Context, name, email string, refresh, retry, expire, ttl int) { - tokenId := []byte(tokenIDFromName(name)) + tokenId := []byte(tokenIDFromName(ctx, name)) data := name + " " + email + " " + std.Itoa(runtime.GetTime(), 10) + " " + std.Itoa(refresh, 10) + " " + @@ -999,13 +999,12 @@ func checkIPv6(data string) bool { } // tokenIDFromName returns token ID (domain.root) from the provided name. -func tokenIDFromName(name string) string { +func tokenIDFromName(ctx storage.Context, name string) string { fragments := splitAndCheck(name, true) if fragments == nil { panic("invalid domain name format") } - ctx := storage.GetReadOnlyContext() sum := 0 l := len(fragments) - 1 for i := 0; i < l; i++ { @@ -1057,7 +1056,7 @@ func resolve(ctx storage.Context, res []string, name string, typ recordtype.Type // specified name. Optional fragments parameter allows to pass pre-calculated // elements of the domain name path: if empty, splits name on its own. func getAllRecords(ctx storage.Context, name string, fragments []string) iterator.Iterator { - tokenID := []byte(tokenIDFromName(name)) + tokenID := []byte(tokenIDFromName(ctx, name)) _ = getFragmentedNameState(ctx, tokenID, fragments) // ensure not expired recordsKey := getRecordsKey(tokenID, name) return storage.Find(ctx, recordsKey, storage.ValuesOnly|storage.DeserializeValues) From 1bb8b1cde4bafe0ca9f3c5d49b07fd00fc85407c Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 14 Sep 2022 12:59:39 +0300 Subject: [PATCH 14/19] nns: Keep `isAvailable` in sync with `register` If conflicting records '*.domain' are present on new domain registration, then `isAvailable` should return false for this domain. Ref. https://github.com/nspcc-dev/neofs-contract/pull/175/commits/f25296b17a4dcaca50855c40d44a42bbcf0bb6a1. Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 35 ++++++++++++++++++++++++----------- tests/nns_test.go | 2 ++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 8db3ada9..93ab4783 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -299,7 +299,7 @@ func GetPrice() int { // IsAvailable checks whether the provided domain name is available. Notice that // TLD is available for the committee only. func IsAvailable(name string) bool { - fragments := splitAndCheck(name, false) + fragments := splitAndCheck(name, true) if fragments == nil { panic("invalid domain name format") } @@ -311,7 +311,27 @@ func IsAvailable(name string) bool { } return true } - return parentExpired(ctx, 0, fragments) + 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 is expired. @@ -371,15 +391,8 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry, ns.checkAdmin() } - 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) { - panic("parent domain has conflicting records: " + r.Name) - } + if conflict := getParentConflictingRecord(ctx, name, fragments); len(conflict) != 0 { + panic("parent domain has conflicting records: " + conflict) } if !isValid(owner) { diff --git a/tests/nns_test.go b/tests/nns_test.go index 16e3cbbb..5ac3e6f8 100644 --- a/tests/nns_test.go +++ b/tests/nns_test.go @@ -192,11 +192,13 @@ func TestNNSRegisterMulti(t *testing.T) { "another.fs.neo.com", int64(recordtype.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(recordtype.A)) + c2.Invoke(t, stackitem.NewBool(true), "isAvailable", "mainnet.fs.neo.com") c2.Invoke(t, true, "register", args...) c2 = c.WithSigners(acc2) From 8f751bc640af8f8a283e7f03d53a0a1be7cd6a65 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 14 Sep 2022 17:41:19 +0300 Subject: [PATCH 15/19] nns: Use millisecondsInSeconds constant where appropriate Signed-off-by: Anna Shaleva --- contracts/nns/contract.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 93ab4783..0c205e6d 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -68,8 +68,10 @@ const ( // defaultRegisterPrice is the default price for new domain registration. // nolint:unused defaultRegisterPrice = 10_0000_0000 + // millisecondsInSecond is the amount of milliseconds per second. + millisecondsInSecond = 1000 // millisecondsInYear is amount of milliseconds per year. - millisecondsInYear = int64(365 * 24 * 3600 * 1000) + millisecondsInYear = int64(365 * 24 * 3600 * millisecondsInSecond) ) // RecordState is a type that registered entities are saved to. @@ -460,7 +462,7 @@ func saveDomain(ctx storage.Context, name, email string, refresh, retry, expire, Owner: owner, Name: name, // NNS expiration is in milliseconds - Expiration: int64(runtime.GetTime() + expire*1000), + Expiration: int64(runtime.GetTime() + expire*millisecondsInSecond), }) putSoaRecord(ctx, name, email, refresh, retry, expire, ttl) } From 22be33c75243f0cc132cbbdf52a0b291d9b7d143 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 29 Jul 2024 16:30:36 +0300 Subject: [PATCH 16/19] nns: limit max number of records to 16 These numbers are pretty much arbitrary, but we currently don't need more than that. Signed-off-by: Roman Khimov --- contracts/nns/contract.go | 2 +- contracts/nns/contract.nef | Bin 7081 -> 6992 bytes contracts/nns/manifest.json | 2 +- tests/nns_test.go | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 0c205e6d..6e37d893 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -60,7 +60,7 @@ const ( maxTXTRecordLength = 255 // maxRecordID is the maximum value of record ID (the upper bound for the number // of records with the same type). - maxRecordID = 255 + maxRecordID = 15 ) // Other constants. diff --git a/contracts/nns/contract.nef b/contracts/nns/contract.nef index d91de27db1277b11a1bcbb948d2284453951b42a..75ef315daf64836991fd781559f6e71346fb15e2 100755 GIT binary patch delta 1904 zcmZWpYiv_x82(OsdOz)Y+Ff9<(Us9%gt5(i8(BnJ?AdIlX!1E#V>S(k(xDVT6yY-=id0b-sU74hbwM>~AtzOYx=GfnZ%}s9 zP47_kn#m09R;@LYpXsMNkQt$rV9zm{q2VYW6Kpxg7!nhFYG{Oy#{@RV(6OOt$bQBh zR8b=Sp@Q^lcB)2{Pd)icQ%`l1n;I{B!GuuKv;X$aTuR`5cqv1k(JpsH7=#2V!tM{? zNu%;tWCJ)2|01H43EAe9m6yWTmg8TyD!V*y0~t>3~TdBGG}O=(oOeD_*dZIsLRHf`PdjVe%KKo zkA+fj@abJy#>f7FSbbdYnR{XPX|V(q@6#bPZZM4-1%_()KY4M(w9m75>-COtQ{0qq zToX5y&U@x-8)rP9%3Zn*f3AD%|2nDV8X0sI=QAT32EnB^|~+dWxM za0OlpPbc|uX+Hd|MzI4vWg&l?7OMm;S;(xdiNa=*$Nd(BxN@ENw3du9d#7@=5@5~Y z&u+`IB-l4x;)OyDKD-UW+(DWyJd%LOY@n+T9#f=02^FO{p$ID-&bz7^w%o>4XnQ<0 z?BIucIpuP;dH~NrDh_<*0LV&8QS#v(U^(w|Mg1oX$&8nsb(ymGoF0mG2E0|Xu8J(k zZVRmQN0ZK{yvRE1vS;VDM9Jvmr1LQ^T3m=bY=Ag&&s<0Gq{GszN(tn+Wl4w={P;~Z zLg_FTz|EXHs8!Dc0w4}qmK98XjG?xaHO&bn1CB(BzVn0+q$3Yo54@EV_qm#)Y))!z zh}v=)!rJRrgqL)>n1<+($VW$`j@W4Q#Zbx--{C0Ik%sTkb}lsBD^<02hsopiE*m@> z&Hy`Cp+1G3oisUYX>0P_J{i6OHTm!qU@D0?#S0*uEh>cictMl`*hBubv^JHY_;A~U zyGh=AxfzB;GefocdeqQgprU>e#{(DNQw1(Q1l<-;?}7f0aAcxliIt1_JSzv!-Edw} zirJ(lA5=V{uZ6VA=p@H0RuYGXrU`7!uCsb6s)_7nU23UoHTjZlSv7AmklzaEnE?Kv z9xv$@fqwB|;lL*VZg} zMKPBmo)aLb5;F;jp&ZkhY2~-zCV>03z0;BuIjP#p1HTi{W3r&KkJ6BBmG3QF2UBS} z!IfridCcPW`?IxG8)>y(C6V~TmMLozu9Rjsdk~Zn7CvecV7NvHqF#FN)`@HX E0cM_easU7T delta 1941 zcmZuyZEO>D7{9w-+iQDU@7gjL?zN0=EDMa6KVyry)==XLk%M-YK~RXB^sZg6Z(Hxy z%|+u9lcE9sA22Y7fy4wp_+_ZX01M2w5GeaV%MG>*-f1 zmBkaZlNw|3O?p#fCrc19$&z7>eM&@(?M;YsSmHA*9p2mDXF2cmQ>22bIecDQgX7vR z@-#lD-AE4NM_LbGVIqjA>*(CROiU656f)r#buD!t!dpoV@kf2=3q$Tv;eF^VJcSf> z$cHxIy` zuw`O+aBoQLt&@}CzF3Efn1|Ucy8k$vO_s@JuIZio&z6{?20cL}%S}nM#FF(-b|;so z3%VsanVW6sl)2*fws~Qat9aKZ({E{H-jZ2K8=NxFWdN##E}L_c`&3I1cB8}#=!g#g zVyxM40A9#uwG?IYAzDWejMGo(&`Bdf0@?>)F$ukF#$9X`bx(`;vCmaMq+u;Z^q`y|0wqRh6=(*eC>&v1@Ep6ReHTQ< zBrbytf5IFj>$VwD%F__gZTRH$)3kze?kEZvQBwkn!D?Vvd8JWd$(2Hz2I?Fts~IhN zARv0t*BXL|2hnF`u=)Aw72W7Q;7I`iMWH{;O*%NY3LAZn zA&A*iPWvVg!A&}BZgqZ<$Sm=()867CstTzDXzsLEv5N=7@z#MzM2;o;dh23+ugJ09 zx`Z0(>u^I08U<~I1)FbSNI-AE+Cd9VRGxa%0_r!YJoPKkbDsL5ZnUAuJf+2>=9SA8 zbQH=K(W0)o)4^Nd{D2pwAk{62it0l>_?o%7RzX|-jf@05*PFnTO$$YJ@DlNP`@gSEOI_zi+@|jd~ebAB_#=n624`1lQeF!ov0MxLZxBAh2yFgaZ&r@$=kN= zw8lUxxV>@(+;`n57lW|k#pI90xkM=VQR4{6qDY%BZ9cZYl$!=a4*ch`^`{3#*oEAG zLQ*g5XB(PjeKBLfZDR_jje*lvUfHOwCa6SyG~{s$=)4g8(W7Hj4vkG9Wcv+jT@pzrXRr9Gq# NTb}BmtzVA*`VaIpf9wDN diff --git a/contracts/nns/manifest.json b/contracts/nns/manifest.json index 61b8440d..df9a873c 100755 --- a/contracts/nns/manifest.json +++ b/contracts/nns/manifest.json @@ -1 +1 @@ -{"name":"NameService","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":32,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addRecord","offset":3122,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"balanceOf","offset":862,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":677,"parameters":[],"returntype":"Integer","safe":true},{"name":"deleteRecords","offset":3346,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Void","safe":false},{"name":"getAllRecords","offset":3590,"parameters":[{"name":"name","type":"String"}],"returntype":"InteropInterface","safe":true},{"name":"getPrice","offset":1313,"parameters":[],"returntype":"Integer","safe":true},{"name":"getRecords","offset":3262,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"isAvailable","offset":1347,"parameters":[{"name":"name","type":"String"}],"returntype":"Boolean","safe":true},{"name":"ownerOf","offset":699,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Hash160","safe":true},{"name":"properties","offset":769,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Map","safe":true},{"name":"register","offset":1608,"parameters":[{"name":"name","type":"String"},{"name":"owner","type":"Hash160"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"registerTLD","offset":2283,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"renew","offset":2496,"parameters":[{"name":"name","type":"String"}],"returntype":"Integer","safe":false},{"name":"resolve","offset":3528,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"roots","offset":1207,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"setAdmin","offset":2707,"parameters":[{"name":"name","type":"String"},{"name":"admin","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"setPrice","offset":1235,"parameters":[{"name":"price","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRecord","offset":2882,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"id","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"symbol","offset":671,"parameters":[],"returntype":"String","safe":true},{"name":"tokens","offset":938,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"tokensOf","offset":967,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"InteropInterface","safe":true},{"name":"totalSupply","offset":683,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":1029,"parameters":[{"name":"to","type":"Hash160"},{"name":"tokenID","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"update","offset":587,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"String"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"updateSOA","offset":2617,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"version","offset":679,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenId","type":"ByteArray"}]}]},"features":{},"groups":[],"permissions":[{"contract":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","methods":["update"]},{"contract":"*","methods":["onNEP11Payment"]}],"supportedstandards":["NEP-11"],"trusts":[],"extra":null} \ No newline at end of file +{"name":"NameService","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":32,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addRecord","offset":3253,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"balanceOf","offset":872,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":677,"parameters":[],"returntype":"Integer","safe":true},{"name":"deleteRecords","offset":3570,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Void","safe":false},{"name":"getAllRecords","offset":3816,"parameters":[{"name":"name","type":"String"}],"returntype":"InteropInterface","safe":true},{"name":"getPrice","offset":1323,"parameters":[],"returntype":"Integer","safe":true},{"name":"getRecords","offset":3484,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"isAvailable","offset":1357,"parameters":[{"name":"name","type":"String"}],"returntype":"Boolean","safe":true},{"name":"ownerOf","offset":699,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Hash160","safe":true},{"name":"properties","offset":769,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Map","safe":true},{"name":"register","offset":1810,"parameters":[{"name":"name","type":"String"},{"name":"owner","type":"Hash160"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"registerTLD","offset":2361,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"renew","offset":2574,"parameters":[{"name":"name","type":"String"}],"returntype":"Integer","safe":false},{"name":"resolve","offset":3754,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"roots","offset":1217,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"setAdmin","offset":2785,"parameters":[{"name":"name","type":"String"},{"name":"admin","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"setPrice","offset":1245,"parameters":[{"name":"price","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRecord","offset":2960,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"id","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"symbol","offset":671,"parameters":[],"returntype":"String","safe":true},{"name":"tokens","offset":948,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"tokensOf","offset":977,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"InteropInterface","safe":true},{"name":"totalSupply","offset":683,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":1039,"parameters":[{"name":"to","type":"Hash160"},{"name":"tokenID","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"update","offset":587,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"String"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"updateSOA","offset":2695,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"version","offset":679,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenId","type":"ByteArray"}]}]},"features":{},"groups":[],"permissions":[{"contract":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","methods":["update"]},{"contract":"*","methods":["onNEP11Payment"]}],"supportedstandards":["NEP-11"],"trusts":[],"extra":null} \ No newline at end of file diff --git a/tests/nns_test.go b/tests/nns_test.go index 5ac3e6f8..ac1b4966 100644 --- a/tests/nns_test.go +++ b/tests/nns_test.go @@ -23,7 +23,7 @@ const nnsPath = "../contracts/nns" const ( msPerYear = 365 * 24 * time.Hour / time.Millisecond - maxRecordID = 255 // value from the contract. + maxRecordID = 15 // value from the contract. ) func newNNSInvoker(t *testing.T, addRoot bool, tldSet ...string) *neotest.ContractInvoker { From 650a7dc416412511c1d499d455c65a7f4a1dbdf3 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 29 Jul 2024 18:01:26 +0300 Subject: [PATCH 17/19] nns: separate "too long name" from other errors Part of #411. Signed-off-by: Roman Khimov --- contracts/nns/contract.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 6e37d893..63ca187c 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -470,7 +470,7 @@ func saveDomain(ctx storage.Context, name, email string, refresh, retry, expire, // Renew increases domain expiration date. func Renew(name string) int64 { if len(name) > maxDomainNameLength { - panic("invalid domain name format") + panic("too long name") } runtime.BurnGas(GetPrice()) ctx := storage.GetContext() @@ -484,7 +484,7 @@ func Renew(name string) int64 { // UpdateSOA updates soa record. func UpdateSOA(name, email string, refresh, retry, expire, ttl int) { if len(name) > maxDomainNameLength { - panic("invalid domain name format") + panic("too long name") } ctx := storage.GetContext() ns := getNameState(ctx, []byte(name)) @@ -495,7 +495,7 @@ func UpdateSOA(name, email string, refresh, retry, expire, ttl int) { // SetAdmin updates domain admin. The name MUST NOT be a TLD. func SetAdmin(name string, admin interop.Hash160) { if len(name) > maxDomainNameLength { - panic("invalid domain name format") + panic("too long name") } fragments := std.StringSplit(name, ".") From 6dfd53be80651c06d9d5653c9111d4ba62d626d2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 29 Jul 2024 18:02:01 +0300 Subject: [PATCH 18/19] nns: simplify checks on split, use different errors 1. splitAndCheck doesn't need allowMultipleFragments since there is exactly one user of it and it checks for the number of fragments anyway. 2. We can panic right inside of it with a bit more details, nil fragments are never valid. 3. Except for one case in checkRecord where the error must be different, so safeSplitAndCheck is introduced. Fixes #411. Signed-off-by: Roman Khimov --- contracts/nns/contract.go | 46 ++++++++++++++++++------------------ contracts/nns/contract.nef | Bin 6992 -> 6912 bytes contracts/nns/manifest.json | 2 +- tests/nns_test.go | 11 +++++---- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/contracts/nns/contract.go b/contracts/nns/contract.go index 63ca187c..98c5ecd1 100644 --- a/contracts/nns/contract.go +++ b/contracts/nns/contract.go @@ -301,10 +301,7 @@ func GetPrice() int { // IsAvailable checks whether the provided domain name is available. Notice that // TLD is available for the committee only. func IsAvailable(name string) bool { - fragments := splitAndCheck(name, true) - if fragments == nil { - panic("invalid domain name format") - } + fragments := splitAndCheck(name) ctx := storage.GetReadOnlyContext() l := len(fragments) if storage.Get(ctx, append([]byte{prefixRoot}, []byte(fragments[l-1])...)) == nil { @@ -367,10 +364,7 @@ func parentExpired(ctx storage.Context, first int, fragments []string) bool { // - starting from the 3rd level, the domain can only be registered by the // owner or administrator (if any) of the previous level domain func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool { - fragments := splitAndCheck(name, true) - if fragments == nil { - panic("invalid domain name format") - } + fragments := splitAndCheck(name) l := len(fragments) if l == 1 { @@ -436,9 +430,9 @@ func RegisterTLD(name, email string, refresh, retry, expire, ttl int) { // record and saves domain state calling saveDomain with given parameters and // empty owner. The name MUST be a valid TLD name. func saveCommitteeDomain(ctx storage.Context, name, email string, refresh, retry, expire, ttl int) { - fragments := splitAndCheck(name, false) + fragments := splitAndCheck(name) if len(fragments) != 1 { - panic("invalid domain name format") + panic("not a TLD") } tldKey := makeTLDKey(name) @@ -535,7 +529,8 @@ func checkRecord(ctx storage.Context, name string, typ recordtype.Type, data str case recordtype.A: ok = checkIPv4(data) case recordtype.CNAME: - ok = splitAndCheck(data, true) != nil + _, msg := safeSplitAndCheck(data) + ok = len(msg) == 0 case recordtype.TXT: ok = len(data) <= maxTXTRecordLength case recordtype.AAAA: @@ -884,23 +879,31 @@ func isAlNum(c uint8) bool { return c >= 'a' && c <= 'z' || c >= '0' && c <= '9' } -// splitAndCheck splits domain name into parts and validates it. -func splitAndCheck(name string, allowMultipleFragments bool) []string { +// safeSplitAndCheck validates the name and splits it into parts, if anything +// is wrong it returns a non-empty string in the second result. +func safeSplitAndCheck(name string) ([]string, string) { l := len(name) if l < minDomainNameLength || maxDomainNameLength < l { - return nil + return nil, "invalid domain name length" } fragments := std.StringSplit(name, ".") l = len(fragments) - if l > 2 && !allowMultipleFragments { - return nil - } for i := 0; i < l; i++ { if !checkFragment(fragments[i], i == l-1) { - return nil + return nil, "invalid domain fragment" } } - return fragments + return fragments, "" +} + +// splitAndCheck splits domain name into parts and validates it. It panics +// if anything is wrong. +func splitAndCheck(name string) []string { + r, err := safeSplitAndCheck(name) + if len(err) != 0 { + panic(err) + } + return r } // checkIPv4 checks record on IPv4 compliance. @@ -1015,10 +1018,7 @@ func checkIPv6(data string) bool { // tokenIDFromName returns token ID (domain.root) from the provided name. func tokenIDFromName(ctx storage.Context, name string) string { - fragments := splitAndCheck(name, true) - if fragments == nil { - panic("invalid domain name format") - } + fragments := splitAndCheck(name) sum := 0 l := len(fragments) - 1 diff --git a/contracts/nns/contract.nef b/contracts/nns/contract.nef index 75ef315daf64836991fd781559f6e71346fb15e2..9c084bd2c2eceff393496968f6dfc4c7531f11a5 100755 GIT binary patch delta 1081 zcmaiyZ)_7~9LJyMdUySE*K+N)j!A$^Gn!@KvMA4()x^#)g+}y9J1e{(mQZ_lba&TV z!|paU-<6s zp5J|b-_P&)EeyOgaQ;V(!7ksg%-a&|V2jL`BAjN^%z_BF*qzL4 zA|$y7aS1-Lfamya@*VnBcgxXJ zIpPmB9-U~8EC&%PJMKh|xbQB&1q*PMH+j;#4j~I(@J}=ILHON2&#VMsHt+~fPJ|l= zTY^2F5~Vt7miz`>=m|1Io19I?#IR-@DU`KCvN>)P?UYN< zUC$$bEP38|zzht{?tlG~lUeNVX20(zB{&)?Of7Q=B|>^sAvgI3a$@r6vE%Og5po_i zUhfyXH2$}=(hRw+PeP77MgIt-^N1&ZQnls~s2&A$j%@d(^FF^z%5)Hxnz_-<%_Z1j zDcTbegN9j^3v%x1oiraVfF-muTbJP#;qk5g6etXRGJC>z4FBvqbK0Hqw}1XcX7BuW zS#q16{YyXIIYMl>Ep!Y};i_rMWz#6hMq#v=BDs5>XQ@rmW}b@H%Bzq$Dq!oR4!gvS zQ~MjXv3fes@Xl&H?4gJL!)$Uab&aD_PDLX|1R=HMOJ*pKDl`8Bb41*y?C>F2lw{Zx z9vr;EHbO~t^5|cl=jv^H7c{+TtPqv}7mZR?72ry|{dr5Wb@k)q!F_*t(#B9w{yPl zoZtDK-#O>Y=DFre4>9hCNBAz+8-S}E;FoZ~^^B`lU=QENwMsC_C%OF+Jm5d#kOXme zf?Jf~NB0&Oa<_BCj=7g>mSNT1uH5w@G`R2V%BPi*X=tR*4~INE)tnz8)6OYpGh|G3 zOiRg-A!g+)dn7|%zy;4X{1x2x6y6myQY#}=)aT<{{ZV7!cs6GoQ;!w$Sz|yQ$maXh z{z56A9d3mWs>ZpP2(wkQT$dNl3GX|(-O^j-E(r~I1O#tAu7ZU3W30iLw?*U?#?8{Z zNdGtew%4VoYp@*gFTwDdr`6 zT-f@r2Bv7&##PAkSMakh8eecDR5Ho+0BbE&aiN`;dl(v;G(%l;hB=e&n*B{9=7hgC zwK3k>mK61MDKwI2z=Hrgc|X1c3$iUl zy-o!VGu}-+8x)jJ1v@d`4vWfBoPfQdU-6Z)TwUQJ-2VeA>eWpGo08thgB*$M&_lZf zdn(TRlEIRxUyDZ#vtsJX_hoiKl+v{y^Se^)kx@Pw8Rwkih`grsEeZp@A8$$e*@t9WTyZC diff --git a/contracts/nns/manifest.json b/contracts/nns/manifest.json index df9a873c..6c784b51 100755 --- a/contracts/nns/manifest.json +++ b/contracts/nns/manifest.json @@ -1 +1 @@ -{"name":"NameService","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":32,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addRecord","offset":3253,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"balanceOf","offset":872,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":677,"parameters":[],"returntype":"Integer","safe":true},{"name":"deleteRecords","offset":3570,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Void","safe":false},{"name":"getAllRecords","offset":3816,"parameters":[{"name":"name","type":"String"}],"returntype":"InteropInterface","safe":true},{"name":"getPrice","offset":1323,"parameters":[],"returntype":"Integer","safe":true},{"name":"getRecords","offset":3484,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"isAvailable","offset":1357,"parameters":[{"name":"name","type":"String"}],"returntype":"Boolean","safe":true},{"name":"ownerOf","offset":699,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Hash160","safe":true},{"name":"properties","offset":769,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Map","safe":true},{"name":"register","offset":1810,"parameters":[{"name":"name","type":"String"},{"name":"owner","type":"Hash160"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"registerTLD","offset":2361,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"renew","offset":2574,"parameters":[{"name":"name","type":"String"}],"returntype":"Integer","safe":false},{"name":"resolve","offset":3754,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"roots","offset":1217,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"setAdmin","offset":2785,"parameters":[{"name":"name","type":"String"},{"name":"admin","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"setPrice","offset":1245,"parameters":[{"name":"price","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRecord","offset":2960,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"id","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"symbol","offset":671,"parameters":[],"returntype":"String","safe":true},{"name":"tokens","offset":948,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"tokensOf","offset":977,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"InteropInterface","safe":true},{"name":"totalSupply","offset":683,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":1039,"parameters":[{"name":"to","type":"Hash160"},{"name":"tokenID","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"update","offset":587,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"String"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"updateSOA","offset":2695,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"version","offset":679,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenId","type":"ByteArray"}]}]},"features":{},"groups":[],"permissions":[{"contract":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","methods":["update"]},{"contract":"*","methods":["onNEP11Payment"]}],"supportedstandards":["NEP-11"],"trusts":[],"extra":null} \ No newline at end of file +{"name":"NameService","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":32,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addRecord","offset":3135,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"balanceOf","offset":872,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":677,"parameters":[],"returntype":"Integer","safe":true},{"name":"deleteRecords","offset":3452,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Void","safe":false},{"name":"getAllRecords","offset":3698,"parameters":[{"name":"name","type":"String"}],"returntype":"InteropInterface","safe":true},{"name":"getPrice","offset":1323,"parameters":[],"returntype":"Integer","safe":true},{"name":"getRecords","offset":3366,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"isAvailable","offset":1357,"parameters":[{"name":"name","type":"String"}],"returntype":"Boolean","safe":true},{"name":"ownerOf","offset":699,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Hash160","safe":true},{"name":"properties","offset":769,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Map","safe":true},{"name":"register","offset":1775,"parameters":[{"name":"name","type":"String"},{"name":"owner","type":"Hash160"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"registerTLD","offset":2291,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"renew","offset":2485,"parameters":[{"name":"name","type":"String"}],"returntype":"Integer","safe":false},{"name":"resolve","offset":3636,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"roots","offset":1217,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"setAdmin","offset":2670,"parameters":[{"name":"name","type":"String"},{"name":"admin","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"setPrice","offset":1245,"parameters":[{"name":"price","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRecord","offset":2832,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"id","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"symbol","offset":671,"parameters":[],"returntype":"String","safe":true},{"name":"tokens","offset":948,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"tokensOf","offset":977,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"InteropInterface","safe":true},{"name":"totalSupply","offset":683,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":1039,"parameters":[{"name":"to","type":"Hash160"},{"name":"tokenID","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"update","offset":587,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"String"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"updateSOA","offset":2593,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"version","offset":679,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenId","type":"ByteArray"}]}]},"features":{},"groups":[],"permissions":[{"contract":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","methods":["update"]},{"contract":"*","methods":["onNEP11Payment"]}],"supportedstandards":["NEP-11"],"trusts":[],"extra":null} \ No newline at end of file diff --git a/tests/nns_test.go b/tests/nns_test.go index ac1b4966..fe43f6ce 100644 --- a/tests/nns_test.go +++ b/tests/nns_test.go @@ -80,9 +80,12 @@ func TestNNSRegisterTLD(t *testing.T) { refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104) - c.InvokeFail(t, "invalid domain name format", "registerTLD", + c.InvokeFail(t, "invalid domain fragment", "registerTLD", "0com", "email@nspcc.ru", refresh, retry, expire, ttl) + c.InvokeFail(t, "not a TLD", "registerTLD", + "neo.org", "email@nspcc.ru", refresh, retry, expire, ttl) + acc := c.NewAccount(t) cAcc := c.WithSigners(acc) cAcc.InvokeFail(t, "not witnessed by committee", "registerTLD", @@ -108,10 +111,10 @@ func TestNNSRegister(t *testing.T) { c3 := c.WithSigners(accTop, acc) t.Run("domain names with hyphen", func(t *testing.T) { - c3.InvokeFail(t, "invalid domain name format", "register", + c3.InvokeFail(t, "invalid domain fragment", "register", "-testdomain.com", acc.ScriptHash(), "myemail@nspcc.ru", refresh, retry, expire, ttl) - c3.InvokeFail(t, "invalid domain name format", "register", + c3.InvokeFail(t, "invalid domain fragment", "register", "testdomain-.com", acc.ScriptHash(), "myemail@nspcc.ru", refresh, retry, expire, ttl) c3.Invoke(t, true, "register", @@ -411,7 +414,7 @@ func TestNNSResolve(t *testing.T) { records := stackitem.NewArray([]stackitem.Item{stackitem.Make("expected result")}) c.Invoke(t, records, "resolve", "test.com", int64(recordtype.TXT)) c.Invoke(t, records, "resolve", "test.com.", int64(recordtype.TXT)) - c.InvokeFail(t, "invalid domain name format", "resolve", "test.com..", int64(recordtype.TXT)) + c.InvokeFail(t, "invalid domain fragment", "resolve", "test.com..", int64(recordtype.TXT)) // Check CNAME is properly resolved and is not included into the result list. c.Invoke(t, stackitem.NewArray([]stackitem.Item{stackitem.Make("1.2.3.4"), stackitem.Make("5.6.7.8")}), "resolve", "test.com", int64(recordtype.A)) From e5d3659b545e8d1341df3c8d5d8d2180da3b8f50 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 30 Jul 2024 11:42:20 +0300 Subject: [PATCH 19/19] tests: fix sporadic container test failure Sometimes 10 is exactly the value, so changing it doesn't make the ID different: Error Trace: /home/runner/go/pkg/mod/github.com/nspcc-dev/neo-go@v0.106.2/pkg/neotest/basic.go:218 /home/runner/go/pkg/mod/github.com/nspcc-dev/neo-go@v0.106.2/pkg/neotest/client.go:108 /home/runner/work/neofs-contract/neofs-contract/tests/container_test.go:237 Error: Not equal: expected: 0x1 actual : 0x2 Test: TestContainerPut/standard_deploy/with_nice_names/register_in_advance Messages: at instruction 2149 (THROW): unhandled exception: "container was previously deleted" Signed-off-by: Roman Khimov --- tests/container_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/container_test.go b/tests/container_test.go index 65edc7a7..cd45f5c6 100644 --- a/tests/container_test.go +++ b/tests/container_test.go @@ -220,7 +220,7 @@ func TestContainerPut(t *testing.T) { cNNS.Invoke(t, stackitem.Null{}, "resolve", "mycnt."+containerDomain, int64(recordtype.TXT)) t.Run("register in advance", func(t *testing.T) { - cnt.value[len(cnt.value)-1] = 10 + cnt.value[len(cnt.value)-1]++ cnt.id = sha256.Sum256(cnt.value) cNNS.Invoke(t, stackitem.Null{}, "registerTLD",