core: calculate a price of System.Storage.Put correctly

It also depends on the item already stored by key.
This commit is contained in:
Evgenii Stratonikov 2020-06-15 11:39:15 +03:00
parent fe31c7ed2d
commit 8f20a70969
4 changed files with 17 additions and 85 deletions

View file

@ -24,7 +24,11 @@ func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) util.Fixed8 {
switch op {
case opcode.SYSCALL:
interopID := vm.GetInteropID(parameter)
return getSyscallPrice(v, interopID)
ifunc := v.GetInteropByID(interopID)
if ifunc != nil && ifunc.Price > 0 {
return toFixed8(int64(ifunc.Price))
}
return toFixed8(1)
default:
return toFixed8(1)
}
@ -33,30 +37,3 @@ func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) util.Fixed8 {
func toFixed8(n int64) util.Fixed8 {
return util.Fixed8(n * interopGasRatio)
}
// getSyscallPrice returns cost of executing syscall with provided id.
// Is SYSCALL is not found, cost is 1.
func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 {
ifunc := v.GetInteropByID(id)
if ifunc != nil && ifunc.Price > 0 {
return toFixed8(int64(ifunc.Price))
}
const (
systemStoragePut = 0x84183fe6 // System.Storage.Put
systemStoragePutEx = 0x3a9be173 // System.Storage.PutEx
neoStoragePut = 0xf541a152 // Neo.Storage.Put
)
estack := v.Estack()
switch id {
case systemStoragePut, systemStoragePutEx, neoStoragePut:
// price for storage PUT is 1 GAS per 1 KiB
keySize := len(estack.Peek(1).Bytes())
valSize := len(estack.Peek(2).Bytes())
return util.Fixed8FromInt64(int64((keySize+valSize-1)/1024 + 1))
default:
return util.Fixed8FromInt64(1)
}
}

View file

@ -1,54 +0,0 @@
package core
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"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/stretchr/testify/require"
)
// These tests are taken from C# code
// https://github.com/neo-project/neo/blob/master-2.x/neo.UnitTests/UT_InteropPrices.cs#L245
func TestGetPrice(t *testing.T) {
bc := newTestChain(t)
defer bc.Close()
sdao := dao.NewSimple(storage.NewMemoryStore())
systemInterop := bc.newInteropContext(trigger.Application, sdao, nil, nil)
v := SpawnVM(systemInterop)
v.SetPriceGetter(getPrice)
t.Run("System.Storage.Put", func(t *testing.T) {
// System.Storage.Put: e63f1884 (requires push key and value)
v.Load([]byte{byte(opcode.PUSH3), byte(opcode.PUSH3), byte(opcode.PUSH0),
byte(opcode.SYSCALL), 0xe6, 0x3f, 0x18, 0x84})
require.NoError(t, v.StepInto()) // push 03 (length 1)
require.NoError(t, v.StepInto()) // push 03 (length 1)
require.NoError(t, v.StepInto()) // push 00
checkGas(t, util.Fixed8FromInt64(1), v)
})
t.Run("System.Storage.PutEx", func(t *testing.T) {
// System.Storage.PutEx: 73e19b3a (requires push key and value)
v.Load([]byte{byte(opcode.PUSH3), byte(opcode.PUSH3), byte(opcode.PUSH0),
byte(opcode.SYSCALL), 0x73, 0xe1, 0x9b, 0x3a})
require.NoError(t, v.StepInto()) // push 03 (length 1)
require.NoError(t, v.StepInto()) // push 03 (length 1)
require.NoError(t, v.StepInto()) // push 00
checkGas(t, util.Fixed8FromInt64(1), v)
})
}
func checkGas(t *testing.T, expected util.Fixed8, v *vm.VM) {
op, par, err := v.Context().Next()
require.NoError(t, err)
require.Equal(t, expected, getPrice(v, op, par))
}

View file

@ -26,6 +26,8 @@ const (
MaxContractStringLen = 252
)
var errGasLimitExceeded = errors.New("gas limit exceeded")
// storageFind finds stored key-value pair.
func storageFind(ic *interop.Context, v *vm.VM) error {
stcInterface := v.Estack().Pop().Value()
@ -71,7 +73,7 @@ func createContractStateFromVM(ic *interop.Context, v *vm.VM) (*state.Contract,
return nil, errors.New("manifest is too big")
}
if !v.AddGas(util.Fixed8(StoragePrice * (len(script) + len(manifestBytes)))) {
return nil, errors.New("gas limit exceeded")
return nil, errGasLimitExceeded
}
var m manifest.Manifest
r := io.NewBinReaderFromBuf(manifestBytes)

View file

@ -332,7 +332,7 @@ func storageGetReadOnlyContext(ic *interop.Context, v *vm.VM) error {
return nil
}
func putWithContextAndFlags(ic *interop.Context, stc *StorageContext, key []byte, value []byte, isConst bool) error {
func putWithContextAndFlags(ic *interop.Context, v *vm.VM, stc *StorageContext, key []byte, value []byte, isConst bool) error {
if len(key) > MaxStorageKeyLen {
return errors.New("key is too big")
}
@ -350,6 +350,13 @@ func putWithContextAndFlags(ic *interop.Context, stc *StorageContext, key []byte
if si.IsConst {
return errors.New("storage item exists and is read-only")
}
sizeInc := 1
if len(value) > len(si.Value) {
sizeInc = len(value) - len(si.Value)
}
if !v.AddGas(util.Fixed8(sizeInc) * StoragePrice) {
return errGasLimitExceeded
}
si.Value = value
si.IsConst = isConst
return ic.DAO.PutStorageItem(stc.ScriptHash, key, si)
@ -368,7 +375,7 @@ func storagePutInternal(ic *interop.Context, v *vm.VM, getFlag bool) error {
if getFlag {
flag = int(v.Estack().Pop().BigInt().Int64())
}
return putWithContextAndFlags(ic, stc, key, value, flag == 1)
return putWithContextAndFlags(ic, v, stc, key, value, flag == 1)
}
// storagePut puts key-value pair into the storage.