From d34353aec29aab3c49321e6aefb1c192d02e62e6 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 16 Dec 2020 16:11:12 +0300 Subject: [PATCH] core: add MinimumDeploymentFee --- config/protocol.unit_testnet.single.yml | 2 +- config/protocol.unit_testnet.yml | 2 +- pkg/core/native/management.go | 65 ++++++++++++++++++++++--- pkg/core/native_management_test.go | 38 ++++++++++----- pkg/core/native_policy_test.go | 35 +++++++------ 5 files changed, 101 insertions(+), 41 deletions(-) diff --git a/config/protocol.unit_testnet.single.yml b/config/protocol.unit_testnet.single.yml index 3f655c658..966780345 100644 --- a/config/protocol.unit_testnet.single.yml +++ b/config/protocol.unit_testnet.single.yml @@ -41,7 +41,7 @@ ApplicationConfiguration: Password: "one" RPC: Address: 127.0.0.1 - MaxGasInvoke: 10 + MaxGasInvoke: 15 Enabled: true EnableCORSWorkaround: false Port: 0 # let the system choose port dynamically diff --git a/config/protocol.unit_testnet.yml b/config/protocol.unit_testnet.yml index e63b288ee..966656350 100644 --- a/config/protocol.unit_testnet.yml +++ b/config/protocol.unit_testnet.yml @@ -48,7 +48,7 @@ ApplicationConfiguration: MinPeers: 1 RPC: Address: 127.0.0.1 - MaxGasInvoke: 10 + MaxGasInvoke: 15 Enabled: true EnableCORSWorkaround: false Port: 0 # let the system choose port dynamically diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index ff92097d3..0462d43a1 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -37,10 +37,16 @@ const StoragePrice = 100000 const ( prefixContract = 8 + + defaultMinimumDeploymentFee = 10_00000000 ) -var errGasLimitExceeded = errors.New("gas limit exceeded") -var keyNextAvailableID = []byte{15} +var ( + errGasLimitExceeded = errors.New("gas limit exceeded") + + keyNextAvailableID = []byte{15} + keyMinimumDeploymentFee = []byte{20} +) // makeContractKey creates a key from account script hash. func makeContractKey(h util.Uint160) []byte { @@ -75,6 +81,14 @@ func newManagement() *Management { md = newMethodAndPrice(m.destroy, 1000000, smartcontract.WriteStates) m.AddMethod(md, desc) + desc = newDescriptor("getMinimumDeploymentFee", smartcontract.IntegerType) + md = newMethodAndPrice(m.getMinimumDeploymentFee, 100_0000, smartcontract.ReadStates) + m.AddMethod(md, desc) + + desc = newDescriptor("setMinimumDeploymentFee", smartcontract.BoolType, + manifest.NewParameter("value", smartcontract.IntegerType)) + md = newMethodAndPrice(m.setMinimumDeploymentFee, 300_0000, smartcontract.WriteStates) + m.AddMethod(md, desc) return m } @@ -140,7 +154,7 @@ func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) { // getNefAndManifestFromItems converts input arguments into NEF and manifest // adding appropriate deployment GAS price and sanitizing inputs. -func getNefAndManifestFromItems(ic *interop.Context, args []stackitem.Item) (*nef.File, *manifest.Manifest, error) { +func (m *Management) getNefAndManifestFromItems(ic *interop.Context, args []stackitem.Item, isDeploy bool) (*nef.File, *manifest.Manifest, error) { nefBytes, err := getLimitedSlice(args[0], math.MaxInt32) // Upper limits are checked during NEF deserialization. if err != nil { return nil, nil, fmt.Errorf("invalid NEF file: %w", err) @@ -150,7 +164,14 @@ func getNefAndManifestFromItems(ic *interop.Context, args []stackitem.Item) (*ne return nil, nil, fmt.Errorf("invalid manifest: %w", err) } - if !ic.VM.AddGas(ic.Chain.GetPolicer().GetStoragePrice() * int64(len(nefBytes)+len(manifestBytes))) { + gas := ic.Chain.GetPolicer().GetStoragePrice() * int64(len(nefBytes)+len(manifestBytes)) + if isDeploy { + fee := m.GetMinimumDeploymentFee(ic.DAO) + if fee > gas { + gas = fee + } + } + if !ic.VM.AddGas(gas) { return nil, nil, errGasLimitExceeded } var resManifest *manifest.Manifest @@ -175,7 +196,7 @@ func getNefAndManifestFromItems(ic *interop.Context, args []stackitem.Item) (*ne // deploy is an implementation of public deploy method, it's run under // VM protections, so it's OK for it to panic instead of returning errors. func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackitem.Item { - neff, manif, err := getNefAndManifestFromItems(ic, args) + neff, manif, err := m.getNefAndManifestFromItems(ic, args, true) if err != nil { panic(err) } @@ -236,7 +257,7 @@ func (m *Management) Deploy(d dao.DAO, sender util.Uint160, neff *nef.File, mani // update is an implementation of public update method, it's run under // VM protections, so it's OK for it to panic instead of returning errors. func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item { - neff, manif, err := getNefAndManifestFromItems(ic, args) + neff, manif, err := m.getNefAndManifestFromItems(ic, args, false) if err != nil { panic(err) } @@ -319,6 +340,34 @@ func (m *Management) Destroy(d dao.DAO, hash util.Uint160) error { return nil } +func (m *Management) getMinimumDeploymentFee(ic *interop.Context, args []stackitem.Item) stackitem.Item { + return stackitem.NewBigInteger(big.NewInt(m.GetMinimumDeploymentFee(ic.DAO))) +} + +// GetMinimumDeploymentFee returns the minimum required fee for contract deploy. +func (m *Management) GetMinimumDeploymentFee(dao dao.DAO) int64 { + return getInt64WithKey(m.ContractID, dao, keyMinimumDeploymentFee, defaultMinimumDeploymentFee) +} + +func (m *Management) setMinimumDeploymentFee(ic *interop.Context, args []stackitem.Item) stackitem.Item { + value := toBigInt(args[0]).Int64() + if value < 0 { + panic(fmt.Errorf("MinimumDeploymentFee cannot be negative")) + } + ok, err := checkValidators(ic) + if err != nil { + panic(err) + } + if !ok { + return stackitem.NewBool(false) + } + err = setInt64WithKey(m.ContractID, ic.DAO, keyMinimumDeploymentFee, value) + if err != nil { + panic(err) + } + return stackitem.NewBool(true) +} + func callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) { md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy) if md != nil { @@ -422,8 +471,8 @@ func (m *Management) PostPersist(ic *interop.Context) error { } // Initialize implements Contract interface. -func (m *Management) Initialize(_ *interop.Context) error { - return nil +func (m *Management) Initialize(ic *interop.Context) error { + return setInt64WithKey(m.ContractID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee) } // PutContractState saves given contract state into given DAO. diff --git a/pkg/core/native_management_test.go b/pkg/core/native_management_test.go index 786f5655d..41e0e0883 100644 --- a/pkg/core/native_management_test.go +++ b/pkg/core/native_management_test.go @@ -89,42 +89,42 @@ func TestContractDeploy(t *testing.T) { require.NoError(t, err) t.Run("no NEF", func(t *testing.T) { - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nil, manif1) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nil, manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("no manifest", func(t *testing.T) { - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, nil) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, nil) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("int for NEF", func(t *testing.T) { - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", int64(1), manif1) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", int64(1), manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("zero-length NEF", func(t *testing.T) { - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", []byte{}, manif1) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", []byte{}, manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("array for NEF", func(t *testing.T) { - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", []interface{}{int64(1)}, manif1) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", []interface{}{int64(1)}, manif1) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("int for manifest", func(t *testing.T) { - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, int64(1)) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, int64(1)) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("zero-length manifest", func(t *testing.T) { - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, []byte{}) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, []byte{}) require.NoError(t, err) checkFAULTState(t, res) }) t.Run("array for manifest", func(t *testing.T) { - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, []interface{}{int64(1)}) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, []interface{}{int64(1)}) require.NoError(t, err) checkFAULTState(t, res) }) @@ -137,7 +137,7 @@ func TestContractDeploy(t *testing.T) { manifB, err := json.Marshal(badManifest) require.NoError(t, err) - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, manifB) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manifB) require.NoError(t, err) checkFAULTState(t, res) }) @@ -147,7 +147,7 @@ func TestContractDeploy(t *testing.T) { checkFAULTState(t, res) }) t.Run("positive", func(t *testing.T) { - tx1, err := prepareContractMethodInvoke(bc, 10_00000000, mgmtHash, "deploy", nef1b, manif1) + tx1, err := prepareContractMethodInvoke(bc, 11_00000000, mgmtHash, "deploy", nef1b, manif1) require.NoError(t, err) tx2, err := prepareContractMethodInvoke(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) require.NoError(t, err) @@ -184,7 +184,7 @@ func TestContractDeploy(t *testing.T) { }) }) t.Run("contract already exists", func(t *testing.T) { - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, manif1) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manif1) require.NoError(t, err) checkFAULTState(t, res) }) @@ -207,7 +207,7 @@ func TestContractDeploy(t *testing.T) { require.NoError(t, err) manifD, err := json.Marshal(m) require.NoError(t, err) - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nefDb, manifD) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nefDb, manifD) require.NoError(t, err) checkFAULTState(t, res) @@ -236,7 +236,7 @@ func TestContractDeploy(t *testing.T) { require.NoError(t, err) manifD, err := json.Marshal(m) require.NoError(t, err) - res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nefDb, manifD) + res, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nefDb, manifD) require.NoError(t, err) checkFAULTState(t, res) @@ -455,3 +455,15 @@ func compareContractStates(t *testing.T, expected *state.Contract, actual stacki require.Equal(t, expected.Script, act[3].Value().([]byte)) require.Equal(t, expectedManifest, act[4].Value().([]byte)) } + +func TestMinimumDeploymentFee(t *testing.T) { + chain := newTestChain(t) + defer chain.Close() + + t.Run("get, internal method", func(t *testing.T) { + n := chain.contracts.Management.GetMinimumDeploymentFee(chain.dao) + require.Equal(t, 10_00000000, int(n)) + }) + + testGetSet(t, chain, chain.contracts.Management.Hash, "MinimumDeploymentFee", 10_00000000, 0, 0) +} diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index 94d11a729..6e0684665 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -14,34 +14,33 @@ import ( "github.com/stretchr/testify/require" ) -func testPolicyGetSet(t *testing.T, chain *Blockchain, name string, defaultValue, minValue, maxValue int64) { - policyHash := chain.contracts.Policy.Metadata().Hash +func testGetSet(t *testing.T, chain *Blockchain, hash util.Uint160, name string, defaultValue, minValue, maxValue int64) { getName := "get" + name setName := "set" + name 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, policyHash, setName, minValue+1) + invokeRes, err := invokeContractMethodBy(t, chain, signer, hash, setName, minValue+1) checkResult(t, invokeRes, stackitem.NewBool(false)) }) - t.Run("get", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, getName) + 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)) require.NoError(t, chain.persist()) }) - t.Run("set, zero fee", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, setName, minValue-1) + t.Run("set, too small value", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, hash, setName, minValue-1) require.NoError(t, err) checkFAULTState(t, res) }) if maxValue != 0 { - t.Run("set, too big fee", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, setName, maxValue+1) + t.Run("set, too large value", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, hash, setName, maxValue+1) require.NoError(t, err) checkFAULTState(t, res) }) @@ -49,9 +48,9 @@ func testPolicyGetSet(t *testing.T, chain *Blockchain, name string, defaultValue t.Run("set, success", func(t *testing.T) { // Set and get in the same block. - txSet, err := prepareContractMethodInvoke(chain, 100000000, policyHash, setName, defaultValue+1) + txSet, err := prepareContractMethodInvoke(chain, 100000000, hash, setName, defaultValue+1) require.NoError(t, err) - txGet1, err := prepareContractMethodInvoke(chain, 100000000, policyHash, getName) + txGet1, err := prepareContractMethodInvoke(chain, 100000000, hash, getName) require.NoError(t, err) aers, err := persistBlock(chain, txSet, txGet1) require.NoError(t, err) @@ -60,7 +59,7 @@ func testPolicyGetSet(t *testing.T, chain *Blockchain, name string, defaultValue require.NoError(t, chain.persist()) // Get in the next block. - res, err := invokeContractMethod(chain, 100000000, policyHash, getName) + res, err := invokeContractMethod(chain, 100000000, hash, getName) require.NoError(t, err) checkResult(t, res, stackitem.Make(defaultValue+1)) require.NoError(t, chain.persist()) @@ -76,7 +75,7 @@ func TestMaxTransactionsPerBlock(t *testing.T) { require.Equal(t, 512, int(n)) }) - testPolicyGetSet(t, chain, "MaxTransactionsPerBlock", 512, 0, block.MaxTransactionsPerBlock) + testGetSet(t, chain, chain.contracts.Policy.Hash, "MaxTransactionsPerBlock", 512, 0, block.MaxTransactionsPerBlock) } func TestMaxBlockSize(t *testing.T) { @@ -88,7 +87,7 @@ func TestMaxBlockSize(t *testing.T) { require.Equal(t, 1024*256, int(n)) }) - testPolicyGetSet(t, chain, "MaxBlockSize", 1024*256, 0, payload.MaxSize) + testGetSet(t, chain, chain.contracts.Policy.Hash, "MaxBlockSize", 1024*256, 0, payload.MaxSize) } func TestFeePerByte(t *testing.T) { @@ -100,7 +99,7 @@ func TestFeePerByte(t *testing.T) { require.Equal(t, 1000, int(n)) }) - testPolicyGetSet(t, chain, "FeePerByte", 1000, 0, 100_000_000) + testGetSet(t, chain, chain.contracts.Policy.Hash, "FeePerByte", 1000, 0, 100_000_000) } func TestExecFeeFactor(t *testing.T) { @@ -112,7 +111,7 @@ func TestExecFeeFactor(t *testing.T) { require.EqualValues(t, interop.DefaultBaseExecFee, n) }) - testPolicyGetSet(t, chain, "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000) + testGetSet(t, chain, chain.contracts.Policy.Hash, "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000) } func TestBlockSystemFee(t *testing.T) { @@ -124,7 +123,7 @@ func TestBlockSystemFee(t *testing.T) { require.Equal(t, 9000*native.GASFactor, int(n)) }) - testPolicyGetSet(t, chain, "MaxBlockSystemFee", 9000*native.GASFactor, 4007600, 0) + testGetSet(t, chain, chain.contracts.Policy.Hash, "MaxBlockSystemFee", 9000*native.GASFactor, 4007600, 0) } func TestStoragePrice(t *testing.T) { @@ -136,7 +135,7 @@ func TestStoragePrice(t *testing.T) { require.Equal(t, int64(native.StoragePrice), n) }) - testPolicyGetSet(t, chain, "StoragePrice", native.StoragePrice, 1, 10000000) + testGetSet(t, chain, chain.contracts.Policy.Hash, "StoragePrice", native.StoragePrice, 1, 10000000) } func TestBlockedAccounts(t *testing.T) {