diff --git a/internal/testchain/transaction.go b/internal/testchain/transaction.go index 14186be9a..b0dcb003d 100644 --- a/internal/testchain/transaction.go +++ b/internal/testchain/transaction.go @@ -92,7 +92,7 @@ func NewDeployTx(bc blockchainer.Blockchainer, name string, sender util.Uint160, func SignTx(bc blockchainer.Blockchainer, txs ...*transaction.Transaction) error { for _, tx := range txs { size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(ownerScript) + netFee, sizeDelta := fee.Calculate(bc.GetPolicer().GetBaseExecFee(), ownerScript) tx.NetworkFee += netFee size += sizeDelta tx.NetworkFee += int64(size) * bc.FeePerByte() diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index 19912da09..d3983f9bf 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -11,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/storage" @@ -32,7 +33,7 @@ func TestNewService(t *testing.T) { tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 100000) tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) require.NoError(t, srv.Chain.PoolTx(tx)) var txx []block.Transaction @@ -170,7 +171,7 @@ func TestService_GetVerified(t *testing.T) { txs = append(txs, tx) } addSender(t, txs...) - signTx(t, srv.Chain.FeePerByte(), txs...) + signTx(t, srv.Chain, txs...) require.NoError(t, srv.Chain.PoolTx(txs[3])) hashes := []util.Uint256{txs[0].Hash(), txs[1].Hash(), txs[2].Hash()} @@ -257,7 +258,7 @@ func TestService_getTx(t *testing.T) { tx.Nonce = 1234 tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) h := tx.Hash() require.Equal(t, nil, srv.getTx(h)) @@ -351,7 +352,7 @@ func TestVerifyBlock(t *testing.T) { tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 100000) tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) require.NoError(t, srv.Chain.PoolTx(tx)) b := testchain.NewBlock(t, srv.Chain, 1, 0, tx) require.True(t, srv.verifyBlock(&neoBlock{Block: *b})) @@ -360,7 +361,7 @@ func TestVerifyBlock(t *testing.T) { tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 100000) tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) b := testchain.NewBlock(t, srv.Chain, 1, 0, tx) require.True(t, srv.verifyBlock(&neoBlock{Block: *b})) }) @@ -369,12 +370,12 @@ func TestVerifyBlock(t *testing.T) { tx1.NetworkFee = 20_000_000 * native.GASFactor tx1.ValidUntilBlock = 1 addSender(t, tx1) - signTx(t, srv.Chain.FeePerByte(), tx1) + signTx(t, srv.Chain, tx1) tx2 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 100000) tx2.NetworkFee = 20_000_000 * native.GASFactor tx2.ValidUntilBlock = 1 addSender(t, tx2) - signTx(t, srv.Chain.FeePerByte(), tx2) + signTx(t, srv.Chain, tx2) require.NoError(t, srv.Chain.PoolTx(tx1)) require.Error(t, srv.Chain.PoolTx(tx2)) b := testchain.NewBlock(t, srv.Chain, 1, 0, tx2) @@ -391,7 +392,7 @@ func TestVerifyBlock(t *testing.T) { tx := transaction.New(netmode.UnitTestNet, script, 100000) tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) b := testchain.NewBlock(t, srv.Chain, 1, 0, tx) require.False(t, srv.verifyBlock(&neoBlock{Block: *b})) }) @@ -399,7 +400,7 @@ func TestVerifyBlock(t *testing.T) { tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 100000) tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) tx.Scripts[0].InvocationScript[16] = ^tx.Scripts[0].InvocationScript[16] b := testchain.NewBlock(t, srv.Chain, 1, 0, tx) require.False(t, srv.verifyBlock(&neoBlock{Block: *b})) @@ -410,7 +411,7 @@ func TestVerifyBlock(t *testing.T) { txes[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, srv.Chain.GetPolicer().GetMaxBlockSystemFee()/2+1) txes[i].ValidUntilBlock = 1 addSender(t, txes[i]) - signTx(t, srv.Chain.FeePerByte(), txes[i]) + signTx(t, srv.Chain, txes[i]) } b := testchain.NewBlock(t, srv.Chain, 1, 0, txes...) require.False(t, srv.verifyBlock(&neoBlock{Block: *b})) @@ -500,7 +501,7 @@ func addSender(t *testing.T, txs ...*transaction.Transaction) { } } -func signTx(t *testing.T, feePerByte int64, txs ...*transaction.Transaction) { +func signTx(t *testing.T, bc blockchainer.Blockchainer, txs ...*transaction.Transaction) { validators := make([]*keys.PublicKey, 4) privNetKeys := make([]*keys.PrivateKey, 4) for i := 0; i < 4; i++ { @@ -512,10 +513,10 @@ func signTx(t *testing.T, feePerByte int64, txs ...*transaction.Transaction) { require.NoError(t, err) for _, tx := range txs { size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetPolicer().GetBaseExecFee(), rawScript) tx.NetworkFee += +netFee size += sizeDelta - tx.NetworkFee += int64(size) * feePerByte + tx.NetworkFee += int64(size) * bc.FeePerByte() data := tx.GetSignedPart() buf := io.NewBufBinWriter() diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 443f8609c..8098eef19 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -620,7 +620,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx) v := systemInterop.SpawnVM() v.LoadScriptWithFlags(tx.Script, smartcontract.All) - v.SetPriceGetter(getPrice) + v.SetPriceGetter(bc.getPrice) v.GasLimit = tx.SystemFee err := v.Run() @@ -753,7 +753,7 @@ func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.C systemInterop := bc.newInteropContext(trig, cache, block, nil) v := systemInterop.SpawnVM() v.LoadScriptWithFlags(script, smartcontract.WriteStates|smartcontract.AllowCall) - v.SetPriceGetter(getPrice) + v.SetPriceGetter(bc.getPrice) if err := v.Run(); err != nil { return nil, fmt.Errorf("VM has failed: %w", err) } else if _, err := systemInterop.DAO.Persist(); err != nil { @@ -1622,7 +1622,7 @@ func (bc *Blockchain) GetTestVM(tx *transaction.Transaction, b *block.Block) *vm d.MPT = nil systemInterop := bc.newInteropContext(trigger.Application, d, b, tx) vm := systemInterop.SpawnVM() - vm.SetPriceGetter(getPrice) + vm.SetPriceGetter(bc.getPrice) return vm } @@ -1692,7 +1692,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa } vm := interopCtx.SpawnVM() - vm.SetPriceGetter(getPrice) + vm.SetPriceGetter(bc.getPrice) vm.GasLimit = gas if err := bc.initVerificationVM(interopCtx, hash, witness); err != nil { return 0, err @@ -1813,6 +1813,11 @@ func (bc *Blockchain) GetPolicer() blockchainer.Policer { return bc } +// GetBaseExecFee return execution price for `NOP`. +func (bc *Blockchain) GetBaseExecFee() int64 { + return bc.contracts.Policy.GetExecFeeFactorInternal(bc.dao) +} + // GetMaxBlockSize returns maximum allowed block size from native Policy contract. func (bc *Blockchain) GetMaxBlockSize() uint32 { return bc.contracts.Policy.GetMaxBlockSizeInternal(bc.dao) @@ -1828,4 +1833,9 @@ func (bc *Blockchain) GetMaxVerificationGAS() int64 { return bc.contracts.Policy.GetMaxVerificationGas(bc.dao) } +// GetStoragePrice returns current storage price. +func (bc *Blockchain) GetStoragePrice() int64 { + return bc.contracts.Policy.GetStoragePriceInternal(bc.dao) +} + // -- end Policer. diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index b2d50d93d..8256ad16a 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -338,7 +338,7 @@ func TestVerifyTx(t *testing.T) { }) t.Run("AlmostEnoughNetworkFee", func(t *testing.T) { tx := bc.newTestTx(h, testScript) - verificationNetFee, calcultedScriptSize := fee.Calculate(accs[0].Contract.Script) + verificationNetFee, calcultedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) expectedSize := io.GetVarSize(tx) + calcultedScriptSize calculatedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() tx.NetworkFee = calculatedNetFee - 1 @@ -348,7 +348,7 @@ func TestVerifyTx(t *testing.T) { }) t.Run("EnoughNetworkFee", func(t *testing.T) { tx := bc.newTestTx(h, testScript) - verificationNetFee, calcultedScriptSize := fee.Calculate(accs[0].Contract.Script) + verificationNetFee, calcultedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) expectedSize := io.GetVarSize(tx) + calcultedScriptSize calculatedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() tx.NetworkFee = calculatedNetFee @@ -359,7 +359,7 @@ func TestVerifyTx(t *testing.T) { t.Run("CalculateNetworkFee, signature script", func(t *testing.T) { tx := bc.newTestTx(h, testScript) expectedSize := io.GetVarSize(tx) - verificationNetFee, calculatedScriptSize := fee.Calculate(accs[0].Contract.Script) + verificationNetFee, calculatedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) expectedSize += calculatedScriptSize expectedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() tx.NetworkFee = expectedNetFee @@ -378,7 +378,7 @@ func TestVerifyTx(t *testing.T) { require.NoError(t, multisigAcc.ConvertMultisig(1, pKeys)) multisigHash := hash.Hash160(multisigAcc.Contract.Script) tx := bc.newTestTx(multisigHash, testScript) - verificationNetFee, calculatedScriptSize := fee.Calculate(multisigAcc.Contract.Script) + verificationNetFee, calculatedScriptSize := fee.Calculate(bc.GetBaseExecFee(), multisigAcc.Contract.Script) expectedSize := io.GetVarSize(tx) + calculatedScriptSize expectedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() tx.NetworkFee = expectedNetFee @@ -478,7 +478,7 @@ func TestVerifyTx(t *testing.T) { rawScript := testchain.CommitteeVerificationScript() require.NoError(t, err) size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() data := tx.GetSignedPart() @@ -517,7 +517,7 @@ func TestVerifyTx(t *testing.T) { Scopes: transaction.None, }} size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(oracleScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), oracleScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() return tx @@ -614,7 +614,7 @@ func TestVerifyTx(t *testing.T) { rawScript := testchain.CommitteeVerificationScript() require.NoError(t, err) size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() data := tx.GetSignedPart() @@ -652,7 +652,7 @@ func TestVerifyTx(t *testing.T) { rawScript := testchain.CommitteeVerificationScript() require.NoError(t, err) size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() data := tx.GetSignedPart() @@ -692,7 +692,7 @@ func TestVerifyTx(t *testing.T) { rawScript := testchain.CommitteeVerificationScript() require.NoError(t, err) size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() data := tx.GetSignedPart() @@ -772,7 +772,7 @@ func TestVerifyTx(t *testing.T) { } rawScript := testchain.CommitteeVerificationScript() size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() data := tx.GetSignedPart() @@ -964,14 +964,14 @@ func TestVerifyTx(t *testing.T) { }, } size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(testchain.MultisigVerificationScript()) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), testchain.MultisigVerificationScript()) tx.NetworkFee = netFee + // multisig witness verification price int64(size)*bc.FeePerByte() + // fee for unsigned size int64(sizeDelta)*bc.FeePerByte() + //fee for multisig size 66*bc.FeePerByte() + // fee for Notary signature size (66 bytes for Invocation script and 0 bytes for Verification script) 2*bc.FeePerByte() + // fee for the length of each script in Notary witness (they are nil, so we did not take them into account during `size` calculation) transaction.NotaryServiceFeePerKey + // fee for Notary attribute - fee.Opcode( // Notary verification script + fee.Opcode(bc.GetBaseExecFee(), // Notary verification script opcode.PUSHDATA1, opcode.RET, // invocation script opcode.DEPTH, opcode.PACK, opcode.PUSHDATA1, opcode.RET, // arguments for native verification call opcode.PUSHDATA1, opcode.SYSCALL, opcode.RET) + // Neo.Native.Call diff --git a/pkg/core/blockchainer/policer.go b/pkg/core/blockchainer/policer.go index 56888de1d..abfe4aa02 100644 --- a/pkg/core/blockchainer/policer.go +++ b/pkg/core/blockchainer/policer.go @@ -2,7 +2,9 @@ package blockchainer // Policer is an interface that abstracts the implementation of policy methods. type Policer interface { + GetBaseExecFee() int64 GetMaxBlockSize() uint32 GetMaxBlockSystemFee() int64 GetMaxVerificationGAS() int64 + GetStoragePrice() int64 } diff --git a/pkg/core/fee/calculate.go b/pkg/core/fee/calculate.go index 38ad41a22..0cf4e6383 100644 --- a/pkg/core/fee/calculate.go +++ b/pkg/core/fee/calculate.go @@ -9,31 +9,31 @@ import ( ) // Calculate returns network fee for transaction -func Calculate(script []byte) (int64, int) { +func Calculate(base int64, script []byte) (int64, int) { var ( netFee int64 size int ) if vm.IsSignatureContract(script) { size += 67 + io.GetVarSize(script) - netFee += Opcode(opcode.PUSHDATA1, opcode.PUSHNULL, opcode.PUSHDATA1) + crypto.ECDSAVerifyPrice + netFee += Opcode(base, opcode.PUSHDATA1, opcode.PUSHNULL, opcode.PUSHDATA1) + base*crypto.ECDSAVerifyPrice } else if m, pubs, ok := vm.ParseMultiSigContract(script); ok { n := len(pubs) sizeInv := 66 * m size += io.GetVarSize(sizeInv) + sizeInv + io.GetVarSize(script) - netFee += calculateMultisig(m) + calculateMultisig(n) - netFee += Opcode(opcode.PUSHNULL) + crypto.ECDSAVerifyPrice*int64(n) + netFee += calculateMultisig(base, m) + calculateMultisig(base, n) + netFee += Opcode(base, opcode.PUSHNULL) + base*crypto.ECDSAVerifyPrice*int64(n) } else { // We can support more contract types in the future. } return netFee, size } -func calculateMultisig(n int) int64 { - result := Opcode(opcode.PUSHDATA1) * int64(n) +func calculateMultisig(base int64, n int) int64 { + result := Opcode(base, opcode.PUSHDATA1) * int64(n) bw := io.NewBufBinWriter() emit.Int(bw.BinWriter, int64(n)) - // it's a hack because prices of small PUSH* opcodes are equal - result += Opcode(opcode.Opcode(bw.Bytes()[0])) + // it's a hack because coefficients of small PUSH* opcodes are equal + result += Opcode(base, opcode.Opcode(bw.Bytes()[0])) return result } diff --git a/pkg/core/fee/opcode.go b/pkg/core/fee/opcode.go index af8604993..822a229ad 100644 --- a/pkg/core/fee/opcode.go +++ b/pkg/core/fee/opcode.go @@ -1,199 +1,201 @@ package fee -import "github.com/nspcc-dev/neo-go/pkg/vm/opcode" +import ( + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" +) -// Opcode returns the deployment prices of specified opcodes. -func Opcode(opcodes ...opcode.Opcode) int64 { +// Opcode returns the deployment coefficients of specified opcodes. +func Opcode(base int64, opcodes ...opcode.Opcode) int64 { var result int64 for _, op := range opcodes { - result += prices[op] + result += coefficients[op] } - return result + return result * base } -var prices = map[opcode.Opcode]int64{ - opcode.PUSHINT8: 30, - opcode.PUSHINT32: 30, - opcode.PUSHINT64: 30, - opcode.PUSHINT16: 30, - opcode.PUSHINT128: 120, - opcode.PUSHINT256: 120, - opcode.PUSHA: 120, - opcode.PUSHNULL: 30, - opcode.PUSHDATA1: 180, - opcode.PUSHDATA2: 13000, - opcode.PUSHDATA4: 110000, - opcode.PUSHM1: 30, - opcode.PUSH0: 30, - opcode.PUSH1: 30, - opcode.PUSH2: 30, - opcode.PUSH3: 30, - opcode.PUSH4: 30, - opcode.PUSH5: 30, - opcode.PUSH6: 30, - opcode.PUSH7: 30, - opcode.PUSH8: 30, - opcode.PUSH9: 30, - opcode.PUSH10: 30, - opcode.PUSH11: 30, - opcode.PUSH12: 30, - opcode.PUSH13: 30, - opcode.PUSH14: 30, - opcode.PUSH15: 30, - opcode.PUSH16: 30, - opcode.NOP: 30, - opcode.JMP: 70, - opcode.JMPL: 70, - opcode.JMPIF: 70, - opcode.JMPIFL: 70, - opcode.JMPIFNOT: 70, - opcode.JMPIFNOTL: 70, - opcode.JMPEQ: 70, - opcode.JMPEQL: 70, - opcode.JMPNE: 70, - opcode.JMPNEL: 70, - opcode.JMPGT: 70, - opcode.JMPGTL: 70, - opcode.JMPGE: 70, - opcode.JMPGEL: 70, - opcode.JMPLT: 70, - opcode.JMPLTL: 70, - opcode.JMPLE: 70, - opcode.JMPLEL: 70, - opcode.CALL: 22000, - opcode.CALLL: 22000, - opcode.CALLA: 22000, - opcode.ABORT: 30, - opcode.ASSERT: 30, - opcode.THROW: 22000, - opcode.TRY: 100, - opcode.TRYL: 100, - opcode.ENDTRY: 100, - opcode.ENDTRYL: 100, - opcode.ENDFINALLY: 100, +var coefficients = map[opcode.Opcode]int64{ + opcode.PUSHINT8: 1 << 0, + opcode.PUSHINT16: 1 << 0, + opcode.PUSHINT32: 1 << 0, + opcode.PUSHINT64: 1 << 0, + opcode.PUSHINT128: 1 << 2, + opcode.PUSHINT256: 1 << 2, + opcode.PUSHA: 1 << 2, + opcode.PUSHNULL: 1 << 0, + opcode.PUSHDATA1: 1 << 3, + opcode.PUSHDATA2: 1 << 9, + opcode.PUSHDATA4: 1 << 12, + opcode.PUSHM1: 1 << 0, + opcode.PUSH0: 1 << 0, + opcode.PUSH1: 1 << 0, + opcode.PUSH2: 1 << 0, + opcode.PUSH3: 1 << 0, + opcode.PUSH4: 1 << 0, + opcode.PUSH5: 1 << 0, + opcode.PUSH6: 1 << 0, + opcode.PUSH7: 1 << 0, + opcode.PUSH8: 1 << 0, + opcode.PUSH9: 1 << 0, + opcode.PUSH10: 1 << 0, + opcode.PUSH11: 1 << 0, + opcode.PUSH12: 1 << 0, + opcode.PUSH13: 1 << 0, + opcode.PUSH14: 1 << 0, + opcode.PUSH15: 1 << 0, + opcode.PUSH16: 1 << 0, + opcode.NOP: 1 << 0, + opcode.JMP: 1 << 1, + opcode.JMPL: 1 << 1, + opcode.JMPIF: 1 << 1, + opcode.JMPIFL: 1 << 1, + opcode.JMPIFNOT: 1 << 1, + opcode.JMPIFNOTL: 1 << 1, + opcode.JMPEQ: 1 << 1, + opcode.JMPEQL: 1 << 1, + opcode.JMPNE: 1 << 1, + opcode.JMPNEL: 1 << 1, + opcode.JMPGT: 1 << 1, + opcode.JMPGTL: 1 << 1, + opcode.JMPGE: 1 << 1, + opcode.JMPGEL: 1 << 1, + opcode.JMPLT: 1 << 1, + opcode.JMPLTL: 1 << 1, + opcode.JMPLE: 1 << 1, + opcode.JMPLEL: 1 << 1, + opcode.CALL: 1 << 9, + opcode.CALLL: 1 << 9, + opcode.CALLA: 1 << 9, + opcode.ABORT: 0, + opcode.ASSERT: 1 << 0, + opcode.THROW: 1 << 9, + opcode.TRY: 1 << 2, + opcode.TRYL: 1 << 2, + opcode.ENDTRY: 1 << 2, + opcode.ENDTRYL: 1 << 2, + opcode.ENDFINALLY: 1 << 2, opcode.RET: 0, opcode.SYSCALL: 0, - opcode.DEPTH: 60, - opcode.DROP: 60, - opcode.NIP: 60, - opcode.XDROP: 400, - opcode.CLEAR: 400, - opcode.DUP: 60, - opcode.OVER: 60, - opcode.PICK: 60, - opcode.TUCK: 60, - opcode.SWAP: 60, - opcode.ROT: 60, - opcode.ROLL: 400, - opcode.REVERSE3: 60, - opcode.REVERSE4: 60, - opcode.REVERSEN: 400, - opcode.INITSSLOT: 400, - opcode.INITSLOT: 1600, - opcode.LDSFLD0: 60, - opcode.LDSFLD1: 60, - opcode.LDSFLD2: 60, - opcode.LDSFLD3: 60, - opcode.LDSFLD4: 60, - opcode.LDSFLD5: 60, - opcode.LDSFLD6: 60, - opcode.LDSFLD: 60, - opcode.STSFLD0: 60, - opcode.STSFLD1: 60, - opcode.STSFLD2: 60, - opcode.STSFLD3: 60, - opcode.STSFLD4: 60, - opcode.STSFLD5: 60, - opcode.STSFLD6: 60, - opcode.STSFLD: 60, - opcode.LDLOC0: 60, - opcode.LDLOC1: 60, - opcode.LDLOC2: 60, - opcode.LDLOC3: 60, - opcode.LDLOC4: 60, - opcode.LDLOC5: 60, - opcode.LDLOC6: 60, - opcode.LDLOC: 60, - opcode.STLOC0: 60, - opcode.STLOC1: 60, - opcode.STLOC2: 60, - opcode.STLOC3: 60, - opcode.STLOC4: 60, - opcode.STLOC5: 60, - opcode.STLOC6: 60, - opcode.STLOC: 60, - opcode.LDARG0: 60, - opcode.LDARG1: 60, - opcode.LDARG2: 60, - opcode.LDARG3: 60, - opcode.LDARG4: 60, - opcode.LDARG5: 60, - opcode.LDARG6: 60, - opcode.LDARG: 60, - opcode.STARG0: 60, - opcode.STARG1: 60, - opcode.STARG2: 60, - opcode.STARG3: 60, - opcode.STARG4: 60, - opcode.STARG5: 60, - opcode.STARG6: 60, - opcode.STARG: 60, - opcode.NEWBUFFER: 80000, - opcode.MEMCPY: 80000, - opcode.CAT: 80000, - opcode.SUBSTR: 80000, - opcode.LEFT: 80000, - opcode.RIGHT: 80000, - opcode.INVERT: 100, - opcode.AND: 200, - opcode.OR: 200, - opcode.XOR: 200, - opcode.EQUAL: 1000, - opcode.NOTEQUAL: 1000, - opcode.SIGN: 100, - opcode.ABS: 100, - opcode.NEGATE: 100, - opcode.INC: 100, - opcode.DEC: 100, - opcode.ADD: 200, - opcode.SUB: 200, - opcode.MUL: 300, - opcode.DIV: 300, - opcode.MOD: 300, - opcode.SHL: 300, - opcode.SHR: 300, - opcode.NOT: 100, - opcode.BOOLAND: 200, - opcode.BOOLOR: 200, - opcode.NZ: 100, - opcode.NUMEQUAL: 200, - opcode.NUMNOTEQUAL: 200, - opcode.LT: 200, - opcode.LTE: 200, - opcode.GT: 200, - opcode.GTE: 200, - opcode.MIN: 200, - opcode.MAX: 200, - opcode.WITHIN: 200, - opcode.PACK: 15000, - opcode.UNPACK: 15000, - opcode.NEWARRAY0: 400, - opcode.NEWARRAY: 15000, - opcode.NEWARRAYT: 15000, - opcode.NEWSTRUCT0: 400, - opcode.NEWSTRUCT: 15000, - opcode.NEWMAP: 200, - opcode.SIZE: 150, - opcode.HASKEY: 270000, - opcode.KEYS: 500, - opcode.VALUES: 270000, - opcode.PICKITEM: 270000, - opcode.APPEND: 270000, - opcode.SETITEM: 270000, - opcode.REVERSEITEMS: 270000, - opcode.REMOVE: 500, - opcode.CLEARITEMS: 400, - opcode.ISNULL: 60, - opcode.ISTYPE: 60, - opcode.CONVERT: 80000, + opcode.DEPTH: 1 << 1, + opcode.DROP: 1 << 1, + opcode.NIP: 1 << 1, + opcode.XDROP: 1 << 4, + opcode.CLEAR: 1 << 4, + opcode.DUP: 1 << 1, + opcode.OVER: 1 << 1, + opcode.PICK: 1 << 1, + opcode.TUCK: 1 << 1, + opcode.SWAP: 1 << 1, + opcode.ROT: 1 << 1, + opcode.ROLL: 1 << 4, + opcode.REVERSE3: 1 << 1, + opcode.REVERSE4: 1 << 1, + opcode.REVERSEN: 1 << 4, + opcode.INITSSLOT: 1 << 4, + opcode.INITSLOT: 1 << 6, + opcode.LDSFLD0: 1 << 1, + opcode.LDSFLD1: 1 << 1, + opcode.LDSFLD2: 1 << 1, + opcode.LDSFLD3: 1 << 1, + opcode.LDSFLD4: 1 << 1, + opcode.LDSFLD5: 1 << 1, + opcode.LDSFLD6: 1 << 1, + opcode.LDSFLD: 1 << 1, + opcode.STSFLD0: 1 << 1, + opcode.STSFLD1: 1 << 1, + opcode.STSFLD2: 1 << 1, + opcode.STSFLD3: 1 << 1, + opcode.STSFLD4: 1 << 1, + opcode.STSFLD5: 1 << 1, + opcode.STSFLD6: 1 << 1, + opcode.STSFLD: 1 << 1, + opcode.LDLOC0: 1 << 1, + opcode.LDLOC1: 1 << 1, + opcode.LDLOC2: 1 << 1, + opcode.LDLOC3: 1 << 1, + opcode.LDLOC4: 1 << 1, + opcode.LDLOC5: 1 << 1, + opcode.LDLOC6: 1 << 1, + opcode.LDLOC: 1 << 1, + opcode.STLOC0: 1 << 1, + opcode.STLOC1: 1 << 1, + opcode.STLOC2: 1 << 1, + opcode.STLOC3: 1 << 1, + opcode.STLOC4: 1 << 1, + opcode.STLOC5: 1 << 1, + opcode.STLOC6: 1 << 1, + opcode.STLOC: 1 << 1, + opcode.LDARG0: 1 << 1, + opcode.LDARG1: 1 << 1, + opcode.LDARG2: 1 << 1, + opcode.LDARG3: 1 << 1, + opcode.LDARG4: 1 << 1, + opcode.LDARG5: 1 << 1, + opcode.LDARG6: 1 << 1, + opcode.LDARG: 1 << 1, + opcode.STARG0: 1 << 1, + opcode.STARG1: 1 << 1, + opcode.STARG2: 1 << 1, + opcode.STARG3: 1 << 1, + opcode.STARG4: 1 << 1, + opcode.STARG5: 1 << 1, + opcode.STARG6: 1 << 1, + opcode.STARG: 1 << 1, + opcode.NEWBUFFER: 1 << 8, + opcode.MEMCPY: 1 << 11, + opcode.CAT: 1 << 11, + opcode.SUBSTR: 1 << 11, + opcode.LEFT: 1 << 11, + opcode.RIGHT: 1 << 11, + opcode.INVERT: 1 << 2, + opcode.AND: 1 << 3, + opcode.OR: 1 << 3, + opcode.XOR: 1 << 3, + opcode.EQUAL: 1 << 5, + opcode.NOTEQUAL: 1 << 5, + opcode.SIGN: 1 << 2, + opcode.ABS: 1 << 2, + opcode.NEGATE: 1 << 2, + opcode.INC: 1 << 2, + opcode.DEC: 1 << 2, + opcode.ADD: 1 << 3, + opcode.SUB: 1 << 3, + opcode.MUL: 1 << 3, + opcode.DIV: 1 << 3, + opcode.MOD: 1 << 3, + opcode.SHL: 1 << 3, + opcode.SHR: 1 << 3, + opcode.NOT: 1 << 2, + opcode.BOOLAND: 1 << 3, + opcode.BOOLOR: 1 << 3, + opcode.NZ: 1 << 2, + opcode.NUMEQUAL: 1 << 3, + opcode.NUMNOTEQUAL: 1 << 3, + opcode.LT: 1 << 3, + opcode.LTE: 1 << 3, + opcode.GT: 1 << 3, + opcode.GTE: 1 << 3, + opcode.MIN: 1 << 3, + opcode.MAX: 1 << 3, + opcode.WITHIN: 1 << 3, + opcode.PACK: 1 << 9, + opcode.UNPACK: 1 << 9, + opcode.NEWARRAY0: 1 << 4, + opcode.NEWARRAY: 1 << 9, + opcode.NEWARRAYT: 1 << 9, + opcode.NEWSTRUCT0: 1 << 4, + opcode.NEWSTRUCT: 1 << 9, + opcode.NEWMAP: 1 << 3, + opcode.SIZE: 1 << 2, + opcode.HASKEY: 1 << 6, + opcode.KEYS: 1 << 4, + opcode.VALUES: 1 << 13, + opcode.PICKITEM: 1 << 6, + opcode.APPEND: 1 << 13, + opcode.SETITEM: 1 << 13, + opcode.REVERSEITEMS: 1 << 13, + opcode.REMOVE: 1 << 4, + opcode.CLEARITEMS: 1 << 4, + opcode.ISNULL: 1 << 1, + opcode.ISTYPE: 1 << 1, + opcode.CONVERT: 1 << 11, } diff --git a/pkg/core/gas_price.go b/pkg/core/gas_price.go index b5ead6426..406e1e65a 100644 --- a/pkg/core/gas_price.go +++ b/pkg/core/gas_price.go @@ -7,6 +7,6 @@ import ( ) // getPrice returns a price for executing op with the provided parameter. -func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) int64 { - return fee.Opcode(op) +func (bc *Blockchain) getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) int64 { + return fee.Opcode(bc.GetBaseExecFee(), op) } diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 5f0738e30..003fea1f4 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -398,13 +398,13 @@ func addSigners(txs ...*transaction.Transaction) { func addNetworkFee(bc *Blockchain, tx *transaction.Transaction, sender *wallet.Account) error { size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(sender.Contract.Script) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), sender.Contract.Script) tx.NetworkFee += netFee size += sizeDelta for _, cosigner := range tx.Signers { contract := bc.GetContractState(cosigner.Account) if contract != nil { - netFee, sizeDelta = fee.Calculate(contract.Script) + netFee, sizeDelta = fee.Calculate(bc.GetBaseExecFee(), contract.Script) tx.NetworkFee += netFee size += sizeDelta } @@ -413,7 +413,8 @@ func addNetworkFee(bc *Blockchain, tx *transaction.Transaction, sender *wallet.A return nil } -func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) { +func prepareContractMethodInvoke(chain *Blockchain, sysfee int64, + hash util.Uint160, method string, args ...interface{}) (*transaction.Transaction, error) { w := io.NewBufBinWriter() emit.AppCallWithOperationAndArgs(w.BinWriter, hash, method, args...) if w.Err != nil { @@ -427,17 +428,37 @@ func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, me if err != nil { return nil, err } - b := chain.newBlock(tx) - err = chain.AddBlock(b) + return tx, nil +} + +func persistBlock(chain *Blockchain, txs ...*transaction.Transaction) ([]*state.AppExecResult, error) { + b := chain.newBlock(txs...) + err := chain.AddBlock(b) if err != nil { return nil, err } - res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application) + aers := make([]*state.AppExecResult, len(txs)) + for i, tx := range txs { + res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application) + if err != nil { + return nil, err + } + aers[i] = &res[0] + } + return aers, nil +} + +func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) { + tx, err := prepareContractMethodInvoke(chain, sysfee, hash, method, args...) if err != nil { return nil, err } - return &res[0], nil + aers, err := persistBlock(chain, tx) + if err != nil { + return nil, err + } + return aers[0], nil } func invokeContractMethodBy(t *testing.T, chain *Blockchain, signer *wallet.Account, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) { diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 4322491b7..ee141cfdf 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -20,6 +20,11 @@ import ( "go.uber.org/zap" ) +const ( + // DefaultBaseExecFee specifies default multiplier for opcode and syscall prices. + DefaultBaseExecFee = 30 +) + // Context represents context in which interops are executed. type Context struct { Chain blockchainer.Blockchainer @@ -155,6 +160,14 @@ func (ic *Context) GetFunction(id uint32) *Function { return nil } +// BaseExecFee represents factor to multiply syscall prices with. +func (ic *Context) BaseExecFee() int64 { + if ic.Chain == nil { + return DefaultBaseExecFee + } + return ic.Chain.GetPolicer().GetBaseExecFee() +} + // SyscallHandler handles syscall with id. func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error { f := ic.GetFunction(id) @@ -165,7 +178,7 @@ func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error { if !cf.Has(f.RequiredFlags) { return fmt.Errorf("missing call flags: %05b vs %05b", cf, f.RequiredFlags) } - if !ic.VM.AddGas(f.Price) { + if !ic.VM.AddGas(f.Price * ic.BaseExecFee()) { return errors.New("insufficient amount of gas") } return f.Func(ic) diff --git a/pkg/core/interop/crypto/ecdsa.go b/pkg/core/interop/crypto/ecdsa.go index 5f79e02ad..3e27810af 100644 --- a/pkg/core/interop/crypto/ecdsa.go +++ b/pkg/core/interop/crypto/ecdsa.go @@ -16,7 +16,7 @@ import ( ) // ECDSAVerifyPrice is a gas price of a single verification. -const ECDSAVerifyPrice = 1000000 +const ECDSAVerifyPrice = 1 << 15 // ECDSASecp256r1Verify checks ECDSA signature using Secp256r1 elliptic curve. func ECDSASecp256r1Verify(ic *interop.Context) error { @@ -69,7 +69,7 @@ func ecdsaCheckMultisig(ic *interop.Context, curve elliptic.Curve) error { if err != nil { return fmt.Errorf("wrong parameters: %w", err) } - if !ic.VM.AddGas(ECDSAVerifyPrice * int64(len(pkeys))) { + if !ic.VM.AddGas(ic.BaseExecFee() * ECDSAVerifyPrice * int64(len(pkeys))) { return errors.New("gas limit exceeded") } sigs, err := ic.VM.Estack().PopSigElements() diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index e455f482c..1edeb9012 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -11,7 +11,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/dao" "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/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -201,6 +200,7 @@ func storageDelete(ic *interop.Context) error { if stc.ReadOnly { return errors.New("StorageContext is read only") } + ic.VM.AddGas(ic.Chain.GetPolicer().GetStoragePrice()) key := ic.VM.Estack().Pop().Bytes() si := ic.DAO.GetStorageItem(stc.ID, key) if si != nil && si.IsConst { @@ -276,7 +276,7 @@ func putWithContextAndFlags(ic *interop.Context, stc *StorageContext, key []byte sizeInc = (len(si.Value)-1)/4 + 1 + len(value) - len(si.Value) } } - if !ic.VM.AddGas(int64(sizeInc) * native.StoragePrice) { + if !ic.VM.AddGas(int64(sizeInc) * ic.Chain.GetPolicer().GetStoragePrice()) { return errGasLimitExceeded } si.Value = value diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 668fdb956..4e061ac8c 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -33,80 +33,80 @@ func SpawnVM(ic *interop.Context) *vm.VM { // All lists are sorted, keep 'em this way, please. var systemInterops = []interop.Function{ - {Name: interopnames.SystemBinaryAtoi, Func: binary.Atoi, Price: 100000, ParamCount: 2}, - {Name: interopnames.SystemBinaryBase58Decode, Func: binary.DecodeBase58, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase58Encode, Func: binary.EncodeBase58, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase64Decode, Func: binary.DecodeBase64, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase64Encode, Func: binary.EncodeBase64, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 500000, ParamCount: 1}, - {Name: interopnames.SystemBinaryItoa, Func: binary.Itoa, Price: 100000, ParamCount: 2}, - {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 2500000, + {Name: interopnames.SystemBinaryAtoi, Func: binary.Atoi, Price: 1 << 12, ParamCount: 2}, + {Name: interopnames.SystemBinaryBase58Decode, Func: binary.DecodeBase58, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemBinaryBase58Encode, Func: binary.EncodeBase58, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemBinaryBase64Decode, Func: binary.DecodeBase64, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemBinaryBase64Encode, Func: binary.EncodeBase64, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 1 << 14, ParamCount: 1}, + {Name: interopnames.SystemBinaryItoa, Func: binary.Itoa, Price: 1 << 12, ParamCount: 2}, + {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 1 << 16, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetHeight, Func: bcGetHeight, Price: 400, + {Name: interopnames.SystemBlockchainGetHeight, Func: bcGetHeight, Price: 1 << 4, RequiredFlags: smartcontract.ReadStates}, - {Name: interopnames.SystemBlockchainGetTransaction, Func: bcGetTransaction, Price: 1000000, + {Name: interopnames.SystemBlockchainGetTransaction, Func: bcGetTransaction, Price: 1 << 15, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetTransactionFromBlock, Func: bcGetTransactionFromBlock, Price: 1000000, + {Name: interopnames.SystemBlockchainGetTransactionFromBlock, Func: bcGetTransactionFromBlock, Price: 1 << 15, RequiredFlags: smartcontract.ReadStates, ParamCount: 2}, - {Name: interopnames.SystemBlockchainGetTransactionHeight, Func: bcGetTransactionHeight, Price: 1000000, + {Name: interopnames.SystemBlockchainGetTransactionHeight, Func: bcGetTransactionHeight, Price: 1 << 15, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemCallbackCreate, Func: callback.Create, Price: 400, ParamCount: 3, DisallowCallback: true}, - {Name: interopnames.SystemCallbackCreateFromMethod, Func: callback.CreateFromMethod, Price: 1000000, RequiredFlags: smartcontract.ReadStates, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemCallbackCreateFromSyscall, Func: callback.CreateFromSyscall, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemCallbackInvoke, Func: callback.Invoke, Price: 1000000, RequiredFlags: smartcontract.AllowCall, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1000000, + {Name: interopnames.SystemCallbackCreate, Func: callback.Create, Price: 1 << 4, ParamCount: 3, DisallowCallback: true}, + {Name: interopnames.SystemCallbackCreateFromMethod, Func: callback.CreateFromMethod, Price: 1 << 15, ParamCount: 2, DisallowCallback: true}, + {Name: interopnames.SystemCallbackCreateFromSyscall, Func: callback.CreateFromSyscall, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemCallbackInvoke, Func: callback.Invoke, Price: 1 << 15, ParamCount: 2, DisallowCallback: true}, + {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15, RequiredFlags: smartcontract.AllowCall, ParamCount: 3, DisallowCallback: true}, - {Name: interopnames.SystemContractCallEx, Func: contract.CallEx, Price: 1000000, + {Name: interopnames.SystemContractCallEx, Func: contract.CallEx, Price: 1 << 15, RequiredFlags: smartcontract.AllowCall, ParamCount: 4, DisallowCallback: true}, {Name: interopnames.SystemContractCallNative, Func: native.Call, Price: 0, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemContractCreateStandardAccount, Func: contractCreateStandardAccount, Price: 10000, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemContractIsStandard, Func: contractIsStandard, Price: 30000, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 30000, DisallowCallback: true}, + {Name: interopnames.SystemContractCreateStandardAccount, Func: contractCreateStandardAccount, Price: 1 << 8, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemContractIsStandard, Func: contractIsStandard, Price: 1 << 10, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, + {Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 1 << 10, DisallowCallback: true}, {Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, RequiredFlags: smartcontract.WriteStates, DisallowCallback: true}, {Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, RequiredFlags: smartcontract.WriteStates, DisallowCallback: true}, - {Name: interopnames.SystemEnumeratorConcat, Func: enumerator.Concat, Price: 400, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemEnumeratorCreate, Func: enumerator.Create, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemEnumeratorNext, Func: enumerator.Next, Price: 1000000, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemEnumeratorValue, Func: enumerator.Value, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemIteratorConcat, Func: iterator.Concat, Price: 400, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemIteratorCreate, Func: iterator.Create, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemIteratorKey, Func: iterator.Key, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemIteratorKeys, Func: iterator.Keys, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemIteratorValues, Func: iterator.Values, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemJSONDeserialize, Func: json.Deserialize, Price: 500000, ParamCount: 1}, - {Name: interopnames.SystemJSONSerialize, Func: json.Serialize, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 30000, + {Name: interopnames.SystemEnumeratorConcat, Func: enumerator.Concat, Price: 1 << 4, ParamCount: 2, DisallowCallback: true}, + {Name: interopnames.SystemEnumeratorCreate, Func: enumerator.Create, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemEnumeratorNext, Func: enumerator.Next, Price: 1 << 15, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemEnumeratorValue, Func: enumerator.Value, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemIteratorConcat, Func: iterator.Concat, Price: 1 << 4, ParamCount: 2, DisallowCallback: true}, + {Name: interopnames.SystemIteratorCreate, Func: iterator.Create, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemIteratorKey, Func: iterator.Key, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemIteratorKeys, Func: iterator.Keys, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemIteratorValues, Func: iterator.Values, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemJSONDeserialize, Func: json.Deserialize, Price: 1 << 14, ParamCount: 1}, + {Name: interopnames.SystemJSONSerialize, Func: json.Serialize, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 1 << 10, RequiredFlags: smartcontract.NoneFlag, ParamCount: 1}, - {Name: interopnames.SystemRuntimeGasLeft, Func: runtime.GasLeft, Price: 400}, - {Name: interopnames.SystemRuntimeGetCallingScriptHash, Func: runtime.GetCallingScriptHash, Price: 400}, - {Name: interopnames.SystemRuntimeGetEntryScriptHash, Func: runtime.GetEntryScriptHash, Price: 400}, - {Name: interopnames.SystemRuntimeGetExecutingScriptHash, Func: runtime.GetExecutingScriptHash, Price: 400}, - {Name: interopnames.SystemRuntimeGetInvocationCounter, Func: runtime.GetInvocationCounter, Price: 400}, - {Name: interopnames.SystemRuntimeGetNotifications, Func: runtime.GetNotifications, Price: 10000, ParamCount: 1}, - {Name: interopnames.SystemRuntimeGetScriptContainer, Func: engineGetScriptContainer, Price: 250}, - {Name: interopnames.SystemRuntimeGetTime, Func: runtime.GetTime, Price: 250}, - {Name: interopnames.SystemRuntimeGetTrigger, Func: runtime.GetTrigger, Price: 250}, - {Name: interopnames.SystemRuntimeLog, Func: runtime.Log, Price: 1000000, RequiredFlags: smartcontract.AllowNotify, + {Name: interopnames.SystemRuntimeGasLeft, Func: runtime.GasLeft, Price: 1 << 4}, + {Name: interopnames.SystemRuntimeGetCallingScriptHash, Func: runtime.GetCallingScriptHash, Price: 1 << 4}, + {Name: interopnames.SystemRuntimeGetEntryScriptHash, Func: runtime.GetEntryScriptHash, Price: 1 << 4}, + {Name: interopnames.SystemRuntimeGetExecutingScriptHash, Func: runtime.GetExecutingScriptHash, Price: 1 << 4}, + {Name: interopnames.SystemRuntimeGetInvocationCounter, Func: runtime.GetInvocationCounter, Price: 1 << 4}, + {Name: interopnames.SystemRuntimeGetNotifications, Func: runtime.GetNotifications, Price: 1 << 8, ParamCount: 1}, + {Name: interopnames.SystemRuntimeGetScriptContainer, Func: engineGetScriptContainer, Price: 1 << 3}, + {Name: interopnames.SystemRuntimeGetTime, Func: runtime.GetTime, Price: 1 << 3, RequiredFlags: smartcontract.ReadStates}, + {Name: interopnames.SystemRuntimeGetTrigger, Func: runtime.GetTrigger, Price: 1 << 3}, + {Name: interopnames.SystemRuntimeLog, Func: runtime.Log, Price: 1 << 15, RequiredFlags: smartcontract.AllowNotify, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemRuntimeNotify, Func: runtime.Notify, Price: 1000000, RequiredFlags: smartcontract.AllowNotify, + {Name: interopnames.SystemRuntimeNotify, Func: runtime.Notify, Price: 1 << 15, RequiredFlags: smartcontract.AllowNotify, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemRuntimePlatform, Func: runtime.Platform, Price: 250}, - {Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: native.StoragePrice, + {Name: interopnames.SystemRuntimePlatform, Func: runtime.Platform, Price: 1 << 3}, + {Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: 0, RequiredFlags: smartcontract.WriteStates, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemStorageFind, Func: storageFind, Price: 1000000, RequiredFlags: smartcontract.ReadStates, + {Name: interopnames.SystemStorageFind, Func: storageFind, Price: 1 << 15, RequiredFlags: smartcontract.ReadStates, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemStorageGet, Func: storageGet, Price: 1000000, RequiredFlags: smartcontract.ReadStates, + {Name: interopnames.SystemStorageGet, Func: storageGet, Price: 1 << 15, RequiredFlags: smartcontract.ReadStates, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemStorageGetContext, Func: storageGetContext, Price: 400, + {Name: interopnames.SystemStorageGetContext, Func: storageGetContext, Price: 1 << 4, RequiredFlags: smartcontract.ReadStates, DisallowCallback: true}, - {Name: interopnames.SystemStorageGetReadOnlyContext, Func: storageGetReadOnlyContext, Price: 400, + {Name: interopnames.SystemStorageGetReadOnlyContext, Func: storageGetReadOnlyContext, Price: 1 << 4, RequiredFlags: smartcontract.ReadStates, DisallowCallback: true}, {Name: interopnames.SystemStoragePut, Func: storagePut, Price: 0, RequiredFlags: smartcontract.WriteStates, ParamCount: 3, DisallowCallback: true}, // These don't have static price in C# code. {Name: interopnames.SystemStoragePutEx, Func: storagePutEx, Price: 0, RequiredFlags: smartcontract.WriteStates, ParamCount: 4, DisallowCallback: true}, - {Name: interopnames.SystemStorageAsReadOnly, Func: storageContextAsReadOnly, Price: 400, + {Name: interopnames.SystemStorageAsReadOnly, Func: storageContextAsReadOnly, Price: 1 << 4, RequiredFlags: smartcontract.ReadStates, ParamCount: 1, DisallowCallback: true}, } @@ -117,8 +117,8 @@ var neoInterops = []interop.Function{ Price: crypto.ECDSAVerifyPrice, ParamCount: 3}, {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0, ParamCount: 3}, {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0, ParamCount: 3}, - {Name: interopnames.NeoCryptoSHA256, Func: crypto.Sha256, Price: 1000000, ParamCount: 1}, - {Name: interopnames.NeoCryptoRIPEMD160, Func: crypto.RipeMD160, Price: 1000000, ParamCount: 1}, + {Name: interopnames.NeoCryptoSHA256, Func: crypto.Sha256, Price: 1 << 15, ParamCount: 1}, + {Name: interopnames.NeoCryptoRIPEMD160, Func: crypto.RipeMD160, Price: 1 << 15, ParamCount: 1}, } // initIDinInteropsSlice initializes IDs from names in one given diff --git a/pkg/core/mempool/mem_pool_test.go b/pkg/core/mempool/mem_pool_test.go index 64b9669ad..37847c99b 100644 --- a/pkg/core/mempool/mem_pool_test.go +++ b/pkg/core/mempool/mem_pool_test.go @@ -24,6 +24,10 @@ type FeerStub struct { balance int64 } +func (fs *FeerStub) GetBaseExecFee() int64 { + return 30 +} + func (fs *FeerStub) FeePerByte() int64 { return fs.feePerByte } diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 78737da08..ef5e434de 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -35,6 +35,7 @@ func Call(ic *interop.Context) error { if !ic.VM.Context().GetCallFlags().Has(m.RequiredFlags) { return fmt.Errorf("missing call flags for native %s `%s` operation call: %05b vs %05b", name, operation, ic.VM.Context().GetCallFlags(), m.RequiredFlags) } + // Native contract prices are not multiplied by `BaseExecFee`. if !ic.VM.AddGas(m.Price) { return errors.New("gas limit exceeded") } diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 04631c5e8..92be3e91f 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -119,7 +119,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(args []stackitem.Item, v *vm.VM) (*nef.File, *manifest.Manifest, error) { +func getNefAndManifestFromItems(ic *interop.Context, args []stackitem.Item) (*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) @@ -129,7 +129,7 @@ func getNefAndManifestFromItems(args []stackitem.Item, v *vm.VM) (*nef.File, *ma return nil, nil, fmt.Errorf("invalid manifest: %w", err) } - if !v.AddGas(int64(StoragePrice * (len(nefBytes) + len(manifestBytes)))) { + if !ic.VM.AddGas(ic.Chain.GetPolicer().GetStoragePrice() * int64(len(nefBytes)+len(manifestBytes))) { return nil, nil, errGasLimitExceeded } var resManifest *manifest.Manifest @@ -154,7 +154,7 @@ func getNefAndManifestFromItems(args []stackitem.Item, v *vm.VM) (*nef.File, *ma // 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(args, ic.VM) + neff, manif, err := getNefAndManifestFromItems(ic, args) if err != nil { panic(err) } @@ -208,7 +208,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(args, ic.VM) + neff, manif, err := getNefAndManifestFromItems(ic, args) if err != nil { panic(err) } diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 192aa7728..3c7411315 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -24,13 +24,18 @@ const ( defaultMaxBlockSize = 1024 * 256 defaultMaxTransactionsPerBlock = 512 + defaultExecFeeFactor = interop.DefaultBaseExecFee defaultFeePerByte = 1000 defaultMaxVerificationGas = 50000000 defaultMaxBlockSystemFee = 9000 * GASFactor // minBlockSystemFee is the minimum allowed system fee per block. minBlockSystemFee = 4007600 + // maxExecFeeFactor is the maximum allowed execution fee factor. + maxExecFeeFactor = 1000 // maxFeePerByte is the maximum allowed fee per byte value. maxFeePerByte = 100_000_000 + // maxStoragePrice is the maximum allowed price for a byte of storage. + maxStoragePrice = 10000000 // blockedAccountPrefix is a prefix used to store blocked account. blockedAccountPrefix = 15 @@ -40,6 +45,8 @@ var ( // maxTransactionsPerBlockKey is a key used to store the maximum number of // transactions allowed in block. maxTransactionsPerBlockKey = []byte{23} + // execFeeFactorKey is a key used to store execution fee factor. + execFeeFactorKey = []byte{18} // feePerByteKey is a key used to store the minimum fee per byte for // transaction. feePerByteKey = []byte{10} @@ -47,6 +54,8 @@ var ( maxBlockSizeKey = []byte{12} // maxBlockSystemFeeKey is a key used to store the maximum block system fee value. maxBlockSystemFeeKey = []byte{17} + // storagePriceKey is a key used to store storage price. + storagePriceKey = []byte{19} ) // Policy represents Policy native contract. @@ -59,9 +68,11 @@ type Policy struct { isValid bool maxTransactionsPerBlock uint32 maxBlockSize uint32 + execFeeFactor uint32 feePerByte int64 maxBlockSystemFee int64 maxVerificationGas int64 + storagePrice uint32 blockedAccounts []util.Uint160 } @@ -94,6 +105,24 @@ func newPolicy() *Policy { md = newMethodAndPrice(p.getMaxBlockSystemFee, 1000000, smartcontract.ReadStates) p.AddMethod(md, desc) + desc = newDescriptor("getExecFeeFactor", smartcontract.IntegerType) + md = newMethodAndPrice(p.getExecFeeFactor, 1000000, smartcontract.ReadStates) + p.AddMethod(md, desc) + + desc = newDescriptor("setExecFeeFactor", smartcontract.BoolType, + manifest.NewParameter("value", smartcontract.IntegerType)) + md = newMethodAndPrice(p.setExecFeeFactor, 3000000, smartcontract.WriteStates) + p.AddMethod(md, desc) + + desc = newDescriptor("getStoragePrice", smartcontract.IntegerType) + md = newMethodAndPrice(p.getStoragePrice, 1000000, smartcontract.ReadStates) + p.AddMethod(md, desc) + + desc = newDescriptor("setStoragePrice", smartcontract.BoolType, + manifest.NewParameter("value", smartcontract.IntegerType)) + md = newMethodAndPrice(p.setStoragePrice, 3000000, smartcontract.WriteStates) + p.AddMethod(md, desc) + desc = newDescriptor("setMaxBlockSize", smartcontract.BoolType, manifest.NewParameter("value", smartcontract.IntegerType)) md = newMethodAndPrice(p.setMaxBlockSize, 3000000, smartcontract.WriteStates) @@ -137,9 +166,11 @@ func (p *Policy) Initialize(ic *interop.Context) error { p.isValid = true p.maxTransactionsPerBlock = defaultMaxTransactionsPerBlock p.maxBlockSize = defaultMaxBlockSize + p.execFeeFactor = defaultExecFeeFactor p.feePerByte = defaultFeePerByte p.maxBlockSystemFee = defaultMaxBlockSystemFee p.maxVerificationGas = defaultMaxVerificationGas + p.storagePrice = StoragePrice p.blockedAccounts = make([]util.Uint160, 0) return nil @@ -160,9 +191,11 @@ func (p *Policy) PostPersist(ic *interop.Context) error { p.maxTransactionsPerBlock = getUint32WithKey(p.ContractID, ic.DAO, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock) p.maxBlockSize = getUint32WithKey(p.ContractID, ic.DAO, maxBlockSizeKey, defaultMaxBlockSize) + p.execFeeFactor = getUint32WithKey(p.ContractID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor) p.feePerByte = getInt64WithKey(p.ContractID, ic.DAO, feePerByteKey, defaultFeePerByte) p.maxBlockSystemFee = getInt64WithKey(p.ContractID, ic.DAO, maxBlockSystemFeeKey, defaultMaxBlockSystemFee) p.maxVerificationGas = defaultMaxVerificationGas + p.storagePrice = getUint32WithKey(p.ContractID, ic.DAO, storagePriceKey, StoragePrice) p.blockedAccounts = make([]util.Uint160, 0) siMap, err := ic.DAO.GetStorageItemsWithPrefix(p.ContractID, []byte{blockedAccountPrefix}) @@ -256,6 +289,41 @@ func (p *Policy) GetMaxBlockSystemFeeInternal(dao dao.DAO) int64 { return getInt64WithKey(p.ContractID, dao, maxBlockSystemFeeKey, defaultMaxBlockSystemFee) } +func (p *Policy) getExecFeeFactor(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.NewBigInteger(big.NewInt(int64(p.GetExecFeeFactorInternal(ic.DAO)))) +} + +// GetExecFeeFactorInternal returns current execution fee factor. +func (p *Policy) GetExecFeeFactorInternal(d dao.DAO) int64 { + p.lock.RLock() + defer p.lock.RUnlock() + if p.isValid { + return int64(p.execFeeFactor) + } + return int64(getUint32WithKey(p.ContractID, d, execFeeFactorKey, defaultExecFeeFactor)) +} + +func (p *Policy) setExecFeeFactor(ic *interop.Context, args []stackitem.Item) stackitem.Item { + value := toUint32(args[0]) + if value <= 0 || maxExecFeeFactor < value { + panic(fmt.Errorf("ExecFeeFactor must be between 0 and %d", maxExecFeeFactor)) + } + ok, err := checkValidators(ic) + if err != nil { + panic(err) + } else if !ok { + return stackitem.NewBool(false) + } + p.lock.Lock() + defer p.lock.Unlock() + err = setUint32WithKey(p.ContractID, ic.DAO, execFeeFactorKey, uint32(value)) + if err != nil { + panic(err) + } + p.isValid = false + return stackitem.NewBool(true) +} + // isBlocked is Policy contract method and checks whether provided account is blocked. func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem.Item { hash := toUint160(args[0]) @@ -280,6 +348,41 @@ func (p *Policy) IsBlockedInternal(dao dao.DAO, hash util.Uint160) bool { return dao.GetStorageItem(p.ContractID, key) != nil } +func (p *Policy) getStoragePrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.NewBigInteger(big.NewInt(p.GetStoragePriceInternal(ic.DAO))) +} + +// GetStoragePriceInternal returns current execution fee factor. +func (p *Policy) GetStoragePriceInternal(d dao.DAO) int64 { + p.lock.RLock() + defer p.lock.RUnlock() + if p.isValid { + return int64(p.storagePrice) + } + return int64(getUint32WithKey(p.ContractID, d, storagePriceKey, StoragePrice)) +} + +func (p *Policy) setStoragePrice(ic *interop.Context, args []stackitem.Item) stackitem.Item { + value := toUint32(args[0]) + if value <= 0 || maxStoragePrice < value { + panic(fmt.Errorf("StoragePrice must be between 0 and %d", maxStoragePrice)) + } + ok, err := checkValidators(ic) + if err != nil { + panic(err) + } else if !ok { + return stackitem.NewBool(false) + } + p.lock.Lock() + defer p.lock.Unlock() + err = setUint32WithKey(p.ContractID, ic.DAO, storagePriceKey, uint32(value)) + if err != nil { + panic(err) + } + p.isValid = false + return stackitem.NewBool(true) +} + // setMaxTransactionsPerBlock is Policy contract method and sets the upper limit // of transactions per block. func (p *Policy) setMaxTransactionsPerBlock(ic *interop.Context, args []stackitem.Item) stackitem.Item { diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 074a0c7ce..7bcd848a4 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/dao" + "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/native" @@ -17,6 +18,7 @@ import ( "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/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -54,7 +56,7 @@ func (bc *Blockchain) registerNative(c interop.Contract) { bc.contracts.Contracts = append(bc.contracts.Contracts, c) } -const testSumPrice = 1000000 +const testSumPrice = 1 << 15 * interop.DefaultBaseExecFee // same as contract.Call func newTestNative() *testNative { tn := &testNative{ @@ -176,8 +178,12 @@ func TestNativeContract_Invoke(t *testing.T) { }) require.NoError(t, err) - // System.Contract.Call + "sum" itself + opcodes for pushing arguments (PACK is 15000) - res, err := invokeContractMethod(chain, testSumPrice*2+18000, tn.Metadata().Hash, "sum", int64(14), int64(28)) + // System.Contract.Call + "sum" itself + opcodes for pushing arguments. + price := int64(testSumPrice * 2) + price += 3 * fee.Opcode(chain.GetBaseExecFee(), opcode.PUSHINT8, opcode.PUSHDATA1) + price += 2 * fee.Opcode(chain.GetBaseExecFee(), opcode.SYSCALL) + price += fee.Opcode(chain.GetBaseExecFee(), opcode.PACK) + res, err := invokeContractMethod(chain, price, tn.Metadata().Hash, "sum", int64(14), int64(28)) require.NoError(t, err) checkResult(t, res, stackitem.Make(42)) require.NoError(t, chain.persist()) @@ -190,10 +196,9 @@ func TestNativeContract_Invoke(t *testing.T) { } // Enough for Call and other opcodes, but not enough for "sum" call. - res, err = invokeContractMethod(chain, testSumPrice*2+8000, tn.Metadata().Hash, "sum", int64(14), int64(28)) + res, err = invokeContractMethod(chain, price-1, tn.Metadata().Hash, "sum", int64(14), int64(28)) require.NoError(t, err) checkFAULTState(t, res) - } func TestNativeContract_InvokeInternal(t *testing.T) { diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index 8ecfd0a2a..94d11a729 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -1,13 +1,12 @@ package core import ( - "math/big" "testing" "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/pkg/core/block" + "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/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/network/payload" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -15,174 +14,129 @@ 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 + 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) + checkResult(t, invokeRes, stackitem.NewBool(false)) + }) + + t.Run("get", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, policyHash, 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) + 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) + require.NoError(t, err) + checkFAULTState(t, res) + }) + } + + t.Run("set, success", func(t *testing.T) { + // Set and get in the same block. + txSet, err := prepareContractMethodInvoke(chain, 100000000, policyHash, setName, defaultValue+1) + require.NoError(t, err) + txGet1, err := prepareContractMethodInvoke(chain, 100000000, policyHash, getName) + require.NoError(t, err) + aers, err := persistBlock(chain, txSet, txGet1) + require.NoError(t, err) + checkResult(t, aers[0], stackitem.NewBool(true)) + checkResult(t, aers[1], stackitem.Make(defaultValue+1)) + require.NoError(t, chain.persist()) + + // Get in the next block. + res, err := invokeContractMethod(chain, 100000000, policyHash, getName) + require.NoError(t, err) + checkResult(t, res, stackitem.Make(defaultValue+1)) + require.NoError(t, chain.persist()) + }) +} + func TestMaxTransactionsPerBlock(t *testing.T) { chain := newTestChain(t) defer chain.Close() - policyHash := chain.contracts.Policy.Metadata().Hash t.Run("get, internal method", func(t *testing.T) { n := chain.contracts.Policy.GetMaxTransactionsPerBlockInternal(chain.dao) require.Equal(t, 512, int(n)) }) - t.Run("get, contract method", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "getMaxTransactionsPerBlock") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(512))) - require.NoError(t, chain.persist()) - }) - - t.Run("set", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024))) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - require.NoError(t, chain.persist()) - n := chain.contracts.Policy.GetMaxTransactionsPerBlockInternal(chain.dao) - require.Equal(t, 1024, int(n)) - }) - - t.Run("set, too big value", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(block.MaxContentsPerBlock))) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - 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, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024))) - checkResult(t, invokeRes, stackitem.NewBool(false)) - }) + testPolicyGetSet(t, chain, "MaxTransactionsPerBlock", 512, 0, block.MaxTransactionsPerBlock) } func TestMaxBlockSize(t *testing.T) { chain := newTestChain(t) defer chain.Close() - policyHash := chain.contracts.Policy.Metadata().Hash t.Run("get, internal method", func(t *testing.T) { n := chain.contracts.Policy.GetMaxBlockSizeInternal(chain.dao) require.Equal(t, 1024*256, int(n)) }) - t.Run("get, contract method", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSize") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1024*256))) - require.NoError(t, chain.persist()) - }) - - t.Run("set", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400))) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - require.NoError(t, chain.persist()) - res, err = invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSize") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(102400))) - require.NoError(t, chain.persist()) - }) - - t.Run("set, too big value", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSize", bigint.ToBytes(big.NewInt(payload.MaxSize+1))) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - 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, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400))) - checkResult(t, invokeRes, stackitem.NewBool(false)) - }) + testPolicyGetSet(t, chain, "MaxBlockSize", 1024*256, 0, payload.MaxSize) } func TestFeePerByte(t *testing.T) { chain := newTestChain(t) defer chain.Close() - policyHash := chain.contracts.Policy.Metadata().Hash t.Run("get, internal method", func(t *testing.T) { n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao) require.Equal(t, 1000, int(n)) }) - t.Run("get, contract method", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "getFeePerByte") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1000))) - require.NoError(t, chain.persist()) + testPolicyGetSet(t, chain, "FeePerByte", 1000, 0, 100_000_000) +} + +func TestExecFeeFactor(t *testing.T) { + chain := newTestChain(t) + defer chain.Close() + + t.Run("get, internal method", func(t *testing.T) { + n := chain.contracts.Policy.GetExecFeeFactorInternal(chain.dao) + require.EqualValues(t, interop.DefaultBaseExecFee, n) }) - t.Run("set", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(1024))) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - require.NoError(t, chain.persist()) - n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao) - require.Equal(t, 1024, int(n)) - }) - - t.Run("set, negative value", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(-1))) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - t.Run("set, too big value", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(100_000_000+1))) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - 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, "setFeePerByte", bigint.ToBytes(big.NewInt(1024))) - checkResult(t, invokeRes, stackitem.NewBool(false)) - }) + testPolicyGetSet(t, chain, "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000) } func TestBlockSystemFee(t *testing.T) { chain := newTestChain(t) defer chain.Close() - policyHash := chain.contracts.Policy.Metadata().Hash t.Run("get, internal method", func(t *testing.T) { n := chain.contracts.Policy.GetMaxBlockSystemFeeInternal(chain.dao) require.Equal(t, 9000*native.GASFactor, int(n)) }) - t.Run("get", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSystemFee") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(9000*native.GASFactor))) - require.NoError(t, chain.persist()) + testPolicyGetSet(t, chain, "MaxBlockSystemFee", 9000*native.GASFactor, 4007600, 0) +} + +func TestStoragePrice(t *testing.T) { + chain := newTestChain(t) + defer chain.Close() + + t.Run("get, internal method", func(t *testing.T) { + n := chain.contracts.Policy.GetStoragePriceInternal(chain.dao) + require.Equal(t, int64(native.StoragePrice), n) }) - t.Run("set, too low fee", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(4007600))) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - t.Run("set, success", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(100000000))) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - require.NoError(t, chain.persist()) - res, err = invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSystemFee") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(100000000))) - require.NoError(t, chain.persist()) - }) - - 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, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(100000000))) - checkResult(t, invokeRes, stackitem.NewBool(false)) - }) + testPolicyGetSet(t, chain, "StoragePrice", native.StoragePrice, 1, 10000000) } func TestBlockedAccounts(t *testing.T) { diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 764e0efe5..b4c98b223 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -14,7 +14,9 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" + "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/mempool" + "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto" @@ -101,7 +103,12 @@ func (chain *testChain) GetNotaryBalance(acc util.Uint160) *big.Int { func (chain *testChain) GetPolicer() blockchainer.Policer { return chain } - +func (chain *testChain) GetBaseExecFee() int64 { + return interop.DefaultBaseExecFee +} +func (chain *testChain) GetStoragePrice() int64 { + return native.StoragePrice +} func (chain *testChain) GetMaxVerificationGAS() int64 { if chain.maxVerificationGAS != 0 { return chain.maxVerificationGAS diff --git a/pkg/network/server_test.go b/pkg/network/server_test.go index 2926b78fc..9e457f1a2 100644 --- a/pkg/network/server_test.go +++ b/pkg/network/server_test.go @@ -15,6 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/consensus" "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/network/capability" "github.com/nspcc-dev/neo-go/pkg/network/payload" @@ -798,6 +799,7 @@ func (f feerStub) FeePerByte() int64 { return 1 } func (f feerStub) GetUtilityTokenBalance(util.Uint160) *big.Int { return big.NewInt(100000000) } func (f feerStub) BlockHeight() uint32 { return f.blockHeight } func (f feerStub) P2PSigExtensionsEnabled() bool { return false } +func (f feerStub) GetBaseExecFee() int64 { return interop.DefaultBaseExecFee } func TestMemPool(t *testing.T) { s, shutdown := startTestServer(t) diff --git a/pkg/rpc/client/policy.go b/pkg/rpc/client/policy.go index 6c89baf68..26ff5ae9c 100644 --- a/pkg/rpc/client/policy.go +++ b/pkg/rpc/client/policy.go @@ -25,6 +25,11 @@ func (c *Client) GetFeePerByte() (int64, error) { return c.invokeNativePolicyMethod("getFeePerByte") } +// GetExecFeeFactor invokes `getExecFeeFactor` method on a native Policy contract. +func (c *Client) GetExecFeeFactor() (int64, error) { + return c.invokeNativePolicyMethod("getExecFeeFactor") +} + func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) { if !c.initDone { return 0, errNetworkNotInitialized diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 3f4b59c6d..9103cea3b 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -569,6 +569,7 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs return errors.New("number of signers must match number of scripts") } size := io.GetVarSize(tx) + var ef int64 for i, cosigner := range tx.Signers { if accs[i].Contract.Deployed { res, err := c.InvokeFunction(cosigner.Account, manifest.MethodVerify, []smartcontract.Parameter{}, tx.Signers) @@ -589,7 +590,15 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs size += io.GetVarSize([]byte{}) * 2 // both scripts are empty continue } - netFee, sizeDelta := fee.Calculate(accs[i].Contract.Script) + + if ef == 0 { + var err error + ef, err = c.GetExecFeeFactor() + if err != nil { + return fmt.Errorf("can't get `ExecFeeFactor`: %w", err) + } + } + netFee, sizeDelta := fee.Calculate(ef, accs[i].Contract.Script) tx.NetworkFee += netFee size += sizeDelta } diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index 500f2c3e6..b72dec8e9 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -102,7 +102,7 @@ func TestAddNetworkFee(t *testing.T) { }} require.NoError(t, c.AddNetworkFee(tx, 10, accs[0])) require.NoError(t, accs[0].SignTx(tx)) - cFee, _ := fee.Calculate(accs[0].Contract.Script) + cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script) require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+10, tx.NetworkFee) }) @@ -126,8 +126,8 @@ func TestAddNetworkFee(t *testing.T) { require.NoError(t, accs[0].SignTx(tx)) require.NoError(t, accs[1].SignTx(tx)) require.NoError(t, accs[2].SignTx(tx)) - cFee, _ := fee.Calculate(accs[0].Contract.Script) - cFeeM, _ := fee.Calculate(accs[1].Contract.Script) + cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script) + cFeeM, _ := fee.Calculate(chain.GetBaseExecFee(), accs[1].Contract.Script) require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+cFeeM+10, tx.NetworkFee) }) t.Run("Contract", func(t *testing.T) { diff --git a/pkg/rpc/server/server_helper_test.go b/pkg/rpc/server/server_helper_test.go index a62c70820..6f49e8bf9 100644 --- a/pkg/rpc/server/server_helper_test.go +++ b/pkg/rpc/server/server_helper_test.go @@ -11,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core/block" + "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/storage" "github.com/nspcc-dev/neo-go/pkg/io" @@ -102,3 +103,7 @@ func (fs *FeerStub) GetUtilityTokenBalance(acc util.Uint160) *big.Int { func (fs FeerStub) P2PSigExtensionsEnabled() bool { return false } + +func (fs FeerStub) GetBaseExecFee() int64 { + return interop.DefaultBaseExecFee +} diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 830680ee6..27d1d2e84 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -1003,7 +1003,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] addNetworkFee := func(tx *transaction.Transaction) { size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(acc0.Contract.Script) + netFee, sizeDelta := fee.Calculate(chain.GetBaseExecFee(), acc0.Contract.Script) tx.NetworkFee += netFee size += sizeDelta tx.NetworkFee += int64(size) * chain.FeePerByte()