Skip to content

Commit 0f6bf7b

Browse files
authored
Defender: Improve license type handling (#1013)
1 parent 20f0207 commit 0f6bf7b

File tree

11 files changed

+311
-28
lines changed

11 files changed

+311
-28
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
'unicorn/no-array-reduce': 'warn',
1616
'no-plusplus': ['warn', { allowForLoopAfterthoughts: true }],
1717
},
18+
ignorePatterns: ['submodules'],
1819
overrides: [
1920
{
2021
files: ['*.ts'],

docs/modules/ROOT/pages/foundry/api/pages/api-foundry-upgrades.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ struct DefenderOptions {
7272
string relayerId;
7373
bytes32 salt;
7474
string upgradeApprovalProcessId;
75+
string licenseType;
76+
bool skipLicenseType;
7577
}
7678
```
7779

packages/plugin-hardhat/CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 3.1.0 (2024-04-22)
4+
5+
- Defender: Fix handling of license types for block explorer verification, support `licenseType` and `skipLicenseType` options. ([#1013](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/1013))
6+
7+
### Breaking changes
8+
- When deploying through Defender, if your contract does not have an SPDX license identifier, the verified source code on Etherscan will no longer show any license type.
9+
- If you want the license type to appear as "None", either set your contract to have `// SPDX-License-Identifier: UNLICENSED` according to [Solidity docs](https://docs.soliditylang.org/en/latest/layout-of-source-files.html#spdx-license-identifier), or set the `licenseType` option to `"None"`.
10+
311
## 3.0.5 (2024-03-08)
412

513
- Simplify console logging for `admin.transferProxyAdminOwnership`. ([#978](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/978))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pragma solidity ^0.8.20;
2+
3+
contract NoLicense {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
contract Unlicensed {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// SPDX-License-Identifier: UnrecognizedId
2+
pragma solidity ^0.8.20;
3+
4+
contract UnrecognizedLicense {}

packages/plugin-hardhat/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openzeppelin/hardhat-upgrades",
3-
"version": "3.0.5",
3+
"version": "3.1.0",
44
"description": "",
55
"repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/plugin-hardhat",
66
"license": "MIT",

packages/plugin-hardhat/src/defender/deploy.ts

+76-17
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,39 @@ export async function defenderDeploy(
7070
const verifySourceCode = opts.verifySourceCode ?? true;
7171
debug(`Verify source code: ${verifySourceCode}`);
7272

73-
let license: string | undefined = undefined;
74-
if (verifySourceCode) {
75-
license = getLicenseFromMetadata(contractInfo);
76-
debug(`License type: ${license}`);
77-
}
78-
7973
if (opts.salt !== undefined) {
8074
debug(`Salt: ${opts.salt}`);
8175
}
8276

77+
if (opts.licenseType !== undefined) {
78+
if (opts.verifySourceCode === false) {
79+
throw new UpgradesError('The `licenseType` option cannot be used when the `verifySourceCode` option is `false`');
80+
} else if (opts.skipLicenseType) {
81+
throw new UpgradesError('The `licenseType` option cannot be used when the `skipLicenseType` option is `true`');
82+
}
83+
}
84+
85+
let licenseType: SourceCodeLicense | undefined = undefined;
86+
if (verifySourceCode) {
87+
if (opts.licenseType !== undefined) {
88+
licenseType = opts.licenseType;
89+
debug(`licenseType option: ${licenseType}`);
90+
} else if (!opts.skipLicenseType) {
91+
const spdxIdentifier = getSpdxLicenseIdentifier(contractInfo);
92+
debug(`SPDX license identifier from metadata: ${spdxIdentifier}`);
93+
if (spdxIdentifier !== undefined) {
94+
licenseType = toLicenseType(spdxIdentifier, contractInfo);
95+
debug(`licenseType inferred: ${licenseType}`);
96+
}
97+
}
98+
}
99+
83100
const deploymentRequest: DeployContractRequest = {
84101
contractName: contractInfo.contractName,
85102
contractPath: contractInfo.sourceName,
86103
network: network,
87104
artifactPayload: JSON.stringify(contractInfo.buildInfo),
88-
licenseType: license as SourceCodeLicense | undefined, // cast without validation but catch error from API below
105+
licenseType: licenseType,
89106
constructorInputs: constructorArgs,
90107
verifySourceCode: verifySourceCode,
91108
relayerId: opts.relayerId,
@@ -101,8 +118,9 @@ export async function defenderDeploy(
101118
} catch (e: any) {
102119
if (e.response?.data?.message?.includes('licenseType should be equal to one of the allowed values')) {
103120
throw new UpgradesError(
104-
`License type ${license} is not a valid SPDX license identifier for block explorer verification.`,
105-
() => 'Specify a valid SPDX-License-Identifier in your contract.',
121+
`The licenseType option "${licenseType}" is not valid for block explorer verification.`,
122+
() =>
123+
'See https://etherscan.io/contract-license-types for supported values and use the string found in brackets, e.g. "MIT"',
106124
);
107125
} else {
108126
throw e;
@@ -197,9 +215,9 @@ async function getContractInfo(
197215
}
198216

199217
/**
200-
* Get the license type from the contract metadata without validating its validity, except converts undefined or UNLICENSED to None.
218+
* Get the SPDX license identifier from the contract metadata without validating it.
201219
*/
202-
function getLicenseFromMetadata(contractInfo: ContractInfo): string {
220+
function getSpdxLicenseIdentifier(contractInfo: ContractInfo): string | undefined {
203221
const compilerOutput: CompilerOutputWithMetadata =
204222
contractInfo.buildInfo.output.contracts[contractInfo.sourceName][contractInfo.contractName];
205223

@@ -213,11 +231,52 @@ function getLicenseFromMetadata(contractInfo: ContractInfo): string {
213231

214232
const metadata = JSON.parse(metadataString);
215233

216-
const license: string = metadata.sources[contractInfo.sourceName].license;
217-
if (license === undefined || license === 'UNLICENSED') {
218-
// UNLICENSED means no license according to solidity docs
219-
return 'None';
220-
} else {
221-
return license;
234+
return metadata.sources[contractInfo.sourceName].license;
235+
}
236+
237+
/**
238+
* Infers a SourceCodeLicense from an SPDX license identifier.
239+
*/
240+
function toLicenseType(spdxIdentifier: string, contractInfo: ContractInfo): SourceCodeLicense {
241+
switch (spdxIdentifier) {
242+
case 'UNLICENSED':
243+
return 'None';
244+
case 'Unlicense':
245+
return 'Unlicense';
246+
case 'MIT':
247+
return 'MIT';
248+
case 'GPL-2.0-only':
249+
case 'GPL-2.0-or-later':
250+
return 'GNU GPLv2';
251+
case 'GPL-3.0-only':
252+
case 'GPL-3.0-or-later':
253+
return 'GNU GPLv3';
254+
case 'LGPL-2.1-only':
255+
case 'LGPL-2.1-or-later':
256+
return 'GNU LGPLv2.1';
257+
case 'LGPL-3.0-only':
258+
case 'LGPL-3.0-or-later':
259+
return 'GNU LGPLv3';
260+
case 'BSD-2-Clause':
261+
return 'BSD-2-Clause';
262+
case 'BSD-3-Clause':
263+
return 'BSD-3-Clause';
264+
case 'MPL-2.0':
265+
return 'MPL-2.0';
266+
case 'OSL-3.0':
267+
return 'OSL-3.0';
268+
case 'Apache-2.0':
269+
return 'Apache-2.0';
270+
case 'AGPL-3.0-only':
271+
case 'AGPL-3.0-or-later':
272+
return 'GNU AGPLv3';
273+
case 'BUSL-1.1':
274+
return 'BSL 1.1';
275+
default:
276+
throw new UpgradesError(
277+
`SPDX license identifier ${spdxIdentifier} in ${contractInfo.sourceName} does not look like a supported license for block explorer verification.`,
278+
() =>
279+
`Use the \`licenseType\` option to specify a license type, or set the \`skipLicenseType\` option to \`true\` to skip.`,
280+
);
222281
}
223282
}

packages/plugin-hardhat/src/utils/options.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { SourceCodeLicense } from '@openzeppelin/defender-sdk-deploy-client';
12
import {
23
DeployOpts,
34
ProxyKindOption,
@@ -64,6 +65,8 @@ export type DefenderDeployOptions = DefenderDeploy & {
6465
relayerId?: string;
6566
salt?: string;
6667
createFactoryAddress?: string;
68+
licenseType?: SourceCodeLicense;
69+
skipLicenseType?: boolean;
6770
};
6871

6972
/**

0 commit comments

Comments
 (0)