993 lines
37 KiB
Go
993 lines
37 KiB
Go
package native
|
|
|
|
import (
|
|
"cmp"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"maps"
|
|
"math/big"
|
|
"slices"
|
|
|
|
"github.com/tutus-one/tutus-chain/pkg/config"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/dao"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/interop"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/native/nativeids"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/state"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/storage"
|
|
"github.com/tutus-one/tutus-chain/pkg/core/transaction"
|
|
"github.com/tutus-one/tutus-chain/pkg/encoding/bigint"
|
|
"github.com/tutus-one/tutus-chain/pkg/smartcontract"
|
|
"github.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
|
|
"github.com/tutus-one/tutus-chain/pkg/smartcontract/manifest"
|
|
"github.com/tutus-one/tutus-chain/pkg/util"
|
|
"github.com/tutus-one/tutus-chain/pkg/vm"
|
|
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
|
|
)
|
|
|
|
const (
|
|
defaultExecFeeFactor = interop.DefaultBaseExecFee
|
|
defaultFeePerByte = 1000
|
|
defaultMaxVerificationGas = 1_50000000
|
|
// defaultAttributeFee is a default fee for a transaction attribute those price wasn't set yet.
|
|
defaultAttributeFee = 0
|
|
// defaultNotaryAssistedFee is a default fee for a NotaryAssisted transaction attribute per key.
|
|
defaultNotaryAssistedFee = 1000_0000 // 0.1 GAS
|
|
// DefaultStoragePrice is the price to pay for 1 byte of storage.
|
|
DefaultStoragePrice = 100000
|
|
|
|
// maxExecFeeFactor is the maximum allowed execution fee factor.
|
|
maxExecFeeFactor uint32 = 100
|
|
// 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
|
|
// maxAttributeFee is the maximum allowed value for a transaction attribute fee.
|
|
maxAttributeFee = 10_00000000
|
|
// maxMillisecondsPerBlock is the maximum allowed value (in milliseconds) for a block generation time.
|
|
maxMillisecondsPerBlock = 30_000
|
|
// maxMaxVUBIncrement the maximum value for upper increment size of blockchain
|
|
// height (in blocks) exceeding that a transaction should fail validation.
|
|
maxMaxVUBIncrement = 86400
|
|
// the maximum value of maximum number of blocks reachable to contracts.
|
|
maxMaxTraceableBlocks = 2102400
|
|
|
|
// blockedAccountPrefix is a prefix used to store blocked account.
|
|
blockedAccountPrefix = 15
|
|
// whitelistedFeeContractPrefix is a prefix used to store whitelisted contract.
|
|
whitelistedFeeContractPrefix = 16
|
|
// attributeFeePrefix is a prefix used to store attribute fee.
|
|
attributeFeePrefix = 20
|
|
)
|
|
|
|
var (
|
|
// 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}
|
|
// storagePriceKey is a key used to store storage price.
|
|
storagePriceKey = []byte{19}
|
|
// msPerBlockKey is a key used to store block generation time.
|
|
msPerBlockKey = []byte{21}
|
|
// maxVUBIncrementKey is a key used to store maximum ValidUntilBlock increment.
|
|
maxVUBIncrementKey = []byte{22}
|
|
// MaxTraceableBlocksKey is a key used to store the maximum number of traceable blocks.
|
|
MaxTraceableBlocksKey = []byte{23}
|
|
)
|
|
|
|
// Policy represents Policy native contract.
|
|
type Policy struct {
|
|
interop.ContractMD
|
|
Tutus ITutus
|
|
}
|
|
|
|
type PolicyCache struct {
|
|
execFeeFactor uint32
|
|
faunInitialized bool
|
|
feePerByte int64
|
|
maxVerificationGas int64
|
|
storagePrice uint32
|
|
msPerBlock uint32
|
|
maxVUBIncrement uint32
|
|
maxTraceableBlocks uint32
|
|
attributeFee map[transaction.AttrType]uint32
|
|
blockedAccounts []util.Uint160
|
|
whitelistedContracts []whitelistedContract
|
|
}
|
|
|
|
// whitelistedContract is a structure representing a whitelisted contract with
|
|
// the pre-defined execution price.
|
|
type whitelistedContract struct {
|
|
hash util.Uint160
|
|
offset uint32
|
|
fee int64
|
|
}
|
|
|
|
var _ = (stackitem.Convertible)(&whitelistedContract{})
|
|
|
|
// Compare compares two whitelistedContract structures in the order that matches
|
|
// a whitelisted contract key serialization format.
|
|
func (c whitelistedContract) Compare(other whitelistedContract) int {
|
|
return cmp.Or(
|
|
c.hash.Compare(other.hash),
|
|
cmp.Compare(c.offset, other.offset), // offset is stored in BE.
|
|
)
|
|
}
|
|
|
|
// ToStackItem implements [stackitem.Convertible] interface.
|
|
func (c whitelistedContract) ToStackItem() (stackitem.Item, error) {
|
|
return stackitem.NewByteArray(makeWhitelistedKey(c.hash, c.offset)[1:]), // strip prefix.
|
|
nil
|
|
}
|
|
|
|
// FromStackItem implements [stackitem.Convertible] interface. Not really needed
|
|
// since whitelistedContract conversion is one-directional only.
|
|
func (c *whitelistedContract) FromStackItem(item stackitem.Item) error {
|
|
panic("not supported")
|
|
}
|
|
|
|
var (
|
|
_ interop.Contract = (*Policy)(nil)
|
|
_ dao.NativeContractCache = (*PolicyCache)(nil)
|
|
)
|
|
|
|
// Copy implements NativeContractCache interface.
|
|
func (c *PolicyCache) Copy() dao.NativeContractCache {
|
|
cp := &PolicyCache{}
|
|
copyPolicyCache(c, cp)
|
|
return cp
|
|
}
|
|
|
|
func copyPolicyCache(src, dst *PolicyCache) {
|
|
*dst = *src
|
|
dst.attributeFee = maps.Clone(src.attributeFee)
|
|
dst.blockedAccounts = slices.Clone(src.blockedAccounts)
|
|
dst.whitelistedContracts = slices.Clone(src.whitelistedContracts)
|
|
}
|
|
|
|
// newPolicy returns Policy native contract.
|
|
func newPolicy() *Policy {
|
|
p := &Policy{ContractMD: *interop.NewContractMD(nativenames.Policy, nativeids.PolicyContract)}
|
|
defer p.BuildHFSpecificMD(p.ActiveIn())
|
|
|
|
desc := NewDescriptor("getFeePerByte", smartcontract.IntegerType)
|
|
md := NewMethodAndPrice(p.getFeePerByte, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("isBlocked", smartcontract.BoolType,
|
|
manifest.NewParameter("account", smartcontract.Hash160Type))
|
|
md = NewMethodAndPrice(p.isBlocked, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getExecFeeFactor", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getExecFeeFactor, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("setExecFeeFactor", smartcontract.VoidType,
|
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.setExecFeeFactor, 1<<15, callflag.States)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getStoragePrice", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getStoragePrice, 1<<15, callflag.ReadStates)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("setStoragePrice", smartcontract.VoidType,
|
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.setStoragePrice, 1<<15, callflag.States)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getAttributeFee", smartcontract.IntegerType,
|
|
manifest.NewParameter("attributeType", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.getAttributeFeeV0, 1<<15, callflag.ReadStates, config.HFDefault, transaction.NotaryAssistedActivation)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getAttributeFee", smartcontract.IntegerType,
|
|
manifest.NewParameter("attributeType", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.getAttributeFeeV1, 1<<15, callflag.ReadStates, transaction.NotaryAssistedActivation)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("setAttributeFee", smartcontract.VoidType,
|
|
manifest.NewParameter("attributeType", smartcontract.IntegerType),
|
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.setAttributeFeeV0, 1<<15, callflag.States, config.HFDefault, transaction.NotaryAssistedActivation)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("setAttributeFee", smartcontract.VoidType,
|
|
manifest.NewParameter("attributeType", smartcontract.IntegerType),
|
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.setAttributeFeeV1, 1<<15, callflag.States, transaction.NotaryAssistedActivation)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("setFeePerByte", smartcontract.VoidType,
|
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.setFeePerByte, 1<<15, callflag.States)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("blockAccount", smartcontract.BoolType,
|
|
manifest.NewParameter("account", smartcontract.Hash160Type))
|
|
md = NewMethodAndPrice(p.blockAccount, 1<<15, callflag.States, config.HFDefault, config.HFFaun)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("blockAccount", smartcontract.BoolType,
|
|
manifest.NewParameter("account", smartcontract.Hash160Type))
|
|
md = NewMethodAndPrice(p.blockAccount, 1<<15, callflag.States|callflag.AllowNotify, config.HFFaun)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("unblockAccount", smartcontract.BoolType,
|
|
manifest.NewParameter("account", smartcontract.Hash160Type))
|
|
md = NewMethodAndPrice(p.unblockAccount, 1<<15, callflag.States)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getMaxValidUntilBlockIncrement", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getMaxValidUntilBlockIncrement, 1<<15, callflag.ReadStates, config.HFEchidna)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("setMaxValidUntilBlockIncrement", smartcontract.VoidType,
|
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.setMaxValidUntilBlockIncrement, 1<<15, callflag.States, config.HFEchidna)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getMillisecondsPerBlock", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getMillisecondsPerBlock, 1<<15, callflag.ReadStates, config.HFEchidna)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("setMillisecondsPerBlock", smartcontract.VoidType,
|
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.setMillisecondsPerBlock, 1<<15, callflag.States|callflag.AllowNotify, config.HFEchidna)
|
|
p.AddMethod(md, desc)
|
|
|
|
eDesc := NewEventDescriptor("MillisecondsPerBlockChanged",
|
|
manifest.NewParameter("old", smartcontract.IntegerType),
|
|
manifest.NewParameter("new", smartcontract.IntegerType),
|
|
)
|
|
eMD := NewEvent(eDesc, config.HFEchidna)
|
|
p.AddEvent(eMD)
|
|
|
|
desc = NewDescriptor("getMaxTraceableBlocks", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getMaxTraceableBlocks, 1<<15, callflag.ReadStates, config.HFEchidna)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("setMaxTraceableBlocks", smartcontract.VoidType,
|
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.setMaxTraceableBlocks, 1<<15, callflag.States, config.HFEchidna)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getBlockedAccounts", smartcontract.InteropInterfaceType)
|
|
md = NewMethodAndPrice(p.getBlockedAccounts, 1<<15, callflag.ReadStates, config.HFFaun)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getExecPicoFeeFactor", smartcontract.IntegerType)
|
|
md = NewMethodAndPrice(p.getExecPicoFeeFactor, 1<<15, callflag.ReadStates, config.HFFaun)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("setWhitelistFeeContract", smartcontract.VoidType,
|
|
manifest.NewParameter("contractHash", smartcontract.Hash160Type),
|
|
manifest.NewParameter("method", smartcontract.StringType),
|
|
manifest.NewParameter("argCount", smartcontract.IntegerType),
|
|
manifest.NewParameter("fixedFee", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.setWhitelistFeeContract, 1<<15, callflag.States|callflag.AllowNotify, config.HFFaun)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("removeWhitelistFeeContract", smartcontract.VoidType,
|
|
manifest.NewParameter("contractHash", smartcontract.Hash160Type),
|
|
manifest.NewParameter("method", smartcontract.StringType),
|
|
manifest.NewParameter("argCount", smartcontract.IntegerType))
|
|
md = NewMethodAndPrice(p.removeWhitelistFeeContract, 1<<15, callflag.States|callflag.AllowNotify, config.HFFaun)
|
|
p.AddMethod(md, desc)
|
|
|
|
desc = NewDescriptor("getWhitelistFeeContracts", smartcontract.InteropInterfaceType)
|
|
md = NewMethodAndPrice(p.getWhitelistFeeContracts, 1<<15, callflag.ReadStates, config.HFFaun)
|
|
p.AddMethod(md, desc)
|
|
|
|
eDesc = NewEventDescriptor("WhitelistFeeChanged",
|
|
manifest.NewParameter("contract", smartcontract.Hash160Type),
|
|
manifest.NewParameter("method", smartcontract.StringType),
|
|
manifest.NewParameter("argCount", smartcontract.IntegerType),
|
|
manifest.NewParameter("fee", smartcontract.AnyType),
|
|
)
|
|
eMD = NewEvent(eDesc, config.HFFaun)
|
|
p.AddEvent(eMD)
|
|
|
|
return p
|
|
}
|
|
|
|
// Metadata implements the Contract interface.
|
|
func (p *Policy) Metadata() *interop.ContractMD {
|
|
return &p.ContractMD
|
|
}
|
|
|
|
// Initialize initializes Policy native contract and implements the Contract interface.
|
|
func (p *Policy) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
|
|
if hf == p.ActiveIn() {
|
|
setIntWithKey(p.ID, ic.DAO, feePerByteKey, defaultFeePerByte)
|
|
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor)
|
|
setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice)
|
|
|
|
cache := &PolicyCache{
|
|
execFeeFactor: defaultExecFeeFactor,
|
|
feePerByte: defaultFeePerByte,
|
|
maxVerificationGas: defaultMaxVerificationGas,
|
|
storagePrice: DefaultStoragePrice,
|
|
attributeFee: map[transaction.AttrType]uint32{},
|
|
blockedAccounts: make([]util.Uint160, 0),
|
|
whitelistedContracts: make([]whitelistedContract, 0),
|
|
}
|
|
ic.DAO.SetCache(p.ID, cache)
|
|
}
|
|
|
|
if hf != nil && *hf == config.HFEchidna {
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
|
|
maxVUBIncrement := ic.Chain.GetConfig().Genesis.MaxValidUntilBlockIncrement
|
|
setIntWithKey(p.ID, ic.DAO, maxVUBIncrementKey, int64(maxVUBIncrement))
|
|
cache.maxVUBIncrement = maxVUBIncrement
|
|
|
|
msPerBlock := ic.Chain.GetConfig().Genesis.TimePerBlock.Milliseconds()
|
|
setIntWithKey(p.ID, ic.DAO, msPerBlockKey, msPerBlock)
|
|
cache.msPerBlock = uint32(msPerBlock)
|
|
|
|
maxTraceableBlocks := ic.Chain.GetConfig().Genesis.MaxTraceableBlocks
|
|
setIntWithKey(p.ID, ic.DAO, MaxTraceableBlocksKey, int64(maxTraceableBlocks))
|
|
cache.maxTraceableBlocks = maxTraceableBlocks
|
|
|
|
setIntWithKey(p.ID, ic.DAO, []byte{attributeFeePrefix, byte(transaction.NotaryAssistedT)}, defaultNotaryAssistedFee)
|
|
cache.attributeFee[transaction.NotaryAssistedT] = defaultNotaryAssistedFee
|
|
}
|
|
|
|
if hf != nil && *hf == config.HFFaun {
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
cache.execFeeFactor = cache.execFeeFactor * vm.ExecFeeFactorMultiplier
|
|
cache.faunInitialized = true
|
|
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, int64(cache.execFeeFactor))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Policy) InitializeCache(isHardforkEnabled interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
|
|
cache := &PolicyCache{}
|
|
err := p.fillCacheFromDAO(cache, d, isHardforkEnabled, blockHeight)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.SetCache(p.ID, cache)
|
|
return nil
|
|
}
|
|
|
|
func (p *Policy) fillCacheFromDAO(cache *PolicyCache, d *dao.Simple, isHardforkEnabled interop.IsHardforkEnabled, blockHeight uint32) error {
|
|
cache.execFeeFactor = uint32(getIntWithKey(p.ID, d, execFeeFactorKey))
|
|
cache.feePerByte = getIntWithKey(p.ID, d, feePerByteKey)
|
|
cache.maxVerificationGas = defaultMaxVerificationGas
|
|
cache.storagePrice = uint32(getIntWithKey(p.ID, d, storagePriceKey))
|
|
|
|
cache.blockedAccounts = make([]util.Uint160, 0)
|
|
var fErr error
|
|
d.Seek(p.ID, storage.SeekRange{Prefix: []byte{blockedAccountPrefix}}, func(k, _ []byte) bool {
|
|
hash, err := util.Uint160DecodeBytesBE(k)
|
|
if err != nil {
|
|
fErr = fmt.Errorf("failed to decode blocked account hash: %w", err)
|
|
return false
|
|
}
|
|
cache.blockedAccounts = append(cache.blockedAccounts, hash)
|
|
return true
|
|
})
|
|
if fErr != nil {
|
|
return fmt.Errorf("failed to initialize blocked accounts: %w", fErr)
|
|
}
|
|
|
|
cache.attributeFee = make(map[transaction.AttrType]uint32)
|
|
d.Seek(p.ID, storage.SeekRange{Prefix: []byte{attributeFeePrefix}}, func(k, v []byte) bool {
|
|
if len(k) != 1 {
|
|
fErr = fmt.Errorf("unexpected attribute type len %d (%s)", len(k), hex.EncodeToString(k))
|
|
return false
|
|
}
|
|
t := transaction.AttrType(k[0])
|
|
value := bigint.FromBytes(v)
|
|
if value == nil {
|
|
fErr = fmt.Errorf("unexpected attribute value format: key=%s, value=%s", hex.EncodeToString(k), hex.EncodeToString(v))
|
|
return false
|
|
}
|
|
cache.attributeFee[t] = uint32(value.Int64())
|
|
return true
|
|
})
|
|
if fErr != nil {
|
|
return fmt.Errorf("failed to initialize attribute fees: %w", fErr)
|
|
}
|
|
|
|
var echidna = config.HFEchidna
|
|
if isHardforkEnabled(&echidna, blockHeight) {
|
|
cache.maxVUBIncrement = uint32(getIntWithKey(p.ID, d, maxVUBIncrementKey))
|
|
cache.msPerBlock = uint32(getIntWithKey(p.ID, d, msPerBlockKey))
|
|
cache.maxTraceableBlocks = uint32(getIntWithKey(p.ID, d, MaxTraceableBlocksKey))
|
|
}
|
|
|
|
cache.whitelistedContracts = make([]whitelistedContract, 0)
|
|
var faun = config.HFFaun
|
|
if isHardforkEnabled(&faun, blockHeight) {
|
|
cache.faunInitialized = true
|
|
d.Seek(p.ID, storage.SeekRange{Prefix: []byte{whitelistedFeeContractPrefix}}, func(k, v []byte) bool {
|
|
if len(k) != util.Uint160Size+4 {
|
|
fErr = fmt.Errorf("unexpected whitelisted contract key length %d vs %d", len(k), util.Uint160Size+4)
|
|
return false
|
|
}
|
|
h, err := util.Uint160DecodeBytesBE(k[:util.Uint160Size])
|
|
if err != nil {
|
|
fErr = fmt.Errorf("failed to decode whitelisted contract hash: %w", err)
|
|
return false
|
|
}
|
|
offset := binary.BigEndian.Uint32(k[util.Uint160Size:])
|
|
value := bigint.FromBytes(v)
|
|
if value == nil {
|
|
fErr = fmt.Errorf("unexpected whitelisted contract fee format: key=%s, value=%s", hex.EncodeToString(k), hex.EncodeToString(v))
|
|
return false
|
|
}
|
|
cache.whitelistedContracts = append(cache.whitelistedContracts, whitelistedContract{
|
|
hash: h,
|
|
offset: offset,
|
|
fee: value.Int64(),
|
|
})
|
|
return true
|
|
})
|
|
if fErr != nil {
|
|
return fmt.Errorf("failed to initialize whitelisted contracts: %w", fErr)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// OnPersist implements Contract interface.
|
|
func (p *Policy) OnPersist(ic *interop.Context) error {
|
|
return nil
|
|
}
|
|
|
|
// PostPersist implements Contract interface.
|
|
func (p *Policy) PostPersist(ic *interop.Context) error {
|
|
return nil
|
|
}
|
|
|
|
// ActiveIn implements the Contract interface.
|
|
func (p *Policy) ActiveIn() *config.Hardfork {
|
|
return nil
|
|
}
|
|
|
|
// getFeePerByte is a Policy contract method that returns the required transaction's fee
|
|
// per byte.
|
|
func (p *Policy) getFeePerByte(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(big.NewInt(p.GetFeePerByteInternal(ic.DAO)))
|
|
}
|
|
|
|
// GetFeePerByteInternal returns required transaction's fee per byte.
|
|
func (p *Policy) GetFeePerByteInternal(dao *dao.Simple) int64 {
|
|
cache := dao.GetROCache(p.ID).(*PolicyCache)
|
|
return cache.feePerByte
|
|
}
|
|
|
|
// GetMaxVerificationGas returns the maximum gas allowed to be burned during verification.
|
|
func (p *Policy) GetMaxVerificationGas(dao *dao.Simple) int64 {
|
|
cache := dao.GetROCache(p.ID).(*PolicyCache)
|
|
return cache.maxVerificationGas
|
|
}
|
|
|
|
// getExecFeeFactor returns the current execution fee factor in Datoshi units.
|
|
func (p *Policy) getExecFeeFactor(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(big.NewInt(p.GetExecFeeFactorInternal(ic.DAO) / vm.ExecFeeFactorMultiplier))
|
|
}
|
|
|
|
// GetExecFeeFactorInternal returns the current execution fee factor in picoGAS
|
|
// units.
|
|
func (p *Policy) GetExecFeeFactorInternal(d *dao.Simple) int64 {
|
|
cache := d.GetROCache(p.ID).(*PolicyCache)
|
|
if cache.faunInitialized {
|
|
return int64(cache.execFeeFactor)
|
|
}
|
|
return int64(cache.execFeeFactor * vm.ExecFeeFactorMultiplier)
|
|
}
|
|
|
|
// getExecPicoFeeFactor is active starting from [config.HFFaun] and returns the
|
|
// current execution fee factor in picoGAS units.
|
|
func (p *Policy) getExecPicoFeeFactor(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
cache := ic.DAO.GetROCache(p.ID).(*PolicyCache)
|
|
return stackitem.NewBigInteger(big.NewInt(int64(cache.execFeeFactor)))
|
|
}
|
|
|
|
func (p *Policy) setExecFeeFactor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
value := toUint32(args[0])
|
|
maxValue := maxExecFeeFactor
|
|
if ic.IsHardforkEnabled(config.HFFaun) {
|
|
maxValue *= vm.ExecFeeFactorMultiplier
|
|
}
|
|
if value <= 0 || maxValue < value {
|
|
panic(fmt.Errorf("ExecFeeFactor must be between 1 and %d", maxExecFeeFactor))
|
|
}
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, int64(value))
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
cache.execFeeFactor = value
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
// isBlocked is Policy contract method that checks whether provided account is blocked.
|
|
func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
hash := toUint160(args[0])
|
|
_, blocked := p.isBlockedInternal(ic.DAO.GetROCache(p.ID).(*PolicyCache), hash)
|
|
return stackitem.NewBool(blocked)
|
|
}
|
|
|
|
// IsBlocked checks whether provided account is blocked. Normally it uses Policy
|
|
// cache, falling back to the DB queries when Policy cache is not available yet
|
|
// (the only case is native cache initialization pipeline, where native Neo cache
|
|
// is being initialized before the Policy's one).
|
|
func (p *Policy) IsBlocked(dao *dao.Simple, hash util.Uint160) bool {
|
|
cache := dao.GetROCache(p.ID)
|
|
if cache == nil {
|
|
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
|
return dao.GetStorageItem(p.ID, key) != nil
|
|
}
|
|
_, isBlocked := p.isBlockedInternal(cache.(*PolicyCache), hash)
|
|
return isBlocked
|
|
}
|
|
|
|
// isBlockedInternal checks whether provided account is blocked. It returns position
|
|
// of the blocked account in the blocked accounts list (or the position it should be
|
|
// put at).
|
|
func (p *Policy) isBlockedInternal(roCache *PolicyCache, hash util.Uint160) (int, bool) {
|
|
return slices.BinarySearchFunc(roCache.blockedAccounts, hash, util.Uint160.Compare)
|
|
}
|
|
|
|
func (p *Policy) getStoragePrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(big.NewInt(p.GetStoragePriceInternal(ic.DAO) / vm.ExecFeeFactorMultiplier))
|
|
}
|
|
|
|
// GetStoragePriceInternal returns the current storage price in picoGAS units.
|
|
func (p *Policy) GetStoragePriceInternal(d *dao.Simple) int64 {
|
|
cache := d.GetROCache(p.ID).(*PolicyCache)
|
|
return int64(cache.storagePrice) * vm.ExecFeeFactorMultiplier
|
|
}
|
|
|
|
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 1 and %d", maxStoragePrice))
|
|
}
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
setIntWithKey(p.ID, ic.DAO, storagePriceKey, int64(value))
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
cache.storagePrice = value
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
func (p *Policy) getAttributeFeeV0(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
return p.getAttributeFeeGeneric(ic, args, false)
|
|
}
|
|
|
|
func (p *Policy) getAttributeFeeV1(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
return p.getAttributeFeeGeneric(ic, args, true)
|
|
}
|
|
|
|
func (p *Policy) getAttributeFeeGeneric(ic *interop.Context, args []stackitem.Item, allowNotaryAssisted bool) stackitem.Item {
|
|
t := transaction.AttrType(toUint8(args[0]))
|
|
if !transaction.IsValidAttrType(ic.Chain.GetConfig().ReservedAttributes, t) ||
|
|
(!allowNotaryAssisted && t == transaction.NotaryAssistedT) {
|
|
panic(fmt.Errorf("invalid attribute type: %d", t))
|
|
}
|
|
return stackitem.NewBigInteger(big.NewInt(p.GetAttributeFeeInternal(ic.DAO, t)))
|
|
}
|
|
|
|
func (p *Policy) getBlockedAccounts(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
cache := ic.DAO.GetROCache(p.ID).(*PolicyCache)
|
|
cloned := slices.Clone(cache.blockedAccounts)
|
|
return stackitem.NewInterop(&iterator[util.Uint160]{keys: cloned})
|
|
}
|
|
|
|
// GetAttributeFeeInternal returns required transaction's attribute fee.
|
|
func (p *Policy) GetAttributeFeeInternal(d *dao.Simple, t transaction.AttrType) int64 {
|
|
cache := d.GetROCache(p.ID).(*PolicyCache)
|
|
v, ok := cache.attributeFee[t]
|
|
if !ok {
|
|
// We may safely omit this part, but let it be here in case if defaultAttributeFee value is changed.
|
|
v = defaultAttributeFee
|
|
}
|
|
return int64(v)
|
|
}
|
|
|
|
func (p *Policy) setAttributeFeeV0(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
return p.setAttributeFeeGeneric(ic, args, false)
|
|
}
|
|
|
|
func (p *Policy) setAttributeFeeV1(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
return p.setAttributeFeeGeneric(ic, args, true)
|
|
}
|
|
|
|
func (p *Policy) setAttributeFeeGeneric(ic *interop.Context, args []stackitem.Item, allowNotaryAssisted bool) stackitem.Item {
|
|
t := transaction.AttrType(toUint8(args[0]))
|
|
value := toUint32(args[1])
|
|
if !transaction.IsValidAttrType(ic.Chain.GetConfig().ReservedAttributes, t) ||
|
|
(!allowNotaryAssisted && t == transaction.NotaryAssistedT) {
|
|
panic(fmt.Errorf("invalid attribute type: %d", t))
|
|
}
|
|
if value > maxAttributeFee {
|
|
panic(fmt.Errorf("attribute value is out of range: %d", value))
|
|
}
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
setIntWithKey(p.ID, ic.DAO, []byte{attributeFeePrefix, byte(t)}, int64(value))
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
cache.attributeFee[t] = value
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
// setFeePerByte is a Policy contract method that sets transaction's fee per byte.
|
|
func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
value := toBigInt(args[0]).Int64()
|
|
if value < 0 || value > maxFeePerByte {
|
|
panic(fmt.Errorf("FeePerByte shouldn't be negative or greater than %d", maxFeePerByte))
|
|
}
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
setIntWithKey(p.ID, ic.DAO, feePerByteKey, value)
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
cache.feePerByte = value
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
// blockAccount is a Policy contract method that adds the given account hash to the list
|
|
// of blocked accounts.
|
|
func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
hash := toUint160(args[0])
|
|
for i := range ic.Natives {
|
|
if ic.Natives[i].Metadata().Hash == hash {
|
|
panic("cannot block native contract")
|
|
}
|
|
}
|
|
return stackitem.NewBool(p.BlockAccountInternal(ic, hash))
|
|
}
|
|
|
|
func (p *Policy) BlockAccountInternal(ic *interop.Context, hash util.Uint160) bool {
|
|
i, blocked := p.isBlockedInternal(ic.DAO.GetROCache(p.ID).(*PolicyCache), hash)
|
|
if blocked {
|
|
return false
|
|
}
|
|
var _ = p.Tutus.RevokeVotes(ic, hash) // ignore error, as in the reference.
|
|
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
|
ic.DAO.PutStorageItem(p.ID, key, state.StorageItem{})
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
if len(cache.blockedAccounts) == i {
|
|
cache.blockedAccounts = append(cache.blockedAccounts, hash)
|
|
} else {
|
|
cache.blockedAccounts = append(cache.blockedAccounts[:i+1], cache.blockedAccounts[i:]...)
|
|
cache.blockedAccounts[i] = hash
|
|
}
|
|
return true
|
|
}
|
|
|
|
// unblockAccount is a Policy contract method that removes the given account hash from
|
|
// the list of blocked accounts.
|
|
func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
hash := toUint160(args[0])
|
|
i, blocked := p.isBlockedInternal(ic.DAO.GetROCache(p.ID).(*PolicyCache), hash)
|
|
if !blocked {
|
|
return stackitem.NewBool(false)
|
|
}
|
|
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
|
ic.DAO.DeleteStorageItem(p.ID, key)
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
cache.blockedAccounts = append(cache.blockedAccounts[:i], cache.blockedAccounts[i+1:]...)
|
|
return stackitem.NewBool(true)
|
|
}
|
|
|
|
func (p *Policy) getMaxValidUntilBlockIncrement(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(big.NewInt(int64(p.GetMaxValidUntilBlockIncrementFromCache(ic.DAO))))
|
|
}
|
|
|
|
// GetMaxValidUntilBlockIncrementInternal returns current MaxValidUntilBlockIncrement.
|
|
// It respects Echidna enabling height.
|
|
func (p *Policy) GetMaxValidUntilBlockIncrementInternal(ic *interop.Context) uint32 {
|
|
if ic.IsHardforkEnabled(config.HFEchidna) {
|
|
return p.GetMaxValidUntilBlockIncrementFromCache(ic.DAO)
|
|
}
|
|
return ic.Chain.GetConfig().MaxValidUntilBlockIncrement
|
|
}
|
|
|
|
// GetMaxValidUntilBlockIncrementFromCache returns current MaxValidUntilBlockIncrement.
|
|
// It doesn't check neither Echidna enabling height nor cache initialization, so it's
|
|
// the caller's duty to ensure that Echidna is enabled before a call to this method.
|
|
func (p *Policy) GetMaxValidUntilBlockIncrementFromCache(d *dao.Simple) uint32 {
|
|
cache := d.GetROCache(p.ID).(*PolicyCache)
|
|
return cache.maxVUBIncrement
|
|
}
|
|
|
|
func (p *Policy) setMaxValidUntilBlockIncrement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
value := toUint32(args[0])
|
|
if value <= 0 || maxMaxVUBIncrement < value {
|
|
panic(fmt.Errorf("MaxValidUntilBlockIncrement should be positive and not greater than %d, got %d", maxMaxVUBIncrement, value))
|
|
}
|
|
mtb := p.GetMaxTraceableBlocksInternal(ic.DAO)
|
|
if value >= mtb {
|
|
panic(fmt.Errorf("MaxValidUntilBlockIncrement should be less than MaxTraceableBlocks %d, got %d", mtb, value))
|
|
}
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
setIntWithKey(p.ID, ic.DAO, maxVUBIncrementKey, int64(value))
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
cache.maxVUBIncrement = value
|
|
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
func (p *Policy) getMillisecondsPerBlock(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(big.NewInt(int64(p.GetMillisecondsPerBlockInternal(ic.DAO))))
|
|
}
|
|
|
|
// GetMillisecondsPerBlockInternal returns current block generation time in milliseconds.
|
|
func (p *Policy) GetMillisecondsPerBlockInternal(d *dao.Simple) uint32 {
|
|
cache := d.GetROCache(p.ID).(*PolicyCache)
|
|
return cache.msPerBlock
|
|
}
|
|
|
|
func (p *Policy) setMillisecondsPerBlock(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
value := toUint32(args[0])
|
|
if value <= 0 || maxMillisecondsPerBlock < value {
|
|
panic(fmt.Errorf("MillisecondsPerBlock should be positive and not greater than %d, got %d", maxMillisecondsPerBlock, value))
|
|
}
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
setIntWithKey(p.ID, ic.DAO, msPerBlockKey, int64(value))
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
old := cache.msPerBlock
|
|
cache.msPerBlock = value
|
|
|
|
err := ic.AddNotification(p.Hash, "MillisecondsPerBlockChanged", stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(int64(old))),
|
|
stackitem.NewBigInteger(big.NewInt(int64(value))),
|
|
}))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
func (p *Policy) getMaxTraceableBlocks(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(big.NewInt(int64(p.GetMaxTraceableBlocksInternal(ic.DAO))))
|
|
}
|
|
|
|
// GetMaxTraceableBlocksInternal returns current MaxValidUntilBlockIncrement.
|
|
func (p *Policy) GetMaxTraceableBlocksInternal(d *dao.Simple) uint32 {
|
|
cache := d.GetROCache(p.ID).(*PolicyCache)
|
|
return cache.maxTraceableBlocks
|
|
}
|
|
|
|
func (p *Policy) setMaxTraceableBlocks(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
value := toUint32(args[0])
|
|
if value <= 0 || maxMaxTraceableBlocks < value {
|
|
panic(fmt.Errorf("MaxTraceableBlocks should be positive and not greater than %d, got %d", maxMaxTraceableBlocks, value))
|
|
}
|
|
old := p.GetMaxTraceableBlocksInternal(ic.DAO)
|
|
if value > old {
|
|
panic(fmt.Errorf("MaxTraceableBlocks should not be greater than previous value %d, got %d", old, value))
|
|
}
|
|
maxVUBInc := p.GetMaxValidUntilBlockIncrementFromCache(ic.DAO)
|
|
if value <= maxVUBInc {
|
|
panic(fmt.Errorf("MaxTraceableBlocks should be larger than MaxValidUntilBlockIncrement %d, got %d", maxVUBInc, value))
|
|
}
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
|
|
setIntWithKey(p.ID, ic.DAO, MaxTraceableBlocksKey, int64(value))
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
cache.maxTraceableBlocks = value
|
|
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
// CheckPolicy checks whether a transaction conforms to the current policy restrictions,
|
|
// like not being signed by a blocked account or not exceeding the block-level system
|
|
// fee limit.
|
|
func (p *Policy) CheckPolicy(d *dao.Simple, tx *transaction.Transaction) error {
|
|
cache := d.GetROCache(p.ID).(*PolicyCache)
|
|
for _, signer := range tx.Signers {
|
|
if _, isBlocked := p.isBlockedInternal(cache, signer.Account); isBlocked {
|
|
return fmt.Errorf("account %s is blocked", signer.Account.StringLE())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WhitelistedFee checks whether the specified contract method is whitelisted and
|
|
// returns a non-negative execution fee in picoGAS units if so. It always uses
|
|
// native cache, hence cache is expected to be initialized by this moment.
|
|
func (p *Policy) WhitelistedFee(dao *dao.Simple, hash util.Uint160, offset int) int64 {
|
|
cache := dao.GetROCache(p.ID).(*PolicyCache)
|
|
i, ok := slices.BinarySearchFunc(cache.whitelistedContracts, whitelistedContract{
|
|
hash: hash,
|
|
offset: uint32(offset),
|
|
}, whitelistedContract.Compare)
|
|
if !ok {
|
|
return -1
|
|
}
|
|
return cache.whitelistedContracts[i].fee * vm.ExecFeeFactorMultiplier
|
|
}
|
|
|
|
// CleanWhitelist removes a contract with the specified hash from the list of
|
|
// whitelisted contracts.
|
|
func (p *Policy) CleanWhitelist(ic *interop.Context, cs *state.Contract) error {
|
|
var (
|
|
cache = ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
err error
|
|
)
|
|
cache.whitelistedContracts = slices.DeleteFunc(cache.whitelistedContracts, func(c whitelistedContract) bool {
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if !c.hash.Equals(cs.Hash) {
|
|
return false
|
|
}
|
|
k := makeWhitelistedKey(c.hash, c.offset)
|
|
ic.DAO.DeleteStorageItem(p.ID, k)
|
|
m := cs.Manifest.ABI.GetMethodByOffset(int(c.offset))
|
|
if m == nil {
|
|
err = fmt.Errorf("method with offset %d not found in contract %s", c.offset, cs.Hash.StringLE())
|
|
return false
|
|
}
|
|
err = ic.AddNotification(p.Hash, "WhitelistFeeChanged", stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray(cs.Hash.BytesBE()),
|
|
stackitem.NewByteArray([]byte(m.Name)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(len(m.Parameters)))),
|
|
stackitem.Null{},
|
|
}))
|
|
|
|
return err == nil
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (p *Policy) removeWhitelistFeeContract(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
h := toUint160(args[0])
|
|
method := toString(args[1])
|
|
argCnt := int(toInt64(args[2]))
|
|
|
|
cs, err := ic.GetContract(h)
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to get contract %s: %w", h.StringLE(), err))
|
|
}
|
|
md := cs.Manifest.ABI.GetMethod(method, argCnt)
|
|
if md == nil {
|
|
panic(fmt.Errorf("method not found: %s/%d", method, argCnt))
|
|
}
|
|
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
k := makeWhitelistedKey(h, uint32(md.Offset))
|
|
i, ok := slices.BinarySearchFunc(cache.whitelistedContracts, whitelistedContract{
|
|
hash: h,
|
|
offset: uint32(md.Offset),
|
|
}, whitelistedContract.Compare)
|
|
if !ok {
|
|
panic(fmt.Errorf("whitelist for %s/%d not found", h.StringLE(), md.Offset))
|
|
}
|
|
ic.DAO.DeleteStorageItem(p.ID, k)
|
|
cache.whitelistedContracts = slices.Delete(cache.whitelistedContracts, i, i+1)
|
|
|
|
err = ic.AddNotification(p.Hash, "WhitelistFeeChanged", stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray(h.BytesBE()),
|
|
stackitem.NewByteArray([]byte(method)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(argCnt))),
|
|
stackitem.Null{},
|
|
}))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
func (p *Policy) setWhitelistFeeContract(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
h := toUint160(args[0])
|
|
method := toString(args[1])
|
|
argCnt := int(toInt64(args[2]))
|
|
fee := toInt64(args[3])
|
|
if fee < 0 {
|
|
panic(fmt.Errorf("fee should be positive, got %d", fee))
|
|
}
|
|
if !p.Tutus.CheckCommittee(ic) {
|
|
panic("invalid committee signature")
|
|
}
|
|
|
|
cs, err := ic.GetContract(h)
|
|
if err != nil {
|
|
panic(fmt.Errorf("contract %s not found: %w", h.StringLE(), err))
|
|
}
|
|
md := cs.Manifest.ABI.GetMethod(method, argCnt)
|
|
if md == nil {
|
|
panic(fmt.Errorf("contract %s: method not found: %s/%d", h.StringLE(), method, argCnt))
|
|
}
|
|
|
|
setIntWithKey(p.ID, ic.DAO, makeWhitelistedKey(h, uint32(md.Offset)), fee)
|
|
cache := ic.DAO.GetRWCache(p.ID).(*PolicyCache)
|
|
c := whitelistedContract{
|
|
hash: h,
|
|
offset: uint32(md.Offset),
|
|
fee: fee,
|
|
}
|
|
i, ok := slices.BinarySearchFunc(cache.whitelistedContracts, c, whitelistedContract.Compare)
|
|
if !ok {
|
|
cache.whitelistedContracts = slices.Insert(cache.whitelistedContracts, i, c)
|
|
}
|
|
|
|
err = ic.AddNotification(p.Hash, "WhitelistFeeChanged", stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray(h.BytesBE()),
|
|
stackitem.NewByteArray([]byte(method)),
|
|
stackitem.NewBigInteger(big.NewInt(int64(argCnt))),
|
|
stackitem.NewBigInteger(big.NewInt(fee)),
|
|
}))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return stackitem.Null{}
|
|
}
|
|
|
|
func (p *Policy) getWhitelistFeeContracts(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
cache := ic.DAO.GetROCache(p.ID).(*PolicyCache)
|
|
res := make([]stackitem.Item, len(cache.whitelistedContracts))
|
|
for i, c := range cache.whitelistedContracts {
|
|
res[i], _ = c.ToStackItem() // never returns an error.
|
|
}
|
|
return stackitem.NewInterop(&iterator[stackitem.Item]{keys: res})
|
|
}
|
|
|
|
func makeWhitelistedKey(h util.Uint160, offset uint32) []byte {
|
|
k := make([]byte, 1+util.Uint160Size+4)
|
|
k[0] = whitelistedFeeContractPrefix
|
|
copy(k[1:], h.BytesBE())
|
|
binary.BigEndian.PutUint32(k[1+util.Uint160Size:], uint32(offset))
|
|
return k
|
|
}
|
|
|
|
// iterator provides an iterator over a slice of T. T must be convertible to
|
|
// [stackitem.Item] via [stackitem.Make].
|
|
type iterator[T any] struct {
|
|
keys []T
|
|
next bool
|
|
}
|
|
|
|
// Next advances the iterator and returns true if Value can be called at the
|
|
// current position.
|
|
func (i *iterator[T]) Next() bool {
|
|
if i.next {
|
|
i.keys = i.keys[1:]
|
|
}
|
|
i.next = len(i.keys) > 0
|
|
return i.next
|
|
}
|
|
|
|
// Value returns current iterators value.
|
|
func (i *iterator[T]) Value() stackitem.Item {
|
|
if !i.next {
|
|
panic("iterator index out of range")
|
|
}
|
|
return stackitem.Make(i.keys[0])
|
|
}
|