-
Notifications
You must be signed in to change notification settings - Fork 45
Upgradable Authenticator #351
Changes from 19 commits
4fe914f
8d96c84
6a0ba03
137f6a1
dd520d2
ee3cd11
3920986
7f3e8c0
39e5c51
cee5b69
9526057
ecd7315
26e8335
106c2c0
1a1f5ad
b4e7d5c
6c5ad0f
504228d
888c203
9414ed8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,28 +3,51 @@ pragma solidity ^0.7.6; | |
|
||
import "@gnosis.pm/util-contracts/contracts/StorageAccessible.sol"; | ||
import "@openzeppelin/contracts/utils/EnumerableSet.sol"; | ||
import "@openzeppelin/contracts/proxy/Proxy.sol"; | ||
import "./interfaces/GPv2Authentication.sol"; | ||
import "./ownership/CustomInitiallyOwnable.sol"; | ||
|
||
/// @title Gnosis Protocol v2 Access Control Contract | ||
/// @author Gnosis Developers | ||
contract GPv2AllowListAuthentication is | ||
CustomInitiallyOwnable, | ||
GPv2Authentication, | ||
StorageAccessible | ||
{ | ||
contract GPv2AllowListAuthentication is GPv2Authentication, StorageAccessible { | ||
using EnumerableSet for EnumerableSet.AddressSet; | ||
|
||
address public manager; | ||
|
||
EnumerableSet.AddressSet private solvers; | ||
|
||
// solhint-disable-next-line no-empty-blocks | ||
constructor(address initialOwner) CustomInitiallyOwnable(initialOwner) {} | ||
function initializeManager(address manager_) external { | ||
require(manager == address(0), "GPv2: already initialized"); | ||
manager = manager_; | ||
} | ||
|
||
modifier onlyProxyAdmin() { | ||
// Slot taken from https://eips.ethereum.org/EIPS/eip-1967#specification | ||
// obtained as bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1) | ||
bytes32 slot = | ||
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; | ||
address proxyAdmin; | ||
// solhint-disable-next-line no-inline-assembly | ||
assembly { | ||
proxyAdmin := sload(slot) | ||
} | ||
require(msg.sender == proxyAdmin, "GPv2: caller not proxyAdmin"); | ||
_; | ||
} | ||
|
||
modifier onlyManager() { | ||
require(manager == msg.sender, "GPv2: caller not manager"); | ||
_; | ||
} | ||
|
||
function updateManager(address newManager) external onlyProxyAdmin { | ||
manager = newManager; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the manager gets set to Maybe instead of adding the ability of changing the manager in this PR, we do it as a separate PR. What do you think @fedgiac ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding is that the current changes on the owner are work in progress. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Selbstverständlich There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reverted the |
||
} | ||
|
||
function addSolver(address solver) external onlyOwner { | ||
function addSolver(address solver) external onlyManager { | ||
solvers.add(solver); | ||
} | ||
|
||
function removeSolver(address solver) external onlyOwner { | ||
function removeSolver(address solver) external onlyManager { | ||
solvers.remove(solver); | ||
} | ||
|
||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// SPDX-License-Identifier: LGPL-3.0-or-later | ||
pragma solidity ^0.7.6; | ||
|
||
import "../GPv2AllowListAuthentication.sol"; | ||
|
||
contract GPv2AllowListAuthenticationV2 is GPv2AllowListAuthentication { | ||
function newMethod() external pure returns (uint256) { | ||
return 1337; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// defined in https://eips.ethereum.org/EIPS/eip-1967 | ||
|
||
import { BigNumber } from "ethers"; | ||
import { ethers } from "hardhat"; | ||
|
||
// The proxy contract used by hardhat-deploy implements EIP-1967 (Standard Proxy | ||
// Storage Slot). See <https://eips.ethereum.org/EIPS/eip-1967>. | ||
function slot(string: string) { | ||
return ethers.utils.defaultAbiCoder.encode( | ||
["bytes32"], | ||
[BigNumber.from(ethers.utils.id(string)).sub(1)], | ||
); | ||
} | ||
const IMPLEMENTATION_STORAGE_SLOT = slot("eip1967.proxy.implementation"); | ||
const OWNER_STORAGE_SLOT = slot("eip1967.proxy.admin"); | ||
|
||
/** | ||
* Returns the address of the implementation of an EIP-1967-compatible proxy | ||
* from its address. | ||
* | ||
* @param proxy Address of the proxy contract. | ||
* @returns The address of the contract storing the proxy implementation. | ||
*/ | ||
export async function implementationAddress(proxy: string): Promise<string> { | ||
const [implementation] = await ethers.utils.defaultAbiCoder.decode( | ||
["address"], | ||
await ethers.provider.getStorageAt(proxy, IMPLEMENTATION_STORAGE_SLOT), | ||
); | ||
return implementation; | ||
} | ||
|
||
/** | ||
* Returns the address of the implementation of an EIP-1967-compatible proxy | ||
* from its address. | ||
* | ||
* @param proxy Address of the proxy contract. | ||
* @returns The address of the administrator of the proxy. | ||
*/ | ||
export async function ownerAddress(proxy: string): Promise<string> { | ||
bh2smith marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const [owner] = await ethers.utils.defaultAbiCoder.decode( | ||
["address"], | ||
await ethers.provider.getStorageAt(proxy, OWNER_STORAGE_SLOT), | ||
); | ||
return owner; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { expect } from "chai"; | ||
import { Contract, Wallet } from "ethers"; | ||
import { deployments, ethers } from "hardhat"; | ||
|
||
import { deployTestContracts } from "./fixture"; | ||
|
||
describe("Upgrade Authenticator", () => { | ||
let authenticator: Contract; | ||
let deployer: Wallet; | ||
let owner: Wallet; | ||
let solver: Wallet; | ||
|
||
beforeEach(async () => { | ||
({ | ||
authenticator, | ||
deployer, | ||
owner, | ||
wallets: [solver], | ||
} = await deployTestContracts()); | ||
}); | ||
|
||
it("should upgrade authenticator", async () => { | ||
const GPv2AllowListAuthenticationV2 = await ethers.getContractFactory( | ||
"GPv2AllowListAuthenticationV2", | ||
deployer, | ||
); | ||
// Note that, before the upgrade this is actually the old instance | ||
const authenticatorV2 = GPv2AllowListAuthenticationV2.attach( | ||
authenticator.address, | ||
); | ||
// This method doesn't exist before upgrade | ||
await expect(authenticatorV2.newMethod()).to.be.reverted; | ||
|
||
await upgrade( | ||
"GPv2AllowListAuthentication", | ||
"GPv2AllowListAuthenticationV2", | ||
); | ||
// This method should exist on after upgrade | ||
expect(await authenticatorV2.newMethod()).to.equal(1337); | ||
}); | ||
|
||
it("should preserve storage", async () => { | ||
await authenticator.connect(owner).addSolver(solver.address); | ||
|
||
// Upgrade after storage is set. | ||
await upgrade( | ||
"GPv2AllowListAuthentication", | ||
"GPv2AllowListAuthenticationV2", | ||
); | ||
|
||
const GPv2AllowListAuthenticationV2 = await ethers.getContractFactory( | ||
"GPv2AllowListAuthenticationV2", | ||
deployer, | ||
); | ||
const authenticatorV2 = GPv2AllowListAuthenticationV2.attach( | ||
authenticator.address, | ||
); | ||
// Both, the listed solvers and original manager are still set | ||
expect(await authenticatorV2.isSolver(solver.address)).to.equal(true); | ||
expect(await authenticatorV2.manager()).to.equal(owner.address); | ||
}); | ||
|
||
fedgiac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
async function upgrade(contractName: string, newContractName: string) { | ||
// Note that deterministic deployment and gasLimit are not needed/used here as deployment args. | ||
await deployments.deploy(contractName, { | ||
contract: newContractName, | ||
// From differs from initial deployment here since the proxy owner is the Authenticator manager. | ||
from: owner.address, | ||
bh2smith marked this conversation as resolved.
Show resolved
Hide resolved
|
||
proxy: true, | ||
}); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be a constant.