Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions test/src/concrete/BatchRecordingReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {StoxReceipt} from "../../../src/concrete/StoxReceipt.sol";

/// @dev Contract recipient that records its own balance per id observed
/// during `onERC1155BatchReceived`. Mirrors `RecordingReceiver` for the
/// batch path.
contract BatchRecordingReceiver {
StoxReceipt public immutable RECEIPT;
mapping(uint256 => uint256) public observedBalance;

constructor(StoxReceipt receipt_) {
RECEIPT = receipt_;
}

function onERC1155BatchReceived(address, address, uint256[] calldata ids, uint256[] calldata, bytes calldata)
external
returns (bytes4)
{
for (uint256 i = 0; i < ids.length; i++) {
observedBalance[ids[i]] = RECEIPT.balanceOf(address(this), ids[i]);
}
return this.onERC1155BatchReceived.selector;
}
}
78 changes: 78 additions & 0 deletions test/src/concrete/CorporateActionHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {LibCorporateAction} from "../../../src/lib/LibCorporateAction.sol";
import {
CorporateActionNode,
CompletionFilter,
LibCorporateActionNode
} from "../../../src/lib/LibCorporateActionNode.sol";
import {LibTestCorporateAction} from "../../lib/LibTestCorporateAction.sol";

/// @dev Harness to test library functions directly.
contract CorporateActionHarness {
uint8 public constant decimals = 18;

function resolveActionType(bytes32 typeHash, bytes calldata parameters) external returns (uint256) {
return LibCorporateAction.resolveActionType(typeHash, parameters);
}

function schedule(uint256 actionType, uint64 effectiveTime, bytes memory parameters) external returns (uint256) {
return LibCorporateAction.schedule(actionType, effectiveTime, parameters);
}

function cancel(uint256 actionIndex) external {
LibCorporateAction.cancel(actionIndex);
}

function countCompleted() external view returns (uint256) {
return LibCorporateAction.countCompleted();
}

function nextOfType(uint256 cursor, uint256 mask, CompletionFilter filter) external view returns (uint256) {
return LibCorporateActionNode.nextOfType(cursor, mask, filter);
}

function prevOfType(uint256 cursor, uint256 mask, CompletionFilter filter) external view returns (uint256) {
return LibCorporateActionNode.prevOfType(cursor, mask, filter);
}

function getNode(uint256 actionIndex) external view returns (CorporateActionNode memory) {
return LibCorporateAction.getStorage().nodes[actionIndex];
}

function head() external view returns (uint256) {
return LibTestCorporateAction.head();
}

function tail() external view returns (uint256) {
return LibTestCorporateAction.tail();
}

/// Library-path readers — all go through `LibCorporateAction.getStorage()`,
/// so `testStorageLayoutPin` can prove a value written at a specific slot
/// is actually the slot the library reads from.
function accountMigrationCursor(address account) external view returns (uint256) {
return LibCorporateAction.getStorage().accountMigrationCursor[account];
}

function unmigrated(uint256 cursor) external view returns (uint256) {
return LibCorporateAction.getStorage().unmigrated[cursor];
}

function totalSupplyLatestCursor() external view returns (uint256) {
return LibCorporateAction.getStorage().totalSupplyLatestCursor;
}

/// Direct call to `ensureBootstrap` so tests can observe the
/// post-bootstrap pre-user-action state (`bootstrap.prev` /
/// `bootstrap.next` both `NODE_NONE`). The production `schedule` call
/// fires `ensureBootstrap` and then immediately splices in the user
/// action, mutating `bootstrap.next` away from `NODE_NONE` — so the
/// only way to pin the in-between state is to invoke
/// `ensureBootstrap` standalone.
function ensureBootstrap() external {
LibCorporateAction.ensureBootstrap(LibCorporateAction.getStorage());
}
}
48 changes: 48 additions & 0 deletions test/src/concrete/DelegatecallHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {LibCorporateAction} from "../../../src/lib/LibCorporateAction.sol";
import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol";

/// @dev Minimal harness that delegates calls to a facet. Also exposes an
/// `authorizer()` function so the facet's `OffchainAssetReceiptVault(address
/// (this)).authorizer()` lookup resolves to a test-controlled mock instead of
/// a real rain.vats authorizer.
contract DelegatecallHarness {
address public immutable FACET;
IAuthorizeV1 public authorizer;
uint8 public constant decimals = 18;

constructor(address facet_) {
FACET = facet_;
}

function setAuthorizer(IAuthorizeV1 authorizer_) external {
authorizer = authorizer_;
}

/// @dev Schedule directly via the library, bypassing auth and
/// `resolveActionType` validation. Runs in this harness's storage so that
/// a subsequent delegatecalled `getActionParameters` (which also runs in
/// this harness's storage) observes the write. Exists purely to support
/// fuzzing arbitrary `bytes` payloads that the normal facet path would
/// reject.
function scheduleRaw(uint256 actionType, uint64 effectiveTime, bytes memory parameters) external returns (uint256) {
return LibCorporateAction.schedule(actionType, effectiveTime, parameters);
}

fallback() external payable {
address target = FACET;
assembly {
calldatacopy(0, 0, calldatasize())
let success := delegatecall(gas(), target, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch success
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}

receive() external payable {}
}
29 changes: 29 additions & 0 deletions test/src/concrete/InvariantReceipt.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {StoxReceipt} from "../../../src/concrete/StoxReceipt.sol";
import {LibCorporateActionReceipt} from "../../../src/lib/LibCorporateActionReceipt.sol";
import {LibERC1155Storage} from "../../../src/lib/LibERC1155Storage.sol";

/// @dev Receipt harness used by the invariant suite. Initializes `manager`
/// to the invariant vault via direct slot write (bypassing the Receipt
/// base's `initializer` lock — we're in a fresh deployment, not a proxy
/// upgrade path). Exposes the raw stored balance and cursor for
/// assertions.
contract InvariantReceipt is StoxReceipt {
function testInit(address vaultAddr) external {
bytes32 slot = 0xe5444a702a2f437387f4eb075af275e349f1dba9a68923d27352f035d01dc200;
assembly {
sstore(slot, vaultAddr)
}
}

function rawReceiptBalance(address account, uint256 id) external view returns (uint256) {
return LibERC1155Storage.underlyingBalance(account, id);
}

function holderIdCursor(address account, uint256 id) external view returns (uint256) {
return LibCorporateActionReceipt.getStorage().accountIdCursor[account][id];
}
}
136 changes: 136 additions & 0 deletions test/src/concrete/InvariantVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {StoxReceiptVault} from "../../../src/concrete/StoxReceiptVault.sol";
import {ERC20Upgradeable} from "@openzeppelin-contracts-upgradeable-5.6.1/token/ERC20/ERC20Upgradeable.sol";
import {LibCorporateAction} from "../../../src/lib/LibCorporateAction.sol";
import {ACTION_TYPE_STOCK_SPLIT_V1} from "../../../src/interface/ICorporateActionsV1.sol";
import {LibERC20Storage} from "../../../src/lib/LibERC20Storage.sol";
import {LibTotalSupply} from "../../../src/lib/LibTotalSupply.sol";
import {
CorporateActionNode,
CompletionFilter,
LibCorporateActionNode,
NODE_NONE
} from "../../../src/lib/LibCorporateActionNode.sol";
import {LibTestCorporateAction} from "../../lib/LibTestCorporateAction.sol";

/// @dev Auth-bypassed vault subclass used by the invariant harness. Mirrors
/// the production `StoxReceiptVault._update` flow exactly, only skipping the
/// `OffchainAssetReceiptVault` authorizer/freeze middle layer — the migration
/// semantics under test live entirely in `StoxReceiptVault` and the libraries
/// it calls. Also exposes the internal cursor / split state so invariants can
/// read it.
contract InvariantVault is StoxReceiptVault {
function _update(address from, address to, uint256 amount) internal override {
LibTotalSupply.fold();

migrateAccount(from);
migrateAccount(to);

ERC20Upgradeable._update(from, to, amount);

if (from == address(0)) {
LibTotalSupply.onMint(amount);
} else if (to == address(0)) {
LibTotalSupply.onBurn(amount);
}
}

function publicUpdate(address from, address to, uint256 amount) external {
_update(from, to, amount);
}

function publicSchedule(uint256 actionType, uint64 effectiveTime, bytes memory parameters)
external
returns (uint256)
{
return LibCorporateAction.schedule(actionType, effectiveTime, parameters);
}

function publicCancel(uint256 actionIndex) external {
LibCorporateAction.cancel(actionIndex);
}

function migrationCursor(address account) external view returns (uint256) {
return LibCorporateAction.getStorage().accountMigrationCursor[account];
}

function totalSupplyLatestCursor() external view returns (uint256) {
return LibCorporateAction.getStorage().totalSupplyLatestCursor;
}

function listHead() external view returns (uint256) {
return LibTestCorporateAction.head();
}

function listTail() external view returns (uint256) {
return LibTestCorporateAction.tail();
}

function getNode(uint256 index) external view returns (CorporateActionNode memory) {
return LibCorporateAction.getStorage().nodes[index];
}

function nodesLength() external view returns (uint256) {
return LibCorporateAction.getStorage().nodes.length;
}

function rawStoredBalance(address account) external view returns (uint256) {
return LibERC20Storage.underlyingBalance(account);
}

/// @dev Whether any stock split in the list has reached its effective
/// time. `effectiveTotalSupply` applies multipliers once this is true
/// even if `fold()` has not yet been called to update
/// `totalSupplyLatestCursor`, so invariants that depend on the
/// no-multiplier regime must gate on this rather than
/// `totalSupplyLatestCursor == 0`.
function hasCompletedSplit() external view returns (bool) {
return LibCorporateActionNode.nextOfType(NODE_NONE, ACTION_TYPE_STOCK_SPLIT_V1, CompletionFilter.COMPLETED)
!= NODE_NONE;
}

// -----------------------------------------------------------------------
// ICorporateActionsV1 read surface — forwarded directly to the libraries
// so the receipt contract can read stock split multipliers cross-contract
// without a facet delegatecall router.
//
// Only the subset LibReceiptRebase actually consumes is implemented; the
// other traversal getters are omitted because the receipt never calls
// them. These are view-only; no migration or authorization logic needed.

function nextOfType(uint256 cursor, uint256 mask, CompletionFilter filter)
external
view
returns (uint256, uint256, uint64)
{
uint256 nextCursor = LibCorporateActionNode.nextOfType(cursor, mask, filter);
if (nextCursor == NODE_NONE) {
return (nextCursor, 0, 0);
}
CorporateActionNode storage node = LibCorporateAction.getStorage().nodes[nextCursor];
return (nextCursor, node.actionType, node.effectiveTime);
}

function getActionParameters(uint256 cursor) external view returns (bytes memory) {
LibCorporateAction.CorporateActionStorage storage s = LibCorporateAction.getStorage();
require(cursor != NODE_NONE && cursor < s.nodes.length, "InvariantVault: action does not exist");
return s.nodes[cursor].parameters;
}

// -----------------------------------------------------------------------
// IReceiptManagerV2.authorizeReceiptTransfer3 — no-op override (always
// allows the transfer) so the receipt's base `_update` path can run in
// the invariant harness without needing an ethgild authorizer wired in.
// The real ethgild vault derives a multi-layered auth decision here;
// our invariant test doesn't care about auth correctness, only the
// rebase math.

function authorizeReceiptTransfer3(address, address, address, uint256[] memory, uint256[] memory)
public
pure
override
{}
}
31 changes: 31 additions & 0 deletions test/src/concrete/MockAuthorizer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {IAuthorizeV1, Unauthorized} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol";

/// @dev Mock authorizer used by the facet tests. Records the most recent
/// `authorize` call so tests can assert the per-action context that the facet
/// passes through. When `denyMode` is true, every `authorize` call reverts
/// with `Unauthorized`, exercising the auth-denial code path.
contract MockAuthorizer is IAuthorizeV1 {
bool public denyMode;
address public lastUser;
bytes32 public lastPermission;
bytes public lastData;
uint256 public callCount;

function setDenyMode(bool deny) external {
denyMode = deny;
}

function authorize(address user, bytes32 permission, bytes memory data) external override {
callCount++;
lastUser = user;
lastPermission = permission;
lastData = data;
if (denyMode) {
revert Unauthorized(user, permission, data);
}
}
}
Loading
Loading