-
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 and factory functions
Adds two new options that allow override of the deploy and factory functions. Kind is still used to identify the kind of proxy used, but this customization can manage variations of the proxy implementation and its constructor parameters. As a test case, and to show a concrete application of this change, I implemented a AccessManagedProxy. This proxy works like a UUPS (ERC1967) proxy, but before delegating the calls, checks with the access manager if the call is allowed.
- Loading branch information
Showing
5 changed files
with
112 additions
and
6 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; | ||
import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol"; // Artifact for test | ||
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 ACCESSMANAGER; | ||
|
||
constructor(address implementation, bytes memory _data, IAccessManager manager) payable ERC1967Proxy(implementation, _data) { | ||
ACCESSMANAGER = manager; | ||
} | ||
|
||
/** | ||
* @dev Checks with the ACCESSMANAGER if the method can be called by msg.sender and then . | ||
* | ||
* 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, ) = ACCESSMANAGER.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
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,57 @@ | ||
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.anon = anon; | ||
t.context.admin = admin; | ||
t.context.acMgr = await AccessManager.deploy(admin); | ||
}); | ||
|
||
async function getAccessManagedProxy(hre, signer) { | ||
return hre.ethers.getContractFactory('AccessManagedProxy', signer); | ||
} | ||
|
||
async function deployWithExtraProxyArgs(hre, opts, factory, ...args) { | ||
const allArgs = [...args, ...(opts.proxyExtraConstructorArgs || [])]; | ||
return deploy(hre, opts, factory, ...allArgs); | ||
} | ||
|
||
test('accessmanaged proxy / customization of deploy and factory functions', async t => { | ||
const { Greeter, GreeterV2, GreeterV3, acMgr, anon, admin } = t.context; | ||
|
||
const greeter = await upgrades.deployProxy(Greeter, ['Hello, Hardhat!'], { | ||
kind: 'uups', | ||
proxyExtraConstructorArgs: [await acMgr.getAddress()], | ||
deployFunction: deployWithExtraProxyArgs, | ||
proxyFactory: getAccessManagedProxy, | ||
}); | ||
|
||
// 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'); | ||
}); |