-
Notifications
You must be signed in to change notification settings - Fork 1k
[N3] Request blocked funds #4328
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master-n3
Are you sure you want to change the base?
Changes from all commits
c9662dc
45c442c
a10011f
7be6d38
3f5dc7c
cbcdfeb
bbb20d6
aacebf6
2939a5c
6c2ab43
a59d9cb
9d0974f
0b3e3d8
083f41e
d85f6c5
a73bb20
50532ee
f9416b1
8f70d78
04febb0
364df1f
f3341bf
ab2e5b3
83805d4
6b25341
b0f2897
04bb62f
1f15256
0e8fd58
b673923
cefe1f2
f40f1a0
af77c5d
9b51e77
7a8bec6
295b29a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,8 +16,10 @@ | |
| using Neo.Persistence; | ||
| using Neo.SmartContract.Iterators; | ||
| using Neo.SmartContract.Manifest; | ||
| using Neo.VM.Types; | ||
| using System; | ||
| using System.Buffers.Binary; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Linq; | ||
| using System.Numerics; | ||
|
|
@@ -103,12 +105,13 @@ public sealed class PolicyContract : NativeContract | |
| private readonly StorageKey _millisecondsPerBlock; | ||
| private readonly StorageKey _maxValidUntilBlockIncrement; | ||
| private readonly StorageKey _maxTraceableBlocks; | ||
| private const ulong RequiredTimeForRecoverFunds = 365 * 24 * 60 * 60 * 1_000UL; // 1 year in milliseconds | ||
|
|
||
| /// <summary> | ||
| /// The event name for the block generation time changed. | ||
| /// </summary> | ||
| private const string MillisecondsPerBlockChangedEventName = "MillisecondsPerBlockChanged"; | ||
|
|
||
| private const string RecoveredFundsEventName = "RecoveredFunds"; | ||
| private const string WhitelistChangedEventName = "WhitelistFeeChanged"; | ||
|
|
||
| [ContractEvent(Hardfork.HF_Echidna, 0, name: MillisecondsPerBlockChangedEventName, | ||
|
|
@@ -121,6 +124,7 @@ public sealed class PolicyContract : NativeContract | |
| "argCount", ContractParameterType.Integer, | ||
| "fee", ContractParameterType.Any | ||
| )] | ||
| [ContractEvent(Hardfork.HF_Faun, 2, name: RecoveredFundsEventName, "account", ContractParameterType.Hash160)] | ||
| internal PolicyContract() : base() | ||
| { | ||
| _feePerByte = CreateStorageKey(Prefix_FeePerByte); | ||
|
|
@@ -583,12 +587,27 @@ internal async ContractTask<bool> BlockAccountInternal(ApplicationEngine engine, | |
|
|
||
| var key = CreateStorageKey(Prefix_BlockedAccount, account); | ||
|
|
||
| if (engine.SnapshotCache.Contains(key)) return false; | ||
| var blockData = engine.SnapshotCache.TryGet(key); | ||
| if (blockData != null) | ||
| { | ||
| // Check if it is stored the recover funds time | ||
| if (blockData.Value.Length == 0 && engine.IsHardforkEnabled(Hardfork.HF_Faun)) | ||
| { | ||
| // Don't modify it if already exists | ||
| blockData.Set(engine.GetTime()); | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| if (engine.IsHardforkEnabled(Hardfork.HF_Faun)) | ||
| await NEO.VoteInternal(engine, account, null); | ||
|
|
||
| engine.SnapshotCache.Add(key, new StorageItem([])); | ||
| engine.SnapshotCache.Add(key, | ||
| // Set request time for recover funds | ||
| engine.IsHardforkEnabled(Hardfork.HF_Faun) ? new StorageItem(engine.GetTime()) | ||
| : new StorageItem([])); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
|
|
@@ -597,7 +616,6 @@ private bool UnblockAccount(ApplicationEngine engine, UInt160 account) | |
| { | ||
| AssertCommittee(engine); | ||
|
|
||
|
|
||
| var key = CreateStorageKey(Prefix_BlockedAccount, account); | ||
| if (!engine.SnapshotCache.Contains(key)) return false; | ||
|
|
||
|
|
@@ -615,6 +633,76 @@ private StorageIterator GetBlockedAccounts(DataCache snapshot) | |
| return new StorageIterator(enumerator, 1, options); | ||
| } | ||
|
|
||
| [ContractMethod(Hardfork.HF_Faun, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] | ||
| internal async ContractTask RecoverFunds(ApplicationEngine engine, UInt160 account, VM.Types.Array extraTokens) | ||
| { | ||
| var committeeMultiSigAddr = AssertAlmostFullCommittee(engine); | ||
|
|
||
| // Set request time | ||
|
|
||
| var key = CreateStorageKey(Prefix_BlockedAccount, account); | ||
| var entry = engine.SnapshotCache.GetAndChange(key, null) | ||
| ?? throw new InvalidOperationException("Request not found."); | ||
| if (engine.GetTime() - (BigInteger)entry < RequiredTimeForRecoverFunds) | ||
| throw new InvalidOperationException("Request must be signed at least 1 year ago."); | ||
|
|
||
| // Validate and collect extra NEP17 tokens | ||
|
|
||
| var validatedTokens = new HashSet<UInt160> | ||
| { | ||
| NEO.Hash, | ||
| GAS.Hash | ||
| }; | ||
|
|
||
| foreach (var tokenItem in extraTokens) | ||
| { | ||
| var span = tokenItem.GetSpan(); | ||
| if (span.Length != UInt160.Length) | ||
| throw new ArgumentException($"Invalid token hash length: expected {UInt160.Length} bytes, got {span.Length} bytes."); | ||
|
|
||
| var contractHash = new UInt160(span); | ||
|
|
||
| // Validate contract exists | ||
| var contract = ContractManagement.GetContract(engine.SnapshotCache, contractHash); | ||
| if (contract == null) | ||
| throw new InvalidOperationException($"Contract {contractHash} does not exist."); | ||
|
|
||
| // Validate contract implements NEP-17 standard | ||
| if (!contract.Manifest.SupportedStandards.Contains("NEP-17")) | ||
| throw new InvalidOperationException($"Contract {contractHash} does not implement NEP-17 standard."); | ||
|
|
||
| // Prevent NEO and GAS from being in extraTokens | ||
| if (contractHash == NEO.Hash || contractHash == GAS.Hash) | ||
| throw new InvalidOperationException($"NEO and GAS should not be included in extraTokens. They are automatically processed."); | ||
|
|
||
| // Prevent duplicate tokens | ||
| if (!validatedTokens.Add(contractHash)) | ||
| throw new InvalidOperationException($"Duplicate token {contractHash} in extraTokens."); | ||
| } | ||
|
|
||
| // Remove and notify | ||
|
|
||
| engine.SendNotification(Hash, RecoveredFundsEventName, [new ByteString(account.ToArray())]); | ||
|
|
||
| // Transfer funds, NEO, GAS and extra NEP17 tokens | ||
|
|
||
| foreach (var contractHash in validatedTokens) | ||
| { | ||
| // Check balance | ||
| var balance = await engine.CallFromNativeContractAsync<BigInteger>(account, contractHash, "balanceOf", account.ToArray()); | ||
|
|
||
| if (balance > 0) | ||
| { | ||
| // transfer | ||
| var result = await engine.CallFromNativeContractAsync<bool>(account, contractHash, "transfer", | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @superboyiii I receive an error in your UT in But it should work with real environment
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Require #4388
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| account.ToArray(), NativeContract.Treasury.Hash.ToArray(), balance, StackItem.Null); | ||
|
|
||
| if (!result) | ||
| throw new InvalidOperationException($"Transfer of {balance} from {account} to {committeeMultiSigAddr} failed in contract {contractHash}."); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| [ContractMethod(Hardfork.HF_Faun, CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] | ||
| internal StorageIterator GetWhitelistFeeContracts(DataCache snapshot) | ||
| { | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.