Skip to content

Commit

Permalink
Update contract without change the hash (neo-project#2044)
Browse files Browse the repository at this point in the history
* Update without hash change

* Specify version

* Update ContractState.cs

* Update ApplicationEngine.Contract.cs

* Update ApplicationEngine.Contract.cs

* Update ApplicationEngine.Contract.cs

* Change to ushort

* Return Id

* Update ContractState.cs

* Remove hash_new check

* Update ApplicationEngine.Contract.cs

* Fix UT

* Remove Hash from ABI and change hash strategy

* Clean code

* Remove MaxLength

* Change script size verification to nefFile

* Rename Version to UpdateCounter

* Rename ContractState.ScriptHash to Hash

* Some Erik's suggestions

* Some Erik's suggestions

* Move CanCall

* Remove Script hash from NefFile

* Move to Helper

* Simplify ContractState

* Erik's review

* NefFile.GetHash()

* Remove double check

* Some fixes

* Some fixes

* Reduce changes

* Move GetContractHash to Helper

* Use GetContractHash for native contracts

* Fix UT paritially

* Some fixes

* Fix UT

* Rename parameters

* Update NefFile.cs

* Update NefFile.cs

* Change version to string

* Increase to 32

* Fix

* Fix UT

Co-authored-by: Erik Zhang <erik@neo.org>
  • Loading branch information
2 people authored and cloud8little committed Jan 24, 2021
1 parent 9851cb9 commit c0a3840
Show file tree
Hide file tree
Showing 31 changed files with 335 additions and 314 deletions.
42 changes: 26 additions & 16 deletions src/neo/Ledger/ContractState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,28 @@
using Neo.VM.Types;
using System;
using System.IO;
using System.Linq;
using Array = Neo.VM.Types.Array;

namespace Neo.Ledger
{
public class ContractState : ICloneable<ContractState>, ISerializable, IInteroperable
{
public int Id;
public ushort UpdateCounter;
public UInt160 Hash;
public byte[] Script;
public ContractManifest Manifest;

private UInt160 _scriptHash;
public UInt160 ScriptHash
{
get
{
if (_scriptHash == null)
{
_scriptHash = Script.ToScriptHash();
}
return _scriptHash;
}
}

int ISerializable.Size => sizeof(int) + Script.GetVarSize() + Manifest.Size;
int ISerializable.Size => sizeof(int) + sizeof(ushort) + UInt160.Length + Script.GetVarSize() + Manifest.Size;

ContractState ICloneable<ContractState>.Clone()
{
return new ContractState
{
Id = Id,
UpdateCounter = UpdateCounter,
Hash = Hash,
Script = Script,
Manifest = Manifest.Clone()
};
Expand All @@ -44,13 +36,17 @@ ContractState ICloneable<ContractState>.Clone()
void ISerializable.Deserialize(BinaryReader reader)
{
Id = reader.ReadInt32();
UpdateCounter = reader.ReadUInt16();
Hash = reader.ReadSerializable<UInt160>();
Script = reader.ReadVarBytes();
Manifest = reader.ReadSerializable<ContractManifest>();
}

void ICloneable<ContractState>.FromReplica(ContractState replica)
{
Id = replica.Id;
UpdateCounter = replica.UpdateCounter;
Hash = replica.Hash;
Script = replica.Script;
Manifest = replica.Manifest.Clone();
}
Expand All @@ -63,23 +59,37 @@ void IInteroperable.FromStackItem(StackItem stackItem)
void ISerializable.Serialize(BinaryWriter writer)
{
writer.Write(Id);
writer.Write(UpdateCounter);
writer.Write(Hash);
writer.WriteVarBytes(Script);
writer.Write(Manifest);
}

/// <summary>
/// Return true if is allowed
/// </summary>
/// <param name="targetContract">The contract that we are calling</param>
/// <param name="targetMethod">The method that we are calling</param>
/// <returns>Return true or false</returns>
public bool CanCall(ContractState targetContract, string targetMethod)
{
return Manifest.Permissions.Any(u => u.IsAllowed(targetContract, targetMethod));
}

public JObject ToJson()
{
JObject json = new JObject();
json["id"] = Id;
json["hash"] = ScriptHash.ToString();
json["updatecounter"] = UpdateCounter;
json["hash"] = Hash.ToString();
json["script"] = Convert.ToBase64String(Script);
json["manifest"] = Manifest.ToJson();
return json;
}

public StackItem ToStackItem(ReferenceCounter referenceCounter)
{
return new Array(referenceCounter, new StackItem[] { Script, Manifest.ToString() });
return new Array(referenceCounter, new StackItem[] { Id, (int)UpdateCounter, Hash.ToArray(), Script, Manifest.ToString() });
}
}
}
73 changes: 34 additions & 39 deletions src/neo/SmartContract/ApplicationEngine.Contract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@
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, CallFlags.AllowModifyStates, false);
public static readonly InteropDescriptor System_Contract_Update = Register("System.Contract.Update", nameof(UpdateContract), 0, CallFlags.AllowModifyStates, false);
public static readonly InteropDescriptor System_Contract_Destroy = Register("System.Contract.Destroy", nameof(DestroyContract), 0_01000000, CallFlags.AllowModifyStates, false);
Expand All @@ -28,22 +25,27 @@ partial class ApplicationEngine
/// </summary>
public static readonly InteropDescriptor System_Contract_CreateStandardAccount = Register("System.Contract.CreateStandardAccount", nameof(CreateStandardAccount), 0_00010000, CallFlags.None, true);

protected internal void CreateContract(byte[] script, byte[] manifest)
protected internal void CreateContract(byte[] nefFile, byte[] manifest)
{
if (script.Length == 0 || script.Length > MaxContractLength)
throw new ArgumentException($"Invalid Script Length: {script.Length}");
if (!(ScriptContainer is Transaction tx))
throw new InvalidOperationException();
if (nefFile.Length == 0)
throw new ArgumentException($"Invalid NefFile Length: {nefFile.Length}");
if (manifest.Length == 0 || manifest.Length > ContractManifest.MaxLength)
throw new ArgumentException($"Invalid Manifest Length: {manifest.Length}");

AddGas(StoragePrice * (script.Length + manifest.Length));
AddGas(StoragePrice * (nefFile.Length + manifest.Length));

UInt160 hash = script.ToScriptHash();
NefFile nef = nefFile.AsSerializable<NefFile>();
UInt160 hash = Helper.GetContractHash(tx.Sender, nef.Script);
ContractState contract = Snapshot.Contracts.TryGet(hash);
if (contract != null) throw new InvalidOperationException($"Contract Already Exists: {hash}");
contract = new ContractState
{
Id = Snapshot.ContractId.GetAndChange().NextId++,
Script = script.ToArray(),
UpdateCounter = 0,
Script = nef.Script,
Hash = hash,
Manifest = ContractManifest.Parse(manifest)
};

Expand All @@ -62,42 +64,35 @@ protected internal void CreateContract(byte[] script, byte[] manifest)
CallContractInternal(contract, md, new Array(ReferenceCounter) { false }, CallFlags.All, ReturnTypeConvention.EnsureIsEmpty);
}

protected internal void UpdateContract(byte[] script, byte[] manifest)
protected internal void UpdateContract(byte[] nefFile, byte[] manifest)
{
if (script is null && manifest is null) throw new ArgumentException();
if (nefFile is null && manifest is null) throw new ArgumentException();

AddGas(StoragePrice * ((script?.Length ?? 0) + (manifest?.Length ?? 0)));
AddGas(StoragePrice * ((nefFile?.Length ?? 0) + (manifest?.Length ?? 0)));

var contract = Snapshot.Contracts.TryGet(CurrentScriptHash);
var contract = Snapshot.Contracts.GetAndChange(CurrentScriptHash);
if (contract is null) throw new InvalidOperationException($"Updating Contract Does Not Exist: {CurrentScriptHash}");

if (script != null)
if (nefFile != null)
{
if (script.Length == 0 || script.Length > MaxContractLength)
throw new ArgumentException($"Invalid Script Length: {script.Length}");
UInt160 hash_new = script.ToScriptHash();
if (hash_new.Equals(CurrentScriptHash) || Snapshot.Contracts.TryGet(hash_new) != null)
throw new InvalidOperationException($"Adding Contract Hash Already Exist: {hash_new}");
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 (nefFile.Length == 0)
throw new ArgumentException($"Invalid NefFile Length: {nefFile.Length}");

NefFile nef = nefFile.AsSerializable<NefFile>();

// Update script
contract.Script = nef.Script;
}
if (manifest != null)
{
if (manifest.Length == 0 || manifest.Length > ContractManifest.MaxLength)
throw new ArgumentException($"Invalid Manifest Length: {manifest.Length}");
contract = Snapshot.Contracts.GetAndChange(contract.ScriptHash);
contract.Manifest = ContractManifest.Parse(manifest);
if (!contract.Manifest.IsValid(contract.ScriptHash))
throw new InvalidOperationException($"Invalid Manifest Hash: {contract.ScriptHash}");
if (!contract.Manifest.IsValid(contract.Hash))
throw new InvalidOperationException($"Invalid Manifest Hash: {contract.Hash}");
}
if (script != null)
contract.UpdateCounter++; // Increase update counter
if (nefFile != null)
{
ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy");
if (md != null)
Expand Down Expand Up @@ -136,22 +131,22 @@ private void CallContractInternal(UInt160 contractHash, string method, Array arg
ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method);
if (md is null) throw new InvalidOperationException($"Method {method} Does Not Exist In Contract {contractHash}");

ContractManifest currentManifest = Snapshot.Contracts.TryGet(CurrentScriptHash)?.Manifest;
if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, method))
ContractState currentContract = Snapshot.Contracts.TryGet(CurrentScriptHash);
if (currentContract?.CanCall(contract, method) == false)
throw new InvalidOperationException($"Cannot Call Method {method} Of Contract {contractHash} From Contract {CurrentScriptHash}");

CallContractInternal(contract, md, args, flags, convention);
}

private void CallContractInternal(ContractState contract, ContractMethodDescriptor method, Array args, CallFlags flags, ReturnTypeConvention convention)
{
if (invocationCounter.TryGetValue(contract.ScriptHash, out var counter))
if (invocationCounter.TryGetValue(contract.Hash, out var counter))
{
invocationCounter[contract.ScriptHash] = counter + 1;
invocationCounter[contract.Hash] = counter + 1;
}
else
{
invocationCounter[contract.ScriptHash] = 1;
invocationCounter[contract.Hash] = 1;
}

GetInvocationState(CurrentContext).Convention = convention;
Expand All @@ -161,11 +156,11 @@ private void CallContractInternal(ContractState contract, ContractMethodDescript
CallFlags callingFlags = state.CallFlags;

if (args.Count != method.Parameters.Length) throw new InvalidOperationException($"Method {method.Name} Expects {method.Parameters.Length} Arguments But Receives {args.Count} Arguments");
ExecutionContext context_new = LoadContract(contract, method.Name, flags & callingFlags);
ExecutionContext context_new = LoadContract(contract, method.Name, flags & callingFlags, false);
state = context_new.GetState<ExecutionContextState>();
state.CallingScriptHash = callingScriptHash;

if (NativeContract.IsNative(contract.ScriptHash))
if (NativeContract.IsNative(contract.Hash))
{
context_new.EvaluationStack.Push(args);
context_new.EvaluationStack.Push(method.Name);
Expand Down
1 change: 1 addition & 0 deletions src/neo/SmartContract/ApplicationEngine.Native.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ protected internal void DeployNativeContracts()
{
Id = contract.Id,
Script = contract.Script,
Hash = contract.Hash, // Use the native hash
Manifest = contract.Manifest
});
contract.Initialize(this);
Expand Down
15 changes: 10 additions & 5 deletions src/neo/SmartContract/ApplicationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,9 @@ public ExecutionContext LoadContract(ContractState contract, string method, Call
ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method);
if (md is null) return null;

ExecutionContext context = LoadScript(contract.Script, callFlags, md.Offset);
ExecutionContext context = LoadScript(contract.Script, callFlags, contract.Hash, md.Offset);

if (NativeContract.IsNative(contract.ScriptHash))
if (NativeContract.IsNative(contract.Hash))
{
if (packParameters)
{
Expand All @@ -202,10 +202,15 @@ public ExecutionContext LoadContract(ContractState contract, string method, Call
return context;
}

public ExecutionContext LoadScript(Script script, CallFlags callFlags, int initialPosition = 0)
public ExecutionContext LoadScript(Script script, CallFlags callFlags, UInt160 scriptHash = null, int initialPosition = 0)
{
ExecutionContext context = LoadScript(script, initialPosition);
context.GetState<ExecutionContextState>().CallFlags = callFlags;
// Create and configure context
ExecutionContext context = CreateContext(script, initialPosition);
var state = context.GetState<ExecutionContextState>();
state.CallFlags = callFlags;
state.ScriptHash = scriptHash ?? ((byte[])script).ToScriptHash();
// Load context
LoadContext(context);
return context;
}

Expand Down
6 changes: 3 additions & 3 deletions src/neo/SmartContract/Callbacks/MethodCallback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public MethodCallback(ApplicationEngine engine, UInt160 hash, string method)
{
if (method.StartsWith('_')) throw new ArgumentException();
this.contract = engine.Snapshot.Contracts[hash];
ContractManifest currentManifest = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash)?.Manifest;
if (currentManifest != null && !currentManifest.CanCall(this.contract.Manifest, method))
ContractState currentContract = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash);
if (currentContract?.CanCall(this.contract, method) == false)
throw new InvalidOperationException();
this.method = this.contract.Manifest.Abi.Methods.First(p => p.Name == method);
}
Expand All @@ -29,7 +29,7 @@ public override void LoadContext(ApplicationEngine engine, Array args)
{
engine.Push(args);
engine.Push(method.Name);
engine.Push(contract.ScriptHash.ToArray());
engine.Push(contract.Hash.ToArray());
}
}
}
2 changes: 1 addition & 1 deletion src/neo/SmartContract/DeployedContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public DeployedContract(ContractState contract)
if (contract is null) throw new ArgumentNullException(nameof(contract));

Script = null;
ScriptHash = contract.ScriptHash;
ScriptHash = contract.Hash;
ContractMethodDescriptor descriptor = contract.Manifest.Abi.GetMethod("verify");
if (descriptor is null) throw new NotSupportedException("The smart contract haven't got verify method.");

Expand Down
12 changes: 11 additions & 1 deletion src/neo/SmartContract/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ public static class Helper
{
private const long MaxVerificationGas = 0_50000000;

public static UInt160 GetContractHash(UInt160 sender, byte[] script)
{
using var sb = new ScriptBuilder();
sb.Emit(OpCode.ABORT);
sb.EmitPush(sender);
sb.EmitPush(script);

return sb.ToArray().ToScriptHash();
}

public static UInt160 GetScriptHash(this ExecutionContext context)
{
return context.GetState<ExecutionContextState>().ScriptHash;
Expand Down Expand Up @@ -170,7 +180,7 @@ internal static bool VerifyWitnesses(this IVerifiable verifiable, StoreView snap
{
if (NativeContract.IsNative(hashes[i])) return false;
if (hashes[i] != verifiable.Witnesses[i].ScriptHash) return false;
engine.LoadScript(verification, callFlags, 0);
engine.LoadScript(verification, callFlags, hashes[i], 0);
}

engine.LoadScript(verifiable.Witnesses[i].InvocationScript, CallFlags.None);
Expand Down
8 changes: 0 additions & 8 deletions src/neo/SmartContract/Manifest/ContractAbi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ public class ContractAbi
{
private IReadOnlyDictionary<string, ContractMethodDescriptor> methodDictionary;

/// <summary>
/// Hash is the script hash of the contract. It is encoded as a hexadecimal string in big-endian.
/// </summary>
public UInt160 Hash { get; set; }

/// <summary>
/// Methods is an array of Method objects which describe the details of each method in the contract.
/// </summary>
Expand All @@ -30,7 +25,6 @@ public ContractAbi Clone()
{
return new ContractAbi
{
Hash = Hash,
Methods = Methods.Select(p => p.Clone()).ToArray(),
Events = Events.Select(p => p.Clone()).ToArray()
};
Expand All @@ -45,7 +39,6 @@ public static ContractAbi FromJson(JObject json)
{
return new ContractAbi
{
Hash = UInt160.Parse(json["hash"].AsString()),
Methods = ((JArray)json["methods"]).Select(u => ContractMethodDescriptor.FromJson(u)).ToArray(),
Events = ((JArray)json["events"]).Select(u => ContractEventDescriptor.FromJson(u)).ToArray()
};
Expand All @@ -61,7 +54,6 @@ public ContractMethodDescriptor GetMethod(string name)
public JObject ToJson()
{
var json = new JObject();
json["hash"] = Hash.ToString();
json["methods"] = new JArray(Methods.Select(u => u.ToJson()).ToArray());
json["events"] = new JArray(Events.Select(u => u.ToJson()).ToArray());
return json;
Expand Down
Loading

0 comments on commit c0a3840

Please sign in to comment.