-
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.
Options to change deploy function and proxy contract factory (#1104)
Co-authored-by: Eric Lau <ericglau@outlook.com>
- Loading branch information
Showing
13 changed files
with
219 additions
and
24 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
// Example of a custom proxy. | ||
// This contract is for testing only. | ||
|
||
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; | ||
import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol"; | ||
import {IAccessManager} from "@openzeppelin/contracts/access/manager/IAccessManager.sol"; | ||
import {IAccessManaged} from "@openzeppelin/contracts/access/manager/IAccessManaged.sol"; | ||
|
||
contract AccessManagedProxy is ERC1967Proxy { | ||
IAccessManager public immutable ACCESS_MANAGER; | ||
|
||
constructor(address implementation, bytes memory _data, IAccessManager manager) payable ERC1967Proxy(implementation, _data) { | ||
ACCESS_MANAGER = manager; | ||
} | ||
|
||
/** | ||
* @dev Checks with the ACCESS_MANAGER if msg.sender is authorized to call the current call's function, | ||
* and if so, delegates the current call to `implementation`. | ||
* | ||
* This function does not return to its internal call site, it will return directly to the external caller. | ||
*/ | ||
function _delegate(address implementation) internal virtual override { | ||
(bool immediate, ) = ACCESS_MANAGER.canCall(msg.sender, address(this), bytes4(msg.data[0:4])); | ||
if (!immediate) revert IAccessManaged.AccessManagedUnauthorized(msg.sender); | ||
super._delegate(implementation); | ||
} | ||
} |
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,24 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; | ||
|
||
// Example of a custom beacon proxy. | ||
// This contract is for testing only, it is not safe for use in production. | ||
|
||
contract CustomBeaconProxy is BeaconProxy { | ||
address private immutable _deployer; | ||
// The beacon that will be used on calls by the deployer address | ||
address private immutable _deployerBeacon; | ||
|
||
constructor(address beacon, bytes memory data, address deployerBeacon) payable BeaconProxy(beacon, data) { | ||
_deployer = msg.sender; | ||
_deployerBeacon = deployerBeacon; | ||
} | ||
|
||
function _getBeacon() internal view override returns (address) { | ||
if (msg.sender == _deployer) return _deployerBeacon; | ||
return super._getBeacon(); | ||
} | ||
} |
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
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
56 changes: 56 additions & 0 deletions
56
packages/plugin-hardhat/test/beacon-custom-beacon-proxy.js
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,56 @@ | ||
const test = require('ava'); | ||
|
||
const { ethers, upgrades } = require('hardhat'); | ||
const { deploy } = require('../dist/utils/deploy'); | ||
|
||
test.before(async t => { | ||
t.context.Greeter = await ethers.getContractFactory('Greeter'); | ||
t.context.GreeterV2 = await ethers.getContractFactory('GreeterV2'); | ||
t.context.GreeterV3 = await ethers.getContractFactory('GreeterV3'); | ||
t.context.CustomBeaconProxy = await ethers.getContractFactory('CustomBeaconProxy'); | ||
const [deployer, anon] = await ethers.getSigners(); | ||
t.context.deployer = deployer; | ||
t.context.anon = anon; | ||
}); | ||
|
||
async function deployWithExtraProxyArgs(hre, opts, factory, ...args) { | ||
const allArgs = [...args, ...(opts.proxyExtraConstructorArgs || [])]; | ||
return deploy(hre, opts, factory, ...allArgs); | ||
} | ||
|
||
test('custom beacon proxy factory and deploy function', async t => { | ||
const { Greeter, GreeterV2, GreeterV3, CustomBeaconProxy, deployer, anon } = t.context; | ||
|
||
const greeterBeacon = await upgrades.deployBeacon(Greeter); | ||
const greeterBeaconDeployer = await upgrades.deployBeacon(GreeterV2); | ||
const greeter = await upgrades.deployBeaconProxy(greeterBeacon, Greeter, ['Hello, Hardhat!'], { | ||
proxyFactory: CustomBeaconProxy, | ||
deployFunction: deployWithExtraProxyArgs, | ||
proxyExtraConstructorArgs: [await greeterBeaconDeployer.getAddress()], | ||
}); | ||
await greeter.waitForDeployment(); | ||
t.is(await greeter.greet(), 'Hello, Hardhat!'); | ||
|
||
const greeterAsV2 = GreeterV2.attach(await greeter.getAddress()); | ||
|
||
// When calling from anon, uses Greeter as implementation and doesn't have resetGreeting method | ||
let e = await t.throwsAsync(() => greeterAsV2.connect(anon).resetGreeting()); | ||
t.true(e.message.includes('Transaction reverted: function selector was not recognized'), e.message); | ||
|
||
// When calling from deployer address, uses Greeter as implementation and doesn't have resetGreeting method | ||
await greeterAsV2.connect(deployer).resetGreeting(); | ||
|
||
// For both changes the greet, because even when using different implementations, they share the storage | ||
t.is(await greeterAsV2.connect(anon).greet(), 'Hello World'); | ||
t.is(await greeterAsV2.connect(deployer).greet(), 'Hello World'); | ||
|
||
// Upgrade only the deployer beacon | ||
await upgrades.upgradeBeacon(greeterBeaconDeployer, GreeterV3); | ||
|
||
const greeterAsV3 = GreeterV3.attach(await greeter.getAddress()); | ||
|
||
// When calling from anon, still uses Greeter as implementation and doesn't have version() method | ||
e = await t.throwsAsync(() => greeterAsV3.connect(anon).version()); | ||
t.true(e.message.includes('Transaction reverted: function selector was not recognized'), e.message); | ||
t.is(await greeterAsV3.connect(deployer).version(), 'V3'); | ||
}); |
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,53 @@ | ||
const test = require('ava'); | ||
|
||
const { ethers, upgrades } = require('hardhat'); | ||
const { deploy } = require('../dist/utils/deploy'); | ||
|
||
test.before(async t => { | ||
t.context.Greeter = await ethers.getContractFactory('GreeterProxiable'); | ||
t.context.GreeterV2 = await ethers.getContractFactory('GreeterV2Proxiable'); | ||
t.context.GreeterV3 = await ethers.getContractFactory('GreeterV3Proxiable'); | ||
t.context.AccessManagedProxy = await ethers.getContractFactory('AccessManagedProxy'); | ||
const AccessManager = await ethers.getContractFactory('AccessManager'); | ||
const [admin, anon] = await ethers.getSigners(); | ||
t.context.admin = admin; | ||
t.context.anon = anon; | ||
t.context.acMgr = await AccessManager.deploy(admin); | ||
}); | ||
|
||
async function deployWithExtraProxyArgs(hre, opts, factory, ...args) { | ||
const allArgs = [...args, ...(opts.proxyExtraConstructorArgs || [])]; | ||
return deploy(hre, opts, factory, ...allArgs); | ||
} | ||
|
||
test('custom uups proxy factory and deploy function', async t => { | ||
const { Greeter, GreeterV2, GreeterV3, AccessManagedProxy, acMgr, admin, anon } = t.context; | ||
|
||
const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { | ||
kind: 'uups', | ||
proxyExtraConstructorArgs: [await acMgr.getAddress()], | ||
deployFunction: deployWithExtraProxyArgs, | ||
proxyFactory: AccessManagedProxy, | ||
}); | ||
|
||
// By default it calls from admin address, so, it works fine | ||
let greet = await greeter.connect(admin).greet(); | ||
t.is(greet, 'Hello, Hardhat!'); | ||
// But fails when called from other user | ||
let e = await t.throwsAsync(() => greeter.connect(anon).greet()); | ||
t.true(e.message.includes('AccessManagedUnauthorized'), e.message); | ||
|
||
// Upgrades work well, because the call executed from the default signer that is the admin | ||
const greeter2 = await upgrades.upgradeProxy(greeter, GreeterV2); | ||
await greeter2.waitForDeployment(); | ||
await greeter2.resetGreeting(); | ||
|
||
// Upgrades don't break the access control | ||
e = await t.throwsAsync(() => greeter2.connect(anon).resetGreeting()); | ||
t.true(e.message.includes('AccessManagedUnauthorized'), e.message); | ||
|
||
const greeter3ImplAddr = await upgrades.prepareUpgrade(await greeter.getAddress(), GreeterV3); | ||
const greeter3 = GreeterV3.attach(greeter3ImplAddr); | ||
const version3 = await greeter3.version(); | ||
t.is(version3, 'V3'); | ||
}); |