Make container.Delete() GAS cost more predictable #42
6 changed files with 133 additions and 3 deletions
|
@ -1,6 +1,7 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/convert"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||
)
|
||||
|
@ -10,3 +11,18 @@ func SetSerialized(ctx storage.Context, key interface{}, value interface{}) {
|
|||
data := std.Serialize(value)
|
||||
storage.Put(ctx, key, data)
|
||||
}
|
||||
|
||||
// ToFixedWidth64 converts x to bytes such that numbers <= math.MaxUint64
|
||||
// have constant with of 9.
|
||||
func ToFixedWidth64(x int) []byte {
|
||||
data := convert.ToBytes(x)
|
||||
if x < 0 || len(data) >= 9 {
|
||||
return data
|
||||
}
|
||||
return append(data, make([]byte, 9-len(data))...)
|
||||
}
|
||||
|
||||
// FromFixedWidth64 is a reverse function for ToFixedWidth64.
|
||||
func FromFixedWidth64(x []byte) int {
|
||||
return convert.ToInteger(x)
|
||||
}
|
||||
|
|
|
@ -343,6 +343,11 @@ type DelInfo struct {
|
|||
Epoch int
|
||||
}
|
||||
|
||||
type delInfo struct {
|
||||
Owner []byte
|
||||
Epoch []byte
|
||||
}
|
||||
|
||||
// DeletionInfo method returns container deletion info.
|
||||
// If the container had never existed, NotFoundError is throwed.
|
||||
// It can be used to check whether non-existing container was indeed deleted
|
||||
|
@ -354,7 +359,12 @@ func DeletionInfo(containerID []byte) DelInfo {
|
|||
if data == nil {
|
||||
panic(NotFoundError)
|
||||
}
|
||||
return std.Deserialize(data).(DelInfo)
|
||||
|
||||
d := std.Deserialize(data).(delInfo)
|
||||
return DelInfo{
|
||||
Owner: d.Owner,
|
||||
Epoch: common.FromFixedWidth64(d.Epoch),
|
||||
}
|
||||
}
|
||||
|
||||
// Get method returns a structure that contains a stable marshaled Container structure,
|
||||
|
@ -631,9 +641,9 @@ func removeContainer(ctx storage.Context, id []byte, owner []byte) {
|
|||
graveKey := append([]byte{graveKeyPrefix}, id...)
|
||||
netmapContractAddr := storage.Get(ctx, netmapContractKey).(interop.Hash160)
|
||||
epoch := contract.Call(netmapContractAddr, "epoch", contract.ReadOnly).(int)
|
||||
common.SetSerialized(ctx, graveKey, DelInfo{
|
||||
common.SetSerialized(ctx, graveKey, delInfo{
|
||||
Owner: owner,
|
||||
Epoch: epoch,
|
||||
Epoch: common.ToFixedWidth64(epoch),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
61
tests/common_test.go
Normal file
61
tests/common_test.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package tests
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const testdataPath = "./testdata"
|
||||
|
||||
func newTestdataInvoker(t *testing.T) *neotest.ContractInvoker {
|
||||
e := newExecutor(t)
|
||||
ctr := neotest.CompileFile(t, e.CommitteeHash, testdataPath, path.Join(testdataPath, "config.yml"))
|
||||
e.DeployContract(t, ctr, nil)
|
||||
return e.CommitteeInvoker(ctr.Hash)
|
||||
}
|
||||
|
||||
func TestEncodeU64(t *testing.T) {
|
||||
// Let's check boundary values for all bit sizes:
|
||||
var nums []uint64
|
||||
for i := 0; i < 64; i++ {
|
||||
if i != 0 {
|
||||
nums = append(nums, (1<<i)-1)
|
||||
}
|
||||
nums = append(nums, 1<<i)
|
||||
if i != 63 {
|
||||
nums = append(nums, (1<<i)+1)
|
||||
}
|
||||
}
|
||||
|
||||
c := newTestdataInvoker(t)
|
||||
for _, n := range nums {
|
||||
v, err := c.TestInvoke(t, "encode", n)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, v.Len())
|
||||
|
||||
r := v.Pop().Bytes()
|
||||
require.Equal(t, 9, len(r), "got: %x", r)
|
||||
|
||||
v, err = c.TestInvoke(t, "encodeDecode", n)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, v.Len())
|
||||
require.Equal(t, n, v.Pop().BigInt().Uint64())
|
||||
}
|
||||
|
||||
t.Run("bad cases should be handled", func(t *testing.T) {
|
||||
x := new(big.Int).SetUint64(math.MaxUint64)
|
||||
x.Add(x, big.NewInt(1))
|
||||
|
||||
nums := []*big.Int{x, big.NewInt(-1), big.NewInt(-128)}
|
||||
for _, n := range nums {
|
||||
v, err := c.TestInvoke(t, "encodeDecode", n)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, v.Pop().BigInt().Cmp(n))
|
||||
}
|
||||
})
|
||||
}
|
|
@ -282,6 +282,30 @@ func TestContainerDelete(t *testing.T) {
|
|||
c.InvokeFail(t, container.NotFoundError, "deletionInfo", id[:])
|
||||
})
|
||||
|
||||
t.Run("gas costs are the same for different epochs", func(t *testing.T) {
|
||||
_, cnt2 := addContainer(t, c, cBal)
|
||||
args := []interface{}{cnt2.id[:], cnt2.sig, cnt2.pub, cnt2.token}
|
||||
|
||||
tx := c.PrepareInvoke(t, "delete", args...)
|
||||
for _, e := range []int{126, 127, 128, 129, 65536} {
|
||||
cNm.Invoke(t, stackitem.Null{}, "newEpoch", e)
|
||||
|
||||
// Sanity check.
|
||||
s, err := cNm.TestInvoke(t, "epoch")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(int64(e)), s.Top().BigInt())
|
||||
|
||||
tx2 := c.PrepareInvoke(t, "delete", args...)
|
||||
require.Equal(t, tx.Size(), tx2.Size())
|
||||
require.Equal(t, tx.SystemFee, tx2.SystemFee)
|
||||
require.Equal(t, tx.NetworkFee, tx2.NetworkFee)
|
||||
}
|
||||
|
||||
// Another sanity check: we want to test successful invocations,
|
||||
// bad ones can trivially be equal.
|
||||
c.Invoke(t, stackitem.Null{}, "delete", args...)
|
||||
})
|
||||
|
||||
c.InvokeFail(t, container.NotFoundError, "get", cnt.id[:])
|
||||
}
|
||||
|
||||
|
|
2
tests/testdata/config.yml
vendored
Normal file
2
tests/testdata/config.yml
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
name: "TestContract"
|
||||
safemethods: ["encodeDecode"]
|
17
tests/testdata/encode.go
vendored
Normal file
17
tests/testdata/encode.go
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
package testdata
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
|
||||
)
|
||||
|
||||
// EncodeDecode encodes x in fixed-width little-endian representation
|
||||
// and deserializes it back.
|
||||
func EncodeDecode(x int) int {
|
||||
y := common.ToFixedWidth64(x)
|
||||
return common.FromFixedWidth64(y)
|
||||
}
|
||||
|
||||
// Encode encodes x in fixed-width little-endian representation.
|
||||
func Encode(x int) []byte {
|
||||
return common.ToFixedWidth64(x)
|
||||
}
|
Loading…
Add table
Reference in a new issue