neotest: add contract client wrapper

Reduces amount of boilerplate code in tests.

Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgeniy Stratonikov 2021-10-23 15:36:26 +03:00
parent e3625152c6
commit 1f9fd4a472
3 changed files with 276 additions and 226 deletions

View file

@ -6,21 +6,16 @@ import (
nns "github.com/nspcc-dev/neo-go/examples/nft-nd-nns" 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/compiler"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain" "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/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/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require" "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) bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc) e := neotest.NewExecutor(t, bc, acc)
c := neotest.CompileFile(t, e.CommitteeHash, "..", "../nns.yml") 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) h, err := e.Chain.GetContractScriptHash(1)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, c.Hash, h) require.Equal(t, c.Hash, h)
return e, c.Hash return e.CommitteeInvoker(h)
} }
// func TestNameService_Price(t *testing.T) {
//func TestNameService_Price(t *testing.T) { const (
// e, nsHash := newExecutorWithNS(t) minPrice = int64(0)
// maxPrice = int64(10000_00000000)
// testGetSet(t, e.Chain, nsHash, "Price", )
// defaultNameServiceDomainPrice, 0, 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) { func TestNonfungible(t *testing.T) {
e, nsHash := newExecutorWithNS(t) c := newNSClient(t)
acc := e.NewAccount(t) c.Signer = c.NewAccount(t)
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "symbol", "NNS") c.Invoke(t, "NNS", "symbol")
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "decimals", 0) c.Invoke(t, 0, "decimals")
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "totalSupply", 0) c.Invoke(t, 0, "totalSupply")
} }
func TestAddRoot(t *testing.T) { func TestAddRoot(t *testing.T) {
e, nsHash := newExecutorWithNS(t) c := newNSClient(t)
t.Run("invalid format", func(t *testing.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) { t.Run("not signed by committee", func(t *testing.T) {
acc := e.NewAccount(t) acc := c.NewAccount(t)
tx := e.PrepareInvoke(t, acc, nsHash, "addRoot", "some") c := c.WithSigner(acc)
e.AddBlock(t, tx) c.InvokeFail(t, "not witnessed by committee", "addRoot", "some")
e.CheckFault(t, tx.Hash(), "not witnessed by committee")
}) })
testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "some") c.Invoke(t, stackitem.Null{}, "addRoot", "some")
t.Run("already exists", func(t *testing.T) { 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) { func TestExpiration(t *testing.T) {
e, nsHash := newExecutorWithNS(t) c := newNSClient(t)
e := c.Executor
bc := e.Chain bc := e.Chain
acc := e.NewAccount(t) acc := e.NewAccount(t)
cAcc := c.WithSigner(acc)
testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") c.Invoke(t, stackitem.Null{}, "addRoot", "com")
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, acc, "register", cAcc.Invoke(t, true, "register", "first.com", acc.Contract.ScriptHash())
true, "first.com", acc.Contract.ScriptHash()) cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext")
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc,
"setRecord", stackitem.Null{}, "first.com", int64(nns.TXT), "sometext")
b1 := e.TopBlock(t) b1 := e.TopBlock(t)
tx := e.PrepareInvoke(t, acc, nsHash, "register", "second.com", acc.Contract.ScriptHash()) tx := cAcc.PrepareInvoke(t, "register", "second.com", acc.Contract.ScriptHash())
b2 := e.NewBlock(t, tx) b2 := e.NewUnsignedBlock(t, tx)
b2.Index = b1.Index + 1 b2.Index = b1.Index + 1
b2.PrevHash = b1.Hash() b2.PrevHash = b1.Hash()
b2.Timestamp = b1.Timestamp + 10000 b2.Timestamp = b1.Timestamp + 10000
require.NoError(t, bc.AddBlock(e.SignBlock(b2))) require.NoError(t, bc.AddBlock(e.SignBlock(b2)))
e.CheckHalt(t, tx.Hash()) e.CheckHalt(t, tx.Hash())
tx = e.PrepareInvoke(t, acc, nsHash, "isAvailable", "first.com") tx = cAcc.PrepareInvoke(t, "isAvailable", "first.com")
b3 := e.NewBlock(t, tx) b3 := e.NewUnsignedBlock(t, tx)
b3.Index = b2.Index + 1 b3.Index = b2.Index + 1
b3.PrevHash = b2.Hash() b3.PrevHash = b2.Hash()
b3.Timestamp = b1.Timestamp + (millisecondsInYear + 1) b3.Timestamp = b1.Timestamp + (millisecondsInYear + 1)
require.NoError(t, bc.AddBlock(e.SignBlock(b3))) require.NoError(t, bc.AddBlock(e.SignBlock(b3)))
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true)) e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true))
tx = e.PrepareInvoke(t, acc, nsHash, "isAvailable", "second.com") tx = cAcc.PrepareInvoke(t, "isAvailable", "second.com")
b4 := e.NewBlock(t, tx) b4 := e.NewUnsignedBlock(t, tx)
b4.Index = b3.Index + 1 b4.Index = b3.Index + 1
b4.PrevHash = b3.Hash() b4.PrevHash = b3.Hash()
b4.Timestamp = b3.Timestamp + 1000 b4.Timestamp = b3.Timestamp + 1000
require.NoError(t, bc.AddBlock(e.SignBlock(b4))) require.NoError(t, bc.AddBlock(e.SignBlock(b4)))
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(false)) e.CheckHalt(t, tx.Hash(), stackitem.NewBool(false))
tx = e.PrepareInvoke(t, acc, nsHash, "getRecord", "first.com", int64(nns.TXT)) tx = cAcc.PrepareInvoke(t, "getRecord", "first.com", int64(nns.TXT))
b5 := e.NewBlock(t, tx) b5 := e.NewUnsignedBlock(t, tx)
b5.Index = b4.Index + 1 b5.Index = b4.Index + 1
b5.PrevHash = b4.Hash() b5.PrevHash = b4.Hash()
b5.Timestamp = b4.Timestamp + 1000 b5.Timestamp = b4.Timestamp + 1000
@ -118,90 +141,87 @@ func TestExpiration(t *testing.T) {
const millisecondsInYear = 365 * 24 * 3600 * 1000 const millisecondsInYear = 365 * 24 * 3600 * 1000
func TestRegisterAndRenew(t *testing.T) { func TestRegisterAndRenew(t *testing.T) {
e, nsHash := newExecutorWithNS(t) c := newNSClient(t)
e := c.Executor
testNameServiceInvoke(t, e, nsHash, "isAvailable", nil, "neo.com") c.InvokeFail(t, "root not found", "isAvailable", "neo.com")
testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "org") c.Invoke(t, stackitem.Null{}, "addRoot", "org")
testNameServiceInvoke(t, e, nsHash, "isAvailable", nil, "neo.com") c.InvokeFail(t, "root not found", "isAvailable", "neo.com")
testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") c.Invoke(t, stackitem.Null{}, "addRoot", "com")
testNameServiceInvoke(t, e, nsHash, "isAvailable", true, "neo.com") c.Invoke(t, true, "isAvailable", "neo.com")
testNameServiceInvoke(t, e, nsHash, "register", nil, "neo.org", e.CommitteeHash) c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash)
testNameServiceInvoke(t, e, nsHash, "register", nil, "docs.neo.org", e.CommitteeHash) c.InvokeFail(t, "invalid domain name format", "register", "docs.neo.org", e.CommitteeHash)
testNameServiceInvoke(t, e, nsHash, "register", nil, "\nneo.com'", e.CommitteeHash) c.InvokeFail(t, "invalid domain name format", "register", "\nneo.com'", e.CommitteeHash)
testNameServiceInvoke(t, e, nsHash, "register", nil, "neo.com\n", e.CommitteeHash) c.InvokeFail(t, "invalid domain name format", "register", "neo.com\n", e.CommitteeHash)
testNameServiceInvoke(t, e, nsHash, "register", nil, "neo.com", e.CommitteeHash) c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceSysfee, "register", "neo.org", e.CommitteeHash)
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceDomainPrice, e.Committee, "register", c.InvokeWithFeeFail(t, "GAS limit exceeded", defaultNameServiceDomainPrice, "register", "neo.com", e.CommitteeHash)
nil, "neo.com", e.CommitteeHash)
testNameServiceInvoke(t, e, nsHash, "isAvailable", true, "neo.com") c.Invoke(t, true, "isAvailable", "neo.com")
testNameServiceInvoke(t, e, nsHash, "balanceOf", 0, e.CommitteeHash) c.Invoke(t, 0, "balanceOf", e.CommitteeHash)
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, e.Committee, "register", c.Invoke(t, true, "register", "neo.com", e.CommitteeHash)
true, "neo.com", e.CommitteeHash)
topBlock := e.TopBlock(t) topBlock := e.TopBlock(t)
expectedExpiration := topBlock.Timestamp + millisecondsInYear expectedExpiration := topBlock.Timestamp + millisecondsInYear
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, e.Committee, "register", c.Invoke(t, false, "register", "neo.com", e.CommitteeHash)
false, "neo.com", e.CommitteeHash) c.Invoke(t, false, "isAvailable", "neo.com")
testNameServiceInvoke(t, e, nsHash, "isAvailable", false, "neo.com")
props := stackitem.NewMap() props := stackitem.NewMap()
props.Add(stackitem.Make("name"), stackitem.Make("neo.com")) props.Add(stackitem.Make("name"), stackitem.Make("neo.com"))
props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration)) props.Add(stackitem.Make("expiration"), stackitem.Make(expectedExpiration))
testNameServiceInvoke(t, e, nsHash, "properties", props, "neo.com") c.Invoke(t, props, "properties", "neo.com")
testNameServiceInvoke(t, e, nsHash, "balanceOf", 1, e.CommitteeHash) c.Invoke(t, 1, "balanceOf", e.CommitteeHash)
testNameServiceInvoke(t, e, nsHash, "ownerOf", e.CommitteeHash.BytesBE(), []byte("neo.com")) c.Invoke(t, e.CommitteeHash.BytesBE(), "ownerOf", []byte("neo.com"))
t.Run("invalid token ID", func(t *testing.T) { t.Run("invalid token ID", func(t *testing.T) {
testNameServiceInvoke(t, e, nsHash, "properties", nil, "not.exists") c.InvokeFail(t, "token not found", "properties", "not.exists")
testNameServiceInvoke(t, e, nsHash, "ownerOf", nil, "not.exists") c.InvokeFail(t, "token not found", "ownerOf", "not.exists")
testNameServiceInvoke(t, e, nsHash, "properties", nil, []interface{}{}) c.InvokeFail(t, "invalid conversion", "properties", []interface{}{})
testNameServiceInvoke(t, e, nsHash, "ownerOf", nil, []interface{}{}) c.InvokeFail(t, "invalid conversion", "ownerOf", []interface{}{})
}) })
// Renew // Renew
expectedExpiration += millisecondsInYear 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)) 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) { func TestSetGetRecord(t *testing.T) {
e, nsHash := newExecutorWithNS(t) c := newNSClient(t)
e := c.Executor
acc := e.NewAccount(t) 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) { 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", c.Invoke(t, true, "register", "neo.com", e.CommitteeHash)
true, "neo.com", e.CommitteeHash)
t.Run("invalid parameters", func(t *testing.T) { t.Run("invalid parameters", func(t *testing.T) {
testNameServiceInvoke(t, e, nsHash, "setRecord", nil, "neo.com", int64(0xFF), "1.2.3.4") c.InvokeFail(t, "unsupported record type", "setRecord", "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, "invalid record", "setRecord", "neo.com", int64(nns.A), "not.an.ip.address")
}) })
t.Run("invalid witness", func(t *testing.T) { t.Run("invalid witness", func(t *testing.T) {
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "setRecord", nil, cAcc.InvokeFail(t, "not witnessed by admin", "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
"neo.com", int64(nns.A), "1.2.3.4")
}) })
testNameServiceInvoke(t, e, nsHash, "getRecord", stackitem.Null{}, "neo.com", int64(nns.A)) c.Invoke(t, stackitem.Null{}, "getRecord", "neo.com", int64(nns.A))
testNameServiceInvoke(t, e, nsHash, "setRecord", stackitem.Null{}, "neo.com", int64(nns.A), "1.2.3.4") c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
testNameServiceInvoke(t, e, nsHash, "getRecord", "1.2.3.4", "neo.com", int64(nns.A)) c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A))
testNameServiceInvoke(t, e, nsHash, "setRecord", stackitem.Null{}, "neo.com", int64(nns.A), "1.2.3.4") c.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
testNameServiceInvoke(t, e, nsHash, "getRecord", "1.2.3.4", "neo.com", int64(nns.A)) c.Invoke(t, "1.2.3.4", "getRecord", "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") c.Invoke(t, stackitem.Null{}, "setRecord", "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") c.Invoke(t, stackitem.Null{}, "setRecord", "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{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
// Delete record. // Delete record.
t.Run("invalid witness", func(t *testing.T) { t.Run("invalid witness", func(t *testing.T) {
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "setRecord", nil, cAcc.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.CNAME))
"neo.com", int64(nns.CNAME))
}) })
testNameServiceInvoke(t, e, nsHash, "getRecord", "nspcc.ru", "neo.com", int64(nns.CNAME)) c.Invoke(t, "nspcc.ru", "getRecord", "neo.com", int64(nns.CNAME))
testNameServiceInvoke(t, e, nsHash, "deleteRecord", stackitem.Null{}, "neo.com", int64(nns.CNAME)) c.Invoke(t, stackitem.Null{}, "deleteRecord", "neo.com", int64(nns.CNAME))
testNameServiceInvoke(t, e, nsHash, "getRecord", stackitem.Null{}, "neo.com", int64(nns.CNAME)) c.Invoke(t, stackitem.Null{}, "getRecord", "neo.com", int64(nns.CNAME))
testNameServiceInvoke(t, e, nsHash, "getRecord", "1.2.3.4", "neo.com", int64(nns.A)) c.Invoke(t, "1.2.3.4", "getRecord", "neo.com", int64(nns.A))
t.Run("SetRecord_compatibility", func(t *testing.T) { t.Run("SetRecord_compatibility", func(t *testing.T) {
// tests are got from the NNS C# implementation and changed accordingly to non-native implementation behaviour // 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}, {Type: nns.AAAA, Name: "2001::13.1.68.3", ShouldFail: true},
} }
for _, testCase := range testCases { for _, testCase := range testCases {
var expected interface{} args := []interface{}{"neo.com", int64(testCase.Type), testCase.Name}
if testCase.ShouldFail {
expected = nil
} else {
expected = stackitem.Null{}
}
t.Run(testCase.Name, func(t *testing.T) { 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) { func TestSetAdmin(t *testing.T) {
e, nsHash := newExecutorWithNS(t) c := newNSClient(t)
e := c.Executor
owner := e.NewAccount(t) owner := e.NewAccount(t)
cOwner := c.WithSigner(owner)
admin := e.NewAccount(t) admin := e.NewAccount(t)
cAdmin := c.WithSigner(admin)
guest := e.NewAccount(t) guest := e.NewAccount(t)
testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") cGuest := c.WithSigner(guest)
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, owner, "register", true, c.Invoke(t, stackitem.Null{}, "addRoot", "com")
"neo.com", owner.PrivateKey().GetScriptHash())
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, guest, "setAdmin", nil, cOwner.Invoke(t, true, "register", "neo.com", owner.PrivateKey().GetScriptHash())
"neo.com", admin.PrivateKey().GetScriptHash()) cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
// Must be witnessed by both owner and admin. // Must be witnessed by both owner and admin.
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, owner, "setAdmin", nil, cOwner.InvokeFail(t, "not witnessed by admin", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
"neo.com", admin.PrivateKey().GetScriptHash()) cAdmin.InvokeFail(t, "not witnessed by owner", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, admin, "setAdmin", nil, cc := c.WithSigner([]*wallet.Account{owner, admin})
"neo.com", admin.PrivateKey().GetScriptHash()) cc.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, []*wallet.Account{owner, admin},
"setAdmin", stackitem.Null{},
"neo.com", admin.PrivateKey().GetScriptHash())
t.Run("set and delete by admin", func(t *testing.T) { t.Run("set and delete by admin", func(t *testing.T) {
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{}, cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
"neo.com", int64(nns.TXT), "sometext") cGuest.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.TXT))
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, guest, "deleteRecord", nil, cAdmin.Invoke(t, stackitem.Null{}, "deleteRecord", "neo.com", int64(nns.TXT))
"neo.com", int64(nns.TXT))
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, admin, "deleteRecord", stackitem.Null{},
"neo.com", int64(nns.TXT))
}) })
t.Run("set admin to null", func(t *testing.T) { t.Run("set admin to null", func(t *testing.T) {
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{}, cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
"neo.com", int64(nns.TXT), "sometext") cOwner.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", nil)
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, owner, "setAdmin", stackitem.Null{}, cAdmin.InvokeFail(t, "not witnessed by admin", "deleteRecord", "neo.com", int64(nns.TXT))
"neo.com", nil)
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, admin, "deleteRecord", nil,
"neo.com", int64(nns.TXT))
}) })
} }
func TestTransfer(t *testing.T) { func TestTransfer(t *testing.T) {
e, nsHash := newExecutorWithNS(t) c := newNSClient(t)
e := c.Executor
from := e.NewAccount(t) from := e.NewAccount(t)
cFrom := c.WithSigner(from)
to := e.NewAccount(t) to := e.NewAccount(t)
cTo := c.WithSigner(to)
testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") c.Invoke(t, stackitem.Null{}, "addRoot", "com")
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, from, "register", cFrom.Invoke(t, true, "register", "neo.com", from.PrivateKey().GetScriptHash())
true, "neo.com", from.PrivateKey().GetScriptHash()) cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, from, "setRecord", stackitem.Null{}, cFrom.InvokeFail(t, "token not found", "transfer", to.Contract.ScriptHash(), "not.exists", nil)
"neo.com", int64(nns.A), "1.2.3.4") c.Invoke(t, false, "transfer", to.Contract.ScriptHash(), "neo.com", nil)
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, from, "transfer", cFrom.Invoke(t, true, "transfer", to.Contract.ScriptHash(), "neo.com", nil)
nil, to.Contract.ScriptHash().BytesBE(), []byte("not.exists"), nil) cFrom.Invoke(t, 1, "totalSupply")
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, e.Committee, "transfer", cFrom.Invoke(t, to.Contract.ScriptHash().BytesBE(), "ownerOf", "neo.com")
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"))
// without onNEP11Transfer // without onNEP11Transfer
c := neotest.CompileSource(t, e.CommitteeHash, ctr := neotest.CompileSource(t, e.CommitteeHash,
strings.NewReader(`package foo strings.NewReader(`package foo
func Main() int { return 0 }`), func Main() int { return 0 }`),
&compiler.Options{Name: "foo"}) &compiler.Options{Name: "foo"})
e.DeployContract(t, c, nil) e.DeployContract(t, ctr, nil)
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, to, "transfer", cTo.InvokeFail(t, "method not found", "transfer", ctr.Hash, []byte("neo.com"), nil)
nil, c.Hash.BytesBE(), []byte("neo.com"), nil)
// with onNEP11Transfer // with onNEP11Transfer
c = neotest.CompileSource(t, e.CommitteeHash, ctr = neotest.CompileSource(t, e.CommitteeHash,
strings.NewReader(`package foo strings.NewReader(`package foo
import "github.com/nspcc-dev/neo-go/pkg/interop" import "github.com/nspcc-dev/neo-go/pkg/interop"
func OnNEP11Payment(from interop.Hash160, amount int, token []byte, data interface{}) {}`), func OnNEP11Payment(from interop.Hash160, amount int, token []byte, data interface{}) {}`),
&compiler.Options{Name: "foo"}) &compiler.Options{Name: "foo"})
e.DeployContract(t, c, nil) e.DeployContract(t, ctr, nil)
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, to, "transfer", cTo.Invoke(t, true, "transfer", ctr.Hash, []byte("neo.com"), nil)
true, c.Hash.BytesBE(), []byte("neo.com"), nil) cFrom.Invoke(t, 1, "totalSupply")
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, from, "totalSupply", 1) cFrom.Invoke(t, ctr.Hash.BytesBE(), "ownerOf", []byte("neo.com"))
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, from, "ownerOf",
c.Hash.BytesBE(), []byte("neo.com"))
} }
func TestTokensOf(t *testing.T) { func TestTokensOf(t *testing.T) {
e, nsHash := newExecutorWithNS(t) c := newNSClient(t)
e := c.Executor
acc1 := e.NewAccount(t) acc1 := e.NewAccount(t)
cAcc1 := c.WithSigner(acc1)
acc2 := e.NewAccount(t) acc2 := e.NewAccount(t)
cAcc2 := c.WithSigner(acc2)
testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") c.Invoke(t, stackitem.Null{}, "addRoot", "com")
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, acc1, "register", cAcc1.Invoke(t, true, "register", "neo.com", acc1.PrivateKey().GetScriptHash())
true, "neo.com", acc1.PrivateKey().GetScriptHash()) cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.PrivateKey().GetScriptHash())
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, acc2, "register",
true, "nspcc.com", acc2.PrivateKey().GetScriptHash())
testTokensOf(t, e, nsHash, [][]byte{[]byte("neo.com")}, acc1.Contract.ScriptHash().BytesBE()) testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.Contract.ScriptHash().BytesBE())
testTokensOf(t, e, nsHash, [][]byte{[]byte("nspcc.com")}, acc2.Contract.ScriptHash().BytesBE()) testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.Contract.ScriptHash().BytesBE())
testTokensOf(t, e, nsHash, [][]byte{[]byte("neo.com"), []byte("nspcc.com")}) testTokensOf(t, c, [][]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{}, 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" method := "tokensOf"
if len(args) == 0 { if len(args) == 0 {
method = "tokens" method = "tokens"
} }
w := io.NewBufBinWriter() s, err := c.TestInvoke(t, method, args...)
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)
if result == nil { if result == nil {
require.Error(t, err) require.Error(t, err)
return return
} }
require.NoError(t, err) require.NoError(t, err)
iter := s.Pop().Interop().Value().(*storage.Iterator)
arr := make([]stackitem.Item, 0, len(result)) 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])) 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) { func TestResolve(t *testing.T) {
e, nsHash := newExecutorWithNS(t) c := newNSClient(t)
e := c.Executor
acc := e.NewAccount(t) acc := e.NewAccount(t)
cAcc := c.WithSigner(acc)
testNameServiceInvoke(t, e, nsHash, "addRoot", stackitem.Null{}, "com") c.Invoke(t, stackitem.Null{}, "addRoot", "com")
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, acc, "register", cAcc.Invoke(t, true, "register", "neo.com", acc.PrivateKey().GetScriptHash())
true, "neo.com", acc.PrivateKey().GetScriptHash()) cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com")
"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")
testNameServiceInvokeAux(t, e, nsHash, defaultRegisterSysfee, acc, "register", cAcc.Invoke(t, true, "register", "alias.com", acc.PrivateKey().GetScriptHash())
true, "alias.com", acc.PrivateKey().GetScriptHash()) cAcc.Invoke(t, stackitem.Null{}, "setRecord", "alias.com", int64(nns.TXT), "sometxt")
testNameServiceInvokeAux(t, e, nsHash, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{},
"alias.com", int64(nns.TXT), "sometxt")
testNameServiceInvoke(t, e, nsHash, "resolve", "1.2.3.4", c.Invoke(t, "1.2.3.4", "resolve", "neo.com", int64(nns.A))
"neo.com", int64(nns.A)) c.Invoke(t, "alias.com", "resolve", "neo.com", int64(nns.CNAME))
testNameServiceInvoke(t, e, nsHash, "resolve", "alias.com", c.Invoke(t, "sometxt", "resolve", "neo.com", int64(nns.TXT))
"neo.com", int64(nns.CNAME)) c.Invoke(t, stackitem.Null{}, "resolve", "neo.com", int64(nns.AAAA))
testNameServiceInvoke(t, e, nsHash, "resolve", "sometxt",
"neo.com", int64(nns.TXT))
testNameServiceInvoke(t, e, nsHash, "resolve", stackitem.Null{},
"neo.com", int64(nns.AAAA))
} }
const ( const (
defaultNameServiceDomainPrice = 10_0000_0000 defaultNameServiceDomainPrice = 10_0000_0000
defaultNameServiceSysfee = 6000_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))
}
}

View file

@ -238,8 +238,8 @@ func (e *Executor) SignBlock(b *block.Block) *block.Block {
return b return b
} }
// AddBlockCheckHalt is a convenient wrapper over AddNewBlock and CheckHalt. // AddBlockCheckHalt is a convenient wrapper over AddBlock and CheckHalt.
func (e *Executor) AddBlockCheckHalt(t *testing.T, bc blockchainer.Blockchainer, txs ...*transaction.Transaction) *block.Block { func (e *Executor) AddBlockCheckHalt(t *testing.T, txs ...*transaction.Transaction) *block.Block {
b := e.AddNewBlock(t, txs...) b := e.AddNewBlock(t, txs...)
for _, tx := range txs { for _, tx := range txs {
e.CheckHalt(t, tx.Hash()) e.CheckHalt(t, tx.Hash())

83
pkg/neotest/client.go Normal file
View file

@ -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)
}