package valopers
import (
"chain"
"strings"
"testing"
"gno.land/p/nt/avl/v0"
"gno.land/p/nt/ownable/v0/exts/authorizable"
"gno.land/p/nt/testutils/v0"
"gno.land/p/nt/uassert/v0"
"gno.land/p/nt/ufmt/v0"
)
func validValidatorInfo(t *testing.T) struct {
Moniker string
Description string
ServerType string
Address address
PubKey string
} {
t.Helper()
return struct {
Moniker string
Description string
ServerType string
Address address
PubKey string
}{
Moniker: "test-1",
Description: "test-1's description",
ServerType: ServerTypeOnPrem,
Address: address("g1sp8v98h2gadm5jggtzz9w5ksexqn68ympsd68h"),
PubKey: "gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqwpdwpd0f9fvqla089ndw5g9hcsufad77fml2vlu73fk8q8sh8v72cza5p",
}
}
func TestValopers_Register(t *testing.T) {
test1 := testutils.TestAddress("test1")
testing.SetRealm(testing.NewUserRealm(test1))
t.Run("already a valoper", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
v := Valoper{
Moniker: info.Moniker,
Description: info.Description,
ServerType: info.ServerType,
Address: info.Address,
PubKey: info.PubKey,
KeepRunning: true,
}
// Add the valoper
valopers.Set(v.Address.String(), v)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
uassert.AbortsWithMessage(t, ErrValoperExists.Error(), func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
})
t.Run("no coins deposited", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Send no coins
testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", 0)})
uassert.AbortsWithMessage(t, ufmt.Sprintf("payment must not be less than %d%s", minFee.Amount, minFee.Denom), func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
})
t.Run("insufficient coins amount deposited", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Send invalid coins
testing.SetOriginSend(chain.Coins{chain.NewCoin("ugnot", minFee.Amount-1)})
uassert.AbortsWithMessage(t, ufmt.Sprintf("payment must not be less than %d%s", minFee.Amount, minFee.Denom), func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
})
t.Run("coin amount deposited is not ugnot", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Send invalid coins
testing.SetOriginSend(chain.Coins{chain.NewCoin("gnogno", minFee.Amount)})
uassert.AbortsWithMessage(t, "incompatible coin denominations: gnogno, ugnot", func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
})
t.Run("successful registration", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
uassert.NotAborts(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
uassert.NotPanics(t, func() {
valoper := GetByAddr(info.Address)
uassert.Equal(t, info.Moniker, valoper.Moniker)
uassert.Equal(t, info.Description, valoper.Description)
uassert.Equal(t, info.ServerType, valoper.ServerType)
uassert.Equal(t, info.Address, valoper.Address)
uassert.Equal(t, info.PubKey, valoper.PubKey)
uassert.Equal(t, true, valoper.KeepRunning)
})
})
}
func TestValopers_UpdateAuthMembers(t *testing.T) {
test1Address := testutils.TestAddress("test1")
test2Address := testutils.TestAddress("test2")
t.Run("unauthorized member adds member", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
testing.SetRealm(testing.NewUserRealm(test1Address))
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
testing.SetRealm(testing.NewUserRealm(info.Address))
// try to add member without being authorized
uassert.AbortsWithMessage(t, authorizable.ErrNotSuperuser.Error(), func() {
AddToAuthList(cross, info.Address, test2Address)
})
})
t.Run("unauthorized member deletes member", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
testing.SetRealm(testing.NewUserRealm(test1Address))
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
uassert.NotPanics(t, func() {
// XXX this panics.
AddToAuthList(cross, info.Address, test2Address)
})
testing.SetRealm(testing.NewUserRealm(info.Address))
// try to add member without being authorized
uassert.AbortsWithMessage(t, authorizable.ErrNotSuperuser.Error(), func() {
DeleteFromAuthList(cross, info.Address, test2Address)
})
})
t.Run("authorized member adds member", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
testing.SetRealm(testing.NewUserRealm(test1Address))
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
uassert.NotPanics(t, func() {
AddToAuthList(cross, info.Address, test2Address)
})
testing.SetRealm(testing.NewUserRealm(test2Address))
newMoniker := "new moniker"
// Update the valoper
uassert.NotPanics(t, func() {
UpdateMoniker(cross, info.Address, newMoniker)
})
uassert.NotPanics(t, func() {
valoper := GetByAddr(info.Address)
uassert.Equal(t, newMoniker, valoper.Moniker)
})
})
}
func TestValopers_UpdateMoniker(t *testing.T) {
test1Address := testutils.TestAddress("test1")
test2Address := testutils.TestAddress("test2")
t.Run("non-existing valoper", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Update the valoper
uassert.AbortsWithMessage(t, ErrValoperMissing.Error(), func() {
UpdateMoniker(cross, info.Address, "new moniker")
})
})
t.Run("invalid caller", func(t *testing.T) {
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
// Change the origin caller
testing.SetOriginCaller(test2Address)
// Update the valoper
uassert.AbortsWithMessage(t, authorizable.ErrNotInAuthList.Error(), func() {
UpdateMoniker(cross, info.Address, "new moniker")
})
})
t.Run("invalid moniker", func(t *testing.T) {
invalidMonikers := []string{
"", // Empty
" ", // Whitespace
"a", // Too short
"a very long moniker that is longer than 32 characters", // Too long
"!@#$%^&*()+{}|:<>?/.,;'", // Invalid characters
" space in front",
"space in back ",
}
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
for _, invalidMoniker := range invalidMonikers {
// Update the valoper
uassert.AbortsWithMessage(t, ErrInvalidMoniker.Error(), func() {
UpdateMoniker(cross, info.Address, invalidMoniker)
})
}
})
t.Run("too long moniker", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
// Update the valoper
uassert.AbortsWithMessage(t, ErrInvalidMoniker.Error(), func() {
UpdateMoniker(cross, info.Address, strings.Repeat("a", MonikerMaxLength+1))
})
})
t.Run("successful update", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
newMoniker := "new moniker"
// Update the valoper
uassert.NotPanics(t, func() {
UpdateMoniker(cross, info.Address, newMoniker)
})
// Make sure the valoper is updated
uassert.NotPanics(t, func() {
valoper := GetByAddr(info.Address)
uassert.Equal(t, newMoniker, valoper.Moniker)
})
})
}
func TestValopers_UpdateDescription(t *testing.T) {
test1Address := testutils.TestAddress("test1")
test2Address := testutils.TestAddress("test2")
t.Run("non-existing valoper", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
// Update the valoper
uassert.AbortsWithMessage(t, ErrValoperMissing.Error(), func() {
UpdateDescription(cross, validValidatorInfo(t).Address, "new description")
})
})
t.Run("invalid caller", func(t *testing.T) {
// Set the origin caller
testing.SetOriginCaller(test1Address)
info := validValidatorInfo(t)
// Clear the set for the test
valopers = avl.NewTree()
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
// Change the origin caller
testing.SetOriginCaller(test2Address)
// Update the valoper
uassert.AbortsWithMessage(t, authorizable.ErrNotInAuthList.Error(), func() {
UpdateDescription(cross, info.Address, "new description")
})
})
t.Run("empty description", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
emptyDescription := ""
// Update the valoper
uassert.AbortsWithMessage(t, ErrInvalidDescription.Error(), func() {
UpdateDescription(cross, info.Address, emptyDescription)
})
})
t.Run("too long description", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
// Update the valoper
uassert.AbortsWithMessage(t, ErrInvalidDescription.Error(), func() {
UpdateDescription(cross, info.Address, strings.Repeat("a", DescriptionMaxLength+1))
})
})
t.Run("successful update", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
newDescription := "new description"
// Update the valoper
uassert.NotPanics(t, func() {
UpdateDescription(cross, info.Address, newDescription)
})
// Make sure the valoper is updated
uassert.NotPanics(t, func() {
valoper := GetByAddr(info.Address)
uassert.Equal(t, newDescription, valoper.Description)
})
})
}
func TestValopers_UpdateKeepRunning(t *testing.T) {
test1Address := testutils.TestAddress("test1")
test2Address := testutils.TestAddress("test2")
t.Run("non-existing valoper", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
// Update the valoper
uassert.AbortsWithMessage(t, ErrValoperMissing.Error(), func() {
UpdateKeepRunning(cross, validValidatorInfo(t).Address, false)
})
})
t.Run("invalid caller", func(t *testing.T) {
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
// Change the origin caller
testing.SetOriginCaller(test2Address)
// Update the valoper
uassert.AbortsWithMessage(t, authorizable.ErrNotInAuthList.Error(), func() {
UpdateKeepRunning(cross, info.Address, false)
})
})
t.Run("successful update", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
// Update the valoper
uassert.NotPanics(t, func() {
UpdateKeepRunning(cross, info.Address, false)
})
// Make sure the valoper is updated
uassert.NotPanics(t, func() {
valoper := GetByAddr(info.Address)
uassert.Equal(t, false, valoper.KeepRunning)
})
})
}
func TestValopers_UpdateServerType(t *testing.T) {
test1Address := testutils.TestAddress("test1")
test2Address := testutils.TestAddress("test2")
t.Run("non-existing valoper", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
// Update the valoper
uassert.AbortsWithMessage(t, ErrValoperMissing.Error(), func() {
UpdateServerType(cross, validValidatorInfo(t).Address, ServerTypeCloud)
})
})
t.Run("invalid caller", func(t *testing.T) {
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
// Change the origin caller
testing.SetOriginCaller(test2Address)
// Update the valoper
uassert.AbortsWithMessage(t, authorizable.ErrNotInAuthList.Error(), func() {
UpdateServerType(cross, info.Address, ServerTypeCloud)
})
})
t.Run("invalid server type", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
invalidServerTypes := []string{
"",
"invalid",
"Cloud", // case sensitive
"ON-PREM", // case sensitive
"datacenter", // wrong format
}
for _, invalidType := range invalidServerTypes {
// Update the valoper with invalid server type
uassert.AbortsWithMessage(t, ErrInvalidServerType.Error(), func() {
UpdateServerType(cross, info.Address, invalidType)
})
}
})
t.Run("successful update", func(t *testing.T) {
// Clear the set for the test
valopers = avl.NewTree()
info := validValidatorInfo(t)
// Set the origin caller
testing.SetOriginCaller(test1Address)
// Send coins
testing.SetOriginSend(chain.Coins{minFee})
// Add the valoper with on-prem server type
uassert.NotPanics(t, func() {
Register(cross, info.Moniker, info.Description, info.ServerType, info.Address, info.PubKey)
})
// Update the valoper to cloud
uassert.NotPanics(t, func() {
UpdateServerType(cross, info.Address, ServerTypeCloud)
})
// Make sure the valoper is updated
uassert.NotPanics(t, func() {
valoper := GetByAddr(info.Address)
uassert.Equal(t, ServerTypeCloud, valoper.ServerType)
})
// Update the valoper to data-center
uassert.NotPanics(t, func() {
UpdateServerType(cross, info.Address, ServerTypeDataCenter)
})
// Make sure the valoper is updated
uassert.NotPanics(t, func() {
valoper := GetByAddr(info.Address)
uassert.Equal(t, ServerTypeDataCenter, valoper.ServerType)
})
})
}