Skip to content

Commit

Permalink
Merge pull request #78 from aboutcircles/v1.0.1-patch2-upgradable-gro…
Browse files Browse the repository at this point in the history
…up-policies

(groups): upgradeableRenounceableProxy for group policies
  • Loading branch information
benjaminbollen authored Nov 18, 2024
2 parents a7136aa + 723ea36 commit 91cb016
Show file tree
Hide file tree
Showing 14 changed files with 298 additions and 25 deletions.
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;
}

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,123 @@
// 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 {
// current admin
address admin = address(uint160(uint256(vm.load(address(proxy), ADMIN_SLOT))));
assertEq(admin, group);

// renounce admin
vm.prank(group);
proxy.renounceUpgradeability();

// renounced admin
admin = address(uint160(uint256(vm.load(address(proxy), ADMIN_SLOT))));
assertEq(admin, address(0x1));

// 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

0 comments on commit 91cb016

Please sign in to comment.