Skip to content

Commit

Permalink
Update SafeMigration tests for zkSync (#833)
Browse files Browse the repository at this point in the history
This PR:
- Partially solves
#767 (test
updates for `SafeToL2Upgrade` are still pending)
- It is based on version 1.5.0 because 1.4.1 cannot be compiled at the
moment because we used `.send` in there, and hardhat zksync compiler
plugin needs to be updated to support suppressing errors. I will
cherry-pick it later.
- I updated the `deployContract` function name and return type to be
more self-explanatory
- The main changes were around adding zksync compatible bytecode and
also using the ContractFactory from the "zksync-ethers" package because
in ZkSync you need to interact with a system contract to deploy
contracts and not just send a transaction with the bytecode and
`to` address omitted.

One bug found: matter-labs/hardhat-zksync#1420
  • Loading branch information
mmv08 committed Sep 26, 2024
1 parent 3375920 commit 855b6fd
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 2 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
},
"devDependencies": {
"@matterlabs/hardhat-zksync-deploy": "^1.5.0",
"@matterlabs/hardhat-zksync-ethers": "^1.2.0-beta.3",
"@matterlabs/hardhat-zksync-ethers": "^1.2.1",
"@matterlabs/hardhat-zksync-node": "^1.1.1",
"@matterlabs/hardhat-zksync-solc": "^1.2.4",
"@matterlabs/hardhat-zksync-verify": "^1.6.0",
Expand Down Expand Up @@ -91,6 +91,6 @@
"ts-node": "^9.1.1",
"typescript": "^4.2.4",
"yargs": "^16.1.1",
"zksync-ethers": "6.11.2"
"zksync-ethers": "6.12.1"
}
}
66 changes: 66 additions & 0 deletions test/libraries/Migration.120.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { expect } from "chai";
import { ethers, deployments, waffle } from "hardhat";
import "@nomicfoundation/hardhat-ethers";
import { AddressZero } from "@ethersproject/constants";
import { getSafeWithOwners, getSafeSingleton, migrationContract } from "../utils/setup";
import deploymentData from "../json/safeDeployment.json";
import { executeContractCallWithSigners } from "../../src/utils/execution";

describe("Migration", async () => {
const MigratedInterface = new ethers.utils.Interface([
"function domainSeparator() view returns(bytes32)",
"function masterCopy() view returns(address)",
]);

const [user1, user2] = waffle.provider.getWallets();

const setupTests = deployments.createFixture(async ({ deployments }) => {
await deployments.fixture();
const singleton120 = (await (await user1.sendTransaction({ data: deploymentData.safe120 })).wait()).contractAddress;
const migration = await (await migrationContract()).deploy(singleton120);
return {
singleton: await getSafeSingleton(),
singleton120,
safe: await getSafeWithOwners([user1.address]),
migration,
};
});
describe("constructor", async () => {
it("can not use 0 Address", async () => {
await setupTests();
const tx = (await migrationContract()).getDeployTransaction(AddressZero);
await expect(user1.sendTransaction(tx)).to.be.revertedWith("Invalid singleton address provided");
});
});

describe("migrate", async () => {
it("can only be called from Safe itself", async () => {
const { migration } = await setupTests();
await expect(migration.migrate()).to.be.revertedWith("Migration should only be called via delegatecall");
});

it("can migrate", async () => {
const { safe, migration, singleton120 } = await setupTests();
// The emit matcher checks the address, which is the Safe as delegatecall is used
const migrationSafe = migration.attach(safe.address);

await expect(await ethers.provider.getStorageAt(safe.address, "0x" + "".padEnd(62, "0") + "06")).to.be.eq(
"0x" + "".padEnd(64, "0"),
);

await expect(executeContractCallWithSigners(safe, migration, "migrate", [], [user1], true))
.to.emit(migrationSafe, "ChangedMasterCopy")
.withArgs(singleton120);

const expectedDomainSeparator = ethers.utils._TypedDataEncoder.hashDomain({ verifyingContract: safe.address });

await expect(await ethers.provider.getStorageAt(safe.address, "0x06")).to.be.eq(expectedDomainSeparator);

const respData = await user1.call({ to: safe.address, data: MigratedInterface.encodeFunctionData("domainSeparator") });
await expect(MigratedInterface.decodeFunctionResult("domainSeparator", respData)[0]).to.be.eq(expectedDomainSeparator);

const masterCopyResp = await user1.call({ to: safe.address, data: MigratedInterface.encodeFunctionData("masterCopy") });
await expect(MigratedInterface.decodeFunctionResult("masterCopy", masterCopyResp)[0]).to.be.eq(singleton120);
});
});
});
81 changes: 81 additions & 0 deletions test/utils/zkSync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { exec } from "child_process";
import { JsonFragment } from "ethers";
import { TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD } from "hardhat/builtin-tasks/task-names";
import { Compiler } from "hardhat/internal/solidity/compiler/downloader";
import { HardhatRuntimeEnvironment } from "hardhat/types";

let _solcBuild: Compiler;
async function getSolcBuild(hre: HardhatRuntimeEnvironment) {
if (!_solcBuild) {
_solcBuild = await hre.run(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, {
quiet: false,
solcVersion: hre.config.solidity.compilers[0].version,
compilationJob: {
getSolcConfig: () => {
return hre.config.solidity.compilers[0];
},
},
});
}

return _solcBuild;
}

export async function zkCompile(
hre: HardhatRuntimeEnvironment,
source: string,
): Promise<{ bytecode: string; abi: ReadonlyArray<JsonFragment> }> {
const zkSolcCompilerPath = hre.config.zksolc.settings.compilerPath;
const solcBuild = await getSolcBuild(hre);

const input = JSON.stringify({
language: "Solidity",
settings: {
optimizer: {
runs: 200,
enabled: false,
},
outputSelection: {
"*": {
"*": ["abi"],
},
},
},
sources: {
"tmp.sol": {
content: source,
},
},
});

const zkSolcData: string = await new Promise((resolve, reject) => {
const process = exec(
`${zkSolcCompilerPath} --standard-json --solc ${solcBuild.compilerPath}`,
{
maxBuffer: 1024 * 1024 * 500,
},
(err, stdout) => {
if (err !== null) {
return reject(err);
}
resolve(stdout);
},
);

process.stdin?.write(input);
process.stdin?.end();
});

const output = JSON.parse(zkSolcData);
if (!output["contracts"]) {
console.log(output);
throw Error("Could not compile contract");
}

const fileOutput = output["contracts"]["tmp.sol"];
const contractOutput = fileOutput[Object.keys(fileOutput)[0]];
const abi = contractOutput["abi"];
const bytecode = "0x" + contractOutput["evm"]["bytecode"]["object"];

return { bytecode, abi };
}

0 comments on commit 855b6fd

Please sign in to comment.