Skip to content

Commit

Permalink
Merge pull request #1108 from NexusMutual/release-candidate
Browse files Browse the repository at this point in the history
Release v2.6.0: Cover Re Investment
  • Loading branch information
roxdanila authored May 22, 2024
2 parents 59287e2 + 76a50a5 commit d72648f
Show file tree
Hide file tree
Showing 44 changed files with 5,005 additions and 126 deletions.
3 changes: 2 additions & 1 deletion contracts/interfaces/IMasterAwareV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ interface IMasterAwareV2 {
AS, // Assessment.sol
CI, // IndividualClaims.sol - Claims for Individuals
CG, // YieldTokenIncidents.sol - Claims for Groups
RA // Ramm.sol
RA, // Ramm.sol
ST // SafeTracker.sol
}

function changeMasterAddress(address masterAddress) external;
Expand Down
2 changes: 2 additions & 0 deletions contracts/interfaces/IPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ struct Asset {

interface IPool {

function swapOperator() external view returns (address);

function getAsset(uint assetId) external view returns (Asset memory);

function getAssets() external view returns (Asset[] memory);
Expand Down
19 changes: 19 additions & 0 deletions contracts/interfaces/ISafeTracker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity >=0.5.0;

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

interface ISafeTracker is IERC20 {

function symbol() external view returns (string memory);

function decimals() external view returns (uint8);

function safe() external view returns (address);

event CoverReInvestmentUSDCUpdated(uint investedUSDC);

error OnlySafe();
error InvestmentSurpassesLimit();
}
7 changes: 6 additions & 1 deletion contracts/interfaces/ISwapOperator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,16 @@ interface ISwapOperator {

function recoverAsset(address assetAddress, address receiver) external;

function requestAsset(address asset, uint amount) external;

function transferRequestedAsset(address requestedAsset, uint requestedAmount) external;

/* ========== EVENTS AND ERRORS ========== */

event OrderPlaced(GPv2Order.Data order);
event OrderClosed(GPv2Order.Data order, uint filledAmount);
event Swapped(address indexed fromAsset, address indexed toAsset, uint amountIn, uint amountOut);
event TransferredToSafe(address asset, uint amount);

// Swap Order
error OrderInProgress(bytes currentOrderUID);
Expand All @@ -60,7 +65,7 @@ interface ISwapOperator {

// Access Controls
error OnlyController();

// Transfer
error TransferFailed(address to, uint value, address token);

Expand Down
17 changes: 17 additions & 0 deletions contracts/mocks/SafeTracker/STMockPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;

import "../common/PoolMock.sol";
import "../../interfaces/IPriceFeedOracle.sol";

contract STMockPool is PoolMock {

IPriceFeedOracle public override priceFeedOracle;

constructor(address _priceFeedOracle, address _swapOperator) {
priceFeedOracle = IPriceFeedOracle(_priceFeedOracle);
swapOperator = _swapOperator;
}

}
48 changes: 48 additions & 0 deletions contracts/mocks/SafeTracker/STMockSwapOperator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;

import "../../interfaces/ISwapOperator.sol";

contract STMockSwapOperator is ISwapOperator {

function requestAsset(address, uint) external virtual pure {
revert("Unsupported");
}

function transferRequestedAsset(address, uint) external virtual pure {
revert("Unsupported");
}

function getDigest(GPv2Order.Data calldata) external virtual view returns (bytes32) {
revert("Unsupported");
}

function getUID(GPv2Order.Data calldata) external virtual view returns (bytes memory) {
revert("Unsupported");
}

function orderInProgress() external virtual pure returns (bool) {
revert("Unsupported");
}

function placeOrder(GPv2Order.Data calldata, bytes calldata) external virtual {
revert("Unsupported");
}

function closeOrder(GPv2Order.Data calldata) external virtual {
revert("Unsupported");
}

function swapEnzymeVaultShareForETH(uint, uint) external virtual {
revert("Unsupported");
}

function swapETHForEnzymeVaultShare(uint, uint) external virtual {
revert("Unsupported");
}

function recoverAsset(address, address) external virtual {
revert("Unsupported");
}
}
1 change: 1 addition & 0 deletions contracts/mocks/common/PoolMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ contract PoolMock is IPool {
using SafeUintCast for uint;

Asset[] public assets;
address public swapOperator;

uint public constant MCR_RATIO_DECIMALS = 4;
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
Expand Down
29 changes: 29 additions & 0 deletions contracts/mocks/common/PriceFeedOracleMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;

import "../../interfaces/IPriceFeedOracle.sol";

contract PriceFeedOracleMock is IPriceFeedOracle {

address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
mapping(address => OracleAsset) public assets;

uint public ethRate;

constructor(uint _ethRate) {
ethRate = _ethRate;
}

function getAssetToEthRate(address) public view returns (uint) {
return ethRate;
}

function getAssetForEth(address, uint ethIn) external view returns (uint) {
return ethIn * ethRate;
}

function getEthForAsset(address, uint amount) external view returns (uint) {
return amount / ethRate;
}
}
14 changes: 10 additions & 4 deletions contracts/modules/capital/PriceFeedOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,22 @@ contract PriceFeedOracle is IPriceFeedOracle {
mapping(address => OracleAsset) public assets;

address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public immutable safeTracker;

constructor(
address[] memory _assetAddresses,
address[] memory _assetAggregators,
uint8[] memory _assetDecimals
uint8[] memory _assetDecimals,
address _safeTracker
) {
require(
_assetAddresses.length == _assetAggregators.length && _assetAggregators.length == _assetDecimals.length,
"PriceFeedOracle: different args length"
);
require(_safeTracker != address(0), "PriceFeedOracle: safeTracker cannot be zero address");

safeTracker = _safeTracker;
assets[_safeTracker] = OracleAsset(Aggregator(_safeTracker), 18);

for (uint i = 0; i < _assetAddresses.length; i++) {
assets[_assetAddresses[i]] = OracleAsset(Aggregator(_assetAggregators[i]), _assetDecimals[i]);
Expand All @@ -32,7 +38,7 @@ contract PriceFeedOracle is IPriceFeedOracle {
* @return price in ether
*/
function getAssetToEthRate(address assetAddress) public view returns (uint) {
if (assetAddress == ETH) {
if (assetAddress == ETH || assetAddress == safeTracker) {
return 1 ether;
}

Expand All @@ -47,7 +53,7 @@ contract PriceFeedOracle is IPriceFeedOracle {
* @return asset amount
*/
function getAssetForEth(address assetAddress, uint ethIn) external view returns (uint) {
if (assetAddress == ETH) {
if (assetAddress == ETH || assetAddress == safeTracker) {
return ethIn;
}

Expand All @@ -64,7 +70,7 @@ contract PriceFeedOracle is IPriceFeedOracle {
* @return amount of ether
*/
function getEthForAsset(address assetAddress, uint amount) external view returns (uint) {
if (assetAddress == ETH) {
if (assetAddress == ETH || assetAddress == safeTracker) {
return amount;
}

Expand Down
157 changes: 157 additions & 0 deletions contracts/modules/capital/SafeTracker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;

import "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol";
import "../../abstract/MasterAwareV2.sol";
import "../../interfaces/IPool.sol";
import "../../interfaces/IPriceFeedOracle.sol";
import "../../interfaces/ISafeTracker.sol";

contract SafeTracker is ISafeTracker, MasterAwareV2 {

uint public coverReInvestmentUSDC;

string public constant symbol = "NXMIS";
string public constant name = "NXMIS";
uint8 public constant decimals = 18;

address public immutable safe;
uint public immutable investmentLimit;

IERC20 public immutable usdc;
IERC20 public immutable dai;
IERC20 public immutable weth;
IERC20 public immutable aweth;
IERC20 public immutable debtUsdc;

/* ========== CONSTRUCTOR ========== */

constructor(
uint _investmentLimit,
address _safe,
address _usdc,
address _dai,
address _weth,
address _aweth,
address _debtUsdc
) {
require(
_usdc != address(0) && _dai != address(0) && _aweth != address(0) && _aweth != address(0) && _debtUsdc != address(0),
"SafeTracker: tokens address cannot be zero address"
);

investmentLimit = _investmentLimit;
safe = _safe;
usdc = IERC20(_usdc);
dai = IERC20(_dai);
weth = IERC20(_weth);
aweth = IERC20(_aweth);
debtUsdc = IERC20(_debtUsdc);
}

/**
* @dev Gets the balance of the safe
* @return An uint256 representing the amount of the safe.
*/
function totalSupply() external view returns (uint256) {
return _calculateBalance();
}

/**
* @dev Gets the balance of the safe
* @return An uint256 representing the amount of the safe.
*/
function balanceOf(address account) external view returns (uint256) {
if (account != address(pool())) {
return 0;
}
return _calculateBalance();
}

/**
* @dev Updates invested USDC in CoverRe
*/
function updateCoverReInvestmentUSDC(uint investedUSDC) external {
if (msg.sender != safe) {
revert OnlySafe();
}
if (investedUSDC > investmentLimit) {
revert InvestmentSurpassesLimit();
}
coverReInvestmentUSDC = investedUSDC;

emit CoverReInvestmentUSDCUpdated(investedUSDC);
}

/**
* @dev emits Transfer event only if it's called by Pool or SwapOperator
*/
function transfer(address to, uint256 amount) external returns (bool) {
return _transfer(msg.sender, to, amount);
}

/**
* @dev emits Transfer event only if it's called by Pool or SwapOperator
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
return _transfer(from, to, amount);
}

function allowance(address, address) external pure returns (uint256) {
return 0;
}

function approve(address spender, uint256 value) external override returns (bool) {
emit Approval(msg.sender, spender, value);
return true;
}

function latestAnswer() external pure returns (uint256) {
return 1e18;
}

/**
* @dev Fetches all necessary information about the tokens that are used in the safe and calculates the balance
* @return balance ETH value of the safe.
*/
function _calculateBalance() internal view returns (uint256 balance) {

// eth in the safe, weth and aweth balance, weth and aweth are 1:1 to eth
uint ethAmount = address(safe).balance + weth.balanceOf(safe) + aweth.balanceOf(safe);

IPriceFeedOracle priceFeedOracle = pool().priceFeedOracle();

// dai in the safe
uint daiAmount = dai.balanceOf(safe);
uint daiValueInEth = priceFeedOracle.getEthForAsset(address(dai), daiAmount);

// usdc actually in the safe and usdc invested in CoverRe
uint usdcAmount = usdc.balanceOf(safe) + coverReInvestmentUSDC;
uint usdcValueInEth = priceFeedOracle.getEthForAsset(address(usdc), usdcAmount);

// usdc debt (borrowed usdc)
uint debtUsdcAmount = debtUsdc.balanceOf(safe);
uint debtUsdcValueInEth = priceFeedOracle.getEthForAsset(address(usdc), debtUsdcAmount);

return ethAmount + usdcValueInEth + daiValueInEth - debtUsdcValueInEth;
}

function _transfer(address from, address to, uint256 amount) internal returns (bool) {
if (amount == 0 || msg.sender == address(pool())) {
emit Transfer(from, to, amount);
return true;
}
revert("Amount exceeds balance");
}

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

function pool() internal view returns (IPool) {
return IPool(internalContracts[uint(ID.P1)]);
}

function changeDependentContractAddress() external override {
internalContracts[uint(ID.P1)] = master.getLatestAddress("P1");
}
}
Loading

0 comments on commit d72648f

Please sign in to comment.