diff --git a/src/neo/Ledger/Blockchain.cs b/src/neo/Ledger/Blockchain.cs
index 811db655a6..2d2fafab04 100644
--- a/src/neo/Ledger/Blockchain.cs
+++ b/src/neo/Ledger/Blockchain.cs
@@ -156,7 +156,7 @@ private static Transaction DeployNativeContracts()
byte[] script;
using (ScriptBuilder sb = new ScriptBuilder())
{
- sb.EmitSysCall(InteropService.Native.Deploy);
+ sb.EmitSysCall(ApplicationEngine.Neo_Native_Deploy);
script = sb.ToArray();
}
return new Transaction
diff --git a/src/neo/SmartContract/ApplicationEngine.Binary.cs b/src/neo/SmartContract/ApplicationEngine.Binary.cs
new file mode 100644
index 0000000000..d5f86feefb
--- /dev/null
+++ b/src/neo/SmartContract/ApplicationEngine.Binary.cs
@@ -0,0 +1,20 @@
+using Neo.VM.Types;
+
+namespace Neo.SmartContract
+{
+ partial class ApplicationEngine
+ {
+ public static readonly InteropDescriptor System_Binary_Serialize = Register("System.Binary.Serialize", nameof(BinarySerialize), 0_00100000, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Binary_Deserialize = Register("System.Binary.Deserialize", nameof(BinaryDeserialize), 0_00500000, TriggerType.All, CallFlags.None);
+
+ internal byte[] BinarySerialize(StackItem item)
+ {
+ return BinarySerializer.Serialize(item, MaxItemSize);
+ }
+
+ internal StackItem BinaryDeserialize(byte[] data)
+ {
+ return BinarySerializer.Deserialize(data, MaxStackSize, MaxItemSize, ReferenceCounter);
+ }
+ }
+}
diff --git a/src/neo/SmartContract/ApplicationEngine.Blockchain.cs b/src/neo/SmartContract/ApplicationEngine.Blockchain.cs
new file mode 100644
index 0000000000..025fae972a
--- /dev/null
+++ b/src/neo/SmartContract/ApplicationEngine.Blockchain.cs
@@ -0,0 +1,103 @@
+using Neo.Ledger;
+using Neo.Network.P2P.Payloads;
+using Neo.Persistence;
+using System;
+using System.Numerics;
+
+namespace Neo.SmartContract
+{
+ partial class ApplicationEngine
+ {
+ public const uint MaxTraceableBlocks = Transaction.MaxValidUntilBlockIncrement;
+
+ public static readonly InteropDescriptor System_Blockchain_GetHeight = Register("System.Blockchain.GetHeight", nameof(GetBlockchainHeight), 0_00000400, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Blockchain_GetBlock = Register("System.Blockchain.GetBlock", nameof(GetBlock), 0_02500000, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Blockchain_GetTransaction = Register("System.Blockchain.GetTransaction", nameof(GetTransaction), 0_01000000, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Blockchain_GetTransactionHeight = Register("System.Blockchain.GetTransactionHeight", nameof(GetTransactionHeight), 0_01000000, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Blockchain_GetTransactionFromBlock = Register("System.Blockchain.GetTransactionFromBlock", nameof(GetTransactionFromBlock), 0_01000000, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Blockchain_GetContract = Register("System.Blockchain.GetContract", nameof(GetContract), 0_01000000, TriggerType.Application, CallFlags.AllowStates);
+
+ internal uint GetBlockchainHeight()
+ {
+ return Snapshot.Height;
+ }
+
+ internal Block GetBlock(byte[] indexOrHash)
+ {
+ UInt256 hash;
+ if (indexOrHash.Length < UInt256.Length)
+ {
+ BigInteger bi = new BigInteger(indexOrHash);
+ if (bi < uint.MinValue || bi > uint.MaxValue)
+ throw new ArgumentOutOfRangeException(nameof(indexOrHash));
+ hash = Blockchain.Singleton.GetBlockHash((uint)bi);
+ }
+ else if (indexOrHash.Length == UInt256.Length)
+ {
+ hash = new UInt256(indexOrHash);
+ }
+ else
+ {
+ throw new ArgumentException();
+ }
+ if (hash is null) return null;
+ Block block = Snapshot.GetBlock(hash);
+ if (block is null) return null;
+ if (!IsTraceableBlock(Snapshot, block.Index)) return null;
+ return block;
+ }
+
+ internal Transaction GetTransaction(UInt256 hash)
+ {
+ TransactionState state = Snapshot.Transactions.TryGet(hash);
+ if (state != null && !IsTraceableBlock(Snapshot, state.BlockIndex)) state = null;
+ return state?.Transaction;
+ }
+
+ internal int GetTransactionHeight(UInt256 hash)
+ {
+ TransactionState state = Snapshot.Transactions.TryGet(hash);
+ if (state is null) return -1;
+ if (!IsTraceableBlock(Snapshot, state.BlockIndex)) return -1;
+ return (int)state.BlockIndex;
+ }
+
+ internal Transaction GetTransactionFromBlock(byte[] blockIndexOrHash, int txIndex)
+ {
+ UInt256 hash;
+ if (blockIndexOrHash.Length < UInt256.Length)
+ {
+ BigInteger bi = new BigInteger(blockIndexOrHash);
+ if (bi < uint.MinValue || bi > uint.MaxValue)
+ throw new ArgumentOutOfRangeException(nameof(blockIndexOrHash));
+ hash = Blockchain.Singleton.GetBlockHash((uint)bi);
+ }
+ else if (blockIndexOrHash.Length == UInt256.Length)
+ {
+ hash = new UInt256(blockIndexOrHash);
+ }
+ else
+ {
+ throw new ArgumentException();
+ }
+ if (hash is null) return null;
+ TrimmedBlock block = Snapshot.Blocks.TryGet(hash);
+ if (block is null) return null;
+ if (!IsTraceableBlock(Snapshot, block.Index)) return null;
+ if (txIndex < 0 || txIndex >= block.Hashes.Length - 1)
+ throw new ArgumentOutOfRangeException(nameof(txIndex));
+ return Snapshot.GetTransaction(block.Hashes[txIndex + 1]);
+ }
+
+ internal ContractState GetContract(UInt160 hash)
+ {
+ return Snapshot.Contracts.TryGet(hash);
+ }
+
+ private static bool IsTraceableBlock(StoreView snapshot, uint index)
+ {
+ if (index > snapshot.Height) return false;
+ return index + MaxTraceableBlocks > snapshot.Height;
+ }
+ }
+}
diff --git a/src/neo/SmartContract/ApplicationEngine.Contract.cs b/src/neo/SmartContract/ApplicationEngine.Contract.cs
new file mode 100644
index 0000000000..31407a596c
--- /dev/null
+++ b/src/neo/SmartContract/ApplicationEngine.Contract.cs
@@ -0,0 +1,181 @@
+using Neo.Cryptography.ECC;
+using Neo.IO;
+using Neo.Ledger;
+using Neo.SmartContract.Manifest;
+using Neo.SmartContract.Native;
+using Neo.VM;
+using System;
+using System.Linq;
+using Array = Neo.VM.Types.Array;
+
+namespace Neo.SmartContract
+{
+ partial class ApplicationEngine
+ {
+ public const int MaxContractLength = 1024 * 1024;
+
+ public static readonly InteropDescriptor System_Contract_Create = Register("System.Contract.Create", nameof(CreateContract), 0, TriggerType.Application, CallFlags.AllowModifyStates);
+ public static readonly InteropDescriptor System_Contract_Update = Register("System.Contract.Update", nameof(UpdateContract), 0, TriggerType.Application, CallFlags.AllowModifyStates);
+ public static readonly InteropDescriptor System_Contract_Destroy = Register("System.Contract.Destroy", nameof(DestroyContract), 0_01000000, TriggerType.Application, CallFlags.AllowModifyStates);
+ public static readonly InteropDescriptor System_Contract_Call = Register("System.Contract.Call", nameof(CallContract), 0_01000000, TriggerType.System | TriggerType.Application, CallFlags.AllowCall);
+ public static readonly InteropDescriptor System_Contract_CallEx = Register("System.Contract.CallEx", nameof(CallContractEx), 0_01000000, TriggerType.System | TriggerType.Application, CallFlags.AllowCall);
+ public static readonly InteropDescriptor System_Contract_IsStandard = Register("System.Contract.IsStandard", nameof(IsStandardContract), 0_00030000, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Contract_GetCallFlags = Register("System.Contract.GetCallFlags", nameof(GetCallFlags), 0_00030000, TriggerType.All, CallFlags.None);
+ ///
+ /// Calculate corresponding account scripthash for given public key
+ /// Warning: check first that input public key is valid, before creating the script.
+ ///
+ public static readonly InteropDescriptor System_Contract_CreateStandardAccount = Register("System.Contract.CreateStandardAccount", nameof(CreateStandardAccount), 0_00010000, TriggerType.All, CallFlags.None);
+
+ internal ContractState CreateContract(byte[] script, byte[] manifest)
+ {
+ if (script.Length == 0 || script.Length > MaxContractLength || manifest.Length == 0 || manifest.Length > ContractManifest.MaxLength)
+ throw new ArgumentException();
+
+ if (!AddGas(StoragePrice * (script.Length + manifest.Length)))
+ throw new InvalidOperationException();
+
+ UInt160 hash = script.ToScriptHash();
+ ContractState contract = Snapshot.Contracts.TryGet(hash);
+ if (contract != null) throw new InvalidOperationException();
+ contract = new ContractState
+ {
+ Id = Snapshot.ContractId.GetAndChange().NextId++,
+ Script = script.ToArray(),
+ Manifest = ContractManifest.Parse(manifest)
+ };
+
+ if (!contract.Manifest.IsValid(hash)) throw new InvalidOperationException();
+
+ Snapshot.Contracts.Add(hash, contract);
+ return contract;
+ }
+
+ internal void UpdateContract(byte[] script, byte[] manifest)
+ {
+ if (!AddGas(StoragePrice * (script?.Length ?? 0 + manifest?.Length ?? 0)))
+ throw new InvalidOperationException();
+
+ var contract = Snapshot.Contracts.TryGet(CurrentScriptHash);
+ if (contract is null) throw new InvalidOperationException();
+
+ if (script != null)
+ {
+ if (script.Length == 0 || script.Length > MaxContractLength)
+ throw new ArgumentException();
+ UInt160 hash_new = script.ToScriptHash();
+ if (hash_new.Equals(CurrentScriptHash) || Snapshot.Contracts.TryGet(hash_new) != null)
+ throw new InvalidOperationException();
+ contract = new ContractState
+ {
+ Id = contract.Id,
+ Script = script.ToArray(),
+ Manifest = contract.Manifest
+ };
+ contract.Manifest.Abi.Hash = hash_new;
+ Snapshot.Contracts.Add(hash_new, contract);
+ Snapshot.Contracts.Delete(CurrentScriptHash);
+ }
+ if (manifest != null)
+ {
+ if (manifest.Length == 0 || manifest.Length > ContractManifest.MaxLength)
+ throw new ArgumentException();
+ contract = Snapshot.Contracts.GetAndChange(contract.ScriptHash);
+ contract.Manifest = ContractManifest.Parse(manifest);
+ if (!contract.Manifest.IsValid(contract.ScriptHash))
+ throw new InvalidOperationException();
+ if (!contract.HasStorage && Snapshot.Storages.Find(BitConverter.GetBytes(contract.Id)).Any())
+ throw new InvalidOperationException();
+ }
+ }
+
+ internal void DestroyContract()
+ {
+ UInt160 hash = CurrentScriptHash;
+ ContractState contract = Snapshot.Contracts.TryGet(hash);
+ if (contract == null) return;
+ Snapshot.Contracts.Delete(hash);
+ if (contract.HasStorage)
+ foreach (var (key, _) in Snapshot.Storages.Find(BitConverter.GetBytes(contract.Id)))
+ Snapshot.Storages.Delete(key);
+ }
+
+ internal void CallContract(UInt160 contractHash, string method, Array args)
+ {
+ CallContractInternal(contractHash, method, args, CallFlags.All);
+ }
+
+ internal void CallContractEx(UInt160 contractHash, string method, Array args, CallFlags callFlags)
+ {
+ if ((callFlags & ~CallFlags.All) != 0)
+ throw new ArgumentOutOfRangeException(nameof(callFlags));
+ CallContractInternal(contractHash, method, args, callFlags);
+ }
+
+ private void CallContractInternal(UInt160 contractHash, string method, Array args, CallFlags flags)
+ {
+ if (method.StartsWith('_')) throw new ArgumentException();
+
+ ContractState contract = Snapshot.Contracts.TryGet(contractHash);
+ if (contract is null) throw new InvalidOperationException();
+
+ ContractManifest currentManifest = Snapshot.Contracts.TryGet(CurrentScriptHash)?.Manifest;
+
+ if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, method))
+ throw new InvalidOperationException();
+
+ if (invocationCounter.TryGetValue(contract.ScriptHash, out var counter))
+ {
+ invocationCounter[contract.ScriptHash] = counter + 1;
+ }
+ else
+ {
+ invocationCounter[contract.ScriptHash] = 1;
+ }
+
+ ExecutionContextState state = CurrentContext.GetState();
+ UInt160 callingScriptHash = state.ScriptHash;
+ CallFlags callingFlags = state.CallFlags;
+
+ ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method);
+ if (md is null) throw new InvalidOperationException();
+ int rvcount = md.ReturnType == ContractParameterType.Void ? 0 : 1;
+ ExecutionContext context_new = LoadScript(contract.Script, rvcount);
+ state = context_new.GetState();
+ state.CallingScriptHash = callingScriptHash;
+ state.CallFlags = flags & callingFlags;
+
+ if (NativeContract.IsNative(contractHash))
+ {
+ context_new.EvaluationStack.Push(args);
+ context_new.EvaluationStack.Push(method);
+ }
+ else
+ {
+ for (int i = args.Count - 1; i >= 0; i--)
+ context_new.EvaluationStack.Push(args[i]);
+ context_new.InstructionPointer = md.Offset;
+ }
+
+ md = contract.Manifest.Abi.GetMethod("_initialize");
+ if (md != null) LoadClonedContext(md.Offset);
+ }
+
+ internal bool IsStandardContract(UInt160 hash)
+ {
+ ContractState contract = Snapshot.Contracts.TryGet(hash);
+ return contract is null || contract.Script.IsStandardContract();
+ }
+
+ internal CallFlags GetCallFlags()
+ {
+ var state = CurrentContext.GetState();
+ return state.CallFlags;
+ }
+
+ internal UInt160 CreateStandardAccount(ECPoint pubKey)
+ {
+ return Contract.CreateSignatureRedeemScript(pubKey).ToScriptHash();
+ }
+ }
+}
diff --git a/src/neo/SmartContract/ApplicationEngine.Crypto.cs b/src/neo/SmartContract/ApplicationEngine.Crypto.cs
new file mode 100644
index 0000000000..9cb3a9abf4
--- /dev/null
+++ b/src/neo/SmartContract/ApplicationEngine.Crypto.cs
@@ -0,0 +1,99 @@
+using Neo.Cryptography;
+using Neo.Cryptography.ECC;
+using Neo.Network.P2P;
+using Neo.Network.P2P.Payloads;
+using Neo.VM;
+using Neo.VM.Types;
+using System;
+
+namespace Neo.SmartContract
+{
+ partial class ApplicationEngine
+ {
+ public const long ECDsaVerifyPrice = 0_01000000;
+
+ public static readonly InteropDescriptor Neo_Crypto_SHA256 = Register("Neo.Crypto.SHA256", nameof(Sha256), 0_01000000, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor Neo_Crypto_VerifyWithECDsaSecp256r1 = Register("Neo.Crypto.VerifyWithECDsaSecp256r1", nameof(VerifyWithECDsaSecp256r1), ECDsaVerifyPrice, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor Neo_Crypto_VerifyWithECDsaSecp256k1 = Register("Neo.Crypto.VerifyWithECDsaSecp256k1", nameof(VerifyWithECDsaSecp256k1), ECDsaVerifyPrice, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor Neo_Crypto_CheckMultisigWithECDsaSecp256r1 = Register("Neo.Crypto.CheckMultisigWithECDsaSecp256r1", nameof(CheckMultisigWithECDsaSecp256r1), 0, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor Neo_Crypto_CheckMultisigWithECDsaSecp256k1 = Register("Neo.Crypto.CheckMultisigWithECDsaSecp256k1", nameof(CheckMultisigWithECDsaSecp256k1), 0, TriggerType.All, CallFlags.None);
+
+ internal byte[] Sha256(StackItem item)
+ {
+ ReadOnlySpan value = item switch
+ {
+ InteropInterface _interface => _interface.GetInterface().GetHashData(),
+ Null _ => ScriptContainer.GetHashData(),
+ _ => item.GetSpan()
+ };
+ return value.Sha256();
+ }
+
+ internal bool VerifyWithECDsaSecp256r1(StackItem item, byte[] pubkey, byte[] signature)
+ {
+ return VerifyWithECDsa(item, pubkey, signature, ECCurve.Secp256r1);
+ }
+
+ internal bool VerifyWithECDsaSecp256k1(StackItem item, byte[] pubkey, byte[] signature)
+ {
+ return VerifyWithECDsa(item, pubkey, signature, ECCurve.Secp256k1);
+ }
+
+ private bool VerifyWithECDsa(StackItem item, byte[] pubkey, byte[] signature, ECCurve curve)
+ {
+ ReadOnlySpan message = item switch
+ {
+ InteropInterface _interface => _interface.GetInterface().GetHashData(),
+ Null _ => ScriptContainer.GetHashData(),
+ _ => item.GetSpan()
+ };
+ try
+ {
+ return Crypto.VerifySignature(message, signature, pubkey, curve);
+ }
+ catch (ArgumentException)
+ {
+ return false;
+ }
+ }
+
+ internal bool CheckMultisigWithECDsaSecp256r1(StackItem item, byte[][] pubkeys, byte[][] signatures)
+ {
+ return CheckMultiSigWithECDsa(item, pubkeys, signatures, ECCurve.Secp256r1);
+ }
+
+ internal bool CheckMultisigWithECDsaSecp256k1(StackItem item, byte[][] pubkeys, byte[][] signatures)
+ {
+ return CheckMultiSigWithECDsa(item, pubkeys, signatures, ECCurve.Secp256k1);
+ }
+
+ private bool CheckMultiSigWithECDsa(StackItem item0, byte[][] pubkeys, byte[][] signatures, ECCurve curve)
+ {
+ int m = signatures.Length, n = pubkeys.Length;
+ ReadOnlySpan message = item0 switch
+ {
+ InteropInterface _interface => _interface.GetInterface().GetHashData(),
+ Null _ => ScriptContainer.GetHashData(),
+ _ => item0.GetSpan()
+ };
+ if (n == 0 || m == 0 || m > n) throw new ArgumentException();
+ if (!AddGas(ECDsaVerifyPrice * n)) throw new InvalidOperationException();
+ try
+ {
+ for (int i = 0, j = 0; i < m && j < n;)
+ {
+ if (Crypto.VerifySignature(message, signatures[i], pubkeys[j], curve))
+ i++;
+ j++;
+ if (m - i > n - j)
+ return false;
+ }
+ }
+ catch (ArgumentException)
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/src/neo/SmartContract/ApplicationEngine.Enumerator.cs b/src/neo/SmartContract/ApplicationEngine.Enumerator.cs
new file mode 100644
index 0000000000..7fdbbd8bf8
--- /dev/null
+++ b/src/neo/SmartContract/ApplicationEngine.Enumerator.cs
@@ -0,0 +1,41 @@
+using Neo.SmartContract.Enumerators;
+using Neo.SmartContract.Iterators;
+using Neo.VM.Types;
+using System;
+using Array = Neo.VM.Types.Array;
+
+namespace Neo.SmartContract
+{
+ partial class ApplicationEngine
+ {
+ public static readonly InteropDescriptor System_Enumerator_Create = Register("System.Enumerator.Create", nameof(CreateEnumerator), 0_00000400, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Enumerator_Next = Register("System.Enumerator.Next", nameof(EnumeratorNext), 0_01000000, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Enumerator_Value = Register("System.Enumerator.Value", nameof(EnumeratorValue), 0_00000400, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Enumerator_Concat = Register("System.Enumerator.Concat", nameof(ConcatEnumerators), 0_00000400, TriggerType.All, CallFlags.None);
+
+ internal IEnumerator CreateEnumerator(StackItem item)
+ {
+ return item switch
+ {
+ Array array => new ArrayWrapper(array),
+ PrimitiveType primitive => new ByteArrayWrapper(primitive),
+ _ => throw new ArgumentException()
+ };
+ }
+
+ internal bool EnumeratorNext(IEnumerator enumerator)
+ {
+ return enumerator.Next();
+ }
+
+ internal StackItem EnumeratorValue(IEnumerator enumerator)
+ {
+ return enumerator.Value();
+ }
+
+ internal IEnumerator ConcatEnumerators(IEnumerator first, IEnumerator second)
+ {
+ return new ConcatenatedEnumerator(first, second);
+ }
+ }
+}
diff --git a/src/neo/SmartContract/ApplicationEngine.Iterator.cs b/src/neo/SmartContract/ApplicationEngine.Iterator.cs
new file mode 100644
index 0000000000..be3bde386b
--- /dev/null
+++ b/src/neo/SmartContract/ApplicationEngine.Iterator.cs
@@ -0,0 +1,48 @@
+using Neo.SmartContract.Enumerators;
+using Neo.SmartContract.Iterators;
+using Neo.VM.Types;
+using System;
+using Array = Neo.VM.Types.Array;
+
+namespace Neo.SmartContract
+{
+ partial class ApplicationEngine
+ {
+ public static readonly InteropDescriptor System_Iterator_Create = Register("System.Iterator.Create", nameof(CreateIterator), 0_00000400, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Iterator_Key = Register("System.Iterator.Key", nameof(IteratorKey), 0_00000400, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Iterator_Keys = Register("System.Iterator.Keys", nameof(IteratorKeys), 0_00000400, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Iterator_Values = Register("System.Iterator.Values", nameof(IteratorValues), 0_00000400, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Iterator_Concat = Register("System.Iterator.Concat", nameof(ConcatIterators), 0_00000400, TriggerType.All, CallFlags.None);
+
+ internal IIterator CreateIterator(StackItem item)
+ {
+ return item switch
+ {
+ Array array => new ArrayWrapper(array),
+ Map map => new MapWrapper(map),
+ PrimitiveType primitive => new ByteArrayWrapper(primitive),
+ _ => throw new ArgumentException()
+ };
+ }
+
+ internal PrimitiveType IteratorKey(IIterator iterator)
+ {
+ return iterator.Key();
+ }
+
+ internal IEnumerator IteratorKeys(IIterator iterator)
+ {
+ return new IteratorKeysWrapper(iterator);
+ }
+
+ internal IEnumerator IteratorValues(IIterator iterator)
+ {
+ return new IteratorValuesWrapper(iterator);
+ }
+
+ internal IIterator ConcatIterators(IIterator first, IIterator second)
+ {
+ return new ConcatenatedIterator(first, second);
+ }
+ }
+}
diff --git a/src/neo/SmartContract/ApplicationEngine.Json.cs b/src/neo/SmartContract/ApplicationEngine.Json.cs
new file mode 100644
index 0000000000..d8f972c343
--- /dev/null
+++ b/src/neo/SmartContract/ApplicationEngine.Json.cs
@@ -0,0 +1,21 @@
+using Neo.IO.Json;
+using Neo.VM.Types;
+
+namespace Neo.SmartContract
+{
+ partial class ApplicationEngine
+ {
+ public static readonly InteropDescriptor System_Json_Serialize = Register("System.Json.Serialize", nameof(JsonSerialize), 0_00100000, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Json_Deserialize = Register("System.Json.Deserialize", nameof(JsonDeserialize), 0_00500000, TriggerType.All, CallFlags.None);
+
+ internal byte[] JsonSerialize(StackItem item)
+ {
+ return JsonSerializer.SerializeToByteArray(item, MaxItemSize);
+ }
+
+ internal StackItem JsonDeserialize(byte[] json)
+ {
+ return JsonSerializer.Deserialize(JObject.Parse(json, 10), ReferenceCounter);
+ }
+ }
+}
diff --git a/src/neo/SmartContract/ApplicationEngine.Native.cs b/src/neo/SmartContract/ApplicationEngine.Native.cs
new file mode 100644
index 0000000000..e413df7b17
--- /dev/null
+++ b/src/neo/SmartContract/ApplicationEngine.Native.cs
@@ -0,0 +1,34 @@
+using Neo.Ledger;
+using Neo.SmartContract.Native;
+using System;
+
+namespace Neo.SmartContract
+{
+ partial class ApplicationEngine
+ {
+ public static readonly InteropDescriptor Neo_Native_Deploy = Register("Neo.Native.Deploy", nameof(DeployNativeContracts), 0, TriggerType.Application, CallFlags.AllowModifyStates);
+ public static readonly InteropDescriptor Neo_Native_Call = Register("Neo.Native.Call", nameof(CallNativeContract), 0, TriggerType.System | TriggerType.Application, CallFlags.None);
+
+ internal void DeployNativeContracts()
+ {
+ if (Snapshot.PersistingBlock.Index != 0)
+ throw new InvalidOperationException();
+ foreach (NativeContract contract in NativeContract.Contracts)
+ {
+ Snapshot.Contracts.Add(contract.Hash, new ContractState
+ {
+ Id = contract.Id,
+ Script = contract.Script,
+ Manifest = contract.Manifest
+ });
+ contract.Initialize(this);
+ }
+ }
+
+ internal void CallNativeContract(string name)
+ {
+ if (!NativeContract.GetContract(name).Invoke(this))
+ throw new InvalidOperationException();
+ }
+ }
+}
diff --git a/src/neo/SmartContract/ApplicationEngine.Runtime.cs b/src/neo/SmartContract/ApplicationEngine.Runtime.cs
new file mode 100644
index 0000000000..481e6fcfcb
--- /dev/null
+++ b/src/neo/SmartContract/ApplicationEngine.Runtime.cs
@@ -0,0 +1,173 @@
+using Neo.Cryptography.ECC;
+using Neo.IO;
+using Neo.Network.P2P.Payloads;
+using Neo.VM.Types;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Array = Neo.VM.Types.Array;
+
+namespace Neo.SmartContract
+{
+ partial class ApplicationEngine
+ {
+ public const int MaxNotificationSize = 1024;
+
+ public static readonly InteropDescriptor System_Runtime_Platform = Register("System.Runtime.Platform", nameof(GetPlatform), 0_00000250, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Runtime_GetTrigger = Register("System.Runtime.GetTrigger", nameof(Trigger), 0_00000250, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Runtime_GetTime = Register("System.Runtime.GetTime", nameof(GetTime), 0_00000250, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Runtime_GetScriptContainer = Register("System.Runtime.GetScriptContainer", nameof(GetScriptContainer), 0_00000250, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Runtime_GetExecutingScriptHash = Register("System.Runtime.GetExecutingScriptHash", nameof(CurrentScriptHash), 0_00000400, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Runtime_GetCallingScriptHash = Register("System.Runtime.GetCallingScriptHash", nameof(CallingScriptHash), 0_00000400, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Runtime_GetEntryScriptHash = Register("System.Runtime.GetEntryScriptHash", nameof(EntryScriptHash), 0_00000400, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Runtime_CheckWitness = Register("System.Runtime.CheckWitness", nameof(CheckWitness), 0_00030000, TriggerType.All, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Runtime_GetInvocationCounter = Register("System.Runtime.GetInvocationCounter", nameof(GetInvocationCounter), 0_00000400, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Runtime_Log = Register("System.Runtime.Log", nameof(RuntimeLog), 0_01000000, TriggerType.All, CallFlags.AllowNotify);
+ public static readonly InteropDescriptor System_Runtime_Notify = Register("System.Runtime.Notify", nameof(RuntimeNotify), 0_01000000, TriggerType.All, CallFlags.AllowNotify);
+ public static readonly InteropDescriptor System_Runtime_GetNotifications = Register("System.Runtime.GetNotifications", nameof(GetNotifications), 0_00010000, TriggerType.All, CallFlags.None);
+ public static readonly InteropDescriptor System_Runtime_GasLeft = Register("System.Runtime.GasLeft", nameof(GasLeft), 0_00000400, TriggerType.All, CallFlags.None);
+
+ private static bool CheckItemForNotification(StackItem state)
+ {
+ int size = 0;
+ List items_checked = new List();
+ Queue items_unchecked = new Queue();
+ while (true)
+ {
+ switch (state)
+ {
+ case Struct array:
+ foreach (StackItem item in array)
+ items_unchecked.Enqueue(item);
+ break;
+ case Array array:
+ if (items_checked.All(p => !ReferenceEquals(p, array)))
+ {
+ items_checked.Add(array);
+ foreach (StackItem item in array)
+ items_unchecked.Enqueue(item);
+ }
+ break;
+ case PrimitiveType primitive:
+ size += primitive.Size;
+ break;
+ case Null _:
+ break;
+ case InteropInterface _:
+ return false;
+ case Map map:
+ if (items_checked.All(p => !ReferenceEquals(p, map)))
+ {
+ items_checked.Add(map);
+ foreach (var pair in map)
+ {
+ size += pair.Key.Size;
+ items_unchecked.Enqueue(pair.Value);
+ }
+ }
+ break;
+ }
+ if (size > MaxNotificationSize) return false;
+ if (items_unchecked.Count == 0) return true;
+ state = items_unchecked.Dequeue();
+ }
+ }
+
+ internal string GetPlatform()
+ {
+ return "NEO";
+ }
+
+ internal ulong GetTime()
+ {
+ return Snapshot.PersistingBlock.Timestamp;
+ }
+
+ internal IInteroperable GetScriptContainer()
+ {
+ return ScriptContainer as IInteroperable;
+ }
+
+ internal bool CheckWitness(byte[] hashOrPubkey)
+ {
+ UInt160 hash = hashOrPubkey.Length switch
+ {
+ 20 => new UInt160(hashOrPubkey),
+ 33 => Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(hashOrPubkey, ECCurve.Secp256r1)).ToScriptHash(),
+ _ => throw new ArgumentException()
+ };
+ return CheckWitnessInternal(hash);
+ }
+
+ internal bool CheckWitnessInternal(UInt160 hash)
+ {
+ if (ScriptContainer is Transaction tx)
+ {
+ Cosigner cosigner = tx.Cosigners.FirstOrDefault(p => p.Account.Equals(hash));
+ if (cosigner is null) return false;
+ if (cosigner.Scopes == WitnessScope.Global) return true;
+ if (cosigner.Scopes.HasFlag(WitnessScope.CalledByEntry))
+ {
+ if (CallingScriptHash == EntryScriptHash)
+ return true;
+ }
+ if (cosigner.Scopes.HasFlag(WitnessScope.CustomContracts))
+ {
+ if (cosigner.AllowedContracts.Contains(CurrentScriptHash))
+ return true;
+ }
+ if (cosigner.Scopes.HasFlag(WitnessScope.CustomGroups))
+ {
+ var contract = Snapshot.Contracts[CallingScriptHash];
+ // check if current group is the required one
+ if (contract.Manifest.Groups.Select(p => p.PubKey).Intersect(cosigner.AllowedGroups).Any())
+ return true;
+ }
+ return false;
+ }
+
+ // only for non-Transaction types (Block, etc)
+
+ var hashes_for_verifying = ScriptContainer.GetScriptHashesForVerifying(Snapshot);
+ return hashes_for_verifying.Contains(hash);
+ }
+
+ internal int GetInvocationCounter()
+ {
+ if (!invocationCounter.TryGetValue(CurrentScriptHash, out var counter))
+ throw new InvalidOperationException();
+ return counter;
+ }
+
+ internal void RuntimeLog(byte[] state)
+ {
+ if (state.Length > MaxNotificationSize) throw new ArgumentException();
+ string message = Encoding.UTF8.GetString(state);
+ Log?.Invoke(this, new LogEventArgs(ScriptContainer, CurrentScriptHash, message));
+ }
+
+ internal void RuntimeNotify(StackItem state)
+ {
+ if (!CheckItemForNotification(state)) throw new ArgumentException();
+ SendNotification(CurrentScriptHash, state);
+ }
+
+ internal void SendNotification(UInt160 script_hash, StackItem state)
+ {
+ NotifyEventArgs notification = new NotifyEventArgs(ScriptContainer, script_hash, state);
+ Notify?.Invoke(this, notification);
+ notifications.Add(notification);
+ }
+
+ internal NotifyEventArgs[] GetNotifications(UInt160 hash)
+ {
+ IEnumerable notifications = Notifications;
+ if (hash != null) // must filter by scriptHash
+ notifications = notifications.Where(p => p.ScriptHash == hash);
+ NotifyEventArgs[] array = notifications.ToArray();
+ if (array.Length > MaxStackSize) throw new InvalidOperationException();
+ return array;
+ }
+ }
+}
diff --git a/src/neo/SmartContract/ApplicationEngine.Storage.cs b/src/neo/SmartContract/ApplicationEngine.Storage.cs
new file mode 100644
index 0000000000..b579c5f37f
--- /dev/null
+++ b/src/neo/SmartContract/ApplicationEngine.Storage.cs
@@ -0,0 +1,127 @@
+using Neo.Ledger;
+using Neo.SmartContract.Iterators;
+using System;
+using System.Linq;
+
+namespace Neo.SmartContract
+{
+ partial class ApplicationEngine
+ {
+ public const long StoragePrice = 100000;
+ public const int MaxStorageKeySize = 64;
+ public const int MaxStorageValueSize = ushort.MaxValue;
+
+ public static readonly InteropDescriptor System_Storage_GetContext = Register("System.Storage.GetContext", nameof(GetStorageContext), 0_00000400, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Storage_GetReadOnlyContext = Register("System.Storage.GetReadOnlyContext", nameof(GetReadOnlyContext), 0_00000400, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Storage_AsReadOnly = Register("System.Storage.AsReadOnly", nameof(AsReadOnly), 0_00000400, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Storage_Get = Register("System.Storage.Get", nameof(Get), 0_01000000, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Storage_Find = Register("System.Storage.Find", nameof(Find), 0_01000000, TriggerType.Application, CallFlags.AllowStates);
+ public static readonly InteropDescriptor System_Storage_Put = Register("System.Storage.Put", nameof(Put), 0, TriggerType.Application, CallFlags.AllowModifyStates);
+ public static readonly InteropDescriptor System_Storage_PutEx = Register("System.Storage.PutEx", nameof(PutEx), 0, TriggerType.Application, CallFlags.AllowModifyStates);
+ public static readonly InteropDescriptor System_Storage_Delete = Register("System.Storage.Delete", nameof(Delete), 1 * StoragePrice, TriggerType.Application, CallFlags.AllowModifyStates);
+
+ internal StorageContext GetStorageContext()
+ {
+ ContractState contract = Snapshot.Contracts.TryGet(CurrentScriptHash);
+ if (!contract.HasStorage) throw new InvalidOperationException();
+ return new StorageContext
+ {
+ Id = contract.Id,
+ IsReadOnly = false
+ };
+ }
+
+ internal StorageContext GetReadOnlyContext()
+ {
+ ContractState contract = Snapshot.Contracts.TryGet(CurrentScriptHash);
+ if (!contract.HasStorage) throw new InvalidOperationException();
+ return new StorageContext
+ {
+ Id = contract.Id,
+ IsReadOnly = true
+ };
+ }
+
+ internal StorageContext AsReadOnly(StorageContext context)
+ {
+ if (!context.IsReadOnly)
+ context = new StorageContext
+ {
+ Id = context.Id,
+ IsReadOnly = true
+ };
+ return context;
+ }
+
+ internal byte[] Get(StorageContext context, byte[] key)
+ {
+ return Snapshot.Storages.TryGet(new StorageKey
+ {
+ Id = context.Id,
+ Key = key.ToArray()
+ })?.Value;
+ }
+
+ internal IIterator Find(StorageContext context, byte[] prefix)
+ {
+ byte[] prefix_key = StorageKey.CreateSearchPrefix(context.Id, prefix);
+ StorageIterator iterator = new StorageIterator(Snapshot.Storages.Find(prefix_key).Where(p => p.Key.Key.AsSpan().StartsWith(prefix)).GetEnumerator());
+ disposables.Add(iterator);
+ return iterator;
+ }
+
+ internal void Put(StorageContext context, byte[] key, byte[] value)
+ {
+ PutExInternal(context, key, value, StorageFlags.None);
+ }
+
+ internal void PutEx(StorageContext context, byte[] key, byte[] value, StorageFlags flags)
+ {
+ PutExInternal(context, key, value, flags);
+ }
+
+ private void PutExInternal(StorageContext context, byte[] key, byte[] value, StorageFlags flags)
+ {
+ if (key.Length > MaxStorageKeySize || value.Length > MaxStorageValueSize || context.IsReadOnly)
+ throw new ArgumentException();
+
+ int newDataSize;
+ StorageKey skey = new StorageKey
+ {
+ Id = context.Id,
+ Key = key
+ };
+ StorageItem item = Snapshot.Storages.GetAndChange(skey);
+ if (item is null)
+ {
+ newDataSize = key.Length + value.Length;
+ Snapshot.Storages.Add(skey, item = new StorageItem());
+ }
+ else
+ {
+ if (item.IsConstant) throw new InvalidOperationException();
+ if (value.Length <= item.Value.Length)
+ newDataSize = 1;
+ else
+ newDataSize = value.Length - item.Value.Length;
+ }
+ if (!AddGas(newDataSize * StoragePrice)) throw new InvalidOperationException();
+
+ item.Value = value;
+ item.IsConstant = flags.HasFlag(StorageFlags.Constant);
+ }
+
+ internal void Delete(StorageContext context, byte[] key)
+ {
+ if (context.IsReadOnly) throw new ArgumentException();
+ StorageKey skey = new StorageKey
+ {
+ Id = context.Id,
+ Key = key
+ };
+ if (Snapshot.Storages.TryGet(skey)?.IsConstant == true)
+ throw new InvalidOperationException();
+ Snapshot.Storages.Delete(skey);
+ }
+ }
+}
diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs
index bfec85c8f8..11708d9609 100644
--- a/src/neo/SmartContract/ApplicationEngine.cs
+++ b/src/neo/SmartContract/ApplicationEngine.cs
@@ -1,3 +1,4 @@
+using Neo.IO;
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
@@ -5,8 +6,12 @@
using Neo.VM.Types;
using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Reflection;
using System.Text;
using Array = System.Array;
+using VMArray = Neo.VM.Types.Array;
namespace Neo.SmartContract
{
@@ -16,22 +21,24 @@ public partial class ApplicationEngine : ExecutionEngine
public static event EventHandler Log;
public const long GasFree = 0;
+
+ private static Dictionary services;
private readonly long gas_amount;
private readonly bool testMode;
private readonly List notifications = new List();
private readonly List disposables = new List();
+ private readonly Dictionary invocationCounter = new Dictionary();
+ public static IEnumerable Services => services.Values;
public TriggerType Trigger { get; }
public IVerifiable ScriptContainer { get; }
public StoreView Snapshot { get; }
public long GasConsumed { get; private set; } = 0;
public long GasLeft => testMode ? -1 : gas_amount - GasConsumed;
-
public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash;
public UInt160 CallingScriptHash => CurrentContext?.GetState().CallingScriptHash;
public UInt160 EntryScriptHash => EntryContext?.GetState().ScriptHash;
public IReadOnlyList Notifications => notifications;
- internal Dictionary InvocationCounter { get; } = new Dictionary();
public ApplicationEngine(TriggerType trigger, IVerifiable container, StoreView snapshot, long gas, bool testMode = false)
{
@@ -42,12 +49,6 @@ public ApplicationEngine(TriggerType trigger, IVerifiable container, StoreView s
this.Snapshot = snapshot;
}
- internal T AddDisposable(T disposable) where T : IDisposable
- {
- disposables.Add(disposable);
- return disposable;
- }
-
internal bool AddGas(long gas)
{
GasConsumed = checked(GasConsumed + gas);
@@ -70,6 +71,32 @@ public ExecutionContext LoadScript(Script script, CallFlags callFlags, int rvcou
return context;
}
+ private StackItem ConvertReturnValue(object value)
+ {
+ return value switch
+ {
+ null => StackItem.Null,
+ bool b => b,
+ sbyte i => i,
+ byte i => (BigInteger)i,
+ short i => i,
+ ushort i => (BigInteger)i,
+ int i => i,
+ uint i => i,
+ long i => i,
+ ulong i => i,
+ Enum e => ConvertReturnValue(Convert.ChangeType(e, e.GetTypeCode())),
+ byte[] data => data,
+ string s => s,
+ UInt160 i => i.ToArray(),
+ UInt256 i => i.ToArray(),
+ IInteroperable interoperable => interoperable.ToStackItem(ReferenceCounter),
+ IInteroperable[] array => new VMArray(ReferenceCounter, array.Select(p => p.ToStackItem(ReferenceCounter))),
+ StackItem item => item,
+ _ => StackItem.FromInterface(value)
+ };
+ }
+
public override void Dispose()
{
foreach (IDisposable disposable in disposables)
@@ -80,7 +107,53 @@ public override void Dispose()
protected override bool OnSysCall(uint method)
{
- return InteropService.Invoke(this, method);
+ if (!services.TryGetValue(method, out InteropDescriptor descriptor))
+ return false;
+ if (!descriptor.AllowedTriggers.HasFlag(Trigger))
+ return false;
+ ExecutionContextState state = CurrentContext.GetState();
+ if (!state.CallFlags.HasFlag(descriptor.RequiredCallFlags))
+ return false;
+ if (!AddGas(descriptor.FixedPrice))
+ return false;
+ List