-
Notifications
You must be signed in to change notification settings - Fork 953
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update
SafeMigration
tests for zkSync (#833)
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
Showing
3 changed files
with
149 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; | ||
} |