Skip to content

Commit

Permalink
Update hardhat dependencies. Vendor factory code, and make it compati…
Browse files Browse the repository at this point in the history
…ble with eters v6
  • Loading branch information
cristovaoth committed Jul 16, 2024
1 parent a385524 commit 199dd7d
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 5,232 deletions.
17 changes: 17 additions & 0 deletions contracts/test/IModuleProxyFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: LGPL-3.0-only
// !! THIS FILE WAS AUTOGENERATED BY abi-to-sol v0.8.0. !!
pragma solidity ^0.8.4;

interface IModuleProxyFactory {
error FailedInitialization();
error TakenAddress(address address_);
error TargetHasNoCode(address target);
error ZeroAddress(address target);
event ModuleProxyCreation(address indexed proxy, address indexed masterCopy);

function deployModule(
address masterCopy,
bytes memory initializer,
uint256 saltNonce
) external returns (address proxy);
}
43 changes: 14 additions & 29 deletions deploy/01_mastercopy_module.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,29 @@
import "hardhat-deploy"
import { ZeroHash } from "ethers"
import { DeployFunction } from "hardhat-deploy/types"
import { HardhatRuntimeEnvironment } from "hardhat/types"
import { computeTargetAddress, deployMastercopy } from "@gnosis.pm/zodiac"
import { createFactory, deployViaFactory } from "../factories/eip2470"

import MODULE_CONTRACT_ARTIFACT from "../artifacts/contracts/MyModule.sol/MyModule.json"

const FirstAddress = "0x0000000000000000000000000000000000000001"
const Salt = "0x0000000000000000000000000000000000000000000000000000000000000000"

const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { getNamedAccounts, ethers } = hre
const { deployer_address } = await getNamedAccounts()
const deployer = await ethers.getSigner(deployer_address)

let address = await deployMastercopy(
deployer,
MODULE_CONTRACT_ARTIFACT,
[
FirstAddress, // owner
FirstAddress, // button
],
Salt,
)

if (address === ethers.ZeroAddress) {
// the mastercopy was already deployed
const target = await computeTargetAddress(
deployer,
MODULE_CONTRACT_ARTIFACT,
[
FirstAddress, // owner
FirstAddress, // button
],
Salt,
)
address = target.address
}

// const deployer = await ethers.getSigner()

const [deployer] = await hre.ethers.getSigners()
await createFactory(deployer)

const MyModule = await ethers.getContractFactory("MyModule")
const tx = await MyModule.getDeployTransaction(FirstAddress, FirstAddress)

const mastercopy = await deployViaFactory({ bytecode: tx.data, salt: ZeroHash }, deployer)

hre.deployments.save("MyModuleMastercopy", {
abi: MODULE_CONTRACT_ARTIFACT.abi,
address,
address: mastercopy,
})
}

Expand Down
18 changes: 6 additions & 12 deletions deploy/02_test_dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
import "hardhat-deploy"
import { DeployFunction } from "hardhat-deploy/types"
import { HardhatRuntimeEnvironment } from "hardhat/types"
import { deployModuleFactory } from "@gnosis.pm/zodiac"
import { ethers } from "ethers"

const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
console.log("Deploying 'external' dependencies (Button and Avatar)")
const { deployments, getNamedAccounts, ethers } = hre
const { deploy } = deployments
const { deployer_address } = await getNamedAccounts()
const deployer = await ethers.provider.getSigner(deployer_address)

// Deploys the ModuleFactory (and the Singleton factory) if it is not already deployed
await deployModuleFactory(deployer)
// const { deployer_address } = await getNamedAccounts()
const [deployer] = await ethers.getSigners()

const testAvatarDeployment = await deploy("TestAvatar", {
from: deployer_address,
from: await deployer.getAddress(),
})
console.log("TestAvatar deployed to:", testAvatarDeployment.address)

const buttonDeployment = await deploy("Button", {
from: deployer_address,
from: await deployer.getAddress(),
})
console.log("Button deployed to:", buttonDeployment.address)

// Make the TestAvatar the owner of the button
const dependenciesDeployerSigner = await ethers.getSigner(deployer_address)
const buttonContract = await ethers.getContractAt("Button", buttonDeployment.address, dependenciesDeployerSigner)
// const dependenciesDeployerSigner = await ethers.getSigner(deployer_address)
const buttonContract = await ethers.getContractAt("Button", buttonDeployment.address, deployer)
const currentOwner = await buttonContract.owner()
if (currentOwner !== testAvatarDeployment.address) {
const tx = await buttonContract.transferOwnership(testAvatarDeployment.address)
Expand Down
41 changes: 22 additions & 19 deletions deploy/03_proxy_module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import "hardhat-deploy"
import { ZeroHash } from "ethers"
import { DeployFunction } from "hardhat-deploy/types"
import { HardhatRuntimeEnvironment } from "hardhat/types"
import { deployAndSetUpCustomModule, ContractAddresses, KnownContracts, SupportedNetworks } from "@gnosis.pm/zodiac"

import { createFactory, deployModAsProxy } from "../factories/moduleProxyFactory"

import MODULE_CONTRACT_ARTIFACT from "../artifacts/contracts/MyModule.sol/MyModule.json"

const deploy: DeployFunction = async function ({
Expand All @@ -11,38 +13,39 @@ const deploy: DeployFunction = async function ({
getChainId,
}: HardhatRuntimeEnvironment) {
console.log("Deploying MyModule Proxy")
const { deployer } = await getNamedAccounts()
const deployerSigner = await ethers.getSigner(deployer)
// const { deployer } = await getNamedAccounts()
// const deployerSigner = await ethers.getSigner(deployer)
const [deployer] = await ethers.getSigners()

const buttonDeployment = await deployments.get("Button")
const testAvatarDeployment = await deployments.get("TestAvatar")

const myModuleMastercopyDeployment = await deployments.get("MyModuleMastercopy")

const chainId = await getChainId()
const network: SupportedNetworks = Number(chainId)
/// const chainId = await getChainId()
// const network: SupportedNetworks = Number(chainId)

if ((await ethers.provider.getCode(ContractAddresses[network][KnownContracts.FACTORY])) === "0x") {
// the Module Factory should already be deployed to all supported chains
// if you are deploying to a chain where its not deployed yet (most likely locale test chains), run deployModuleFactory from the zodiac package
throw Error("The Module Factory is not deployed on this network. Please deploy it first.")
}
// if ((await ethers.provider.getCode(ContractAddresses[network][KnownContracts.FACTORY])) === "0x") {
// // the Module Factory should already be deployed to all supported chains
// // if you are deploying to a chain where its not deployed yet (most likely locale test chains), run deployModuleFactory from the zodiac package
// throw Error("The Module Factory is not deployed on this network. Please deploy it first.")
// }

console.log("buttonDeployment.address:", buttonDeployment.address)

const { transaction } = deployAndSetUpCustomModule(
// Deploys the ModuleFactory (and the Singleton factory) if it is not already deployed
const factory = await createFactory(deployer)
const { transaction } = await deployModAsProxy(
factory,
myModuleMastercopyDeployment.address,
myModuleMastercopyDeployment.abi,
{
values: [testAvatarDeployment.address, buttonDeployment.address],
types: ["address", "address"],
},
ethers.provider,
Number(chainId),
Date.now().toString(),
ZeroHash,
)
const deploymentTransaction = await deployerSigner.sendTransaction(transaction)
const receipt = await deploymentTransaction.wait()
const deploymentTransaction = await deployer.sendTransaction(transaction)
const receipt = (await deploymentTransaction.wait())!
const myModuleProxyAddress = receipt.logs[1].address
console.log("MyModule minimal proxy deployed to:", myModuleProxyAddress)

Expand All @@ -52,7 +55,7 @@ const deploy: DeployFunction = async function ({
})

// Enable MyModule as a module on the safe to give it access to the safe's execTransactionFromModule() function
const testAvatarContract = await ethers.getContractAt("TestAvatar", testAvatarDeployment.address, deployerSigner)
const testAvatarContract = await ethers.getContractAt("TestAvatar", testAvatarDeployment.address, deployer)
const currentActiveModule = await testAvatarContract.module()
if (currentActiveModule !== myModuleProxyAddress) {
const tx = await testAvatarContract.enableModule(myModuleProxyAddress)
Expand Down
56 changes: 56 additions & 0 deletions factories/eip2470.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"

import { Contract, Signer, ZeroHash, getCreate2Address, keccak256, parseEther } from "ethers"

export async function deployViaFactory(
{ bytecode, salt = ZeroHash }: { bytecode: string; salt?: string },
deployer: Signer,
displayName?: string,
): Promise<string> {
const provider = deployer.provider!

const factory = new Contract(
factoryInfo.address,
["function deploy(bytes memory _initCode, bytes32 _salt) public returns (address payable createdContract)"],
deployer,
)

const computedAddress = getCreate2Address(factoryInfo.address, salt, keccak256(bytecode))

if ((await provider.getCode(computedAddress)) == "0x") {
const receipt = await (await factory.deploy(bytecode, salt, { gasLimit: 10000000 })).wait()

if (receipt?.status != 1) {
throw new Error(`Couldn't deploy ${displayName || "contract"} eip-2470 factory`)
}
}

return computedAddress
}

const factoryInfo = {
address: "0xce0042b868300000d44a59004da54a005ffdcf9f",
deployer: "0xBb6e024b9cFFACB947A71991E386681B1Cd1477D",
transaction:
"0xf9016c8085174876e8008303c4d88080b90154608060405234801561001057600080fd5b50610134806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80634af63f0214602d575b600080fd5b60cf60048036036040811015604157600080fd5b810190602081018135640100000000811115605b57600080fd5b820183602082011115606c57600080fd5b80359060200191846001830284011164010000000083111715608d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550509135925060eb915050565b604080516001600160a01b039092168252519081900360200190f35b6000818351602085016000f5939250505056fea26469706673582212206b44f8a82cb6b156bfcc3dc6aadd6df4eefd204bc928a4397fd15dacf6d5320564736f6c634300060200331b83247000822470",
}

/**
* If it is not deployed on the network, deploys the singleton factory contract
*
* https://eips.ethereum.org/EIPS/eip-2470
*/
export async function createFactory(signer: SignerWithAddress) {
const { address, deployer } = factoryInfo

// fund the singleton factory deployer account
await signer.sendTransaction({
to: deployer,
value: parseEther("0.0247"),
})

// deploy the singleton factory
await signer.provider.broadcastTransaction(factoryInfo.transaction)

return address
}
58 changes: 58 additions & 0 deletions factories/moduleProxyFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { AbiCoder, getCreate2Address, keccak256, solidityPackedKeccak256 } from "ethers"
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"

import { deployViaFactory } from "./eip2470"

import { IModuleProxyFactory__factory, Module__factory } from "../typechain-types"

export function deployModAsProxy(
factory: string,
mastercopy: string,
setupArgs: {
types: Array<string>
values: Array<any>
},
saltNonce: string,
) {
function predictProxyAddress(initData: string): string {
const mastercopyAddressFormatted = mastercopy.toLowerCase().replace(/^0x/, "")
const byteCode =
"0x602d8060093d393df3363d3d373d3d3d363d73" + mastercopyAddressFormatted + "5af43d82803e903d91602b57fd5bf3"

const salt = solidityPackedKeccak256(
["bytes32", "uint256"],
[solidityPackedKeccak256(["bytes"], [initData]), saltNonce],
)

return getCreate2Address(factory, salt, keccak256(byteCode))
}

const abiCoder = new AbiCoder()

const encodedInitParams = abiCoder.encode(setupArgs.types, setupArgs.values)
const moduleSetupData = Module__factory.createInterface().encodeFunctionData("setUp", [encodedInitParams])

const deployData = IModuleProxyFactory__factory.createInterface().encodeFunctionData("deployModule", [
mastercopy,
moduleSetupData,
saltNonce,
])
const transaction = {
data: deployData,
to: factory,
value: 0,
}
return {
transaction,
expectedModuleAddress: predictProxyAddress(moduleSetupData),
}
}

export async function createFactory(signer: SignerWithAddress) {
const address = await deployViaFactory({ bytecode: creationBytecode, salt }, signer)
return address
}

const creationBytecode =
"0x608060405234801561001057600080fd5b506107fe806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f1ab873c14610030575b600080fd5b61004a600480360381019061004591906103b6565b610060565b6040516100579190610564565b60405180910390f35b600061009b848480519060200120846040516020016100809291906104f0565b604051602081830303815290604052805190602001206101a3565b905060008173ffffffffffffffffffffffffffffffffffffffff16846040516100c4919061051c565b6000604051808303816000865af19150503d8060008114610101576040519150601f19603f3d011682016040523d82523d6000602084013e610106565b606091505b5050905080610141576040517f7dabd39900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f2150ada912bf189ed721c44211199e270903fc88008c2a1e1e889ef30fe67c5f60405160405180910390a3509392505050565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561021657826040517fc80b883400000000000000000000000000000000000000000000000000000000815260040161020d9190610564565b60405180910390fd5b60008373ffffffffffffffffffffffffffffffffffffffff163b141561027357826040517ffff2336100000000000000000000000000000000000000000000000000000000815260040161026a9190610564565b60405180910390fd5b6000836040516020016102869190610533565b6040516020818303038152906040529050828151602083016000f59150600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561031557816040517fdc7a7a2400000000000000000000000000000000000000000000000000000000815260040161030c9190610564565b60405180910390fd5b5092915050565b600061032f61032a846105a4565b61057f565b90508281526020810184848401111561034b5761034a61071b565b5b61035684828561063c565b509392505050565b60008135905061036d8161079a565b92915050565b600082601f83011261038857610387610716565b5b813561039884826020860161031c565b91505092915050565b6000813590506103b0816107b1565b92915050565b6000806000606084860312156103cf576103ce610725565b5b60006103dd8682870161035e565b935050602084013567ffffffffffffffff8111156103fe576103fd610720565b5b61040a86828701610373565b925050604061041b868287016103a1565b9150509250925092565b61042e816105f6565b82525050565b610445610440826105f6565b6106af565b82525050565b61045c61045782610608565b6106c1565b82525050565b600061046d826105d5565b61047781856105e0565b935061048781856020860161064b565b80840191505092915050565b60006104a0600f836105eb565b91506104ab82610748565b600f82019050919050565b60006104c36013836105eb565b91506104ce82610771565b601382019050919050565b6104ea6104e582610632565b6106dd565b82525050565b60006104fc828561044b565b60208201915061050c82846104d9565b6020820191508190509392505050565b60006105288284610462565b915081905092915050565b600061053e826104b6565b915061054a8284610434565b60148201915061055982610493565b915081905092915050565b60006020820190506105796000830184610425565b92915050565b600061058961059a565b9050610595828261067e565b919050565b6000604051905090565b600067ffffffffffffffff8211156105bf576105be6106e7565b5b6105c88261072a565b9050602081019050919050565b600081519050919050565b600081905092915050565b600081905092915050565b600061060182610612565b9050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b82818337600083830152505050565b60005b8381101561066957808201518184015260208101905061064e565b83811115610678576000848401525b50505050565b6106878261072a565b810181811067ffffffffffffffff821117156106a6576106a56106e7565b5b80604052505050565b60006106ba826106cb565b9050919050565b6000819050919050565b60006106d68261073b565b9050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b60008160601b9050919050565b7f5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000600082015250565b7f602d8060093d393df3363d3d373d3d3d363d7300000000000000000000000000600082015250565b6107a3816105f6565b81146107ae57600080fd5b50565b6107ba81610632565b81146107c557600080fd5b5056fea264697066735822122068cfe87f5242e3087c0b46218f6f8e6b1a5960ed6b562cb03d27ec764881916064736f6c63430008060033"
const salt = "0xb8db55294bb8fea809aee38a26aad7ce1ab40d019486aa51af3a3de7e7434530"
8 changes: 3 additions & 5 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import * as dotenv from "dotenv"
import { HardhatUserConfig } from "hardhat/config"
import "@nomicfoundation/hardhat-verify"
import "@nomiclabs/hardhat-ethers"
import "@nomiclabs/hardhat-waffle"
import "@typechain/hardhat"
import { HardhatUserConfig } from "hardhat/types"

import "@nomicfoundation/hardhat-toolbox"
import "hardhat-gas-reporter"
import "solidity-coverage"
import "hardhat-deploy"
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@
},
"homepage": "https://github.com/gnosis/zodiac-mod-starter-kit",
"devDependencies": {
"@gnosis.pm/zodiac": "4.0.3",
"@gnosis.pm/safe-contracts": "^1.3.0",
"@gnosis.pm/zodiac": "4.0.3",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.7",
"@nomicfoundation/hardhat-ethers": "3.0.6",
"@nomicfoundation/hardhat-network-helpers": "^1.0.7",
"@nomicfoundation/hardhat-toolbox": "4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@openzeppelin/contracts": "^5.0.0",
"@openzeppelin/contracts-upgradeable": "^5.0.0",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.3.3",
Expand All @@ -47,7 +49,6 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-promise": "^6.0.0",
"ethereum-waffle": "^3.4.0",
"ethers": "^6.9.2",
"hardhat": "^2.14.0",
"hardhat-deploy": "^0.11.28",
Expand Down
3 changes: 2 additions & 1 deletion test/myModule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe("MyModule", function () {
})
it("Should NOT be possible to 'press the button' directly on the Button contract", async function () {
const { buttonContract } = await setup()
await expect(buttonContract.pushButton()).to.revertedWith("Ownable: caller is not the owner")

await expect(buttonContract.pushButton()).to.revertedWithCustomError(buttonContract, "OwnableUnauthorizedAccount")
})
})
Loading

0 comments on commit 199dd7d

Please sign in to comment.