Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(groups): upgradeableRenounceableProxy for group policies #78

Merged
merged 16 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/groups/UpgradeableRenounceableProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.24;

import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";

interface IUpgradeableRenounceableProxy {
function implementation() external view returns (address);
function upgradeToAndCall(address _newImplementation, bytes memory _data) external;
function renounceUpgradeability() external;
}

benjaminbollen marked this conversation as resolved.
Show resolved Hide resolved
contract UpgradeableRenounceableProxy is ERC1967Proxy {
// Errors

error BlockReceive();

// Constants

/// @dev Initial proxy admin.
address internal immutable ADMIN_INIT;

// Constructor

constructor(address _implementation, bytes memory _data) ERC1967Proxy(_implementation, _data) {
// set the admin to the deployer
ERC1967Utils.changeAdmin(msg.sender);
// set the admin as immutable
ADMIN_INIT = msg.sender;
}

/// @dev Handles proxy function calls: attempts to dispatch to a specific
/// function or delegates all calls to the implementation contract.
function _fallback() internal virtual override {
// staticcall implementation() returns the address
if (msg.sig == IUpgradeableRenounceableProxy.implementation.selector) {
bytes32 slot = ERC1967Utils.IMPLEMENTATION_SLOT;
assembly {
let implementation := sload(slot)
mstore(0, shr(12, shl(12, implementation)))
return(0, 0x20)
}
}
// dispatch if caller is admin, otherwise delegate to the implementation
if (msg.sender == ADMIN_INIT && msg.sender == ERC1967Utils.getAdmin()) {
_dispatchAdmin();
} else {
super._fallback();
}
}

/// @dev Upgrades to new implementation, renounces the ability to upgrade or moves to regular flow based on admin request.
function _dispatchAdmin() private {
if (msg.sig == IUpgradeableRenounceableProxy.upgradeToAndCall.selector) {
// upgrades to new implementation
(address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} else if (msg.sig == IUpgradeableRenounceableProxy.renounceUpgradeability.selector) {
// renounces the ability to upgrade the contract, by setting the admin to 0x01.
ERC1967Utils.changeAdmin(address(0x01));
} else {
_delegate(_implementation());
}
}

// Fallback function

receive() external payable {
revert BlockReceive();
}
}
2 changes: 1 addition & 1 deletion test/groups/compositeMintGroups.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Test} from "forge-std/Test.sol";
import {StdCheats} from "forge-std/StdCheats.sol";
import "forge-std/console.sol";
import "../../src/errors/Errors.sol";
import "./setup.sol";
import "./groupSetup.sol";

contract CompositeMintGroupsTest is Test, GroupSetup, IHubErrors {
// State variables
Expand Down
2 changes: 1 addition & 1 deletion test/groups/createGroups.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity >=0.8.13;
import {Test} from "forge-std/Test.sol";
import {StdCheats} from "forge-std/StdCheats.sol";
import "forge-std/console.sol";
import "./setup.sol";
import "./groupSetup.sol";

contract GroupMintTest is Test, GroupSetup {
// Constructor
Expand Down
6 changes: 3 additions & 3 deletions test/groups/setup.sol → test/groups/groupSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
pragma solidity >=0.8.13;

import "../../src/groups/BaseMintPolicy.sol";
import "../setup/HumanRegistration.sol";
import "../setup/AvatarCreation.sol";
import "../setup/TimeCirclesSetup.sol";
import "../hub/MockDeployment.sol";
import "../hub/MockHub.sol";

contract GroupSetup is TimeCirclesSetup, HumanRegistration {
contract GroupSetup is TimeCirclesSetup, AvatarCreation {
// State variables

MockDeployment public mockDeployment;
Expand All @@ -16,7 +16,7 @@ contract GroupSetup is TimeCirclesSetup, HumanRegistration {

// Constructor

constructor() HumanRegistration(40) {}
constructor() AvatarCreation(40) {}

// Setup

Expand Down
2 changes: 1 addition & 1 deletion test/groups/mintGroupCircles.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {Test} from "forge-std/Test.sol";
import {StdCheats} from "forge-std/StdCheats.sol";
import "forge-std/console.sol";
import "../../src/errors/Errors.sol";
import "./setup.sol";
import "./groupSetup.sol";

contract MintGroupCirclesTest is Test, GroupSetup, IHubErrors {
// State variables
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.13;

import {Test} from "forge-std/Test.sol";
import {StdCheats} from "forge-std/StdCheats.sol";
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
import "forge-std/console.sol";
import "../../../src/groups/UpgradeableRenounceableProxy.sol";
import "../../../src/errors/Errors.sol";
import "../groupSetup.sol";

contract adminOperationsUpgradeableRenounceableProxy is Test, GroupSetup {
// Constants

bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

// State variables

address public group;
IUpgradeableRenounceableProxy public proxy;

// Constructor

constructor() GroupSetup() {}

// Setup

function setUp() public {
// first 35 addresses are registered as human
// in mock deployment, with 14 days of mint
groupSetup();

// 36: Kevin
group = addresses[36];

// create a proxy deployment with the mint policy as implementation
vm.startPrank(group);
proxy = IUpgradeableRenounceableProxy(address(new UpgradeableRenounceableProxy(mintPolicy, "")));
hub.registerGroup(address(proxy), "ProxyPolicyGroup", "PPG", bytes32(0));
vm.stopPrank();

for (uint256 i = 0; i < 5; i++) {
vm.prank(group);
hub.trust(addresses[i], INDEFINITE_FUTURE);
}
}

// Tests

function testGetImplementation() public {
address implementation = proxy.implementation();
assertEq(implementation, mintPolicy);
}

/* todo: - test getting admin from proxy
* - test admin cannot be changed
* - test noone else can call upgradeToAndCall
* - test upgradeToAndCall with call data
* - test renouncing admin (DONE)
* - test accessibility of interface functions from non-Admin callers
*/

function testUpgradeToAndCall() public {
address originalImplementation = proxy.implementation();
assertEq(originalImplementation, mintPolicy);

// deploy a new copy of base mint policy
address newMintPolicy = address(new MintPolicy());

// upgrade the proxy to the new implementation
vm.prank(group);
proxy.upgradeToAndCall(newMintPolicy, "");

// check that the implementation has changed
address newImplementation = proxy.implementation();
assertEq(newImplementation, newMintPolicy);

// test minting to group with new policy
_testGroupMintOwnCollateral(addresses[0], group, 1 * CRC);
}

function testRenounceAdmin() public {
// todo: it's not trivial (or impossible?) to read the ADMIN_SLOT from the proxy from a test contract
// this can only be read over the RPC?
// So for now, just test that after renouncing the admin, the group is no longer able to upgrade
// To properly test this, we need to mock the proxy to have an admin() func
// But we can also see this in the test trace, so maybe not necessary

// renounce admin
vm.prank(group);
proxy.renounceUpgradeability();
benjaminbollen marked this conversation as resolved.
Show resolved Hide resolved

// expect revert when trying to upgrade to implementation 0xdead
vm.startPrank(group);
vm.expectRevert();
proxy.upgradeToAndCall(address(0xdead), "");
vm.stopPrank();
}

// Internal functions

// todo: this is a duplicate; test helpers can be better structured
function _testGroupMintOwnCollateral(address _minter, address _group, uint256 _amount) internal {
uint256 tokenIdGroup = uint256(uint160(_group));

address[] memory collateral = new address[](1);
uint256[] memory amounts = new uint256[](1);
collateral[0] = _minter;
amounts[0] = _amount;

// check balance of group before mint
uint256 balanceBefore = hub.balanceOf(_minter, tokenIdGroup);

vm.prank(_minter);
hub.groupMint(_group, collateral, amounts, "");

// check balance of group after mint
uint256 balanceAfter = hub.balanceOf(_minter, tokenIdGroup);
assertEq(balanceAfter, balanceBefore + _amount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.13;

import {Test} from "forge-std/Test.sol";
import {StdCheats} from "forge-std/StdCheats.sol";
import "forge-std/console.sol";
import "../../../src/groups/UpgradeableRenounceableProxy.sol";
import "../../../src/errors/Errors.sol";
import "../groupSetup.sol";

contract usePolicyUpgradeableRenounceableProxyTest is Test, GroupSetup {
// State variables
address public group;

// Constructor

constructor() GroupSetup() {}

// Setup

function setUp() public {
// first 35 addresses are registered as human
// in mock deployment, with 14 days of mint
groupSetup();

// 36: Kevin
group = addresses[36];
}

// Tests

function testRegisterGroupWithProxyPolicy() public {
_createGroupWithProxyPolicy();
}

function testMintGroupWithProxyPolicy() public {
UpgradeableRenounceableProxy proxy = _createGroupWithProxyPolicy();

address alice = addresses[0];

// group must trust alice
vm.prank(group);
hub.trust(alice, INDEFINITE_FUTURE);

// test minting to group
_testGroupMintOwnCollateral(alice, group, 1 * CRC);
}

// Internal functions

function _createGroupWithProxyPolicy() internal returns (UpgradeableRenounceableProxy) {
// create a proxy deployment with the mint policy as implementation
vm.startPrank(group);
UpgradeableRenounceableProxy proxy = new UpgradeableRenounceableProxy(mintPolicy, "");
hub.registerGroup(address(proxy), "ProxyPolicyGroup", "PPG", bytes32(0));
vm.stopPrank();

return proxy;
}

function _testGroupMintOwnCollateral(address _minter, address _group, uint256 _amount) internal {
uint256 tokenIdGroup = uint256(uint160(_group));

address[] memory collateral = new address[](1);
uint256[] memory amounts = new uint256[](1);
collateral[0] = _minter;
amounts[0] = _amount;

// check balance of group before mint
uint256 balanceBefore = hub.balanceOf(_minter, tokenIdGroup);

vm.prank(_minter);
hub.groupMint(_group, collateral, amounts, "");

// check balance of group after mint
uint256 balanceAfter = hub.balanceOf(_minter, tokenIdGroup);
assertEq(balanceAfter, balanceBefore + _amount);
}
}
6 changes: 3 additions & 3 deletions test/hub/PathTransferHub.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import "forge-std/console.sol";
import "../../src/hub/Hub.sol";
import "../../src/hub/TypeDefinitions.sol";
import "../setup/TimeCirclesSetup.sol";
import "../setup/HumanRegistration.sol";
import "../setup/AvatarCreation.sol";
import "../utils/Approximation.sol";
import "./MockPathTransferHub.sol";

contract HubPathTransferTest is Test, TimeCirclesSetup, HumanRegistration, Approximation {
contract HubPathTransferTest is Test, TimeCirclesSetup, AvatarCreation, Approximation {
// State variables

MockPathTransferHub public mockHub;

// Constructor

constructor() HumanRegistration(4) {}
constructor() AvatarCreation(4) {}

// Setup

Expand Down
6 changes: 3 additions & 3 deletions test/hub/V1MintStatusUpdate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import "../../src/migration/IToken.sol";
import "../../src/migration/Migration.sol";
import "../../src/names/NameRegistry.sol";
import "../setup/TimeCirclesSetup.sol";
import "../setup/HumanRegistration.sol";
import "../setup/AvatarCreation.sol";
import "../migration/MockHub.sol";
import "./MockMigrationHub.sol";

contract V1MintStatusUpdateTest is Test, TimeCirclesSetup, HumanRegistration {
contract V1MintStatusUpdateTest is Test, TimeCirclesSetup, AvatarCreation {
// State variables

MockMigrationHub public mockHub;
Expand All @@ -24,7 +24,7 @@ contract V1MintStatusUpdateTest is Test, TimeCirclesSetup, HumanRegistration {

// Constructor

constructor() HumanRegistration(2) {}
constructor() AvatarCreation(2) {}

// Setup

Expand Down
6 changes: 3 additions & 3 deletions test/lift/ERC20Demurrage.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import {StdCheats} from "forge-std/StdCheats.sol";
import "forge-std/console.sol";
import "../../src/circles/Demurrage.sol";
import "../setup/TimeCirclesSetup.sol";
import "../setup/HumanRegistration.sol";
import "../setup/AvatarCreation.sol";
import "../hub/MockDeployment.sol";
import "../hub/MockHub.sol";

contract ERC20LiftTest is Test, TimeCirclesSetup, HumanRegistration {
contract ERC20LiftTest is Test, TimeCirclesSetup, AvatarCreation {
// State variables

MockDeployment public mockDeployment;
Expand All @@ -20,7 +20,7 @@ contract ERC20LiftTest is Test, TimeCirclesSetup, HumanRegistration {

// Constructor

constructor() HumanRegistration(2) {}
constructor() AvatarCreation(2) {}

// Setup

Expand Down
Loading
Loading