From 74a7effdb4da869085e9c795f3547bacb0328f2f Mon Sep 17 00:00:00 2001 From: Tianjie Wei Date: Thu, 31 Mar 2022 08:27:31 -0700 Subject: [PATCH] Addressing PR comments --- .../mainnet/connectors/notional/events.sol | 1 + .../mainnet/connectors/notional/helpers.sol | 486 ++++--- .../mainnet/connectors/notional/interface.sol | 194 +-- .../mainnet/connectors/notional/main.sol | 1217 +++++++++-------- test/mainnet/notional/notional.helpers.ts | 2 +- test/mainnet/notional/notional.test.ts | 6 +- 6 files changed, 953 insertions(+), 953 deletions(-) diff --git a/contracts/mainnet/connectors/notional/events.sol b/contracts/mainnet/connectors/notional/events.sol index a6c86119..5163e18d 100644 --- a/contracts/mainnet/connectors/notional/events.sol +++ b/contracts/mainnet/connectors/notional/events.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.6; contract Events { diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index 79161b4f..1b18f12e 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -1,286 +1,276 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.6; pragma abicoder v2; -import {Token, NotionalInterface, BalanceAction, BalanceActionWithTrades, DepositActionType} from "./interface.sol"; -import {Basic} from "../../common/basic.sol"; -import {DSMath} from "../../common/math.sol"; -import {TokenInterface} from "../../common/interfaces.sol"; +import { Token, NotionalInterface, BalanceAction, BalanceActionWithTrades, DepositActionType } from "./interface.sol"; +import { Basic } from "../../common/basic.sol"; +import { DSMath } from "../../common/math.sol"; +import { TokenInterface } from "../../common/interfaces.sol"; abstract contract Helpers is DSMath, Basic { - uint8 internal constant LEND_TRADE = 0; - uint8 internal constant BORROW_TRADE = 1; - uint256 internal constant INTERNAL_TOKEN_PRECISION = 1e8; - uint256 internal constant ETH_CURRENCY_ID = 1; - uint256 internal constant MAX_DEPOSIT = uint256(-1); + uint8 internal constant LEND_TRADE = 0; + uint8 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; - /// @dev Contract address is different on Kovan: 0x0EAE7BAdEF8f95De91fDDb74a89A786cF891Eb0e - NotionalInterface internal constant notional = - NotionalInterface(0x1344A36A1B56144C3Bc62E7757377D288fDE0369); + /// @dev Contract address is different on Kovan: 0x0EAE7BAdEF8f95De91fDDb74a89A786cF891Eb0e + NotionalInterface internal constant notional = + NotionalInterface(0x1344A36A1B56144C3Bc62E7757377D288fDE0369); - /// @notice Returns the address of the underlying token for a given currency id, - function getUnderlyingToken(uint16 currencyId) - internal - view - returns (address) - { - // prettier-ignore - (/* assetToken */, Token memory underlyingToken) = notional.getCurrency(currencyId); - return underlyingToken.tokenAddress; - } + /// @notice Returns the address of the underlying token for a given currency id, + 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; + } - /// @notice Returns the address of the asset token for a given currency id - function getAssetToken(uint16 currencyId) internal view returns (address) { - // prettier-ignore - (Token memory assetToken, /* underlyingToken */) = notional.getCurrency(currencyId); - return assetToken.tokenAddress; - } - - function getCashBalance(uint16 currencyId) - internal - view - returns (int256 cashBalance) - { - // prettier-ignore - ( - cashBalance, - /* int256 nTokenBalance */, - /* int256 lastClaimTime */ - ) = notional.getAccountBalance(currencyId, address(this)); - } - - function getNTokenBalance(uint16 currencyId) - internal - view - returns (uint256) - { - // prettier-ignore - ( - /* int256 cashBalance */, + function getCashOrNTokenBalance(uint16 currencyId, bool nToken) + internal + view + returns (uint256) + { + // prettier-ignore + ( + int256 cashBalance, int256 nTokenBalance, /* int256 lastClaimTime */ ) = notional.getAccountBalance(currencyId, address(this)); - return toUint(nTokenBalance); - } + return toUint(nToken ? nTokenBalance : cashBalance); + } - function getNTokenRedeemAmount( - uint16 currencyId, - uint96 _tokensToRedeem, - uint256 getId - ) internal returns (uint96 tokensToRedeem) { - tokensToRedeem = uint96(getUint(getId, _tokensToRedeem)); - if (tokensToRedeem == uint96(-1)) { - tokensToRedeem = uint96(getNTokenBalance(currencyId)); - } - } + function getNTokenRedeemAmount( + uint16 currencyId, + uint96 _tokensToRedeem, + uint256 getId + ) internal returns (uint96 tokensToRedeem) { + tokensToRedeem = toUint96(getUint(getId, _tokensToRedeem)); + if (tokensToRedeem == type(uint96).max) { + tokensToRedeem = toUint96(getCashOrNTokenBalance(currencyId, true)); + } + } - function getMsgValue( - uint16 currencyId, - bool useUnderlying, - uint256 depositAmount - ) internal pure returns (uint256 msgValue) { - msgValue = (currencyId == ETH_CURRENCY_ID && useUnderlying) - ? depositAmount - : 0; - } + function toUint96(uint256 value) internal pure returns (uint96) { + require(value <= type(uint96).max, "uint96 value overflow"); + return uint96(value); + } - function convertToInternal(uint16 currencyId, uint256 amount) - internal - view - returns (uint256) - { - // 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. - // prettier-ignore - (Token memory assetToken, /* underlyingToken */) = notional.getCurrency(currencyId); - uint256 decimals = toUint(assetToken.decimals); - if (decimals == INTERNAL_TOKEN_PRECISION) return amount; - return div(mul(amount, INTERNAL_TOKEN_PRECISION), decimals); - } + function toUint88(uint256 value) internal pure returns (uint88) { + require(value <= type(uint88).max, "uint88 value overflow"); + return uint88(value); + } - function encodeLendTrade( - uint8 marketIndex, - uint88 fCashAmount, - uint32 minLendRate - ) internal pure returns (bytes32) { - return - (bytes32(uint256(LEND_TRADE)) << 248) | - (bytes32(uint256(marketIndex)) << 240) | - (bytes32(uint256(fCashAmount)) << 152) | - (bytes32(uint256(minLendRate)) << 120); - } + function getMsgValue( + uint16 currencyId, + bool useUnderlying, + uint256 depositAmount + ) internal pure returns (uint256 msgValue) { + msgValue = (currencyId == ETH_CURRENCY_ID && useUnderlying) + ? depositAmount + : 0; + } - function encodeBorrowTrade( - uint8 marketIndex, - uint88 fCashAmount, - uint32 maxBorrowRate - ) internal pure returns (bytes32) { - return - (bytes32(uint256(BORROW_TRADE)) << 248) | - (bytes32(uint256(marketIndex)) << 240) | - (bytes32(uint256(fCashAmount)) << 152) | - (bytes32(uint256(maxBorrowRate)) << 120); - } + function convertToInternal(uint16 currencyId, uint256 amount) + internal + view + returns (uint256) + { + // 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. + // prettier-ignore + (Token memory assetToken, /* underlyingToken */) = notional.getCurrency(currencyId); + uint256 decimals = toUint(assetToken.decimals); + if (decimals == INTERNAL_TOKEN_PRECISION) return amount; + return div(mul(amount, INTERNAL_TOKEN_PRECISION), decimals); + } - /// @dev Uses getId to set approval for the given token up to the specified deposit - /// amount only - function getDepositAmountAndSetApproval( - uint256 getId, - uint16 currencyId, - bool useUnderlying, - uint256 depositAmount - ) internal returns (uint256) { - depositAmount = getUint(getId, depositAmount); - 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; - } + function encodeLendTrade( + uint8 marketIndex, + uint88 fCashAmount, + uint32 minLendRate + ) internal pure returns (bytes32) { + return + (bytes32(uint256(LEND_TRADE)) << 248) | + (bytes32(uint256(marketIndex)) << 240) | + (bytes32(uint256(fCashAmount)) << 152) | + (bytes32(uint256(minLendRate)) << 120); + } - address tokenAddress = useUnderlying - ? getUnderlyingToken(currencyId) - : getAssetToken(currencyId); + function encodeBorrowTrade( + uint8 marketIndex, + uint88 fCashAmount, + uint32 maxBorrowRate + ) internal pure returns (bytes32) { + return + (bytes32(uint256(BORROW_TRADE)) << 248) | + (bytes32(uint256(marketIndex)) << 240) | + (bytes32(uint256(fCashAmount)) << 152) | + (bytes32(uint256(maxBorrowRate)) << 120); + } - if (depositAmount == MAX_DEPOSIT) { - depositAmount = TokenInterface(tokenAddress).balanceOf( - address(this) - ); - } - approve(TokenInterface(tokenAddress), address(notional), depositAmount); - return depositAmount; - } + /// @dev Uses getId to set approval for the given token up to the specified deposit + /// amount only + function getDepositAmountAndSetApproval( + uint256 getId, + uint16 currencyId, + bool useUnderlying, + uint256 depositAmount + ) internal returns (uint256) { + depositAmount = getUint(getId, depositAmount); + 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; + } - function getBalance(address addr) internal view returns (uint256) { - if (addr == ethAddr) { - return address(this).balance; - } + address tokenAddress = getAssetOrUnderlyingToken( + currencyId, + useUnderlying + ); - return TokenInterface(addr).balanceOf(address(this)); - } + if (depositAmount == MAX_DEPOSIT) { + depositAmount = TokenInterface(tokenAddress).balanceOf( + address(this) + ); + } + approve(TokenInterface(tokenAddress), address(notional), depositAmount); + return depositAmount; + } - function getAddress(uint16 currencyId, bool useUnderlying) - internal - view - returns (address) - { - if (currencyId == ETH_CURRENCY_ID && useUnderlying) { - return ethAddr; - } + function getBalance(address addr) internal view returns (uint256) { + if (addr == ethAddr) { + return address(this).balance; + } - return - useUnderlying - ? getUnderlyingToken(currencyId) - : getAssetToken(currencyId); - } + return TokenInterface(addr).balanceOf(address(this)); + } - /// @dev Executes a trade action and sets the balance change to setId - 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); - } + function getAddress(uint16 currencyId, bool useUnderlying) + internal + view + returns (address) + { + if (currencyId == ETH_CURRENCY_ID && useUnderlying) { + return ethAddr; + } - notional.batchBalanceAndTradeAction{value: msgValue}( - address(this), - action - ); + return getAssetOrUnderlyingToken(currencyId, useUnderlying); + } - if (setId != 0) { - uint256 balanceAfter = getBalance(tokenAddress); - setUint(setId, sub(balanceAfter, balanceBefore)); - } - } + /// @dev Executes a trade action and sets the balance change to setId + 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); + } - /// @dev Executes a balance action and sets the balance change to setId - 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.batchBalanceAndTradeAction{ value: msgValue }( + address(this), + action + ); - notional.batchBalanceAction{value: msgValue}(address(this), action); + if (setId != 0) { + uint256 balanceAfter = getBalance(tokenAddress); + setUint(setId, sub(balanceAfter, balanceBefore)); + } + } - if (setId != 0) { - uint256 balanceAfter = getBalance(tokenAddress); - setUint(setId, sub(balanceAfter, balanceBefore)); - } - } + /// @dev Executes a balance action and sets the balance change to setId + 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); + } - function getDepositCollateralBorrowAndWithdrawActions( - uint16 depositCurrencyId, - DepositActionType depositAction, - 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); + notional.batchBalanceAction{ value: msgValue }(address(this), action); - 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; + if (setId != 0) { + uint256 balanceAfter = getBalance(tokenAddress); + setUint(setId, sub(balanceAfter, balanceBefore)); + } + } - return actions; - } + function getDepositCollateralBorrowAndWithdrawActions( + uint16 depositCurrencyId, + DepositActionType depositAction, + 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); - // This is the more common case that the account is borrowing against - // collateral in a different currency - actions = new BalanceActionWithTrades[](2); + 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; - 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; - } + return actions; + } - actions[depositIndex].actionType = depositAction; - actions[depositIndex].currencyId = depositCurrencyId; - actions[depositIndex].depositActionAmount = depositAmount; + // This is the more common case that the account is borrowing against + // collateral in a different currency + actions = new BalanceActionWithTrades[](2); - 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; + 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; + } - return actions; - } + actions[depositIndex].actionType = depositAction; + 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; + } } diff --git a/contracts/mainnet/connectors/notional/interface.sol b/contracts/mainnet/connectors/notional/interface.sol index 779bbce7..dea1ab6c 100644 --- a/contracts/mainnet/connectors/notional/interface.sol +++ b/contracts/mainnet/connectors/notional/interface.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.6; pragma abicoder v2; @@ -8,126 +9,127 @@ pragma abicoder v2; /// - Ether: the one and only /// - NonMintable: tokens that do not have an underlying (therefore not cTokens) enum TokenType { - UnderlyingToken, - cToken, - cETH, - Ether, - NonMintable + 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 + // 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; + 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; + // 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; + // 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 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 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 depositUnderlyingToken( + address account, + uint16 currencyId, + uint256 amountExternalPrecision + ) external payable returns (uint256); - function depositAssetToken( - address account, - uint16 currencyId, - uint256 amountExternalPrecision - ) external 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 withdraw( + uint16 currencyId, + uint88 amountInternalPrecision, + bool redeemToUnderlying + ) external returns (uint256); - function nTokenClaimIncentives() external returns (uint256); + function nTokenClaimIncentives() external returns (uint256); - function nTokenRedeem( - address redeemer, - uint16 currencyId, - uint96 tokensToRedeem_, - bool sellTokenAssets - ) external returns (int256); + 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 batchBalanceAction( + address account, + BalanceAction[] calldata actions + ) external payable; - function batchBalanceAndTradeAction( - address account, - BalanceActionWithTrades[] calldata actions - ) external payable; + function batchBalanceAndTradeAction( + address account, + BalanceActionWithTrades[] calldata actions + ) external payable; } diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index cb8c3447..c859b15c 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.7.6; pragma abicoder v2; @@ -6,666 +7,672 @@ pragma abicoder v2; * @dev Fixed Rate Lending and Borrowing */ -import {Helpers} from "./helpers.sol"; -import {Events} from "./events.sol"; -import {DepositActionType, BalanceActionWithTrades, BalanceAction} from "./interface.sol"; -import {TokenInterface} from "../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; +import { DepositActionType, BalanceActionWithTrades, BalanceAction } from "./interface.sol"; +import { TokenInterface } from "../../common/interfaces.sol"; abstract contract NotionalResolver is Events, Helpers { - /** - * @notice Deposit collateral into Notional, this should only be used for reducing risk of - * liquidation. - * @dev Deposits into Notional are not earning fixed rates, they are earning the cToken - * lending rate. In order to lend at fixed rates use `depositAndLend` - * @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, - uint256 depositAmount, - uint256 getId, - uint256 setId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - depositAmount = getDepositAmountAndSetApproval( - getId, - currencyId, - useUnderlying, - depositAmount - ); + /** + * @notice Deposit collateral into Notional, this should only be used for reducing risk of + * liquidation. + * @dev Deposits into Notional are not earning fixed rates, they are earning the cToken + * lending rate. In order to lend at fixed rates use `depositAndLend` + * @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, + uint256 depositAmount, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + depositAmount = getDepositAmountAndSetApproval( + getId, + currencyId, + useUnderlying, + depositAmount + ); - uint256 assetCashDeposited; - if (useUnderlying && currencyId == ETH_CURRENCY_ID) { - assetCashDeposited = notional.depositUnderlyingToken{ - value: depositAmount - }(address(this), currencyId, depositAmount); - } else if (useUnderlying) { - assetCashDeposited = notional.depositUnderlyingToken( - address(this), - currencyId, - depositAmount - ); - } else { - assetCashDeposited = notional.depositAssetToken( - address(this), - currencyId, - depositAmount - ); - } + uint256 assetCashDeposited; + if (useUnderlying && currencyId == ETH_CURRENCY_ID) { + assetCashDeposited = notional.depositUnderlyingToken{ + value: depositAmount + }(address(this), currencyId, depositAmount); + } else if (useUnderlying) { + assetCashDeposited = notional.depositUnderlyingToken( + address(this), + currencyId, + depositAmount + ); + } else { + assetCashDeposited = notional.depositAssetToken( + address(this), + currencyId, + depositAmount + ); + } - setUint(setId, assetCashDeposited); + setUint(setId, assetCashDeposited); - _eventName = "LogDepositCollateral(address,uint16,bool,uint256,uint256)"; - _eventParam = abi.encode( - address(this), - currencyId, - useUnderlying, - depositAmount, - assetCashDeposited - ); - } + _eventName = "LogDepositCollateral(address,uint16,bool,uint256,uint256)"; + _eventParam = abi.encode( + address(this), + currencyId, + useUnderlying, + depositAmount, + assetCashDeposited + ); + } - /** - * @notice Withdraw collateral from Notional - * @dev This spell allows users to 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, - uint256 withdrawAmount, - uint256 getId, - uint256 setId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - withdrawAmount = getUint(getId, withdrawAmount); - uint88 amountInternalPrecision = withdrawAmount == uint256(-1) - ? uint88(getCashBalance(currencyId)) - : uint88( - convertToInternal(currencyId, withdrawAmount) - ); + /** + * @notice Withdraw collateral from Notional + * @dev This spell allows users to 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, + uint256 withdrawAmount, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + withdrawAmount = getUint(getId, withdrawAmount); + uint88 amountInternalPrecision = withdrawAmount == type(uint256).max + ? toUint88(getCashOrNTokenBalance(currencyId, false)) + : toUint88(convertToInternal(currencyId, withdrawAmount)); - uint256 amountWithdrawn = notional.withdraw( - currencyId, - amountInternalPrecision, - redeemToUnderlying - ); - // Sets the amount of tokens withdrawn to address(this), Notional returns this value - // in the native precision of the token that was withdrawn - setUint(setId, amountWithdrawn); + uint256 amountWithdrawn = notional.withdraw( + currencyId, + amountInternalPrecision, + redeemToUnderlying + ); + // Sets the amount of tokens withdrawn to address(this), Notional returns this value + // in the native precision of the token that was withdrawn + setUint(setId, amountWithdrawn); - _eventName = "LogWithdrawCollateral(address,uint16,bool,uint256)"; - _eventParam = abi.encode( - address(this), - currencyId, - redeemToUnderlying, - amountWithdrawn - ); - } + _eventName = "LogWithdrawCollateral(address,uint16,bool,uint256)"; + _eventParam = abi.encode( + address(this), + currencyId, + redeemToUnderlying, + amountWithdrawn + ); + } - /** - * @notice Claims NOTE tokens and transfers to the address - * @dev This spell allows users to claim nToken incentives - * @param setId the id to set the balance of NOTE tokens claimed - */ - function claimNOTE(uint256 setId) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - uint256 notesClaimed = notional.nTokenClaimIncentives(); - setUint(setId, notesClaimed); + /** + * @notice Claims NOTE tokens and transfers to the address + * @dev This spell allows users to claim nToken incentives + * @param setId the id to set the balance of NOTE tokens claimed + */ + function claimNOTE(uint256 setId) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 notesClaimed = notional.nTokenClaimIncentives(); + setUint(setId, notesClaimed); - _eventName = "LogClaimNOTE(address,uint256)"; - _eventParam = abi.encode(address(this), 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, - uint96 tokensToRedeem, - uint256 getId, - uint256 setId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - tokensToRedeem = getNTokenRedeemAmount( - currencyId, - tokensToRedeem, - getId - ); + /** + * @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, + uint96 tokensToRedeem, + bool acceptResidualAssets, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + tokensToRedeem = getNTokenRedeemAmount( + currencyId, + tokensToRedeem, + getId + ); - int256 _assetCashChange = notional.nTokenRedeem( - address(this), - currencyId, - tokensToRedeem, - sellTokenAssets - ); + int256 _assetCashChange = notional.nTokenRedeem( + address(this), + currencyId, + tokensToRedeem, + sellTokenAssets, + acceptResidualAssets + ); - // 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. - uint256 assetCashChange = _assetCashChange > 0 - ? uint256(_assetCashChange) - : 0; + // 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. + uint256 assetCashChange = _assetCashChange > 0 + ? uint256(_assetCashChange) + : 0; - setUint(setId, assetCashChange); + setUint(setId, assetCashChange); - _eventName = "LogRedeemNTokenRaw(address,uint16,bool,uint96,int256)"; - _eventParam = abi.encode( - address(this), - currencyId, - sellTokenAssets, - tokensToRedeem, - assetCashChange - ); - } + _eventName = "LogRedeemNTokenRaw(address,uint16,bool,uint96,int256)"; + _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, - uint96 tokensToRedeem, - uint256 amountToWithdraw, - bool redeemToUnderlying, - uint256 getId, - uint256 setId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - tokensToRedeem = getNTokenRedeemAmount( - currencyId, - tokensToRedeem, - getId - ); + /** + * @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, + uint96 tokensToRedeem, + uint256 amountToWithdraw, + bool redeemToUnderlying, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + tokensToRedeem = getNTokenRedeemAmount( + currencyId, + tokensToRedeem, + getId + ); - 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 == uint256(-1)) { - // This setting will override the withdrawAmountInternalPrecision - action[0].withdrawEntireCashBalance = true; - } else { - action[0].withdrawAmountInternalPrecision = amountToWithdraw; - } + 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 == type(uint256).max) { + // This setting will override the withdrawAmountInternalPrecision + action[0].withdrawEntireCashBalance = true; + } else { + action[0].withdrawAmountInternalPrecision = amountToWithdraw; + } - executeActionWithBalanceChange( - action, - 0, - currencyId, - redeemToUnderlying, - setId - ); + executeActionWithBalanceChange( + action, + 0, + currencyId, + redeemToUnderlying, + setId + ); - _eventName = "LogRedeemNTokenWithdraw(address,uint16,uint96,uint256,bool)"; - _eventParam = abi.encode( - address(this), - currencyId, - tokensToRedeem, - amountToWithdraw, - redeemToUnderlying - ); - } + _eventName = "LogRedeemNTokenWithdraw(address,uint16,uint96,uint256,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 (this has the effect or repaying - * the borrowed cash at current market rates), 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, - uint96 tokensToRedeem, - uint8 marketIndex, - uint88 fCashAmount, - uint32 minLendRate, - uint256 getId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - tokensToRedeem = getNTokenRedeemAmount( - currencyId, - tokensToRedeem, - getId - ); + /** + * @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 (this has the effect or repaying + * the borrowed cash at current market rates), 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, + uint96 tokensToRedeem, + uint8 marketIndex, + uint88 fCashAmount, + uint32 minLendRate, + uint256 getId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + tokensToRedeem = getNTokenRedeemAmount( + currencyId, + tokensToRedeem, + getId + ); - 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 + 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; + bytes32[] memory trades = new bytes32[](1); + trades[0] = encodeLendTrade(marketIndex, fCashAmount, minLendRate); + action[0].trades = trades; - notional.batchBalanceAndTradeAction(address(this), action); + notional.batchBalanceAndTradeAction(address(this), action); - _eventName = "LogRedeemNTokenAndDeleverage(address,uint16,uint96,uint8,uint88)"; - _eventParam = abi.encode( - address(this), - currencyId, - tokensToRedeem, - marketIndex, - fCashAmount - ); - } + _eventName = "LogRedeemNTokenAndDeleverage(address,uint16,uint96,uint8,uint88)"; + _eventParam = abi.encode( + address(this), + currencyId, + tokensToRedeem, + marketIndex, + fCashAmount + ); + } - /** - * @notice Deposit asset or underlying tokens and mint nTokens in a single transaction - * @dev This spell allows users to deposit and mint nTokens (providing liquidity) - * @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 nToken balance change - */ - function depositAndMintNToken( - uint16 currencyId, - uint256 depositAmount, - bool useUnderlying, - uint256 getId, - uint256 setId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - depositAmount = getDepositAmountAndSetApproval( - getId, - currencyId, - useUnderlying, - depositAmount - ); + /** + * @notice Deposit asset or underlying tokens and mint nTokens in a single transaction + * @dev This spell allows users to deposit and mint nTokens (providing liquidity) + * @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 nToken balance change + */ + function depositAndMintNToken( + uint16 currencyId, + uint256 depositAmount, + bool useUnderlying, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + depositAmount = getDepositAmountAndSetApproval( + getId, + currencyId, + useUnderlying, + 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 + 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 - uint256 nTokenBefore = getNTokenBalance(currencyId); - uint256 msgValue = getMsgValue( - currencyId, - useUnderlying, - depositAmount - ); + uint256 nTokenBefore = getCashOrNTokenBalance(currencyId, true); + uint256 msgValue = getMsgValue( + currencyId, + useUnderlying, + depositAmount + ); - notional.batchBalanceAction{value: msgValue}(address(this), action); + notional.batchBalanceAction{ value: msgValue }(address(this), action); - uint256 nTokenBalanceChange = sub(getNTokenBalance(currencyId), nTokenBefore); + uint256 nTokenBalanceChange = sub( + getCashOrNTokenBalance(currencyId, true), + nTokenBefore + ); - if (setId != 0) { - // Set the amount of nTokens minted - setUint(setId, uint256(nTokenBalanceChange)); - } + if (setId != 0) { + // Set the amount of nTokens minted + setUint(setId, uint256(nTokenBalanceChange)); + } - _eventName = "LogDepositAndMintNToken(address,uint16,bool,uint256,int256)"; - _eventParam = abi.encode( - address(this), - currencyId, - useUnderlying, - depositAmount, - nTokenBalanceChange - ); - } + _eventName = "LogDepositAndMintNToken(address,uint16,bool,uint256,int256)"; + _eventParam = abi.encode( + address(this), + currencyId, + useUnderlying, + depositAmount, + nTokenBalanceChange + ); + } - /** - * @notice Uses existing Notional cash balance (deposits in Notional held as cTokens) and uses them to mint - * nTokens. - * @dev This spell allows users to mint nTokens (providing liquidity) from existing cash balance. - * @param currencyId notional defined currency id of the cash balance - * @param cashBalanceToMint amount of account's cash balance to convert to nTokens - * @param getId id of cash balance - * @param setId id to set the value of nToken increase - */ - function mintNTokenFromCash( - uint16 currencyId, - uint256 cashBalanceToMint, - uint256 getId, - uint256 setId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - cashBalanceToMint = getUint(getId, cashBalanceToMint); - if (cashBalanceToMint == uint256(-1)) - cashBalanceToMint = uint256(getCashBalance(currencyId)); + /** + * @notice Uses existing Notional cash balance (deposits in Notional held as cTokens) and uses them to mint + * nTokens. + * @dev This spell allows users to mint nTokens (providing liquidity) from existing cash balance. + * @param currencyId notional defined currency id of the cash balance + * @param cashBalanceToMint amount of account's cash balance to convert to nTokens + * @param getId id of cash balance + * @param setId id to set the value of nToken increase + */ + function mintNTokenFromCash( + uint16 currencyId, + uint256 cashBalanceToMint, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + cashBalanceToMint = getUint(getId, cashBalanceToMint); + if (cashBalanceToMint == type(uint256).max) + cashBalanceToMint = getCashOrNTokenBalance(currencyId, false); - 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 + 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 - uint256 nTokenBefore = getNTokenBalance(currencyId); + uint256 nTokenBefore = getCashOrNTokenBalance(currencyId, true); - notional.batchBalanceAction(address(this), action); + notional.batchBalanceAction(address(this), action); - uint256 nTokenBalanceChange = sub(getNTokenBalance(currencyId), nTokenBefore); + uint256 nTokenBalanceChange = sub( + getCashOrNTokenBalance(currencyId, true), + nTokenBefore + ); - if (setId != 0) { - // Set the amount of nTokens minted - setUint(setId, uint256(nTokenBalanceChange)); - } + if (setId != 0) { + // Set the amount of nTokens minted + setUint(setId, uint256(nTokenBalanceChange)); + } - _eventName = "LogMintNTokenFromCash(address,uint16,uint256,int256)"; - _eventParam = abi.encode( - address(this), - currencyId, - cashBalanceToMint, - nTokenBalanceChange - ); - } + _eventName = "LogMintNTokenFromCash(address,uint16,uint256,int256)"; + _eventParam = abi.encode( + address(this), + currencyId, + cashBalanceToMint, + nTokenBalanceChange + ); + } - /** - * @notice Deposits some amount of tokens and lends them in the specified market. This method can also be used to repay a - * borrow early by specifying the corresponding market index of an existing borrow. - * @dev Setting the fCash amount and minLendRate are best calculated using the Notional SDK off chain. They can - * be calculated on chain but there is a significant gas cost to doing so. If there is insufficient depositAmount for the - * fCashAmount specified Notional will revert. In most cases there will be some dust amount of cash left after lending and - * this method will withdraw that dust back to the account. - * @param currencyId notional defined currency id to lend - * @param depositAmount amount of cash to deposit to lend - * @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 marketIndex the market index to lend to. This is a number from 1 to 7 which corresponds to the tenor - * of the fCash asset to lend. Tenors are described here: https://docs.notional.finance/notional-v2/quarterly-rolls/tenors - * @param fCashAmount amount of fCash for the account to receive, this is equal to how much the account will receive - * at maturity (principal plus interest). - * @param minLendRate the minimum interest rate that the account is willing to lend at, if set to zero the account will accept - * any lending rate - * @param getId returns the deposit amount - */ - function depositAndLend( - uint16 currencyId, - uint256 depositAmount, - bool useUnderlying, - uint8 marketIndex, - uint88 fCashAmount, - uint32 minLendRate, - uint256 getId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - depositAmount = getDepositAmountAndSetApproval( - getId, - currencyId, - useUnderlying, - depositAmount - ); + /** + * @notice Deposits some amount of tokens and lends them in the specified market. This method can also be used to repay a + * borrow early by specifying the corresponding market index of an existing borrow. + * @dev Setting the fCash amount and minLendRate are best calculated using the Notional SDK off chain. They can + * be calculated on chain but there is a significant gas cost to doing so. If there is insufficient depositAmount for the + * fCashAmount specified Notional will revert. In most cases there will be some dust amount of cash left after lending and + * this method will withdraw that dust back to the account. + * @param currencyId notional defined currency id to lend + * @param depositAmount amount of cash to deposit to lend + * @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 marketIndex the market index to lend to. This is a number from 1 to 7 which corresponds to the tenor + * of the fCash asset to lend. Tenors are described here: https://docs.notional.finance/notional-v2/quarterly-rolls/tenors + * @param fCashAmount amount of fCash for the account to receive, this is equal to how much the account will receive + * at maturity (principal plus interest). + * @param minLendRate the minimum interest rate that the account is willing to lend at, if set to zero the account will accept + * any lending rate + * @param getId returns the deposit amount + */ + function depositAndLend( + uint16 currencyId, + uint256 depositAmount, + bool useUnderlying, + uint8 marketIndex, + uint88 fCashAmount, + uint32 minLendRate, + uint256 getId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + depositAmount = getDepositAmountAndSetApproval( + getId, + currencyId, + useUnderlying, + depositAmount + ); - BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[]( - 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; - action[0].redeemToUnderlying = useUnderlying; + BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[]( + 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; + action[0].redeemToUnderlying = useUnderlying; - bytes32[] memory trades = new bytes32[](1); - trades[0] = encodeLendTrade(marketIndex, fCashAmount, minLendRate); - action[0].trades = trades; + bytes32[] memory trades = new bytes32[](1); + trades[0] = encodeLendTrade(marketIndex, fCashAmount, minLendRate); + action[0].trades = trades; - uint256 msgValue = getMsgValue( - currencyId, - useUnderlying, - depositAmount - ); - notional.batchBalanceAndTradeAction{value: msgValue}( - address(this), - action - ); + uint256 msgValue = getMsgValue( + currencyId, + useUnderlying, + depositAmount + ); + notional.batchBalanceAndTradeAction{ value: msgValue }( + address(this), + action + ); - _eventName = "LogDepositAndLend(address,uint16,bool,uint256,uint8,uint88,uint32)"; - _eventParam = abi.encode( - address(this), - currencyId, - useUnderlying, - depositAmount, - marketIndex, - fCashAmount, - minLendRate - ); - } + _eventName = "LogDepositAndLend(address,uint16,bool,uint256,uint8,uint88,uint32)"; + _eventParam = abi.encode( + address(this), + currencyId, + useUnderlying, + depositAmount, + marketIndex, + fCashAmount, + minLendRate + ); + } - /** - * @notice Deposits some amount of tokens as collateral and borrows. This can be achieved by combining multiple spells but this - * method is more gas efficient by only making a single call to Notional. - * @dev Setting the fCash amount and maxBorrowRate are best calculated using the Notional SDK off chain. The amount of fCash - * when borrowing is more forgiving compared to lending since generally accounts will over collateralize and dust amounts are - * less likely to cause reverts. The Notional SDK will also provide calculations to tell the user what their LTV is for a given - * borrowing action. - * @param depositCurrencyId notional defined currency id of the collateral to deposit - * @param depositAction one of the following values which will define how the collateral is deposited: - * - None: no collateral will be deposited - * - DepositAsset: deposit amount will be specified in asset tokens (i.e. cTokens) - * - DepositUnderlying: deposit amount will be specified in underlying tokens (i.e. DAI) - * - DepositAssetAndMintNToken: deposit amount will be converted to nTokens - * - DepositUnderlyingAndMintNToken: deposit amount will be converted to nTokens - * - * Technically these two deposit types can be used, but there is not a clear reason why they would be used in combination - * with borrowing: - * - RedeemNToken - * - ConvertCashToNToken - * - * @param depositAmount amount of cash to deposit as collateral - * @param borrowCurrencyId id of the currency to borrow - * @param marketIndex the market index to borrow from. This is a number from 1 to 7 which corresponds to the tenor - * of the fCash asset to borrow. Tenors are described here: https://docs.notional.finance/notional-v2/quarterly-rolls/tenors - * @param fCashAmount amount of fCash for the account to borrow, this is equal to how much the account must pay - * at maturity (principal plus interest). - * @param maxBorrowRate the maximum interest rate that the account is willing to borrow at, if set to zero the account will accept - * any borrowing rate - * @param redeemToUnderlying if true, redeems the borrowed balance from cTokens down to the underlying token before transferring - * to the account - * @param getId returns the collateral deposit amount - * @param setId sets the amount that the account borrowed (i.e. how much of borrowCurrencyId it has received) - */ - function depositCollateralBorrowAndWithdraw( - uint16 depositCurrencyId, - DepositActionType depositAction, - uint256 depositAmount, - uint16 borrowCurrencyId, - uint8 marketIndex, - uint88 fCashAmount, - uint32 maxBorrowRate, - bool redeemToUnderlying, - uint256 getId, - uint256 setId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - bool useUnderlying = (depositAction == - DepositActionType.DepositUnderlying || - depositAction == DepositActionType.DepositUnderlyingAndMintNToken); + /** + * @notice Deposits some amount of tokens as collateral and borrows. This can be achieved by combining multiple spells but this + * method is more gas efficient by only making a single call to Notional. + * @dev Setting the fCash amount and maxBorrowRate are best calculated using the Notional SDK off chain. The amount of fCash + * when borrowing is more forgiving compared to lending since generally accounts will over collateralize and dust amounts are + * less likely to cause reverts. The Notional SDK will also provide calculations to tell the user what their LTV is for a given + * borrowing action. + * @param depositCurrencyId notional defined currency id of the collateral to deposit + * @param depositAction one of the following values which will define how the collateral is deposited: + * - None: no collateral will be deposited + * - DepositAsset: deposit amount will be specified in asset tokens (i.e. cTokens) + * - DepositUnderlying: deposit amount will be specified in underlying tokens (i.e. DAI) + * - DepositAssetAndMintNToken: deposit amount will be converted to nTokens + * - DepositUnderlyingAndMintNToken: deposit amount will be converted to nTokens + * + * Technically these two deposit types can be used, but there is not a clear reason why they would be used in combination + * with borrowing: + * - RedeemNToken + * - ConvertCashToNToken + * + * @param depositAmount amount of cash to deposit as collateral + * @param borrowCurrencyId id of the currency to borrow + * @param marketIndex the market index to borrow from. This is a number from 1 to 7 which corresponds to the tenor + * of the fCash asset to borrow. Tenors are described here: https://docs.notional.finance/notional-v2/quarterly-rolls/tenors + * @param fCashAmount amount of fCash for the account to borrow, this is equal to how much the account must pay + * at maturity (principal plus interest). + * @param maxBorrowRate the maximum interest rate that the account is willing to borrow at, if set to zero the account will accept + * any borrowing rate + * @param redeemToUnderlying if true, redeems the borrowed balance from cTokens down to the underlying token before transferring + * to the account + * @param getId returns the collateral deposit amount + * @param setId sets the amount that the account borrowed (i.e. how much of borrowCurrencyId it has received) + */ + function depositCollateralBorrowAndWithdraw( + uint16 depositCurrencyId, + DepositActionType depositAction, + uint256 depositAmount, + uint16 borrowCurrencyId, + uint8 marketIndex, + uint88 fCashAmount, + uint32 maxBorrowRate, + bool redeemToUnderlying, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + bool useUnderlying = (depositAction == + DepositActionType.DepositUnderlying || + depositAction == DepositActionType.DepositUnderlyingAndMintNToken); - depositAmount = getDepositAmountAndSetApproval( - getId, - depositCurrencyId, - useUnderlying, - depositAmount - ); + depositAmount = getDepositAmountAndSetApproval( + getId, + depositCurrencyId, + useUnderlying, + depositAmount + ); - BalanceActionWithTrades[] - memory actions = getDepositCollateralBorrowAndWithdrawActions( - depositCurrencyId, - depositAction, - depositAmount, - borrowCurrencyId, - marketIndex, - fCashAmount, - maxBorrowRate, - redeemToUnderlying - ); + BalanceActionWithTrades[] + memory actions = getDepositCollateralBorrowAndWithdrawActions( + depositCurrencyId, + depositAction, + depositAmount, + borrowCurrencyId, + marketIndex, + fCashAmount, + maxBorrowRate, + redeemToUnderlying + ); - uint256 msgValue = getMsgValue( - depositCurrencyId, - useUnderlying, - depositAmount - ); - executeTradeActionWithBalanceChange( - actions, - msgValue, - borrowCurrencyId, - redeemToUnderlying, - setId - ); + uint256 msgValue = getMsgValue( + depositCurrencyId, + useUnderlying, + depositAmount + ); + executeTradeActionWithBalanceChange( + actions, + msgValue, + borrowCurrencyId, + redeemToUnderlying, + setId + ); - _eventName = "LogDepositCollateralBorrowAndWithdraw(address,bool,uint256,uint16,uint8,uint88,uint32,bool)"; - _eventParam = abi.encode( - address(this), - useUnderlying, - depositAmount, - borrowCurrencyId, - marketIndex, - fCashAmount, - maxBorrowRate, - redeemToUnderlying - ); - } + _eventName = "LogDepositCollateralBorrowAndWithdraw(address,bool,uint256,uint16,uint8,uint88,uint32,bool)"; + _eventParam = abi.encode( + address(this), + useUnderlying, + depositAmount, + borrowCurrencyId, + marketIndex, + fCashAmount, + maxBorrowRate, + redeemToUnderlying + ); + } - /** - * @notice Allows an account to withdraw from a fixed rate lend by selling the fCash back to the market. Equivalent to - * borrowing from the Notional perspective. - * @dev Setting the fCash amount and maxBorrowRate are best calculated using the Notional SDK off chain. Similar to borrowing, - * setting these amounts are a bit more forgiving since there is no change of reverts due to dust amounts. - * @param currencyId notional defined currency id of the lend asset to withdraw - * @param marketIndex the market index of the fCash asset. This is a number from 1 to 7 which corresponds to the tenor - * of the fCash asset. Tenors are described here: https://docs.notional.finance/notional-v2/quarterly-rolls/tenors - * @param fCashAmount amount of fCash at the marketIndex that should be sold - * @param maxBorrowRate the maximum interest rate that the account is willing to sell fCash at at, if set to zero the - * account will accept any rate - * @param setId sets the amount that the account has received when withdrawing its lend - */ - function withdrawLend( - uint16 currencyId, - uint8 marketIndex, - uint88 fCashAmount, - uint32 maxBorrowRate, - uint256 setId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - bool useUnderlying = currencyId != ETH_CURRENCY_ID; - BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[]( - 1 - ); - action[0].actionType = DepositActionType.None; - action[0].currencyId = currencyId; - // Withdraw borrowed amount to wallet - action[0].withdrawEntireCashBalance = true; - action[0].redeemToUnderlying = useUnderlying; + /** + * @notice Allows an account to withdraw from a fixed rate lend by selling the fCash back to the market. Equivalent to + * borrowing from the Notional perspective. + * @dev Setting the fCash amount and maxBorrowRate are best calculated using the Notional SDK off chain. Similar to borrowing, + * setting these amounts are a bit more forgiving since there is no change of reverts due to dust amounts. + * @param currencyId notional defined currency id of the lend asset to withdraw + * @param marketIndex the market index of the fCash asset. This is a number from 1 to 7 which corresponds to the tenor + * of the fCash asset. Tenors are described here: https://docs.notional.finance/notional-v2/quarterly-rolls/tenors + * @param fCashAmount amount of fCash at the marketIndex that should be sold + * @param maxBorrowRate the maximum interest rate that the account is willing to sell fCash at at, if set to zero the + * account will accept any rate + * @param setId sets the amount that the account has received when withdrawing its lend + */ + function withdrawLend( + uint16 currencyId, + uint8 marketIndex, + uint88 fCashAmount, + uint32 maxBorrowRate, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + bool useUnderlying = currencyId != ETH_CURRENCY_ID; + BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[]( + 1 + ); + action[0].actionType = DepositActionType.None; + action[0].currencyId = currencyId; + // Withdraw borrowed amount to wallet + action[0].withdrawEntireCashBalance = true; + action[0].redeemToUnderlying = useUnderlying; - bytes32[] memory trades = new bytes32[](1); - trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); - action[0].trades = trades; + bytes32[] memory trades = new bytes32[](1); + trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); + action[0].trades = trades; - executeTradeActionWithBalanceChange( - action, - 0, - currencyId, - useUnderlying, - setId - ); + executeTradeActionWithBalanceChange( + action, + 0, + currencyId, + useUnderlying, + setId + ); - _eventName = "LogWithdrawLend(address,uint16,uint8,uint88,uint32)"; - _eventParam = abi.encode( - address(this), - currencyId, - marketIndex, - fCashAmount, - maxBorrowRate - ); - } + _eventName = "LogWithdrawLend(address,uint16,uint8,uint88,uint32)"; + _eventParam = abi.encode( + address(this), + currencyId, + marketIndex, + fCashAmount, + maxBorrowRate + ); + } - /** - * @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. This can be used to roll lends and borrows forward. - * @param actions a set of BatchActionWithTrades that will be executed for this account - */ - function batchActionRaw(BalanceActionWithTrades[] memory actions) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - notional.batchBalanceAndTradeAction(address(this), actions); + /** + * @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. This can be used to roll lends and borrows forward. + * @param actions a set of BatchActionWithTrades that will be executed for this account + */ + function batchActionRaw(BalanceActionWithTrades[] memory actions) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + notional.batchBalanceAndTradeAction(address(this), actions); - _eventName = "LogBatchActionRaw(address)"; - _eventParam = abi.encode(address(this)); - } + _eventName = "LogBatchActionRaw(address)"; + _eventParam = abi.encode(address(this)); + } } contract ConnectV2Notional is NotionalResolver { - string public name = "Notional-v1.1"; + string public name = "Notional-v1.1"; } diff --git a/test/mainnet/notional/notional.helpers.ts b/test/mainnet/notional/notional.helpers.ts index 61233e44..4ba8c422 100644 --- a/test/mainnet/notional/notional.helpers.ts +++ b/test/mainnet/notional/notional.helpers.ts @@ -95,7 +95,7 @@ const redeemNTokenRaw = async ( { connector: "NOTIONAL-TEST-A", method: "redeemNTokenRaw", - args: [currencyId, sellTokenAssets, tokensToRedeem, 0, 0] + args: [currencyId, sellTokenAssets, tokensToRedeem, false, 0, 0] } ]; diff --git a/test/mainnet/notional/notional.test.ts b/test/mainnet/notional/notional.test.ts index 967548cb..92c78853 100644 --- a/test/mainnet/notional/notional.test.ts +++ b/test/mainnet/notional/notional.test.ts @@ -52,7 +52,7 @@ describe("Notional", function () { forking: { //@ts-ignore jsonRpcUrl: hre.config.networks.hardhat.forking.url, - blockNumber: 13798624, + blockNumber: 14483893, }, }, ], @@ -240,7 +240,7 @@ describe("Notional", function () { expect( await daiToken.balanceOf(dsaWallet0.address), "expect DSA wallet to contain borrowed balance minus fees" - ).to.be.gte(ethers.utils.parseEther("990")); + ).to.be.gte(ethers.utils.parseEther("985")); }); it("test_deposit_ETH_and_borrow_DAI_asset", async function () { @@ -256,7 +256,7 @@ describe("Notional", function () { expect( await cdaiToken.balanceOf(dsaWallet0.address), "expect DSA wallet to contain borrowed balance minus fees" - ).to.be.gte(ethers.utils.parseUnits("4500000000000", 0)); + ).to.be.gte(ethers.utils.parseUnits("4490000000000", 0)); }); it("test_deposit_DAI_underlying_and_borrow_ETH", async function () {