@openzeppelin/upgrades-core 1.11.1 → 1.13.1
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.
- package/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.dbg.json +1 -1
- package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.dbg.json +1 -1
- package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.dbg.json +1 -1
- package/dist/add-proxy-to-manifest.d.ts +3 -0
- package/dist/add-proxy-to-manifest.d.ts.map +1 -0
- package/dist/add-proxy-to-manifest.js +15 -0
- package/dist/add-proxy-to-manifest.js.map +1 -0
- package/dist/deployment.d.ts +35 -4
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +85 -28
- package/dist/deployment.js.map +1 -1
- package/dist/deployment.test.js +23 -0
- package/dist/deployment.test.js.map +1 -1
- package/dist/eip-1967-type.d.ts +1 -0
- package/dist/eip-1967-type.d.ts.map +1 -1
- package/dist/eip-1967-type.js +6 -1
- package/dist/eip-1967-type.js.map +1 -1
- package/dist/eip-1967.d.ts +1 -0
- package/dist/eip-1967.d.ts.map +1 -1
- package/dist/eip-1967.js +3 -2
- package/dist/eip-1967.js.map +1 -1
- package/dist/impl-address.js +1 -1
- package/dist/impl-address.js.map +1 -1
- package/dist/impl-store.d.ts +24 -3
- package/dist/impl-store.d.ts.map +1 -1
- package/dist/impl-store.js +77 -22
- package/dist/impl-store.js.map +1 -1
- package/dist/impl-store.test.js +24 -0
- package/dist/impl-store.test.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/manifest-storage-layout.d.ts.map +1 -1
- package/dist/manifest-storage-layout.js +3 -2
- package/dist/manifest-storage-layout.js.map +1 -1
- package/dist/manifest-storage-layout.test.js.map +1 -1
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +6 -4
- package/dist/manifest.js.map +1 -1
- package/dist/manifest.test.js +32 -0
- package/dist/manifest.test.js.map +1 -1
- package/dist/provider.d.ts +1 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +6 -2
- package/dist/provider.js.map +1 -1
- package/dist/proxy-kind.d.ts +9 -0
- package/dist/proxy-kind.d.ts.map +1 -1
- package/dist/proxy-kind.js +27 -1
- package/dist/proxy-kind.js.map +1 -1
- package/dist/scripts/migrate-oz-cli-project.test.js +1 -1
- package/dist/scripts/migrate-oz-cli-project.test.js.map +1 -1
- package/dist/solc-api.d.ts +5 -0
- package/dist/solc-api.d.ts.map +1 -1
- package/dist/standalone.test.js.map +1 -1
- package/dist/storage/extract.d.ts +1 -1
- package/dist/storage/extract.d.ts.map +1 -1
- package/dist/storage/extract.js +72 -38
- package/dist/storage/extract.js.map +1 -1
- package/dist/storage/layout.d.ts +4 -0
- package/dist/storage/layout.d.ts.map +1 -1
- package/dist/storage/layout.js.map +1 -1
- package/dist/storage/report.test.js.map +1 -1
- package/dist/storage.test.js +10 -2
- package/dist/storage.test.js.map +1 -1
- package/dist/usage-error.d.ts +3 -0
- package/dist/usage-error.d.ts.map +1 -1
- package/dist/usage-error.js +7 -1
- package/dist/usage-error.js.map +1 -1
- package/dist/validate/query.d.ts.map +1 -1
- package/dist/validate/query.js +10 -5
- package/dist/validate/query.js.map +1 -1
- package/dist/validate/run.d.ts.map +1 -1
- package/dist/validate/run.js +1 -1
- package/dist/validate/run.js.map +1 -1
- package/dist/validate.test.js.map +1 -1
- package/package.json +4 -4
- package/src/add-proxy-to-manifest.ts +12 -0
- package/src/deployment.test.ts +25 -0
- package/src/deployment.ts +124 -27
- package/src/eip-1967-type.ts +7 -0
- package/src/eip-1967.ts +4 -3
- package/src/impl-address.ts +1 -1
- package/src/impl-store.test.ts +33 -1
- package/src/impl-store.ts +91 -18
- package/src/index.ts +22 -4
- package/src/manifest-storage-layout.test.ts +3 -2
- package/src/manifest-storage-layout.ts +8 -3
- package/src/manifest.test.ts +34 -1
- package/src/manifest.ts +9 -4
- package/src/provider.ts +5 -1
- package/src/proxy-kind.ts +24 -1
- package/src/scripts/migrate-oz-cli-project.test.ts +1 -1
- package/src/scripts/migrate-oz-cli-project.test.ts.snap +0 -0
- package/src/solc-api.ts +5 -0
- package/src/standalone.test.ts +2 -2
- package/src/storage/extract.ts +85 -52
- package/src/storage/layout.ts +4 -0
- package/src/storage/report.test.ts +2 -2
- package/src/storage/report.test.ts.snap +0 -0
- package/src/storage.test.ts +14 -4
- package/src/storage.test.ts.md +202 -109
- package/src/storage.test.ts.snap +0 -0
- package/src/usage-error.ts +9 -0
- package/src/utils/parse-type-id.test.ts.md +106 -106
- package/src/utils/parse-type-id.test.ts.snap +0 -0
- package/src/validate/query.ts +9 -5
- package/src/validate/run.ts +6 -1
- package/src/validate.test.ts +2 -2
package/src/index.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
export * from './validate';
|
|
2
|
-
export
|
|
2
|
+
export { fetchOrDeploy, fetchOrDeployAdmin } from './impl-store';
|
|
3
3
|
export * from './version';
|
|
4
|
-
export * from './manifest';
|
|
5
4
|
export * from './storage';
|
|
6
|
-
export
|
|
5
|
+
export {
|
|
6
|
+
EIP1967BeaconNotFound,
|
|
7
|
+
EIP1967ImplementationNotFound,
|
|
8
|
+
getAdminAddress,
|
|
9
|
+
getBeaconAddress,
|
|
10
|
+
getImplementationAddress,
|
|
11
|
+
toEip1967Hash,
|
|
12
|
+
toFallbackEip1967Hash,
|
|
13
|
+
} from './eip-1967';
|
|
7
14
|
export * from './provider';
|
|
8
15
|
export * from './src-decoder';
|
|
9
16
|
export * from './solc-api';
|
|
@@ -11,18 +18,28 @@ export * from './deployment';
|
|
|
11
18
|
export * from './link-refs';
|
|
12
19
|
export * from './error';
|
|
13
20
|
|
|
21
|
+
export {
|
|
22
|
+
ManifestData,
|
|
23
|
+
ImplDeployment,
|
|
24
|
+
ProxyDeployment,
|
|
25
|
+
Manifest,
|
|
26
|
+
migrateManifest,
|
|
27
|
+
DeploymentNotFound,
|
|
28
|
+
} from './manifest';
|
|
29
|
+
|
|
14
30
|
export { getStorageLayoutForAddress } from './manifest-storage-layout';
|
|
15
31
|
|
|
16
32
|
export * from './scripts/migrate-oz-cli-project';
|
|
17
33
|
|
|
18
34
|
export { logWarning } from './utils/log';
|
|
19
|
-
export { setProxyKind, processProxyKind } from './proxy-kind';
|
|
35
|
+
export { setProxyKind, processProxyKind, detectProxyKind } from './proxy-kind';
|
|
20
36
|
|
|
21
37
|
export { UpgradeableContract } from './standalone';
|
|
22
38
|
|
|
23
39
|
export { isTransparentOrUUPSProxy, isBeaconProxy } from './eip-1967-type';
|
|
24
40
|
export { getImplementationAddressFromBeacon, getImplementationAddressFromProxy } from './impl-address';
|
|
25
41
|
export { isBeacon } from './beacon';
|
|
42
|
+
export { addProxyToManifest } from './add-proxy-to-manifest';
|
|
26
43
|
|
|
27
44
|
export {
|
|
28
45
|
BeaconProxyUnsupportedError,
|
|
@@ -31,5 +48,6 @@ export {
|
|
|
31
48
|
DeployBeaconProxyUnsupportedError,
|
|
32
49
|
DeployBeaconProxyImplUnknownError,
|
|
33
50
|
DeployBeaconProxyKindError,
|
|
51
|
+
ForceImportUnsupportedError,
|
|
34
52
|
assertNotProxy,
|
|
35
53
|
} from './usage-error';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import _test, {
|
|
1
|
+
import _test, { TestFn } from 'ava';
|
|
2
2
|
import assert from 'assert';
|
|
3
3
|
|
|
4
4
|
import { artifacts } from 'hardhat';
|
|
@@ -17,7 +17,7 @@ interface Context {
|
|
|
17
17
|
validationData: ValidationData;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const test = _test as
|
|
20
|
+
const test = _test as TestFn<Context>;
|
|
21
21
|
|
|
22
22
|
test.before(async t => {
|
|
23
23
|
const buildInfo = await artifacts.getBuildInfo('contracts/test/ManifestMigrate.sol:ManifestMigrateUnique');
|
|
@@ -44,6 +44,7 @@ test('getStorageLayoutForAddress - update layout', async t => {
|
|
|
44
44
|
},
|
|
45
45
|
proxies: [],
|
|
46
46
|
});
|
|
47
|
+
|
|
47
48
|
const layout = await getStorageLayoutForAddress(manifest, t.context.validationData, address);
|
|
48
49
|
t.deepEqual(layout, updatedLayout);
|
|
49
50
|
t.like(manifest.data, {
|
|
@@ -5,6 +5,7 @@ import { ValidationData } from './validate/data';
|
|
|
5
5
|
import { findVersionWithoutMetadataMatches, unfoldStorageLayout } from './validate/query';
|
|
6
6
|
import { stabilizeTypeIdentifier } from './utils/type-id';
|
|
7
7
|
import { DeepArray, deepEqual } from './utils/deep-array';
|
|
8
|
+
import { UpgradesError } from './error';
|
|
8
9
|
|
|
9
10
|
export async function getStorageLayoutForAddress(
|
|
10
11
|
manifest: Manifest,
|
|
@@ -12,13 +13,17 @@ export async function getStorageLayoutForAddress(
|
|
|
12
13
|
implAddress: string,
|
|
13
14
|
): Promise<StorageLayout> {
|
|
14
15
|
const data = await manifest.read();
|
|
15
|
-
const versionWithoutMetadata = Object.keys(data.impls).find(
|
|
16
|
+
const versionWithoutMetadata = Object.keys(data.impls).find(
|
|
17
|
+
v => data.impls[v]?.address === implAddress || data.impls[v]?.allAddresses?.includes(implAddress),
|
|
18
|
+
);
|
|
16
19
|
if (versionWithoutMetadata === undefined) {
|
|
17
|
-
throw new
|
|
20
|
+
throw new UpgradesError(
|
|
21
|
+
`Deployment at address ${implAddress} is not registered`,
|
|
22
|
+
() => 'To register a previously deployed proxy for upgrading, use the forceImport function.',
|
|
23
|
+
);
|
|
18
24
|
}
|
|
19
25
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
20
26
|
const { layout } = data.impls[versionWithoutMetadata]!;
|
|
21
|
-
|
|
22
27
|
if (isCurrentLayoutVersion(layout)) {
|
|
23
28
|
return layout;
|
|
24
29
|
} else {
|
package/src/manifest.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import test from 'ava';
|
|
2
|
-
import { Manifest } from './manifest';
|
|
2
|
+
import { Manifest, ManifestData, normalizeManifestData } from './manifest';
|
|
3
3
|
|
|
4
4
|
test('manifest name for a known network', t => {
|
|
5
5
|
const manifest = new Manifest(1);
|
|
@@ -11,3 +11,36 @@ test('manifest name for an unknown network', t => {
|
|
|
11
11
|
const manifest = new Manifest(id);
|
|
12
12
|
t.is(manifest.file, `.openzeppelin/unknown-${id}.json`);
|
|
13
13
|
});
|
|
14
|
+
|
|
15
|
+
test('normalize manifest', t => {
|
|
16
|
+
const deployment = {
|
|
17
|
+
address: '0x1234',
|
|
18
|
+
txHash: '0x1234',
|
|
19
|
+
kind: 'uups' as const,
|
|
20
|
+
layout: { types: {}, storage: [] },
|
|
21
|
+
deployTransaction: {},
|
|
22
|
+
};
|
|
23
|
+
const input: ManifestData = {
|
|
24
|
+
manifestVersion: '3.0',
|
|
25
|
+
admin: deployment,
|
|
26
|
+
impls: { a: deployment },
|
|
27
|
+
proxies: [deployment],
|
|
28
|
+
};
|
|
29
|
+
const norm = normalizeManifestData(input);
|
|
30
|
+
t.like(norm.admin, {
|
|
31
|
+
...deployment,
|
|
32
|
+
kind: undefined,
|
|
33
|
+
layout: undefined,
|
|
34
|
+
deployTransaction: undefined,
|
|
35
|
+
});
|
|
36
|
+
t.like(norm.impls.a, {
|
|
37
|
+
...deployment,
|
|
38
|
+
kind: undefined,
|
|
39
|
+
deployTransaction: undefined,
|
|
40
|
+
});
|
|
41
|
+
t.like(norm.proxies[0], {
|
|
42
|
+
...deployment,
|
|
43
|
+
layout: undefined,
|
|
44
|
+
deployTransaction: undefined,
|
|
45
|
+
});
|
|
46
|
+
});
|
package/src/manifest.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface ManifestData {
|
|
|
22
22
|
|
|
23
23
|
export interface ImplDeployment extends Deployment {
|
|
24
24
|
layout: StorageLayout;
|
|
25
|
+
allAddresses?: string[];
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export interface ProxyDeployment extends Deployment {
|
|
@@ -57,7 +58,10 @@ export class Manifest {
|
|
|
57
58
|
|
|
58
59
|
async getDeploymentFromAddress(address: string): Promise<ImplDeployment> {
|
|
59
60
|
const data = await this.read();
|
|
60
|
-
const deployment = Object.values(data.impls).find(
|
|
61
|
+
const deployment = Object.values(data.impls).find(
|
|
62
|
+
d => d?.address === address || d?.allAddresses?.includes(address),
|
|
63
|
+
);
|
|
64
|
+
|
|
61
65
|
if (deployment === undefined) {
|
|
62
66
|
throw new DeploymentNotFound(`Deployment at address ${address} is not registered`);
|
|
63
67
|
}
|
|
@@ -160,11 +164,12 @@ export function migrateManifest(data: ManifestData): ManifestData {
|
|
|
160
164
|
|
|
161
165
|
export class DeploymentNotFound extends Error {}
|
|
162
166
|
|
|
163
|
-
function normalizeManifestData(input: ManifestData): ManifestData {
|
|
167
|
+
export function normalizeManifestData(input: ManifestData): ManifestData {
|
|
164
168
|
return {
|
|
165
|
-
|
|
169
|
+
manifestVersion: input.manifestVersion,
|
|
170
|
+
admin: input.admin && normalizeDeployment(input.admin),
|
|
166
171
|
proxies: input.proxies.map(p => normalizeDeployment(p, ['kind'])),
|
|
167
|
-
impls: mapValues(input.impls, i => i && normalizeDeployment(i, ['layout'])),
|
|
172
|
+
impls: mapValues(input.impls, i => i && normalizeDeployment(i, ['layout', 'allAddresses'])),
|
|
168
173
|
};
|
|
169
174
|
}
|
|
170
175
|
|
package/src/provider.ts
CHANGED
|
@@ -71,7 +71,11 @@ export async function call(
|
|
|
71
71
|
|
|
72
72
|
export async function hasCode(provider: EthereumProvider, address: string, block?: string): Promise<boolean> {
|
|
73
73
|
const code = await getCode(provider, address, block);
|
|
74
|
-
return code
|
|
74
|
+
return !isEmpty(code);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isEmpty(code: string) {
|
|
78
|
+
return code.replace(/^0x/, '') === '';
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
export async function getTransactionByHash(
|
package/src/proxy-kind.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { EthereumProvider } from './provider';
|
|
2
2
|
import { inferProxyKind, ValidationData, ValidationOptions } from './validate';
|
|
3
3
|
import { Manifest, DeploymentNotFound, ProxyDeployment } from './manifest';
|
|
4
|
-
import { isBeaconProxy } from './eip-1967-type';
|
|
4
|
+
import { isBeaconProxy, isTransparentOrUUPSProxy, isTransparentProxy } from './eip-1967-type';
|
|
5
5
|
import { Version } from './version';
|
|
6
6
|
import { BeaconProxyUnsupportedError } from './usage-error';
|
|
7
|
+
import { UpgradesError } from '.';
|
|
7
8
|
|
|
8
9
|
export async function setProxyKind(
|
|
9
10
|
provider: EthereumProvider,
|
|
@@ -57,3 +58,25 @@ export async function processProxyKind(
|
|
|
57
58
|
throw new BeaconProxyUnsupportedError();
|
|
58
59
|
}
|
|
59
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Detects the kind of proxy at an address by reading its ERC 1967 storage slots.
|
|
64
|
+
*
|
|
65
|
+
* @param provider the Ethereum provider
|
|
66
|
+
* @param proxyAddress the proxy address
|
|
67
|
+
* @returns the proxy kind
|
|
68
|
+
* @throws {UpgradesError} if the contract at address does not look like an ERC 1967 proxy
|
|
69
|
+
*/
|
|
70
|
+
export async function detectProxyKind(provider: EthereumProvider, proxyAddress: string) {
|
|
71
|
+
let importKind: ProxyDeployment['kind'];
|
|
72
|
+
if (await isTransparentProxy(provider, proxyAddress)) {
|
|
73
|
+
importKind = 'transparent';
|
|
74
|
+
} else if (await isTransparentOrUUPSProxy(provider, proxyAddress)) {
|
|
75
|
+
importKind = 'uups';
|
|
76
|
+
} else if (await isBeaconProxy(provider, proxyAddress)) {
|
|
77
|
+
importKind = 'beacon';
|
|
78
|
+
} else {
|
|
79
|
+
throw new UpgradesError(`Contract at ${proxyAddress} doesn't look like an ERC 1967 proxy`);
|
|
80
|
+
}
|
|
81
|
+
return importKind;
|
|
82
|
+
}
|
|
@@ -50,7 +50,7 @@ test('produces network export data', async t => {
|
|
|
50
50
|
|
|
51
51
|
test('deletes project file', async t => {
|
|
52
52
|
const error = await t.throwsAsync(() => fs.access(PROJECT_FILE));
|
|
53
|
-
t.is(error
|
|
53
|
+
t.is(error?.message, `ENOENT: no such file or directory, access '${PROJECT_FILE}'`);
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
test('export file contains networks and compiler data', async t => {
|
|
Binary file
|
package/src/solc-api.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SourceUnit } from 'solidity-ast';
|
|
2
|
+
import { StorageItem, TypeItem } from './storage';
|
|
2
3
|
|
|
3
4
|
export interface SolcOutput {
|
|
4
5
|
contracts: {
|
|
@@ -8,6 +9,10 @@ export interface SolcOutput {
|
|
|
8
9
|
bytecode: SolcBytecode;
|
|
9
10
|
methodIdentifiers?: Record<string, string>;
|
|
10
11
|
};
|
|
12
|
+
storageLayout?: {
|
|
13
|
+
storage: StorageItem[];
|
|
14
|
+
types: Record<string, TypeItem>;
|
|
15
|
+
};
|
|
11
16
|
};
|
|
12
17
|
};
|
|
13
18
|
};
|
package/src/standalone.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import _test, {
|
|
1
|
+
import _test, { TestFn } from 'ava';
|
|
2
2
|
import { artifacts } from 'hardhat';
|
|
3
3
|
|
|
4
4
|
import { SolcInput, SolcOutput } from './solc-api';
|
|
@@ -9,7 +9,7 @@ interface Context {
|
|
|
9
9
|
solcOutput: SolcOutput;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const test = _test as
|
|
12
|
+
const test = _test as TestFn<Context>;
|
|
13
13
|
|
|
14
14
|
test.before(async t => {
|
|
15
15
|
const buildInfo = await artifacts.getBuildInfo('contracts/test/Standalone.sol:StandaloneV1');
|
package/src/storage/extract.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import assert from 'assert';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ContractDefinition,
|
|
4
|
+
StructDefinition,
|
|
5
|
+
EnumDefinition,
|
|
6
|
+
TypeDescriptions,
|
|
7
|
+
VariableDeclaration,
|
|
8
|
+
} from 'solidity-ast';
|
|
3
9
|
import { isNodeType, findAll } from 'solidity-ast/utils';
|
|
4
|
-
|
|
5
10
|
import { StorageLayout, TypeItem } from './layout';
|
|
6
11
|
import { normalizeTypeIdentifier } from '../utils/type-id';
|
|
7
12
|
import { SrcDecoder } from '../src-decoder';
|
|
8
13
|
import { ASTDereferencer } from '../ast-dereferencer';
|
|
14
|
+
import { mapValues } from '../utils/map-values';
|
|
15
|
+
import { pick } from '../utils/pick';
|
|
9
16
|
|
|
10
17
|
const currentLayoutVersion = '1.1';
|
|
11
18
|
|
|
@@ -17,62 +24,46 @@ export function extractStorageLayout(
|
|
|
17
24
|
contractDef: ContractDefinition,
|
|
18
25
|
decodeSrc: SrcDecoder,
|
|
19
26
|
deref: ASTDereferencer,
|
|
27
|
+
storageLayout?: StorageLayout | undefined,
|
|
20
28
|
): StorageLayout {
|
|
21
|
-
const layout: StorageLayout = { storage: [], types: {}, layoutVersion: currentLayoutVersion };
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
const layout: StorageLayout = { storage: [], types: {}, layoutVersion: currentLayoutVersion, flat: false };
|
|
30
|
+
if (storageLayout !== undefined) {
|
|
31
|
+
layout.types = mapValues(storageLayout.types, m => {
|
|
32
|
+
return {
|
|
33
|
+
label: m.label,
|
|
34
|
+
members: m.members?.map(m => (typeof m === 'string' ? m : pick(m, ['label', 'type']))) as TypeItem['members'],
|
|
35
|
+
};
|
|
36
|
+
});
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (type in layout.types) {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let members;
|
|
57
|
-
|
|
58
|
-
if ('referencedDeclaration' in typeName && !/^t_contract\b/.test(type)) {
|
|
59
|
-
const typeDef = derefUserDefinedType(typeName.referencedDeclaration);
|
|
60
|
-
members = getTypeMembers(typeDef);
|
|
61
|
-
// Recursively look for the types referenced in this definition and add them to the queue.
|
|
62
|
-
for (const typeName of findTypeNames(typeDef)) {
|
|
63
|
-
const { typeIdentifier } = typeDescriptions(typeName);
|
|
64
|
-
if (!typeNames.has(typeIdentifier)) {
|
|
65
|
-
typeNames.set(typeIdentifier, typeName);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
layout.types[type] = { label, members };
|
|
38
|
+
for (const storage of storageLayout.storage) {
|
|
39
|
+
const origin = getOriginContract(contractDef, storage.astId, deref);
|
|
40
|
+
assert(origin, `Did not find variable declaration node for '${storage.label}'`);
|
|
41
|
+
const { varDecl, contract } = origin;
|
|
42
|
+
// Solc layout doesn't bring members for enums so we get them using the ast method
|
|
43
|
+
loadLayoutType(varDecl, layout, deref);
|
|
44
|
+
const { label, offset, slot, type } = storage;
|
|
45
|
+
const src = decodeSrc(varDecl);
|
|
46
|
+
layout.storage.push({ label, offset, slot, type, contract, src });
|
|
47
|
+
layout.flat = true;
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
for (const varDecl of contractDef.nodes) {
|
|
51
|
+
if (isNodeType('VariableDeclaration', varDecl)) {
|
|
52
|
+
if (!varDecl.constant && varDecl.mutability !== 'immutable') {
|
|
53
|
+
const type = normalizeTypeIdentifier(typeDescriptions(varDecl).typeIdentifier);
|
|
54
|
+
|
|
55
|
+
layout.storage.push({
|
|
56
|
+
contract: contractDef.name,
|
|
57
|
+
label: varDecl.name,
|
|
58
|
+
type,
|
|
59
|
+
src: decodeSrc(varDecl),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
loadLayoutType(varDecl, layout, deref);
|
|
71
63
|
}
|
|
72
64
|
}
|
|
73
65
|
}
|
|
74
66
|
}
|
|
75
|
-
|
|
76
67
|
return layout;
|
|
77
68
|
}
|
|
78
69
|
|
|
@@ -108,3 +99,45 @@ function getTypeMembers(typeDef: StructDefinition | EnumDefinition): TypeItem['m
|
|
|
108
99
|
return typeDef.members.map(m => m.name);
|
|
109
100
|
}
|
|
110
101
|
}
|
|
102
|
+
|
|
103
|
+
function getOriginContract(contract: ContractDefinition, astId: number | undefined, deref: ASTDereferencer) {
|
|
104
|
+
for (const id of contract.linearizedBaseContracts.reverse()) {
|
|
105
|
+
const parentContract = deref(['ContractDefinition'], id);
|
|
106
|
+
|
|
107
|
+
const varDecl = parentContract.nodes.find(n => n.id == astId);
|
|
108
|
+
if (varDecl && isNodeType('VariableDeclaration', varDecl)) {
|
|
109
|
+
return { varDecl, contract: parentContract.name };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function loadLayoutType(varDecl: VariableDeclaration, layout: StorageLayout, deref: ASTDereferencer) {
|
|
115
|
+
// Note: A UserDefinedTypeName can also refer to a ContractDefinition but we won't care about those.
|
|
116
|
+
const derefUserDefinedType = deref(['StructDefinition', 'EnumDefinition']);
|
|
117
|
+
|
|
118
|
+
assert(varDecl.typeName != null);
|
|
119
|
+
|
|
120
|
+
// We will recursively look for all types involved in this variable declaration in order to store their type
|
|
121
|
+
// information. We iterate over a Map that is indexed by typeIdentifier to ensure we visit each type only once.
|
|
122
|
+
// Note that there can be recursive types.
|
|
123
|
+
const typeNames = new Map([...findTypeNames(varDecl.typeName)].map(n => [typeDescriptions(n).typeIdentifier, n]));
|
|
124
|
+
|
|
125
|
+
for (const typeName of typeNames.values()) {
|
|
126
|
+
const { typeIdentifier, typeString: label } = typeDescriptions(typeName);
|
|
127
|
+
const type = normalizeTypeIdentifier(typeIdentifier);
|
|
128
|
+
layout.types[type] ??= { label };
|
|
129
|
+
|
|
130
|
+
if ('referencedDeclaration' in typeName && !/^t_contract\b/.test(type)) {
|
|
131
|
+
const typeDef = derefUserDefinedType(typeName.referencedDeclaration);
|
|
132
|
+
layout.types[type].members ??= getTypeMembers(typeDef);
|
|
133
|
+
|
|
134
|
+
// Recursively look for the types referenced in this definition and add them to the queue.
|
|
135
|
+
for (const typeName of findTypeNames(typeDef)) {
|
|
136
|
+
const { typeIdentifier } = typeDescriptions(typeName);
|
|
137
|
+
if (!typeNames.has(typeIdentifier)) {
|
|
138
|
+
typeNames.set(typeIdentifier, typeName);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
package/src/storage/layout.ts
CHANGED
|
@@ -9,13 +9,17 @@ export interface StorageLayout {
|
|
|
9
9
|
layoutVersion?: string;
|
|
10
10
|
storage: StorageItem[];
|
|
11
11
|
types: Record<string, TypeItem>;
|
|
12
|
+
flat?: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface StorageItem<Type = string> {
|
|
16
|
+
astId?: number;
|
|
15
17
|
contract: string;
|
|
16
18
|
label: string;
|
|
17
19
|
type: Type;
|
|
18
20
|
src: string;
|
|
21
|
+
offset?: number;
|
|
22
|
+
slot?: string;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export interface TypeItem<Type = string> {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import _test, {
|
|
1
|
+
import _test, { TestFn } from 'ava';
|
|
2
2
|
import { ContractDefinition } from 'solidity-ast';
|
|
3
3
|
import { findAll } from 'solidity-ast/utils';
|
|
4
4
|
import { artifacts } from 'hardhat';
|
|
@@ -13,7 +13,7 @@ interface Context {
|
|
|
13
13
|
extractStorageLayout: (contract: string) => ReturnType<typeof extractStorageLayout>;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const test = _test as
|
|
16
|
+
const test = _test as TestFn<Context>;
|
|
17
17
|
|
|
18
18
|
const dummyDecodeSrc = () => 'file.sol:1';
|
|
19
19
|
|
|
Binary file
|
package/src/storage.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import _test, {
|
|
1
|
+
import _test, { TestFn } from 'ava';
|
|
2
2
|
import { ContractDefinition } from 'solidity-ast';
|
|
3
3
|
import { findAll } from 'solidity-ast/utils';
|
|
4
4
|
import { artifacts } from 'hardhat';
|
|
@@ -14,7 +14,7 @@ interface Context {
|
|
|
14
14
|
extractStorageLayout: (contract: string) => ReturnType<typeof extractStorageLayout>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const test = _test as
|
|
17
|
+
const test = _test as TestFn<Context>;
|
|
18
18
|
|
|
19
19
|
test.before(async t => {
|
|
20
20
|
const buildInfo = await artifacts.getBuildInfo('contracts/test/Storage.sol:Storage1');
|
|
@@ -23,11 +23,15 @@ test.before(async t => {
|
|
|
23
23
|
}
|
|
24
24
|
const solcOutput: SolcOutput = buildInfo.output;
|
|
25
25
|
const contracts: Record<string, ContractDefinition> = {};
|
|
26
|
+
const storageLayouts: Record<string, StorageLayout> = {};
|
|
26
27
|
for (const def of findAll('ContractDefinition', solcOutput.sources['contracts/test/Storage.sol'].ast)) {
|
|
27
28
|
contracts[def.name] = def;
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
30
|
+
storageLayouts[def.name] = solcOutput.contracts['contracts/test/Storage.sol'][def.name].storageLayout!;
|
|
28
31
|
}
|
|
29
32
|
const deref = astDereferencer(solcOutput);
|
|
30
|
-
t.context.extractStorageLayout = name =>
|
|
33
|
+
t.context.extractStorageLayout = name =>
|
|
34
|
+
extractStorageLayout(contracts[name], dummyDecodeSrc, deref, storageLayouts[name]);
|
|
31
35
|
});
|
|
32
36
|
|
|
33
37
|
const dummyDecodeSrc = () => 'file.sol:1';
|
|
@@ -344,7 +348,7 @@ test('storage upgrade with recursive type', t => {
|
|
|
344
348
|
const v1 = t.context.extractStorageLayout('StorageUpgrade_Recursive_V1');
|
|
345
349
|
const v2 = t.context.extractStorageLayout('StorageUpgrade_Recursive_V2');
|
|
346
350
|
const e = t.throws(() => getStorageUpgradeErrors(v1, v2));
|
|
347
|
-
t.true(e
|
|
351
|
+
t.true(e?.message.includes('Recursion found'));
|
|
348
352
|
});
|
|
349
353
|
|
|
350
354
|
test('storage upgrade with contract type', t => {
|
|
@@ -464,6 +468,12 @@ test('storage upgrade with enum key in mapping', t => {
|
|
|
464
468
|
});
|
|
465
469
|
});
|
|
466
470
|
|
|
471
|
+
test('storage upgrade with embedded enum inside struct type', t => {
|
|
472
|
+
const v1 = t.context.extractStorageLayout('StorageUpgrade_StructEnum_V2');
|
|
473
|
+
const v2 = t.context.extractStorageLayout('StorageUpgrade_StructEnum_V2');
|
|
474
|
+
t.deepEqual(getStorageUpgradeErrors(v1, v2), []);
|
|
475
|
+
});
|
|
476
|
+
|
|
467
477
|
function stabilizeStorageLayout(layout: StorageLayout) {
|
|
468
478
|
return {
|
|
469
479
|
storage: layout.storage.map(s => ({ ...s, type: stabilizeTypeIdentifier(s.type) })),
|