initial commit

This commit is contained in:
Jeff Wu 2021-11-10 14:52:16 -08:00
parent 5389b7e1a2
commit b097558460
No known key found for this signature in database
GPG Key ID: 3E6E4F431F4D40C3
4 changed files with 411 additions and 0 deletions

View File

@ -0,0 +1,5 @@
pragma solidity ^0.7.6;
contract Events {
}

View File

@ -0,0 +1,11 @@
pragma solidity ^0.7.6;
contract Helpers {
// function getUnderlyingToken(uint16 currencyId);
// function getAssetToken(uint16 currencyId);
// function getCashBalance(uint16 currencyId);
// function getNTokenBalance(uint16 currencyId);
// function convertToInternal(uint16 currencyId);
}

View File

@ -0,0 +1,395 @@
pragma solidity ^0.7.6;
import { Helpers } from "./helpers.sol";
import { Events } from "./events.sol";
/**
* @title Notional
* @notice Fixed Rate Lending and Borrowing
*/
abstract contract NotionalResolver is Events, Helpers {
/**
* @notice Deposit collateral into Notional
* @param currencyId notional defined currency id to deposit
* @param useUnderlying if true, will accept a deposit in the underlying currency (i.e DAI), if false
* will use the asset currency (i.e. cDAI)
* @param depositAmount amount of tokens to deposit
* @param getId id of depositAmount
* @param setId id to set the value of notional cash deposit increase (denominated in asset cash, i.e. cDAI)
*/
function depositCollateral(
uint16 currencyId,
bool useUnderlying,
uint depositAmount,
uint getId,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
uint assetCashDeposited;
address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId);
depositAmount = getUint(getId, depositAmount);
if (depositAmount == uint(-1)) depositAmount = ERC20(tokenAddress).balanceOf(address(this));
approve(tokenAddress, address(notional), depositAmount);
if (useUnderlying && currencyId == ETH_CURRENCY_ID) {
assetCashDeposited = notional.depositUnderlying{value: depositAmount}(address(this), currencyId, depositAmount);
} else if (useUnderlying) {
assetCashDeposited = notional.depositUnderlying{value: depositAmount}(address(this), currencyId, depositAmount);
} else {
assetCashDeposited = notional.depositAssetToken(address(this), currencyId, depositAmount);
}
setUint(setId, assetCashDeposited);
_eventName = "LogDepositCollateral(uint16,bool,uint256,uint256)";
_eventParam = abi.encode(address(this), currencyId, useUnderlying, depositAmount, assetCashDeposited);
}
/**
* @notice Withdraw collateral from Notional
* @param currencyId notional defined currency id to withdraw
* @param redeemToUnderlying if true, will redeem the amount withdrawn to the underlying currency (i.e. DAI),
* if false, will simply withdraw the asset token (i.e. cDAI)
* @param withdrawAmount amount of tokens to withdraw, denominated in asset tokens (i.e. cDAI)
* @param getId id of withdraw amount
* @param setId id to set the value of amount withdrawn, if redeemToUnderlying this amount will be in underlying
* (i.e. DAI), if not redeemToUnderlying this amount will be asset tokens (i.e. cDAI)
*/
function withdrawCollateral(
uint16 currencyId,
bool redeemToUnderlying,
uint withdrawAmount,
uint getId,
uint setId
) external returns (string memory _eventName, bytes memory _eventParam) {
withdrawAmount = getUint(getId, withdrawAmount);
uint amountInternalPrecision = withdrawAmount == uint(-1) ?
getCashBalance(currencyId) :
convertToInternal(currencyId, withdrawAmount);
uint amountWithdrawn = notional.withdraw(currencyId, amountInternalPrecision, redeemToUnderlying);
// Sets the amount of tokens withdrawn to address(this)
setUint(setId, amountWithdrawn);
_eventName = "LogWithdrawCollateral(address,uint16,bool,uint256)";
_eventParam = abi.encode(address(this), currencyId, redeemToUnderlying, amountWithdrawn);
}
/**
* @dev Claims NOTE tokens and transfers to the address
* @param setId the id to set the balance of NOTE tokens claimed
*/
function claimNOTE(
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
uint notesClaimed = notional.nTokenClaimIncentives();
setUint(setId, notesClaimed);
_eventName = "LogClaimNOTE(address,uint256)";
_eventParam = abi.encode(address(this), notesClaimed);
}
/**
* @notice Redeem nTokens allowing for accepting of fCash residuals
* @dev This spell allows users to redeem nTokens even when there are fCash residuals that
* cannot be sold when markets are at extremely high utilization
* @param currencyId notional defined currency id of nToken
* @param sellTokenAssets set to false to accept fCash residuals into portfolio, set to true will
* sell fCash residuals back to cash
* @param tokensToRedeem amount of nTokens to redeem
* @param getId id of amount of tokens to redeem
* @param setId id to set amount of asset cash from redeem
*/
function redeemNTokenRaw(
uint16 currencyId,
bool sellTokenAssets,
uint tokensToRedeem,
uint getId,
uint setId
) external returns (string memory _eventName, bytes memory _eventParam) {
tokensToRedeem = getUint(getId, tokensToRedeem);
if (tokensToRedeem == uint(-1)) tokensToRedeem = getNTokenBalance(currencyId);
int _assetCashChange = notional.nTokenRedeem(address(this), currencyId, tokensToRedeem, sellTokenAssets);
// Floor asset cash change at zero in order to properly set the uint. If the asset cash change is negative
// (this will almost certainly never happen), then no withdraw is possible.
uint assetCashChange = _assetCashChange > 0 ? uint(_assetCashChange) : 0;
setUint(setId, assetCashChange);
_eventName = "LogRedeemNTokenRaw(address,uint16,bool,uint,uint)";
_eventParam = abi.encode(address(this), currencyId, sellTokenAssets, tokensToRedeem, assetCashChange);
}
/**
* @notice Redeems nTokens to cash and withdraws the resulting cash
* @dev Also possible to use redeemNTokenRaw and withdrawCollateral to achieve the same
* result but this is more gas efficient, it does it in one call to Notional
* @param currencyId notional defined currency id of nToken
* @param tokensToRedeem amount of nTokens to redeem
* @param amountToWithdraw amount of asset cash to withdraw, if set to uint(-1) then will withdraw the
* entire cash balance in notional
* @param redeemToUnderlying if true, will redeem the asset cash withdrawn to underlying tokens
* @param getId id of amount of tokens to redeem
* @param setId id to set amount of asset cash or underlying tokens withdrawn
*/
function redeemNTokenAndWithdraw(
uint16 currencyId,
uint tokensToRedeem,
uint amountToWithdraw,
bool redeemToUnderlying,
uint getId,
uint setId
) external returns (string memory _eventName, bytes memory _eventParam) {
tokensToRedeem = getUint(getId, tokensToRedeem);
if (tokensToRedeem == uint(-1)) tokensToRedeem = getNTokenBalance(currencyId);
BalanceAction[] memory action = new BalanceAction[1];
action[0].actionType = DepositActionType.RedeemNToken;
action[0].currencyId = currencyId;
action[0].depositActionAmount = tokensToRedeem;
action[0].redeemToUnderlying = redeemToUnderlying;
if (amountToWithdraw == uint(-1)) {
action[0].withdrawEntireCashBalance = true;
} else {
action[0].withdrawAmountInternalPrecision = amountToWithdraw;
}
uint balanceBefore;
address tokenAddress;
if (setId != 0) {
// Only run this if we are going to use the balance change
address tokenAddress = redeemToUnderlying ?
getUnderlyingToken(currencyId) :
getAssetToken(currencyId);
// TODO: handle ETH
balanceBefore = ERC20(tokenAddress).balanceOf(address(this));
}
notional.batchBalanceAction(address(this), action);
if (setId != 0) {
// TODO: handle ETH
uint netBalance = balanceBefore.sub(ERC20(tokenAddress).balanceOf(address(this)));
// This can be used to determine the exact amount withdrawn
setUint(setId, netBalance);
}
_eventName = "LogRedeemNTokenWithdraw(address,uint16,uint,uint,bool)";
_eventParam = abi.encode(address(this), currencyId, tokensToRedeem, amountToWithdraw, redeemToUnderlying);
}
/**
* @notice Redeems nTokens and uses the cash to repay a borrow.
* @dev When specifying fCashAmount be sure to calculate it such that the account
* has enough cash after redeeming nTokens to pay down the debt. This can be done
* off-chain using the Notional SDK.
* @param currencyId notional defined currency id of nToken
* @param tokensToRedeem amount of nTokens to redeem
* @param marketIndex the market index that references where the account will lend
* @param fCashAmount amount of fCash to lend into the market, the corresponding amount of cash will
* be taken from the account after redeeming nTokens
* @param minLendRate minimum rate where the user will lend, if the rate is lower will revert
* @param getId id of amount of tokens to redeem
*/
function redeemNTokenAndDeleverage(
uint16 currencyId,
uint tokensToRedeem,
uint8 marketIndex,
uint fCashAmount,
uint32 minLendRate,
uint getId
) external returns (string memory _eventName, bytes memory _eventParam) {
tokensToRedeem = getUint(getId, tokensToRedeem);
if (tokensToRedeem == uint(-1)) tokensToRedeem = getNTokenBalance(currencyId);
notional.nTokenRedeem(currencyId, tokensToRedeem, sellTokenAssets);
BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[1];
action[0].actionType = DepositActionType.RedeemNToken;
action[0].currencyId = currencyId;
action[0].depositActionAmount = tokensToRedeem;
// Withdraw amount, withdraw cash balance and redeemToUnderlying are all 0 or false
bytes32[] memory trades = new bytes32[](1);
trades[0] = encodeLendTrade(marketIndex, fCashAmount, minLendRate);
action[0].trades = trades;
notional.batchBalanceActionWithTrades(address(this), action);
_eventName = "LogRedeemNTokenAndDeleverage(address,uint16,uint,uint8,uint)";
_eventParam = abi.encode(address(this), currencyId, tokensToRedeem, marketIndex, fCashAmount);
}
/**
* @notice Deposit asset or underlying tokens and mint nTokens in a single transaction
* @param currencyId notional defined currency id to deposit
* @param depositAmount amount of tokens to deposit
* @param useUnderlying if true, will accept a deposit in the underlying currency (i.e DAI), if false
* will use the asset currency (i.e. cDAI)
* @param getId id of depositAmount
* @param setId id to set the value of notional cash deposit increase (denominated in asset cash, i.e. cDAI)
*/
function depositAndMintNToken(
uint16 currencyId,
uint depositAmount,
bool useUnderlying,
uint getId,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId);
depositAmount = getUint(getId, depositAmount);
if (depositAmount == uint(-1)) depositAmount = ERC20(tokenAddress).balanceOf(address(this));
approve(tokenAddress, address(notional), depositAmount);
BalanceAction[] memory action = new BalanceAction[1];
action[0].actionType = useUnderlying ? DepositActionType.DepositUnderlyingAndMintNToken : DepositActionType.DepositAssetAndMintNToken;
action[0].currencyId = currencyId;
action[0].depositActionAmount = depositAmount;
// withdraw amount, withdraw cash and redeem to underlying are all 0 and false
uint nTokenBefore
if (setId != 0) {
nTokenBefore = getNTokenBalance(currencyId);
}
uint msgValue = currencyId == ETH_CURRENCY_ID ? depositAmount : 0;
notional.batchBalanceActionWithTrades{value: msgValue}(address(this), action);
if (setId != 0) {
// Set the amount of nTokens minted
setUint(setId, getNTokenBalance(currencyId).sub(nTokenBefore));
}
// todo: events
}
function mintNTokenFromCash(
uint16 currencyId,
uint cashBalanceToMint,
uint getId,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
cashBalanceToMint = getUint(getId, cashBalanceToMint);
if (cashBalanceToMint == uint(-1)) = cashBalanceToMint = getCashBalance(currencyId);
BalanceAction[] memory action = new BalanceAction[1];
action[0].actionType = DepositActionType.ConvertCashToNToken;
action[0].currencyId = currencyId;
action[0].depositActionAmount = cashBalanceToMint;
// NOTE: withdraw amount, withdraw cash and redeem to underlying are all 0 and false
uint nTokenBefore
if (setId != 0) {
nTokenBefore = getNTokenBalance(currencyId);
}
notional.batchBalanceActionWithTrades(address(this), action);
if (setId != 0) {
// Set the amount of nTokens minted
setUint(setId, getNTokenBalance(currencyId).sub(nTokenBefore));
}
// todo: events
}
function depositAndLend(
uint16 currencyId,
uint depositAmount,
bool useUnderlying,
uint8 marketIndex,
uint fCashAmount,
uint minLendRate,
uint getId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId);
depositAmount = getUint(getId, depositAmount);
if (depositAmount == uint(-1)) depositAmount = ERC20(tokenAddress).balanceOf(address(this));
approve(tokenAddress, address(notional), depositAmount);
BalanceAction[] memory action = new BalanceAction[1];
action[0].actionType = useUnderlying ? DepositActionType.DepositUnderlying : DepositActionType.DepositAsset;
action[0].currencyId = currencyId;
action[0].depositActionAmount = depositAmount;
// Withdraw any residual cash from lending back to the token that was used
action[0].withdrawEntireCashBalance = true;
// TODO: will redeem underlying work with ETH?
action[0].redeemToUnderlying = useUnderlying;
bytes32[] memory trades = new bytes32[](1);
trades[0] = encodeLendTrade(marketIndex, fCashAmount, minLendRate);
action[0].trades = trades;
uint msgValue = currencyId == ETH_CURRENCY_ID ? depositAmount : 0;
notional.batchBalanceActionWithTrades{value: msgValue}(address(this), action);
// todo: events
}
function depositCollateralBorrowAndWithdraw(
uint16 depositCurrencyId,
bool useUnderlying,
uint16 borrowCurrencyId,
uint8 marketIndex,
uint fCashAmount,
uint maxBorrowRate,
bool redeemToUnderlying,
uint getId,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
require(depositCurrencyId != borrowCurrencyId);
address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId);
depositAmount = getUint(getId, depositAmount);
if (depositAmount == uint(-1)) depositAmount = ERC20(tokenAddress).balanceOf(address(this));
approve(tokenAddress, address(notional), depositAmount);
BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](2);
uint256 depositIndex;
uint256 borrowIndex;
if (depositCurrencyId < borrowCurrencyId) {
depositIndex = 0;
borrowIndex = 1;
} else {
depositIndex = 1;
borrowIndex = 0;
}
action[depositIndex].actionType = useUnderlying ? DepositActionType.DepositUnderlying : DepositActionType.DepositAsset;
action[depositIndex].currencyId = depositCurrencyId;
action[depositIndex].depositActionAmount = depositAmount;
uint msgValue = depositCurrencyId == ETH_CURRENCY_ID ? depositAmount : 0;
action[borrowIndex].actionType = DepositActionType.None;
action[borrowIndex].currencyId = borrowCurrencyId;
// Withdraw borrowed amount to wallet
action[borrowIndex].withdrawEntireCashBalance = true;
// TODO: will redeem underlying work with ETH?
action[borrowIndex].redeemToUnderlying = useUnderlying;
bytes32[] memory trades = new bytes32[](1);
trades[borrowIndex] = encodeBorrowTrade(marketIndex, fCashAmount, minLendRate);
action[borrowIndex].trades = trades;
notional.batchBalanceActionWithTrades{value: msgValue}(address(this), action);
// todo: events
}
/**
* @notice Executes a number of batch actions on the account without getId or setId integration
* @dev This method will allow the user to take almost any action on Notional but does not have any
* getId or setId integration.
* @param actions a set of BatchActionWithTrades that will be executed for this account
*/
function batchActionRaw(
BatchActionWithTrades[] memory actions
) external payable returns (string memory _eventName, bytes memory _eventParam) {
notional.batchBalanceActionWithTrades(address(this), actions);
// todo: events
}
}