Files
linea-monorepo/contracts/test/tools/RecoverFunds.ts
The Dark Jester 4af87b5fe6 [Chore] - First pass contracts and makefile cleanup (#276)
* first pass contracts and makefile cleanup

* remove unused 4844 test files

* Remove Goerli references

* lint fixing

* lint fixing

* remove more Goerli references

* correct folder paths for L2 service tests

* fix pathing and hardhat version

* reinstate deleted file

* correct file pathing
2024-11-06 02:21:14 -08:00

151 lines
5.9 KiB
TypeScript

import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { ethers } from "hardhat";
import { RecoverFunds, TestExternalCalls } from "../../typechain-types";
import {
ADDRESS_ZERO,
DEFAULT_ADMIN_ROLE,
EMPTY_CALLDATA,
FUNCTION_EXECUTOR_ROLE,
INITIALIZED_ALREADY_MESSAGE,
INITIAL_WITHDRAW_LIMIT,
} from "../common/constants";
import { deployUpgradableFromFactory } from "../common/deployment";
import { buildAccessErrorMessage, expectRevertWithCustomError, expectRevertWithReason } from "../common/helpers";
describe("RecoverFunds contract", () => {
let recoverFunds: RecoverFunds;
let recoverFundsAddress: string;
let testContract: TestExternalCalls;
let testContractAddress: string;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let admin: SignerWithAddress;
let securityCouncil: SignerWithAddress;
let executor: SignerWithAddress;
let nonAuthorizedAccount: SignerWithAddress;
async function deployRecoverFundsFixture() {
const recoverFunds = (await deployUpgradableFromFactory(
"RecoverFunds",
[securityCouncil.address, executor.address],
{
initializer: "initialize(address,address)",
unsafeAllow: ["constructor"],
},
)) as unknown as RecoverFunds;
recoverFundsAddress = await recoverFunds.getAddress();
return recoverFunds;
}
async function deployTestContractFixture() {
const testExternalCallsFactory = await ethers.getContractFactory("TestExternalCalls");
const testExternalCalls = await testExternalCallsFactory.deploy();
await testExternalCalls.waitForDeployment();
testContractAddress = await testExternalCalls.getAddress();
return testExternalCalls;
}
before(async () => {
[admin, securityCouncil, nonAuthorizedAccount, executor] = await ethers.getSigners();
});
beforeEach(async () => {
recoverFunds = await loadFixture(deployRecoverFundsFixture);
testContract = await loadFixture(deployTestContractFixture);
});
describe("Fallback/Receive tests", () => {
const sendEthToContract = async (data: string) => {
return admin.sendTransaction({ to: await recoverFunds.getAddress(), value: INITIAL_WITHDRAW_LIMIT, data });
};
it("Should fail to send eth to the recoverFunds contract through the fallback", async () => {
await expect(sendEthToContract(EMPTY_CALLDATA)).to.be.reverted;
});
it("Should fail to send eth to the recoverFunds contract through the receive function", async () => {
await expect(sendEthToContract("0x1234")).to.be.reverted;
});
});
describe("Initialisation", () => {
it("Should revert if security council address is zero address", async () => {
const deployCall = deployUpgradableFromFactory("RecoverFunds", [ADDRESS_ZERO, executor.address], {
initializer: "initialize(address,address)",
unsafeAllow: ["constructor"],
});
await expectRevertWithCustomError(recoverFunds, deployCall, "ZeroAddressNotAllowed");
});
it("Should revert if security council address is zero address", async () => {
const deployCall = deployUpgradableFromFactory("RecoverFunds", [securityCouncil.address, ADDRESS_ZERO], {
initializer: "initialize(address,address)",
unsafeAllow: ["constructor"],
});
await expectRevertWithCustomError(recoverFunds, deployCall, "ZeroAddressNotAllowed");
});
it("Should grant default admin role to security council", async () => {
expect(await recoverFunds.hasRole(DEFAULT_ADMIN_ROLE, securityCouncil.address)).true;
});
it("Should grant function executor admin role to executor address", async () => {
expect(await recoverFunds.hasRole(FUNCTION_EXECUTOR_ROLE, executor.address)).true;
});
it("Should revert if the initialize function is called a second time", async () => {
recoverFunds = await loadFixture(deployRecoverFundsFixture);
const initializeCall = recoverFunds.initialize(securityCouncil.address, executor.address);
await expectRevertWithReason(initializeCall, INITIALIZED_ALREADY_MESSAGE);
});
});
describe("Executing functions", () => {
it("Should revert if not security council", async () => {
const functionCall = recoverFunds
.connect(nonAuthorizedAccount)
.executeExternalCall(nonAuthorizedAccount, EMPTY_CALLDATA, 1000n);
expectRevertWithReason(functionCall, buildAccessErrorMessage(nonAuthorizedAccount, FUNCTION_EXECUTOR_ROLE));
});
it("Should send half of the ETH sent", async () => {
await recoverFunds
.connect(executor)
.executeExternalCall(testContractAddress, EMPTY_CALLDATA, 1000n, { value: 2000n });
expect(await ethers.provider.getBalance(recoverFundsAddress)).equal(1000n);
expect(await ethers.provider.getBalance(testContractAddress)).equal(1000n);
});
it("Should call and set the value on the test contract", async () => {
const calldata = testContract.interface.encodeFunctionData("setValue", [1000n]);
await recoverFunds.connect(executor).executeExternalCall(testContractAddress, calldata, 0n);
expect(await testContract.testValue()).equal(1000n);
});
it("Should revert with the destinations custom error", async () => {
const calldata = testContract.interface.encodeFunctionData("revertWithError");
const functionCall = recoverFunds.connect(executor).executeExternalCall(testContractAddress, calldata, 0n);
await expectRevertWithCustomError(testContract, functionCall, "TestError");
});
it("Should revert with the recovery contract custom error", async () => {
const calldata = "0xdeadbeef";
const functionCall = recoverFunds.connect(executor).executeExternalCall(testContractAddress, calldata, 0n);
await expectRevertWithCustomError(recoverFunds, functionCall, "ExternalCallFailed", [testContractAddress]);
});
});
});