-
Notifications
You must be signed in to change notification settings - Fork 1
fix(BOP-274): port Rust parity fixes into Solidity mocks #140
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
base: main
Are you sure you want to change the base?
Changes from all commits
c7e2ebe
b8e718d
ad44978
ea1b5b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,28 @@ contract MockB20Asset is MockB20, IB20Asset { | |
| /// by this before dividing. | ||
| uint256 public constant WAD_PRECISION = 1e18; | ||
|
|
||
| // ============================================================ | ||
| // ANNOUNCEMENT-ACTIVE FLAG | ||
| // ============================================================ | ||
|
|
||
| /// @dev Per-transaction flag set true at the start of `announce` | ||
| /// and false at the end, surfaced via `isAnnouncementActive()`. | ||
| /// Lives in transient storage (EIP-1153) because the value is | ||
| /// meaningful only within the bracket's call frame and MUST | ||
| /// reset between transactions; transient storage also means | ||
| /// the slot is reclaimed automatically on a revert anywhere in | ||
| /// the bracket, so the flag never gets stuck `true`. | ||
| /// | ||
| /// Declared as a contract-level state variable (not an | ||
| /// ERC-7201 namespaced struct field) because transient storage | ||
| /// lives in its own opcode-distinct address space — slot | ||
| /// indices here can't collide with the regular-storage layout | ||
| /// written by the factory bootstrap. The Rust precompile | ||
| /// exposes the same value via a runtime context flag rather | ||
| /// than a storage slot, so there is no persistent layout the | ||
| /// Rust impl needs to mirror. | ||
| bool internal transient _announcementActive; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the transient storage location is only available in solidity as of 0.8.28 but our pragma is open to 0.8.20 (not good). we could:
I'm partial to bumping the pragma to at least 0.8.24 and using tstorage (more closely reflects the rust revm memory impl), but I could also see us not bringing this feature to parity.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think bumping the pragma to support the most expressive/accurate mock implementation makes sense to me? If there were genuine compatibility concerns for other devs importing/working with this source code I'd hesitate more but given that these aren't production implementations I don't think it matters? |
||
|
|
||
| // ============================================================ | ||
| // DECIMALS | ||
| // ============================================================ | ||
|
|
@@ -97,6 +119,13 @@ contract MockB20Asset is MockB20, IB20Asset { | |
| // selector check were ever weakened. | ||
| $.usedAnnouncementIds[id] = true; | ||
|
|
||
| // Open the bracket — flip the transient flag BEFORE emitting | ||
| // `Announcement` and before dispatching any inner call, so any | ||
| // contract reached transitively through `internalCalls` sees | ||
| // `isAnnouncementActive() == true` for the full lifetime of the | ||
| // bracket. | ||
| _announcementActive = true; | ||
|
|
||
| emit Announcement(msg.sender, id, description, uri); | ||
|
|
||
| for (uint256 i = 0; i < internalCalls.length; i++) { | ||
|
|
@@ -106,12 +135,21 @@ contract MockB20Asset is MockB20, IB20Asset { | |
| } | ||
|
|
||
| emit EndAnnouncement(id); | ||
|
|
||
| // Close the bracket. A revert above leaves transient storage | ||
| // untouched at tx end (per EIP-1153), so an aborted bracket | ||
| // also resets the flag implicitly. | ||
| _announcementActive = false; | ||
| } | ||
|
|
||
| function isAnnouncementIdUsed(string calldata id) external view returns (bool) { | ||
| return MockB20AssetStorage.layout().usedAnnouncementIds[id]; | ||
| } | ||
|
|
||
| function isAnnouncementActive() external view returns (bool) { | ||
| return _announcementActive; | ||
| } | ||
|
|
||
| // ============================================================ | ||
| // MULTIPLIER | ||
| // ============================================================ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.20; | ||
|
|
||
| import {B20AssetTest} from "test/lib/B20AssetTest.sol"; | ||
|
|
||
| contract B20AssetIsAnnouncementActiveTest is B20AssetTest { | ||
| /// @notice Verifies isAnnouncementActive is false before any announce | ||
| /// @dev Default transient value is false; readback on a freshly bootstrapped token | ||
| /// with no announce call yet must report false. Fuzz parameter would add | ||
| /// no value here — the view takes no input. | ||
| function test_isAnnouncementActive_success_falseBeforeAnnounce() public view { | ||
| assertFalse(asset().isAnnouncementActive(), "must read false before any announce"); | ||
| } | ||
|
|
||
| /// @notice Verifies isAnnouncementActive resets to false after a completed announce | ||
| /// @dev The bracket flips the flag true at open and false at close. After a | ||
| /// successful announce returns, the next external view call must observe false. | ||
| /// Fuzz over `id` to exercise the flag-reset path independently of the consumed-id | ||
| /// bookkeeping that `isAnnouncementIdUsed` already covers. | ||
| function test_isAnnouncementActive_success_falseAfterAnnounce(string calldata id) public { | ||
| _announce(id); | ||
| assertFalse(asset().isAnnouncementActive(), "must read false after announce closes"); | ||
| } | ||
|
|
||
| /// @notice Verifies the flag also resets when announce reverts mid-bracket | ||
| /// @dev Per EIP-1153, transient storage is cleared at transaction end regardless of | ||
| /// whether the top-level call succeeds. A revert inside `internalCalls` therefore | ||
| /// cannot leave the flag stuck `true` across transactions. We trigger the | ||
| /// revert path via the existing recursion guard (inner call re-invoking `announce` | ||
| /// reverts AnnouncementInProgress), then in a *separate* transaction observe the | ||
| /// view reads false. | ||
| function test_isAnnouncementActive_success_falseAfterRevertedAnnounce() public { | ||
| _grantOperator(); | ||
|
|
||
| // Inner call that the recursion guard will reject — forces the outer | ||
| // announce to revert AFTER the flag has been set true at the top of the body. | ||
| bytes[] memory inner = _singletonBytes( | ||
| abi.encodeWithSelector( | ||
| bytes4(keccak256("announce(bytes[],string,string,string)")), new bytes[](0), "inner", "desc", "uri" | ||
| ) | ||
| ); | ||
|
|
||
| vm.prank(operator); | ||
| // Don't care about the specific revert selector here; any revert during the | ||
| // bracket exercises the "did the flag survive the abort?" property. | ||
| try asset().announce(inner, "id-revert", "desc", "uri") { | ||
| revert("announce was expected to revert"); | ||
| } catch {} | ||
|
|
||
| // New top-level call — transient storage from the prior reverted tx is gone. | ||
| assertFalse(asset().isAnnouncementActive(), "transient flag must reset after a reverted announce"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sold on this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if we wanted the state, it's not clear any external consumer would ever be able to use this given internal calls are internal so no external entity gets control flow when announcement is active
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I'm also not sold the state is even useful given no code is actually using it seemingly?