notional-aaveV2 refinance

This commit is contained in:
Richa-iitr 2022-06-10 21:45:47 +05:30
parent ae51a19713
commit fbca8bdfe3
6 changed files with 734 additions and 0 deletions

View File

@ -0,0 +1,16 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
import { TokenInterface } from "./interface.sol";
contract Events {
event LogRefinance(
uint256 collateralFee,
uint256 debtFee,
address[] tokens,
uint256[] borrowAmts,
uint256[] depositAmts,
uint256[] borrowMarketIndices,
uint256[] maxBorrowingRates
);
}

View File

@ -0,0 +1,194 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;
// import { Helpers } from "./helpers.sol";
import { Basic } from "../../common/basic.sol";
import { Token, NotionalInterface, BalanceAction, BalanceActionWithTrades, DepositActionType, AaveV2LendingPoolProviderInterface, AaveV2DataProviderInterface, AaveV2Interface } from "./interface.sol";
import { TokenInterface } from "../../common/interfaces.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Helpers is Basic {
using SafeERC20 for IERC20;
uint256 internal constant LEND_TRADE = 0;
uint256 internal constant BORROW_TRADE = 1;
uint256 internal constant INTERNAL_TOKEN_PRECISION = 1e8;
uint256 internal constant ETH_CURRENCY_ID = 1;
uint256 internal constant MAX_DEPOSIT = type(uint256).max;
address payable constant feeCollector =
0xb1DC62EC38E6E3857a887210C38418E4A17Da5B2;
AaveV2DataProviderInterface internal constant aaveData =
AaveV2DataProviderInterface(0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d);
NotionalInterface internal constant notional =
NotionalInterface(0x1344A36A1B56144C3Bc62E7757377D288fDE0369);
/**
* @dev get Aave Lending Pool Provider
*/
AaveV2LendingPoolProviderInterface internal constant getAaveV2Provider =
AaveV2LendingPoolProviderInterface(
0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5
);
/**
* @dev get Referral Code
*/
uint16 internal constant getReferralCode = 3228;
struct NotionalBorrowData {
uint256 length;
// debt fee
uint256 fee;
// borrow tokens
address[] tokens;
// true, then redeems the borrowed balance from cTokens to underlying token before transferring to account
bool[] redeemToUnderlying;
// borrow amts im underlying token denomination
uint256[] amts;
// aave V2 borrow rate modes
uint256[] rateModes;
// fCashAmount accounting for the borrowAmt with debtfee, calculated through SDK
uint256[] fCashAmount;
// borrow rate max, 0 means any is acceptable
uint256[] maxBorrowRate;
// notion defined currency IDs of borrowTokens
uint256[] currencyIDs;
// borrow markets based on the maturity where user wants to borrow
uint256[] marketIndex;
}
// withdraw balance of Aave v2
function getWithdrawBalanceV2(
AaveV2DataProviderInterface aaveData,
address token
) internal view returns (uint256 bal) {
(bal, , , , , , , , ) = aaveData.getUserReserveData(
token,
address(this)
);
}
function getAssetOrUnderlyingToken(uint16 currencyId, bool underlying)
internal
view
returns (address)
{
// prettier-ignore
(Token memory assetToken, Token memory underlyingToken) = notional.getCurrency(currencyId);
return
underlying ? underlyingToken.tokenAddress : assetToken.tokenAddress;
}
function toUint88(uint256 value) internal pure returns (uint88) {
require(value <= type(uint88).max, "uint88 value overflow");
return uint88(value);
}
function toUint32(uint256 value) internal pure returns (uint32) {
require(value <= type(uint32).max, "uint32 value overflow");
return uint32(value);
}
function toUint16(uint256 value) internal pure returns (uint16) {
require(value <= type(uint16).max, "uint16 value overflow");
return uint16(value);
}
function toUint8(uint256 value) internal pure returns (uint8) {
require(value <= type(uint8).max, "uint8 value overflow");
return uint8(value);
}
function getAaveV2PaybackAmt(uint256 rateMode, address token)
internal
returns (uint256 bal)
{
if (rateMode == 1) {
(, bal, , , , , , , ) = aaveData.getUserReserveData(
token,
address(this)
);
} else {
(, , bal, , , , , , ) = aaveData.getUserReserveData(
token,
address(this)
);
}
}
function calculateFee(
uint256 amount,
uint256 fee,
bool toAdd
) internal pure returns (uint256 feeAmount, uint256 _amount) {
feeAmount = wmul(amount, fee);
_amount = toAdd ? add(amount, feeAmount) : sub(amount, feeAmount);
}
function transferFees(address token, uint256 feeAmt) internal {
if (feeAmt > 0) {
if (token == ethAddr) {
feeCollector.transfer(feeAmt);
} else {
IERC20(token).safeTransfer(feeCollector, feeAmt);
}
}
}
function calculateAndTransferFees(
address token,
uint256 amt,
uint256 fee,
bool toAdd
) internal {
token = (token == wethAddr) ? ethAddr : token;
(uint256 feeAmt, uint256 _amt) = calculateFee(amt, fee, toAdd);
transferFees(token, feeAmt);
}
function encodeBorrowTrade(
uint256 marketIndex,
uint256 fCashAmount,
uint256 maxBorrowRate
) internal pure returns (bytes32) {
return
(bytes32(BORROW_TRADE) << 248) |
(bytes32(marketIndex) << 240) |
(bytes32(fCashAmount) << 152) |
(bytes32(maxBorrowRate) << 120);
}
function getTokens(uint256 length, uint256[] memory currencyIDs)
internal
view
returns (address[] memory)
{
address[] memory tokens = new address[](length);
for (uint256 i = 0; i < length; i++) {
uint16 _currencyId = toUint16(currencyIDs[i]);
if (_currencyId == ETH_CURRENCY_ID) {
tokens[i] = wethAddr;
} else {
tokens[i] = getAssetOrUnderlyingToken(_currencyId, true);
}
}
return tokens;
}
function getTokenInterfaces(uint256 length, address[] memory _tokens)
internal
pure
returns (TokenInterface[] memory)
{
TokenInterface[] memory tokens = new TokenInterface[](length);
for (uint256 i = 0; i < length; i++) {
tokens[i] = TokenInterface(_tokens[i]);
}
return tokens;
}
}

View File

@ -0,0 +1,73 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;
import { Helpers } from "../helpers.sol";
import { AaveV2DataProviderInterface, AaveV2LendingPoolProviderInterface, AaveV2Interface } from "../interface.sol";
import { TokenInterface } from "../../../common/interfaces.sol";
contract AaveHelpers is Helpers {
// payback token to Aave V2, amts sent already checked for MAX
function _aaveV2PaybackOne(
AaveV2Interface aave,
TokenInterface token,
uint256 amt,
uint256 rateMode
) internal {
if (amt > 0) {
bool isEth = address(token) == wethAddr;
convertEthToWeth(isEth, token, amt);
approve(token, address(aave), amt);
aave.repay(address(token), amt, rateMode, address(this));
}
}
function _aaveV2Payback(
uint256 length,
AaveV2Interface aave,
TokenInterface[] memory tokens,
uint256[] memory amts,
uint256[] memory rateModes
) internal {
for (uint256 i = 0; i < length; i++) {
_aaveV2PaybackOne(aave, tokens[i], amts[i], rateModes[i]);
}
}
// withdraw aToken from aaveV2
function _aaveV2WithdrawOne(
AaveV2Interface aave,
AaveV2DataProviderInterface aaveData,
TokenInterface token,
uint256 amt
) internal returns (uint256 _amt) {
if (amt > 0) {
bool isEth = address(token) == wethAddr;
aave.withdraw(address(token), amt, address(this));
_amt = amt == uint256(-1)
? getWithdrawBalanceV2(aaveData, address(token))
: amt;
convertWethToEth(isEth, token, _amt);
}
}
function _aaveV2Withdraw(
AaveV2Interface aave,
AaveV2DataProviderInterface aaveData,
uint256 length,
TokenInterface[] memory tokens,
uint256[] memory amts
) internal returns (uint256[] memory) {
uint256[] memory finalAmts = new uint256[](length);
for (uint256 i = 0; i < length; i++) {
finalAmts[i] = _aaveV2WithdrawOne(
aave,
aaveData,
tokens[i],
amts[i]
);
}
return finalAmts;
}
}

View File

@ -0,0 +1,136 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;
import { Helpers } from "../helpers.sol";
import { Token, NotionalInterface, BalanceAction, BalanceActionWithTrades, DepositActionType, AaveV2DataProviderInterface } from "../interface.sol";
contract NotionalHelpers is Helpers {
function _notionalBorrowOne(
uint256 fee,
bool redeemToUnderlying,
uint256 fCashAmount,
uint256 maxBorrowRate,
uint16 currencyID,
uint256 marketIndex
) internal {
//collateral should be depositied beforehand in other currency
BalanceActionWithTrades[]
memory actions = new BalanceActionWithTrades[](1);
bytes32[] memory trades = new bytes32[](1);
trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate);
actions[0].actionType = DepositActionType.None;
actions[0].currencyId = currencyID;
actions[0].withdrawEntireCashBalance = true;
actions[0].depositActionAmount = 0;
actions[0].redeemToUnderlying = redeemToUnderlying;
actions[0].trades = trades;
notional.batchBalanceAndTradeAction(address(this), actions);
}
/// @return finalAmts payback amount on Aave
function _notionalBorrow(NotionalBorrowData memory data)
internal
returns (uint256[] memory)
{
uint256[] memory finalAmts = new uint256[](data.length);
for (uint256 i = 0; i < data.length; i++) {
uint16 _currencyID = toUint16(data.currencyIDs[i]);
uint256 _amt = data.amts[i];
//borrow token on Notional
_notionalBorrowOne(
data.fee,
data.redeemToUnderlying[i],
data.fCashAmount[i],
data.maxBorrowRate[i],
_currencyID,
data.marketIndex[i]
);
//calculating payback amounts for Aave v2
if (_amt == uint256(-1)) {
_amt = getAaveV2PaybackAmt(data.rateModes[i], data.tokens[i]);
}
finalAmts[i] = _amt;
//calculate and transferFees
calculateAndTransferFees(data.tokens[i], _amt, data.fee, true);
}
}
// deposit as collateral i.e. reduces the risk of liquidation or mint ntokens i.e. provides liquidity, this method doesn't lends to the market at fixed rate
function _notionalDepositOne(
uint256 fee,
uint256 amt,
address token,
bool mintNToken,
uint16 currencyID
) internal {
if (amt > 0) {
(uint256 feeAmt, uint256 depositAmount) = calculateFee(
amt,
fee,
false
);
token = (token == wethAddr) ? ethAddr : token;
transferFees(token, feeAmt);
if (mintNToken) {
//deposit cash and mint nTokens
BalanceAction[] memory action = new BalanceAction[](1);
action[0].actionType = DepositActionType
.DepositUnderlyingAndMintNToken;
action[0].currencyId = toUint16(currencyID);
action[0].depositActionAmount = depositAmount;
if (currencyID == ETH_CURRENCY_ID) {
notional.batchBalanceAction{ value: depositAmount }(
address(this),
action
);
} else {
notional.batchBalanceAction(address(this), action);
}
} else {
//deposit as collateral
if (currencyID == ETH_CURRENCY_ID) {
notional.depositUnderlyingToken{ value: depositAmount }(
address(this),
currencyID,
depositAmount
);
} else {
notional.depositAssetToken(
address(this),
currencyID,
depositAmount
);
}
}
}
}
// deposits on notional
function _notionalDeposit(
uint256 length,
uint256 fee,
uint256[] memory currencyIDs,
uint256[] memory amts,
address[] memory tokens,
bool[] memory mintNTokens
) internal {
for (uint256 i = 0; i < length; i++) {
_notionalDepositOne(
fee,
amts[i],
tokens[i],
mintNTokens[i],
toUint16(currencyIDs[i])
);
}
}
}

View File

@ -0,0 +1,172 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma abicoder v2;
import { TokenInterface } from "../../common/interfaces.sol";
/// @notice Different types of internal tokens
/// - UnderlyingToken: underlying asset for a cToken (except for Ether)
/// - cToken: Compound interest bearing token
/// - cETH: Special handling for cETH tokens
/// - Ether: the one and only
/// - NonMintable: tokens that do not have an underlying (therefore not cTokens)
enum TokenType {
UnderlyingToken,
cToken,
cETH,
Ether,
NonMintable
}
/// @notice Specifies different deposit actions that can occur during BalanceAction or BalanceActionWithTrades
enum DepositActionType {
// No deposit action
None,
// Deposit asset cash, depositActionAmount is specified in asset cash external precision
DepositAsset,
// Deposit underlying tokens that are mintable to asset cash, depositActionAmount is specified in underlying token
// external precision
DepositUnderlying,
// Deposits specified asset cash external precision amount into an nToken and mints the corresponding amount of
// nTokens into the account
DepositAssetAndMintNToken,
// Deposits specified underlying in external precision, mints asset cash, and uses that asset cash to mint nTokens
DepositUnderlyingAndMintNToken,
// Redeems an nToken balance to asset cash. depositActionAmount is specified in nToken precision. Considered a deposit action
// because it deposits asset cash into an account. If there are fCash residuals that cannot be sold off, will revert.
RedeemNToken,
// Converts specified amount of asset cash balance already in Notional to nTokens. depositActionAmount is specified in
// Notional internal 8 decimal precision.
ConvertCashToNToken
}
/// @notice Defines a balance action with a set of trades to do as well
struct BalanceActionWithTrades {
DepositActionType actionType;
uint16 currencyId;
uint256 depositActionAmount;
uint256 withdrawAmountInternalPrecision;
bool withdrawEntireCashBalance;
bool redeemToUnderlying;
// Array of tightly packed 32 byte objects that represent trades. See TradeActionType documentation
bytes32[] trades;
}
/// @notice Defines a balance action for batchAction
struct BalanceAction {
// Deposit action to take (if any)
DepositActionType actionType;
uint16 currencyId;
// Deposit action amount must correspond to the depositActionType, see documentation above.
uint256 depositActionAmount;
// Withdraw an amount of asset cash specified in Notional internal 8 decimal precision
uint256 withdrawAmountInternalPrecision;
// If set to true, will withdraw entire cash balance. Useful if there may be an unknown amount of asset cash
// residual left from trading.
bool withdrawEntireCashBalance;
// If set to true, will redeem asset cash to the underlying token on withdraw.
bool redeemToUnderlying;
}
struct Token {
// Address of the token
address tokenAddress;
// True if the token has a transfer fee which is used internally to determine
// the proper balance change
bool hasTransferFee;
// Decimal precision of the token as a power of 10
int256 decimals;
// Type of token, enumerated above
TokenType tokenType;
// Used internally for tokens that have a collateral cap, zero if there is no cap
uint256 maxCollateralBalance;
}
interface NotionalInterface {
function getCurrency(uint16 currencyId)
external
view
returns (Token memory assetToken, Token memory underlyingToken);
function getAccountBalance(uint16 currencyId, address account)
external
view
returns (
int256 cashBalance,
int256 nTokenBalance,
uint256 lastClaimTime
);
function depositUnderlyingToken(
address account,
uint16 currencyId,
uint256 amountExternalPrecision
) external payable returns (uint256);
function depositAssetToken(
address account,
uint16 currencyId,
uint256 amountExternalPrecision
) external returns (uint256);
function withdraw(
uint16 currencyId,
uint88 amountInternalPrecision,
bool redeemToUnderlying
) external returns (uint256);
function nTokenClaimIncentives() external returns (uint256);
function nTokenRedeem(
address redeemer,
uint16 currencyId,
uint96 tokensToRedeem_,
bool sellTokenAssets,
bool acceptResidualAssets
) external returns (int256);
function batchBalanceAction(
address account,
BalanceAction[] calldata actions
) external payable;
function batchBalanceAndTradeAction(
address account,
BalanceActionWithTrades[] calldata actions
) external payable;
}
///@dev Aave Interfaces
// Aave Protocol Data Provider
interface AaveV2DataProviderInterface {
function getUserReserveData(address _asset, address _user) external view returns (
uint256 currentATokenBalance,
uint256 currentStableDebt,
uint256 currentVariableDebt,
uint256 principalStableDebt,
uint256 scaledVariableDebt,
uint256 stableBorrowRate,
uint256 liquidityRate,
uint40 stableRateLastUpdated,
bool usageAsCollateralEnabled
);
}
// Aave v2 Helpers
interface AaveV2Interface {
function deposit(address _asset, uint256 _amount, address _onBehalfOf, uint16 _referralCode) external;
function withdraw(address _asset, uint256 _amount, address _to) external;
function borrow(
address _asset,
uint256 _amount,
uint256 _interestRateMode,
uint16 _referralCode,
address _onBehalfOf
) external;
function repay(address _asset, uint256 _amount, uint256 _rateMode, address _onBehalfOf) external;
function setUserUseReserveAsCollateral(address _asset, bool _useAsCollateral) external;
}
interface AaveV2LendingPoolProviderInterface {
function getLendingPool() external view returns (address);
}

View File

@ -0,0 +1,143 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;
/**
* @title Refinance Notional, Aave v2.
* @dev Refinancing.
*/
import { AaveV2Interface, AaveV2DataProviderInterface, AaveV2LendingPoolProviderInterface } from "./interface.sol";
import { TokenInterface } from "../../common/interfaces.sol";
import { AaveHelpers } from "./helpers/aaveV2.sol";
import { NotionalHelpers } from "./helpers/notional.sol";
contract RefinanceResolver is AaveHelpers, NotionalHelpers {
struct RefinanceData {
uint256 collateralFee;
uint256 debtFee;
uint256[] currencyIDs;
uint256[] borrowMarketIndices;
uint256[] borrowfCashAmts;
uint256[] borrowAmts;
uint256[] withdrawAmts;
uint256[] paybackRateModes;
uint256[] maxBorrowingRates;
bool[] redeemBorrowToUnderlying;
bool[] mintNTokens;
}
struct RefinanceInternalData {
AaveV2Interface aave;
AaveV2DataProviderInterface aaveData;
uint256[] depositAmts;
uint256[] paybackAmts;
address[] tokens;
TokenInterface[] tokenInterfaces;
}
function _refinance(RefinanceData calldata data)
internal
returns (string memory _eventName, bytes memory _eventParam)
{
uint256 length = data.currencyIDs.length;
require(data.borrowMarketIndices.length == length, "length-mismatch");
require(data.borrowfCashAmts.length == length, "length-mismatch");
require(data.borrowAmts.length == length, "length-mismatch");
require(data.withdrawAmts.length == length, "length-mismatch");
require(data.paybackRateModes.length == length, "length-mismatch");
require(data.maxBorrowingRates.length == length, "length-mismatch");
require(
data.redeemBorrowToUnderlying.length == length,
"length-mismatch"
);
require(data.mintNTokens.length == length, "length-mismatch");
RefinanceInternalData memory refinanceInternalData;
refinanceInternalData.aave = AaveV2Interface(
AaveV2LendingPoolProviderInterface(getAaveV2Provider)
.getLendingPool()
);
refinanceInternalData.aaveData = aaveData;
refinanceInternalData.depositAmts;
refinanceInternalData.paybackAmts;
refinanceInternalData.tokens = getTokens(length, data.currencyIDs);
refinanceInternalData.tokenInterfaces = getTokenInterfaces(
length,
refinanceInternalData.tokens
);
// Aave v2 to Notional
NotionalBorrowData memory _notionalBorrowData;
_notionalBorrowData.length = length;
_notionalBorrowData.fee = data.debtFee;
_notionalBorrowData.tokens = refinanceInternalData.tokens;
_notionalBorrowData.redeemToUnderlying = data.redeemBorrowToUnderlying;
_notionalBorrowData.amts = data.borrowAmts;
_notionalBorrowData.rateModes = data.paybackRateModes;
_notionalBorrowData.fCashAmount = data.borrowfCashAmts;
_notionalBorrowData.maxBorrowRate = data.maxBorrowingRates;
_notionalBorrowData.currencyIDs = data.currencyIDs;
_notionalBorrowData.marketIndex = data.borrowMarketIndices;
//borrow on Notional
refinanceInternalData.paybackAmts = _notionalBorrow(
_notionalBorrowData
);
//payback debt on Aave-v2
_aaveV2Payback(
length,
refinanceInternalData.aave,
refinanceInternalData.tokenInterfaces,
refinanceInternalData.paybackAmts,
data.paybackRateModes
);
//withdraw aTokens from Aave-v2
refinanceInternalData.depositAmts = _aaveV2Withdraw(
refinanceInternalData.aave,
refinanceInternalData.aaveData,
length,
refinanceInternalData.tokenInterfaces,
data.withdrawAmts
);
//deposit on Notional
_notionalDeposit(
length,
data.collateralFee,
data.currencyIDs,
refinanceInternalData.depositAmts,
refinanceInternalData.tokens,
data.mintNTokens
);
_eventName = "LogRefinance(uint,uint,address[],uint[],uint[],uint[],uint[])";
_eventParam = abi.encode(
data.collateralFee,
data.debtFee,
refinanceInternalData.tokens,
refinanceInternalData.paybackAmts,
refinanceInternalData.depositAmts,
data.borrowMarketIndices,
data.maxBorrowingRates
);
}
/**
* @dev Refinance
* @notice Refinancing between Notional and Aave V2
* @param data refinance data.
*/
function refinance(RefinanceData calldata data)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
(_eventName, _eventParam) = _refinance(data);
}
}
contract ConnectV2Refinance is RefinanceResolver {
string public name = "Refinance-v1.2";
}