Skip to content

Commit 4f18675

Browse files
committed
feat(deploy): add fresh OUSD deployment
1 parent a4959a7 commit 4f18675

4 files changed

Lines changed: 241 additions & 5 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ lcov.info
9595
# Foundry / Soldeer
9696
contracts/dependencies/
9797
contracts/out/
98+
contracts/broadcast/
9899

99100
# Possible Agent.md file
100101
AGENTS.md

contracts/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ MAKEFLAGS += --no-print-directory
88
# ╚══════════════════════════════════════════════════════════════════════════════╝
99

1010
DEPLOY_SCRIPT := scripts/deploy/DeployManager.s.sol
11-
DEPLOY_BASE := --account deployerKey --sender $(DEPLOYER_ADDRESS) --broadcast --slow
11+
DEPLOY_BASE := --account deployerKey --sender $(DEPLOYER_ADDRESS) --broadcast --slow --no-isolate
1212
DEPLOY_BUILD := contracts/ scripts/deploy/
1313

1414
# ╔══════════════════════════════════════════════════════════════════════════════╗

contracts/scripts/deploy/helpers/GovHelper.sol

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,9 @@ library GovHelper {
250250
require(block.chainid == MAINNET_CHAIN_ID, "Unsupported governance chain");
251251

252252
// ===== Setup: Label addresses for trace readability =====
253-
address govMultisig = Mainnet.Timelock;
253+
// The Timelock owns governed contracts but has no delegated xOGN voting power.
254+
// The Guardian multisig proposes and votes, matching the established mainnet deployment flow.
255+
address govMultisig = Mainnet.Guardian;
254256
vm.label(govMultisig, "Gov Multisig");
255257

256258
IGovernance governance = IGovernance(Mainnet.GovernorSix);
@@ -294,7 +296,7 @@ library GovHelper {
294296
if (state == IGovernance.ProposalState.Pending) {
295297
log.info("Waiting for voting period...");
296298
// Fast-forward past the voting delay
297-
vm.roll(block.number + governance.votingDelay() + 1);
299+
_rollWithoutBlockhashBackfill(block.number + governance.votingDelay() + 1);
298300
vm.warp(block.timestamp + 1 minutes);
299301

300302
state = governance.state(proposalId);
@@ -308,7 +310,7 @@ library GovHelper {
308310
governance.castVote(proposalId, 1);
309311

310312
// Fast-forward past the voting period end
311-
vm.roll(governance.proposalDeadline(proposalId) + 20);
313+
_rollWithoutBlockhashBackfill(governance.proposalDeadline(proposalId) + 20);
312314
vm.warp(block.timestamp + 2 days);
313315
log.success("Vote cast");
314316

@@ -331,7 +333,7 @@ library GovHelper {
331333
log.info("Executing proposal...");
332334
// Fast-forward past the timelock delay
333335
uint256 propEta = governance.proposalEta(proposalId);
334-
vm.roll(block.number + 10);
336+
_rollWithoutBlockhashBackfill(block.number + 10);
335337
vm.warp(propEta + 20);
336338

337339
// Execute the proposal actions
@@ -349,6 +351,16 @@ library GovHelper {
349351
}
350352
}
351353

354+
/// @dev Foundry backfills the EIP-2935 history contract on every forward roll under Prague,
355+
/// causing one block hash and storage lookup per skipped block on forks. Temporarily use
356+
/// Cancun execution semantics for the cheatcode itself, then restore the active version.
357+
function _rollWithoutBlockhashBackfill(uint256 blockNumber) private {
358+
string memory evmVersion = vm.getEvmVersion();
359+
vm.setEvmVersion("cancun");
360+
vm.roll(blockNumber);
361+
vm.setEvmVersion(evmVersion);
362+
}
363+
352364
// ==================== TimelockController ==================== //
353365

354366
function _salt(GovProposal memory prop) private pure returns (bytes32) {
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.0;
3+
4+
// Deployment framework
5+
import { AbstractDeployScript } from "scripts/deploy/helpers/AbstractDeployScript.s.sol";
6+
import { GovHelper } from "scripts/deploy/helpers/GovHelper.sol";
7+
import { GovProposal } from "scripts/deploy/helpers/DeploymentTypes.sol";
8+
9+
// Contracts
10+
import { IVault } from "contracts/interfaces/IVault.sol";
11+
import { IWOToken } from "contracts/interfaces/IWOToken.sol";
12+
import { OUSD } from "contracts/token/OUSD.sol";
13+
import { WrappedOusd } from "contracts/token/WrappedOusd.sol";
14+
import { OUSDVault } from "contracts/vault/OUSDVault.sol";
15+
import { VaultValueChecker } from "contracts/strategies/VaultValueChecker.sol";
16+
import { InitializeGovernedUpgradeabilityProxy } from "contracts/proxies/InitializeGovernedUpgradeabilityProxy.sol";
17+
import { OUSDProxy, VaultProxy, WrappedOUSDProxy } from "contracts/proxies/Proxies.sol";
18+
19+
// OpenZeppelin
20+
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
21+
22+
// Mainnet addresses
23+
import { CrossChain, Mainnet } from "tests/utils/Addresses.sol";
24+
25+
/// @title 003_DeployOUSD
26+
/// @notice Deploys a fresh OUSD core system and its supporting contracts.
27+
/// @dev The deployment has two phases:
28+
/// 1. _execute() deploys and initializes OUSD, its Vault, Wrapped OUSD,
29+
/// and the VaultValueChecker.
30+
/// 2. _buildGovernanceProposal() configures and activates the new Vault.
31+
contract $003_DeployOUSD is AbstractDeployScript("003_DeployOUSD") {
32+
using GovHelper for GovProposal;
33+
34+
address internal constant GOVERNOR = Mainnet.Timelock;
35+
address internal constant STRATEGIST = CrossChain.multichainStrategist;
36+
uint256 internal constant INITIAL_CREDITS_PER_TOKEN = 1e27;
37+
uint256 internal constant REBASE_RATE_MAX = 200e18;
38+
uint256 internal constant WITHDRAWAL_CLAIM_DELAY = 10 minutes;
39+
40+
bool public constant override skip = true;
41+
42+
// ==================== Deployment Logic ==================== //
43+
44+
function _execute() internal override {
45+
// Deploy the core proxies first so their addresses can be passed to the
46+
// implementations' initializers and constructors.
47+
OUSDProxy ousdProxy = new OUSDProxy();
48+
VaultProxy vaultProxy = new VaultProxy();
49+
50+
OUSD ousdImpl = new OUSD();
51+
OUSDVault vaultImpl = new OUSDVault(Mainnet.USDC);
52+
53+
// Initialize each proxy atomically and hand governance over only after
54+
// its implementation initializer has completed.
55+
ousdProxy.initialize(
56+
address(ousdImpl),
57+
GOVERNOR,
58+
abi.encodeCall(
59+
OUSD.initialize,
60+
(address(vaultProxy), INITIAL_CREDITS_PER_TOKEN)
61+
)
62+
);
63+
vaultProxy.initialize(
64+
address(vaultImpl),
65+
GOVERNOR,
66+
abi.encodeCall(IVault.initialize, (address(ousdProxy)))
67+
);
68+
69+
WrappedOUSDProxy wrappedOusdProxy = new WrappedOUSDProxy();
70+
WrappedOusd wrappedOusdImpl = new WrappedOusd(
71+
ERC20(address(ousdProxy))
72+
);
73+
wrappedOusdProxy.initialize(
74+
address(wrappedOusdImpl),
75+
GOVERNOR,
76+
abi.encodeCall(IWOToken.initialize, ())
77+
);
78+
79+
VaultValueChecker vaultValueChecker = new VaultValueChecker(
80+
address(vaultProxy),
81+
address(ousdProxy)
82+
);
83+
84+
_recordDeployment("OUSD_IMPL", address(ousdImpl));
85+
_recordDeployment("OUSD_PROXY", address(ousdProxy));
86+
_recordDeployment("OUSD_VAULT_IMPL", address(vaultImpl));
87+
_recordDeployment("OUSD_VAULT_PROXY", address(vaultProxy));
88+
_recordDeployment("WRAPPED_OUSD_IMPL", address(wrappedOusdImpl));
89+
_recordDeployment("WRAPPED_OUSD_PROXY", address(wrappedOusdProxy));
90+
_recordDeployment(
91+
"OUSD_VAULT_VALUE_CHECKER",
92+
address(vaultValueChecker)
93+
);
94+
}
95+
96+
// ==================== Governance Proposal ==================== //
97+
98+
function _buildGovernanceProposal() internal override {
99+
address vaultProxy = resolver.resolve("OUSD_VAULT_PROXY");
100+
101+
govProposal.setDescription(
102+
"Configure and activate the fresh OUSD Vault"
103+
);
104+
govProposal.action(
105+
vaultProxy,
106+
"setStrategistAddr(address)",
107+
abi.encode(STRATEGIST)
108+
);
109+
govProposal.action(
110+
vaultProxy,
111+
"setRebaseRateMax(uint256)",
112+
abi.encode(REBASE_RATE_MAX)
113+
);
114+
govProposal.action(
115+
vaultProxy,
116+
"setWithdrawalClaimDelay(uint256)",
117+
abi.encode(WITHDRAWAL_CLAIM_DELAY)
118+
);
119+
govProposal.action(vaultProxy, "unpauseCapital()", bytes(""));
120+
}
121+
122+
// ==================== Fork Verification ==================== //
123+
124+
function _fork() internal override {
125+
address ousdProxyAddr = resolver.resolve("OUSD_PROXY");
126+
address vaultProxyAddr = resolver.resolve("OUSD_VAULT_PROXY");
127+
address wrappedOusdProxyAddr = resolver.resolve("WRAPPED_OUSD_PROXY");
128+
129+
InitializeGovernedUpgradeabilityProxy ousdProxy = InitializeGovernedUpgradeabilityProxy(
130+
payable(ousdProxyAddr)
131+
);
132+
InitializeGovernedUpgradeabilityProxy vaultProxy = InitializeGovernedUpgradeabilityProxy(
133+
payable(vaultProxyAddr)
134+
);
135+
InitializeGovernedUpgradeabilityProxy wrappedOusdProxy = InitializeGovernedUpgradeabilityProxy(
136+
payable(wrappedOusdProxyAddr)
137+
);
138+
139+
require(
140+
ousdProxy.implementation() == resolver.resolve("OUSD_IMPL"),
141+
"Unexpected OUSD implementation"
142+
);
143+
require(
144+
vaultProxy.implementation() == resolver.resolve("OUSD_VAULT_IMPL"),
145+
"Unexpected OUSD Vault implementation"
146+
);
147+
require(
148+
wrappedOusdProxy.implementation() ==
149+
resolver.resolve("WRAPPED_OUSD_IMPL"),
150+
"Unexpected Wrapped OUSD implementation"
151+
);
152+
require(ousdProxy.governor() == GOVERNOR, "Unexpected OUSD governor");
153+
require(
154+
vaultProxy.governor() == GOVERNOR,
155+
"Unexpected OUSD Vault governor"
156+
);
157+
require(
158+
wrappedOusdProxy.governor() == GOVERNOR,
159+
"Unexpected Wrapped OUSD governor"
160+
);
161+
162+
OUSD ousd = OUSD(ousdProxyAddr);
163+
IVault vault = IVault(vaultProxyAddr);
164+
WrappedOusd wrappedOusd = WrappedOusd(wrappedOusdProxyAddr);
165+
VaultValueChecker vaultValueChecker = VaultValueChecker(
166+
resolver.resolve("OUSD_VAULT_VALUE_CHECKER")
167+
);
168+
169+
require(
170+
ousd.vaultAddress() == vaultProxyAddr,
171+
"OUSD Vault link is incorrect"
172+
);
173+
require(
174+
address(vault.oToken()) == ousdProxyAddr,
175+
"Vault OUSD link is incorrect"
176+
);
177+
require(
178+
ousd.rebasingCreditsPerTokenHighres() == INITIAL_CREDITS_PER_TOKEN,
179+
"Unexpected OUSD credits resolution"
180+
);
181+
require(
182+
keccak256(bytes(ousd.name())) == keccak256(bytes("Origin Dollar")),
183+
"Unexpected OUSD name"
184+
);
185+
require(
186+
keccak256(bytes(ousd.symbol())) == keccak256(bytes("OUSD")),
187+
"Unexpected OUSD symbol"
188+
);
189+
require(ousd.totalSupply() == 0, "OUSD supply is not zero");
190+
191+
require(vault.asset() == Mainnet.USDC, "Unexpected OUSD Vault asset");
192+
require(
193+
vault.strategistAddr() == STRATEGIST,
194+
"Unexpected OUSD Vault strategist"
195+
);
196+
require(!vault.capitalPaused(), "OUSD Vault capital is paused");
197+
require(
198+
vault.withdrawalClaimDelay() == WITHDRAWAL_CLAIM_DELAY,
199+
"Unexpected withdrawal claim delay"
200+
);
201+
require(
202+
vault.rebasePerSecondMax() == REBASE_RATE_MAX / 100 / 365 days,
203+
"Unexpected maximum rebase rate"
204+
);
205+
206+
require(
207+
wrappedOusd.asset() == ousdProxyAddr,
208+
"Unexpected Wrapped OUSD asset"
209+
);
210+
require(
211+
wrappedOusd.adjuster() == INITIAL_CREDITS_PER_TOKEN,
212+
"Wrapped OUSD is not initialized"
213+
);
214+
require(
215+
address(vaultValueChecker.vault()) == vaultProxyAddr,
216+
"VaultValueChecker Vault link is incorrect"
217+
);
218+
require(
219+
address(vaultValueChecker.ousd()) == ousdProxyAddr,
220+
"VaultValueChecker OUSD link is incorrect"
221+
);
222+
}
223+
}

0 commit comments

Comments
 (0)