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,
// where token key = hash160(token ID) and token ID = domain name.
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
// prefixRoot contains set of roots (map from root to 0).
prefixRoot byte = 0x20
@ -91,7 +92,13 @@ func _deploy(data interface{}, isUpdate bool) {
}
ctx := storage.GetContext()
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.
@ -193,19 +200,31 @@ func Roots() iterator.Iterator {
}
// SetPrice sets the domain registration price.
func SetPrice(price int) {
func SetPrice(priceList []int) {
checkCommittee()
if price < 0 || price > maxRegisterPrice {
panic("The price is out of range.")
if len(priceList) == 0 {
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()
storage.Put(ctx, []byte{prefixRegisterPrice}, price)
storage.Put(ctx, []byte{prefixRegisterPrice}, std.Serialize(priceList))
}
// GetPrice returns the domain registration price.
func GetPrice() int {
// GetPrice returns the domain registration price depending on the domain name length.
func GetPrice(length int) int {
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.
@ -222,6 +241,9 @@ func IsAvailable(name string) bool {
}
return true
}
if GetPrice(len(fragments[0])) < 0 {
return false
}
if !parentExpired(ctx, 0, fragments) {
return false
}
@ -306,7 +328,13 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
if !runtime.CheckWitness(owner) {
panic("not witnessed by owner")
}
runtime.BurnGas(GetPrice())
price := GetPrice(len(fragments[0]))
if price < 0 {
checkCommittee()
} else {
runtime.BurnGas(price)
}
var (
tokenKey = getTokenKey([]byte(name))
oldOwner interop.Hash160
@ -390,7 +418,17 @@ func Renew(name string) int {
if len(name) > maxDomainNameLength {
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()
ns := getNameState(ctx, []byte(name))
ns.Expiration += millisecondsInYear

View file

@ -39,8 +39,10 @@ func newNSClient(t *testing.T, registerComTLD bool) *neotest.ContractInvoker {
func TestNameService_Price(t *testing.T) {
const (
minPrice = int64(0)
maxPrice = int64(10000_00000000)
minPrice = int64(-1)
maxPrice = int64(10000_00000000)
defaultPrice = 10_0000_0000
committeePrice = -1
)
c := newNSClient(t, false)
@ -50,28 +52,38 @@ func TestNameService_Price(t *testing.T) {
cAcc := c.WithSigners(acc)
cAcc.InvokeFail(t, "not witnessed by committee", "setPrice", minPrice+1)
})
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) {
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) {
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) {
txSet := c.PrepareInvoke(t, "setPrice", int64(defaultNameServiceDomainPrice+1))
txGet := c.PrepareInvoke(t, "getPrice")
c.AddBlockCheckHalt(t, txSet, txGet)
txSet := c.PrepareInvoke(t, "setPrice", []interface{}{defaultPrice - 1, committeePrice, committeePrice, committeePrice, committeePrice, committeePrice})
txGet1 := c.PrepareInvoke(t, "getPrice", 5)
txGet2 := c.PrepareInvoke(t, "getPrice", 6)
c.AddBlockCheckHalt(t, txSet, txGet1, txGet2)
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.
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
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.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, "isAvailable", "neo.com")
c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.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, "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, 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", defaultNameServiceDomainPrice, "register", "neo.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
c.Invoke(t, true, "isAvailable", "neo-go.com")
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-go.org", 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-go.com\n", 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-go.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
var maxLenFragment string
for i := 0; i < maxDomainNameFragmentLength; i++ {
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.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, 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)
expectedExpiration := topBlock.Timestamp + uint64(expire*1000)
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, "register", "neo-go.com", e.CommitteeHash, mail, refresh, retry, expire, ttl)
c.Invoke(t, false, "isAvailable", "neo-go.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)
@ -182,12 +194,12 @@ func TestRegisterAndRenew(t *testing.T) {
})
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("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, 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) {
c.InvokeFail(t, "token not found", "properties", "not.exists")
@ -198,10 +210,10 @@ func TestRegisterAndRenew(t *testing.T) {
// Renew
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))
c.Invoke(t, props, "properties", "neo.com")
c.Invoke(t, props, "properties", "neo-go.com")
}
func TestSetAddGetRecord(t *testing.T) {
@ -594,43 +606,44 @@ func TestNNSRegisterArbitraryLevelDomain(t *testing.T) {
cBoth.Invoke(t, true, "register", args...)
c1 := c.WithSigners(acc)
// Use long (>4 chars) domain name to avoid committee signature check.
// 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...)
args[0] = "fs.neo.com"
args[0] = "filestorage.neo.com"
c1.Invoke(t, true, "register", args...)
args[0] = "testnet.fs.neo.com"
args[0] = "testnet.filestorage.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)
args = newArgs("mainnet.filestorage.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")
"something.mainnet.filestorage.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")
"another.filestorage.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",
c2.Invoke(t, stackitem.NewBool(false), "isAvailable", "mainnet.filestorage.neo.com")
c2.InvokeFail(t, "parent domain has conflicting records: something.mainnet.filestorage.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")
"something.mainnet.filestorage.neo.com", int64(nns.A))
c2.Invoke(t, stackitem.NewBool(true), "isAvailable", "mainnet.filestorage.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")
"cdn.mainnet.filestorage.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))
c2.Invoke(t, result, "resolve", "cdn.mainnet.filestorage.neo.com", int64(nns.A))
}
const (