Skip to content

Commit 56e8334

Browse files
committed
feat: extended multisig change unit tests
1 parent e2ba3b7 commit 56e8334

File tree

3 files changed

+269
-14
lines changed

3 files changed

+269
-14
lines changed

contracts/safe/util/Encoder.sol

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: LGPL-3.0-only
2+
pragma solidity >=0.7.0 <0.9.0;
3+
4+
contract Encoder {
5+
function getCallDataSetProviderStatus(uint _index, bool _status) external pure returns (bytes memory) {
6+
return abi.encodeWithSignature(
7+
"setProviderStatus(uint256,bool)",
8+
_index,
9+
_status
10+
);
11+
}
12+
13+
function getCallDataForUpgrade(address proxy, address implementation) external pure returns (bytes memory) {
14+
return abi.encodeWithSignature(
15+
"upgrade(address,address)",
16+
proxy,
17+
implementation
18+
);
19+
}
20+
}

e2e/multisig-migration.test.ts

Lines changed: 244 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,28 @@ const { FORK_NETWORK_NAME } = process.env;
1717

1818
const multsigInfo: MultisigInfo = multsigInfoJson;
1919

20-
describe("Should change LBC owner to the multisig", function () {
20+
describe("Should change LBC owner to the multisig.ts", function () {
2121
it("Should change the owner", async () => {
2222
await checkForkedNetwork();
23-
2423
const networkName = FORK_NETWORK_NAME ?? "rskTestnet";
24+
console.log("Network name:", networkName);
2525

26+
const lbcName = "LiquidityBridgeContract";
2627
const addresses: Partial<DeploymentConfig> = read();
2728
const networkDeployments: Partial<DeploymentConfig[string]> | undefined =
2829
addresses[networkName];
29-
3030
const lbcAddress = networkDeployments?.LiquidityBridgeContract?.address;
31+
const safeAddress = multsigInfo[networkName].address;
3132

3233
if (!lbcAddress) {
3334
throw new Error(
3435
"LiquidityBridgeContract proxy deployment info not found"
3536
);
3637
}
38+
console.info(`LBC address: ${lbcAddress}`);
39+
console.info(`Safe address: ${safeAddress}`);
3740

38-
const lbc = await ethers.getContractAt(
39-
"LiquidityBridgeContractV2",
40-
lbcAddress
41-
);
42-
43-
const safeAddress = multsigInfo[networkName].address;
41+
const lbc = await ethers.getContractAt(lbcName, lbcAddress);
4442

4543
const lbcOwner = await lbc.owner();
4644
console.info("LBC owner:", lbcOwner);
@@ -52,6 +50,17 @@ describe("Should change LBC owner to the multisig", function () {
5250
).to.not.be.reverted;
5351
const newLbcOwner = await lbc.owner();
5452
console.info("New LBC owner:", newLbcOwner);
53+
54+
await expect(
55+
lbc.connect(impersonatedSigner).setProviderStatus(1, false)
56+
).to.be.revertedWith("LBC005");
57+
58+
expect(
59+
multisigExecProviderStatusChangeTransaction(safeAddress, lbcAddress)
60+
).to.eventually.be.equal(true);
61+
expect(
62+
multisigExecUpgradeTransaction(safeAddress, lbcAddress)
63+
).to.eventually.be.equal(true);
5564
});
5665
});
5766

@@ -62,3 +71,229 @@ async function checkForkedNetwork() {
6271
console.error("Not a forked network:", error);
6372
}
6473
}
74+
75+
function generateConcatenatedSignatures(owners: string[]) {
76+
const concatenatedSignatures =
77+
"0x" +
78+
owners
79+
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())) // SORT owners in ascending order
80+
.map((owner) => {
81+
return "0".repeat(24) + owner.slice(2) + "0".repeat(64) + "01";
82+
})
83+
.join("");
84+
85+
return concatenatedSignatures;
86+
}
87+
88+
export async function multisigExecProviderStatusChangeTransaction(
89+
safeAddress: string,
90+
lbcAddress: string
91+
): Promise<boolean> {
92+
const safeContract = await ethers.getContractAt("GnosisSafe", safeAddress);
93+
94+
const EncoderFactory = await ethers.getContractFactory("Encoder");
95+
const encoderDeployed = await EncoderFactory.deploy();
96+
const encoder = await ethers.getContractAt(
97+
"Encoder",
98+
await encoderDeployed.getAddress()
99+
);
100+
101+
const callData = await encoder.getCallDataSetProviderStatus(1, false);
102+
console.info("Call data:", callData);
103+
104+
const nonce = await safeContract.nonce();
105+
console.info("Nonce:", nonce);
106+
107+
const txData = {
108+
to: lbcAddress,
109+
value: 0,
110+
data: callData,
111+
operation: 0,
112+
safeTxGas: 0,
113+
baseGas: 0,
114+
gasPrice: 0,
115+
gasToken: ethers.ZeroAddress,
116+
refundReceiver: ethers.ZeroAddress,
117+
nonce: nonce,
118+
signatures: "0x",
119+
};
120+
121+
const owners = await safeContract.getOwners();
122+
123+
const desiredBalance = ethers.toQuantity(ethers.parseEther("100"));
124+
125+
await helpers.impersonateAccount(owners[0]);
126+
const impersonateOwner1 = await ethers.getSigner(owners[0]);
127+
await ethers.provider.send("hardhat_setBalance", [
128+
impersonateOwner1.address,
129+
desiredBalance,
130+
]);
131+
await helpers.impersonateAccount(owners[1]);
132+
const impersonateOwner2 = await ethers.getSigner(owners[1]);
133+
await ethers.provider.send("hardhat_setBalance", [
134+
impersonateOwner2.address,
135+
desiredBalance,
136+
]);
137+
await helpers.impersonateAccount(owners[2]);
138+
const impersonateOwner3 = await ethers.getSigner(owners[2]);
139+
await ethers.provider.send("hardhat_setBalance", [
140+
impersonateOwner3.address,
141+
desiredBalance,
142+
]);
143+
144+
const transactionHash = await safeContract
145+
.connect(impersonateOwner1)
146+
.getTransactionHash(
147+
txData.to,
148+
txData.value,
149+
txData.data,
150+
txData.operation,
151+
txData.safeTxGas,
152+
txData.baseGas,
153+
txData.gasPrice,
154+
txData.gasToken,
155+
txData.refundReceiver,
156+
txData.nonce
157+
);
158+
console.info("Transaction hash:", transactionHash);
159+
160+
await safeContract.connect(impersonateOwner1).approveHash(transactionHash);
161+
await safeContract.connect(impersonateOwner2).approveHash(transactionHash);
162+
await safeContract.connect(impersonateOwner3).approveHash(transactionHash);
163+
const signature = generateConcatenatedSignatures([
164+
impersonateOwner1.address,
165+
impersonateOwner2.address,
166+
impersonateOwner3.address,
167+
]);
168+
console.info("Signature:", signature);
169+
170+
txData.signatures = signature;
171+
172+
const result = await safeContract.execTransaction(
173+
txData.to,
174+
txData.value,
175+
txData.data,
176+
txData.operation,
177+
txData.safeTxGas,
178+
txData.baseGas,
179+
txData.gasPrice,
180+
txData.gasToken,
181+
txData.refundReceiver,
182+
txData.signatures
183+
);
184+
185+
return Boolean(result);
186+
}
187+
188+
export async function multisigExecUpgradeTransaction(
189+
safeAddress: string,
190+
lbcAddress: string
191+
): Promise<boolean> {
192+
const safeContract = await ethers.getContractAt("GnosisSafe", safeAddress);
193+
194+
const EncoderFactory = await ethers.getContractFactory("Encoder");
195+
const encoderDeployed = await EncoderFactory.deploy();
196+
const encoder = await ethers.getContractAt(
197+
"Encoder",
198+
await encoderDeployed.getAddress()
199+
);
200+
201+
const NewLbcFactory = await ethers.getContractFactory(
202+
"LiquidityBridgeContractV2"
203+
);
204+
const newLbcDeplolyed = await NewLbcFactory.deploy();
205+
const newLbc = await ethers.getContractAt(
206+
"LiquidityBridgeContractV2",
207+
await newLbcDeplolyed.getAddress()
208+
);
209+
const newLbcAddress = await newLbc.getAddress();
210+
211+
const callData = await encoder.getCallDataForUpgrade(
212+
lbcAddress,
213+
newLbcAddress
214+
);
215+
console.info("Call data:", callData);
216+
217+
const nonce = await safeContract.nonce();
218+
console.info("Nonce:", nonce);
219+
220+
const txData = {
221+
to: lbcAddress,
222+
value: 0,
223+
data: callData,
224+
operation: 0,
225+
safeTxGas: 0,
226+
baseGas: 0,
227+
gasPrice: 0,
228+
gasToken: ethers.ZeroAddress,
229+
refundReceiver: ethers.ZeroAddress,
230+
nonce: nonce,
231+
signatures: "0x",
232+
};
233+
234+
const owners = await safeContract.getOwners();
235+
236+
const desiredBalance = ethers.toQuantity(ethers.parseEther("100"));
237+
238+
await helpers.impersonateAccount(owners[0]);
239+
const impersonateOwner1 = await ethers.getSigner(owners[0]);
240+
await ethers.provider.send("hardhat_setBalance", [
241+
impersonateOwner1.address,
242+
desiredBalance,
243+
]);
244+
await helpers.impersonateAccount(owners[1]);
245+
const impersonateOwner2 = await ethers.getSigner(owners[1]);
246+
await ethers.provider.send("hardhat_setBalance", [
247+
impersonateOwner2.address,
248+
desiredBalance,
249+
]);
250+
await helpers.impersonateAccount(owners[2]);
251+
const impersonateOwner3 = await ethers.getSigner(owners[2]);
252+
await ethers.provider.send("hardhat_setBalance", [
253+
impersonateOwner3.address,
254+
desiredBalance,
255+
]);
256+
257+
const transactionHash = await safeContract
258+
.connect(impersonateOwner1)
259+
.getTransactionHash(
260+
txData.to,
261+
txData.value,
262+
txData.data,
263+
txData.operation,
264+
txData.safeTxGas,
265+
txData.baseGas,
266+
txData.gasPrice,
267+
txData.gasToken,
268+
txData.refundReceiver,
269+
txData.nonce
270+
);
271+
console.info("Transaction hash:", transactionHash);
272+
273+
await safeContract.connect(impersonateOwner1).approveHash(transactionHash);
274+
await safeContract.connect(impersonateOwner2).approveHash(transactionHash);
275+
await safeContract.connect(impersonateOwner3).approveHash(transactionHash);
276+
const signature = generateConcatenatedSignatures([
277+
impersonateOwner1.address,
278+
impersonateOwner2.address,
279+
impersonateOwner3.address,
280+
]);
281+
console.info("Signature:", signature);
282+
283+
txData.signatures = signature;
284+
285+
const result = await safeContract.execTransaction(
286+
txData.to,
287+
txData.value,
288+
txData.data,
289+
txData.operation,
290+
txData.safeTxGas,
291+
txData.baseGas,
292+
txData.gasPrice,
293+
txData.gasToken,
294+
txData.refundReceiver,
295+
txData.signatures
296+
);
297+
298+
return Boolean(result);
299+
}

scripts/deployment-utils/change-multisig-owner.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@ import multisigOwners from "../../multisig-owners.json";
44
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
55

66
/**
7-
* Changes the multisig owner of the `LiquidityBridgeContract` deployed on the current network to the safe wallet
7+
* Changes the multisig.ts owner of the `LiquidityBridgeContract` deployed on the current network to the safe wallet
88
* provided.
99
*
1010
* This function validates the provided `newOwner` address, ensures ownership configuration matches
11-
* expectations, and performs the transfer of ownership to the new multisig address. Additionally,
11+
* expectations, and performs the transfer of ownership to the new multisig.ts address. Additionally,
1212
* it updates both the contract and proxy admin ownership.
1313
*
1414
* @async
15-
* @param {string} newOwner - The address of the new multisig owner (Safe contract).
15+
* @param {string} newOwner - The address of the new multisig.ts owner (Safe contract).
1616
* @param {string} network - The network where the script will run, by default will be the environment network.
1717
* @param {HardhatEthersSigner} signer - Optional signer for test.
1818
* @throws {Error} If the proxy contract is not deployed on the current network.
19-
* @throws {Error} If the provided `newOwner` address is not a valid multisig Safe contract.
19+
* @throws {Error} If the provided `newOwner` address is not a valid multisig.ts Safe contract.
2020
* @throws {Error} If the configuration of owners on the Safe does not match the expected configuration.
2121
* @returns {Promise<void>} Resolves when the ownership transfer process is complete.
2222
*
2323
* @example
24-
* // Change the multisig owner of the contract
24+
* // Change the multisig.ts owner of the contract
2525
* const newMultisigAddress = "0xNewSafeAddress";
2626
* await changeMultisigOwner(newMultisigAddress);
2727
*/

0 commit comments

Comments
 (0)