Yorke Rhodes 1 year ago committed by GitHub
parent b71050a0d4
commit 8e84ef4496
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      solidity/.gitignore
  2. 23
      solidity/flatten.sh
  3. 4
      solidity/hardhat.config.ts
  4. 1
      solidity/package.json
  5. 3970
      typescript/infra/config/environments/testnet4/core/verification.json
  6. 9
      typescript/infra/scripts/verify.ts
  7. 6
      typescript/sdk/src/consts/chainMetadata.ts
  8. 17
      typescript/sdk/src/consts/environments/testnet.json
  9. 115
      typescript/sdk/src/deploy/HyperlaneDeployer.ts
  10. 11
      typescript/sdk/src/deploy/proxy.ts
  11. 16
      typescript/sdk/src/deploy/verify/ContractVerifier.ts

@ -11,3 +11,4 @@ coverage.json
out
forge-cache
docs
flattened/

@ -0,0 +1,23 @@
LICENSE="// SPDX-License-Identifier: MIT OR Apache-2.0"
rm -rf flattened
mkdir -p flattened
# flatten contracts
yarn hardhat flatten > flattened/flattened.sol
# remove duplicate licenses
grep -vE "// SPDX.*" flattened/flattened.sol > flattened/delicensed.sol
# add license
echo "$LICENSE" | cat - flattened/delicensed.sol > flattened/licensed.sol
# compile
solc flattened/licensed.sol
# TODO: automate this?
if [ $? -ne 0 ]; then
echo "Remove @openzeppelin/../ICrossDomainMessenger and replace Optimism_Bridge with ICrossDomainMessenger"
echo "Then try compiling again with solc flattened/licensed.sol"
exit 1
fi

@ -9,11 +9,7 @@ import 'solidity-coverage';
*/
module.exports = {
solidity: {
compilers: [
{
version: '0.8.19',
},
],
settings: {
optimizer: {
enabled: true,

@ -50,6 +50,7 @@
"clean": "hardhat clean && rm -rf ./dist ./cache ./types ./coverage",
"coverage": "./coverage.sh",
"docs": "forge doc",
"flatten": "./flatten.sh",
"prettier": "prettier --write ./contracts ./test",
"test": "hardhat test && forge test -vvv",
"gas": "forge snapshot",

@ -14,8 +14,7 @@ import { assertEnvironment, getArgs, getEnvironmentConfig } from './utils';
// already be installed via `solc-select install $VERSION`
async function main() {
const argv = await getArgs()
// This file can be generated by running `yarn hardhat flatten > flattened.sol`,
// and then removing any lines with SPDX identifiers (`solc` complains otherwise).
// This file can be generated by running `$ yarn workspace @hyperlane-xyz/core flatten`,
.string('source')
.describe('source', 'flattened solidity source file')
.demandOption('source')
@ -37,9 +36,9 @@ async function main() {
// from solidity/core/hardhat.config.ts
const compilerOptions: CompilerOptions = {
codeformat: 'solidity-single-file',
compilerversion: 'v0.8.17+commit.8df45f5f',
optimizationUsed: '1',
runs: '999999',
compilerversion: 'v0.8.19+commit.7dd6d404',
optimizationUsed: '0',
runs: '200',
};
const versionRegex = /v(\d.\d.\d+)\+commit.\w+/;

@ -433,9 +433,9 @@ export const scrollsepolia: ChainMetadata = {
blockExplorers: [
{
name: 'Scroll Explorer',
url: 'https://sepolia-blockscout.scroll.io',
apiUrl: 'https://sepolia-blockscout.scroll.io/api',
family: ExplorerFamily.Blockscout,
url: 'https://sepolia.scrollscan.dev/',
apiUrl: 'https://api-sepolia.scrollscan.com/api',
family: ExplorerFamily.Etherscan,
},
],
blocks: {

@ -190,22 +190,5 @@
"protocolFee": "0x244d1F7e30Be144A87602905baBF86630e8f39DC",
"mailbox": "0x2d1889fe5B092CD988972261434F7E5f26041115",
"validatorAnnounce": "0x99303EFF09332cDd93E8BC8b2F07b2416e4501e5"
},
"solanadevnet": {
"storageGasOracle": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x0000000000000000000000000000000000000000",
"proxyAdmin": "0x0000000000000000000000000000000000000000",
"mailbox": "4v25Dz9RccqUrTzmfHzJMsjd1iVoNrWzeJ4o6GYuJrVn",
"interchainGasPaymaster": "7hMPEGdgBQFsjEz3aaNwZp8WMFHs615zAM3erXBDJuJR",
"defaultIsmInterchainGasPaymaster": "0x0000000000000000000000000000000000000000",
"multisigIsm": "0x0000000000000000000000000000000000000000",
"testRecipient": "0x0000000000000000000000000000000000000000",
"interchainAccountIsm": "0x0000000000000000000000000000000000000000",
"aggregationIsmFactory": "0x0000000000000000000000000000000000000000",
"routingIsmFactory": "0x0000000000000000000000000000000000000000",
"interchainQueryRouter": "0x0000000000000000000000000000000000000000",
"interchainAccountRouter": "0x0000000000000000000000000000000000000000",
"merkleRootMultisigIsmFactory": "0x0000000000000000000000000000000000000000",
"messageIdMultisigIsmFactory": "0x0000000000000000000000000000000000000000"
}
}

@ -34,9 +34,18 @@ import { MultiProvider } from '../providers/MultiProvider';
import { MailboxClientConfig } from '../router/types';
import { ChainMap, ChainName } from '../types';
import { UpgradeConfig, proxyAdmin } from './proxy';
import {
UpgradeConfig,
isProxy,
proxyAdmin,
proxyConstructorArgs,
proxyImplementation,
} from './proxy';
import { ContractVerificationInput } from './verify/types';
import { getContractVerificationInput } from './verify/utils';
import {
buildVerificationInput,
getContractVerificationInput,
} from './verify/utils';
export interface DeployerOptions {
logger?: Debugger;
@ -60,6 +69,7 @@ export abstract class HyperlaneDeployer<
protected readonly multiProvider: MultiProvider,
protected readonly factories: Factories,
protected readonly options?: DeployerOptions,
protected readonly recoverVerificationInputs = false,
) {
this.logger = options?.logger ?? debug('hyperlane:deployer');
this.chainTimeoutMs = options?.chainTimeoutMs ?? 5 * 60 * 1000; // 5 minute timeout per chain
@ -266,21 +276,29 @@ export abstract class HyperlaneDeployer<
): Promise<ReturnType<F['deploy']>> {
const cachedContract = this.readCache(chain, factory, contractName);
if (cachedContract) {
if (this.recoverVerificationInputs) {
const recoveredInputs = await this.recoverVerificationArtifacts(
chain,
contractName,
cachedContract,
constructorArgs,
initializeArgs,
);
this.addVerificationArtifacts(chain, recoveredInputs);
}
return cachedContract;
}
const signer = this.multiProvider.getSigner(chain);
const overrides = this.multiProvider.getTransactionOverrides(chain);
this.logger(`Deploy ${contractName} on ${chain}`);
const contract = await (factory
.connect(signer)
.deploy(...constructorArgs, overrides) as ReturnType<F['deploy']>);
await this.multiProvider.handleTx(chain, contract.deployTransaction);
const contract = await this.multiProvider.handleDeploy(
chain,
factory,
constructorArgs,
);
if (initializeArgs) {
this.logger(`Initialize ${contractName} on ${chain}`);
const overrides = this.multiProvider.getTransactionOverrides(chain);
const initTx = await contract.initialize(...initializeArgs, overrides);
await this.multiProvider.handleTx(chain, initTx);
}
@ -391,19 +409,21 @@ export abstract class HyperlaneDeployer<
proxyAdmin: string,
initializeArgs?: Parameters<C['initialize']>,
): Promise<C> {
const initData = initializeArgs
? implementation.interface.encodeFunctionData(
'initialize',
initializeArgs,
)
: '0x';
const isProxied = await isProxy(
this.multiProvider.getProvider(chain),
implementation.address,
);
if (isProxied) {
// if the implementation is already a proxy, do not deploy a new proxy
return implementation;
}
this.logger(`Deploying transparent upgradable proxy`);
const constructorArgs: [string, string, string] = [
implementation.address,
const constructorArgs = proxyConstructorArgs(
implementation,
proxyAdmin,
initData,
];
initializeArgs,
);
const proxy = await this.deployContractFromFactory(
chain,
new TransparentUpgradeableProxy__factory(),
@ -465,6 +485,52 @@ export abstract class HyperlaneDeployer<
return undefined;
}
async recoverVerificationArtifacts<C extends ethers.Contract>(
chain: ChainName,
contractName: string,
cachedContract: C,
constructorArgs: Parameters<C['deploy']>,
initializeArgs?: Parameters<C['initialize']>,
): Promise<ContractVerificationInput[]> {
const provider = this.multiProvider.getProvider(chain);
const isProxied = await isProxy(provider, cachedContract.address);
let implementation: string;
if (isProxied) {
implementation = await proxyImplementation(
provider,
cachedContract.address,
);
} else {
implementation = cachedContract.address;
}
const implementationInput = buildVerificationInput(
contractName,
implementation,
cachedContract.interface.encodeDeploy(constructorArgs),
);
if (!isProxied) {
return [implementationInput];
}
const admin = await proxyAdmin(provider, cachedContract.address);
const proxyArgs = proxyConstructorArgs(
cachedContract.attach(implementation),
admin,
initializeArgs,
);
const proxyInput = buildVerificationInput(
'TransparentUpgradeableProxy',
cachedContract.address,
TransparentUpgradeableProxy__factory.createInterface().encodeDeploy(
proxyArgs,
),
);
return [implementationInput, proxyInput];
}
/**
* Deploys the Implementation and Proxy for a given contract
*
@ -476,15 +542,6 @@ export abstract class HyperlaneDeployer<
constructorArgs: Parameters<Factories[K]['deploy']>,
initializeArgs?: Parameters<HyperlaneContracts<Factories>[K]['initialize']>,
): Promise<HyperlaneContracts<Factories>[K]> {
const cachedContract = this.readCache(
chain,
this.factories[contractName],
contractName.toString(),
);
if (cachedContract) {
return cachedContract;
}
// Try to initialize the implementation even though it may not be necessary
const implementation = await this.deployContract(
chain,

@ -37,6 +37,17 @@ export async function proxyAdmin(
return ethers.utils.getAddress(storageValue.slice(26));
}
export function proxyConstructorArgs<C extends ethers.Contract>(
implementation: C,
proxyAdmin: string,
initializeArgs?: Parameters<C['initialize']>,
): [string, string, string] {
const initData = initializeArgs
? implementation.interface.encodeFunctionData('initialize', initializeArgs)
: '0x';
return [implementation.address, proxyAdmin, initData];
}
export async function isProxy(
provider: ethers.providers.Provider,
proxy: Address,

@ -27,6 +27,7 @@ enum ExplorerApiErrors {
ALREADY_VERIFIED_ALT = 'Already Verified',
VERIFICATION_PENDING = 'Pending in queue',
PROXY_FAILED = 'A corresponding implementation contract was unfortunately not detected for the proxy address.',
BYTECODE_MISMATCH = 'Fail - Unable to verify. Compiled contract deployment bytecode does NOT match the transaction deployment bytecode.',
}
export class ContractVerifier extends MultiGeneric<VerificationInput> {
@ -106,7 +107,12 @@ export class ContractVerifier extends MultiGeneric<VerificationInput> {
case ExplorerApiErrors.ALREADY_VERIFIED_ALT:
return;
case ExplorerApiErrors.PROXY_FAILED:
this.logger(`Proxy verification failed, try manually?`);
this.logger(`Proxy verification failed for, try manually?`);
return;
case ExplorerApiErrors.BYTECODE_MISMATCH:
this.logger(
`Compiled bytecode does not match deployed bytecode, check constructor arguments?`,
);
return;
default:
this.logger(
@ -233,7 +239,13 @@ export class ContractVerifier extends MultiGeneric<VerificationInput> {
}
if (await this.isAlreadyVerified(chain, input)) {
this.logger(`Contract ${input.name} already verified on ${chain}`);
const addressUrl = await this.multiProvider.tryGetExplorerAddressUrl(
chain,
input.address,
);
this.logger(
`Contract ${input.name} already verified on ${chain} at ${addressUrl}#code`,
);
// There is a rate limit of 5 requests per second
await sleep(200);
return;

Loading…
Cancel
Save