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

Contract redirectiton #1391

Closed
wants to merge 19 commits into from
22 changes: 20 additions & 2 deletions src/neo/Ledger/ContractState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,33 +31,47 @@ public UInt160 ScriptHash
}
}

int ISerializable.Size => Script.GetVarSize() + Manifest.ToJson().ToString().GetVarSize();
int ISerializable.Size => Script.GetVarSize() + Manifest.ToJson().ToString().GetVarSize() + sizeof(bool) + RedirectionHash.Size;

public UInt160 RedirectionHash = UInt160.Zero;

public bool HasRedirection => !RedirectionHash.Equals(UInt160.Zero);

public bool WasUpgraded = false;

doubiliu marked this conversation as resolved.
Show resolved Hide resolved
ContractState ICloneable<ContractState>.Clone()
{
return new ContractState
{
Script = Script,
Manifest = Manifest.Clone()
Manifest = Manifest.Clone(),
RedirectionHash = RedirectionHash,
WasUpgraded = WasUpgraded
};
}

void ISerializable.Deserialize(BinaryReader reader)
{
Script = reader.ReadVarBytes();
Manifest = reader.ReadSerializable<ContractManifest>();
RedirectionHash = reader.ReadSerializable<UInt160>();
WasUpgraded = reader.ReadBoolean();
}

void ICloneable<ContractState>.FromReplica(ContractState replica)
{
Script = replica.Script;
Manifest = replica.Manifest.Clone();
RedirectionHash = replica.RedirectionHash;
WasUpgraded = replica.WasUpgraded;
}

void ISerializable.Serialize(BinaryWriter writer)
{
writer.WriteVarBytes(Script);
writer.Write(Manifest);
writer.Write(RedirectionHash);
writer.Write(WasUpgraded);
}

public JObject ToJson()
Expand All @@ -66,6 +80,8 @@ public JObject ToJson()
json["hash"] = ScriptHash.ToString();
json["script"] = Convert.ToBase64String(Script);
json["manifest"] = Manifest.ToJson();
json["redirectionHash"] = RedirectionHash.ToString();
json["wasUpgraded"] = WasUpgraded;
return json;
}

Expand All @@ -74,6 +90,8 @@ public static ContractState FromJson(JObject json)
ContractState contractState = new ContractState();
contractState.Script = Convert.FromBase64String(json["script"].AsString());
contractState.Manifest = ContractManifest.FromJson(json["manifest"]);
contractState.RedirectionHash = UInt160.Parse(json["redirectionHash"].AsString());
contractState.WasUpgraded = json["wasUpgraded"].AsBoolean();
return contractState;
}

Expand Down
63 changes: 35 additions & 28 deletions src/neo/SmartContract/InteropService.Contract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ private static bool Contract_Create(ApplicationEngine engine)
Script = script,
Manifest = ContractManifest.Parse(manifest)
};

if (!contract.Manifest.IsValid(hash)) return false;

engine.Snapshot.Contracts.Add(hash, contract);
Expand All @@ -56,44 +55,39 @@ private static bool Contract_Update(ApplicationEngine engine)
var manifest = engine.CurrentContext.EvaluationStack.Pop().GetString();
if (manifest.Length > ContractManifest.MaxLength) return false;

var contract = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash);
if (contract is null) return false;

var oldContract = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash);
if (oldContract is null || oldContract.WasUpgraded) return false;
ContractState newcontract = null;
if (script.Length > 0)
{
UInt160 hash_new = script.ToScriptHash();
if (hash_new.Equals(engine.CurrentScriptHash)) return false;
if (engine.Snapshot.Contracts.TryGet(hash_new) != null) return false;
contract = new ContractState
newcontract = new ContractState
{
Script = script,
Manifest = contract.Manifest
Manifest = oldContract.Manifest
};
contract.Manifest.Abi.Hash = hash_new;
engine.Snapshot.Contracts.Add(hash_new, contract);
if (contract.HasStorage)
if (oldContract.HasRedirection)
{
newcontract.RedirectionHash = oldContract.RedirectionHash;
DeleteContractByHash(engine, oldContract.ScriptHash, out _);
}
else
{
foreach (var (key, value) in engine.Snapshot.Storages.Find(engine.CurrentScriptHash.ToArray()).ToArray())
{
engine.Snapshot.Storages.Add(new StorageKey
{
ScriptHash = hash_new,
Key = key.Key
}, new StorageItem
{
Value = value.Value,
IsConstant = false
});
}
newcontract.RedirectionHash = oldContract.ScriptHash;
oldContract = engine.Snapshot.Contracts.GetAndChange(oldContract.ScriptHash);
oldContract.WasUpgraded = true;
}
Contract_Destroy(engine);
newcontract.Manifest.Abi.Hash = hash_new;
engine.Snapshot.Contracts.Add(hash_new, newcontract);
}
if (manifest.Length > 0)
{
contract = engine.Snapshot.Contracts.GetAndChange(contract.ScriptHash);
contract.Manifest = ContractManifest.Parse(manifest);
if (!contract.Manifest.IsValid(contract.ScriptHash)) return false;
if (!contract.HasStorage && engine.Snapshot.Storages.Find(engine.CurrentScriptHash.ToArray()).Any()) return false;
newcontract = engine.Snapshot.Contracts.GetAndChange(newcontract.ScriptHash);
newcontract.Manifest = ContractManifest.Parse(manifest);
if (!newcontract.Manifest.IsValid(newcontract.ScriptHash)) return false;
if (!newcontract.HasStorage && engine.Snapshot.Storages.Find(engine.CurrentScriptHash.ToArray()).Any()) return false;
}

return true;
Expand All @@ -102,7 +96,20 @@ private static bool Contract_Update(ApplicationEngine engine)
private static bool Contract_Destroy(ApplicationEngine engine)
{
UInt160 hash = engine.CurrentScriptHash;
ContractState contract = engine.Snapshot.Contracts.TryGet(hash);
if (DeleteContractByHash(engine, hash, out ContractState contract))
{
if (contract != null && contract.HasRedirection)
{
DeleteContractByHash(engine, contract.RedirectionHash, out _);
}
return true;
}
return false;
}

doubiliu marked this conversation as resolved.
Show resolved Hide resolved
private static bool DeleteContractByHash(ApplicationEngine engine, UInt160 hash, out ContractState contract)
{
contract = engine.Snapshot.Contracts.TryGet(hash);
if (contract == null) return true;
engine.Snapshot.Contracts.Delete(hash);
if (contract.HasStorage)
Expand Down Expand Up @@ -140,7 +147,7 @@ private static bool Contract_CallEx(ApplicationEngine engine)
private static bool Contract_CallEx(ApplicationEngine engine, UInt160 contractHash, StackItem method, StackItem args, CallFlags flags)
{
ContractState contract = engine.Snapshot.Contracts.TryGet(contractHash);
if (contract is null) return false;
if (contract is null || contract.WasUpgraded) return false;

ContractManifest currentManifest = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash)?.Manifest;

Expand Down
22 changes: 14 additions & 8 deletions src/neo/SmartContract/InteropService.Storage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ private static bool PutExInternal(ApplicationEngine engine, StorageContext conte
if (value.Length > MaxValueSize) return false;
if (context.IsReadOnly) return false;
if (!CheckStorageContext(engine, context)) return false;

StorageKey skey = new StorageKey
{
ScriptHash = context.ScriptHash,
ScriptHash = context.PreDataKey,
Key = key
};

Expand All @@ -68,20 +67,26 @@ private static bool PutExInternal(ApplicationEngine engine, StorageContext conte

private static bool Storage_GetContext(ApplicationEngine engine)
{
ContractState contract = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash);
if (contract == null) return false;
engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext
{
ScriptHash = engine.CurrentScriptHash,
IsReadOnly = false
IsReadOnly = false,
PreDataKey = contract.HasRedirection ? contract.RedirectionHash : engine.CurrentScriptHash
}));
return true;
}

private static bool Storage_GetReadOnlyContext(ApplicationEngine engine)
{
ContractState contract = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash);
if (contract == null) return false;
engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new StorageContext
{
ScriptHash = engine.CurrentScriptHash,
IsReadOnly = true
IsReadOnly = true,
PreDataKey = contract.HasRedirection ? contract.RedirectionHash : engine.CurrentScriptHash
}));
return true;
}
Expand All @@ -95,7 +100,8 @@ private static bool Storage_AsReadOnly(ApplicationEngine engine)
context = new StorageContext
{
ScriptHash = context.ScriptHash,
IsReadOnly = true
IsReadOnly = true,
PreDataKey = context.PreDataKey
};
engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(context));
return true;
Expand All @@ -112,7 +118,7 @@ private static bool Storage_Get(ApplicationEngine engine)
byte[] key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray();
StorageItem item = engine.Snapshot.Storages.TryGet(new StorageKey
{
ScriptHash = context.ScriptHash,
ScriptHash = context.PreDataKey,
Key = key
});
engine.CurrentContext.EvaluationStack.Push(item?.Value ?? StackItem.Null);
Expand All @@ -128,7 +134,7 @@ private static bool Storage_Find(ApplicationEngine engine)
StorageContext context = _interface.GetInterface<StorageContext>();
if (!CheckStorageContext(engine, context)) return false;
byte[] prefix = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray();
byte[] prefix_key = StorageKey.CreateSearchPrefix(context.ScriptHash, prefix);
byte[] prefix_key = StorageKey.CreateSearchPrefix(context.PreDataKey, prefix);
StorageIterator iterator = engine.AddDisposable(new StorageIterator(engine.Snapshot.Storages.Find(prefix_key).Where(p => p.Key.Key.AsSpan().StartsWith(prefix)).GetEnumerator()));
engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(iterator));
return true;
Expand Down Expand Up @@ -166,7 +172,7 @@ private static bool Storage_Delete(ApplicationEngine engine)
if (!CheckStorageContext(engine, context)) return false;
StorageKey key = new StorageKey
{
ScriptHash = context.ScriptHash,
ScriptHash = context.PreDataKey,
Key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray()
};
if (engine.Snapshot.Storages.TryGet(key)?.IsConstant == true) return false;
Expand Down
1 change: 1 addition & 0 deletions src/neo/SmartContract/StorageContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ internal class StorageContext
{
public UInt160 ScriptHash;
public bool IsReadOnly;
public UInt160 PreDataKey;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any other idea for the name of this variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is preFix well? @vncoelho

Copy link
Contributor

@Tommo-L Tommo-L Jan 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming the ScriptHash to ContractHash, PreDataKey to NameSpace, Preifx, or StorageKeyPrefix ?

}
}
6 changes: 5 additions & 1 deletion tests/neo.UnitTests/Ledger/UT_ContractState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,15 @@ public void TestDeserialize()
}
((ContractState)newContract).Manifest.ToJson().ToString().Should().Be(contract.Manifest.ToJson().ToString());
((ContractState)newContract).Script.Should().BeEquivalentTo(contract.Script);
((ContractState)newContract).RedirectionHash.Should().BeEquivalentTo(contract.RedirectionHash);
((ContractState)newContract).WasUpgraded.Should().Be(contract.WasUpgraded);
}

[TestMethod]
public void TestGetSize()
{
ISerializable newContract = contract;
newContract.Size.Should().Be(368);
newContract.Size.Should().Be(389);
}

[TestMethod]
Expand All @@ -94,6 +96,8 @@ public void TestToJson()
json["hash"].AsString().Should().Be("0x820944cfdc70976602d71b0091445eedbc661bc5");
json["script"].AsString().Should().Be("AQ==");
json["manifest"].AsString().Should().Be(manifest.ToJson().AsString());
json["redirectionHash"].AsString().Should().Be(UInt160.Zero.ToString());
json["wasUpgraded"].AsBoolean().Should().Be(false);
}
}
}
33 changes: 30 additions & 3 deletions tests/neo.UnitTests/SmartContract/UT_InteropService.NEO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ public void TestContract_Update()
Signature = signature
}
};
manifest.Features = ContractFeatures.HasStorage;
var snapshot = Blockchain.Singleton.GetSnapshot();
var state = TestUtils.GetContract();
state.Manifest.Features = ContractFeatures.HasStorage;
Expand All @@ -243,14 +244,39 @@ public void TestContract_Update()
// Remove Storage flag with something stored

state.Manifest.Features = ContractFeatures.NoProperty;
snapshot.Contracts.Add(state.ScriptHash, state);
snapshot.Storages.Add(storageKey, storageItem);

engine = new ApplicationEngine(TriggerType.Application, null, snapshot, 0);
doubiliu marked this conversation as resolved.
Show resolved Hide resolved
engine.LoadScript(state.Script);
engine.CurrentContext.EvaluationStack.Push(manifest.ToString());
engine.CurrentContext.EvaluationStack.Push(script);
InteropService.Invoke(engine, InteropService.Contract.Update).Should().BeFalse();

//Secondary upgrade
byte[] newContract = new byte[script.Length];
System.Array.Copy(script, newContract, script.Length);
newContract[newContract.Length - 1] = 0x03;

var manifest2 = ContractManifest.CreateDefault(newContract.ToScriptHash());
byte[] privkey2 = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
KeyPair key2 = new KeyPair(privkey2);
ECPoint pubkey2 = key2.PublicKey;
byte[] signature2 = Crypto.Sign(newContract.ToScriptHash().ToArray(), privkey2, pubkey2.EncodePoint(false).Skip(1).ToArray());
manifest2.Groups = new ContractGroup[]
{
new ContractGroup()
{
PubKey = pubkey2,
Signature = signature2
}
};
manifest2.Features = ContractFeatures.HasStorage;

engine = new ApplicationEngine(TriggerType.Application, null, snapshot, 0);
engine.LoadScript(script);
engine.CurrentContext.EvaluationStack.Push(manifest2.ToString());
engine.CurrentContext.EvaluationStack.Push(newContract);
InteropService.Invoke(engine, InteropService.Contract.Update).Should().BeTrue();
}

[TestMethod]
Expand Down Expand Up @@ -279,7 +305,8 @@ public void TestStorage_Find()
engine.CurrentContext.EvaluationStack.Push(new InteropInterface(new StorageContext
{
ScriptHash = state.ScriptHash,
IsReadOnly = false
IsReadOnly = false,
PreDataKey = state.ScriptHash
}));
InteropService.Invoke(engine, InteropService.Storage.Find).Should().BeTrue();
var iterator = ((InteropInterface)engine.CurrentContext.EvaluationStack.Pop()).GetInterface<StorageIterator>();
Expand Down
Loading