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

View file

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

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