Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functionality for deploying/verifying/checking contracts issued using factories #132

Merged
merged 13 commits into from
Dec 13, 2024
4 changes: 4 additions & 0 deletions deployment-txs/gnosis.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
"0x5d98909Aa2b3d964907B3735597F47E06b04D8b4": "0xa814573ffdd86b757737f901806202098a899736c359c51e38c258301f59c4a2",
"0xE241C6e48CA045C7f631600a0f1403b2bFea05ad": "0xbddecd204c0ff30e450edee2fd8c90b57632ba63525f7c1771a5e0017308b15e",
"0xAc27df81663d139072E615855eF9aB0Af3FBD281": "0x7cd44000bec33d99663fdbcb718ce497d6aaa3ecea1131fbab3a865db9d37725",
"0xbA1333333333a1BA1108E8412f11850A5C319bA9": "0x754f9db9925c52591e5d9d6233979fefb19a60aa3768f5b54daf8ddadb08f23a",
"0x0E8B07657D719B86e06bF0806D6729e3D528C9A9": "0x754f9db9925c52591e5d9d6233979fefb19a60aa3768f5b54daf8ddadb08f23a",
"0x35fFB749B273bEb20F40f35EdeB805012C539864": "0x754f9db9925c52591e5d9d6233979fefb19a60aa3768f5b54daf8ddadb08f23a",
"0xa731C23D7c95436Baaae9D52782f966E1ed07cc8": "0x754f9db9925c52591e5d9d6233979fefb19a60aa3768f5b54daf8ddadb08f23a",
"0xEB1eeaBF0126d813589C3D2CfeFFE410D9aE3863": "0x04965cda30a501e074b983c40c5ff83d70401597da929e937e39d60022f4f0d9",
"0xd43c36038250c66D46854e536Bf959fdDE5294c3": "0x81103c45f5122e7c48e538cdc4defa0831221bf39cf5efb1d463ff8d77c033eb",
"0x22625eEDd92c81a219A83e1dc48f88d54786B017": "0xbd157de3b2e45017b96a93474051c6f390f4c5d46a178a8a2e96c7b68ca85873",
Expand Down
4 changes: 4 additions & 0 deletions deployment-txs/mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@
"0x18CC3C68A5e64b40c846Aa6E45312cbcBb94f71b": "0x1f11890a40a96fb3be92f2ab5ae160a70252e671ade0038242f21a6992f0ceab",
"0xE241C6e48CA045C7f631600a0f1403b2bFea05ad": "0x8ea045aa2c50cc3fe24f6e9122cac4d1825b9367598fce148fce736b2540a1ab",
"0xAc27df81663d139072E615855eF9aB0Af3FBD281": "0xc8ae141334ca62994b5108c39d6d926596272a639edbb06b2d353b8890072db6",
"0xbA1333333333a1BA1108E8412f11850A5C319bA9": "0x49a4986a672bcc20eecf99a3603f0099b19ab663eebe5dd5fe04808c380147b4",
"0x0E8B07657D719B86e06bF0806D6729e3D528C9A9": "0x49a4986a672bcc20eecf99a3603f0099b19ab663eebe5dd5fe04808c380147b4",
"0x35fFB749B273bEb20F40f35EdeB805012C539864": "0x49a4986a672bcc20eecf99a3603f0099b19ab663eebe5dd5fe04808c380147b4",
"0xa731C23D7c95436Baaae9D52782f966E1ed07cc8": "0x49a4986a672bcc20eecf99a3603f0099b19ab663eebe5dd5fe04808c380147b4",
"0x201efd508c8DfE9DE1a13c2452863A78CB2a86Cc": "0x1e14baaeb10fc3a6b689e77ec34e8c5e8e21853f6e23257459dd99c35b6ff06b",
"0x527d0E14acc53FB040DeBeae1cAb973D23FB3568": "0x44d82009c125f39bd2bbd1f723e7284e4226d5d752a66b18536e5d92c37b0c82",
"0xB9d01CA61b9C181dA1051bFDd28e1097e920AB14": "0x2794463090a850910415b88df0f756e01e0838c8782e83a89389992c17469513",
Expand Down
4 changes: 4 additions & 0 deletions deployment-txs/sepolia.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@
"0x05503B3aDE04aCA81c8D6F88eCB73Ba156982D2B": "0x53aa3587002469b758e2bb87135d9599fd06e7be944fe61c7f82045c45328566",
"0x7b0d669142FEDe0eB9065E3B9140a7c90a934a27": "0x254b0ec866c204a1a1eca3d1a85879ddc4b24191141215d7ecff085cf478f70c",
"0xAc27df81663d139072E615855eF9aB0Af3FBD281": "0x35299591bbd0daf8013573a132cec517f16a03b2cdcaa6370c3bcc37459924b9",
"0xbA1333333333a1BA1108E8412f11850A5C319bA9": "0xe9ab355e0f5987453c48b3fe64f7c63ae4ba6dc5a85d1e43fb3a066dffe16a81",
"0x0E8B07657D719B86e06bF0806D6729e3D528C9A9": "0xe9ab355e0f5987453c48b3fe64f7c63ae4ba6dc5a85d1e43fb3a066dffe16a81",
"0x35fFB749B273bEb20F40f35EdeB805012C539864": "0xe9ab355e0f5987453c48b3fe64f7c63ae4ba6dc5a85d1e43fb3a066dffe16a81",
"0xa731C23D7c95436Baaae9D52782f966E1ed07cc8": "0xe9ab355e0f5987453c48b3fe64f7c63ae4ba6dc5a85d1e43fb3a066dffe16a81",
"0x7532d5a3bE916e4a4D900240F49F0BABd4FD855C": "0xe42c9cdc05ab3de2b8698ed32e56dce0f85c1017099aa965784d8023fb29d012",
"0xFc253B433B7225AC7736EAbDF4115F7252aECb91": "0x45579023de61bae57ba62a72a50783509afa7c47f466150b3d5f134d220624b6",
"0xd67F485C07D258B3e93835a3799d862ffcB55923": "0x48d803b01baf630543481ca6eefca5dc269d8670cf44afd08dcba3792a48710f",
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,8 @@
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.2",
"typescript": "^4.3.2"
},
"dependencies": {
"@ethereumjs/evm": "^3.1.1"
}
}
160 changes: 159 additions & 1 deletion src/task.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import fs from 'fs';
import path, { extname } from 'path';
import { BuildInfo, CompilerOutputContract } from 'hardhat/types';
import { Contract } from 'ethers';
import { Contract, ethers } from 'ethers';
import { hexToBytes, Address } from '@ethereumjs/util';
import { Chain, Common, Hardfork } from '@ethereumjs/common';
import { EVM } from '@ethereumjs/evm';
import { getContractAddress } from '@ethersproject/address';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';

Expand All @@ -24,6 +27,7 @@ import {
import { getContractDeploymentTransactionHash, saveContractDeploymentTransactionHash } from './network';
import { getTaskActionIds } from './actionId';
import { getArtifactFromContractOutput } from './artifact';
import { getSigner } from './signers';

// Maps to ../v2 and ../v3.
const VERSION_ROOTS = ['v2', 'v3'].map((version) => path.resolve(__dirname, `../${version}`));
Expand All @@ -46,6 +50,12 @@ export enum TaskStatus {
SCRIPT,
}

type ContractInfo = {
name: string;
expectedAddress: string;
args: Array<Param>;
};

/* eslint-disable @typescript-eslint/no-var-requires */

export default class Task {
Expand Down Expand Up @@ -109,6 +119,154 @@ export default class Task {
return instance;
}

async deployFactoryContracts(
populatedDeployTransaction: ethers.PopulatedTransaction,
expectedContracts: Array<string>,
needsDeploy: boolean,
from?: SignerWithAddress,
force?: boolean
): Promise<ethers.providers.TransactionReceipt | undefined> {
if (!needsDeploy || this.mode == TaskMode.CHECK) {
return undefined;
}

const output = this.output({ ensure: false });

if (force == false) {
let needsDeploy = false;
for (const name of expectedContracts) {
if (!output[name]) {
needsDeploy = true;
}

logger.info(`${name} already deployed at ${output[name]}`);
}

if (needsDeploy) {
logger.info('Some contracts were not deployed, re-deploying all contracts for this transaction...');
} else {
return undefined;
}
}

logger.info(`Deploying contracts using factory...`);

from = from || (await getSigner());
const receipt = await from?.sendTransaction(populatedDeployTransaction);

return await receipt.wait();
}

// NOTE: contractsInfo must be sorted by deployment order
async saveAndVerifyFactoryContracts(
contractsInfo: Array<ContractInfo>,
deployTransaction?: ethers.providers.TransactionReceipt
): Promise<void> {
const { ethers } = await import('hardhat');

if (deployTransaction == null) {
// All contracts are deployed by the one factory transaction, so we can find the transaction hash by the first element
const deployedAddress = this.output()[contractsInfo[0].name];
const deploymentTxHash = getContractDeploymentTransactionHash(deployedAddress, this.network);
deployTransaction = await ethers.provider.getTransactionReceipt(deploymentTxHash);
}

const evm = await this.createEVM();
for (const contractInfo of contractsInfo) {
const isDeployedBytecodeValid = await this.checkBytecodeAndSaveEVMState(
evm,
deployTransaction,
this.artifact(contractInfo.name),
contractInfo.expectedAddress,
contractInfo.args
);

if (isDeployedBytecodeValid && this.mode === TaskMode.CHECK) {
logger.success(`Verified contract '${contractInfo.name}' on network '${this.network}' of task '${this.id}'`);
}

if (isDeployedBytecodeValid == false) {
throw Error(
`Contract ${contractInfo.name} at ${contractInfo.expectedAddress} does not match expected bytecode with abi.`
);
}

if (this.mode === TaskMode.CHECK) {
continue;
}

const instance = await this.instanceAt(contractInfo.name, contractInfo.expectedAddress);
this.save({ [contractInfo.name]: instance });
logger.success(`Contract ${contractInfo.name} attached at ${contractInfo.expectedAddress}`);

if (this.mode === TaskMode.LIVE) {
saveContractDeploymentTransactionHash(
contractInfo.expectedAddress,
deployTransaction.transactionHash,
this.network
);
}

await this.verify(contractInfo.name, contractInfo.expectedAddress, contractInfo.args);
}
}

async createEVM(): Promise<EVM> {
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Cancun });
const evm = await EVM.create({
common,
});
return evm;
}

async checkBytecodeAndSaveEVMState(
evm: EVM,
deployTransaction: ethers.providers.TransactionReceipt,
artifact: Artifact,
contractAddress: string,
args: Array<Param> = []
): Promise<boolean> {
const { ethers } = await import('hardhat');

const runBytecode = hexToBytes(
ethers.utils.hexlify(
ethers.utils.concat([artifact.bytecode, new ethers.utils.Interface(artifact.abi).encodeDeploy(args)])
)
);

const block = await ethers.provider.getBlock(deployTransaction.blockNumber);
if (!block) {
throw Error(`Could not find block ${deployTransaction.blockNumber}`);
}

const res = await evm.runCode({
code: runBytecode,
to: Address.fromString(contractAddress),
block: {
header: {
number: BigInt(block.number),
timestamp: BigInt(block.timestamp),
cliqueSigner: () => Address.fromString(ethers.constants.AddressZero),
coinbase: Address.fromString(ethers.constants.AddressZero),
difficulty: BigInt(0),
gasLimit: block.gasLimit.toBigInt(),
prevRandao: hexToBytes(ethers.constants.HashZero),
baseFeePerGas: undefined,
getBlobGasPrice: () => undefined,
},
},
});

if (res.exceptionError) {
new Error(`computeDeployedBytecode failed: ${res.exceptionError}`);
}

await evm.stateManager.putContractCode(Address.fromString(contractAddress), res.returnValue);

const deployedCode = await ethers.provider.getCode(contractAddress);
return ethers.utils.hexValue(res.returnValue) == deployedCode;
}

async deploy(
name: string,
args: Array<Param> = [],
Expand Down
62 changes: 40 additions & 22 deletions v3/tasks/20241204-v3-vault/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,58 @@ export default async (task: Task, { force, from }: TaskRunOptions = {}): Promise
const vaultFactory = await task.deployAndVerify('VaultFactory', vaultFactoryArgs, from, force);

const vaultAddress = await vaultFactory.getDeploymentAddress(input.salt);

if (vaultAddress !== input.targetVaultAddress) {
throw Error('Incorrect target address');
}

// Skip deployment if it's already done
if ((await vaultFactory.isDeployed(vaultAddress)) === false) {
await vaultFactory.create(
const deployTransaction = await task.deployFactoryContracts(
await vaultFactory.populateTransaction.create(
input.salt,
vaultAddress,
input.vaultCreationCode,
input.vaultExtensionCreationCode,
input.vaultAdminCreationCode,
{ gasLimit: 17e6 }
);
}
),
['Vault', 'VaultExtension', 'VaultAdmin', 'ProtocolFeeController'],
(await vaultFactory.isDeployed(vaultAddress)) === false,
from,
force
);

const protocolFeeControllerAddress = await vaultFactory.deployedProtocolFeeControllers(vaultAddress);
const vaultExtensionAddress = await vaultFactory.deployedVaultExtensions(vaultAddress);
const vaultAdminAddress = await vaultFactory.deployedVaultAdmins(vaultAddress);
const vaultExtensionAddress = await vaultFactory.deployedVaultExtensions(vaultAddress);

await task.verify('Vault', vaultAddress, [vaultExtensionAddress, input.Authorizer, protocolFeeControllerAddress]);
await task.verify('VaultExtension', vaultExtensionAddress, [vaultAddress, vaultAdminAddress]);
await task.verify('VaultAdmin', vaultAdminAddress, [
vaultAddress,
input.pauseWindowDuration,
input.bufferPeriodDuration,
input.minTradeAmount,
input.minWrapAmount,
]);
await task.verify('ProtocolFeeController', protocolFeeControllerAddress, [vaultAddress]);

await task.save({ Vault: vaultAddress });
await task.save({ VaultExtension: vaultExtensionAddress });
await task.save({ VaultAdmin: vaultAdminAddress });
await task.save({ ProtocolFeeController: protocolFeeControllerAddress });
await task.saveAndVerifyFactoryContracts(
[
{
name: 'ProtocolFeeController',
expectedAddress: protocolFeeControllerAddress,
args: [vaultAddress],
},
{
name: 'VaultAdmin',
expectedAddress: vaultAdminAddress,
args: [
vaultAddress,
input.pauseWindowDuration,
input.bufferPeriodDuration,
input.minTradeAmount,
input.minWrapAmount,
],
},
{
name: 'VaultExtension',
expectedAddress: vaultExtensionAddress,
args: [vaultAddress, vaultAdminAddress],
},
{
name: 'Vault',
expectedAddress: vaultAddress,
args: [vaultExtensionAddress, input.Authorizer, protocolFeeControllerAddress],
},
],
deployTransaction
);
};
Loading
Loading