Skip to content

Ensure that view interops can't be changed outside #3812

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

Merged
merged 35 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cf3ac98
Ensure that view interop can't be changed outside
shargon Mar 7, 2025
15992c9
Merge branch 'master' into read-only-interops
shargon Mar 7, 2025
5afc21e
revent one line
shargon Mar 7, 2025
dd923db
Merge branch 'read-only-interops' of https://github.com/neo-project/n…
shargon Mar 7, 2025
d7ec438
Add ut
shargon Mar 7, 2025
dae76d0
more oracle
shargon Mar 7, 2025
bf28a5b
Add neo
shargon Mar 7, 2025
f21c703
Merge branch 'master' into read-only-interops
shargon Mar 10, 2025
d536776
Merge branch 'master' into read-only-interops
cschuchardt88 Mar 12, 2025
eaefc5d
Merge branch 'master' into read-only-interops
shargon Mar 12, 2025
d6eaf88
Merge branch 'master' into read-only-interops
shargon Mar 12, 2025
3c9b331
Merge branch 'master' into read-only-interops
shargon Mar 12, 2025
34575b2
Merge branch 'master' into read-only-interops
shargon Mar 13, 2025
9fb6ea0
Optimize
shargon Mar 13, 2025
f661b30
Merge branch 'master' into read-only-interops
shargon Mar 17, 2025
9c6889d
Merge branch 'master' into read-only-interops
Jim8y Mar 18, 2025
b256627
Merge branch 'master' into read-only-interops
shargon Mar 21, 2025
24d6d51
Merge branch 'master' into read-only-interops
shargon Mar 22, 2025
226e79a
SmartContract: fix XML comment formatting
AnnaShaleva Mar 25, 2025
243e0cd
Merge branch 'master' into read-only-interops
AnnaShaleva Mar 25, 2025
9b2f3d0
Merge branch 'master' into read-only-interops
shargon Mar 28, 2025
5d5fef7
Merge branch 'master' into read-only-interops
Jim8y Apr 1, 2025
d42c384
fix serialize empty value
Jim8y Apr 1, 2025
7d53144
Merge branch 'master' into read-only-interops
Jim8y Apr 1, 2025
b4ed847
Merge branch 'master' into read-only-interops
shargon Apr 3, 2025
0cd4a83
Remove some clones
shargon Apr 3, 2025
19bef0f
check if it's sealed
shargon Apr 3, 2025
5528fe4
optimize
shargon Apr 3, 2025
2a14a6a
change !
shargon Apr 3, 2025
ee274bf
clean
shargon Apr 3, 2025
e5254bd
Merge branch 'master' into read-only-interops
Jim8y Apr 3, 2025
5580e65
Merge branch 'master' into read-only-interops
shargon Apr 4, 2025
f58c413
Merge branch 'master' into read-only-interops
Jim8y Apr 7, 2025
dfe2b5f
Merge branch 'master' into read-only-interops
Jim8y Apr 9, 2025
18e8e7b
Merge branch 'master' into read-only-interops
Jim8y Apr 12, 2025
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
4 changes: 2 additions & 2 deletions src/Neo.VM/Debugger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private void ExecuteAndCheckBreakPoints()
engine.ExecuteNext();
if (engine.State == VMState.NONE && engine.InvocationStack.Count > 0 && break_points.Count > 0)
{
if (break_points.TryGetValue(engine.CurrentContext!.Script, out HashSet<uint>? hashset) && hashset.Contains((uint)engine.CurrentContext.InstructionPointer))
if (break_points.TryGetValue(engine.CurrentContext!.Script, out var hashset) && hashset.Contains((uint)engine.CurrentContext.InstructionPointer))
engine.State = VMState.BREAK;
}
}
Expand All @@ -79,7 +79,7 @@ private void ExecuteAndCheckBreakPoints()
/// </returns>
public bool RemoveBreakPoint(Script script, uint position)
{
if (!break_points.TryGetValue(script, out HashSet<uint>? hashset)) return false;
if (!break_points.TryGetValue(script, out var hashset)) return false;
if (!hashset.Remove(position)) return false;
if (hashset.Count == 0) break_points.Remove(script);
return true;
Expand Down
6 changes: 3 additions & 3 deletions src/Neo/SmartContract/BinarySerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimi
public static StackItem Deserialize(ref MemoryReader reader, uint maxSize, uint maxItems, IReferenceCounter referenceCounter = null)
{
Stack<StackItem> deserialized = new();
int undeserialized = 1;
var undeserialized = 1;
while (undeserialized-- > 0)
{
StackItemType type = (StackItemType)reader.ReadByte();
var type = (StackItemType)reader.ReadByte();
switch (type)
{
case StackItemType.Any:
Expand All @@ -102,7 +102,7 @@ public static StackItem Deserialize(ref MemoryReader reader, uint maxSize, uint
deserialized.Push(reader.ReadVarMemory((int)maxSize));
break;
case StackItemType.Buffer:
ReadOnlyMemory<byte> memory = reader.ReadVarMemory((int)maxSize);
var memory = reader.ReadVarMemory((int)maxSize);
deserialized.Push(new Buffer(memory.Span));
break;
case StackItemType.Array:
Expand Down
2 changes: 1 addition & 1 deletion src/Neo/SmartContract/Native/ContractManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public bool HasMethod(IReadOnlyStore snapshot, UInt160 hash, string method, int
public IEnumerable<ContractState> ListContracts(IReadOnlyStore snapshot)
{
var listContractsPrefix = CreateStorageKey(Prefix_Contract);
return snapshot.Find(listContractsPrefix).Select(kvp => kvp.Value.GetInteroperable<ContractState>(false));
return snapshot.Find(listContractsPrefix).Select(kvp => kvp.Value.GetInteroperableClone<ContractState>(false));
}

[ContractMethod(RequiredCallFlags = CallFlags.All)]
Expand Down
2 changes: 1 addition & 1 deletion src/Neo/SmartContract/Native/NeoToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ public ECPoint[] GetCommittee(IReadOnlyStore snapshot)
public NeoAccountState GetAccountState(IReadOnlyStore snapshot, UInt160 account)
{
var key = CreateStorageKey(Prefix_Account, account);
return snapshot.TryGet(key, out var item) ? item.GetInteroperable<NeoAccountState>() : null;
return snapshot.TryGet(key, out var item) ? item.GetInteroperableClone<NeoAccountState>() : null;
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions src/Neo/SmartContract/Native/OracleContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ private UInt256 GetOriginalTxid(ApplicationEngine engine)
public OracleRequest GetRequest(IReadOnlyStore snapshot, ulong id)
{
var key = CreateStorageKey(Prefix_Request, id);
return snapshot.TryGet(key, out var item) ? item.GetInteroperable<OracleRequest>() : null;
return snapshot.TryGet(key, out var item) ? item.GetInteroperableClone<OracleRequest>() : null;
}

/// <summary>
Expand All @@ -116,7 +116,7 @@ public OracleRequest GetRequest(IReadOnlyStore snapshot, ulong id)
{
var key = CreateStorageKey(Prefix_Request);
return snapshot.Find(key)
.Select(p => (BinaryPrimitives.ReadUInt64BigEndian(p.Key.Key.Span[1..]), p.Value.GetInteroperable<OracleRequest>()));
.Select(p => (BinaryPrimitives.ReadUInt64BigEndian(p.Key.Key.Span[1..]), p.Value.GetInteroperableClone<OracleRequest>()));
}

/// <summary>
Expand All @@ -133,7 +133,7 @@ public OracleRequest GetRequest(IReadOnlyStore snapshot, ulong id)
foreach (ulong id in list)
{
var key = CreateStorageKey(Prefix_Request, id);
yield return (id, snapshot[key].GetInteroperable<OracleRequest>());
yield return (id, snapshot[key].GetInteroperableClone<OracleRequest>());
}
}

Expand Down
58 changes: 43 additions & 15 deletions src/Neo/SmartContract/StorageItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public StorageItem(IInteroperable interoperable)
}

/// <summary>
/// Create a new instance from an sealed <see cref="IInteroperable"/> class.
/// Create a new instance from an sealed <see cref="IInteroperable"/> class.
/// </summary>
/// <param name="interoperable">The <see cref="IInteroperable"/> value of the <see cref="StorageItem"/>.</param>
/// <returns><see cref="StorageItem"/> class</returns>
Expand Down Expand Up @@ -187,32 +187,60 @@ public void FromReplica(StorageItem replica)
/// <returns>The <see cref="IInteroperable"/> in the storage.</returns>
public T GetInteroperable<T>() where T : IInteroperable, new()
{
if (_cache is null)
{
var interoperable = new T();
interoperable.FromStackItem(BinarySerializer.Deserialize(_value, ExecutionEngineLimits.Default));
_cache = interoperable;
}
_cache ??= GetInteroperableClone<T>();
_value = null;
return (T)_cache;
}

/// <summary>
/// Gets an <see cref="IInteroperable"/> from the storage.
/// Gets an <see cref="IInteroperableVerifiable"/> from the storage.
/// </summary>
/// <param name="verify">Verify deserialization</param>
/// <typeparam name="T">The type of the <see cref="IInteroperableVerifiable"/>.</typeparam>
/// <returns>The <see cref="IInteroperableVerifiable"/> in the storage.</returns>
public T GetInteroperable<T>(bool verify = true) where T : IInteroperableVerifiable, new()
{
_cache ??= GetInteroperableClone<T>(verify);
_value = null;
return (T)_cache;
}

/// <summary>
/// Gets an <see cref="IInteroperable"/> from the storage not related to this <see cref="StorageItem"/>.
/// </summary>
/// <typeparam name="T">The type of the <see cref="IInteroperable"/>.</typeparam>
/// <returns>The <see cref="IInteroperable"/> in the storage.</returns>
public T GetInteroperable<T>(bool verify = true) where T : IInteroperableVerifiable, new()
public T GetInteroperableClone<T>() where T : IInteroperable, new()
{
if (_cache is null)
// If it's interoperable and not sealed
if (_value.IsEmpty && _cache is T interoperable)
{
var interoperable = new T();
interoperable.FromStackItem(BinarySerializer.Deserialize(_value, ExecutionEngineLimits.Default), verify);
_cache = interoperable;
// Refresh data without change _value
return (T)interoperable.Clone();
}
_value = null;
return (T)_cache;

interoperable = new T();
interoperable.FromStackItem(BinarySerializer.Deserialize(_value, ExecutionEngineLimits.Default));
return interoperable;
}

/// <summary>
/// Gets an <see cref="IInteroperableVerifiable"/> from the storage not related to this <see cref="StorageItem"/>.
/// </summary>
/// <param name="verify">Verify deserialization</param>
/// <typeparam name="T">The type of the <see cref="IInteroperableVerifiable"/>.</typeparam>
/// <returns>The <see cref="IInteroperableVerifiable"/> in the storage.</returns>
public T GetInteroperableClone<T>(bool verify = true) where T : IInteroperableVerifiable, new()
{
// If it's interoperable and not sealed
if (_value.IsEmpty && _cache is T interoperable)
{
return (T)interoperable.Clone();
}

interoperable = new T();
interoperable.FromStackItem(BinarySerializer.Deserialize(_value, ExecutionEngineLimits.Default), verify);
return interoperable;
}

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions tests/Neo.UnitTests/Extensions/UT_ContractStateExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public void TestGetStorage()

Assert.IsNotNull(item);
Assert.AreEqual(100_000_000, item.GetInteroperable<AccountState>().Balance);

// Ensure GetInteroperableClone don't change nothing

item.GetInteroperableClone<AccountState>().Balance = 123;
Assert.AreEqual(100_000_000, item.GetInteroperable<AccountState>().Balance);
}
}
}
15 changes: 11 additions & 4 deletions tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,24 +83,31 @@ public void Verify()
var key = UT_MemoryPool.CreateStorageKey(NativeContract.Ledger.Id, Prefix_Transaction, _u.ToArray());

// Conflicting transaction is in the Conflicts attribute of some other on-chain transaction.
var tx = new Transaction()
{
Script = new byte[] { (byte)OpCode.RET },
Witnesses = [Witness.Empty],
Signers = [new Signer() { Account = UInt160.Zero }],
Attributes = []
};
var conflict = new TransactionState();
snapshotCache.Add(key, new StorageItem(conflict));
Assert.IsTrue(test.Verify(snapshotCache, new Transaction()));
Assert.IsTrue(test.Verify(snapshotCache, tx));

// Conflicting transaction is on-chain.
snapshotCache.Delete(key);
conflict = new TransactionState
{
BlockIndex = 123,
Transaction = new Transaction(),
Transaction = tx,
State = VMState.NONE
};
snapshotCache.Add(key, new StorageItem(conflict));
Assert.IsFalse(test.Verify(snapshotCache, new Transaction()));
Assert.IsFalse(test.Verify(snapshotCache, tx));

// There's no conflicting transaction at all.
snapshotCache.Delete(key);
Assert.IsTrue(test.Verify(snapshotCache, new Transaction()));
Assert.IsTrue(test.Verify(snapshotCache, tx));
}
}
}
2 changes: 1 addition & 1 deletion tests/Neo.UnitTests/SmartContract/UT_InteropPrices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void ApplicationEngineRegularPut()
snapshot.Add(skey, sItem);
snapshot.AddContract(script.ToScriptHash(), contractState);

using ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, snapshot);
using var ae = ApplicationEngine.Create(TriggerType.Application, null, snapshot);
Debugger debugger = new(ae);
ae.LoadScript(script);
debugger.StepInto();
Expand Down
8 changes: 5 additions & 3 deletions tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public void TestIsStandardContract()
public void TestVerifyWitnesses()
{
var snapshotCache1 = TestBlockchain.GetTestSnapshotCache().CloneCache();
UInt256 index1 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01");
var index1 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01");
TestUtils.BlocksAdd(snapshotCache1, index1, new TrimmedBlock()
{
Header = new Header
Expand All @@ -142,7 +142,7 @@ public void TestVerifyWitnesses()
Assert.IsFalse(Helper.VerifyWitnesses(new Header() { PrevHash = index1 }, TestProtocolSettings.Default, snapshotCache1, 100));

var snapshotCache2 = TestBlockchain.GetTestSnapshotCache();
UInt256 index2 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01");
var index2 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01");
TrimmedBlock block2 = new()
{
Header = new Header
Expand All @@ -163,7 +163,7 @@ public void TestVerifyWitnesses()
Assert.IsFalse(Helper.VerifyWitnesses(header2, TestProtocolSettings.Default, snapshotCache2, 100));

var snapshotCache3 = TestBlockchain.GetTestSnapshotCache();
UInt256 index3 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01");
var index3 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01");
TrimmedBlock block3 = new()
{
Header = new Header
Expand All @@ -180,6 +180,8 @@ public void TestVerifyWitnesses()
Header header3 = new()
{
PrevHash = index3,
MerkleRoot = UInt256.Zero,
NextConsensus = UInt160.Zero,
Witness = Witness.Empty
};
snapshotCache3.AddContract(UInt160.Zero, new ContractState()
Expand Down