Skip to content

Commit 9274324

Browse files
ericglaufrangioAmxx
authored
Use upgradeToAndCall depending on upgrade interface version (#883)
Co-authored-by: Francisco <[email protected]> Co-authored-by: Hadrien Croubois <[email protected]>
1 parent 754f203 commit 9274324

21 files changed

+634
-31
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,8 @@ async function changeProxyAdmin(
616616

617617
Changes the admin for a specific proxy.
618618

619+
NOTE: This function is not supported with admins or proxies from OpenZeppelin Contracts 5.x.
620+
619621
*Parameters:*
620622

621623
* `proxyAddress` - the address of the proxy to change.

packages/core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
- Support new upgrade interface in OpenZeppelin Contracts 5.0. ([883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883))
56
- Add validations for namespaced storage layout. ([#876](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/876))
67
- Deprecate low-level API. Use [CLI or high-level API](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-core) instead.
78

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openzeppelin/upgrades-core",
3-
"version": "1.29.0",
3+
"version": "1.30.0",
44
"description": "",
55
"repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/core",
66
"license": "MIT",
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { keccak256 } from 'ethereumjs-util';
2+
import { call, EthereumProvider } from './provider';
3+
4+
export async function callOptionalSignature(provider: EthereumProvider, address: string, signature: string) {
5+
const data = '0x' + keccak256(Buffer.from(signature)).toString('hex').slice(0, 8);
6+
try {
7+
return await call(provider, address, data);
8+
} catch (e: any) {
9+
if (
10+
e.message.includes('function selector was not recognized') ||
11+
e.message.includes('invalid opcode') ||
12+
e.message.includes('revert') ||
13+
e.message.includes('execution error')
14+
) {
15+
return undefined;
16+
} else {
17+
throw e;
18+
}
19+
}
20+
}

packages/core/src/impl-address.ts

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { keccak256 } from 'ethereumjs-util';
21
import {
3-
call,
42
EIP1967BeaconNotFound,
53
EIP1967ImplementationNotFound,
64
getBeaconAddress,
75
getImplementationAddress,
86
UpgradesError,
97
} from '.';
8+
import { callOptionalSignature } from './call-optional-signature';
109

1110
import { EthereumProvider } from './provider';
1211
import { parseAddress } from './utils/address';
@@ -24,27 +23,17 @@ export async function getImplementationAddressFromBeacon(
2423
provider: EthereumProvider,
2524
beaconAddress: string,
2625
): Promise<string> {
27-
const implementationFunction = '0x' + keccak256(Buffer.from('implementation()')).toString('hex').slice(0, 8);
28-
let result: string | undefined;
29-
try {
30-
const implAddress = await call(provider, beaconAddress, implementationFunction);
31-
result = parseAddress(implAddress);
32-
} catch (e: any) {
33-
if (
34-
!(
35-
e.message.includes('function selector was not recognized') ||
36-
e.message.includes('invalid opcode') ||
37-
e.message.includes('revert') ||
38-
e.message.includes('execution error')
39-
)
40-
) {
41-
throw e;
42-
} // otherwise fall through with no result
26+
const impl = await callOptionalSignature(provider, beaconAddress, 'implementation()');
27+
let parsedImplAddress;
28+
if (impl !== undefined) {
29+
parsedImplAddress = parseAddress(impl);
4330
}
44-
if (result === undefined) {
31+
32+
if (parsedImplAddress === undefined) {
4533
throw new InvalidBeacon(`Contract at ${beaconAddress} doesn't look like a beacon`);
34+
} else {
35+
return parsedImplAddress;
4636
}
47-
return result;
4837
}
4938

5039
/**

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ export {
5858

5959
export { ValidateUpgradeSafetyOptions, validateUpgradeSafety, ProjectReport, ReferenceContractNotFound } from './cli';
6060

61+
export { getUpgradeInterfaceVersion } from './upgrade-interface-version';
6162
export { makeNamespacedInput } from './utils/make-namespaced';
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import test from 'ava';
2+
import { EthereumProvider } from './provider';
3+
import { getUpgradeInterfaceVersion } from './upgrade-interface-version';
4+
5+
const hash = '0x1234';
6+
7+
function makeProviderReturning(result: unknown): EthereumProvider {
8+
return { send: (_method: string, _params: unknown[]) => Promise.resolve(result) } as EthereumProvider;
9+
}
10+
11+
function makeProviderError(msg: string): EthereumProvider {
12+
return {
13+
send: (_method: string, _params: unknown[]) => {
14+
throw new Error(msg);
15+
},
16+
} as EthereumProvider;
17+
}
18+
19+
test('getUpgradeInterfaceVersion returns version', async t => {
20+
// abi encoding of '5.0.0'
21+
const provider = makeProviderReturning(
22+
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005352e302e30000000000000000000000000000000000000000000000000000000',
23+
);
24+
t.is(await getUpgradeInterfaceVersion(provider, hash), '5.0.0');
25+
});
26+
27+
test('getUpgradeInterfaceVersion throws unrelated error', async t => {
28+
const provider = makeProviderError('unrelated error');
29+
await t.throwsAsync(() => getUpgradeInterfaceVersion(provider, hash), { message: 'unrelated error' });
30+
});
31+
32+
test('getUpgradeInterfaceVersion returns undefined for invalid selector', async t => {
33+
const provider = makeProviderError(
34+
`Transaction reverted: function selector was not recognized and there's no fallback function`,
35+
);
36+
t.is(await getUpgradeInterfaceVersion(provider, hash), undefined);
37+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { callOptionalSignature } from './call-optional-signature';
2+
import { EthereumProvider } from './provider';
3+
4+
export async function getUpgradeInterfaceVersion(
5+
provider: EthereumProvider,
6+
address: string,
7+
): Promise<string | undefined> {
8+
const encodedVersion = await callOptionalSignature(provider, address, 'UPGRADE_INTERFACE_VERSION()');
9+
if (encodedVersion !== undefined) {
10+
// Encoded string
11+
const buf = Buffer.from(encodedVersion.replace(/^0x/, ''), 'hex');
12+
13+
// The first 32 bytes represent the offset, which should be 32 for a string
14+
const offset = parseInt(buf.slice(0, 32).toString('hex'), 16);
15+
if (offset !== 32) {
16+
throw new Error(`Unexpected type for UPGRADE_INTERFACE_VERSION at address ${address}. Expected a string`);
17+
}
18+
19+
// The next 32 bytes represent the length of the string
20+
const length = parseInt(buf.slice(32, 64).toString('hex'), 16);
21+
22+
// The rest is the string itself
23+
return buf.slice(64, 64 + length).toString('utf8');
24+
} else {
25+
return undefined;
26+
}
27+
}

packages/core/src/validate/query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export function isUpgradeSafe(data: ValidationData, version: Version): boolean {
194194

195195
export function inferUUPS(runValidation: ValidationRunData, fullContractName: string): boolean {
196196
const methods = getAllMethods(runValidation, fullContractName);
197-
return methods.includes(upgradeToSignature);
197+
return methods.includes(upgradeToSignature) || methods.includes(upgradeToAndCallSignature);
198198
}
199199

200200
export function inferProxyKind(data: ValidationData, version: Version): ProxyDeployment['kind'] {

packages/plugin-hardhat/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Support new upgrade interface in OpenZeppelin Contracts 5.0. ([#883](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/883))
6+
- Support importing and upgrading 5.0 proxies.
7+
- **Note**: Deploying 5.0 proxies is not supported yet.
58
- Add validations for namespaced storage layout. ([#876](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/876))
69

710
## 2.2.1 (2023-08-18)

0 commit comments

Comments
 (0)