@openzeppelin/upgrades-core 1.9.2 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/LICENSE +22 -0
  2. package/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.dbg.json +1 -1
  3. package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.dbg.json +4 -0
  4. package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json +80 -0
  5. package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.dbg.json +4 -0
  6. package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json +113 -0
  7. package/artifacts/AdminUpgradeabilityProxy.json +2 -2
  8. package/artifacts/contracts/proxy/AdminUpgradeabilityProxy.sol/AdminUpgradeabilityProxy.json +2 -2
  9. package/dist/beacon.d.ts +8 -0
  10. package/dist/beacon.d.ts.map +1 -0
  11. package/dist/beacon.js +25 -0
  12. package/dist/beacon.js.map +1 -0
  13. package/dist/deployment.d.ts +14 -3
  14. package/dist/deployment.d.ts.map +1 -1
  15. package/dist/deployment.js +20 -13
  16. package/dist/deployment.js.map +1 -1
  17. package/dist/deployment.test.js +23 -0
  18. package/dist/deployment.test.js.map +1 -1
  19. package/dist/eip-1967-type.d.ts +4 -0
  20. package/dist/eip-1967-type.d.ts.map +1 -0
  21. package/dist/eip-1967-type.js +37 -0
  22. package/dist/eip-1967-type.js.map +1 -0
  23. package/dist/eip-1967.d.ts +5 -0
  24. package/dist/eip-1967.d.ts.map +1 -1
  25. package/dist/eip-1967.js +19 -12
  26. package/dist/eip-1967.js.map +1 -1
  27. package/dist/impl-address.d.ts +19 -0
  28. package/dist/impl-address.d.ts.map +1 -0
  29. package/dist/impl-address.js +67 -0
  30. package/dist/impl-address.js.map +1 -0
  31. package/dist/impl-store.d.ts +3 -2
  32. package/dist/impl-store.d.ts.map +1 -1
  33. package/dist/impl-store.js +21 -10
  34. package/dist/impl-store.js.map +1 -1
  35. package/dist/index.d.ts +7 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +23 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/manifest.d.ts +1 -1
  40. package/dist/manifest.d.ts.map +1 -1
  41. package/dist/provider.d.ts +2 -0
  42. package/dist/provider.d.ts.map +1 -1
  43. package/dist/provider.js +11 -1
  44. package/dist/provider.js.map +1 -1
  45. package/dist/proxy-kind.d.ts +12 -0
  46. package/dist/proxy-kind.d.ts.map +1 -0
  47. package/dist/proxy-kind.js +50 -0
  48. package/dist/proxy-kind.js.map +1 -0
  49. package/dist/standalone.d.ts +19 -0
  50. package/dist/standalone.d.ts.map +1 -0
  51. package/dist/standalone.js +24 -0
  52. package/dist/standalone.js.map +1 -0
  53. package/dist/standalone.test.d.ts +2 -0
  54. package/dist/standalone.test.d.ts.map +1 -0
  55. package/dist/standalone.test.js +33 -0
  56. package/dist/standalone.test.js.map +1 -0
  57. package/dist/storage/index.d.ts +7 -0
  58. package/dist/storage/index.d.ts.map +1 -1
  59. package/dist/storage/index.js +11 -5
  60. package/dist/storage/index.js.map +1 -1
  61. package/dist/storage/report.d.ts +3 -2
  62. package/dist/storage/report.d.ts.map +1 -1
  63. package/dist/storage/report.js +9 -3
  64. package/dist/storage/report.js.map +1 -1
  65. package/dist/usage-error.d.ts +21 -0
  66. package/dist/usage-error.d.ts.map +1 -0
  67. package/dist/usage-error.js +51 -0
  68. package/dist/usage-error.js.map +1 -0
  69. package/dist/utils/address.d.ts +8 -0
  70. package/dist/utils/address.d.ts.map +1 -0
  71. package/dist/utils/address.js +20 -0
  72. package/dist/utils/address.js.map +1 -0
  73. package/dist/utils/make-non-enumerable.d.ts +1 -1
  74. package/dist/utils/make-non-enumerable.d.ts.map +1 -1
  75. package/dist/utils/make-non-enumerable.js.map +1 -1
  76. package/dist/validate/error.d.ts.map +1 -1
  77. package/dist/validate/error.js +2 -65
  78. package/dist/validate/error.js.map +1 -1
  79. package/dist/validate/index.d.ts +2 -1
  80. package/dist/validate/index.d.ts.map +1 -1
  81. package/dist/validate/index.js +4 -1
  82. package/dist/validate/index.js.map +1 -1
  83. package/dist/validate/overrides.js +1 -1
  84. package/dist/validate/overrides.js.map +1 -1
  85. package/dist/validate/query.d.ts +3 -1
  86. package/dist/validate/query.d.ts.map +1 -1
  87. package/dist/validate/query.js +23 -7
  88. package/dist/validate/query.js.map +1 -1
  89. package/dist/validate/report.d.ts +8 -0
  90. package/dist/validate/report.d.ts.map +1 -0
  91. package/dist/validate/report.js +79 -0
  92. package/dist/validate/report.js.map +1 -0
  93. package/dist/version.d.ts +3 -3
  94. package/dist/version.d.ts.map +1 -1
  95. package/dist/version.js +12 -6
  96. package/dist/version.js.map +1 -1
  97. package/package.json +8 -5
  98. package/src/beacon.ts +20 -0
  99. package/src/deployment.test.ts +25 -0
  100. package/src/deployment.ts +43 -13
  101. package/src/eip-1967-type.ts +35 -0
  102. package/src/eip-1967.ts +16 -12
  103. package/src/impl-address.ts +76 -0
  104. package/src/impl-store.ts +23 -7
  105. package/src/index.ts +18 -1
  106. package/src/manifest.ts +1 -1
  107. package/src/provider.ts +16 -0
  108. package/src/proxy-kind.ts +59 -0
  109. package/src/standalone.test.ts +40 -0
  110. package/src/standalone.ts +41 -0
  111. package/src/storage/index.ts +14 -4
  112. package/src/storage/report.ts +10 -5
  113. package/src/usage-error.ts +68 -0
  114. package/src/utils/address.ts +16 -0
  115. package/src/utils/make-non-enumerable.ts +1 -1
  116. package/src/validate/error.ts +5 -67
  117. package/src/validate/index.ts +2 -0
  118. package/src/validate/overrides.ts +1 -1
  119. package/src/validate/query.ts +24 -6
  120. package/src/validate/report.ts +78 -0
  121. package/src/version.ts +14 -6
  122. package/CHANGELOG.md +0 -114
  123. package/dist/set-proxy-kind.d.ts +0 -5
  124. package/dist/set-proxy-kind.d.ts.map +0 -1
  125. package/dist/set-proxy-kind.js +0 -25
  126. package/dist/set-proxy-kind.js.map +0 -1
  127. package/src/set-proxy-kind.ts +0 -27
package/src/impl-store.ts CHANGED
@@ -4,9 +4,11 @@ import { EthereumProvider, isDevelopmentNetwork } from './provider';
4
4
  import { Deployment, InvalidDeployment, resumeOrDeploy, waitAndValidateDeployment } from './deployment';
5
5
  import type { Version } from './version';
6
6
  import assert from 'assert';
7
+ import { DeployOpts } from '.';
7
8
 
8
9
  interface ManifestLens<T> {
9
10
  description: string;
11
+ type: string;
10
12
  (data: ManifestData): ManifestField<T>;
11
13
  }
12
14
 
@@ -15,10 +17,22 @@ interface ManifestField<T> {
15
17
  set(value: T | undefined): void;
16
18
  }
17
19
 
20
+ /**
21
+ * Fetches the deployment from the manifest, or deploys it if not found.
22
+ *
23
+ * @param lens the manifest lens
24
+ * @param provider the Ethereum provider
25
+ * @param deploy the deploy function
26
+ * @param opts options containing the timeout and pollingInterval parameters. If undefined, assumes the timeout is not configurable and will not mention those parameters in the error message for TransactionMinedTimeout.
27
+ * @returns the deployment address
28
+ * @throws {InvalidDeployment} if the deployment is invalid
29
+ * @throws {TransactionMinedTimeout} if the transaction was not confirmed within the timeout period
30
+ */
18
31
  async function fetchOrDeployGeneric<T extends Deployment>(
19
32
  lens: ManifestLens<T>,
20
33
  provider: EthereumProvider,
21
34
  deploy: () => Promise<T>,
35
+ opts?: DeployOpts,
22
36
  ): Promise<string> {
23
37
  const manifest = await Manifest.forNetwork(provider);
24
38
 
@@ -40,7 +54,7 @@ async function fetchOrDeployGeneric<T extends Deployment>(
40
54
  return updated;
41
55
  });
42
56
 
43
- await waitAndValidateDeployment(provider, deployment);
57
+ await waitAndValidateDeployment(provider, deployment, lens.type, opts);
44
58
 
45
59
  return deployment.address;
46
60
  } catch (e) {
@@ -67,12 +81,13 @@ export async function fetchOrDeploy(
67
81
  version: Version,
68
82
  provider: EthereumProvider,
69
83
  deploy: () => Promise<ImplDeployment>,
84
+ opts?: DeployOpts,
70
85
  ): Promise<string> {
71
- return fetchOrDeployGeneric(implLens(version.linkedWithoutMetadata), provider, deploy);
86
+ return fetchOrDeployGeneric(implLens(version.linkedWithoutMetadata), provider, deploy, opts);
72
87
  }
73
88
 
74
89
  const implLens = (versionWithoutMetadata: string) =>
75
- lens(`implementation ${versionWithoutMetadata}`, data => ({
90
+ lens(`implementation ${versionWithoutMetadata}`, 'implementation', data => ({
76
91
  get: () => data.impls[versionWithoutMetadata],
77
92
  set: (value?: ImplDeployment) => (data.impls[versionWithoutMetadata] = value),
78
93
  }));
@@ -80,17 +95,18 @@ const implLens = (versionWithoutMetadata: string) =>
80
95
  export async function fetchOrDeployAdmin(
81
96
  provider: EthereumProvider,
82
97
  deploy: () => Promise<Deployment>,
98
+ opts?: DeployOpts,
83
99
  ): Promise<string> {
84
- return fetchOrDeployGeneric(adminLens, provider, deploy);
100
+ return fetchOrDeployGeneric(adminLens, provider, deploy, opts);
85
101
  }
86
102
 
87
- const adminLens = lens('proxy admin', data => ({
103
+ const adminLens = lens('proxy admin', 'proxy admin', data => ({
88
104
  get: () => data.admin,
89
105
  set: (value?: Deployment) => (data.admin = value),
90
106
  }));
91
107
 
92
- function lens<T>(description: string, fn: (data: ManifestData) => ManifestField<T>): ManifestLens<T> {
93
- return Object.assign(fn, { description });
108
+ function lens<T>(description: string, type: string, fn: (data: ManifestData) => ManifestField<T>): ManifestLens<T> {
109
+ return Object.assign(fn, { description, type });
94
110
  }
95
111
 
96
112
  async function checkForAddressClash(
package/src/index.ts CHANGED
@@ -9,10 +9,27 @@ export * from './src-decoder';
9
9
  export * from './solc-api';
10
10
  export * from './deployment';
11
11
  export * from './link-refs';
12
+ export * from './error';
12
13
 
13
14
  export { getStorageLayoutForAddress } from './manifest-storage-layout';
14
15
 
15
16
  export * from './scripts/migrate-oz-cli-project';
16
17
 
17
18
  export { logWarning } from './utils/log';
18
- export { setProxyKind } from './set-proxy-kind';
19
+ export { setProxyKind, processProxyKind } from './proxy-kind';
20
+
21
+ export { UpgradeableContract } from './standalone';
22
+
23
+ export { isTransparentOrUUPSProxy, isBeaconProxy } from './eip-1967-type';
24
+ export { getImplementationAddressFromBeacon, getImplementationAddressFromProxy } from './impl-address';
25
+ export { isBeacon } from './beacon';
26
+
27
+ export {
28
+ BeaconProxyUnsupportedError,
29
+ LoadProxyUnsupportedError,
30
+ PrepareUpgradeUnsupportedError,
31
+ DeployBeaconProxyUnsupportedError,
32
+ DeployBeaconProxyImplUnknownError,
33
+ DeployBeaconProxyKindError,
34
+ assertNotProxy,
35
+ } from './usage-error';
package/src/manifest.ts CHANGED
@@ -25,7 +25,7 @@ export interface ImplDeployment extends Deployment {
25
25
  }
26
26
 
27
27
  export interface ProxyDeployment extends Deployment {
28
- kind: 'uups' | 'transparent';
28
+ kind: 'uups' | 'transparent' | 'beacon';
29
29
  }
30
30
 
31
31
  function defaultManifest(): ManifestData {
package/src/provider.ts CHANGED
@@ -5,6 +5,7 @@ export interface EthereumProvider {
5
5
  send(method: 'net_version', params: []): Promise<string>;
6
6
  send(method: 'eth_chainId', params: []): Promise<string>;
7
7
  send(method: 'eth_getCode', params: [string, string]): Promise<string>;
8
+ send(method: 'eth_call', params: unknown[]): Promise<string>;
8
9
  send(method: 'eth_getStorageAt', params: [string, string, string]): Promise<string>;
9
10
  send(method: 'eth_getTransactionByHash', params: [string]): Promise<null | EthereumTransaction>;
10
11
  send(method: 'eth_getTransactionReceipt', params: [string]): Promise<null | EthereumTransactionReceipt>;
@@ -53,6 +54,21 @@ export async function getCode(provider: EthereumProvider, address: string, block
53
54
  return provider.send('eth_getCode', [address, block]);
54
55
  }
55
56
 
57
+ export async function call(
58
+ provider: EthereumProvider,
59
+ address: string,
60
+ data: string,
61
+ block = 'latest',
62
+ ): Promise<string> {
63
+ return provider.send('eth_call', [
64
+ {
65
+ to: address,
66
+ data: data,
67
+ },
68
+ block,
69
+ ]);
70
+ }
71
+
56
72
  export async function hasCode(provider: EthereumProvider, address: string, block?: string): Promise<boolean> {
57
73
  const code = await getCode(provider, address, block);
58
74
  return code !== '0x';
@@ -0,0 +1,59 @@
1
+ import { EthereumProvider } from './provider';
2
+ import { inferProxyKind, ValidationData, ValidationOptions } from './validate';
3
+ import { Manifest, DeploymentNotFound, ProxyDeployment } from './manifest';
4
+ import { isBeaconProxy } from './eip-1967-type';
5
+ import { Version } from './version';
6
+ import { BeaconProxyUnsupportedError } from './usage-error';
7
+
8
+ export async function setProxyKind(
9
+ provider: EthereumProvider,
10
+ proxyAddress: string,
11
+ opts: ValidationOptions,
12
+ ): Promise<ProxyDeployment['kind']> {
13
+ const manifest = await Manifest.forNetwork(provider);
14
+
15
+ const manifestDeployment = await manifest.getProxyFromAddress(proxyAddress).catch(e => {
16
+ if (e instanceof DeploymentNotFound) {
17
+ return undefined;
18
+ } else {
19
+ throw e;
20
+ }
21
+ });
22
+
23
+ if (opts.kind === undefined) {
24
+ opts.kind = manifestDeployment?.kind ?? 'transparent';
25
+ } else if (manifestDeployment && opts.kind !== manifestDeployment.kind) {
26
+ throw new Error(`Requested an upgrade of kind ${opts.kind} but proxy is ${manifestDeployment.kind}`);
27
+ }
28
+
29
+ return opts.kind;
30
+ }
31
+
32
+ /**
33
+ * Processes opts.kind when deploying the implementation for a UUPS or Transparent proxy.
34
+ *
35
+ * @throws {BeaconProxyUnsupportedError} If this function is called for a Beacon proxy.
36
+ */
37
+ export async function processProxyKind(
38
+ provider: EthereumProvider,
39
+ proxyAddress: string | undefined,
40
+ opts: ValidationOptions,
41
+ data: ValidationData,
42
+ version: Version,
43
+ ) {
44
+ if (opts.kind === undefined) {
45
+ if (proxyAddress !== undefined && (await isBeaconProxy(provider, proxyAddress))) {
46
+ opts.kind = 'beacon';
47
+ } else {
48
+ opts.kind = inferProxyKind(data, version);
49
+ }
50
+ }
51
+
52
+ if (proxyAddress !== undefined) {
53
+ await setProxyKind(provider, proxyAddress, opts);
54
+ }
55
+
56
+ if (opts.kind === 'beacon') {
57
+ throw new BeaconProxyUnsupportedError();
58
+ }
59
+ }
@@ -0,0 +1,40 @@
1
+ import _test, { TestInterface } from 'ava';
2
+ import { artifacts } from 'hardhat';
3
+
4
+ import { SolcInput, SolcOutput } from './solc-api';
5
+ import { UpgradeableContract } from './standalone';
6
+
7
+ interface Context {
8
+ solcInput: SolcInput;
9
+ solcOutput: SolcOutput;
10
+ }
11
+
12
+ const test = _test as TestInterface<Context>;
13
+
14
+ test.before(async t => {
15
+ const buildInfo = await artifacts.getBuildInfo('contracts/test/Standalone.sol:StandaloneV1');
16
+ if (buildInfo === undefined) {
17
+ throw new Error('Build info not found');
18
+ }
19
+ t.context.solcInput = buildInfo.input;
20
+ t.context.solcOutput = buildInfo.output;
21
+ });
22
+
23
+ test('reports unsafe operation', t => {
24
+ const impl = new UpgradeableContract('StandaloneV1', t.context.solcInput, t.context.solcOutput);
25
+ const report = impl.getErrorReport();
26
+ t.false(report.ok);
27
+ t.true(report.errors[0].kind === 'delegatecall');
28
+ });
29
+
30
+ test('reports storage upgrade errors', t => {
31
+ const v1 = new UpgradeableContract('StandaloneV1', t.context.solcInput, t.context.solcOutput);
32
+
33
+ const v2Good = new UpgradeableContract('StandaloneV2Good', t.context.solcInput, t.context.solcOutput);
34
+ const goodReport = v1.getStorageUpgradeReport(v2Good);
35
+ t.true(goodReport.ok);
36
+
37
+ const v2Bad = new UpgradeableContract('StandaloneV2Bad', t.context.solcInput, t.context.solcOutput);
38
+ const badReport = v1.getStorageUpgradeReport(v2Bad);
39
+ t.false(badReport.ok);
40
+ });
@@ -0,0 +1,41 @@
1
+ import { SolcOutput, SolcInput } from './solc-api';
2
+ import { solcInputOutputDecoder } from './src-decoder';
3
+ import {
4
+ getContractVersion,
5
+ getErrors,
6
+ getStorageLayout,
7
+ UpgradeableContractErrorReport,
8
+ validate,
9
+ ValidationOptions,
10
+ withValidationDefaults,
11
+ } from './validate';
12
+ import { getStorageUpgradeReport, StorageLayout } from './storage';
13
+ import { Version } from './version';
14
+ import { ValidationError } from './validate/run';
15
+
16
+ export interface Report {
17
+ ok: boolean;
18
+ explain(color?: boolean): string;
19
+ }
20
+
21
+ export class UpgradeableContract {
22
+ readonly version: Version;
23
+ readonly errors: ValidationError[];
24
+ readonly layout: StorageLayout;
25
+
26
+ constructor(readonly name: string, solcInput: SolcInput, solcOutput: SolcOutput, opts: ValidationOptions = {}) {
27
+ const decodeSrc = solcInputOutputDecoder(solcInput, solcOutput);
28
+ const validation = validate(solcOutput, decodeSrc);
29
+ this.version = getContractVersion(validation, name);
30
+ this.errors = getErrors(validation, this.version, opts);
31
+ this.layout = getStorageLayout(validation, this.version);
32
+ }
33
+
34
+ getErrorReport() {
35
+ return new UpgradeableContractErrorReport(this.errors);
36
+ }
37
+
38
+ getStorageUpgradeReport(newVersion: UpgradeableContract, opts: ValidationOptions = {}) {
39
+ return getStorageUpgradeReport(this.layout, newVersion.layout, withValidationDefaults(opts));
40
+ }
41
+ }
@@ -29,6 +29,18 @@ export function assertStorageUpgradeSafe(
29
29
  opts = withValidationDefaults({ unsafeAllowCustomTypes });
30
30
  }
31
31
 
32
+ const report = getStorageUpgradeReport(original, updated, opts);
33
+
34
+ if (!report.pass) {
35
+ throw new StorageUpgradeErrors(report);
36
+ }
37
+ }
38
+
39
+ export function getStorageUpgradeReport(
40
+ original: StorageLayout,
41
+ updated: StorageLayout,
42
+ opts: Required<ValidationOptions>,
43
+ ): LayoutCompatibilityReport {
32
44
  const originalDetailed = getDetailedLayout(original);
33
45
  const updatedDetailed = getDetailedLayout(updated);
34
46
  const comparator = new StorageLayoutComparator(opts.unsafeAllowCustomTypes, opts.unsafeAllowRenames);
@@ -43,12 +55,10 @@ export function assertStorageUpgradeSafe(
43
55
  logNote(`\`unsafeAllowCustomTypes\` is no longer necessary. Structs are enums are automatically checked.\n`);
44
56
  }
45
57
 
46
- if (!report.pass) {
47
- throw new StorageUpgradeErrors(report);
48
- }
58
+ return report;
49
59
  }
50
60
 
51
- class StorageUpgradeErrors extends UpgradesError {
61
+ export class StorageUpgradeErrors extends UpgradesError {
52
62
  constructor(readonly report: LayoutCompatibilityReport) {
53
63
  super(`New storage layout is incompatible`, () => report.explain());
54
64
  }
@@ -1,4 +1,4 @@
1
- import chalk from 'chalk';
1
+ import _chalk from 'chalk';
2
2
 
3
3
  import type { BasicOperation } from '../levenshtein';
4
4
  import type { ParsedTypeDetailed } from './layout';
@@ -8,13 +8,18 @@ import { indent } from '../utils/indent';
8
8
  import { assert } from '../utils/assert';
9
9
 
10
10
  export class LayoutCompatibilityReport {
11
- readonly pass: boolean;
11
+ constructor(readonly ops: StorageOperation<StorageItem>[]) {}
12
12
 
13
- constructor(readonly ops: StorageOperation<StorageItem>[]) {
14
- this.pass = ops.length === 0;
13
+ get ok(): boolean {
14
+ return this.pass;
15
15
  }
16
16
 
17
- explain(): string {
17
+ get pass(): boolean {
18
+ return this.ops.length === 0;
19
+ }
20
+
21
+ explain(color = true): string {
22
+ const chalk = new _chalk.Instance({ level: color && _chalk.supportsColor ? _chalk.supportsColor.level : 0 });
18
23
  const res = [];
19
24
 
20
25
  for (const op of this.ops) {
@@ -0,0 +1,68 @@
1
+ import { EthereumProvider, getBeaconAddress, isBeaconProxy, isTransparentOrUUPSProxy, UpgradesError } from '.';
2
+
3
+ export class BeaconProxyUnsupportedError extends UpgradesError {
4
+ constructor() {
5
+ super(
6
+ 'Beacon proxies are not supported with the current function.',
7
+ () => 'Use deployBeacon(), deployBeaconProxy(), or upgradeBeacon() instead.',
8
+ );
9
+ }
10
+ }
11
+
12
+ export class DeployBeaconProxyKindError extends UpgradesError {
13
+ constructor(kind: string) {
14
+ super(
15
+ `Unsupported proxy kind '${kind}'`,
16
+ () => `deployBeaconProxy() is only supported with proxy kind undefined or 'beacon'`,
17
+ );
18
+ }
19
+ }
20
+
21
+ export class DeployBeaconProxyUnsupportedError extends UpgradesError {
22
+ constructor(beaconAddress: string) {
23
+ super(`Contract at ${beaconAddress} doesn't look like a beacon`, () => 'Deploy a beacon using deployBeacon().');
24
+ }
25
+ }
26
+
27
+ export class DeployBeaconProxyImplUnknownError extends UpgradesError {
28
+ constructor(implAddress: string) {
29
+ super(
30
+ `Beacon's current implementation at ${implAddress} is unknown`,
31
+ () => `Call deployBeaconProxy() with the implementation option providing the beacon's current implementation.`,
32
+ );
33
+ }
34
+ }
35
+
36
+ export class LoadProxyUnsupportedError extends UpgradesError {
37
+ constructor(proxyAddress: string) {
38
+ super(
39
+ `Contract at ${proxyAddress} doesn't look like a supported proxy`,
40
+ () => 'Only transparent, UUPS, or beacon proxies can be loaded with the loadProxy() function.',
41
+ );
42
+ }
43
+ }
44
+
45
+ export class PrepareUpgradeUnsupportedError extends UpgradesError {
46
+ constructor(proxyOrBeaconAddress: string) {
47
+ super(
48
+ `Contract at address ${proxyOrBeaconAddress} doesn't look like a supported proxy or beacon`,
49
+ () => `Only transparent, UUPS, or beacon proxies or beacons can be used with the prepareUpgrade() function.`,
50
+ );
51
+ }
52
+ }
53
+
54
+ export async function assertNotProxy(provider: EthereumProvider, address: string) {
55
+ if (await isTransparentOrUUPSProxy(provider, address)) {
56
+ throw new UpgradesError(
57
+ 'Address is a transparent or UUPS proxy which cannot be upgraded using upgradeBeacon().',
58
+ () => 'Use upgradeProxy() instead.',
59
+ );
60
+ } else if (await isBeaconProxy(provider, address)) {
61
+ const beaconAddress = await getBeaconAddress(provider, address);
62
+ throw new UpgradesError(
63
+ 'Address is a beacon proxy which cannot be upgraded directly.',
64
+ () =>
65
+ `upgradeBeacon() must be called with a beacon address, not a beacon proxy address. Call upgradeBeacon() on the beacon address ${beaconAddress} instead.`,
66
+ );
67
+ }
68
+ }
@@ -0,0 +1,16 @@
1
+ import { toChecksumAddress } from 'ethereumjs-util';
2
+
3
+ /**
4
+ * Parses an address from a hex string which may come from storage or a returned address via eth_call.
5
+ *
6
+ * @param addressString The address hex string.
7
+ * @returns The parsed checksum address, or undefined if the input string is not an address.
8
+ */
9
+ export function parseAddress(addressString: string): string | undefined {
10
+ const buf = Buffer.from(addressString.replace(/^0x/, ''), 'hex');
11
+ if (!buf.slice(0, 12).equals(Buffer.alloc(12, 0))) {
12
+ return undefined;
13
+ }
14
+ const address = '0x' + buf.toString('hex', 12, 32); // grab the last 20 bytes
15
+ return toChecksumAddress(address);
16
+ }
@@ -1,3 +1,3 @@
1
- export function makeNonEnumerable<O extends any>(obj: O, key: keyof O): void {
1
+ export function makeNonEnumerable<O>(obj: O, key: keyof O): void {
2
2
  Object.defineProperty(obj, key, { enumerable: false });
3
3
  }
@@ -1,73 +1,11 @@
1
- import chalk from 'chalk';
2
-
3
1
  import { ValidationError } from './run';
4
- import { UpgradesError, ErrorDescriptions } from '../error';
2
+ import { UpgradesError } from '../error';
3
+ import { UpgradeableContractErrorReport } from './report';
5
4
 
6
5
  export class ValidationErrors extends UpgradesError {
7
6
  constructor(contractName: string, readonly errors: ValidationError[]) {
8
- super(`Contract \`${contractName}\` is not upgrade safe`, () => {
9
- return errors.map(describeError).join('\n\n');
10
- });
11
- }
12
- }
13
-
14
- const errorInfo: ErrorDescriptions<ValidationError> = {
15
- constructor: {
16
- msg: e => `Contract \`${e.contract}\` has a constructor`,
17
- hint: () => 'Define an initializer instead',
18
- link: 'https://zpl.in/upgrades/error-001',
19
- },
20
- delegatecall: {
21
- msg: () => `Use of delegatecall is not allowed`,
22
- link: 'https://zpl.in/upgrades/error-002',
23
- },
24
- selfdestruct: {
25
- msg: () => `Use of selfdestruct is not allowed`,
26
- link: 'https://zpl.in/upgrades/error-003',
27
- },
28
- 'state-variable-assignment': {
29
- msg: e => `Variable \`${e.name}\` is assigned an initial value`,
30
- hint: () => 'Move the assignment to the initializer',
31
- link: 'https://zpl.in/upgrades/error-004',
32
- },
33
- 'state-variable-immutable': {
34
- msg: e => `Variable \`${e.name}\` is immutable`,
35
- hint: () => `Use a constant or mutable variable instead`,
36
- link: 'https://zpl.in/upgrades/error-005',
37
- },
38
- 'external-library-linking': {
39
- msg: e => `Linking external libraries like \`${e.name}\` is not yet supported`,
40
- hint: () =>
41
- `Use libraries with internal functions only, or skip this check with the \`unsafeAllowLinkedLibraries\` flag \n` +
42
- ` if you have manually checked that the libraries are upgrade safe`,
43
- link: 'https://zpl.in/upgrades/error-006',
44
- },
45
- 'struct-definition': {
46
- msg: e => `Structs like \`${e.name}\` are supported in the latest version of the plugin`,
47
- hint: () => `Update your dependency and run again`,
48
- },
49
- 'enum-definition': {
50
- msg: e => `Enums like \`${e.name}\` are supported in the latest version of the plugin`,
51
- hint: () => `Update your dependency and run again`,
52
- },
53
- 'missing-public-upgradeto': {
54
- msg: () => `Implementation is missing a public \`upgradeTo(address)\` function`,
55
- hint: () =>
56
- `Inherit UUPSUpgradeable to include this function in your contract\n` +
57
- ` @openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol`,
58
- link: 'https://zpl.in/upgrades/error-008',
59
- },
60
- };
61
-
62
- function describeError(e: ValidationError): string {
63
- const info = errorInfo[e.kind];
64
- const log = [chalk.bold(e.src) + ': ' + info.msg(e as any)];
65
- const hint = info.hint?.(e as any);
66
- if (hint) {
67
- log.push(hint);
68
- }
69
- if (info.link) {
70
- log.push(chalk.dim(info.link));
7
+ super(`Contract \`${contractName}\` is not upgrade safe`, () =>
8
+ new UpgradeableContractErrorReport(errors).explain(),
9
+ );
71
10
  }
72
- return log.join('\n ');
73
11
  }
@@ -11,7 +11,9 @@ export {
11
11
  getUnlinkedBytecode,
12
12
  getErrors,
13
13
  isUpgradeSafe,
14
+ inferProxyKind,
14
15
  } from './query';
16
+ export { UpgradeableContractErrorReport } from './report';
15
17
 
16
18
  // Backwards compatibility
17
19
  export { silenceWarnings } from '../utils/log';
@@ -69,7 +69,7 @@ export function processExceptions(
69
69
  ): ValidationError[] {
70
70
  const { unsafeAllow } = withValidationDefaults(opts);
71
71
 
72
- if (opts.kind === 'transparent') {
72
+ if (opts.kind === 'transparent' || opts.kind === 'beacon') {
73
73
  errors = errors.filter(error => error.kind !== 'missing-public-upgradeto');
74
74
  }
75
75
 
@@ -5,13 +5,15 @@ import { unlinkBytecode } from '../link-refs';
5
5
  import { ValidationOptions, processExceptions } from './overrides';
6
6
  import { ValidationErrors } from './error';
7
7
  import { ValidationData, normalizeValidationData } from './data';
8
+ import { ProxyDeployment } from '../manifest';
9
+
10
+ const upgradeToSignature = 'upgradeTo(address)';
8
11
 
9
12
  export function assertUpgradeSafe(data: ValidationData, version: Version, opts: ValidationOptions): void {
10
13
  const dataV3 = normalizeValidationData(data);
11
14
  const [contractName] = getContractNameAndRunValidation(dataV3, version);
12
15
 
13
- let errors = getErrors(dataV3, version);
14
- errors = processExceptions(contractName, errors, opts);
16
+ const errors = getErrors(dataV3, version, opts);
15
17
 
16
18
  if (errors.length > 0) {
17
19
  throw new ValidationErrors(contractName, errors);
@@ -100,23 +102,28 @@ export function getUnlinkedBytecode(data: ValidationData, bytecode: string): str
100
102
  return bytecode;
101
103
  }
102
104
 
103
- export function getErrors(data: ValidationData, version: Version): ValidationError[] {
105
+ export function getErrors(data: ValidationData, version: Version, opts: ValidationOptions = {}): ValidationError[] {
104
106
  const dataV3 = normalizeValidationData(data);
105
107
  const [contractName, runValidation] = getContractNameAndRunValidation(dataV3, version);
106
108
  const c = runValidation[contractName];
107
109
 
108
110
  const errors = getUsedContractsAndLibraries(contractName, runValidation).flatMap(name => runValidation[name].errors);
109
111
 
110
- const selfAndInheritedMethods = c.methods.concat(...c.inherit.map(name => runValidation[name].methods));
112
+ const selfAndInheritedMethods = getAllMethods(runValidation, contractName);
111
113
 
112
- if (!selfAndInheritedMethods.includes('upgradeTo(address)')) {
114
+ if (!selfAndInheritedMethods.includes(upgradeToSignature)) {
113
115
  errors.push({
114
116
  src: c.src,
115
117
  kind: 'missing-public-upgradeto',
116
118
  });
117
119
  }
118
120
 
119
- return errors;
121
+ return processExceptions(contractName, errors, opts);
122
+ }
123
+
124
+ function getAllMethods(runValidation: ValidationRunData, contractName: string): string[] {
125
+ const c = runValidation[contractName];
126
+ return c.methods.concat(...c.inherit.map(name => runValidation[name].methods));
120
127
  }
121
128
 
122
129
  function getUsedContractsAndLibraries(contractName: string, runValidation: ValidationRunData) {
@@ -136,3 +143,14 @@ export function isUpgradeSafe(data: ValidationData, version: Version): boolean {
136
143
  const dataV3 = normalizeValidationData(data);
137
144
  return getErrors(dataV3, version).length == 0;
138
145
  }
146
+
147
+ export function inferProxyKind(data: ValidationData, version: Version): ProxyDeployment['kind'] {
148
+ const dataV3 = normalizeValidationData(data);
149
+ const [contractName, runValidation] = getContractNameAndRunValidation(dataV3, version);
150
+ const methods = getAllMethods(runValidation, contractName);
151
+ if (methods.includes(upgradeToSignature)) {
152
+ return 'uups';
153
+ } else {
154
+ return 'transparent';
155
+ }
156
+ }