Skip to content

Commit d38e873

Browse files
committed
Abort inconsistent Ownable2Step update state
1 parent 40fb757 commit d38e873

2 files changed

Lines changed: 27 additions & 8 deletions

File tree

src/Neo.SmartContract.Framework/Ownable2Step.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,10 @@ public static void RenounceOwnership()
190190
/// <remarks>
191191
/// On a contract update this preserves the current owner. If no owner is stored yet, it
192192
/// initializes one so contracts adopting <see cref="Ownable2Step"/> during an upgrade do not
193-
/// remain ownerless. Note that an in-flight pending transfer is intentionally left untouched
194-
/// across an update; operators should cancel any in-flight transfer before upgrading if the
195-
/// upgrade changes trust assumptions.
193+
/// remain ownerless. If an owner exists but the initialization marker is missing, the update
194+
/// aborts instead of silently accepting inconsistent ownership state. Note that an in-flight
195+
/// pending transfer is intentionally left untouched across an update; operators should cancel
196+
/// any in-flight transfer before upgrading if the upgrade changes trust assumptions.
196197
/// </remarks>
197198
protected static void InitializeOwner(object? data, bool update)
198199
{
@@ -201,11 +202,7 @@ protected static void InitializeOwner(object? data, bool update)
201202
if (OwnerInitializedStorage.GetBoolean(ByteString.Empty))
202203
return;
203204

204-
if (GetOwner() is not null)
205-
{
206-
OwnerInitializedStorage.Put(ByteString.Empty, true);
207-
return;
208-
}
205+
ExecutionEngine.Assert(GetOwner() is null, "owner initialization state is inconsistent");
209206
}
210207

211208
data ??= Runtime.Transaction.Sender;

tests/Neo.SmartContract.Framework.UnitTests/Ownable2StepTest.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,19 @@ public void InitializeOwner_UpdateInitializesWhenOwnerMissing()
299299
Merge(contract);
300300
}
301301

302+
[TestMethod]
303+
public void InitializeOwner_UpdateWithUnmarkedOwner_Aborts()
304+
{
305+
var engine = CreateEngine();
306+
var contract = Deploy(engine, out _, out _);
307+
308+
contract.SetOwnerWithoutInitializedForTest(Bob.Account);
309+
310+
Assert.ThrowsException<TestException>(() => contract.InitializeForTest(Charlie.Account, true));
311+
Assert.AreEqual(Bob.Account, contract.GetOwner());
312+
Merge(contract);
313+
}
314+
302315
[TestMethod]
303316
public void InitializeOwner_UpdateDoesNotReinitializeAfterRenounce()
304317
{
@@ -438,6 +451,12 @@ public static void ClearOwnershipStateForTest()
438451
Storage.Delete(new byte[] { 0xFD });
439452
Storage.Delete(new byte[] { 0xFB });
440453
}
454+
455+
public static void SetOwnerWithoutInitializedForTest(UInt160 owner)
456+
{
457+
Storage.Put(new byte[] { 0xFD }, owner);
458+
Storage.Delete(new byte[] { 0xFB });
459+
}
441460
}";
442461

443462
var tempFile = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.cs");
@@ -503,6 +522,9 @@ public abstract class OwnableTwoStepProxy(SmartContractInitialize initialize)
503522
[DisplayName("clearOwnershipStateForTest")]
504523
public abstract void ClearOwnershipStateForTest();
505524

525+
[DisplayName("setOwnerWithoutInitializedForTest")]
526+
public abstract void SetOwnerWithoutInitializedForTest(UInt160 owner);
527+
506528
[DisplayName("transferOwnership")]
507529
public abstract void TransferOwnership(UInt160 newOwner);
508530

0 commit comments

Comments
 (0)