Skip to content

Commit f0ca28e

Browse files
committed
Route factory onboarding through propose and approve
1 parent 549344c commit f0ca28e

5 files changed

Lines changed: 75 additions & 12 deletions

File tree

src/factory/CornerStoreFactory.sol

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,31 @@ contract CornerStoreFactory is Governed {
2727
venueRegistry = _venueRegistry;
2828
}
2929

30-
/// @notice Register an RWA token's compliance manifest and execution venue.
30+
/// @notice Register an RWA token's compliance manifest and execution venue in
31+
/// one governed onboarding call. The manifest ends ACTIVE.
3132
/// @dev Governed: only owner/operator may onboard tokens.
33+
///
34+
/// Onboarding deliberately runs the manifest lifecycle's propose→approve
35+
/// steps back-to-back: `registerManifest` lands the manifest in PROPOSED,
36+
/// then `approveManifest` moves it to ACTIVE. The factory is BOTH the
37+
/// owner (register is `onlyOwner`) and the approving operator (approve is
38+
/// `onlyOperator`) of the registry in this flow — governance transfers
39+
/// registry ownership to the factory during deployment (see contract
40+
/// natspec). Collapsing propose+approve into a single trusted call is the
41+
/// intended onboarding shape; the two-step lifecycle exists to gate
42+
/// externally-declared manifests, not this atomic governed path.
43+
///
44+
/// CONSEQUENCE of `msg.sender` semantics: because `registerManifest`
45+
/// records `declaredBy = msg.sender`, the manifest's `declaredBy` is this
46+
/// factory's address (not the EOA/governance account that called the
47+
/// factory), and `approvedBy` is likewise the factory. Attribution stops
48+
/// at the factory boundary for tokens onboarded this way.
3249
function registerRWAToken(
3350
address token,
3451
ManifestCore calldata manifest,
3552
address venue,
3653
VenueConfig calldata venueCfg
3754
) external onlyOperator {
38-
// TASK-2 SEAM: onboarding is propose-then-approve in one governed call.
39-
// The factory is the operator here; register lands PROPOSED, approve
40-
// moves it to ACTIVE, preserving the old one-call "token ends ACTIVE".
4155
tokenPolicyRegistry.registerManifest(token, manifest);
4256
tokenPolicyRegistry.approveManifest(token);
4357
venueRegistry.registerVenue(venue, venueCfg);

test/integration/EmergencyPause.t.sol

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,54 @@ contract EmergencyPauseTest is IntegrationBase {
4848
assertEq(rwaToken.balanceOf(alice), 0, "no RWA while policy suspended");
4949
assertEq(quote.balanceOf(alice), 1_000 ether, "no quote spent");
5050
}
51+
52+
// A PROPOSED (registered but not yet operator-approved) manifest must be
53+
// rejected end-to-end: the engine's default-deny gate fails closed before any
54+
// recipe runs. Drive rwaToken back to PROPOSED via retire -> register
55+
// (deployStack left it ACTIVE and re-register over ACTIVE is illegal).
56+
function test_proposedPolicy_failsClosed() public {
57+
policyReg.retireManifest(address(rwaToken), bytes32("REISSUE"));
58+
policyReg.registerManifest(address(rwaToken), _activeManifest(0, 0)); // PROPOSED, not approved
59+
60+
ExecutionRequest memory req = buildBuyRequest(alice, 50 ether, 50 ether);
61+
vm.prank(alice);
62+
vm.expectRevert(); // ComplianceRejected(reasonCode) — POLICY/PROPOSED
63+
router.execute(req);
64+
65+
assertEq(rwaToken.balanceOf(alice), 0, "no RWA while policy only PROPOSED");
66+
assertEq(quote.balanceOf(alice), 1_000 ether, "no quote spent");
67+
}
68+
69+
// A RETIRED (terminal) manifest must be rejected end-to-end for the same
70+
// default-deny reason.
71+
function test_retiredPolicy_failsClosed() public {
72+
policyReg.retireManifest(address(rwaToken), bytes32("EOL")); // ACTIVE -> RETIRED
73+
74+
ExecutionRequest memory req = buildBuyRequest(alice, 50 ether, 50 ether);
75+
vm.prank(alice);
76+
vm.expectRevert(); // ComplianceRejected(reasonCode) — POLICY/RETIRED
77+
router.execute(req);
78+
79+
assertEq(rwaToken.balanceOf(alice), 0, "no RWA while policy RETIRED");
80+
assertEq(quote.balanceOf(alice), 1_000 ether, "no quote spent");
81+
}
82+
83+
// A suspension is reversible: after resume the token is ACTIVE again and a
84+
// trade that was blocked while SUSPENDED settles once more.
85+
function test_suspendThenResume_tradesAgain() public {
86+
// suspend → blocked.
87+
policyReg.suspendManifest(address(rwaToken), bytes32("EMERGENCY"));
88+
ExecutionRequest memory blocked = buildBuyRequest(alice, 50 ether, 50 ether);
89+
vm.prank(alice);
90+
vm.expectRevert(); // ComplianceRejected — POLICY/SUSPENDED
91+
router.execute(blocked);
92+
assertEq(rwaToken.balanceOf(alice), 0, "blocked while suspended");
93+
94+
// resume → ACTIVE again → trade settles.
95+
policyReg.resumeManifest(address(rwaToken));
96+
ExecutionRequest memory ok = buildBuyRequest(alice, 50 ether, 50 ether);
97+
vm.prank(alice);
98+
router.execute(ok);
99+
assertEq(rwaToken.balanceOf(alice), 50 ether, "trade settles after resume");
100+
}
51101
}

test/integration/IntegrationBase.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,7 @@ abstract contract IntegrationBase is TREXSuite {
170170
quote = new MockERC20("Quote USD", "qUSD");
171171
pool = new MockPool(IERC20(address(quote)), IERC20(address(rwaToken)));
172172

173-
// 7. manifests
174-
// TASK-2 SEAM: onboarding now goes through propose -> approve.
173+
// 7. manifests: onboarding goes through the lifecycle (propose -> approve).
175174
policyReg.registerManifest(address(rwaToken), _activeManifest(fundRecipeId, factsPacked));
176175
policyReg.approveManifest(address(rwaToken));
177176
// Quote/cash is out-of-scope: tag UNREGULATED directly from UNKNOWN.

test/integration/Surveillance.t.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ contract SurveillanceTest is IntegrationBase {
2727

2828
ManifestCore memory m = _activeManifest(0, 0);
2929
m.issuanceRecipeId = 7;
30-
// TASK-2 SEAM: deployStack already left rwaToken ACTIVE; re-pointing the
31-
// issuance recipe now requires the terminal-then-reissue path
32-
// (retire -> register -> approve) since re-register over ACTIVE reverts.
30+
// deployStack already left rwaToken ACTIVE; re-pointing the issuance recipe
31+
// goes through the terminal-then-reissue path (retire -> register ->
32+
// approve) since re-register over ACTIVE reverts by design.
3333
policyReg.retireManifest(address(rwaToken), bytes32("REISSUE"));
3434
policyReg.registerManifest(address(rwaToken), m);
3535
policyReg.approveManifest(address(rwaToken));

test/unit/compliance/Engine.t.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ contract EngineTest is Test {
150150
}
151151

152152
function _registerRWA(uint16 fundRecipeId, uint256 factsPacked) internal {
153-
// TASK-2 SEAM: register lands PROPOSED, approve moves it to ACTIVE.
153+
// Onboard the legal way: register lands PROPOSED, approve moves it to ACTIVE.
154154
policyReg.registerManifest(RWA, _activeManifest(fundRecipeId, factsPacked));
155155
policyReg.approveManifest(RWA);
156156
_registerCashUnregulated();
@@ -161,7 +161,7 @@ contract EngineTest is Test {
161161
/// engine never infers UNREGULATED from an absent manifest, so every
162162
/// ACTIVE-side test must register CASH as UNREGULATED.
163163
function _registerCashUnregulated() internal {
164-
// TASK-2 SEAM: UNREGULATED is now set via setUnregulated (only from UNKNOWN).
164+
// UNREGULATED is set via setUnregulated (only from UNKNOWN).
165165
policyReg.setUnregulated(CASH);
166166
}
167167

@@ -268,7 +268,7 @@ contract EngineTest is Test {
268268
}
269269

270270
function test_suspended_fails_closed() public {
271-
// TASK-2 SEAM: reach SUSPENDED via register -> approve -> suspend.
271+
// Reach SUSPENDED the legal way: register -> approve -> suspend.
272272
ManifestCore memory m = _activeManifest(0, 0);
273273
policyReg.registerManifest(RWA, m);
274274
policyReg.approveManifest(RWA);

0 commit comments

Comments
 (0)