-
Notifications
You must be signed in to change notification settings - Fork 1k
/
ContractManagement.cs
298 lines (264 loc) · 14.2 KB
/
ContractManagement.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// Copyright (C) 2015-2024 The Neo Project.
//
// ContractManagement.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.IO;
using Neo.Network.P2P.Payloads;
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.Linq;
using System.Numerics;
namespace Neo.SmartContract.Native
{
/// <summary>
/// A native contract used to manage all deployed smart contracts.
/// </summary>
public sealed class ContractManagement : NativeContract
{
private const byte Prefix_MinimumDeploymentFee = 20;
private const byte Prefix_NextAvailableId = 15;
private const byte Prefix_Contract = 8;
private const byte Prefix_ContractHash = 12;
[ContractEvent(0, name: "Deploy", "Hash", ContractParameterType.Hash160)]
[ContractEvent(1, name: "Update", "Hash", ContractParameterType.Hash160)]
[ContractEvent(2, name: "Destroy", "Hash", ContractParameterType.Hash160)]
internal ContractManagement() : base() { }
private int GetNextAvailableId(DataCache snapshot)
{
StorageItem item = snapshot.GetAndChange(CreateStorageKey(Prefix_NextAvailableId));
int value = (int)(BigInteger)item;
item.Add(1);
return value;
}
internal override ContractTask Initialize(ApplicationEngine engine, Hardfork? hardfork)
{
if (hardfork == ActiveIn)
{
engine.Snapshot.Add(CreateStorageKey(Prefix_MinimumDeploymentFee), new StorageItem(10_00000000));
engine.Snapshot.Add(CreateStorageKey(Prefix_NextAvailableId), new StorageItem(1));
}
return ContractTask.CompletedTask;
}
private async ContractTask OnDeploy(ApplicationEngine engine, ContractState contract, StackItem data, bool update)
{
ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 2);
if (md is not null)
await engine.CallFromNativeContract(Hash, contract.Hash, md.Name, data, update);
engine.SendNotification(Hash, update ? "Update" : "Deploy", new VM.Types.Array(engine.ReferenceCounter) { contract.Hash.ToArray() });
}
internal override async ContractTask OnPersist(ApplicationEngine engine)
{
foreach (NativeContract contract in Contracts)
{
if (contract.IsInitializeBlock(engine.ProtocolSettings, engine.PersistingBlock.Index, out Hardfork? hf))
{
ContractState contractState = contract.GetContractState(engine.ProtocolSettings, engine.PersistingBlock.Index);
StorageItem state = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Contract).Add(contract.Hash));
if (state is null)
{
// Create the contract state
engine.Snapshot.Add(CreateStorageKey(Prefix_Contract).Add(contract.Hash), new StorageItem(contractState));
engine.Snapshot.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(contract.Hash.ToArray()));
}
else
{
// Parse old contract
var oldContract = state.GetInteroperable<ContractState>(false);
// Increase the update counter
oldContract.UpdateCounter++;
// Modify nef and manifest
oldContract.Nef = contractState.Nef;
oldContract.Manifest = contractState.Manifest;
}
await contract.Initialize(engine, hf);
// Emit native contract notification
engine.SendNotification(Hash, state is null ? "Deploy" : "Update", new VM.Types.Array(engine.ReferenceCounter) { contract.Hash.ToArray() });
}
}
}
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
private long GetMinimumDeploymentFee(DataCache snapshot)
{
return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_MinimumDeploymentFee)];
}
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)]
private void SetMinimumDeploymentFee(ApplicationEngine engine, BigInteger value)
{
if (value < 0) throw new ArgumentOutOfRangeException(nameof(value));
if (!CheckCommittee(engine)) throw new InvalidOperationException();
engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_MinimumDeploymentFee)).Set(value);
}
/// <summary>
/// Gets the deployed contract with the specified hash.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <param name="hash">The hash of the deployed contract.</param>
/// <returns>The deployed contract.</returns>
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
public ContractState GetContract(DataCache snapshot, UInt160 hash)
{
return snapshot.TryGet(CreateStorageKey(Prefix_Contract).Add(hash))?.GetInteroperable<ContractState>(false);
}
/// <summary>
/// Maps specified ID to deployed contract.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <param name="id">Contract ID.</param>
/// <returns>The deployed contract.</returns>
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
public ContractState GetContractById(DataCache snapshot, int id)
{
StorageItem item = snapshot.TryGet(CreateStorageKey(Prefix_ContractHash).AddBigEndian(id));
if (item is null) return null;
var hash = new UInt160(item.Value.Span);
return GetContract(snapshot, hash);
}
/// <summary>
/// Gets hashes of all non native deployed contracts.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <returns>Iterator with hashes of all deployed contracts.</returns>
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
private IIterator GetContractHashes(DataCache snapshot)
{
const FindOptions options = FindOptions.RemovePrefix;
byte[] prefix_key = CreateStorageKey(Prefix_ContractHash).ToArray();
var enumerator = snapshot.Find(prefix_key)
.Select(p => (p.Key, p.Value, Id: BinaryPrimitives.ReadInt32BigEndian(p.Key.Key.Span[1..])))
.Where(p => p.Id >= 0)
.Select(p => (p.Key, p.Value))
.GetEnumerator();
return new StorageIterator(enumerator, 1, options);
}
/// <summary>
/// Check if a method exists in a contract.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <param name="hash">The hash of the deployed contract.</param>
/// <param name="method">The name of the method</param>
/// <param name="pcount">The number of parameters</param>
/// <returns>True if the method exists.</returns>
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
public bool HasMethod(DataCache snapshot, UInt160 hash, string method, int pcount)
{
var contract = GetContract(snapshot, hash);
if (contract is null) return false;
var methodDescriptor = contract.Manifest.Abi.GetMethod(method, pcount);
return methodDescriptor is not null;
}
/// <summary>
/// Gets all deployed contracts.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <returns>The deployed contracts.</returns>
public IEnumerable<ContractState> ListContracts(DataCache snapshot)
{
byte[] listContractsPrefix = CreateStorageKey(Prefix_Contract).ToArray();
return snapshot.Find(listContractsPrefix).Select(kvp => kvp.Value.GetInteroperable<ContractState>(false));
}
[ContractMethod(RequiredCallFlags = CallFlags.All)]
private ContractTask<ContractState> Deploy(ApplicationEngine engine, byte[] nefFile, byte[] manifest)
{
return Deploy(engine, nefFile, manifest, StackItem.Null);
}
[ContractMethod(RequiredCallFlags = CallFlags.All)]
private async ContractTask<ContractState> Deploy(ApplicationEngine engine, byte[] nefFile, byte[] manifest, StackItem data)
{
if (engine.ScriptContainer is not Transaction tx)
throw new InvalidOperationException();
if (nefFile.Length == 0)
throw new ArgumentException($"Invalid NefFile Length: {nefFile.Length}");
if (manifest.Length == 0)
throw new ArgumentException($"Invalid Manifest Length: {manifest.Length}");
engine.AddGas(Math.Max(
engine.StoragePrice * (nefFile.Length + manifest.Length),
GetMinimumDeploymentFee(engine.Snapshot)
));
NefFile nef = nefFile.AsSerializable<NefFile>();
ContractManifest parsedManifest = ContractManifest.Parse(manifest);
Helper.Check(new VM.Script(nef.Script, engine.IsHardforkEnabled(Hardfork.HF_Basilisk)), parsedManifest.Abi);
UInt160 hash = Helper.GetContractHash(tx.Sender, nef.CheckSum, parsedManifest.Name);
if (Policy.IsBlocked(engine.Snapshot, hash))
throw new InvalidOperationException($"The contract {hash} has been blocked.");
StorageKey key = CreateStorageKey(Prefix_Contract).Add(hash);
if (engine.Snapshot.Contains(key))
throw new InvalidOperationException($"Contract Already Exists: {hash}");
ContractState contract = new()
{
Id = GetNextAvailableId(engine.Snapshot),
UpdateCounter = 0,
Nef = nef,
Hash = hash,
Manifest = parsedManifest
};
if (!contract.Manifest.IsValid(engine.Limits, hash)) throw new InvalidOperationException($"Invalid Manifest: {hash}");
engine.Snapshot.Add(key, new StorageItem(contract));
engine.Snapshot.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(hash.ToArray()));
await OnDeploy(engine, contract, data, false);
return contract;
}
[ContractMethod(RequiredCallFlags = CallFlags.All)]
private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest)
{
return Update(engine, nefFile, manifest, StackItem.Null);
}
[ContractMethod(RequiredCallFlags = CallFlags.All)]
private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] manifest, StackItem data)
{
if (nefFile is null && manifest is null) throw new ArgumentException();
engine.AddGas(engine.StoragePrice * ((nefFile?.Length ?? 0) + (manifest?.Length ?? 0)));
var contract = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Contract).Add(engine.CallingScriptHash))?.GetInteroperable<ContractState>(false);
if (contract is null) throw new InvalidOperationException($"Updating Contract Does Not Exist: {engine.CallingScriptHash}");
if (contract.UpdateCounter == ushort.MaxValue) throw new InvalidOperationException($"The contract reached the maximum number of updates.");
if (nefFile != null)
{
if (nefFile.Length == 0)
throw new ArgumentException($"Invalid NefFile Length: {nefFile.Length}");
// Update nef
contract.Nef = nefFile.AsSerializable<NefFile>();
}
if (manifest != null)
{
if (manifest.Length == 0)
throw new ArgumentException($"Invalid Manifest Length: {manifest.Length}");
ContractManifest manifest_new = ContractManifest.Parse(manifest);
if (manifest_new.Name != contract.Manifest.Name)
throw new InvalidOperationException("The name of the contract can't be changed.");
if (!manifest_new.IsValid(engine.Limits, contract.Hash))
throw new InvalidOperationException($"Invalid Manifest: {contract.Hash}");
contract.Manifest = manifest_new;
}
Helper.Check(new VM.Script(contract.Nef.Script, engine.IsHardforkEnabled(Hardfork.HF_Basilisk)), contract.Manifest.Abi);
contract.UpdateCounter++; // Increase update counter
return OnDeploy(engine, contract, data, true);
}
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)]
private void Destroy(ApplicationEngine engine)
{
UInt160 hash = engine.CallingScriptHash;
StorageKey ckey = CreateStorageKey(Prefix_Contract).Add(hash);
ContractState contract = engine.Snapshot.TryGet(ckey)?.GetInteroperable<ContractState>(false);
if (contract is null) return;
engine.Snapshot.Delete(ckey);
engine.Snapshot.Delete(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id));
foreach (var (key, _) in engine.Snapshot.Find(StorageKey.CreateSearchPrefix(contract.Id, ReadOnlySpan<byte>.Empty)))
engine.Snapshot.Delete(key);
// lock contract
Policy.BlockAccount(engine.Snapshot, hash);
// emit event
engine.SendNotification(Hash, "Destroy", new VM.Types.Array(engine.ReferenceCounter) { hash.ToArray() });
}
}
}