nns: make domain registration price dependant on length

Port 8ac2b80734.
This commit is contained in:
Anna Shaleva 2022-09-15 22:42:39 +03:00
parent 14c9858df2
commit dea82ac4dd
2 changed files with 106 additions and 55 deletions

View file

@ -29,7 +29,8 @@ const (
// prefixAccountToken contains map from (owner + token key) to token ID, // prefixAccountToken contains map from (owner + token key) to token ID,
// where token key = hash160(token ID) and token ID = domain name. // where token key = hash160(token ID) and token ID = domain name.
prefixAccountToken byte = 0x02 prefixAccountToken byte = 0x02
// prefixRegisterPrice contains price for new domain name registration. // prefixRegisterPrice contains list of prices for new domain name registration
// depending on the domain name length.
prefixRegisterPrice byte = 0x10 prefixRegisterPrice byte = 0x10
// prefixRoot contains set of roots (map from root to 0). // prefixRoot contains set of roots (map from root to 0).
prefixRoot byte = 0x20 prefixRoot byte = 0x20
@ -91,7 +92,13 @@ func _deploy(data interface{}, isUpdate bool) {
} }
ctx := storage.GetContext() ctx := storage.GetContext()
storage.Put(ctx, []byte{prefixTotalSupply}, 0) storage.Put(ctx, []byte{prefixTotalSupply}, 0)
storage.Put(ctx, []byte{prefixRegisterPrice}, defaultRegisterPrice) storage.Put(ctx, []byte{prefixRegisterPrice}, std.Serialize([]int{
defaultRegisterPrice, // Prices for all other lengths of domain names.
-1, // Domain names with a length of 1 are not open for registration by default.
-1, // Domain names with a length of 2 are not open for registration by default.
-1, // Domain names with a length of 3 are not open for registration by default.
-1, // Domain names with a length of 4 are not open for registration by default.
}))
} }
// Symbol returns NeoNameService symbol. // Symbol returns NeoNameService symbol.
@ -193,19 +200,31 @@ func Roots() iterator.Iterator {
} }
// SetPrice sets the domain registration price. // SetPrice sets the domain registration price.
func SetPrice(price int) { func SetPrice(priceList []int) {
checkCommittee() checkCommittee()
if price < 0 || price > maxRegisterPrice { if len(priceList) == 0 {
panic("The price is out of range.") panic("price list is empty")
}
for i := 0; i < len(priceList); i++ {
if i == 0 && priceList[i] == -1 {
panic("default price is out of range")
}
if priceList[i] < -1 || priceList[i] > maxRegisterPrice {
panic("price is out of range")
}
} }
ctx := storage.GetContext() ctx := storage.GetContext()
storage.Put(ctx, []byte{prefixRegisterPrice}, price) storage.Put(ctx, []byte{prefixRegisterPrice}, std.Serialize(priceList))
} }
// GetPrice returns the domain registration price. // GetPrice returns the domain registration price depending on the domain name length.
func GetPrice() int { func GetPrice(length int) int {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return storage.Get(ctx, []byte{prefixRegisterPrice}).(int) priceList := std.Deserialize(storage.Get(ctx, []byte{prefixRegisterPrice}).([]byte)).([]int)
if length >= len(priceList) {
length = 0
}
return priceList[length]
} }
// IsAvailable checks whether provided domain name is available. // IsAvailable checks whether provided domain name is available.
@ -222,6 +241,9 @@ func IsAvailable(name string) bool {
} }
return true return true
} }
if GetPrice(len(fragments[0])) < 0 {
return false
}
if !parentExpired(ctx, 0, fragments) { if !parentExpired(ctx, 0, fragments) {
return false return false
} }
@ -306,7 +328,13 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
if !runtime.CheckWitness(owner) { if !runtime.CheckWitness(owner) {
panic("not witnessed by owner") panic("not witnessed by owner")
} }
runtime.BurnGas(GetPrice()) price := GetPrice(len(fragments[0]))
if price < 0 {
checkCommittee()
} else {
runtime.BurnGas(price)
}
var ( var (
tokenKey = getTokenKey([]byte(name)) tokenKey = getTokenKey([]byte(name))
oldOwner interop.Hash160 oldOwner interop.Hash160
@ -390,7 +418,17 @@ func Renew(name string) int {
if len(name) > maxDomainNameLength { if len(name) > maxDomainNameLength {
panic("invalid domain name format") panic("invalid domain name format")
} }
runtime.BurnGas(GetPrice()) fragments := splitAndCheck(name, true)
if fragments == nil {
panic("invalid domain name format")
}
price := GetPrice(len(fragments[0]))
if price < 0 {
checkCommittee()
} else {
runtime.BurnGas(price)
}
ctx := storage.GetContext() ctx := storage.GetContext()
ns := getNameState(ctx, []byte(name)) ns := getNameState(ctx, []byte(name))
ns.Expiration += millisecondsInYear ns.Expiration += millisecondsInYear

View file

@ -39,8 +39,10 @@ func newNSClient(t *testing.T, registerComTLD bool) *neotest.ContractInvoker {
func TestNameService_Price(t *testing.T) { func TestNameService_Price(t *testing.T) {
const ( const (
minPrice = int64(0) minPrice = int64(-1)
maxPrice = int64(10000_00000000) maxPrice = int64(10000_00000000)
defaultPrice = 10_0000_0000
committeePrice = -1
) )
c := newNSClient(t, false) c := newNSClient(t, false)
@ -50,28 +52,38 @@ func TestNameService_Price(t *testing.T) {
cAcc := c.WithSigners(acc) cAcc := c.WithSigners(acc)
cAcc.InvokeFail(t, "not witnessed by committee", "setPrice", minPrice+1) cAcc.InvokeFail(t, "not witnessed by committee", "setPrice", minPrice+1)
}) })
t.Run("get, default value", func(t *testing.T) { t.Run("get, default value", func(t *testing.T) {
c.Invoke(t, defaultNameServiceDomainPrice, "getPrice") c.Invoke(t, defaultPrice, "getPrice", 0)
c.Invoke(t, committeePrice, "getPrice", 1)
c.Invoke(t, committeePrice, "getPrice", 2)
c.Invoke(t, committeePrice, "getPrice", 3)
c.Invoke(t, committeePrice, "getPrice", 4)
c.Invoke(t, defaultPrice, "getPrice", 5)
}) })
t.Run("set, too small value", func(t *testing.T) { t.Run("set, too small value", func(t *testing.T) {
c.InvokeFail(t, "The price is out of range.", "setPrice", minPrice-1) c.InvokeFail(t, "price is out of range", "setPrice", []interface{}{minPrice - 1})
c.InvokeFail(t, "price is out of range", "setPrice", []interface{}{defaultPrice, minPrice - 1})
}) })
t.Run("set, too large value", func(t *testing.T) { t.Run("set, too large value", func(t *testing.T) {
c.InvokeFail(t, "The price is out of range.", "setPrice", maxPrice+1) c.InvokeFail(t, "price is out of range", "setPrice", []interface{}{minPrice - 1})
c.InvokeFail(t, "price is out of range", "setPrice", []interface{}{defaultPrice, minPrice - 1})
})
t.Run("set, negative default price", func(t *testing.T) {
c.InvokeFail(t, "default price is out of range", "setPrice", []interface{}{committeePrice, minPrice + 1})
}) })
t.Run("set, success", func(t *testing.T) { t.Run("set, success", func(t *testing.T) {
txSet := c.PrepareInvoke(t, "setPrice", int64(defaultNameServiceDomainPrice+1)) txSet := c.PrepareInvoke(t, "setPrice", []interface{}{defaultPrice - 1, committeePrice, committeePrice, committeePrice, committeePrice, committeePrice})
txGet := c.PrepareInvoke(t, "getPrice") txGet1 := c.PrepareInvoke(t, "getPrice", 5)
c.AddBlockCheckHalt(t, txSet, txGet) txGet2 := c.PrepareInvoke(t, "getPrice", 6)
c.AddBlockCheckHalt(t, txSet, txGet1, txGet2)
c.CheckHalt(t, txSet.Hash(), stackitem.Null{}) c.CheckHalt(t, txSet.Hash(), stackitem.Null{})
c.CheckHalt(t, txGet.Hash(), stackitem.Make(defaultNameServiceDomainPrice+1)) c.CheckHalt(t, txGet1.Hash(), stackitem.Make(committeePrice))
c.CheckHalt(t, txGet2.Hash(), stackitem.Make(defaultPrice-1))
// Get in the next block. // Get in the next block.
c.Invoke(t, stackitem.Make(defaultNameServiceDomainPrice+1), "getPrice") c.Invoke(t, stackitem.Make(committeePrice), "getPrice", 2)
c.Invoke(t, stackitem.Make(committeePrice), "getPrice", 5)
c.Invoke(t, stackitem.Make(defaultPrice-1), "getPrice", 6)
}) })
} }
@ -148,17 +160,17 @@ func TestRegisterAndRenew(t *testing.T) {
e := c.Executor e := c.Executor
mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104) mail, refresh, retry, expire, ttl := "sami@nspcc.ru", int64(101), int64(102), int64(millisecondsInYear/1000*100), int64(104)
c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com") c.InvokeFail(t, "TLD not found", "isAvailable", "neo-go.com")
c.Invoke(t, true, "register", "org", c.CommitteeHash, mail, refresh, retry, expire, ttl) c.Invoke(t, true, "register", "org", c.CommitteeHash, mail, refresh, retry, expire, ttl)
c.InvokeFail(t, "TLD not found", "isAvailable", "neo.com") c.InvokeFail(t, "TLD not found", "isAvailable", "neo-go.com")
c.Invoke(t, true, "register", "com", c.CommitteeHash, mail, refresh, retry, expire, ttl) 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-go.com")
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash, mail, refresh, retry, expire, ttl) c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo-go.org", e.CommitteeHash, mail, refresh, retry, expire, ttl)
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, "one of the parent domains is not registered", "register", "docs.neo-go.org", e.CommitteeHash, mail, refresh, retry, expire, ttl)
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", "\nneo-go.com'", e.CommitteeHash, mail, refresh, retry, expire, ttl)
c.InvokeFail(t, "invalid domain name format", "register", "neo.com\n", e.CommitteeHash, mail, refresh, retry, expire, ttl) c.InvokeFail(t, "invalid domain name format", "register", "neo-go.com\n", e.CommitteeHash, mail, refresh, retry, expire, ttl)
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash, mail, refresh, retry, expire, ttl) c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo-go.org", e.CommitteeHash, mail, refresh, retry, expire, ttl)
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceDomainPrice, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl) c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceDomainPrice, "register", "neo-go.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
var maxLenFragment string var maxLenFragment string
for i := 0; i < maxDomainNameFragmentLength; i++ { for i := 0; i < maxDomainNameFragmentLength; i++ {
maxLenFragment += "q" maxLenFragment += "q"
@ -167,13 +179,13 @@ func TestRegisterAndRenew(t *testing.T) {
c.Invoke(t, true, "register", maxLenFragment+".com", e.CommitteeHash, mail, refresh, retry, expire, ttl) 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.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-go.com")
c.Invoke(t, 3, "balanceOf", e.CommitteeHash) // org, com, qqq...qqq.com c.Invoke(t, 3, "balanceOf", e.CommitteeHash) // org, com, qqq...qqq.com
c.Invoke(t, true, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl) c.Invoke(t, true, "register", "neo-go.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
topBlock := e.TopBlock(t) topBlock := e.TopBlock(t)
expectedExpiration := topBlock.Timestamp + uint64(expire*1000) expectedExpiration := topBlock.Timestamp + uint64(expire*1000)
c.Invoke(t, false, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl) c.Invoke(t, false, "register", "neo-go.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
c.Invoke(t, false, "isAvailable", "neo.com") c.Invoke(t, false, "isAvailable", "neo-go.com")
t.Run("domain names with hyphen", func(t *testing.T) { 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)
@ -182,12 +194,12 @@ func TestRegisterAndRenew(t *testing.T) {
}) })
props := stackitem.NewMap() props := stackitem.NewMap()
props.Add(stackitem.Make("name"), stackitem.Make("neo.com")) props.Add(stackitem.Make("name"), stackitem.Make("neo-go.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 props.Add(stackitem.Make("admin"), stackitem.Null{}) // no admin was set
c.Invoke(t, props, "properties", "neo.com") c.Invoke(t, props, "properties", "neo-go.com")
c.Invoke(t, 5, "balanceOf", e.CommitteeHash) // org, com, qqq...qqq.com, neo.com, test-domain.com 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-go.com"))
t.Run("invalid token ID", func(t *testing.T) { t.Run("invalid token ID", func(t *testing.T) {
c.InvokeFail(t, "token not found", "properties", "not.exists") c.InvokeFail(t, "token not found", "properties", "not.exists")
@ -198,10 +210,10 @@ func TestRegisterAndRenew(t *testing.T) {
// Renew // Renew
expectedExpiration += millisecondsInYear expectedExpiration += millisecondsInYear
c.Invoke(t, expectedExpiration, "renew", "neo.com") c.Invoke(t, expectedExpiration, "renew", "neo-go.com")
props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration)) props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration))
c.Invoke(t, props, "properties", "neo.com") c.Invoke(t, props, "properties", "neo-go.com")
} }
func TestSetAddGetRecord(t *testing.T) { func TestSetAddGetRecord(t *testing.T) {
@ -594,43 +606,44 @@ func TestNNSRegisterArbitraryLevelDomain(t *testing.T) {
cBoth.Invoke(t, true, "register", args...) cBoth.Invoke(t, true, "register", args...)
c1 := c.WithSigners(acc) c1 := c.WithSigners(acc)
// Use long (>4 chars) domain name to avoid committee signature check.
// parent domain is missing // parent domain is missing
args[0] = "testnet.fs.neo.com" args[0] = "testnet.filestorage.neo.com"
c1.InvokeFail(t, "one of the parent domains is not registered", "register", args...) c1.InvokeFail(t, "one of the parent domains is not registered", "register", args...)
args[0] = "fs.neo.com" args[0] = "filestorage.neo.com"
c1.Invoke(t, true, "register", args...) c1.Invoke(t, true, "register", args...)
args[0] = "testnet.fs.neo.com" args[0] = "testnet.filestorage.neo.com"
c1.Invoke(t, true, "register", args...) c1.Invoke(t, true, "register", args...)
acc2 := c.NewAccount(t) acc2 := c.NewAccount(t)
c2 := c.WithSigners(c.Committee, acc2) c2 := c.WithSigners(c.Committee, acc2)
args = newArgs("mainnet.fs.neo.com", acc2) args = newArgs("mainnet.filestorage.neo.com", acc2)
c2.InvokeFail(t, "not witnessed by admin", "register", args...) c2.InvokeFail(t, "not witnessed by admin", "register", args...)
c1.Invoke(t, stackitem.Null{}, "addRecord", c1.Invoke(t, stackitem.Null{}, "addRecord",
"something.mainnet.fs.neo.com", int64(nns.A), "1.2.3.4") "something.mainnet.filestorage.neo.com", int64(nns.A), "1.2.3.4")
c1.Invoke(t, stackitem.Null{}, "addRecord", c1.Invoke(t, stackitem.Null{}, "addRecord",
"another.fs.neo.com", int64(nns.A), "4.3.2.1") "another.filestorage.neo.com", int64(nns.A), "4.3.2.1")
c2 = c.WithSigners(acc, acc2) c2 = c.WithSigners(acc, acc2)
c2.Invoke(t, stackitem.NewBool(false), "isAvailable", "mainnet.fs.neo.com") c2.Invoke(t, stackitem.NewBool(false), "isAvailable", "mainnet.filestorage.neo.com")
c2.InvokeFail(t, "parent domain has conflicting records: something.mainnet.fs.neo.com", c2.InvokeFail(t, "parent domain has conflicting records: something.mainnet.filestorage.neo.com",
"register", args...) "register", args...)
c1.Invoke(t, stackitem.Null{}, "deleteRecords", c1.Invoke(t, stackitem.Null{}, "deleteRecords",
"something.mainnet.fs.neo.com", int64(nns.A)) "something.mainnet.filestorage.neo.com", int64(nns.A))
c2.Invoke(t, stackitem.NewBool(true), "isAvailable", "mainnet.fs.neo.com") c2.Invoke(t, stackitem.NewBool(true), "isAvailable", "mainnet.filestorage.neo.com")
c2.Invoke(t, true, "register", args...) c2.Invoke(t, true, "register", args...)
c2 = c.WithSigners(acc2) c2 = c.WithSigners(acc2)
c2.Invoke(t, stackitem.Null{}, "addRecord", c2.Invoke(t, stackitem.Null{}, "addRecord",
"cdn.mainnet.fs.neo.com", int64(nns.A), "166.15.14.13") "cdn.mainnet.filestorage.neo.com", int64(nns.A), "166.15.14.13")
result := stackitem.NewArray([]stackitem.Item{ result := stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("166.15.14.13")), stackitem.NewByteArray([]byte("166.15.14.13")),
}) })
c2.Invoke(t, result, "resolve", "cdn.mainnet.fs.neo.com", int64(nns.A)) c2.Invoke(t, result, "resolve", "cdn.mainnet.filestorage.neo.com", int64(nns.A))
} }
const ( const (