Skip to content

Commit d5cf2f7

Browse files
committed
feat: implement system-wide pause functionality with new task and tests
1 parent b48ed0f commit d5cf2f7

File tree

4 files changed

+710
-1
lines changed

4 files changed

+710
-1
lines changed

contracts/CollateralManagement.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ contract CollateralManagementContract is
101101
uint256 resignDelayInBlocks,
102102
uint256 rewardPercentage
103103
) external initializer {
104+
__ReentrancyGuard_init();
104105
__AccessControlDefaultAdminRules_init(initialDelay, owner);
105106
__Pausable_init();
106-
__ReentrancyGuard_init();
107107
_minCollateral = minCollateral;
108108
_resignDelayInBlocks = resignDelayInBlocks;
109109
_rewardPercentage = rewardPercentage;

tasks/pause-system.ts

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
2+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
3+
/* eslint-disable @typescript-eslint/no-unsafe-call */
4+
/* eslint-disable @typescript-eslint/restrict-template-expressions */
5+
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
6+
/* eslint-disable @typescript-eslint/dot-notation */
7+
/* eslint-disable @typescript-eslint/no-explicit-any */
8+
/* eslint-disable @typescript-eslint/no-confusing-void-expression */
9+
10+
import { task, types } from "hardhat/config";
11+
import { DeploymentConfig, read } from "../scripts/deployment-utils/deploy";
12+
13+
interface ContractInfo {
14+
name: string;
15+
address: string;
16+
contractName: string;
17+
}
18+
19+
task("pause-system")
20+
.setDescription(
21+
"Pause/unpause all Flyover system contracts simultaneously (FlyoverDiscovery, PegInContract, PegOutContract, CollateralManagement)"
22+
)
23+
.addParam(
24+
"action",
25+
"Action to perform: 'pause' or 'unpause'",
26+
undefined,
27+
types.string
28+
)
29+
.addOptionalParam(
30+
"reason",
31+
"Reason for pausing (required when action=pause)",
32+
undefined,
33+
types.string
34+
)
35+
.addOptionalParam(
36+
"pauser",
37+
"Address of the account with PAUSER_ROLE (defaults to first signer)",
38+
undefined,
39+
types.string
40+
)
41+
.setAction(async (args, hre) => {
42+
const { ethers, network } = hre;
43+
const typedArgs = args as {
44+
action: string;
45+
reason?: string;
46+
pauser?: string;
47+
};
48+
49+
const action = typedArgs.action.toLowerCase();
50+
const reason = typedArgs.reason;
51+
const pauserAddress = typedArgs.pauser;
52+
53+
// Validate inputs
54+
if (!["pause", "unpause"].includes(action)) {
55+
throw new Error("Action must be 'pause' or 'unpause'");
56+
}
57+
58+
if (action === "pause" && !reason) {
59+
throw new Error("Reason parameter is required when pausing");
60+
}
61+
62+
console.info(
63+
`🚀 ${action.toUpperCase()} operation starting on network: ${
64+
network.name
65+
}`
66+
);
67+
if (action === "pause") {
68+
console.info(`📝 Reason: ${reason}`);
69+
}
70+
71+
// Get deployed contract addresses
72+
const addresses: Partial<DeploymentConfig> = read();
73+
const networkDeployments: Partial<DeploymentConfig[string]> | undefined =
74+
addresses[network.name];
75+
76+
if (!networkDeployments) {
77+
throw new Error(
78+
`No deployment config found for network: ${network.name}`
79+
);
80+
}
81+
82+
const contracts: ContractInfo[] = [
83+
{
84+
name: "FlyoverDiscovery",
85+
address: networkDeployments["FlyoverDiscovery"]?.address || "",
86+
contractName: "FlyoverDiscovery",
87+
},
88+
{
89+
name: "PegInContract",
90+
address: networkDeployments["PegInContract"]?.address || "",
91+
contractName: "PegInContract",
92+
},
93+
{
94+
name: "PegOutContract",
95+
address: networkDeployments["PegOutContract"]?.address || "",
96+
contractName: "PegOutContract",
97+
},
98+
{
99+
name: "CollateralManagementContract",
100+
address:
101+
networkDeployments["CollateralManagementContract"]?.address || "",
102+
contractName: "CollateralManagementContract",
103+
},
104+
];
105+
106+
// Validate all contracts are deployed
107+
const missingContracts = contracts.filter((c) => !c.address);
108+
if (missingContracts.length > 0) {
109+
throw new Error(
110+
`Missing contract addresses for: ${missingContracts
111+
.map((c) => c.name)
112+
.join(", ")}`
113+
);
114+
}
115+
116+
// Get signer
117+
const signers = await ethers.getSigners();
118+
let signer = signers[0];
119+
120+
if (pauserAddress) {
121+
// Use specific pauser address if provided
122+
signer = await ethers.getSigner(pauserAddress);
123+
}
124+
125+
console.info(`👤 Using account: ${signer.address}`);
126+
127+
// Check pause status before operation
128+
console.info("\n📊 Current pause status:");
129+
for (const contract of contracts) {
130+
const contractInstance = await ethers.getContractAt(
131+
contract.contractName,
132+
contract.address
133+
);
134+
const pauseStatus = await contractInstance.pauseStatus();
135+
console.info(
136+
` ${contract.name}: ${pauseStatus.isPaused ? "PAUSED" : "ACTIVE"}`
137+
);
138+
if (pauseStatus.isPaused) {
139+
console.info(` - Reason: ${pauseStatus.reason}`);
140+
console.info(
141+
` - Since: ${new Date(
142+
Number(pauseStatus.since) * 1000
143+
).toISOString()}`
144+
);
145+
}
146+
}
147+
148+
// Execute pause/unpause operation
149+
console.info(`\n🔄 Executing ${action} operation...`);
150+
const results: {
151+
contract: string;
152+
success: boolean;
153+
txHash?: string;
154+
error?: string;
155+
}[] = [];
156+
157+
for (const contract of contracts) {
158+
try {
159+
console.info(` Processing ${contract.name}...`);
160+
const contractInstance = await ethers.getContractAt(
161+
contract.contractName,
162+
contract.address
163+
);
164+
165+
let tx;
166+
if (action === "pause") {
167+
tx = await contractInstance.connect(signer).pause(reason!);
168+
} else {
169+
tx = await contractInstance.connect(signer).unpause();
170+
}
171+
172+
const receipt = await tx.wait();
173+
results.push({
174+
contract: contract.name,
175+
success: true,
176+
txHash: receipt!.hash,
177+
});
178+
console.info(
179+
` ✅ ${contract.name} ${action}d successfully - TX: ${
180+
receipt!.hash
181+
}`
182+
);
183+
} catch (error: any) {
184+
results.push({
185+
contract: contract.name,
186+
success: false,
187+
error: error.message,
188+
});
189+
console.info(
190+
` ❌ Failed to ${action} ${contract.name}: ${error.message}`
191+
);
192+
}
193+
}
194+
195+
// Summary
196+
console.info(`\n📋 Operation Summary:`);
197+
const successful = results.filter((r) => r.success);
198+
const failed = results.filter((r) => !r.success);
199+
200+
console.info(` ✅ Successful: ${successful.length}/${results.length}`);
201+
successful.forEach((r) => console.info(` - ${r.contract}: ${r.txHash}`));
202+
203+
if (failed.length > 0) {
204+
console.info(` ❌ Failed: ${failed.length}/${results.length}`);
205+
failed.forEach((r) => console.info(` - ${r.contract}: ${r.error}`));
206+
}
207+
208+
// Final status check
209+
console.info("\n📊 Final pause status:");
210+
for (const contract of contracts) {
211+
const contractInstance = await ethers.getContractAt(
212+
contract.contractName,
213+
contract.address
214+
);
215+
const pauseStatus = await contractInstance.pauseStatus();
216+
console.info(
217+
` ${contract.name}: ${pauseStatus.isPaused ? "PAUSED" : "ACTIVE"}`
218+
);
219+
if (pauseStatus.isPaused) {
220+
console.info(` - Reason: ${pauseStatus.reason}`);
221+
console.info(
222+
` - Since: ${new Date(
223+
Number(pauseStatus.since) * 1000
224+
).toISOString()}`
225+
);
226+
}
227+
}
228+
229+
if (failed.length > 0) {
230+
throw new Error(`${action} operation failed for some contracts`);
231+
}
232+
233+
console.info(
234+
`\n🎉 ${action.toUpperCase()} operation completed successfully!`
235+
);
236+
});

0 commit comments

Comments
 (0)