package core import ( "testing" "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" ) func TestNameService_Price(t *testing.T) { bc := newTestChain(t) testGetSet(t, bc, bc.contracts.NameService.Hash, "Price", native.DefaultDomainPrice, 1, 10000_00000000) } func TestNonfungible(t *testing.T) { bc := newTestChain(t) acc := newAccountWithGAS(t, bc) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "symbol", "NNS") testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "decimals", 0) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "totalSupply", 0) } func TestAddRoot(t *testing.T) { bc := newTestChain(t) transferFundsToCommittee(t, bc) nsHash := bc.contracts.NameService.Hash t.Run("invalid format", func(t *testing.T) { testNameServiceInvoke(t, bc, "addRoot", nil, "") }) t.Run("not signed by committee", func(t *testing.T) { aer, err := invokeContractMethod(bc, 1000_0000, nsHash, "addRoot", "some") require.NoError(t, err) checkFAULTState(t, aer) }) testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "some") t.Run("already exists", func(t *testing.T) { testNameServiceInvoke(t, bc, "addRoot", nil, "some") }) } func TestExpiration(t *testing.T) { bc := newTestChain(t) transferFundsToCommittee(t, bc) acc := newAccountWithGAS(t, bc) testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "com") testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc, "register", true, "first.com", acc.Contract.ScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, "first.com", int64(native.RecordTypeTXT), "sometext") b1 := bc.topBlock.Load().(*block.Block) tx, err := prepareContractMethodInvokeGeneric(bc, defaultRegisterSysfee, bc.contracts.NameService.Hash, "register", acc, "second.com", acc.Contract.ScriptHash()) require.NoError(t, err) b2 := newBlockCustom(bc.GetConfig(), func(b *block.Block) { b.Index = b1.Index + 1 b.PrevHash = b1.Hash() b.Timestamp = b1.Timestamp + 10000 }, tx) require.NoError(t, bc.AddBlock(b2)) checkTxHalt(t, bc, tx.Hash()) tx, err = prepareContractMethodInvokeGeneric(bc, defaultNameServiceSysfee, bc.contracts.NameService.Hash, "isAvailable", acc, "first.com") require.NoError(t, err) b3 := newBlockCustom(bc.GetConfig(), func(b *block.Block) { b.Index = b2.Index + 1 b.PrevHash = b2.Hash() b.Timestamp = b1.Timestamp + (secondsInYear+1)*1000 }, tx) require.NoError(t, bc.AddBlock(b3)) aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application) require.NoError(t, err) checkResult(t, &aer[0], stackitem.NewBool(true)) tx, err = prepareContractMethodInvokeGeneric(bc, defaultNameServiceSysfee, bc.contracts.NameService.Hash, "isAvailable", acc, "second.com") require.NoError(t, err) b4 := newBlockCustom(bc.GetConfig(), func(b *block.Block) { b.Index = b3.Index + 1 b.PrevHash = b3.Hash() b.Timestamp = b3.Timestamp + 1000 }, tx) require.NoError(t, bc.AddBlock(b4)) aer, err = bc.GetAppExecResults(tx.Hash(), trigger.Application) require.NoError(t, err) checkResult(t, &aer[0], stackitem.NewBool(false)) tx, err = prepareContractMethodInvokeGeneric(bc, defaultNameServiceSysfee, bc.contracts.NameService.Hash, "getRecord", acc, "first.com", int64(native.RecordTypeTXT)) require.NoError(t, err) b5 := newBlockCustom(bc.GetConfig(), func(b *block.Block) { b.Index = b4.Index + 1 b.PrevHash = b4.Hash() b.Timestamp = b4.Timestamp + 1000 }, tx) require.NoError(t, bc.AddBlock(b5)) aer, err = bc.GetAppExecResults(tx.Hash(), trigger.Application) require.NoError(t, err) checkResult(t, &aer[0], stackitem.Null{}) } const secondsInYear = 365 * 24 * 3600 func TestRegisterAndRenew(t *testing.T) { bc := newTestChain(t) transferFundsToCommittee(t, bc) testNameServiceInvoke(t, bc, "isAvailable", nil, "neo.com") testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "org") testNameServiceInvoke(t, bc, "isAvailable", nil, "neo.com") testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "com") testNameServiceInvoke(t, bc, "isAvailable", true, "neo.com") testNameServiceInvoke(t, bc, "register", nil, "neo.org", testchain.CommitteeScriptHash()) testNameServiceInvoke(t, bc, "register", nil, "docs.neo.org", testchain.CommitteeScriptHash()) testNameServiceInvoke(t, bc, "register", nil, "\nneo.com'", testchain.CommitteeScriptHash()) testNameServiceInvoke(t, bc, "register", nil, "neo.com\n", testchain.CommitteeScriptHash()) testNameServiceInvoke(t, bc, "register", nil, "neo.com", testchain.CommitteeScriptHash()) testNameServiceInvokeAux(t, bc, native.DefaultDomainPrice, true, "register", nil, "neo.com", testchain.CommitteeScriptHash()) testNameServiceInvoke(t, bc, "isAvailable", true, "neo.com") testNameServiceInvoke(t, bc, "balanceOf", 0, testchain.CommitteeScriptHash()) testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "register", true, "neo.com", testchain.CommitteeScriptHash()) topBlock := bc.topBlock.Load().(*block.Block) expectedExpiration := topBlock.Timestamp/1000 + secondsInYear testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "register", false, "neo.com", testchain.CommitteeScriptHash()) testNameServiceInvoke(t, bc, "isAvailable", false, "neo.com") props := stackitem.NewMap() props.Add(stackitem.Make("name"), stackitem.Make("neo.com")) props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration)) testNameServiceInvoke(t, bc, "properties", props, "neo.com") testNameServiceInvoke(t, bc, "balanceOf", 1, testchain.CommitteeScriptHash()) testNameServiceInvoke(t, bc, "ownerOf", testchain.CommitteeScriptHash().BytesBE(), []byte("neo.com")) t.Run("invalid token ID", func(t *testing.T) { testNameServiceInvoke(t, bc, "properties", nil, "not.exists") testNameServiceInvoke(t, bc, "ownerOf", nil, "not.exists") testNameServiceInvoke(t, bc, "properties", nil, []interface{}{}) testNameServiceInvoke(t, bc, "ownerOf", nil, []interface{}{}) }) // Renew expectedExpiration += secondsInYear testNameServiceInvokeAux(t, bc, 100_0000_0000, true, "renew", expectedExpiration, "neo.com") props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration)) testNameServiceInvoke(t, bc, "properties", props, "neo.com") } func TestSetGetRecord(t *testing.T) { bc := newTestChain(t) transferFundsToCommittee(t, bc) acc := newAccountWithGAS(t, bc) testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "com") t.Run("set before register", func(t *testing.T) { testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(native.RecordTypeTXT), "sometext") }) testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "register", true, "neo.com", testchain.CommitteeScriptHash()) t.Run("invalid parameters", func(t *testing.T) { testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(0xFF), "1.2.3.4") testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(native.RecordTypeA), "not.an.ip.address") }) t.Run("invalid witness", func(t *testing.T) { testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", nil, "neo.com", int64(native.RecordTypeA), "1.2.3.4") }) testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA)) testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA), "1.2.3.4") testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA)) testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA), "1.2.3.4") testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA)) testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeAAAA), "2001:0000:1f1f:0000:0000:0100:11a0:addf") testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME), "nspcc.ru") testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeTXT), "sometext") // Delete record. t.Run("invalid witness", func(t *testing.T) { testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", nil, "neo.com", int64(native.RecordTypeCNAME)) }) testNameServiceInvoke(t, bc, "getRecord", "nspcc.ru", "neo.com", int64(native.RecordTypeCNAME)) testNameServiceInvoke(t, bc, "deleteRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME)) testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME)) testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA)) } func TestSetAdmin(t *testing.T) { bc := newTestChain(t) transferFundsToCommittee(t, bc) owner := newAccountWithGAS(t, bc) admin := newAccountWithGAS(t, bc) guest := newAccountWithGAS(t, bc) testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "com") testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, owner, "register", true, "neo.com", owner.PrivateKey().GetScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, guest, "setAdmin", nil, "neo.com", admin.PrivateKey().GetScriptHash()) // Must be witnessed by both owner and admin. testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, owner, "setAdmin", nil, "neo.com", admin.PrivateKey().GetScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "setAdmin", nil, "neo.com", admin.PrivateKey().GetScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, []*wallet.Account{owner, admin}, "setAdmin", stackitem.Null{}, "neo.com", admin.PrivateKey().GetScriptHash()) t.Run("set and delete by admin", func(t *testing.T) { testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeTXT), "sometext") testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, guest, "deleteRecord", nil, "neo.com", int64(native.RecordTypeTXT)) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "deleteRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeTXT)) }) t.Run("set admin to null", func(t *testing.T) { testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeTXT), "sometext") testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, owner, "setAdmin", stackitem.Null{}, "neo.com", nil) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "deleteRecord", nil, "neo.com", int64(native.RecordTypeTXT)) }) } func TestTransfer(t *testing.T) { bc := newTestChain(t) transferFundsToCommittee(t, bc) from := newAccountWithGAS(t, bc) to := newAccountWithGAS(t, bc) testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "com") testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "register", true, "neo.com", from.PrivateKey().GetScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA), "1.2.3.4") testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "transfer", nil, to.Contract.ScriptHash().BytesBE(), []byte("not.exists")) testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "transfer", false, to.Contract.ScriptHash().BytesBE(), []byte("neo.com")) testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "transfer", true, to.Contract.ScriptHash().BytesBE(), []byte("neo.com")) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "totalSupply", 1) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "ownerOf", to.Contract.ScriptHash().BytesBE(), []byte("neo.com")) cs, cs2 := getTestContractState(bc) // cs2 doesn't have OnNEP11Transfer require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs2)) testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, to, "transfer", nil, cs2.Hash.BytesBE(), []byte("neo.com")) testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, to, "transfer", true, cs.Hash.BytesBE(), []byte("neo.com")) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "totalSupply", 1) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "ownerOf", cs.Hash.BytesBE(), []byte("neo.com")) } func TestTokensOf(t *testing.T) { bc := newTestChain(t) transferFundsToCommittee(t, bc) acc1 := newAccountWithGAS(t, bc) acc2 := newAccountWithGAS(t, bc) testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "com") testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc1, "register", true, "neo.com", acc1.PrivateKey().GetScriptHash()) testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc2, "register", true, "nspcc.com", acc2.PrivateKey().GetScriptHash()) testTokensOf(t, bc, acc1, [][]byte{[]byte("neo.com")}, acc1.Contract.ScriptHash().BytesBE()) testTokensOf(t, bc, acc1, [][]byte{[]byte("nspcc.com")}, acc2.Contract.ScriptHash().BytesBE()) testTokensOf(t, bc, acc1, [][]byte{[]byte("neo.com"), []byte("nspcc.com")}) testTokensOf(t, bc, acc1, nil, util.Uint160{}.BytesBE()) } func testTokensOf(t *testing.T, bc *Blockchain, signer *wallet.Account, result [][]byte, args ...interface{}) { method := "tokensOf" if len(args) == 0 { method = "tokens" } w := io.NewBufBinWriter() emit.AppCall(w.BinWriter, bc.contracts.NameService.Hash, method, callflag.All, args...) for range result { emit.Opcodes(w.BinWriter, opcode.DUP) emit.Syscall(w.BinWriter, interopnames.SystemIteratorNext) emit.Opcodes(w.BinWriter, opcode.ASSERT) emit.Opcodes(w.BinWriter, opcode.DUP) emit.Syscall(w.BinWriter, interopnames.SystemIteratorValue) emit.Opcodes(w.BinWriter, opcode.SWAP) } emit.Opcodes(w.BinWriter, opcode.DROP) emit.Int(w.BinWriter, int64(len(result))) emit.Opcodes(w.BinWriter, opcode.PACK) require.NoError(t, w.Err) script := w.Bytes() tx := transaction.New(bc.GetConfig().Magic, script, defaultNameServiceSysfee) tx.ValidUntilBlock = bc.BlockHeight() + 1 signTxWithAccounts(bc, tx, signer) aers, err := persistBlock(bc, tx) require.NoError(t, err) if result == nil { checkFAULTState(t, aers[0]) return } arr := make([]stackitem.Item, 0, len(result)) for i := len(result) - 1; i >= 0; i-- { arr = append(arr, stackitem.Make(result[i])) } checkResult(t, aers[0], stackitem.NewArray(arr)) } func TestResolve(t *testing.T) { bc := newTestChain(t) transferFundsToCommittee(t, bc) acc := newAccountWithGAS(t, bc) testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "com") testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc, "register", true, "neo.com", acc.PrivateKey().GetScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA), "1.2.3.4") testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME), "alias.com") testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc, "register", true, "alias.com", acc.PrivateKey().GetScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, "alias.com", int64(native.RecordTypeTXT), "sometxt") testNameServiceInvoke(t, bc, "resolve", "1.2.3.4", "neo.com", int64(native.RecordTypeA)) testNameServiceInvoke(t, bc, "resolve", "alias.com", "neo.com", int64(native.RecordTypeCNAME)) testNameServiceInvoke(t, bc, "resolve", "sometxt", "neo.com", int64(native.RecordTypeTXT)) testNameServiceInvoke(t, bc, "resolve", stackitem.Null{}, "neo.com", int64(native.RecordTypeAAAA)) } const ( defaultNameServiceSysfee = 4000_0000 defaultRegisterSysfee = 10_0000_0000 + native.DefaultDomainPrice ) func testNameServiceInvoke(t *testing.T, bc *Blockchain, method string, result interface{}, args ...interface{}) { testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, true, method, result, args...) } func testNameServiceInvokeAux(t *testing.T, bc *Blockchain, sysfee int64, signer interface{}, method string, result interface{}, args ...interface{}) { if sysfee < 0 { sysfee = defaultNameServiceSysfee } aer, err := invokeContractMethodGeneric(bc, sysfee, bc.contracts.NameService.Hash, method, signer, args...) require.NoError(t, err) if result == nil { checkFAULTState(t, aer) return } checkResult(t, aer, stackitem.Make(result)) } func newAccountWithGAS(t *testing.T, bc *Blockchain) *wallet.Account { acc, err := wallet.NewAccount() require.NoError(t, err) transferTokenFromMultisigAccount(t, bc, acc.PrivateKey().GetScriptHash(), bc.contracts.GAS.Hash, 1000_00000000) return acc }