Skip to content

Commit

Permalink
Merge pull request #39 from loreum-org/feat/upgradeable
Browse files Browse the repository at this point in the history
Feat/upgradeable
  • Loading branch information
xhad authored Sep 13, 2023
2 parents c2f9d05 + 5f9c0f4 commit 5811183
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 224 deletions.
121 changes: 41 additions & 80 deletions src/Chamber.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,86 +4,44 @@
pragma solidity 0.8.19;

import { IChamber } from "./interfaces/IChamber.sol";
import "./Common.sol";

import { Context } from "openzeppelin-contracts/contracts/utils/Context.sol";
import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import { IERC721 } from "openzeppelin-contracts/contracts/interfaces/IERC721.sol";
import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import { ERC721Holder } from "openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol";
import { ERC1155Holder } from "openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import { ReentrancyGuard } from "openzeppelin-contracts/contracts/security/ReentrancyGuard.sol";
import {
ERC1967UpgradeUpgradeable
} from "openzeppelin-contracts-upgradeable/contracts/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol";

contract Chamber is IChamber,
ERC1967UpgradeUpgradeable,
ReentrancyGuard,
Context,
ERC721Holder,
ERC1155Holder {

string public version;

/**************************************************
Chamber State Variables
**************************************************/
contract Chamber is IChamber, Common {

/// @notice memberToken The ERC721 contract used for membership.
address public memberToken;

/// @notice govToken The ERC20 contract used for staking.
address public govToken;

/// @notice The leaderboard
/// @notice leaderboard ff members based on total delegation.
/// @dev Limited to maximum 5 leaders requiring 3 approvals
uint8[5] public leaderboard;

/**
* @notice Tracks the amount of "govToken" delegated to a given NFT ID.
* @dev 1st element -> NFT tokenID, 2nd element -> amountDelegated.
*/
/// @notice proposalCount The number of proposals.
uint8 public proposalCount;

/// @notice totalDelegation Tracks the amount of govToken delegated to a given NFT ID.
/// @dev 1st element -> NFT tokenID, 2nd element -> amountDelegated.
mapping(uint8 => uint256) public totalDelegation;

/**
* @notice Tracks a given address's delegatation balance of govToken for a given NFT ID.
* @dev 1st element -> user address, 2nd element -> NFT tokenID, 3rd element -> amountDelegated.
*/
/// @notice accountDelegation Tracks a given address's delegatation balance of govToken for a given NFT ID.
/// @dev 1st element -> user address, 2nd element -> NFT tokenID, 3rd element -> amountDelegated.
mapping(address => mapping(uint8 => uint256)) public accountDelegation;

/**
* @notice Mapping of the Proposals
* @dev 1st element -> index, 2nd element -> Proposal struct
*/
/// @notice proposals Mapping of the Proposals.
/// @dev 1st element -> index, 2nd element -> Proposal struct
mapping(uint8 => Proposal) public proposals;

/** @notice proposalCount The number of proposals.*/
uint8 public proposalCount;

/**
* @notice Tracks which tokenIds have voted on proposals
* @dev 1st element -> proposalId, 2nd element -> tokenId, 3rd element-> voted boolean
*/
/// @notice vtoed Tracks which tokenIds have voted on proposals
/// @dev 1st element -> proposalId, 2nd element -> tokenId, 3rd element-> voted boolean
mapping(uint8 => mapping(uint8 => bool)) public voted;

/**************************************************
Constructor
**************************************************/

constructor() {
_disableInitializers();
}

/**************************************************
Functions
**************************************************/
/// @notice contrcutor disables initialize function on deployment of base implementation.
constructor() { _disableInitializers(); }

/**
* @notice Initializes a new version of Chamber
* @param _memberToken The address of the ERC721 contract used for membership.
* @param _govToken The address of the ERC20 contract used for delegation.
*/
/// @inheritdoc IChamber
function initialize(address _memberToken, address _govToken) external initializer {
version = "0.1.0-alpha";
memberToken = _memberToken;
govToken = _govToken;
}
Expand Down Expand Up @@ -116,7 +74,7 @@ contract Chamber is IChamber,

/// @inheritdoc IChamber
function approveProposal(uint8 _proposalId, uint8 _tokenId) external {
if(_msgSender() != IERC721(memberToken).ownerOf(_tokenId)) revert invalidApproval("Sender isn't owner");
if(_msgSender() != IERC721(memberToken).ownerOf(_tokenId)) revert invalidApproval("Sender isn't NFT owner");
if(proposals[_proposalId].state != State.Initialized) revert invalidApproval("Proposal isn't Initialized");
if(voted[_proposalId][_tokenId]) revert invalidApproval("TokenID aleready voted");

Expand All @@ -137,23 +95,6 @@ contract Chamber is IChamber,
}
}

/**
* @notice _executeProposal function
* @param _proposalId The ID of the proposal to execute.
*/
function _executeProposal(uint8 _proposalId) private {
if(proposals[_proposalId].state != State.Initialized) revert invalidProposalState();

Proposal memory proposal = proposals[_proposalId];
proposals[_proposalId].state = State.Executed;

for (uint256 i = 0; i < proposal.data.length; i++) {
(bool success,) = proposal.target[i].call{value: proposal.value[i]}(proposal.data[i]);
if(!success) revert executionFailed();
}
emit ProposalExecuted(_proposalId);
}

/// @inheritdoc IChamber
function promote(uint256 _amt, uint8 _tokenId) public nonReentrant {
if(_amt == 0 && _tokenId == 0) revert invalidPromotion();
Expand All @@ -179,7 +120,27 @@ contract Chamber is IChamber,
emit Demoted(_msgSender(), _amt, _tokenId);
}

/// @notice Updates the leaderboard
/// @notice _executeProposal function executes the proposal
/// @param _proposalId The ID of the proposal to execute.
function _executeProposal(uint8 _proposalId) private {

// TODO Implement Gas handling and Optimizations
// TODO Implement before and after guards

if(proposals[_proposalId].state != State.Initialized) revert invalidProposalState();

Proposal memory proposal = proposals[_proposalId];
proposals[_proposalId].state = State.Executed;

for (uint256 i = 0; i < proposal.data.length; i++) {
(bool success,) = proposal.target[i].call{value: proposal.value[i]}(proposal.data[i]);
if(!success) revert executionFailed();
}
emit ProposalExecuted(_proposalId);
}

/// @notice _updateLeaderboard Updates the leaderboard array
/// @param _tokenId The ID of the NFT to update.
function _updateLeaderboard (uint8 _tokenId) private {
for (uint8 i = 0; i < 5; i++) {
if (leaderboard[i] == _tokenId) break;
Expand All @@ -197,7 +158,7 @@ contract Chamber is IChamber,
}
}

fallback() external payable {
fallback() external payable {
if (msg.value > 0) emit ReceivedEther(_msgSender(), msg.value);
}

Expand Down
16 changes: 16 additions & 0 deletions src/Common.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
// Loreum Chamber v1

pragma solidity 0.8.19;


import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import { IERC721 } from "openzeppelin-contracts/contracts/interfaces/IERC721.sol";
import { Context } from "openzeppelin-contracts/contracts/utils/Context.sol";
import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import { ERC721Holder } from "openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol";
import { ERC1155Holder } from "openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import { Initializable } from "openzeppelin-contracts/contracts/proxy/utils/Initializable.sol";
import { ReentrancyGuard } from "openzeppelin-contracts/contracts/security/ReentrancyGuard.sol";

abstract contract Common is Initializable, ReentrancyGuard, Context, ERC721Holder, ERC1155Holder {}
35 changes: 35 additions & 0 deletions src/ProxyChamber.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
// Loreum Chamber v1

pragma solidity 0.8.19;

import { IProxyChamber } from "./interfaces/IProxyChamber.sol";
import { ERC1967Proxy } from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract ProxyChamber is IProxyChamber, ERC1967Proxy {

modifier onlyAdmin() {
if(msg.sender != super._getAdmin()) revert notAdmin();
_;
}

constructor(address _logic, bytes memory _data, address _admin) ERC1967Proxy(_logic, _data) {
super._changeAdmin(_admin);
}

function getImplementation() public view returns (address) {
return super._implementation();
}

function getAdmin() public view returns (address) {
return super._getAdmin();
}

function changeAdmin(address newAdmin) public onlyAdmin {
super._changeAdmin(newAdmin);
}

function upgradeTo(address newImplementation) public onlyAdmin {
super._upgradeTo(newImplementation);
}
}
46 changes: 25 additions & 21 deletions src/Registry.sol
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import { Chamber } from "./Chamber.sol";
import { IChamber } from "./interfaces/IChamber.sol";
import { IRegistry } from "./interfaces/IRegistry.sol";
import { ERC1967Proxy } from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { ProxyChamber } from "./ProxyChamber.sol";
import { Ownable } from "openzeppelin-contracts/contracts/access/Ownable.sol";

contract Registry is IRegistry, Ownable {

/// @notice Total number of Chambers
/// @notice totalChamber The tsotal number of Chambers
uint256 public totalChambers;

/// @notice Deployed Chambers
mapping(address => ChamberData) public chambers;

/// @notice Chamber deployer addresses
mapping(address => ChamberData[]) public deployers;
/// @notice chambers The Deployed Chambers
/// @dev serial index -> ChamberData Struct
mapping(uint256 => ChamberData) public chambers;

/// @notice chamerVersion is the latest version of the Chamber contract
address public chamberVersion;

/// @notice contructor receives the base Chamber implementation address
constructor(address _chamberVersion) Ownable() {
chamberVersion = _chamberVersion;
}
Expand All @@ -29,27 +28,32 @@ contract Registry is IRegistry, Ownable {
chamberVersion = _chamberVersion;
}

/// @inheritdoc IRegistry
function getChambers(uint8 limit, uint8 skip) external view returns (ChamberData[] memory) {
ChamberData[] memory _chambers = new ChamberData[](limit);
for (uint8 i = 0; i < limit; i++) {
_chambers[i] = chambers[i + skip];
}
return _chambers;
}

/// @inheritdoc IRegistry
function deploy(address _memberToken, address _govToken) external returns (address) {

bytes memory data = abi.encodeWithSelector(Chamber.initialize.selector, _memberToken, _govToken);
ERC1967Proxy chamberProxy = new ERC1967Proxy(chamberVersion, data);
bytes memory data = abi.encodeWithSelector(IChamber.initialize.selector, _memberToken, _govToken);
ProxyChamber proxyChamber = new ProxyChamber(chamberVersion, data, msg.sender);

IChamber chamber = IChamber(address(chamberProxy));

ChamberData memory chamberData = ChamberData({
chamber: address(chamberProxy),
ChamberData memory chamberData = ChamberData({
chamber: address(proxyChamber),
memberToken: _memberToken,
govToken: _govToken,
version: chamber.version()
govToken: _govToken
});

chambers[address(chamberProxy)] = chamberData;
deployers[msg.sender].push(chamberData);
chambers[totalChambers] = chamberData;
totalChambers++;
emit ChamberDeployed(address(chamberProxy), msg.sender, _memberToken, _govToken, chamber.version());
return address(chamberProxy);

emit ChamberDeployed(address(proxyChamber), totalChambers, msg.sender, _memberToken, _govToken);
return address(proxyChamber);
}
}

Loading

0 comments on commit 5811183

Please sign in to comment.