// Copyright (C) 2015-2026 The Neo Project. // // PolicyContract.cs file belongs to the neo project and is free // software distributed under the MIT software license, see the // accompanying file LICENSE in the main directory of the // repository or http://www.opensource.org/licenses/mit-license.php // for more details. // // Redistribution and use in source and binary forms with or without // modifications are permitted. #pragma warning disable IDE0051 using Neo.Extensions; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract.Iterators; using Neo.SmartContract.Manifest; using Neo.VM.Types; using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; namespace Neo.SmartContract.Native { /// /// A native contract that manages the system policies. /// public sealed class PolicyContract : NativeContract { /// /// The default execution fee factor. /// public const uint DefaultExecFeeFactor = 30; /// /// The default storage price. /// public const uint DefaultStoragePrice = 100000; /// /// The default network fee per byte of transactions. /// In the unit of datoshi, 1 datoshi = 1e-8 GAS /// public const uint DefaultFeePerByte = 1000; /// /// The default fee for attribute. /// public const uint DefaultAttributeFee = 0; /// /// The default fee for NotaryAssisted attribute. /// public const uint DefaultNotaryAssistedAttributeFee = 1000_0000; /// /// The maximum execution fee factor that the committee can set. /// public const ulong MaxExecFeeFactor = 100; /// /// The maximum fee for attribute that the committee can set. /// public const uint MaxAttributeFee = 10_0000_0000; /// /// The maximum storage price that the committee can set. /// public const uint MaxStoragePrice = 10000000; /// /// The maximum block generation time that the committee can set in milliseconds. /// public const uint MaxMillisecondsPerBlock = 30_000; /// /// The maximum MaxValidUntilBlockIncrement value that the committee can set. /// It is set to be a day of 1-second blocks. /// public const uint MaxMaxValidUntilBlockIncrement = 86400; /// /// The maximum MaxTraceableBlocks value that the committee can set. /// It is set to be a year of 15-second blocks. /// public const uint MaxMaxTraceableBlocks = 2102400; /// /// The maximum TemporaryStorageMaxTTL value that the committee can set. /// It is set to be 365 days in milliseconds. /// public const ulong MaxTemporaryStorageMaxTTL = 365 * 24 * 60 * 60 * 1_000UL; private const byte Prefix_BlockedAccount = 15; private const byte Prefix_WhitelistedFeeContracts = 16; private const byte Prefix_FeePerByte = 10; private const byte Prefix_ExecFeeFactor = 18; private const byte Prefix_StoragePrice = 19; private const byte Prefix_AttributeFee = 20; private const byte Prefix_MillisecondsPerBlock = 21; private const byte Prefix_MaxValidUntilBlockIncrement = 22; private const byte Prefix_MaxTraceableBlocks = 23; private const byte Prefix_TempStorageMaxTTL = 24; private readonly StorageKey _feePerByte; private readonly StorageKey _execFeeFactor; private readonly StorageKey _storagePrice; private readonly StorageKey _millisecondsPerBlock; private readonly StorageKey _maxValidUntilBlockIncrement; private readonly StorageKey _maxTraceableBlocks; private readonly StorageKey _tempStorageMaxTTL; private const ulong RequiredTimeForRecoverFund = 365 * 24 * 60 * 60 * 1_000UL; // 1 year in milliseconds /// /// The event name for the block generation time changed. /// private const string MillisecondsPerBlockChangedEventName = "MillisecondsPerBlockChanged"; private const string RecoveredFundEventName = "RecoveredFund"; private const string WhitelistChangedEventName = "WhitelistFeeChanged"; [ContractEvent(Hardfork.HF_Echidna, 0, name: MillisecondsPerBlockChangedEventName, "old", ContractParameterType.Integer, "new", ContractParameterType.Integer )] [ContractEvent(Hardfork.HF_Faun, 1, name: WhitelistChangedEventName, "contract", ContractParameterType.Hash160, "method", ContractParameterType.String, "argCount", ContractParameterType.Integer, "fee", ContractParameterType.Any )] [ContractEvent(Hardfork.HF_Faun, 2, name: RecoveredFundEventName, "account", ContractParameterType.Hash160)] internal PolicyContract() : base() { _feePerByte = CreateStorageKey(Prefix_FeePerByte); _execFeeFactor = CreateStorageKey(Prefix_ExecFeeFactor); _storagePrice = CreateStorageKey(Prefix_StoragePrice); _millisecondsPerBlock = CreateStorageKey(Prefix_MillisecondsPerBlock); _maxValidUntilBlockIncrement = CreateStorageKey(Prefix_MaxValidUntilBlockIncrement); _maxTraceableBlocks = CreateStorageKey(Prefix_MaxTraceableBlocks); _tempStorageMaxTTL = CreateStorageKey(Prefix_TempStorageMaxTTL); } internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfork? hardfork) { if (hardfork == ActiveIn) { engine.SnapshotCache.Add(_feePerByte, new StorageItem(DefaultFeePerByte)); engine.SnapshotCache.Add(_execFeeFactor, new StorageItem(DefaultExecFeeFactor)); engine.SnapshotCache.Add(_storagePrice, new StorageItem(DefaultStoragePrice)); } if (hardfork == Hardfork.HF_Echidna) { engine.SnapshotCache.Add(CreateStorageKey(Prefix_AttributeFee, (byte)TransactionAttributeType.NotaryAssisted), new StorageItem(DefaultNotaryAssistedAttributeFee)); engine.SnapshotCache.Add(_millisecondsPerBlock, new StorageItem(engine.ProtocolSettings.MillisecondsPerBlock)); engine.SnapshotCache.Add(_maxValidUntilBlockIncrement, new StorageItem(engine.ProtocolSettings.MaxValidUntilBlockIncrement)); engine.SnapshotCache.Add(_maxTraceableBlocks, new StorageItem(engine.ProtocolSettings.MaxTraceableBlocks)); } if (hardfork == Hardfork.HF_Faun) { // Add decimals to exec fee factor: after Faun Hardfork the unit is pico-gas, before it was datoshi. var item = engine.SnapshotCache.GetAndChange(_execFeeFactor) ?? throw new InvalidOperationException("Policy was not initialized"); item.Set((uint)(BigInteger)item * ApplicationEngine.FeeFactor); // Add timestamp of the current block to blocked accounts. var time = engine.GetTime(); foreach (var (key, _) in engine.SnapshotCache.Find(CreateStorageKey(Prefix_BlockedAccount), SeekDirection.Forward)) { var blockedAcc = engine.SnapshotCache.GetAndChange(key)!; blockedAcc.Set(time); } } if (hardfork == Hardfork.HF_Huyao) { engine.SnapshotCache.Add(_tempStorageMaxTTL, new StorageItem(engine.ProtocolSettings.TemporaryStorageMaxTTL)); } return ContractTask.CompletedTask; } /// /// Gets the network fee per transaction byte. /// /// The snapshot used to read data. /// The network fee per transaction byte. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public long GetFeePerByte(IReadOnlyStore snapshot) { return (long)(BigInteger)snapshot[_feePerByte]; } /// /// Gets the execution fee factor. This is a multiplier that can be adjusted by the committee to adjust the system fees for transactions. /// /// The execution engine. /// The execution fee factor. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public uint GetExecFeeFactor(ApplicationEngine engine) { if (engine.IsHardforkEnabled(Hardfork.HF_Faun)) return (uint)((BigInteger)engine.SnapshotCache[_execFeeFactor] / ApplicationEngine.FeeFactor); return (uint)(BigInteger)engine.SnapshotCache[_execFeeFactor]; } public long GetExecFeeFactor(ProtocolSettings settings, IReadOnlyStore snapshot, uint index) { if (settings.IsHardforkEnabled(Hardfork.HF_Faun, index)) return (long)((BigInteger)snapshot[_execFeeFactor] / ApplicationEngine.FeeFactor); return (long)(BigInteger)snapshot[_execFeeFactor]; } /// /// Gets the execution fee factor. This is a multiplier that can be adjusted by the committee to adjust the system fees for transactions. /// /// The execution engine. /// The execution fee factor in the unit of pico Gas. 1 picoGAS = 1e-12 GAS [ContractMethod(Hardfork.HF_Faun, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public BigInteger GetExecPicoFeeFactor(ApplicationEngine engine) { return (BigInteger)engine.SnapshotCache[_execFeeFactor]; } /// /// Gets the storage price. /// /// The snapshot used to read data. /// The storage price. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public uint GetStoragePrice(IReadOnlyStore snapshot) { return (uint)(BigInteger)snapshot[_storagePrice]; } /// /// Gets the block generation time in milliseconds. /// /// The snapshot used to read data. /// The block generation time in milliseconds. [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public uint GetMillisecondsPerBlock(IReadOnlyStore snapshot) { return (uint)(BigInteger)snapshot[_millisecondsPerBlock]; } /// /// Gets the upper increment size of blockchain height (in blocks) exceeding /// that a transaction should fail validation. /// /// The snapshot used to read data. /// MaxValidUntilBlockIncrement value. [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public uint GetMaxValidUntilBlockIncrement(IReadOnlyStore snapshot) { return (uint)(BigInteger)snapshot[_maxValidUntilBlockIncrement]; } /// /// Gets the length of the chain accessible to smart contracts. /// /// The snapshot used to read data. /// MaxTraceableBlocks value. [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public uint GetMaxTraceableBlocks(IReadOnlyStore snapshot) { return (uint)(BigInteger)snapshot[_maxTraceableBlocks]; } /// /// Gets the fee for attribute before Echidna hardfork. NotaryAssisted attribute type not supported. /// /// The snapshot used to read data. /// Attribute type excluding /// The fee for attribute. [ContractMethod(true, Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates, Name = "getAttributeFee")] public uint GetAttributeFeeV0(IReadOnlyStore snapshot, byte attributeType) { return GetAttributeFee(snapshot, attributeType, false); } /// /// Gets the fee for attribute after Echidna hardfork. NotaryAssisted attribute type supported. /// /// The snapshot used to read data. /// Attribute type /// The fee for attribute. [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates, Name = "getAttributeFee")] public uint GetAttributeFeeV1(IReadOnlyStore snapshot, byte attributeType) { return GetAttributeFee(snapshot, attributeType, true); } /// /// Generic handler for GetAttributeFeeV0 and GetAttributeFee that /// gets the fee for attribute. /// /// The snapshot used to read data. /// Attribute type /// Whether to support attribute type. /// The fee for attribute. private uint GetAttributeFee(IReadOnlyStore snapshot, byte attributeType, bool allowNotaryAssisted) { if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType) || (!allowNotaryAssisted && attributeType == (byte)(TransactionAttributeType.NotaryAssisted))) { throw new InvalidOperationException($"Attribute type {attributeType} is not supported."); } var key = CreateStorageKey(Prefix_AttributeFee, attributeType); return snapshot.TryGet(key, out var item) ? (uint)(BigInteger)item : DefaultAttributeFee; } /// /// Determines whether the specified account is blocked. /// /// The snapshot used to read data. /// The account to be checked. /// if the account is blocked; otherwise, . [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public bool IsBlocked(IReadOnlyStore snapshot, UInt160 account) { return snapshot.Contains(CreateStorageKey(Prefix_BlockedAccount, account)); } internal bool IsWhitelistFeeContract(DataCache snapshot, UInt160 contractHash, ContractMethodDescriptor method, [NotNullWhen(true)] out long? fixedFee) { // Check contract existence var currentContract = ContractManagement.GetContract(snapshot, contractHash); if (currentContract != null) { // Check state existence var item = snapshot.TryGet(CreateStorageKey(Prefix_WhitelistedFeeContracts, contractHash, method.Offset))?.GetInteroperable(); if (item != null) { fixedFee = item.FixedFee; return true; } } fixedFee = null; return false; } /// /// Remove whitelisted Fee contracts /// /// The execution engine. /// The contract to set the whitelist /// Method /// Argument count [ContractMethod(Hardfork.HF_Faun, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] private void RemoveWhitelistFeeContract(ApplicationEngine engine, UInt160 contractHash, string method, int argCount) { if (!CheckCommittee(engine)) throw new InvalidOperationException("Invalid committee signature"); // Validate methods var contract = ContractManagement.GetContract(engine.SnapshotCache, contractHash) ?? throw new InvalidOperationException("Is not a valid contract"); // If exists multiple instance a exception is throwed var methodDescriptor = contract.Manifest.Abi.Methods.SingleOrDefault(u => u.Name == method && u.Parameters.Length == argCount) ?? throw new InvalidOperationException($"Method {method} with {argCount} args was not found in {contractHash}"); var key = CreateStorageKey(Prefix_WhitelistedFeeContracts, contractHash, methodDescriptor.Offset); if (!engine.SnapshotCache.Contains(key)) throw new InvalidOperationException("Whitelist not found"); engine.SnapshotCache.Delete(key); // Emit event engine.SendNotification(Hash, WhitelistChangedEventName, [new ByteString(contractHash.ToArray()), method, argCount, StackItem.Null]); } internal int CleanWhitelist(ApplicationEngine engine, ContractState contract) { var count = 0; var searchKey = CreateStorageKey(Prefix_WhitelistedFeeContracts, contract.Hash); foreach (var (key, value) in engine.SnapshotCache.Find(searchKey, SeekDirection.Forward)) { engine.SnapshotCache.Delete(key); count++; var data = value.GetInteroperable(); engine.SendNotification(Hash, WhitelistChangedEventName, [ new ByteString(contract.Hash.ToArray()), data.Method, data.ArgCount, StackItem.Null ]); } return count; } /// /// Set whitelisted Fee contracts /// /// The execution engine. /// The contract to set the whitelist /// Method /// Argument count /// Fixed execution fee [ContractMethod(Hardfork.HF_Faun, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] internal void SetWhitelistFeeContract(ApplicationEngine engine, UInt160 contractHash, string method, int argCount, long fixedFee) { ArgumentOutOfRangeException.ThrowIfNegative(fixedFee, nameof(fixedFee)); if (!CheckCommittee(engine)) throw new InvalidOperationException("Invalid committee signature"); // Validate methods var contract = ContractManagement.GetContract(engine.SnapshotCache, contractHash) ?? throw new InvalidOperationException("Is not a valid contract"); // If exists multiple instance a exception is throwed var methodDescriptor = contract.Manifest.Abi.Methods.SingleOrDefault(u => u.Name == method && u.Parameters.Length == argCount) ?? throw new InvalidOperationException($"Method {method} with {argCount} args was not found in {contractHash}"); var key = CreateStorageKey(Prefix_WhitelistedFeeContracts, contractHash, methodDescriptor.Offset); // Set var entry = engine.SnapshotCache.GetAndChange(key, () => new StorageItem(new WhitelistedContract() { ContractHash = contractHash, Method = method, ArgCount = argCount, FixedFee = fixedFee })); entry.GetInteroperable().FixedFee = fixedFee; entry.Seal(); // Emit event engine.SendNotification(Hash, WhitelistChangedEventName, [new VM.Types.ByteString(contractHash.ToArray()), method, argCount, fixedFee]); } /// /// Sets the block generation time in milliseconds. /// /// The execution engine. /// The block generation time in milliseconds. Must be between 1 and MaxBlockGenTime. /// Thrown when the provided value is outside the allowed range. /// Thrown when the caller is not a committee member. [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] public void SetMillisecondsPerBlock(ApplicationEngine engine, uint value) { if (value == 0 || value > MaxMillisecondsPerBlock) throw new ArgumentOutOfRangeException(nameof(value), $"MillisecondsPerBlock must be between [1, {MaxMillisecondsPerBlock}], got {value}"); AssertCommittee(engine); var oldTime = GetMillisecondsPerBlock(engine.SnapshotCache); engine.SnapshotCache.GetAndChange(_millisecondsPerBlock)!.Set(value); engine.SendNotification(Hash, MillisecondsPerBlockChangedEventName, [new VM.Types.Integer(oldTime), new VM.Types.Integer(value)]); } /// /// Sets the fee for attribute before Echidna hardfork. NotaryAssisted attribute type not supported. /// /// The engine used to check committee witness and read data. /// Attribute type excluding /// Attribute fee value /// The fee for attribute. [ContractMethod(true, Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States, Name = "setAttributeFee")] private void SetAttributeFeeV0(ApplicationEngine engine, byte attributeType, uint value) { SetAttributeFee(engine, attributeType, value, false); } /// /// Sets the fee for attribute after Echidna hardfork. NotaryAssisted attribute type supported. /// /// The engine used to check committee witness and read data. /// Attribute type excluding /// Attribute fee value /// The fee for attribute. [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States, Name = "setAttributeFee")] private void SetAttributeFeeV1(ApplicationEngine engine, byte attributeType, uint value) { SetAttributeFee(engine, attributeType, value, true); } /// /// Generic handler for SetAttributeFeeV0 and SetAttributeFeeV1 that /// gets the fee for attribute. /// /// The engine used to check committee witness and read data. /// Attribute type /// Attribute fee value /// Whether to support attribute type. /// The fee for attribute. private void SetAttributeFee(ApplicationEngine engine, byte attributeType, uint value, bool allowNotaryAssisted) { if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType) || (!allowNotaryAssisted && attributeType == (byte)(TransactionAttributeType.NotaryAssisted))) { throw new InvalidOperationException($"Attribute type {attributeType} is not supported."); } if (value > MaxAttributeFee) throw new ArgumentOutOfRangeException(nameof(value), $"AttributeFee must be less than {MaxAttributeFee}"); AssertCommittee(engine); engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_AttributeFee, attributeType), () => new StorageItem(DefaultAttributeFee)).Set(value); } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetFeePerByte(ApplicationEngine engine, long value) { if (value < 0 || value > 1_00000000) throw new ArgumentOutOfRangeException(nameof(value), $"FeePerByte must be between [0, 100000000], got {value}"); AssertCommittee(engine); engine.SnapshotCache.GetAndChange(_feePerByte)!.Set(value); } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetExecFeeFactor(ApplicationEngine engine, ulong value) { // After FAUN hardfork, the max exec fee factor is with decimals defined in ApplicationEngine.FeeFactor var maxValue = engine.IsHardforkEnabled(Hardfork.HF_Faun) ? ApplicationEngine.FeeFactor * MaxExecFeeFactor : MaxExecFeeFactor; if (value == 0 || value > maxValue) throw new ArgumentOutOfRangeException(nameof(value), $"ExecFeeFactor must be between [1, {maxValue}], got {value}"); AssertCommittee(engine); engine.SnapshotCache.GetAndChange(_execFeeFactor)!.Set(value); } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetStoragePrice(ApplicationEngine engine, uint value) { if (value == 0 || value > MaxStoragePrice) throw new ArgumentOutOfRangeException(nameof(value), $"StoragePrice must be between [1, {MaxStoragePrice}], got {value}"); AssertCommittee(engine); engine.SnapshotCache.GetAndChange(_storagePrice)!.Set(value); } [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetMaxValidUntilBlockIncrement(ApplicationEngine engine, uint value) { if (value == 0 || value > MaxMaxValidUntilBlockIncrement) throw new ArgumentOutOfRangeException(nameof(value), $"MaxValidUntilBlockIncrement must be between [1, {MaxMaxValidUntilBlockIncrement}], got {value}"); var mtb = GetMaxTraceableBlocks(engine.SnapshotCache); if (value >= mtb) throw new InvalidOperationException($"MaxValidUntilBlockIncrement must be lower than MaxTraceableBlocks ({value} vs {mtb})"); AssertCommittee(engine); engine.SnapshotCache.GetAndChange(_maxValidUntilBlockIncrement)!.Set(value); } /// /// Sets the length of the chain accessible to smart contracts. /// /// The engine used to check committee witness and read data. /// MaxTraceableBlocks value. [ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetMaxTraceableBlocks(ApplicationEngine engine, uint value) { if (value == 0 || value > MaxMaxTraceableBlocks) throw new ArgumentOutOfRangeException(nameof(value), $"MaxTraceableBlocks must be between [1, {MaxMaxTraceableBlocks}], got {value}"); var oldVal = GetMaxTraceableBlocks(engine.SnapshotCache); if (value > oldVal) throw new InvalidOperationException($"MaxTraceableBlocks can not be increased (old {oldVal}, new {value})"); var mVUBIncrement = GetMaxValidUntilBlockIncrement(engine.SnapshotCache); if (value <= mVUBIncrement) throw new InvalidOperationException($"MaxTraceableBlocks must be larger than MaxValidUntilBlockIncrement ({value} vs {mVUBIncrement})"); AssertCommittee(engine); engine.SnapshotCache.GetAndChange(_maxTraceableBlocks)!.Set(value); } [ContractMethod(true, Hardfork.HF_Faun, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States, Name = "blockAccount")] private async ContractTask BlockAccountV0(ApplicationEngine engine, UInt160 account) { AssertCommittee(engine); return await BlockAccountInternal(engine, account); } [ContractMethod(Hardfork.HF_Faun, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify, Name = "blockAccount")] private async ContractTask BlockAccountV1(ApplicationEngine engine, UInt160 account) { AssertCommittee(engine); return await BlockAccountInternal(engine, account); } internal async ContractTask BlockAccountInternal(ApplicationEngine engine, UInt160 account) { if (IsNative(account)) throw new InvalidOperationException("Cannot block a native contract."); var key = CreateStorageKey(Prefix_BlockedAccount, account); if (engine.SnapshotCache.Contains(key)) return false; if (engine.IsHardforkEnabled(Hardfork.HF_Faun)) await NEO.VoteInternal(engine, account, null); engine.SnapshotCache.Add(key, // Set request time for recover funds engine.IsHardforkEnabled(Hardfork.HF_Faun) ? new StorageItem(engine.GetTime()) : new StorageItem([])); return true; } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private bool UnblockAccount(ApplicationEngine engine, UInt160 account) { AssertCommittee(engine); var key = CreateStorageKey(Prefix_BlockedAccount, account); if (!engine.SnapshotCache.Contains(key)) return false; engine.SnapshotCache.Delete(key); return true; } [ContractMethod(Hardfork.HF_Faun, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] private StorageIterator GetBlockedAccounts(DataCache snapshot) { const FindOptions options = FindOptions.RemovePrefix | FindOptions.KeysOnly; var enumerator = snapshot .Find(CreateStorageKey(Prefix_BlockedAccount), SeekDirection.Forward) .GetEnumerator(); return new StorageIterator(enumerator, 1, options); } [ContractMethod(Hardfork.HF_Faun, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.All)] internal async ContractTask RecoverFund(ApplicationEngine engine, UInt160 account, UInt160 token) { var committeeMultiSigAddr = AssertAlmostFullCommittee(engine); // Set request time var key = CreateStorageKey(Prefix_BlockedAccount, account); var entry = engine.SnapshotCache.TryGet(key) ?? throw new InvalidOperationException("Request not found."); var elapsedTime = engine.GetTime() - (BigInteger)entry; if (elapsedTime < RequiredTimeForRecoverFund) { var remaining = (BigInteger)RequiredTimeForRecoverFund - elapsedTime; var days = remaining / 86_400_000; var hours = (remaining % 86_400_000) / 3_600_000; var minutes = (remaining % 3_600_000) / 60_000; var seconds = (remaining % 60_000) / 1_000; var timeMsg = days > 0 ? $"{days}d {hours}h {minutes}m" : hours > 0 ? $"{hours}h {minutes}m {seconds}s" : minutes > 0 ? $"{minutes}m {seconds}s" : $"{seconds}s"; throw new InvalidOperationException($"Request must be signed at least 1 year ago. Remaining time: {timeMsg}."); } // Validate contract exists var contract = ContractManagement.GetContract(engine.SnapshotCache, token) ?? throw new InvalidOperationException($"Contract {token} does not exist."); // Validate contract implements NEP-17 standard if (!contract.Manifest.SupportedStandards.Contains("NEP-17")) throw new InvalidOperationException($"Contract {token} does not implement NEP-17 standard."); // Check balance var balance = await engine.CallFromNativeContractAsync(account, token, "balanceOf", account.ToArray()); if (balance > 0) { // Transfer var result = await engine.CallFromNativeContractAsync(account, token, "transfer", account.ToArray(), NativeContract.Treasury.Hash.ToArray(), balance, StackItem.Null); if (!result) throw new InvalidOperationException($"Transfer of {balance} from {account} to {NativeContract.Treasury.Hash} failed in contract {token}."); // notify engine.SendNotification(Hash, RecoveredFundEventName, [new ByteString(account.ToArray())]); return true; } return false; } [ContractMethod(Hardfork.HF_Faun, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] internal StorageIterator GetWhitelistFeeContracts(DataCache snapshot) { const FindOptions options = FindOptions.RemovePrefix | FindOptions.ValuesOnly | FindOptions.DeserializeValues; var enumerator = snapshot .Find(CreateStorageKey(Prefix_WhitelistedFeeContracts), SeekDirection.Forward) .GetEnumerator(); return new StorageIterator(enumerator, 1, options); } /// /// Gets the maximum allowed TTL value for key-value records in the native TemporaryStorage contract. /// /// The snapshot used to read data. /// The maximum allowed TTL value in milliseconds. [ContractMethod(Hardfork.HF_Huyao, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public ulong GetTemporaryStorageMaxTTL(IReadOnlyStore snapshot) { return (ulong)(BigInteger)snapshot[_tempStorageMaxTTL]; } /// /// Sets the maximum allowed TTL value for key-value records in the native TemporaryStorage contract. /// /// The engine used to check committee witness and read data. /// TemporaryStorageMaxTTL value. [ContractMethod(Hardfork.HF_Huyao, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetTemporaryStorageMaxTTL(ApplicationEngine engine, ulong value) { if (value > MaxTemporaryStorageMaxTTL) throw new ArgumentOutOfRangeException(nameof(value), $"TemporaryStorageMaxTTL must be between [0, {MaxTemporaryStorageMaxTTL}], got {value}"); AssertCommittee(engine); engine.SnapshotCache.GetAndChange(_tempStorageMaxTTL)!.Set(value); } } }