-
Notifications
You must be signed in to change notification settings - Fork 271
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use upgradeToAndCall depending on upgrade interface version (#883)
Co-authored-by: Francisco <fg@frang.io> Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
- Loading branch information
1 parent
754f203
commit 9274324
Showing
21 changed files
with
634 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { keccak256 } from 'ethereumjs-util'; | ||
import { call, EthereumProvider } from './provider'; | ||
|
||
export async function callOptionalSignature(provider: EthereumProvider, address: string, signature: string) { | ||
const data = '0x' + keccak256(Buffer.from(signature)).toString('hex').slice(0, 8); | ||
try { | ||
return await call(provider, address, data); | ||
} catch (e: any) { | ||
if ( | ||
e.message.includes('function selector was not recognized') || | ||
e.message.includes('invalid opcode') || | ||
e.message.includes('revert') || | ||
e.message.includes('execution error') | ||
) { | ||
return undefined; | ||
} else { | ||
throw e; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import test from 'ava'; | ||
import { EthereumProvider } from './provider'; | ||
import { getUpgradeInterfaceVersion } from './upgrade-interface-version'; | ||
|
||
const hash = '0x1234'; | ||
|
||
function makeProviderReturning(result: unknown): EthereumProvider { | ||
return { send: (_method: string, _params: unknown[]) => Promise.resolve(result) } as EthereumProvider; | ||
} | ||
|
||
function makeProviderError(msg: string): EthereumProvider { | ||
return { | ||
send: (_method: string, _params: unknown[]) => { | ||
throw new Error(msg); | ||
}, | ||
} as EthereumProvider; | ||
} | ||
|
||
test('getUpgradeInterfaceVersion returns version', async t => { | ||
// abi encoding of '5.0.0' | ||
const provider = makeProviderReturning( | ||
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005352e302e30000000000000000000000000000000000000000000000000000000', | ||
); | ||
t.is(await getUpgradeInterfaceVersion(provider, hash), '5.0.0'); | ||
}); | ||
|
||
test('getUpgradeInterfaceVersion throws unrelated error', async t => { | ||
const provider = makeProviderError('unrelated error'); | ||
await t.throwsAsync(() => getUpgradeInterfaceVersion(provider, hash), { message: 'unrelated error' }); | ||
}); | ||
|
||
test('getUpgradeInterfaceVersion returns undefined for invalid selector', async t => { | ||
const provider = makeProviderError( | ||
`Transaction reverted: function selector was not recognized and there's no fallback function`, | ||
); | ||
t.is(await getUpgradeInterfaceVersion(provider, hash), undefined); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { callOptionalSignature } from './call-optional-signature'; | ||
import { EthereumProvider } from './provider'; | ||
|
||
export async function getUpgradeInterfaceVersion( | ||
provider: EthereumProvider, | ||
address: string, | ||
): Promise<string | undefined> { | ||
const encodedVersion = await callOptionalSignature(provider, address, 'UPGRADE_INTERFACE_VERSION()'); | ||
if (encodedVersion !== undefined) { | ||
// Encoded string | ||
const buf = Buffer.from(encodedVersion.replace(/^0x/, ''), 'hex'); | ||
|
||
// The first 32 bytes represent the offset, which should be 32 for a string | ||
const offset = parseInt(buf.slice(0, 32).toString('hex'), 16); | ||
if (offset !== 32) { | ||
throw new Error(`Unexpected type for UPGRADE_INTERFACE_VERSION at address ${address}. Expected a string`); | ||
} | ||
|
||
// The next 32 bytes represent the length of the string | ||
const length = parseInt(buf.slice(32, 64).toString('hex'), 16); | ||
|
||
// The rest is the string itself | ||
return buf.slice(64, 64 + length).toString('utf8'); | ||
} else { | ||
return undefined; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pragma solidity >= 0.4.22 <0.8.0; | ||
|
||
import "./Greeter.sol"; | ||
import "./utils/Proxiable50.sol"; | ||
|
||
contract Greeter50Proxiable is Greeter, Proxiable50 {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pragma solidity ^0.5.1; | ||
|
||
import "./GreeterV2.sol"; | ||
import "./utils/Proxiable50.sol"; | ||
|
||
contract Greeter50V2Proxiable is GreeterV2, Proxiable50 {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pragma solidity ^0.5.1; | ||
|
||
import "./GreeterV3.sol"; | ||
import "./utils/Proxiable50.sol"; | ||
|
||
contract Greeter50V3Proxiable is GreeterV3, Proxiable50 {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "@openzeppelin/contracts-5.0/proxy/beacon/BeaconProxy.sol"; | ||
import "@openzeppelin/contracts-5.0/proxy/beacon/UpgradeableBeacon.sol"; | ||
import "@openzeppelin/contracts-5.0/proxy/ERC1967/ERC1967Proxy.sol"; | ||
import "@openzeppelin/contracts-5.0/proxy/transparent/TransparentUpgradeableProxy.sol"; | ||
import "@openzeppelin/contracts-5.0/proxy/transparent/ProxyAdmin.sol"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
pragma solidity >= 0.4.22 <0.8.0; | ||
|
||
// This contract is for testing only, it is not safe for use in production. | ||
|
||
contract Proxiable50 { | ||
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; | ||
|
||
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; | ||
|
||
function upgradeToAndCall(address newImplementation, bytes calldata data) external { | ||
_setImplementation(newImplementation); | ||
if (data.length > 0) { | ||
/** | ||
* Using address(this).call is dangerous as the call can impersonate the proxy being upgraded. | ||
* a better option is to use a delegate call with an oz-upgrades-unsafe-allow, but this is not | ||
* supported by the early version of solidity used here. | ||
* | ||
* /// @custom:oz-upgrades-unsafe-allow delegatecall | ||
* (bool success, ) = newImplementation.delegatecall(data); | ||
* | ||
* Note that using delegate call can make your implementation contract vulnerable if this function | ||
* is not protected with the `onlyProxy` modifier. Again, This contract is for testing only, it is | ||
* not safe for use in production. Instead, use the `UUPSUpgradeable` contract available in | ||
* @openzeppelin/contracts-upgradeable | ||
*/ | ||
(bool success, ) = address(this).call(data); | ||
require(success, "upgrade call reverted"); | ||
} else { | ||
_checkNonPayable(); | ||
} | ||
} | ||
|
||
function _checkNonPayable() private { | ||
if (msg.value > 0) { | ||
revert('non-payable upgrade call'); | ||
} | ||
} | ||
|
||
function _setImplementation(address newImplementation) private { | ||
bytes32 slot = _IMPLEMENTATION_SLOT; | ||
// solhint-disable-next-line no-inline-assembly | ||
assembly { | ||
sstore(slot, newImplementation) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.