From 1f9fd4a472451e8673e1532ccc5199cdd7629c11 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Sat, 23 Oct 2021 15:36:26 +0300 Subject: [PATCH] neotest: add contract client wrapper Reduces amount of boilerplate code in tests. Signed-off-by: Evgeniy Stratonikov --- .../tests/nonnative_name_service_test.go | 415 ++++++++---------- pkg/neotest/basic.go | 4 +- pkg/neotest/client.go | 83 ++++ 3 files changed, 276 insertions(+), 226 deletions(-) create mode 100644 pkg/neotest/client.go diff --git a/examples/nft-nd-nns/tests/nonnative_name_service_test.go b/examples/nft-nd-nns/tests/nonnative_name_service_test.go index 1b23bb862..3a5d217cb 100644 --- a/examples/nft-nd-nns/tests/nonnative_name_service_test.go +++ b/examples/nft-nd-nns/tests/nonnative_name_service_test.go @@ -6,21 +6,16 @@ import ( nns "github.com/nspcc-dev/neo-go/examples/nft-nd-nns" "github.com/nspcc-dev/neo-go/pkg/compiler" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" - "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" "github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest/chain" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "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 newExecutorWithNS(t *testing.T) (*neotest.Executor, util.Uint160) { +func newNSClient(t *testing.T) *neotest.ContractInvoker { bc, acc := chain.NewSingle(t) e := neotest.NewExecutor(t, bc, acc) c := neotest.CompileFile(t, e.CommitteeHash, "..", "../nns.yml") @@ -29,85 +24,113 @@ func newExecutorWithNS(t *testing.T) (*neotest.Executor, util.Uint160) { h, err := e.Chain.GetContractScriptHash(1) require.NoError(t, err) require.Equal(t, c.Hash, h) - return e, c.Hash + return e.CommitteeInvoker(h) } -// -//func TestNameService_Price(t *testing.T) { -// e, nsHash := newExecutorWithNS(t) -// -// testGetSet(t, e.Chain, nsHash, "Price", -// defaultNameServiceDomainPrice, 0, 10000_00000000) -//} +func TestNameService_Price(t *testing.T) { + const ( + minPrice = int64(0) + maxPrice = int64(10000_00000000) + ) + + c := newNSClient(t) + + t.Run("set, not signed by committee", func(t *testing.T) { + acc := c.NewAccount(t) + cAcc := c.WithSigner(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") + }) + + t.Run("set, too small value", func(t *testing.T) { + c.InvokeFail(t, "The price is out of range.", "setPrice", minPrice-1) + }) + + t.Run("set, too large value", func(t *testing.T) { + c.InvokeFail(t, "The price is out of range.", "setPrice", maxPrice+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) + c.CheckHalt(t, txSet.Hash(), stackitem.Null{}) + c.CheckHalt(t, txGet.Hash(), stackitem.Make(defaultNameServiceDomainPrice+1)) + + // Get in the next block. + c.Invoke(t, stackitem.Make(defaultNameServiceDomainPrice+1), "getPrice") + }) +} func TestNonfungible(t *testing.T) { - e, nsHash := newExecutorWithNS(t) + c := newNSClient(t) - acc := e.NewAccount(t) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "symbol", "NNS") - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "decimals", 0) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "totalSupply", 0) + c.Signer = c.NewAccount(t) + c.Invoke(t, "NNS", "symbol") + c.Invoke(t, 0, "decimals") + c.Invoke(t, 0, "totalSupply") } func TestAddRoot(t *testing.T) { - e, nsHash := newExecutorWithNS(t) + c := newNSClient(t) t.Run("invalid format", func(t *testing.T) { - testNameServiceInvoke(t, e, nsHash, "addRoot", nil, "") + c.InvokeFail(t, "invalid root format", "addRoot", "") }) t.Run("not signed by committee", func(t *testing.T) { - acc := e.NewAccount(t) - tx := e.PrepareInvoke(t, acc, nsHash, "addRoot", "some") - e.AddBlock(t, tx) - e.CheckFault(t, tx.Hash(), "not witnessed by committee") + acc := c.NewAccount(t) + c := c.WithSigner(acc) + c.InvokeFail(t, "not witnessed by committee", "addRoot", "some") }) - testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "some") + c.Invoke(t, stackitem.Null{}, "addRoot", "some") t.Run("already exists", func(t *testing.T) { - testNameServiceInvoke(t, e, nsHash, "addRoot", nil, "some") + c.InvokeFail(t, "already exists", "addRoot", "some") }) } func TestExpiration(t *testing.T) { - e, nsHash := newExecutorWithNS(t) + c := newNSClient(t) + e := c.Executor bc := e.Chain acc := e.NewAccount(t) + cAcc := c.WithSigner(acc) - testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, acc, "register", - true, "first.com", acc.Contract.ScriptHash()) - - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, - "setRecord", stackitem.Null{}, "first.com", int64(nns.TXT), "sometext") + c.Invoke(t, stackitem.Null{}, "addRoot", "com") + cAcc.Invoke(t, true, "register", "first.com", acc.Contract.ScriptHash()) + cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext") b1 := e.TopBlock(t) - tx := e.PrepareInvoke(t, acc, nsHash, "register", "second.com", acc.Contract.ScriptHash()) - b2 := e.NewBlock(t, tx) + tx := cAcc.PrepareInvoke(t, "register", "second.com", acc.Contract.ScriptHash()) + b2 := e.NewUnsignedBlock(t, tx) b2.Index = b1.Index + 1 b2.PrevHash = b1.Hash() b2.Timestamp = b1.Timestamp + 10000 require.NoError(t, bc.AddBlock(e.SignBlock(b2))) e.CheckHalt(t, tx.Hash()) - tx = e.PrepareInvoke(t, acc, nsHash, "isAvailable", "first.com") - b3 := e.NewBlock(t, tx) + tx = cAcc.PrepareInvoke(t, "isAvailable", "first.com") + b3 := e.NewUnsignedBlock(t, tx) b3.Index = b2.Index + 1 b3.PrevHash = b2.Hash() b3.Timestamp = b1.Timestamp + (millisecondsInYear + 1) require.NoError(t, bc.AddBlock(e.SignBlock(b3))) e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true)) - tx = e.PrepareInvoke(t, acc, nsHash, "isAvailable", "second.com") - b4 := e.NewBlock(t, tx) + tx = cAcc.PrepareInvoke(t, "isAvailable", "second.com") + b4 := e.NewUnsignedBlock(t, tx) b4.Index = b3.Index + 1 b4.PrevHash = b3.Hash() b4.Timestamp = b3.Timestamp + 1000 require.NoError(t, bc.AddBlock(e.SignBlock(b4))) e.CheckHalt(t, tx.Hash(), stackitem.NewBool(false)) - tx = e.PrepareInvoke(t, acc, nsHash, "getRecord", "first.com", int64(nns.TXT)) - b5 := e.NewBlock(t, tx) + tx = cAcc.PrepareInvoke(t, "getRecord", "first.com", int64(nns.TXT)) + b5 := e.NewUnsignedBlock(t, tx) b5.Index = b4.Index + 1 b5.PrevHash = b4.Hash() b5.Timestamp = b4.Timestamp + 1000 @@ -118,90 +141,87 @@ func TestExpiration(t *testing.T) { const millisecondsInYear = 365 * 24 * 3600 * 1000 func TestRegisterAndRenew(t *testing.T) { - e, nsHash := newExecutorWithNS(t) + c := newNSClient(t) + e := c.Executor - testNameServiceInvoke(t, e, nsHash, "isAvailable", nil, "neo.com") - testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "org") - testNameServiceInvoke(t, e, nsHash, "isAvailable", nil, "neo.com") - testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") - testNameServiceInvoke(t, e, nsHash, "isAvailable", true, "neo.com") - testNameServiceInvoke(t, e, nsHash, "register", nil, "neo.org", e.CommitteeHash) - testNameServiceInvoke(t, e, nsHash, "register", nil, "docs.neo.org", e.CommitteeHash) - testNameServiceInvoke(t, e, nsHash, "register", nil, "\nneo.com'", e.CommitteeHash) - testNameServiceInvoke(t, e, nsHash, "register", nil, "neo.com\n", e.CommitteeHash) - testNameServiceInvoke(t, e, nsHash, "register", nil, "neo.com", e.CommitteeHash) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceDomainPrice, e.Committee, "register", - nil, "neo.com", e.CommitteeHash) + c.InvokeFail(t, "root not found", "isAvailable", "neo.com") + c.Invoke(t, stackitem.Null{}, "addRoot", "org") + c.InvokeFail(t, "root not found", "isAvailable", "neo.com") + c.Invoke(t, stackitem.Null{}, "addRoot", "com") + c.Invoke(t, true, "isAvailable", "neo.com") + c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash) + c.InvokeFail(t, "invalid domain name format", "register", "docs.neo.org", e.CommitteeHash) + c.InvokeFail(t, "invalid domain name format", "register", "\nneo.com'", e.CommitteeHash) + c.InvokeFail(t, "invalid domain name format", "register", "neo.com\n", e.CommitteeHash) + c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash) + c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceDomainPrice, "register", "neo.com", e.CommitteeHash) - testNameServiceInvoke(t, e, nsHash, "isAvailable", true, "neo.com") - testNameServiceInvoke(t, e, nsHash, "balanceOf", 0, e.CommitteeHash) - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, e.Committee, "register", - true, "neo.com", e.CommitteeHash) + c.Invoke(t, true, "isAvailable", "neo.com") + c.Invoke(t, 0, "balanceOf", e.CommitteeHash) + c.Invoke(t, true, "register", "neo.com", e.CommitteeHash) topBlock := e.TopBlock(t) expectedExpiration := topBlock.Timestamp + millisecondsInYear - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, e.Committee, "register", - false, "neo.com", e.CommitteeHash) - testNameServiceInvoke(t, e, nsHash, "isAvailable", false, "neo.com") + c.Invoke(t, false, "register", "neo.com", e.CommitteeHash) + c.Invoke(t, false, "isAvailable", "neo.com") props := stackitem.NewMap() props.Add(stackitem.Make("name"), stackitem.Make("neo.com")) props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration)) - testNameServiceInvoke(t, e, nsHash, "properties", props, "neo.com") - testNameServiceInvoke(t, e, nsHash, "balanceOf", 1, e.CommitteeHash) - testNameServiceInvoke(t, e, nsHash, "ownerOf", e.CommitteeHash.BytesBE(), []byte("neo.com")) + c.Invoke(t, props, "properties", "neo.com") + c.Invoke(t, 1, "balanceOf", e.CommitteeHash) + c.Invoke(t, e.CommitteeHash.BytesBE(), "ownerOf", []byte("neo.com")) t.Run("invalid token ID", func(t *testing.T) { - testNameServiceInvoke(t, e, nsHash, "properties", nil, "not.exists") - testNameServiceInvoke(t, e, nsHash, "ownerOf", nil, "not.exists") - testNameServiceInvoke(t, e, nsHash, "properties", nil, []interface{}{}) - testNameServiceInvoke(t, e, nsHash, "ownerOf", nil, []interface{}{}) + c.InvokeFail(t, "token not found", "properties", "not.exists") + c.InvokeFail(t, "token not found", "ownerOf", "not.exists") + c.InvokeFail(t, "invalid conversion", "properties", []interface{}{}) + c.InvokeFail(t, "invalid conversion", "ownerOf", []interface{}{}) }) // Renew expectedExpiration += millisecondsInYear - testNameServiceInvokeAux(t, e, nsHash, 100_0000_0000, e.Committee, "renew", expectedExpiration, "neo.com") + c.Invoke(t, expectedExpiration, "renew", "neo.com") props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration)) - testNameServiceInvoke(t, e, nsHash, "properties", props, "neo.com") + c.Invoke(t, props, "properties", "neo.com") } func TestSetGetRecord(t *testing.T) { - e, nsHash := newExecutorWithNS(t) + c := newNSClient(t) + e := c.Executor acc := e.NewAccount(t) - testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") + cAcc := c.WithSigner(acc) + c.Invoke(t, stackitem.Null{}, "addRoot", "com") t.Run("set before register", func(t *testing.T) { - testNameServiceInvoke(t, e, nsHash, "setRecord", nil, "neo.com", int64(nns.TXT), "sometext") + c.InvokeFail(t, "token not found", "setRecord", "neo.com", int64(nns.TXT), "sometext") }) - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, e.Committee, "register", - true, "neo.com", e.CommitteeHash) + c.Invoke(t, true, "register", "neo.com", e.CommitteeHash) t.Run("invalid parameters", func(t *testing.T) { - testNameServiceInvoke(t, e, nsHash, "setRecord", nil, "neo.com", int64(0xFF), "1.2.3.4") - testNameServiceInvoke(t, e, nsHash, "setRecord", nil, "neo.com", int64(nns.A), "not.an.ip.address") + c.InvokeFail(t, "unsupported record type", "setRecord", "neo.com", int64(0xFF), "1.2.3.4") + c.InvokeFail(t, "invalid record", "setRecord", "neo.com", int64(nns.A), "not.an.ip.address") }) t.Run("invalid witness", func(t *testing.T) { - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "setRecord", nil, - "neo.com", int64(nns.A), "1.2.3.4") + cAcc.InvokeFail(t, "not witnessed by admin", "setRecord", "neo.com", int64(nns.A), "1.2.3.4") }) - testNameServiceInvoke(t, e, nsHash, "getRecord", stackitem.Null{}, "neo.com", int64(nns.A)) - testNameServiceInvoke(t, e, nsHash, "setRecord", stackitem.Null{}, "neo.com", int64(nns.A), "1.2.3.4") - testNameServiceInvoke(t, e, nsHash, "getRecord", "1.2.3.4", "neo.com", int64(nns.A)) - testNameServiceInvoke(t, e, nsHash, "setRecord", stackitem.Null{}, "neo.com", int64(nns.A), "1.2.3.4") - testNameServiceInvoke(t, e, nsHash, "getRecord", "1.2.3.4", "neo.com", int64(nns.A)) - testNameServiceInvoke(t, e, nsHash, "setRecord", stackitem.Null{}, "neo.com", int64(nns.AAAA), "2001:0201:1f1f:0000:0000:0100:11a0:11df") - testNameServiceInvoke(t, e, nsHash, "setRecord", stackitem.Null{}, "neo.com", int64(nns.CNAME), "nspcc.ru") - testNameServiceInvoke(t, e, nsHash, "setRecord", stackitem.Null{}, "neo.com", int64(nns.TXT), "sometext") + c.Invoke(t, stackitem.Null{}, "getRecord", "neo.com", int64(nns.A)) + c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") + c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A)) + c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") + c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A)) + c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.AAAA), "2001:0201:1f1f:0000:0000:0100:11a0:11df") + c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "nspcc.ru") + c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext") // Delete record. t.Run("invalid witness", func(t *testing.T) { - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "setRecord", nil, - "neo.com", int64(nns.CNAME)) + cAcc.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.CNAME)) }) - testNameServiceInvoke(t, e, nsHash, "getRecord", "nspcc.ru", "neo.com", int64(nns.CNAME)) - testNameServiceInvoke(t, e, nsHash, "deleteRecord", stackitem.Null{}, "neo.com", int64(nns.CNAME)) - testNameServiceInvoke(t, e, nsHash, "getRecord", stackitem.Null{}, "neo.com", int64(nns.CNAME)) - testNameServiceInvoke(t, e, nsHash, "getRecord", "1.2.3.4", "neo.com", int64(nns.A)) + c.Invoke(t, "nspcc.ru", "getRecord", "neo.com", int64(nns.CNAME)) + c.Invoke(t, stackitem.Null{}, "deleteRecord", "neo.com", int64(nns.CNAME)) + c.Invoke(t, stackitem.Null{}, "getRecord", "neo.com", int64(nns.CNAME)) + c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A)) t.Run("SetRecord_compatibility", func(t *testing.T) { // tests are got from the NNS C# implementation and changed accordingly to non-native implementation behaviour @@ -258,206 +278,153 @@ func TestSetGetRecord(t *testing.T) { {Type: nns.AAAA, Name: "2001::13.1.68.3", ShouldFail: true}, } for _, testCase := range testCases { - var expected interface{} - if testCase.ShouldFail { - expected = nil - } else { - expected = stackitem.Null{} - } + args := []interface{}{"neo.com", int64(testCase.Type), testCase.Name} t.Run(testCase.Name, func(t *testing.T) { - testNameServiceInvoke(t, e, nsHash, "setRecord", expected, "neo.com", int64(testCase.Type), testCase.Name) + if testCase.ShouldFail { + c.InvokeFail(t, "", "setRecord", args...) + } else { + c.Invoke(t, stackitem.Null{}, "setRecord", args...) + } }) } }) } func TestSetAdmin(t *testing.T) { - e, nsHash := newExecutorWithNS(t) + c := newNSClient(t) + e := c.Executor owner := e.NewAccount(t) + cOwner := c.WithSigner(owner) admin := e.NewAccount(t) + cAdmin := c.WithSigner(admin) guest := e.NewAccount(t) - testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") + cGuest := c.WithSigner(guest) - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, owner, "register", true, - "neo.com", owner.PrivateKey().GetScriptHash()) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, guest, "setAdmin", nil, - "neo.com", admin.PrivateKey().GetScriptHash()) + c.Invoke(t, stackitem.Null{}, "addRoot", "com") + + cOwner.Invoke(t, true, "register", "neo.com", owner.PrivateKey().GetScriptHash()) + cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash()) // Must be witnessed by both owner and admin. - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, owner, "setAdmin", nil, - "neo.com", admin.PrivateKey().GetScriptHash()) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, admin, "setAdmin", nil, - "neo.com", admin.PrivateKey().GetScriptHash()) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, []*wallet.Account{owner, admin}, - "setAdmin", stackitem.Null{}, - "neo.com", admin.PrivateKey().GetScriptHash()) + cOwner.InvokeFail(t, "not witnessed by admin", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash()) + cAdmin.InvokeFail(t, "not witnessed by owner", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash()) + cc := c.WithSigner([]*wallet.Account{owner, admin}) + cc.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash()) t.Run("set and delete by admin", func(t *testing.T) { - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{}, - "neo.com", int64(nns.TXT), "sometext") - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, guest, "deleteRecord", nil, - "neo.com", int64(nns.TXT)) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, admin, "deleteRecord", stackitem.Null{}, - "neo.com", int64(nns.TXT)) + cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext") + cGuest.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.TXT)) + cAdmin.Invoke(t, stackitem.Null{}, "deleteRecord", "neo.com", int64(nns.TXT)) }) t.Run("set admin to null", func(t *testing.T) { - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{}, - "neo.com", int64(nns.TXT), "sometext") - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, owner, "setAdmin", stackitem.Null{}, - "neo.com", nil) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, admin, "deleteRecord", nil, - "neo.com", int64(nns.TXT)) + cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext") + cOwner.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", nil) + cAdmin.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.TXT)) }) } func TestTransfer(t *testing.T) { - e, nsHash := newExecutorWithNS(t) + c := newNSClient(t) + e := c.Executor from := e.NewAccount(t) + cFrom := c.WithSigner(from) to := e.NewAccount(t) + cTo := c.WithSigner(to) - testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, from, "register", - true, "neo.com", from.PrivateKey().GetScriptHash()) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, from, "setRecord", stackitem.Null{}, - "neo.com", int64(nns.A), "1.2.3.4") - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, from, "transfer", - nil, to.Contract.ScriptHash().BytesBE(), []byte("not.exists"), nil) - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, e.Committee, "transfer", - false, to.Contract.ScriptHash().BytesBE(), []byte("neo.com"), nil) - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, from, "transfer", - true, to.Contract.ScriptHash().BytesBE(), []byte("neo.com"), nil) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, from, "totalSupply", 1) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, from, "ownerOf", - to.Contract.ScriptHash().BytesBE(), []byte("neo.com")) + c.Invoke(t, stackitem.Null{}, "addRoot", "com") + cFrom.Invoke(t, true, "register", "neo.com", from.PrivateKey().GetScriptHash()) + cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") + cFrom.InvokeFail(t, "token not found", "transfer", to.Contract.ScriptHash(), "not.exists", nil) + c.Invoke(t, false, "transfer", to.Contract.ScriptHash(), "neo.com", nil) + cFrom.Invoke(t, true, "transfer", to.Contract.ScriptHash(), "neo.com", nil) + cFrom.Invoke(t, 1, "totalSupply") + cFrom.Invoke(t, to.Contract.ScriptHash().BytesBE(), "ownerOf", "neo.com") // without onNEP11Transfer - c := neotest.CompileSource(t, e.CommitteeHash, + ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(`package foo func Main() int { return 0 }`), &compiler.Options{Name: "foo"}) - e.DeployContract(t, c, nil) - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, to, "transfer", - nil, c.Hash.BytesBE(), []byte("neo.com"), nil) + e.DeployContract(t, ctr, nil) + cTo.InvokeFail(t, "method not found", "transfer", ctr.Hash, []byte("neo.com"), nil) // with onNEP11Transfer - c = neotest.CompileSource(t, e.CommitteeHash, + ctr = neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(`package foo import "github.com/nspcc-dev/neo-go/pkg/interop" func OnNEP11Payment(from interop.Hash160, amount int, token []byte, data interface{}) {}`), &compiler.Options{Name: "foo"}) - e.DeployContract(t, c, nil) - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, to, "transfer", - true, c.Hash.BytesBE(), []byte("neo.com"), nil) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, from, "totalSupply", 1) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, from, "ownerOf", - c.Hash.BytesBE(), []byte("neo.com")) + e.DeployContract(t, ctr, nil) + cTo.Invoke(t, true, "transfer", ctr.Hash, []byte("neo.com"), nil) + cFrom.Invoke(t, 1, "totalSupply") + cFrom.Invoke(t, ctr.Hash.BytesBE(), "ownerOf", []byte("neo.com")) } func TestTokensOf(t *testing.T) { - e, nsHash := newExecutorWithNS(t) + c := newNSClient(t) + e := c.Executor acc1 := e.NewAccount(t) + cAcc1 := c.WithSigner(acc1) acc2 := e.NewAccount(t) + cAcc2 := c.WithSigner(acc2) - testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, acc1, "register", - true, "neo.com", acc1.PrivateKey().GetScriptHash()) - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, acc2, "register", - true, "nspcc.com", acc2.PrivateKey().GetScriptHash()) + c.Invoke(t, stackitem.Null{}, "addRoot", "com") + cAcc1.Invoke(t, true, "register", "neo.com", acc1.PrivateKey().GetScriptHash()) + cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.PrivateKey().GetScriptHash()) - testTokensOf(t, e, nsHash, [][]byte{[]byte("neo.com")}, acc1.Contract.ScriptHash().BytesBE()) - testTokensOf(t, e, nsHash, [][]byte{[]byte("nspcc.com")}, acc2.Contract.ScriptHash().BytesBE()) - testTokensOf(t, e, nsHash, [][]byte{[]byte("neo.com"), []byte("nspcc.com")}) - testTokensOf(t, e, nsHash, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still + testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.Contract.ScriptHash().BytesBE()) + testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.Contract.ScriptHash().BytesBE()) + testTokensOf(t, c, [][]byte{[]byte("neo.com"), []byte("nspcc.com")}) + testTokensOf(t, c, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still } -func testTokensOf(t *testing.T, e *neotest.Executor, nsHash util.Uint160, result [][]byte, args ...interface{}) { +func testTokensOf(t *testing.T, c *neotest.ContractInvoker, result [][]byte, args ...interface{}) { method := "tokensOf" if len(args) == 0 { method = "tokens" } - w := io.NewBufBinWriter() - emit.AppCall(w.BinWriter, nsHash, 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(script, defaultNameServiceSysfee) - tx.ValidUntilBlock = e.Chain.BlockHeight() + 1 - v, err := neotest.TestInvoke(e.Chain, tx) + s, err := c.TestInvoke(t, method, args...) if result == nil { require.Error(t, err) return } require.NoError(t, err) + iter := s.Pop().Interop().Value().(*storage.Iterator) arr := make([]stackitem.Item, 0, len(result)) - for i := len(result) - 1; i >= 0; i-- { + for i := range result { + require.True(t, iter.Next()) + require.Equal(t, result[i], iter.Value().Value()) arr = append(arr, stackitem.Make(result[i])) } - require.Equal(t, stackitem.NewArray(arr), v.Estack().Pop().Item()) + require.False(t, iter.Next()) } func TestResolve(t *testing.T) { - e, nsHash := newExecutorWithNS(t) + c := newNSClient(t) + e := c.Executor acc := e.NewAccount(t) + cAcc := c.WithSigner(acc) - testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, acc, "register", - true, "neo.com", acc.PrivateKey().GetScriptHash()) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, - "neo.com", int64(nns.A), "1.2.3.4") - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, - "neo.com", int64(nns.CNAME), "alias.com") + c.Invoke(t, stackitem.Null{}, "addRoot", "com") + cAcc.Invoke(t, true, "register", "neo.com", acc.PrivateKey().GetScriptHash()) + cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") + cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com") - testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, acc, "register", - true, "alias.com", acc.PrivateKey().GetScriptHash()) - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, - "alias.com", int64(nns.TXT), "sometxt") + cAcc.Invoke(t, true, "register", "alias.com", acc.PrivateKey().GetScriptHash()) + cAcc.Invoke(t, stackitem.Null{}, "setRecord", "alias.com", int64(nns.TXT), "sometxt") - testNameServiceInvoke(t, e, nsHash, "resolve", "1.2.3.4", - "neo.com", int64(nns.A)) - testNameServiceInvoke(t, e, nsHash, "resolve", "alias.com", - "neo.com", int64(nns.CNAME)) - testNameServiceInvoke(t, e, nsHash, "resolve", "sometxt", - "neo.com", int64(nns.TXT)) - testNameServiceInvoke(t, e, nsHash, "resolve", stackitem.Null{}, - "neo.com", int64(nns.AAAA)) + c.Invoke(t, "1.2.3.4", "resolve", "neo.com", int64(nns.A)) + c.Invoke(t, "alias.com", "resolve", "neo.com", int64(nns.CNAME)) + c.Invoke(t, "sometxt", "resolve", "neo.com", int64(nns.TXT)) + c.Invoke(t, stackitem.Null{}, "resolve", "neo.com", int64(nns.AAAA)) } const ( defaultNameServiceDomainPrice = 10_0000_0000 defaultNameServiceSysfee = 6000_0000 - defaultRegisterSysfee = 10_0000_0000 + defaultNameServiceDomainPrice ) - -func testNameServiceInvoke(t *testing.T, e *neotest.Executor, nsHash util.Uint160, method string, result interface{}, args ...interface{}) { - testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, e.Committee, method, result, args...) -} - -func testNameServiceInvokeAux(t *testing.T, e *neotest.Executor, nsHash util.Uint160, sysfee int64, signer interface{}, method string, result interface{}, args ...interface{}) { - if sysfee < 0 { - sysfee = defaultNameServiceSysfee - } - tx := e.PrepareInvokeNoSign(t, nsHash, method, args...) - e.SignTx(t, tx, sysfee, signer) - e.AddBlock(t, tx) - if result == nil { - e.CheckFault(t, tx.Hash(), "") - } else { - e.CheckHalt(t, tx.Hash(), stackitem.Make(result)) - } -} diff --git a/pkg/neotest/basic.go b/pkg/neotest/basic.go index b374e0d53..0e6e9ac16 100644 --- a/pkg/neotest/basic.go +++ b/pkg/neotest/basic.go @@ -238,8 +238,8 @@ func (e *Executor) SignBlock(b *block.Block) *block.Block { return b } -// AddBlockCheckHalt is a convenient wrapper over AddNewBlock and CheckHalt. -func (e *Executor) AddBlockCheckHalt(t *testing.T, bc blockchainer.Blockchainer, txs ...*transaction.Transaction) *block.Block { +// AddBlockCheckHalt is a convenient wrapper over AddBlock and CheckHalt. +func (e *Executor) AddBlockCheckHalt(t *testing.T, txs ...*transaction.Transaction) *block.Block { b := e.AddNewBlock(t, txs...) for _, tx := range txs { e.CheckHalt(t, tx.Hash()) diff --git a/pkg/neotest/client.go b/pkg/neotest/client.go new file mode 100644 index 000000000..e767ed58b --- /dev/null +++ b/pkg/neotest/client.go @@ -0,0 +1,83 @@ +package neotest + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "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" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// ContractInvoker is a client for specific contract. +type ContractInvoker struct { + *Executor + Hash util.Uint160 + Signer interface{} +} + +// CommitteeInvoker creates new ContractInvoker for contract with hash h. +func (e *Executor) CommitteeInvoker(h util.Uint160) *ContractInvoker { + return &ContractInvoker{ + Executor: e, + Hash: h, + Signer: e.Committee, + } +} + +// TestInvoke creates test VM and invokes method with args. +func (c *ContractInvoker) TestInvoke(t *testing.T, method string, args ...interface{}) (*vm.Stack, error) { + tx := c.PrepareInvokeNoSign(t, method, args...) + b := c.NewUnsignedBlock(t, tx) + v, f := c.Chain.GetTestVM(trigger.Application, tx, b) + t.Cleanup(f) + + v.LoadWithFlags(tx.Script, callflag.All) + err := v.Run() + return v.Estack(), err +} + +// WithSigner creates new client with the provided signer. +func (c *ContractInvoker) WithSigner(signer interface{}) *ContractInvoker { + newC := *c + newC.Signer = signer + return &newC +} + +// PrepareInvoke creates new invocation transaction. +func (c *ContractInvoker) PrepareInvoke(t *testing.T, method string, args ...interface{}) *transaction.Transaction { + return c.Executor.NewTx(t, c.Signer, c.Hash, method, args...) +} + +// PrepareInvokeNoSign creates new unsigned invocation transaction. +func (c *ContractInvoker) PrepareInvokeNoSign(t *testing.T, method string, args ...interface{}) *transaction.Transaction { + return c.Executor.NewUnsignedTx(t, c.Hash, method, args...) +} + +// Invoke invokes method with args, persists transaction and checks the result. +// Returns transaction hash. +func (c *ContractInvoker) Invoke(t *testing.T, result interface{}, method string, args ...interface{}) util.Uint256 { + tx := c.PrepareInvoke(t, method, args...) + c.AddNewBlock(t, tx) + c.CheckHalt(t, tx.Hash(), stackitem.Make(result)) + return tx.Hash() +} + +// InvokeWithFeeFail is like InvokeFail but sets custom system fee for the transaction. +func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee int64, method string, args ...interface{}) util.Uint256 { + tx := c.PrepareInvokeNoSign(t, method, args...) + c.Executor.SignTx(t, tx, sysFee, c.Signer) + c.AddNewBlock(t, tx) + c.CheckFault(t, tx.Hash(), message) + return tx.Hash() +} + +// InvokeFail invokes method with args, persists transaction and checks the error message. +// Returns transaction hash. +func (c *ContractInvoker) InvokeFail(t *testing.T, message string, method string, args ...interface{}) { + tx := c.PrepareInvoke(t, method, args...) + c.AddNewBlock(t, tx) + c.CheckFault(t, tx.Hash(), message) +}