core: calculate gas for System.Storage.Put correctly

If `Put` creates new key, its length should contribute
to a GAS cost of the syscall.
This commit is contained in:
Evgenii Stratonikov 2020-10-05 12:32:04 +03:00
parent 05c1b8746a
commit 833bbb1d35
2 changed files with 41 additions and 5 deletions

View file

@ -389,14 +389,14 @@ func putWithContextAndFlags(ic *interop.Context, stc *StorageContext, key []byte
return errors.New("StorageContext is read only") return errors.New("StorageContext is read only")
} }
si := ic.DAO.GetStorageItem(stc.ID, key) si := ic.DAO.GetStorageItem(stc.ID, key)
if si == nil { if si != nil && si.IsConst {
si = &state.StorageItem{}
}
if si.IsConst {
return errors.New("storage item exists and is read-only") return errors.New("storage item exists and is read-only")
} }
sizeInc := 1 sizeInc := 1
if len(value) > len(si.Value) { if si == nil {
si = &state.StorageItem{}
sizeInc = len(key) + len(value)
} else if len(value) > len(si.Value) {
sizeInc = len(value) - len(si.Value) sizeInc = len(value) - len(si.Value)
} }
if !ic.VM.AddGas(int64(sizeInc) * StoragePrice) { if !ic.VM.AddGas(int64(sizeInc) * StoragePrice) {

View file

@ -1,6 +1,7 @@
package core package core
import ( import (
"errors"
"math/big" "math/big"
"testing" "testing"
@ -327,6 +328,41 @@ func TestBlockchainGetContractState(t *testing.T) {
}) })
} }
func TestStoragePut(t *testing.T) {
_, cs, ic, bc := createVMAndContractState(t)
defer bc.Close()
require.NoError(t, ic.DAO.PutContractState(cs))
initVM := func(t *testing.T, key, value []byte, gas int64) {
v := ic.SpawnVM()
v.LoadScript(cs.Script)
v.GasLimit = gas
v.Estack().PushVal(value)
v.Estack().PushVal(key)
require.NoError(t, storageGetContext(ic))
}
t.Run("create, not enough gas", func(t *testing.T) {
initVM(t, []byte{1}, []byte{2, 3}, 2*StoragePrice)
err := storagePut(ic)
require.True(t, errors.Is(err, errGasLimitExceeded), "got: %v", err)
})
initVM(t, []byte{4}, []byte{5, 6}, 3*StoragePrice)
require.NoError(t, storagePut(ic))
t.Run("update", func(t *testing.T) {
t.Run("not enough gas", func(t *testing.T) {
initVM(t, []byte{4}, []byte{5, 6, 7, 8}, StoragePrice)
err := storagePut(ic)
require.True(t, errors.Is(err, errGasLimitExceeded), "got: %v", err)
})
initVM(t, []byte{4}, []byte{5, 6, 7, 8}, 2*StoragePrice)
require.NoError(t, storagePut(ic))
})
}
// getTestContractState returns 2 contracts second of which is allowed to call the first. // getTestContractState returns 2 contracts second of which is allowed to call the first.
func getTestContractState() (*state.Contract, *state.Contract) { func getTestContractState() (*state.Contract, *state.Contract) {
script := []byte{ script := []byte{