dsa-connectors/contracts/mainnet/connectors/notional/helpers.sol

273 lines
9.7 KiB
Solidity
Raw Normal View History

2021-11-10 22:52:16 +00:00
pragma solidity ^0.7.6;
2021-11-29 20:38:43 +00:00
pragma abicoder v2;
2021-11-10 22:52:16 +00:00
2021-12-16 01:49:32 +00:00
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
2021-12-17 15:12:18 +00:00
import {Token, NotionalInterface, BalanceAction, BalanceActionWithTrades, DepositActionType} from "./interface.sol";
2021-12-16 01:49:32 +00:00
import {SafeInt256} from "./SafeInt256.sol";
2021-11-29 20:38:43 +00:00
import {Basic} from "../../common/basic.sol";
2021-12-16 01:49:32 +00:00
import {TokenInterface} from "../../common/interfaces.sol";
2021-11-10 22:52:16 +00:00
2021-11-29 20:38:43 +00:00
contract Helpers is Basic {
2021-12-16 01:49:32 +00:00
using SafeMath for uint256;
using SafeInt256 for int256;
2021-11-29 20:38:43 +00:00
uint8 internal constant LEND_TRADE = 0;
uint8 internal constant BORROW_TRADE = 1;
int256 internal constant INTERNAL_TOKEN_PRECISION = 1e8;
uint256 internal constant ETH_CURRENCY_ID = 1;
2021-12-17 14:13:13 +00:00
uint256 internal constant MAX_DEPOSIT = uint256(-1);
2021-11-10 22:52:16 +00:00
2021-12-17 14:13:13 +00:00
/// @dev Contract address is different on Kovan: 0x0EAE7BAdEF8f95De91fDDb74a89A786cF891Eb0e
2021-11-29 20:38:43 +00:00
NotionalInterface internal constant notional =
2021-11-30 00:55:26 +00:00
NotionalInterface(0x1344A36A1B56144C3Bc62E7757377D288fDE0369);
2021-11-29 20:38:43 +00:00
2021-12-17 14:13:13 +00:00
/// @notice Returns the address of the underlying token for a given currency id,
2021-12-17 15:12:18 +00:00
function getUnderlyingToken(uint16 currencyId) internal view returns (address) {
2021-12-17 14:13:13 +00:00
(
/* Token memory assetToken */,
Token memory underlyingToken
) = notional.getCurrency(currencyId);
2021-11-29 20:38:43 +00:00
return underlyingToken.tokenAddress;
}
2021-12-17 14:13:13 +00:00
/// @notice Returns the address of the asset token for a given currency id
2021-12-17 15:12:18 +00:00
function getAssetToken(uint16 currencyId) internal view returns (address) {
2021-12-17 14:13:13 +00:00
(
Token memory assetToken,
/* Token memory underlyingToken */
) = notional.getCurrency(currencyId);
2021-11-29 20:38:43 +00:00
return assetToken.tokenAddress;
}
2021-12-17 15:12:18 +00:00
function getCashBalance(uint16 currencyId) internal view returns (int256 cashBalance) {
2021-12-17 14:13:13 +00:00
(
cashBalance,
/* int256 nTokenBalance */,
/* int256 lastClaimTime */
) = notional.getAccountBalance(currencyId, address(this));
2021-11-29 20:38:43 +00:00
}
2021-12-17 15:12:18 +00:00
function getNTokenBalance(uint16 currencyId) internal view returns (int256 nTokenBalance) {
2021-12-17 14:13:13 +00:00
(
/* int256 cashBalance */,
nTokenBalance,
/* int256 lastClaimTime */
) = notional.getAccountBalance(currencyId, address(this));
2021-11-29 20:38:43 +00:00
}
2021-12-17 15:12:18 +00:00
function getNTokenRedeemAmount(uint16 currencyId, uint96 _tokensToRedeem, uint256 getId)
2021-11-29 20:38:43 +00:00
internal
2021-12-17 15:12:18 +00:00
returns (uint96 tokensToRedeem) {
tokensToRedeem = uint96(getUint(getId, _tokensToRedeem));
if (tokensToRedeem == uint96(-1)) {
tokensToRedeem = uint96(getNTokenBalance(currencyId));
}
}
function getMsgValue(
uint16 currencyId,
bool useUnderlying,
uint256 depositAmount
) internal pure returns (uint256 msgValue) {
msgValue = (currencyId == ETH_CURRENCY_ID && useUnderlying)
? depositAmount
: 0;
}
function convertToInternal(uint16 currencyId, int256 amount)
internal view
2021-11-29 20:38:43 +00:00
returns (int256)
{
// If token decimals is greater than INTERNAL_TOKEN_PRECISION then this will truncate
// down to the internal precision. Resulting dust will accumulate to the protocol.
// If token decimals is less than INTERNAL_TOKEN_PRECISION then this will add zeros to the
// end of amount and will not result in dust.
2021-12-17 14:13:13 +00:00
(Token memory assetToken, /* underlyingToken */) = notional.getCurrency(currencyId);
2021-11-29 20:38:43 +00:00
if (assetToken.decimals == INTERNAL_TOKEN_PRECISION) return amount;
2021-12-16 01:49:32 +00:00
return amount.mul(INTERNAL_TOKEN_PRECISION).div(assetToken.decimals);
2021-11-29 20:38:43 +00:00
}
function encodeLendTrade(
uint8 marketIndex,
uint88 fCashAmount,
uint32 minLendRate
2021-12-17 15:12:18 +00:00
) internal pure returns (bytes32) {
2021-11-29 20:38:43 +00:00
return
2021-11-30 00:55:26 +00:00
(bytes32(uint256(LEND_TRADE)) << 248) |
(bytes32(uint256(marketIndex)) << 240) |
(bytes32(uint256(fCashAmount)) << 152) |
(bytes32(uint256(minLendRate)) << 120);
2021-11-29 20:38:43 +00:00
}
function encodeBorrowTrade(
uint8 marketIndex,
uint88 fCashAmount,
uint32 maxBorrowRate
2021-12-17 15:12:18 +00:00
) internal pure returns (bytes32) {
2021-11-29 20:38:43 +00:00
return
2021-11-30 00:55:26 +00:00
(bytes32(uint256(BORROW_TRADE)) << 248) |
(bytes32(uint256(marketIndex)) << 240) |
(bytes32(uint256(fCashAmount)) << 152) |
(bytes32(uint256(maxBorrowRate)) << 120);
2021-11-29 20:38:43 +00:00
}
2021-12-17 14:13:13 +00:00
/// @dev Uses getId to set approval for the given token up to the specified deposit
/// amount only
2021-12-16 01:49:32 +00:00
function getDepositAmountAndSetApproval(
uint256 getId,
uint16 currencyId,
bool useUnderlying,
uint256 depositAmount
) internal returns (uint256) {
depositAmount = getUint(getId, depositAmount);
2021-12-17 14:13:13 +00:00
if (currencyId == ETH_CURRENCY_ID && useUnderlying) {
// No approval required for ETH so we can return the deposit amount
return depositAmount == MAX_DEPOSIT
? address(this).balance
: depositAmount;
}
2021-12-16 01:49:32 +00:00
address tokenAddress = useUnderlying
? getUnderlyingToken(currencyId)
: getAssetToken(currencyId);
2021-12-17 14:13:13 +00:00
if (depositAmount == MAX_DEPOSIT) {
2021-12-16 01:49:32 +00:00
depositAmount = TokenInterface(tokenAddress).balanceOf(
address(this)
);
}
approve(TokenInterface(tokenAddress), address(notional), depositAmount);
return depositAmount;
2021-11-29 20:38:43 +00:00
}
2021-12-17 15:12:18 +00:00
function getBalance(address addr) internal view returns (uint256) {
2021-12-16 01:49:32 +00:00
if (addr == ethAddr) {
return address(this).balance;
}
return TokenInterface(addr).balanceOf(address(this));
2021-11-29 20:38:43 +00:00
}
2021-12-16 01:49:32 +00:00
function getAddress(uint16 currencyId, bool useUnderlying)
internal
2021-12-17 15:12:18 +00:00
view
2021-12-16 01:49:32 +00:00
returns (address)
{
if (currencyId == ETH_CURRENCY_ID && useUnderlying) {
return ethAddr;
}
return
useUnderlying
? getUnderlyingToken(currencyId)
: getAssetToken(currencyId);
2021-11-29 20:38:43 +00:00
}
2021-12-17 14:13:13 +00:00
/// @dev Executes a trade action and sets the balance change to setId
2021-12-16 01:49:32 +00:00
function executeTradeActionWithBalanceChange(
BalanceActionWithTrades[] memory action,
uint256 msgValue,
uint16 currencyId,
bool useUnderlying,
uint256 setId
) internal {
address tokenAddress;
uint256 balanceBefore;
if (setId != 0) {
tokenAddress = getAddress(currencyId, useUnderlying);
balanceBefore = getBalance(tokenAddress);
}
2021-11-29 20:38:43 +00:00
2021-12-16 01:49:32 +00:00
notional.batchBalanceAndTradeAction{value: msgValue}(
address(this),
action
);
if (setId != 0) {
uint256 balanceAfter = getBalance(tokenAddress);
setUint(setId, balanceAfter.sub(balanceBefore));
}
}
2021-12-17 14:13:13 +00:00
/// @dev Executes a balance action and sets the balance change to setId
2021-12-16 01:49:32 +00:00
function executeActionWithBalanceChange(
BalanceAction[] memory action,
uint256 msgValue,
uint16 currencyId,
bool useUnderlying,
uint256 setId
) internal {
address tokenAddress;
uint256 balanceBefore;
if (setId != 0) {
tokenAddress = getAddress(currencyId, useUnderlying);
balanceBefore = getBalance(tokenAddress);
}
notional.batchBalanceAction{value: msgValue}(address(this), action);
if (setId != 0) {
uint256 balanceAfter = getBalance(tokenAddress);
setUint(setId, balanceAfter.sub(balanceBefore));
}
}
2021-12-17 15:12:18 +00:00
function getDepositCollateralBorrowAndWithdrawActions(
uint16 depositCurrencyId,
DepositActionType depositAction,
2021-12-17 15:12:18 +00:00
uint256 depositAmount,
uint16 borrowCurrencyId,
uint8 marketIndex,
uint88 fCashAmount,
uint32 maxBorrowRate,
bool redeemToUnderlying
) internal returns (BalanceActionWithTrades[] memory action) {
BalanceActionWithTrades[] memory actions;
bytes32[] memory trades = new bytes32[](1);
trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate);
if (depositCurrencyId == borrowCurrencyId) {
// In this case the account is likely borrowing against newly minted nTokens
// in the same currency. Technically the other deposit actions may work but
// there's no good reason to borrow against cToken collateral
actions = new BalanceActionWithTrades[](1);
actions[0].actionType = depositAction;
actions[0].currencyId = depositCurrencyId;
actions[0].depositActionAmount = depositAmount;
// Withdraw borrowed amount to wallet
actions[0].withdrawEntireCashBalance = true;
actions[0].redeemToUnderlying = redeemToUnderlying;
actions[0].trades = trades;
return actions;
}
// This is the more common case that the account is borrowing against
// collateral in a different currency
actions = new BalanceActionWithTrades[](2);
2021-12-17 15:12:18 +00:00
uint256 depositIndex;
uint256 borrowIndex;
// Notional requires the batch actions to be ordered by currency id
if (depositCurrencyId < borrowCurrencyId) {
depositIndex = 0;
borrowIndex = 1;
} else {
depositIndex = 1;
borrowIndex = 0;
}
actions[depositIndex].actionType = depositAction;
2021-12-17 15:12:18 +00:00
actions[depositIndex].currencyId = depositCurrencyId;
actions[depositIndex].depositActionAmount = depositAmount;
actions[borrowIndex].actionType = DepositActionType.None;
actions[borrowIndex].currencyId = borrowCurrencyId;
// Withdraw borrowed amount to wallet
actions[borrowIndex].withdrawEntireCashBalance = true;
actions[borrowIndex].redeemToUnderlying = redeemToUnderlying;
actions[borrowIndex].trades = trades;
return actions;
}
2021-11-10 22:52:16 +00:00
}