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

Add PodAdmin gateway - expose admin fns and veto #587

Merged
merged 39 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
9731910
feat: add multiPodAdmin contract
thomas-waite Mar 15, 2022
500124e
test: setup of MultiPodAdmin tests
thomas-waite Mar 15, 2022
421c45a
test: test add pod member
thomas-waite Mar 15, 2022
515979a
test: add remove admin test
thomas-waite Mar 15, 2022
30d6bcf
refactor: transfer admin management to role rather than address
thomas-waite Mar 16, 2022
34af7aa
test: migrate tests to role based management
thomas-waite Mar 16, 2022
f78f201
refactor: delineate admin priviledges, make granular
thomas-waite Mar 16, 2022
d08dc86
test: add admin priviledg delineation tests
thomas-waite Mar 16, 2022
103fc41
Merge branch 'feat-governance-upgrade' into feat-multiple-pod-admins
thomas-waite Mar 16, 2022
f2eb757
feat: add multipod admin into setup deploy script
thomas-waite Mar 16, 2022
01e026b
refactor: add batch member add and remove fns
thomas-waite Mar 16, 2022
3c4bb5d
refactor: update dao script to use multiPodAdmin
thomas-waite Mar 16, 2022
d74b3b5
test: update e2e tests to use multiPodAdmin
thomas-waite Mar 16, 2022
7f9f80c
refactor: rename to PodAdminGateway
thomas-waite Mar 16, 2022
2150be1
refactor: consolidate into config, add veto controller option
thomas-waite Mar 17, 2022
0ff8999
feat: add VetoController
thomas-waite Mar 17, 2022
d53d1ec
test: setup tests for vetoController
thomas-waite Mar 17, 2022
7a1f484
test: validate nominated VetoController can cancel on timelock
thomas-waite Mar 17, 2022
890a7c1
feat: add validate veto permission
thomas-waite Mar 17, 2022
3b7c173
test: add test for pod deployer role
thomas-waite Mar 18, 2022
dfdc4db
refactor: update DAO script to assign various roles
thomas-waite Mar 18, 2022
3f1d81e
refactor: gas optimisation, more tests
thomas-waite Mar 18, 2022
800d9c5
feat: add pod veto admin role
thomas-waite Mar 18, 2022
4a180b5
refactor: adjust deploy script to include roles
thomas-waite Mar 18, 2022
1ee2708
refactor: minor fixes
thomas-waite Mar 21, 2022
a334744
refactor: update permission mapping
thomas-waite Mar 21, 2022
cf53b56
Merge branch 'feat-role-arch-veto' into feat-multiple-pod-admins
thomas-waite Mar 21, 2022
7d8c13a
fix: correct broken merge
thomas-waite Mar 21, 2022
2b86120
refactor: implement PR feedback, gas optimisations
thomas-waite Mar 21, 2022
a4f36d4
refactor: move to deterministic role based auth
thomas-waite Mar 21, 2022
4704306
refactor: move to deterministic role model
thomas-waite Mar 22, 2022
1edaf07
test: migrate tests
thomas-waite Mar 22, 2022
1052d17
refactor: put pod veto functionality on gateway
thomas-waite Mar 22, 2022
58e0b6b
refactor: improve access control
thomas-waite Mar 22, 2022
5be4401
refactor: update fip script
thomas-waite Mar 22, 2022
916b9c3
refactor: permissions cleanup
thomas-waite Mar 22, 2022
bb80e94
feat: add pod admin transfer functionality
thomas-waite Mar 22, 2022
82b1613
refactor: cleanup
thomas-waite Mar 22, 2022
145ba26
feat: gas optimisations
thomas-waite Mar 23, 2022
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
8 changes: 7 additions & 1 deletion contracts/core/TribeRoles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@ library TribeRoles {
/// @notice can mint FEI arbitrarily
bytes32 internal constant MINTER = keccak256("MINTER_ROLE");

/// @notice Can grant any admin role
/// @notice Manages lower level - Admin and Minor - roles. Able to grant and revoke these
bytes32 internal constant ROLE_ADMIN = keccak256("ROLE_ADMIN");

/*///////////////////////////////////////////////////////////////
Admin Roles
//////////////////////////////////////////////////////////////*/

/// @notice has access to all admin functionality on pods
bytes32 internal constant POD_ADMIN = keccak256("POD_ADMIN");

/// @notice capable of granting and revoking other TribeRoles from having veto power over a pod
bytes32 internal constant POD_VETO_ADMIN = keccak256("POD_VETO_ADMIN");
thomas-waite marked this conversation as resolved.
Show resolved Hide resolved

/// @notice can manage the majority of Tribe protocol parameters. Sets boundaries for MINOR_PARAM_ROLE.
bytes32 internal constant PARAMETER_ADMIN = keccak256("PARAMETER_ADMIN");

Expand Down
15 changes: 15 additions & 0 deletions contracts/pods/IPodAdminGateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

interface IPodAdminGateway {
event AddPodMember(uint256 indexed podId, address member);
event RemovePodMember(uint256 indexed podId, address member);
event UpdatePodAdmin(
uint256 indexed podId,
address oldPodAdmin,
address newPodAdmin
);

// Veto functionality
event VetoTimelock(uint256 indexed podId, address indexed timelock);
}
23 changes: 14 additions & 9 deletions contracts/pods/IPodFactory.sol
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import {IControllerV1} from "./orcaInterfaces/IControllerV1.sol";

interface IPodFactory {
/// @notice Configuration used when creating a pod
/// @param members List of members to be added to the pod
/// @param threshold Number of members that need to approve a transaction on the Gnosis safe
/// @param label Metadata, Human readable label for the pod
/// @param ensString Metadata, ENS name of the pod
/// @param imageUrl Metadata, URL to a image to represent the pod in frontends
/// @param admin The admin of the pod - able to add and remove pod members. Expected to be set to a Gateway contract
/// @param minDelay Delay on the timelock
/// @param admin The admin of the pod - able to add and remove pod members
struct PodConfig {
address[] members;
uint256 threshold;
bytes32 label;
string ensString;
string imageUrl;
address admin;
uint256 minDelay;
}

function podController() external view returns (IControllerV1);

function getPodSafe(uint256 podId) external view returns (address);

function getPodTimelock(uint256 podId) external view returns (address);

function getNumMembers(uint256 podId) external view returns (uint256);

function getPodMembers(uint256 podId)
Expand All @@ -34,15 +41,13 @@ interface IPodFactory {

function getPodAdmin(uint256 podId) external view returns (address);

function createChildOptimisticPod(
PodConfig calldata _config,
uint256 minDelay
) external returns (uint256, address);
function createChildOptimisticPod(PodConfig calldata _config)
external
returns (uint256, address);

function updatePodController(address newPodController) external;

function burnerCreateChildOptimisticPods(
PodConfig[] calldata _config,
uint256[] calldata minDelay
) external returns (uint256[] memory, address[] memory);
function burnerCreateChildOptimisticPods(PodConfig[] calldata _config)
external
returns (uint256[] memory, address[] memory);
}
219 changes: 219 additions & 0 deletions contracts/pods/PodAdminGateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
import {IMemberToken} from "./orcaInterfaces/IMemberToken.sol";
import {CoreRef} from "../refs/CoreRef.sol";
import {ICore} from "../core/ICore.sol";
import {IControllerV1} from "./orcaInterfaces/IControllerV1.sol";
import {Core} from "../core/Core.sol";
import {TribeRoles} from "../core/TribeRoles.sol";
import {IPodAdminGateway} from "./IPodAdminGateway.sol";
import {IPodFactory} from "./IPodFactory.sol";

/// @title PodAdminGateway for TRIBE Governance pods
/// @notice Acts as a gateway for admin functionality and vetos in the TRIBE governance pod system
/// @dev Contract is intended to be set as the podAdmin for all deployed Orca pods. It then allows
///
contract PodAdminGateway is CoreRef, IPodAdminGateway {
using EnumerableSet for EnumerableSet.Bytes32Set;

/// @notice Orca membership token for the pods. Handles permissioning pod members
IMemberToken private immutable memberToken;

/// @notice Pod factory which creates optimistic pods and acts as a source of information
IPodFactory private immutable podFactory;

constructor(
address _core,
address _memberToken,
address _podFactory
) CoreRef(_core) {
require(_memberToken != address(0x0), "ZERO_ADDRESS");
require(_podFactory != address(0x0), "ZERO_ADDRESS");

memberToken = IMemberToken(_memberToken);
podFactory = IPodFactory(_podFactory);
}

//////////////////////// GETTERS ////////////////////////////////

/// @notice Calculate the specific pod admin role related to adding pod members
function getPodAddMemberRole(uint256 _podId) public pure returns (bytes32) {
return keccak256(abi.encode(_podId, "ORCA_POD", "POD_ADD_MEMBER_ROLE"));
ElliotFriedman marked this conversation as resolved.
Show resolved Hide resolved
}

/// @notice Calculate the pod admin role related to removing pod members
function getPodRemoveMemberRole(uint256 _podId)
public
pure
returns (bytes32)
{
return
keccak256(abi.encode(_podId, "ORCA_POD", "POD_REMOVE_MEMBER_ROLE"));
ElliotFriedman marked this conversation as resolved.
Show resolved Hide resolved
}

/// @notice Calculate the specific pod veto role, which allows an
function getPodVetoRole(uint256 _podId) public pure returns (bytes32) {
return keccak256(abi.encode(_podId, "ORCA_POD", "POD_VETO_ROLE"));
ElliotFriedman marked this conversation as resolved.
Show resolved Hide resolved
}

/// @notice Calculate the specific pod transfer admin role
function getPodTransferAdminRole(uint256 _podId)
public
pure
returns (bytes32)
{
return
keccak256(
abi.encode(_podId, "ORCA_POD", "POD_TRANSFER_ADMIN_ROLE")
ElliotFriedman marked this conversation as resolved.
Show resolved Hide resolved
);
}

///////////////////////// ADMIN PRIVILEDGES ////////////////////////////

/// @notice Transfer the pod admin address for a pod to another address
function transferPodAdmin(uint256 _podId, address newPodAdmin)
thomas-waite marked this conversation as resolved.
Show resolved Hide resolved
external
hasAnyOfThreeRoles(
TribeRoles.GOVERNOR,
TribeRoles.POD_ADMIN,
getPodTransferAdminRole(_podId)
)
{
require(newPodAdmin != address(0x0), "ZERO_ADDRESS");
_transferPodAdmin(_podId, newPodAdmin);
}

/// @notice Batch transfer the pod admin address for several pods
/// @dev Mass transfer of podAdmins only expected to be performed by GOVERNOR or POD_ADMIN
function batchTransferPodAdmins(
uint256[] memory _podIds,
address[] memory newPodAdmins
thomas-waite marked this conversation as resolved.
Show resolved Hide resolved
) external hasAnyOfTwoRoles(TribeRoles.GOVERNOR, TribeRoles.POD_ADMIN) {
require(
_podIds.length == newPodAdmins.length,
"MISMATCHED_ARG_LENGTHS"
);
uint256 numPodsToTransfer = _podIds.length;
for (uint256 i = 0; i < numPodsToTransfer; ) {
_transferPodAdmin(_podIds[i], newPodAdmins[i]);

// i is bounded by numPodsToTransfer
unchecked {
i += 1;
}
}
}

/// @notice Transfer a pod admin from this gateway to another address
function _transferPodAdmin(uint256 _podId, address newPodAdmin) internal {
IControllerV1 podController = podFactory.podController();

address oldPodAdmin = address(this);
emit UpdatePodAdmin(_podId, oldPodAdmin, newPodAdmin);
podController.updatePodAdmin(_podId, newPodAdmin);
}

/// @notice Admin functionality to add a member to a pod
/// @dev Permissioned to GOVERNOR, POD_ADMIN and POD_ADD_MEMBER_ROLE
function addPodMember(uint256 _podId, address _member)
external
hasAnyOfThreeRoles(
TribeRoles.GOVERNOR,
TribeRoles.POD_ADMIN,
getPodAddMemberRole(_podId)
)
{
_addMemberToPod(_podId, _member);
}

/// @notice Internal method to add a member to a pod
function _addMemberToPod(uint256 _podId, address _member) internal {
require(_member != address(0), "ZERO_ADDRESS");
emit AddPodMember(_podId, _member);
memberToken.mint(_member, _podId, bytes(""));
}

/// @notice Admin functionality to batch add a member to a pod
/// @dev Permissioned to GOVERNOR, POD_ADMIN and POD_ADMIN_REMOVE_MEMBER
function batchAddPodMember(uint256 _podId, address[] memory _members)
external
hasAnyOfThreeRoles(
TribeRoles.GOVERNOR,
TribeRoles.POD_ADMIN,
getPodAddMemberRole(_podId)
)
{
uint256 numMembers = _members.length;
for (uint256 i = 0; i < numMembers; ) {
_addMemberToPod(_podId, _members[i]);
// i is constrained by being < _members.length
unchecked {
i += 1;
}
}
}

/// @notice Admin functionality to remove a member from a pod.
/// @dev Permissioned to GOVERNOR, POD_ADMIN, GUARDIAN and POD_ADMIN_REMOVE_MEMBER
function removePodMember(uint256 _podId, address _member)
external
hasAnyOfFourRoles(
TribeRoles.GOVERNOR,
TribeRoles.POD_ADMIN,
TribeRoles.GUARDIAN,
getPodRemoveMemberRole(_podId)
)
{
_removePodMember(_podId, _member);
}

/// @notice Internal method to remove a member from a pod
function _removePodMember(uint256 _podId, address _member) internal {
require(_member != address(0), "ZERO_ADDRESS");
emit RemovePodMember(_podId, _member);
memberToken.burn(_member, _podId);
}

/// @notice Admin functionality to batch remove a member from a pod
/// @dev Permissioned to GOVERNOR, POD_ADMIN, GUARDIAN and POD_ADMIN_REMOVE_MEMBER
function batchRemovePodMember(uint256 _podId, address[] memory _members)
thomas-waite marked this conversation as resolved.
Show resolved Hide resolved
external
hasAnyOfFourRoles(
TribeRoles.GOVERNOR,
TribeRoles.POD_ADMIN,
TribeRoles.GUARDIAN,
getPodRemoveMemberRole(_podId)
)
{
uint256 numMembers = _members.length;
for (uint256 i = 0; i < numMembers; ) {
_removePodMember(_podId, _members[i]);

// i is constrained by being < _members.length
unchecked {
i += 1;
}
}
}

/////////////// VETO CONTROLLER /////////////////

/// @notice Allow a proposal to be vetoed in a pod timelock
/// @dev Permissioned to GOVERNOR, POD_ADMIN, GUARDIAN and specific POD_VETO_ROLE
function veto(uint256 _podId, bytes32 proposalId)
external
hasAnyOfFourRoles(
TribeRoles.GOVERNOR,
TribeRoles.POD_VETO_ADMIN,
TribeRoles.GUARDIAN,
getPodVetoRole(_podId)
)
{
address timelock = podFactory.getPodTimelock(_podId);
emit VetoTimelock(_podId, timelock);
TimelockController(payable(timelock)).cancel(proposalId);
}
}
Loading