diff --git a/pkg/core/native/native_test/common_test.go b/pkg/core/native/native_test/common_test.go new file mode 100644 index 000000000..80c89d318 --- /dev/null +++ b/pkg/core/native/native_test/common_test.go @@ -0,0 +1,63 @@ +package native_test + +import ( + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +func newNativeClient(t *testing.T, name string) *neotest.ContractInvoker { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + return e.CommitteeInvoker(e.NativeHash(t, name)) +} + +func testGetSet(t *testing.T, c *neotest.ContractInvoker, name string, defaultValue, minValue, maxValue int64) { + getName := "get" + name + setName := "set" + name + + randomInvoker := c.WithSigners(c.NewAccount(t)) + committeeInvoker := c.WithSigners(c.Committee) + + t.Run("set, not signed by committee", func(t *testing.T) { + randomInvoker.InvokeFail(t, "invalid committee signature", setName, minValue+1) + }) + t.Run("get, default value", func(t *testing.T) { + randomInvoker.Invoke(t, defaultValue, getName) + }) + t.Run("set, too small value", func(t *testing.T) { + committeeInvoker.InvokeFail(t, "", setName, minValue-1) + }) + + if maxValue != 0 { + t.Run("set, too large value", func(t *testing.T) { + // use big.Int because max can be `math.MaxInt64` + max := big.NewInt(maxValue) + max.Add(max, big.NewInt(1)) + committeeInvoker.InvokeFail(t, "", setName, max) + }) + } + + t.Run("set, success", func(t *testing.T) { + // Set and get in the same block. + txSet := committeeInvoker.PrepareInvoke(t, setName, defaultValue+1) + txGet := randomInvoker.PrepareInvoke(t, getName) + c.AddNewBlock(t, txSet, txGet) + c.CheckHalt(t, txSet.Hash(), stackitem.Null{}) + + if name != "GasPerBlock" { // GasPerBlock is set on the next block + c.CheckHalt(t, txGet.Hash(), stackitem.Make(defaultValue+1)) + } else { + c.CheckHalt(t, txGet.Hash(), stackitem.Make(defaultValue)) + c.AddNewBlock(t) + randomInvoker.Invoke(t, defaultValue+1, getName) + } + + // Get in the next block. + randomInvoker.Invoke(t, defaultValue+1, getName) + }) +} diff --git a/pkg/core/native/native_test/helpers/policyhelper/policyhelper.go b/pkg/core/native/native_test/helpers/policyhelper/policyhelper.go new file mode 100644 index 000000000..18d0f68de --- /dev/null +++ b/pkg/core/native/native_test/helpers/policyhelper/policyhelper.go @@ -0,0 +1,5 @@ +package policyhelper + +func Do() bool { + return true +} diff --git a/pkg/core/native/native_test/helpers/policyhelper/policyhelper.yml b/pkg/core/native/native_test/helpers/policyhelper/policyhelper.yml new file mode 100644 index 000000000..a7882e096 --- /dev/null +++ b/pkg/core/native/native_test/helpers/policyhelper/policyhelper.yml @@ -0,0 +1,4 @@ +name: "Policy helper contract" +sourceurl: https://github.com/nspcc-dev/neo-go +supportedstandards: [] +safemethods: ["do"] diff --git a/pkg/core/native/native_test/policy_test.go b/pkg/core/native/native_test/policy_test.go new file mode 100644 index 000000000..9dfc1a5eb --- /dev/null +++ b/pkg/core/native/native_test/policy_test.go @@ -0,0 +1,81 @@ +package native_test + +import ( + "fmt" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +func newPolicyClient(t *testing.T) *neotest.ContractInvoker { + return newNativeClient(t, nativenames.Policy) +} + +func TestPolicy_FeePerByte(t *testing.T) { + testGetSet(t, newPolicyClient(t), "FeePerByte", 1000, 0, 100_000_000) +} + +func TestPolicy_ExecFeeFactor(t *testing.T) { + testGetSet(t, newPolicyClient(t), "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000) +} + +func TestPolicy_StoragePrice(t *testing.T) { + testGetSet(t, newPolicyClient(t), "StoragePrice", native.DefaultStoragePrice, 1, 10000000) +} + +func TestPolicy_BlockedAccounts(t *testing.T) { + c := newPolicyClient(t) + e := c.Executor + randomInvoker := c.WithSigners(c.NewAccount(t)) + committeeInvoker := c.WithSigners(c.Committee) + unlucky := util.Uint160{1, 2, 3} + + t.Run("isBlocked", func(t *testing.T) { + randomInvoker.Invoke(t, false, "isBlocked", unlucky) + }) + + t.Run("block-unblock account", func(t *testing.T) { + committeeInvoker.Invoke(t, true, "blockAccount", unlucky) + randomInvoker.Invoke(t, true, "isBlocked", unlucky) + committeeInvoker.Invoke(t, true, "unblockAccount", unlucky) + randomInvoker.Invoke(t, false, "isBlocked", unlucky) + }) + + t.Run("double-block", func(t *testing.T) { + // block + committeeInvoker.Invoke(t, true, "blockAccount", unlucky) + + // double-block should fail + committeeInvoker.Invoke(t, false, "blockAccount", unlucky) + + // unblock + committeeInvoker.Invoke(t, true, "unblockAccount", unlucky) + + // unblock the same account should fail as we don't have it blocked + committeeInvoker.Invoke(t, false, "unblockAccount", unlucky) + }) + + t.Run("not signed by committee", func(t *testing.T) { + randomInvoker.InvokeFail(t, "invalid committee signature", "blockAccount", unlucky) + randomInvoker.InvokeFail(t, "invalid committee signature", "unblockAccount", unlucky) + }) + + t.Run("block-unblock contract", func(t *testing.T) { + committeeInvoker.InvokeFail(t, "cannot block native contract", "blockAccount", c.NativeHash(t, nativenames.Neo)) + + helper := neotest.CompileFile(t, c.CommitteeHash, "./helpers/policyhelper", "./helpers/policyhelper/policyhelper.yml") + e.DeployContract(t, helper, nil) + helperInvoker := e.CommitteeInvoker(helper.Hash) + + helperInvoker.Invoke(t, true, "do") + committeeInvoker.Invoke(t, true, "blockAccount", helper.Hash) + helperInvoker.InvokeFail(t, fmt.Sprintf("contract %s is blocked", helper.Hash.StringLE()), "do") + + committeeInvoker.Invoke(t, true, "unblockAccount", helper.Hash) + helperInvoker.Invoke(t, true, "do") + }) +} diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index 8dd05fc3c..8e06d6571 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -1,16 +1,12 @@ package core import ( - "math/big" "testing" "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/native" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" ) @@ -19,68 +15,6 @@ func transferFundsToCommittee(t *testing.T, chain *Blockchain) { chain.contracts.GAS.Hash, 1000_00000000) } -func testGetSet(t *testing.T, chain *Blockchain, hash util.Uint160, name string, defaultValue, minValue, maxValue int64) { - getName := "get" + name - setName := "set" + name - - transferFundsToCommittee(t, chain) - t.Run("set, not signed by committee", func(t *testing.T) { - signer, err := wallet.NewAccount() - require.NoError(t, err) - invokeRes, err := invokeContractMethodBy(t, chain, signer, hash, setName, minValue+1) - require.NoError(t, err) - checkFAULTState(t, invokeRes) - }) - - t.Run("get, defult value", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, hash, getName) - require.NoError(t, err) - checkResult(t, res, stackitem.Make(defaultValue)) - _, err = chain.persist(false) - require.NoError(t, err) - }) - - t.Run("set, too small value", func(t *testing.T) { - res, err := invokeContractMethodGeneric(chain, 100000000, hash, setName, true, minValue-1) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - if maxValue != 0 { - t.Run("set, too large value", func(t *testing.T) { - // use big.Int because max can be `math.MaxInt64` - max := big.NewInt(maxValue) - max.Add(max, big.NewInt(1)) - res, err := invokeContractMethodGeneric(chain, 100000000, hash, setName, true, max) - require.NoError(t, err) - checkFAULTState(t, res) - }) - } - - t.Run("set, success", func(t *testing.T) { - // Set and get in the same block. - txSet, err := prepareContractMethodInvokeGeneric(chain, 100000000, hash, setName, true, defaultValue+1) - require.NoError(t, err) - txGet1, err := prepareContractMethodInvoke(chain, 100000000, hash, getName) - require.NoError(t, err) - aers, err := persistBlock(chain, txSet, txGet1) - require.NoError(t, err) - checkResult(t, aers[0], stackitem.Null{}) - if name != "GasPerBlock" { // GasPerBlock is set on the next block - checkResult(t, aers[1], stackitem.Make(defaultValue+1)) - } - _, err = chain.persist(false) - require.NoError(t, err) - - // Get in the next block. - res, err := invokeContractMethod(chain, 100000000, hash, getName) - require.NoError(t, err) - checkResult(t, res, stackitem.Make(defaultValue+1)) - _, err = chain.persist(false) - require.NoError(t, err) - }) -} - func TestFeePerByte(t *testing.T) { chain := newTestChain(t) @@ -88,8 +22,6 @@ func TestFeePerByte(t *testing.T) { n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao) require.Equal(t, 1000, int(n)) }) - - testGetSet(t, chain, chain.contracts.Policy.Hash, "FeePerByte", 1000, 0, 100_000_000) } func TestExecFeeFactor(t *testing.T) { @@ -99,8 +31,6 @@ func TestExecFeeFactor(t *testing.T) { n := chain.contracts.Policy.GetExecFeeFactorInternal(chain.dao) require.EqualValues(t, interop.DefaultBaseExecFee, n) }) - - testGetSet(t, chain, chain.contracts.Policy.Hash, "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000) } func TestStoragePrice(t *testing.T) { @@ -110,15 +40,10 @@ func TestStoragePrice(t *testing.T) { n := chain.contracts.Policy.GetStoragePriceInternal(chain.dao) require.Equal(t, int64(native.DefaultStoragePrice), n) }) - - testGetSet(t, chain, chain.contracts.Policy.Hash, "StoragePrice", native.DefaultStoragePrice, 1, 10000000) } func TestBlockedAccounts(t *testing.T) { chain := newTestChain(t) - account := util.Uint160{1, 2, 3} - policyHash := chain.contracts.Policy.Metadata().Hash - transferTokenFromMultisigAccount(t, chain, testchain.CommitteeScriptHash(), chain.contracts.GAS.Hash, 100_00000000) @@ -126,100 +51,4 @@ func TestBlockedAccounts(t *testing.T) { isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, random.Uint160()) require.Equal(t, false, isBlocked) }) - - t.Run("isBlocked, contract method", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "isBlocked", random.Uint160()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(false)) - _, err = chain.persist(false) - require.NoError(t, err) - }) - - t.Run("block-unblock account", func(t *testing.T) { - res, err := invokeContractMethodGeneric(chain, 100000000, policyHash, "blockAccount", true, account.BytesBE()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - - isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, account) - require.Equal(t, isBlocked, true) - _, err = chain.persist(false) - require.NoError(t, err) - - res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "unblockAccount", true, account.BytesBE()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - - isBlocked = chain.contracts.Policy.IsBlockedInternal(chain.dao, account) - require.Equal(t, false, isBlocked) - _, err = chain.persist(false) - require.NoError(t, err) - }) - - t.Run("double-block", func(t *testing.T) { - // block - res, err := invokeContractMethodGeneric(chain, 100000000, policyHash, "blockAccount", true, account.BytesBE()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - _, err = chain.persist(false) - require.NoError(t, err) - - // double-block should fail - res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "blockAccount", true, account.BytesBE()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(false)) - _, err = chain.persist(false) - require.NoError(t, err) - - // unblock - res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "unblockAccount", true, account.BytesBE()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - _, err = chain.persist(false) - require.NoError(t, err) - - // unblock the same account should fail as we don't have it blocked - res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "unblockAccount", true, account.BytesBE()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(false)) - _, err = chain.persist(false) - require.NoError(t, err) - }) - - t.Run("not signed by committee", func(t *testing.T) { - signer, err := wallet.NewAccount() - require.NoError(t, err) - invokeRes, err := invokeContractMethodBy(t, chain, signer, policyHash, "blockAccount", account.BytesBE()) - require.NoError(t, err) - checkFAULTState(t, invokeRes) - - invokeRes, err = invokeContractMethodBy(t, chain, signer, policyHash, "unblockAccount", account.BytesBE()) - require.NoError(t, err) - checkFAULTState(t, invokeRes) - }) - - t.Run("block-unblock contract", func(t *testing.T) { - neoHash := chain.contracts.NEO.Metadata().Hash - res, err := invokeContractMethodGeneric(chain, 100000000, policyHash, "blockAccount", true, neoHash.BytesBE()) - require.NoError(t, err) - checkFAULTState(t, res) - - cs, _ := getTestContractState(chain) - require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, cs)) - - res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "blockAccount", true, cs.Hash.BytesBE()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - - res, err = invokeContractMethod(chain, 100000000, cs.Hash, "justReturn") - require.NoError(t, err) - checkFAULTState(t, res) - - res, err = invokeContractMethodGeneric(chain, 100000000, policyHash, "unblockAccount", true, cs.Hash.BytesBE()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - - res, err = invokeContractMethod(chain, 100000000, cs.Hash, "justReturn") - require.NoError(t, err) - checkResult(t, res, stackitem.Null{}) - }) }