Skip to content

Commit

Permalink
Support externally linked libraries for Defender deployments (#960)
Browse files Browse the repository at this point in the history
Co-authored-by: Ernesto García <[email protected]>
  • Loading branch information
ericglau and ernestognw authored Feb 27, 2024
1 parent 4904c5b commit 5c6829b
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 19 deletions.
4 changes: 4 additions & 0 deletions packages/plugin-hardhat/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 3.0.4 (2024-02-27)

- Support externally linked libraries for Defender deployments. ([#960](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/960))

## 3.0.3 (2024-02-06)

- Support Defender deployments using EOA or Safe. ([#967](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/967))
Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-hardhat/contracts/MultipleExternalLibraries.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pragma solidity ^0.5.1;

import "./ExternalLibraries.sol";

contract MultipleExternalLibraries {

function getLibraryVersion() external pure returns(string memory) {
return SafeMath.version();
}

function getLibrary2Version() external pure returns(string memory) {
return SafeMathV2.version();
}
}
6 changes: 3 additions & 3 deletions packages/plugin-hardhat/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openzeppelin/hardhat-upgrades",
"version": "3.0.3",
"version": "3.0.4",
"description": "",
"repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/plugin-hardhat",
"license": "MIT",
Expand Down Expand Up @@ -37,8 +37,8 @@
"dependencies": {
"@openzeppelin/defender-admin-client": "^1.52.0",
"@openzeppelin/defender-base-client": "^1.52.0",
"@openzeppelin/defender-sdk-base-client": "^1.8.0",
"@openzeppelin/defender-sdk-deploy-client": "^1.8.0",
"@openzeppelin/defender-sdk-base-client": "^1.9.0",
"@openzeppelin/defender-sdk-deploy-client": "^1.9.0",
"@openzeppelin/upgrades-core": "^1.32.0",
"chalk": "^4.1.0",
"debug": "^4.1.1",
Expand Down
35 changes: 30 additions & 5 deletions packages/plugin-hardhat/src/defender/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { CompilerInput, CompilerOutputContract, HardhatRuntimeEnvironment } from

import { parseFullyQualifiedName } from 'hardhat/utils/contract-names';

import { DeploymentResponse, SourceCodeLicense, DeployContractRequest } from '@openzeppelin/defender-sdk-deploy-client';
import {
DeploymentResponse,
SourceCodeLicense,
DeployContractRequest,
DeployRequestLibraries,
} from '@openzeppelin/defender-sdk-deploy-client';
import {
Deployment,
RemoteDeploymentId,
Expand Down Expand Up @@ -41,6 +46,7 @@ interface ContractInfo {
sourceName: string;
contractName: string;
buildInfo: ReducedBuildInfo;
libraries?: DeployRequestLibraries;
}

type CompilerOutputWithMetadata = CompilerOutputContract & {
Expand Down Expand Up @@ -85,6 +91,7 @@ export async function defenderDeploy(
salt: opts.salt,
createFactoryAddress: opts.createFactoryAddress,
txOverrides: parseTxOverrides(opts.txOverrides),
libraries: contractInfo.libraries,
};

let deploymentResponse: DeploymentResponse;
Expand Down Expand Up @@ -135,12 +142,29 @@ async function getContractInfo(
factory: ethers.ContractFactory,
opts: UpgradeOptions,
): Promise<ContractInfo> {
let fullContractName;
let fullContractName, runValidation;
let libraries: DeployRequestLibraries | undefined;
try {
// Get fully qualified contract name from validations
// Get fully qualified contract name and link references from validations
const deployData = await getDeployData(hre, factory, opts);
[fullContractName] = getContractNameAndRunValidation(deployData.validations, deployData.version);
[fullContractName, runValidation] = getContractNameAndRunValidation(deployData.validations, deployData.version);
debug(`Contract ${fullContractName}`);

// Get externally linked libraries
const linkReferences = runValidation[fullContractName].linkReferences;
for (const ref in linkReferences) {
const linkedBytes = factory.bytecode.slice(2);

const start = linkReferences[ref].start * 2;
const length = linkReferences[ref].length * 2;

const linkFullyQualifiedName: `${string}:${string}` = `${linkReferences[ref].src}:${linkReferences[ref].name}`;
const linkAddress = `0x${linkedBytes.substring(start, start + length)}`;

libraries ??= {};
libraries[linkFullyQualifiedName] = linkAddress;
}
debug(`Libraries: ${JSON.stringify(libraries, null, 2)}`);
} catch (e) {
if (e instanceof ContractSourceNotFoundError) {
// Proxy contracts would not be found in the validations, so try to get these from the plugin's precompiled artifacts.
Expand All @@ -167,7 +191,8 @@ async function getContractInfo(
() => `Run \`npx hardhat compile\``,
);
}
return { sourceName, contractName, buildInfo };

return { sourceName, contractName, buildInfo, libraries };
}

/**
Expand Down
85 changes: 85 additions & 0 deletions packages/plugin-hardhat/test/defender-deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const CREATE_FACTORY = '0x0000000000000000000000000000000000000010';
const LOGIC_ADDRESS = '0x0000000000000000000000000000000000000003';
const INITIAL_OWNER_ADDRESS = '0x0000000000000000000000000000000000000004';
const DATA = '0x05';
const EXTERNAL_LIBRARY_ADDRESS = '0x1230000000000000000000000000000000000456';
const EXTERNAL_LIBRARY_2_ADDRESS = '0xabc0000000000000000000000000000000000def';

test.beforeEach(async t => {
t.context.fakeChainId = 'goerli';
Expand Down Expand Up @@ -102,6 +104,7 @@ test('calls defender deploy', async t => {
salt: undefined,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: undefined,
});

assertResult(t, result);
Expand Down Expand Up @@ -129,6 +132,7 @@ test('calls defender deploy with relayerId', async t => {
salt: undefined,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: undefined,
});

assertResult(t, result);
Expand Down Expand Up @@ -156,6 +160,7 @@ test('calls defender deploy with salt', async t => {
salt: SALT,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: undefined,
});

assertResult(t, result);
Expand Down Expand Up @@ -183,6 +188,7 @@ test('calls defender deploy with createFactoryAddress', async t => {
salt: undefined,
createFactoryAddress: CREATE_FACTORY,
txOverrides: undefined,
libraries: undefined,
});

assertResult(t, result);
Expand Down Expand Up @@ -210,6 +216,7 @@ test('calls defender deploy with license', async t => {
salt: undefined,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: undefined,
});

assertResult(t, result);
Expand Down Expand Up @@ -237,6 +244,7 @@ test('calls defender deploy with constructor args', async t => {
salt: undefined,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: undefined,
});

assertResult(t, result);
Expand Down Expand Up @@ -264,6 +272,7 @@ test('calls defender deploy with verify false', async t => {
salt: undefined,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: undefined,
});

assertResult(t, result);
Expand Down Expand Up @@ -291,6 +300,7 @@ test('calls defender deploy with ERC1967Proxy', async t => {
salt: undefined,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: undefined,
});
});

Expand All @@ -316,6 +326,7 @@ test('calls defender deploy with BeaconProxy', async t => {
salt: undefined,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: undefined,
});
});

Expand All @@ -341,6 +352,7 @@ test('calls defender deploy with TransparentUpgradeableProxy', async t => {
salt: undefined,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: undefined,
});
});

Expand Down Expand Up @@ -371,6 +383,7 @@ test('calls defender deploy with txOverrides.gasLimit', async t => {
maxFeePerGas: undefined,
maxPriorityFeePerGas: undefined,
},
libraries: undefined,
});

assertResult(t, result);
Expand Down Expand Up @@ -403,6 +416,7 @@ test('calls defender deploy with txOverrides.gasPrice', async t => {
maxFeePerGas: undefined,
maxPriorityFeePerGas: undefined,
},
libraries: undefined,
});

assertResult(t, result);
Expand Down Expand Up @@ -437,6 +451,77 @@ test('calls defender deploy with txOverrides.maxFeePerGas and txOverrides.maxPri
maxFeePerGas: '0x64',
maxPriorityFeePerGas: '0xa',
},
libraries: undefined,
});

assertResult(t, result);
});

test('calls defender deploy with external library', async t => {
const { spy, deploy, fakeHre, fakeChainId } = t.context;

const contractPath = 'contracts/Token.sol';
const contractName = 'TokenProxiable';

const factory = await ethers.getContractFactory(contractName, {
libraries: {
SafeMath: EXTERNAL_LIBRARY_ADDRESS,
},
});
const result = await deploy.defenderDeploy(fakeHre, factory, {});

const buildInfo = await hre.artifacts.getBuildInfo(`${contractPath}:${contractName}`);
sinon.assert.calledWithExactly(spy, {
contractName: contractName,
contractPath: contractPath,
network: fakeChainId,
artifactPayload: JSON.stringify(buildInfo),
licenseType: 'None',
constructorInputs: [],
verifySourceCode: true,
relayerId: undefined,
salt: undefined,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: {
'contracts/ExternalLibraries.sol:SafeMath': EXTERNAL_LIBRARY_ADDRESS,
},
});

assertResult(t, result);
});

test('calls defender deploy with multiple external libraries', async t => {
const { spy, deploy, fakeHre, fakeChainId } = t.context;

const contractPath = 'contracts/MultipleExternalLibraries.sol';
const contractName = 'MultipleExternalLibraries';

const factory = await ethers.getContractFactory(contractName, {
libraries: {
SafeMath: EXTERNAL_LIBRARY_ADDRESS,
SafeMathV2: EXTERNAL_LIBRARY_2_ADDRESS,
},
});
const result = await deploy.defenderDeploy(fakeHre, factory, {});

const buildInfo = await hre.artifacts.getBuildInfo(`${contractPath}:${contractName}`);
sinon.assert.calledWithExactly(spy, {
contractName: contractName,
contractPath: contractPath,
network: fakeChainId,
artifactPayload: JSON.stringify(buildInfo),
licenseType: 'None',
constructorInputs: [],
verifySourceCode: true,
relayerId: undefined,
salt: undefined,
createFactoryAddress: undefined,
txOverrides: undefined,
libraries: {
'contracts/ExternalLibraries.sol:SafeMath': EXTERNAL_LIBRARY_ADDRESS,
'contracts/ExternalLibraries.sol:SafeMathV2': EXTERNAL_LIBRARY_2_ADDRESS,
},
});

assertResult(t, result);
Expand Down
22 changes: 11 additions & 11 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1057,22 +1057,22 @@
lodash "^4.17.19"
node-fetch "^2.6.0"

"@openzeppelin/defender-sdk-base-client@^1.8.0", "@openzeppelin/defender-sdk-base-client@^1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-base-client/-/defender-sdk-base-client-1.9.0.tgz#2596ab12edc44236290720cd4a89cedbc480b3c0"
integrity sha512-ywxZslKaY7Z5z9APpBunIDp4nXkGnYZAStaIhzzh8vbbzu7lxiZO98tsX3B9vCefqWC4oyX0mm78CdyYUgW5KQ==
"@openzeppelin/defender-sdk-base-client@^1.10.0", "@openzeppelin/defender-sdk-base-client@^1.9.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-base-client/-/defender-sdk-base-client-1.10.0.tgz#038a5b13f92e03de42382ec331b670f40a915816"
integrity sha512-V21oI4G54sdEJ9lVN8q5OqfFRUoVDzjeXfWgpQvUpfy69r56NnE57D6e5RLG1fRp1J0APfW3lFjaaLwl0kqZpg==
dependencies:
amazon-cognito-identity-js "^6.3.6"
async-retry "^1.3.3"

"@openzeppelin/defender-sdk-deploy-client@^1.8.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-deploy-client/-/defender-sdk-deploy-client-1.9.0.tgz#ad2f61912f066386a22221bdcbd73d52b91e4c74"
integrity sha512-xw3qRJzE3XQRBoBBqOC7VOEtaVnzeN9EgsBZSjWlDUcmfJ6jdUuUsoqEkwYBZVEi+Dr3ujURY2DsmEvs0gFoNw==
"@openzeppelin/defender-sdk-deploy-client@^1.9.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@openzeppelin/defender-sdk-deploy-client/-/defender-sdk-deploy-client-1.10.0.tgz#64d7789eceede36ec12dcdae0dc4b67ffa7ae97d"
integrity sha512-PckmUQYwe26/u/s3sjLateSNtKQ0tdAaOyP6spsgaT+us+XUUqAt/EUfEJdGpt8JApsRWYzrQzH6Z0ywoUyqyw==
dependencies:
"@ethersproject/abi" "^5.7.0"
"@openzeppelin/defender-sdk-base-client" "^1.9.0"
axios "^1.4.0"
"@openzeppelin/defender-sdk-base-client" "^1.10.0"
axios "^1.6.7"
lodash "^4.17.21"

"@openzeppelin/docs-utils@^0.1.0":
Expand Down Expand Up @@ -1806,7 +1806,7 @@ available-typed-arrays@^1.0.5, available-typed-arrays@^1.0.6:
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz#ac812d8ce5a6b976d738e1c45f08d0b00bc7d725"
integrity sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==

axios@^1.4.0:
axios@^1.4.0, axios@^1.6.7:
version "1.6.7"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7"
integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==
Expand Down

0 comments on commit 5c6829b

Please sign in to comment.