Skip to content
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

Update contract without change the hash #2044

Merged
merged 59 commits into from
Dec 1, 2020
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
9279b77
Update without hash change
shargon Nov 3, 2020
95e29ba
Specify version
shargon Nov 3, 2020
dbd18ce
Merge branch 'master' into update-no-hash-change
shargon Nov 4, 2020
37828b2
Merge branch 'master' into update-no-hash-change
shargon Nov 5, 2020
b1de3b7
Update ContractState.cs
erikzhang Nov 6, 2020
ab16cea
Update ApplicationEngine.Contract.cs
erikzhang Nov 6, 2020
9ada6b1
Update ApplicationEngine.Contract.cs
erikzhang Nov 6, 2020
4fb82f1
Update ApplicationEngine.Contract.cs
erikzhang Nov 6, 2020
31a4153
Change to ushort
shargon Nov 7, 2020
b4ba71c
Return Id
shargon Nov 7, 2020
b1962d6
Merge branch 'master' into update-no-hash-change
shargon Nov 7, 2020
658a597
Update ContractState.cs
shargon Nov 7, 2020
be896c5
Remove hash_new check
shargon Nov 7, 2020
da309c2
Update ApplicationEngine.Contract.cs
shargon Nov 7, 2020
9835862
Fix UT
shargon Nov 7, 2020
ad441b6
Merge branch 'master' into update-no-hash-change
shargon Nov 10, 2020
1e3d16a
Merge branch 'master' into update-no-hash-change
shargon Nov 12, 2020
7d2d476
Merge branch 'master' into update-no-hash-change
shargon Nov 12, 2020
9d4f65c
Remove Hash from ABI and change hash strategy
shargon Nov 12, 2020
ca93390
Clean code
shargon Nov 12, 2020
6a7912c
Remove MaxLength
shargon Nov 12, 2020
a2618a5
Change script size verification to nefFile
shargon Nov 13, 2020
c9d024c
Rename Version to UpdateCounter
shargon Nov 13, 2020
09d5efd
Rename ContractState.ScriptHash to Hash
shargon Nov 16, 2020
099922d
Merge branch 'master' into update-no-hash-change
erikzhang Nov 18, 2020
457818f
Some Erik's suggestions
shargon Nov 18, 2020
312fb08
Some Erik's suggestions
shargon Nov 18, 2020
12deb7b
Move CanCall
shargon Nov 18, 2020
138d447
Merge remote-tracking branch 'neo-project/master' into update-no-hash…
shargon Nov 19, 2020
15998ac
Remove Script hash from NefFile
shargon Nov 19, 2020
7703317
Merge branch 'master' into update-no-hash-change
erikzhang Nov 21, 2020
4bcf19f
Move to Helper
shargon Nov 21, 2020
92d32ee
Simplify ContractState
shargon Nov 21, 2020
bed862e
Erik's review
shargon Nov 21, 2020
7b1b422
NefFile.GetHash()
erikzhang Nov 22, 2020
4ccf14c
Remove double check
erikzhang Nov 22, 2020
41a67b8
Merge branch 'master' into update-no-hash-change
shargon Nov 24, 2020
a3b45d1
Merge remote-tracking branch 'neo-project/master' into update-no-hash…
shargon Nov 24, 2020
fe709fb
Some fixes
shargon Nov 24, 2020
1b10459
Some fixes
shargon Nov 24, 2020
4deecdc
Merge branch 'master' into update-no-hash-change
shargon Nov 25, 2020
582deb2
Reduce changes
shargon Nov 25, 2020
fa1075e
Merge branch 'master' into update-no-hash-change
shargon Nov 26, 2020
ba92c2e
Move GetContractHash to Helper
erikzhang Nov 26, 2020
d8ec4bc
Use GetContractHash for native contracts
erikzhang Nov 26, 2020
2b1f913
Fix UT paritially
erikzhang Nov 26, 2020
ae342c5
Merge branch 'master' into update-no-hash-change
shargon Nov 26, 2020
f6bc60d
Some fixes
shargon Nov 26, 2020
84b6100
Fix UT
shargon Nov 26, 2020
27b6bf6
Rename parameters
erikzhang Nov 27, 2020
7177b62
Update NefFile.cs
erikzhang Nov 27, 2020
4b08968
Update NefFile.cs
erikzhang Nov 27, 2020
1d26f56
Merge branch 'master' into update-no-hash-change
shargon Nov 27, 2020
13f23a2
Change version to string
shargon Nov 27, 2020
ecaf7ea
Increase to 32
shargon Nov 29, 2020
971be0d
Fix
shargon Nov 29, 2020
24525cd
Merge branch 'master' into update-no-hash-change
shargon Nov 30, 2020
648e646
Fix UT
shargon Nov 30, 2020
de2eefe
Merge branch 'master' into update-no-hash-change
shargon Dec 1, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
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)
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
{
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