Skip to content

Commit

Permalink
Merge pull request #1086 from NexusMutual/release-candidate
Browse files Browse the repository at this point in the history
Release 2.5.0 (merge to master after the contracts are upgraded onchain)
  • Loading branch information
roxdanila authored May 21, 2024
2 parents 24f8711 + fd67642 commit 59287e2
Show file tree
Hide file tree
Showing 60 changed files with 3,380 additions and 1,026 deletions.
38 changes: 0 additions & 38 deletions .github/pull_request_template.md

This file was deleted.

2 changes: 1 addition & 1 deletion contracts/external/WETH9.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../interfaces/IERC20.sol";

contract WETH9 is IERC20 {
string public name = "Wrapped Ether";
Expand Down
184 changes: 184 additions & 0 deletions contracts/external/cover/CoverBroker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;

import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-v4/access/Ownable.sol";

import "../../interfaces/ICover.sol";
import "../../interfaces/ICoverBroker.sol";
import "../../interfaces/IMemberRoles.sol";
import "../../interfaces/INXMMaster.sol";
import "../../interfaces/INXMToken.sol";
import "../../interfaces/IPool.sol";

/// @title Cover Broker Contract
/// @notice Enables non-members of the mutual to purchase cover policies.
/// Supports payments in ETH and pool supported ERC20 assets.
/// For NXM payments by members, please call Cover.buyCover instead.
/// @dev See supported ERC20 asset payments via pool.getAssets.
contract CoverBroker is ICoverBroker, Ownable {
using SafeERC20 for IERC20;

// Immutables
ICover public immutable cover;
IMemberRoles public immutable memberRoles;
INXMToken public immutable nxmToken;
INXMMaster public immutable master;

// Constants
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint private constant ETH_ASSET_ID = 0;
uint private constant NXM_ASSET_ID = type(uint8).max;

constructor(
address _cover,
address _memberRoles,
address _nxmToken,
address _master,
address _owner
) {
cover = ICover(_cover);
memberRoles = IMemberRoles(_memberRoles);
nxmToken = INXMToken(_nxmToken);
master = INXMMaster(_master);
transferOwnership(_owner);
}

/// @notice Buys cover on behalf of the caller. Supports payments in ETH and pool supported ERC20 assets.
/// @dev For ERC20 payments, ensure the Cover contract is approved to spend the tokens first (maxApproveCoverContract).
/// See supported ERC20 asset payments via pool.getAssets.
/// @param params The parameters required to buy cover.
/// @param poolAllocationRequests The allocation requests for the pool's liquidity.
/// @return coverId The ID of the purchased cover.
function buyCover(
BuyCoverParams calldata params,
PoolAllocationRequest[] calldata poolAllocationRequests
) external payable returns (uint coverId) {

if (params.owner == address(0) || params.owner == address(this)) {
revert InvalidOwnerAddress();
}

if (params.paymentAsset == NXM_ASSET_ID) {
revert InvalidPaymentAsset();
}

// ETH payment
if (params.paymentAsset == ETH_ASSET_ID) {
return _buyCoverEthPayment(params, poolAllocationRequests);
}

// msg.value must be 0 if not an ETH payment
if (msg.value > 0) {
revert InvalidPayment();
}

// ERC20 payment
return _buyCoverErc20Payment(params, poolAllocationRequests);
}

/// @notice Handles ETH payments for buying cover.
/// @dev Calculates ETH refunds if applicable and sends back to msg.sender.
/// @param params The parameters required to buy cover.
/// @param poolAllocationRequests The allocation requests for the pool's liquidity.
/// @return coverId The ID of the purchased cover.
function _buyCoverEthPayment(
BuyCoverParams calldata params,
PoolAllocationRequest[] calldata poolAllocationRequests
) internal returns (uint coverId) {

uint ethBalanceBefore = address(this).balance - msg.value;
coverId = cover.buyCover{value: msg.value}(params, poolAllocationRequests);
uint ethBalanceAfter = address(this).balance;

// transfer any ETH refund back to msg.sender
if (ethBalanceAfter > ethBalanceBefore) {
uint ethRefund = ethBalanceAfter - ethBalanceBefore;
(bool sent,) = payable(msg.sender).call{value: ethRefund}("");
if (!sent) {
revert TransferFailed(msg.sender, ethRefund, ETH);
}
}
}

/// @notice Handles ERC20 payments for buying cover.
/// @dev Transfers ERC20 tokens from the caller to the contract, then buys cover on behalf of the caller.
/// Calculates ERC20 refunds if any and sends back to msg.sender.
/// @param params The parameters required to buy cover.
/// @param poolAllocationRequests The allocation requests for the pool's liquidity.
/// @return coverId The ID of the purchased cover.
function _buyCoverErc20Payment(
BuyCoverParams calldata params,
PoolAllocationRequest[] calldata poolAllocationRequests
) internal returns (uint coverId) {

address paymentAsset = _pool().getAsset(params.paymentAsset).assetAddress;
IERC20 erc20 = IERC20(paymentAsset);

uint erc20BalanceBefore = erc20.balanceOf(address(this));

erc20.safeTransferFrom(msg.sender, address(this), params.maxPremiumInAsset);
coverId = cover.buyCover(params, poolAllocationRequests);

uint erc20BalanceAfter = erc20.balanceOf(address(this));

// send any ERC20 refund back to msg.sender
if (erc20BalanceAfter > erc20BalanceBefore) {
uint erc20Refund = erc20BalanceAfter - erc20BalanceBefore;
erc20.safeTransfer(msg.sender, erc20Refund);
}
}

/// @notice Allows the Cover contract to spend the maximum possible amount of a specified ERC20 token on behalf of the CoverBroker.
/// @param erc20 The ERC20 token for which to approve spending.
function maxApproveCoverContract(IERC20 erc20) external onlyOwner {
erc20.safeApprove(address(cover), type(uint256).max);
}

/// @notice Switches CoverBroker's membership to a new address.
/// @dev MemberRoles contract needs to be approved to transfer NXM tokens to new membership address.
/// @param newAddress The address to which the membership will be switched.
function switchMembership(address newAddress) external onlyOwner {
nxmToken.approve(address(memberRoles), type(uint256).max);
memberRoles.switchMembership(newAddress);
}

/// @notice Recovers all available funds of a specified asset (ETH or ERC20) to the contract owner.
/// @param assetAddress The address of the asset to be rescued.
function rescueFunds(address assetAddress) external onlyOwner {

if (assetAddress == ETH) {
uint ethBalance = address(this).balance;
if (ethBalance == 0) {
revert ZeroBalance(ETH);
}

(bool sent,) = payable(msg.sender).call{value: ethBalance}("");
if (!sent) {
revert TransferFailed(msg.sender, ethBalance, ETH);
}

return;
}

IERC20 asset = IERC20(assetAddress);
uint erc20Balance = asset.balanceOf(address(this));
if (erc20Balance == 0) {
revert ZeroBalance(assetAddress);
}

asset.transfer(msg.sender, erc20Balance);
}

/* ========== DEPENDENCIES ========== */

/// @dev Fetches the Pool's instance through master contract
/// @return The Pool's instance
function _pool() internal view returns (IPool) {
return IPool(master.getLatestAddress("P1"));
}

receive() external payable {}
}
31 changes: 31 additions & 0 deletions contracts/interfaces/ICoverBroker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;

import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol";

import "../interfaces/ICover.sol";

interface ICoverBroker {

/* ==== FUNCTIONS ==== */

function buyCover(
BuyCoverParams calldata params,
PoolAllocationRequest[] calldata poolAllocationRequests
) external payable returns (uint coverId);

function maxApproveCoverContract(IERC20 token) external;

function switchMembership(address newAddress) external;

function rescueFunds(address assetAddress) external;

/* ==== ERRORS ==== */

error TransferFailed(address to, uint value, address token);
error ZeroBalance(address token);
error InvalidOwnerAddress();
error InvalidPaymentAsset();
error InvalidPayment();
}
2 changes: 2 additions & 0 deletions contracts/interfaces/ICowSettlement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ interface ICowSettlement {
}

function setPreSignature(bytes calldata orderUid, bool signed) external;

function invalidateOrder(bytes calldata orderUid) external;

function filledAmount(bytes calldata orderUid) external view returns (uint256);

Expand Down
76 changes: 76 additions & 0 deletions contracts/interfaces/IERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
pragma solidity ^0.5.0;

/**
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include
* the optional functions; to access them see {ERC20Detailed}.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);

/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);

/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);

/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);

/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);

/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);

/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
2 changes: 2 additions & 0 deletions contracts/interfaces/IMemberRoles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,6 @@ interface IMemberRoles {
event MemberJoined(address indexed newMember, uint indexed nonce);

event switchedMembership(address indexed previousMember, address indexed newMember, uint timeStamp);

event MembershipWithdrawn(address indexed member, uint timestamp);
}
Loading

0 comments on commit 59287e2

Please sign in to comment.