Skip to content

Commit

Permalink
Role-Based Access Control and Contract Size Optimization
Browse files Browse the repository at this point in the history
- Updated Open Zeppelin version
- Added a new library `AccessControlLibV1` for role based access control. This library infers the roles defined in the the main protocol contract and enables the calling contracts to check if a given user has permission to execute certain transactions
- Added a new base contract for the protocol
- Added numerous roles: Admin, Pause Agent, Unpause Agent, Governance Agent, Cover Manager, and Liquidity Manager.
- The protocol contract has updated features related to `pause`, `unpause`, and `access control`. The rest of the smart contracts now infer the state of the protocol instead of re-implementing the above features and duplicating logic.
- Refactored the protocol base to use `Role Based Access Control` instead of ownership module
- Refactored `Recoverable` to drop the ownership dependency. Moved logic of Recoverable to a new library `BaseLibV1` resulting in a much smaller smart contracts implementing this contract.
- Updated `finalize` feature on the governance contract to be only accessible by governance agent.
- Updated `increaseProvision` feature on the cover provision contract to be only accessible to liquidity manager.
- Refactored vault contract and moved the logic to `VaultLibV1`
- Refactored `setPolicyRatesByKey` on policy admin contract to only allow access by cover manager.
- Added contracts: FakeRecoverable and IPausable
- Refactored the vault interface to and merged events for better clarity. The event `LiquidityAdded` is now merged to `PodsIssued` whereas the event `LiquidityRemoved` is merged to `PodsRedeemed`
- Updated the documentation
- Refactored and fixed unit tests and stories
  • Loading branch information
heyaibi committed Oct 13, 2021
1 parent 0194cd1 commit b0a6598
Show file tree
Hide file tree
Showing 129 changed files with 4,033 additions and 953 deletions.
61 changes: 61 additions & 0 deletions contracts/core/ProtoBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Neptune Mutual Protocol (https://neptunemutual.com)
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.4.22 <0.9.0;
import "openzeppelin-solidity/contracts/access/AccessControl.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-solidity/contracts/security/Pausable.sol";
import "../libraries/ProtoUtilV1.sol";
import "./Recoverable.sol";

abstract contract ProtoBase is AccessControl, Pausable, Recoverable {
using ProtoUtilV1 for IStore;

constructor(IStore store) Recoverable(store) {
_setAccessPolicy();
}

function _setAccessPolicy() private {
_setRoleAdmin(AccessControlLibV1.NS_ROLES_ADMIN, AccessControlLibV1.NS_ROLES_ADMIN);
_setRoleAdmin(AccessControlLibV1.NS_ROLES_COVER_MANAGER, AccessControlLibV1.NS_ROLES_ADMIN);
_setRoleAdmin(AccessControlLibV1.NS_ROLES_LIQUIDITY_MANAGER, AccessControlLibV1.NS_ROLES_ADMIN);
_setRoleAdmin(AccessControlLibV1.NS_ROLES_GOVERNANCE_AGENT, AccessControlLibV1.NS_ROLES_ADMIN);
_setRoleAdmin(AccessControlLibV1.NS_ROLES_UPGRADE_AGENT, AccessControlLibV1.NS_ROLES_ADMIN);
_setRoleAdmin(AccessControlLibV1.NS_ROLES_RECOVERY_AGENT, AccessControlLibV1.NS_ROLES_ADMIN);
_setRoleAdmin(AccessControlLibV1.NS_ROLES_PAUSE_AGENT, AccessControlLibV1.NS_ROLES_ADMIN);
_setRoleAdmin(AccessControlLibV1.NS_ROLES_UNPAUSE_AGENT, AccessControlLibV1.NS_ROLES_ADMIN);

_setupRole(AccessControlLibV1.NS_ROLES_ADMIN, msg.sender);
}

function setupRole(
bytes32 role,
bytes32 adminRole,
address account
) external {
AccessControlLibV1.mustBeAdmin(s);

_setRoleAdmin(role, adminRole);

if (account != address(0)) {
_setupRole(role, account);
}
}

/**
* @dev Pauses this contract.
* Can only be called by "Pause Agents".
*/
function pause() external {
AccessControlLibV1.mustBePauseAgent(s);
super._pause();
}

/**
* @dev Unpauses this contract.
* Can only be called by "Unpause Agents".
*/
function unpause() external whenPaused {
AccessControlLibV1.mustBeUnpauseAgent(s);
super._unpause();
}
}
99 changes: 64 additions & 35 deletions contracts/core/Protocol.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import "../interfaces/IStore.sol";
import "../interfaces/IProtocol.sol";
import "../libraries/ProtoUtilV1.sol";
import "../libraries/StoreKeyUtil.sol";
import "./Recoverable.sol";
import "./ProtoBase.sol";

contract Protocol is IProtocol, Recoverable {
contract Protocol is IProtocol, ProtoBase {
using ProtoUtilV1 for bytes;
using ProtoUtilV1 for IStore;
using StoreKeyUtil for IStore;

constructor(IStore store) Recoverable(store) {
this;
}
uint256 public initialized = 0;

constructor(IStore store) ProtoBase(store) {} // solhint-disable-line

function initialize(
address uniswapV2RouterLike,
Expand All @@ -27,68 +27,93 @@ contract Protocol is IProtocol, Recoverable {
uint256 minLiquidityPeriod,
uint256 claimPeriod
) external {
_mustBeOwnerOrProtoOwner();
s.mustBeProtocolMember(msg.sender);

require(s.getAddressByKey(ProtoUtilV1.NS_SETUP_NPM) == address(0), "Already initialized");
require(initialized == 0, "Already initialized");
require(npm != address(0), "Invalid NPM");
require(uniswapV2RouterLike != address(0), "Invalid Uniswap Router");
require(uniswapV2RouterLike != address(0), "Invalid Router");
require(treasury != address(0), "Invalid Treasury");
require(assuranceVault != address(0), "Invalid Vault");

s.setAddressByKey(ProtoUtilV1.NS_CORE, address(this));
s.setBoolByKeys(ProtoUtilV1.NS_CONTRACTS, address(this), true);
s.setAddressByKey(ProtoUtilV1.NS_BURNER, 0x0000000000000000000000000000000000000001);

s.setAddressByKey(ProtoUtilV1.NS_SETUP_NPM, npm);
s.setAddressByKey(ProtoUtilV1.NS_SETUP_UNISWAP_V2_ROUTER, uniswapV2RouterLike);
s.setAddressByKey(ProtoUtilV1.NS_BURNER, 0x0000000000000000000000000000000000000001);
s.setAddressByKey(ProtoUtilV1.NS_TREASURY, treasury);
s.setAddressByKey(ProtoUtilV1.NS_ASSURANCE_VAULT, assuranceVault);

setCoverFees(coverFee);
setMinStake(minStake);
setMinReportingStake(minReportingStake);
setMinLiquidityPeriod(minLiquidityPeriod);
setClaimPeriod(claimPeriod);
_setCoverFees(coverFee);
_setMinStake(minStake);
_setMinReportingStake(minReportingStake);
_setMinLiquidityPeriod(minLiquidityPeriod);
_setClaimPeriod(claimPeriod);

initialized = 1;
}

function setClaimPeriod(uint256 value) public nonReentrant {
_mustBeOwnerOrProtoOwner();
ValidationLibV1.mustNotBePaused(s);
AccessControlLibV1.mustBeCoverManager(s);
_setClaimPeriod(value);
}

function setCoverFees(uint256 value) public nonReentrant {
ValidationLibV1.mustNotBePaused(s);
AccessControlLibV1.mustBeCoverManager(s);
_setCoverFees(value);
}

function setMinStake(uint256 value) public nonReentrant {
ValidationLibV1.mustNotBePaused(s);
AccessControlLibV1.mustBeCoverManager(s);

_setMinStake(value);
}

function setMinReportingStake(uint256 value) public nonReentrant {
ValidationLibV1.mustNotBePaused(s);
AccessControlLibV1.mustBeCoverManager(s);
_setMinReportingStake(value);
}

function setMinLiquidityPeriod(uint256 value) public nonReentrant {
ValidationLibV1.mustNotBePaused(s);
AccessControlLibV1.mustBeLiquidityManager(s);

_setMinLiquidityPeriod(value);
}

function _setClaimPeriod(uint256 value) private {
uint256 previous = s.getUintByKey(ProtoUtilV1.NS_SETUP_CLAIM_PERIOD);
s.setUintByKey(ProtoUtilV1.NS_SETUP_CLAIM_PERIOD, value);

emit ClaimPeriodSet(previous, value);
}

function setCoverFees(uint256 value) public nonReentrant {
_mustBeOwnerOrProtoOwner();

function _setCoverFees(uint256 value) private {
uint256 previous = s.getUintByKey(ProtoUtilV1.NS_SETUP_COVER_FEE);
s.setUintByKey(ProtoUtilV1.NS_SETUP_COVER_FEE, value);

emit CoverFeeSet(previous, value);
}

function setMinStake(uint256 value) public nonReentrant {
_mustBeOwnerOrProtoOwner();

function _setMinStake(uint256 value) private {
uint256 previous = s.getUintByKey(ProtoUtilV1.NS_SETUP_MIN_STAKE);
s.setUintByKey(ProtoUtilV1.NS_SETUP_MIN_STAKE, value);

emit MinStakeSet(previous, value);
}

function setMinReportingStake(uint256 value) public nonReentrant {
_mustBeOwnerOrProtoOwner();

function _setMinReportingStake(uint256 value) private {
uint256 previous = s.getUintByKey(ProtoUtilV1.NS_SETUP_REPORTING_STAKE);
s.setUintByKey(ProtoUtilV1.NS_SETUP_REPORTING_STAKE, value);

emit MinReportingStakeSet(previous, value);
}

function setMinLiquidityPeriod(uint256 value) public nonReentrant {
_mustBeOwnerOrProtoOwner();

function _setMinLiquidityPeriod(uint256 value) private {
uint256 previous = s.getUintByKey(ProtoUtilV1.NS_SETUP_MIN_LIQ_PERIOD);
s.setUintByKey(ProtoUtilV1.NS_SETUP_MIN_LIQ_PERIOD, value);

Expand All @@ -99,29 +124,33 @@ contract Protocol is IProtocol, Recoverable {
bytes32 namespace,
address previous,
address current
) external override onlyOwner {
_mustBeUnpaused();
) external override {
ValidationLibV1.mustNotBePaused(s);
AccessControlLibV1.mustBeUpgradeAgent(s);

s.upgradeContract(namespace, previous, current);
emit ContractUpgraded(namespace, previous, current);
}

function addContract(bytes32 namespace, address contractAddress) external override onlyOwner {
_mustBeUnpaused();
function addContract(bytes32 namespace, address contractAddress) external override {
ValidationLibV1.mustNotBePaused(s);
AccessControlLibV1.mustBeUpgradeAgent(s);

s.addContract(namespace, contractAddress);
emit ContractAdded(namespace, contractAddress);
}

function removeMember(address member) external override onlyOwner {
_mustBeUnpaused();
function removeMember(address member) external override {
ValidationLibV1.mustNotBePaused(s);
AccessControlLibV1.mustBeUpgradeAgent(s);

s.removeMember(member);
emit MemberRemoved(member);
}

function addMember(address member) external override onlyOwner {
_mustBeUnpaused();
function addMember(address member) external override {
ValidationLibV1.mustNotBePaused(s);
AccessControlLibV1.mustBeUpgradeAgent(s);

s.addMember(member);
emit MemberAdded(member);
Expand Down
76 changes: 11 additions & 65 deletions contracts/core/Recoverable.sol
Original file line number Diff line number Diff line change
@@ -1,88 +1,34 @@
// Neptune Mutual Protocol (https://neptunemutual.com)
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.4.22 <0.9.0;
import "../libraries/ProtoUtilV1.sol";
import "openzeppelin-solidity/contracts/access/Ownable.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-solidity/contracts/security/ReentrancyGuard.sol";
import "openzeppelin-solidity/contracts/security/Pausable.sol";
import "../libraries/BaseLibV1.sol";
import "../libraries/ValidationLibV1.sol";

abstract contract Recoverable is Ownable, ReentrancyGuard, Pausable {
using ProtoUtilV1 for IStore;
abstract contract Recoverable is ReentrancyGuard {
IStore public s;

constructor(IStore store) {
require(address(store) != address(0), "Invalid Store");

s = store;
}

/**
* @dev Recover all Ether held by the contract.
* On success, no event is emitted because the recovery feature does
* not have any significance in the SDK or the UI.
*/
function recoverEther(address sendTo) external {
_mustBeOwnerOrProtoOwner();

// slither-disable-next-line arbitrary-send
payable(sendTo).transfer(address(this).balance);
BaseLibV1.recoverEther(s, sendTo);
}

/**
* @dev Recover all BEP-20 compatible tokens sent to this address.
* @param token BEP-20 The address of the token contract
* @dev Recover all IERC-20 compatible tokens sent to this address.
* On success, no event is emitted because the recovery feature does
* not have any significance in the SDK or the UI.
* @param token IERC-20 The address of the token contract
*/
function recoverToken(address token, address sendTo) external {
_mustBeOwnerOrProtoOwner();

IERC20 bep20 = IERC20(token);

uint256 balance = bep20.balanceOf(address(this));
require(bep20.transfer(sendTo, balance), "Transfer failed");
}

function pause() external {
_mustBeUnpaused();
_mustBeOwnerOrProtoOwner();

super._pause();
}

function unpause() external whenPaused {
_mustBeOwnerOrProtoOwner();

super._unpause();
}

/**
* @dev Reverts if the sender is not the contract owner or a protocol member.
*/
function _mustBeOwnerOrProtoMember() internal view {
bool isProtocol = s.isProtocolMember(super._msgSender());

if (isProtocol == false) {
require(super._msgSender() == super.owner(), "Forbidden");
}
}

/**
* @dev Reverts if the sender is not the contract owner or protocol owner.
*/
function _mustBeOwnerOrProtoOwner() internal view {
IProtocol protocol = ProtoUtilV1.getProtocol(s);

if (address(protocol) == address(0)) {
require(super._msgSender() == owner(), "Forbidden");
return;
}

address protocolOwner = Ownable(address(protocol)).owner();
require(super._msgSender() == owner() || super._msgSender() == protocolOwner, "Forbidden");
}

function _mustBeUnpaused() internal view {
require(super.paused() == false, "Contract is paused");

address protocol = ProtoUtilV1.getProtocolAddress(s);
require(Pausable(protocol).paused() == false, "Protocol is paused");
BaseLibV1.recoverToken(s, token, sendTo);
}
}
4 changes: 2 additions & 2 deletions contracts/core/cToken/cToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ contract cToken is ICToken, Recoverable, ERC20 {
address to,
uint256 amount
) external override {
_mustBeUnpaused();
s.mustNotBePaused();
require(key == coverKey, "Invalid cover");
s.callerMustBePolicyContract();

Expand All @@ -65,6 +65,6 @@ contract cToken is ICToken, Recoverable, ERC20 {
* @param amount Specify the amount of tokens to burn
*/
function burn(uint256 amount) external override {
super._burn(super._msgSender(), amount);
super._burn(msg.sender, amount);
}
}
2 changes: 1 addition & 1 deletion contracts/core/cToken/cTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ contract cTokenFactory is ICTokenFactory, Recoverable {
bytes32 key,
uint256 expiryDate
) external override returns (address deployed) {
_mustBeUnpaused();
s.mustNotBePaused();
s.mustBeValidCoverKey(key);
s.callerMustBePolicyContract();

Expand Down
10 changes: 5 additions & 5 deletions contracts/core/claims/Processor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,23 @@ contract Processor is IClaimsProcessor, Recoverable {
uint256 incidentDate,
uint256 amount
) external override nonReentrant {
require(validate(cToken, key, incidentDate), "Claim not valid");
validate(cToken, key, incidentDate);

IERC20(cToken).ensureTransferFrom(super._msgSender(), address(this), amount);
IERC20(cToken).ensureTransferFrom(msg.sender, address(this), amount);
ICToken(cToken).burn(amount);

IVault vault = s.getVault(key);
vault.transferGovernance(key, super._msgSender(), amount);
vault.transferGovernance(key, msg.sender, amount);

emit Claimed(cToken, key, super._msgSender(), incidentDate, amount);
emit Claimed(cToken, key, msg.sender, incidentDate, amount);
}

function validate(
address cToken,
bytes32 key,
uint256 incidentDate
) public view override returns (bool) {
_mustBeUnpaused();
s.mustNotBePaused();
s.mustBeValidClaim(key, cToken, incidentDate);

return true;
Expand Down
Loading

0 comments on commit b0a6598

Please sign in to comment.