From b09755846016e3bc5495064aab4bd8dfb55b496a Mon Sep 17 00:00:00 2001 From: Jeff Wu <jeffywu@pm.me> Date: Wed, 10 Nov 2021 14:52:16 -0800 Subject: [PATCH 01/20] initial commit --- .../mainnet/connectors/notional/events.sol | 5 + .../mainnet/connectors/notional/helpers.sol | 11 + .../mainnet/connectors/notional/interface.sol | 0 .../mainnet/connectors/notional/main.sol | 395 ++++++++++++++++++ 4 files changed, 411 insertions(+) create mode 100644 contracts/mainnet/connectors/notional/events.sol create mode 100644 contracts/mainnet/connectors/notional/helpers.sol create mode 100644 contracts/mainnet/connectors/notional/interface.sol create mode 100644 contracts/mainnet/connectors/notional/main.sol diff --git a/contracts/mainnet/connectors/notional/events.sol b/contracts/mainnet/connectors/notional/events.sol new file mode 100644 index 00000000..ea195d06 --- /dev/null +++ b/contracts/mainnet/connectors/notional/events.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.7.6; + +contract Events { + +} diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol new file mode 100644 index 00000000..9fab8aab --- /dev/null +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -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); + +} diff --git a/contracts/mainnet/connectors/notional/interface.sol b/contracts/mainnet/connectors/notional/interface.sol new file mode 100644 index 00000000..e69de29b diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol new file mode 100644 index 00000000..8d820a81 --- /dev/null +++ b/contracts/mainnet/connectors/notional/main.sol @@ -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 + } +} \ No newline at end of file From 45a85c09aab6337344c00aac7e4376d9f6f3289a Mon Sep 17 00:00:00 2001 From: Jeff Wu <jeffywu@pm.me> Date: Thu, 11 Nov 2021 10:12:34 -0800 Subject: [PATCH 02/20] some updates --- .../mainnet/connectors/notional/helpers.sol | 2 + .../mainnet/connectors/notional/main.sol | 62 ++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index 9fab8aab..f8e5aa89 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -8,4 +8,6 @@ contract Helpers { // function getNTokenBalance(uint16 currencyId); // function convertToInternal(uint16 currencyId); + function getDepositAmountAndSetApproval(uint16 currencyId) + function executeActionWithBalanceChange(uint16 currencyId) } diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index 8d820a81..b87b9694 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -2,6 +2,7 @@ pragma solidity ^0.7.6; import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; +import { TokenInterface } from "../common/interfaces.sol"; /** * @title Notional @@ -332,6 +333,7 @@ abstract contract NotionalResolver is Events, Helpers { function depositCollateralBorrowAndWithdraw( uint16 depositCurrencyId, bool useUnderlying, + uint depositAmount, uint16 borrowCurrencyId, uint8 marketIndex, uint fCashAmount, @@ -341,7 +343,7 @@ abstract contract NotionalResolver is Events, Helpers { uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { require(depositCurrencyId != borrowCurrencyId); - address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); + address tokenAddress = useUnderlying ? getUnderlyingToken(depositCurrencyId) : getAssetToken(depositCurrencyId); depositAmount = getUint(getId, depositAmount); if (depositAmount == uint(-1)) depositAmount = ERC20(tokenAddress).balanceOf(address(this)); @@ -374,15 +376,71 @@ abstract contract NotionalResolver is Events, Helpers { trades[borrowIndex] = encodeBorrowTrade(marketIndex, fCashAmount, minLendRate); action[borrowIndex].trades = trades; + address borrowToken; + uint balanceBefore; + if (setId != 0) { + address borrowToken = useUnderlying ? getUnderlyingToken(borrowCurrencyId) : getAssetToken(borrowCurrencyId); + balanceBefore = ERC20(borrowToken).balanceOf(address(this)); + } + notional.batchBalanceActionWithTrades{value: msgValue}(address(this), action); + if (setId != 0) { + setUint(setId, ERC20(borrowToken).balanceOf(address(this)).sub(balanceBefore)); + } + // todo: events } + function withdrawLend( + uint16 currencyId, + uint8 marketIndex, + uint fCashAmount, + uint maxBorrowRate, + uint getId, + uint setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](); + action[0].actionType = DepositActionType.None; + action[0].currencyId = currencyId; + // Withdraw borrowed amount to wallet + action[0].withdrawEntireCashBalance = true; + // TODO: will redeem underlying work with ETH? + action[0].redeemToUnderlying = useUnderlying; + + bytes32[] memory trades = new bytes32[](1); + trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); + action[0].trades = trades; + + address tokenAddress; + uint balanceBefore; + if (setId != 0) { + address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); + balanceBefore = ERC20(borrowToken).balanceOf(address(this)); + } + + notional.batchBalanceActionWithTrades{value: msgValue}(address(this), action); + + if (setId != 0) { + setUint(setId, ERC20(borrowToken).balanceOf(address(this)).sub(balanceBefore)); + } + } + + function repayBorrow( + uint16 currencyId, + uint8 marketIndex, + uint fCashAmount, + uint minLendRate, + uint getId, + uint setId + ) external payable returns (string memory _eventName, bytes memory _eventParam) { + // might want to use getfCashAmountGivenCashAmount + } + /** * @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. + * 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( From 6bbfa348f0262e4187de7ac6e2928c3fabb11284 Mon Sep 17 00:00:00 2001 From: Tianjie Wei <weitianjie@gmail.com> Date: Mon, 29 Nov 2021 12:38:43 -0800 Subject: [PATCH 03/20] Fixing build errors --- .../mainnet/connectors/notional/helpers.sol | 114 ++++++++++++++-- .../mainnet/connectors/notional/interface.sol | 126 ++++++++++++++++++ .../mainnet/connectors/notional/main.sol | 52 ++++---- 3 files changed, 260 insertions(+), 32 deletions(-) diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index f8e5aa89..a029797d 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -1,13 +1,111 @@ pragma solidity ^0.7.6; +pragma abicoder v2; -contract Helpers { +import {Token, NotionalInterface} from "./interface.sol"; +import {Basic} from "../../common/basic.sol"; - // function getUnderlyingToken(uint16 currencyId); - // function getAssetToken(uint16 currencyId); - // function getCashBalance(uint16 currencyId); - // function getNTokenBalance(uint16 currencyId); - // function convertToInternal(uint16 currencyId); +contract Helpers is Basic { + 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; + int256 private constant _INT256_MIN = type(int256).min; - function getDepositAmountAndSetApproval(uint16 currencyId) - function executeActionWithBalanceChange(uint16 currencyId) + NotionalInterface internal constant notional = + NotionalInterface(0xE592427A0AEce92De3Edee1F18E0157C05861564); + + function getUnderlyingToken(uint16 currencyId) internal returns (address) { + (, Token memory underlyingToken) = notional.getCurrency(currencyId); + return underlyingToken.tokenAddress; + } + + function getAssetToken(uint16 currencyId) internal returns (address) { + (Token memory assetToken, ) = notional.getCurrency(currencyId); + return assetToken.tokenAddress; + } + + function getCashBalance(uint16 currencyId) + internal + returns (int256 cashBalance) + { + (cashBalance, , ) = notional.getAccountBalance( + currencyId, + address(this) + ); + } + + function getNTokenBalance(uint16 currencyId) + internal + returns (int256 nTokenBalance) + { + (, nTokenBalance, ) = notional.getAccountBalance( + currencyId, + address(this) + ); + } + + function convertToInternal(uint16 currencyId, int256 amount) + internal + pure + 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. + (Token memory assetToken, ) = notional.getCurrency(currencyId); + if (assetToken.decimals == INTERNAL_TOKEN_PRECISION) return amount; + return div(mul(amount, INTERNAL_TOKEN_PRECISION), assetToken.decimals); + } + + function encodeLendTrade( + uint8 marketIndex, + uint88 fCashAmount, + uint32 minLendRate + ) internal returns (bytes32) { + return + abi.encodePacked( + LEND_TRADE, + marketIndex, + fCashAmount, + minLendRate, + uint120(0) + ); + } + + function encodeBorrowTrade( + uint8 marketIndex, + uint88 fCashAmount, + uint32 maxBorrowRate + ) internal returns (bytes32) { + return + abi.encodePacked( + BORROW_TRADE, + marketIndex, + fCashAmount, + maxBorrowRate, + uint120(0) + ); + } + + function mul(int256 a, int256 b) internal pure returns (int256 c) { + c = a * b; + if (a == -1) require(b == 0 || c / b == a); + else require(a == 0 || c / a == b); + } + + function div(int256 a, int256 b) internal pure returns (int256 c) { + require(!(b == -1 && a == _INT256_MIN)); // dev: int256 div overflow + // NOTE: solidity will automatically revert on divide by zero + c = a / b; + } + + function sub(int256 x, int256 y) internal pure returns (int256 z) { + // taken from uniswap v3 + require((z = x - y) <= x == (y >= 0)); + } + + //function getDepositAmountAndSetApproval(uint16 currencyId) internal; + + //function executeActionWithBalanceChange(uint16 currencyId) internal; } diff --git a/contracts/mainnet/connectors/notional/interface.sol b/contracts/mainnet/connectors/notional/interface.sol index e69de29b..329b269c 100644 --- a/contracts/mainnet/connectors/notional/interface.sol +++ b/contracts/mainnet/connectors/notional/interface.sol @@ -0,0 +1,126 @@ +pragma solidity ^0.7.6; +pragma abicoder v2; + +/// @notice Different types of internal tokens +/// - UnderlyingToken: underlying asset for a cToken (except for Ether) +/// - cToken: Compound interest bearing token +/// - cETH: Special handling for cETH tokens +/// - Ether: the one and only +/// - NonMintable: tokens that do not have an underlying (therefore not cTokens) +enum TokenType { + UnderlyingToken, + cToken, + cETH, + Ether, + NonMintable +} + +/// @notice Specifies different deposit actions that can occur during BalanceAction or BalanceActionWithTrades +enum DepositActionType { + // No deposit action + None, + // Deposit asset cash, depositActionAmount is specified in asset cash external precision + DepositAsset, + // Deposit underlying tokens that are mintable to asset cash, depositActionAmount is specified in underlying token + // external precision + DepositUnderlying, + // Deposits specified asset cash external precision amount into an nToken and mints the corresponding amount of + // nTokens into the account + DepositAssetAndMintNToken, + // Deposits specified underlying in external precision, mints asset cash, and uses that asset cash to mint nTokens + DepositUnderlyingAndMintNToken, + // Redeems an nToken balance to asset cash. depositActionAmount is specified in nToken precision. Considered a deposit action + // because it deposits asset cash into an account. If there are fCash residuals that cannot be sold off, will revert. + RedeemNToken, + // Converts specified amount of asset cash balance already in Notional to nTokens. depositActionAmount is specified in + // Notional internal 8 decimal precision. + ConvertCashToNToken +} + +/// @notice Defines a balance action with a set of trades to do as well +struct BalanceActionWithTrades { + DepositActionType actionType; + uint16 currencyId; + uint256 depositActionAmount; + uint256 withdrawAmountInternalPrecision; + bool withdrawEntireCashBalance; + bool redeemToUnderlying; + // Array of tightly packed 32 byte objects that represent trades. See TradeActionType documentation + bytes32[] trades; +} + +/// @notice Defines a balance action for batchAction +struct BalanceAction { + // Deposit action to take (if any) + DepositActionType actionType; + uint16 currencyId; + // Deposit action amount must correspond to the depositActionType, see documentation above. + uint256 depositActionAmount; + // Withdraw an amount of asset cash specified in Notional internal 8 decimal precision + uint256 withdrawAmountInternalPrecision; + // If set to true, will withdraw entire cash balance. Useful if there may be an unknown amount of asset cash + // residual left from trading. + bool withdrawEntireCashBalance; + // If set to true, will redeem asset cash to the underlying token on withdraw. + bool redeemToUnderlying; +} + +struct Token { + address tokenAddress; + bool hasTransferFee; + int256 decimals; + TokenType tokenType; + uint256 maxCollateralBalance; +} + +interface NotionalInterface { + function getCurrency(uint16 currencyId) + external + view + returns (Token memory assetToken, Token memory underlyingToken); + + function getAccountBalance(uint16 currencyId, address account) + external + view + returns ( + int256 cashBalance, + int256 nTokenBalance, + uint256 lastClaimTime + ); + + function depositUnderlyingToken( + address account, + uint16 currencyId, + uint256 amountExternalPrecision + ) external payable returns (uint256); + + function depositAssetToken( + address account, + uint16 currencyId, + uint256 amountExternalPrecision + ) external returns (uint256); + + function withdraw( + uint16 currencyId, + uint88 amountInternalPrecision, + bool redeemToUnderlying + ) external returns (uint256); + + function nTokenClaimIncentives() external returns (uint256); + + function nTokenRedeem( + address redeemer, + uint16 currencyId, + uint96 tokensToRedeem_, + bool sellTokenAssets + ) external returns (int256); + + function batchBalanceAction( + address account, + BalanceAction[] 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 b87b9694..b2dc52cb 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -1,8 +1,11 @@ pragma solidity ^0.7.6; +pragma abicoder v2; import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; -import { TokenInterface } from "../common/interfaces.sol"; +import { DepositActionType, BalanceActionWithTrades, BalanceAction } from "./interface.sol"; +import { TokenInterface } from "../../common/interfaces.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /** * @title Notional @@ -29,14 +32,14 @@ abstract contract NotionalResolver is Events, Helpers { uint assetCashDeposited; address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); depositAmount = getUint(getId, depositAmount); - if (depositAmount == uint(-1)) depositAmount = ERC20(tokenAddress).balanceOf(address(this)); + if (depositAmount == uint(-1)) depositAmount = IERC20(tokenAddress).balanceOf(address(this)); approve(tokenAddress, address(notional), depositAmount); if (useUnderlying && currencyId == ETH_CURRENCY_ID) { - assetCashDeposited = notional.depositUnderlying{value: depositAmount}(address(this), currencyId, depositAmount); + assetCashDeposited = notional.depositUnderlyingToken{value: depositAmount}(address(this), currencyId, depositAmount); } else if (useUnderlying) { - assetCashDeposited = notional.depositUnderlying{value: depositAmount}(address(this), currencyId, depositAmount); + assetCashDeposited = notional.depositUnderlyingToken{value: depositAmount}(address(this), currencyId, depositAmount); } else { assetCashDeposited = notional.depositAssetToken(address(this), currencyId, depositAmount); } @@ -166,14 +169,14 @@ abstract contract NotionalResolver is Events, Helpers { getAssetToken(currencyId); // TODO: handle ETH - balanceBefore = ERC20(tokenAddress).balanceOf(address(this)); + balanceBefore = IERC20(tokenAddress).balanceOf(address(this)); } notional.batchBalanceAction(address(this), action); if (setId != 0) { // TODO: handle ETH - uint netBalance = balanceBefore.sub(ERC20(tokenAddress).balanceOf(address(this))); + uint netBalance = sub(balanceBefore, IERC20(tokenAddress).balanceOf(address(this))); // This can be used to determine the exact amount withdrawn setUint(setId, netBalance); } @@ -205,7 +208,7 @@ abstract contract NotionalResolver is Events, Helpers { ) external returns (string memory _eventName, bytes memory _eventParam) { tokensToRedeem = getUint(getId, tokensToRedeem); if (tokensToRedeem == uint(-1)) tokensToRedeem = getNTokenBalance(currencyId); - notional.nTokenRedeem(currencyId, tokensToRedeem, sellTokenAssets); + notional.nTokenRedeem(currencyId, tokensToRedeem, true); BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[1]; action[0].actionType = DepositActionType.RedeemNToken; @@ -217,7 +220,7 @@ abstract contract NotionalResolver is Events, Helpers { trades[0] = encodeLendTrade(marketIndex, fCashAmount, minLendRate); action[0].trades = trades; - notional.batchBalanceActionWithTrades(address(this), action); + notional.batchBalanceAndTradeAction(address(this), action); _eventName = "LogRedeemNTokenAndDeleverage(address,uint16,uint,uint8,uint)"; _eventParam = abi.encode(address(this), currencyId, tokensToRedeem, marketIndex, fCashAmount); @@ -242,7 +245,7 @@ abstract contract NotionalResolver is Events, Helpers { ) 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)); + if (depositAmount == uint(-1)) depositAmount = IERC20(tokenAddress).balanceOf(address(this)); approve(tokenAddress, address(notional), depositAmount); BalanceAction[] memory action = new BalanceAction[1]; @@ -251,17 +254,17 @@ abstract contract NotionalResolver is Events, Helpers { action[0].depositActionAmount = depositAmount; // withdraw amount, withdraw cash and redeem to underlying are all 0 and false - uint nTokenBefore + uint nTokenBefore; if (setId != 0) { nTokenBefore = getNTokenBalance(currencyId); } uint msgValue = currencyId == ETH_CURRENCY_ID ? depositAmount : 0; - notional.batchBalanceActionWithTrades{value: msgValue}(address(this), action); + notional.batchBalanceAndTradeAction{value: msgValue}(address(this), action); if (setId != 0) { // Set the amount of nTokens minted - setUint(setId, getNTokenBalance(currencyId).sub(nTokenBefore)); + setUint(setId, sub(getNTokenBalance(currencyId), nTokenBefore)); } // todo: events @@ -274,7 +277,7 @@ abstract contract NotionalResolver is Events, Helpers { uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { cashBalanceToMint = getUint(getId, cashBalanceToMint); - if (cashBalanceToMint == uint(-1)) = cashBalanceToMint = getCashBalance(currencyId); + if (cashBalanceToMint == uint(-1)) cashBalanceToMint = getCashBalance(currencyId); BalanceAction[] memory action = new BalanceAction[1]; action[0].actionType = DepositActionType.ConvertCashToNToken; @@ -282,7 +285,7 @@ abstract contract NotionalResolver is Events, Helpers { action[0].depositActionAmount = cashBalanceToMint; // NOTE: withdraw amount, withdraw cash and redeem to underlying are all 0 and false - uint nTokenBefore + uint nTokenBefore; if (setId != 0) { nTokenBefore = getNTokenBalance(currencyId); } @@ -308,7 +311,7 @@ abstract contract NotionalResolver is Events, Helpers { ) 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)); + if (depositAmount == uint(-1)) depositAmount = IERC20(tokenAddress).balanceOf(address(this)); approve(tokenAddress, address(notional), depositAmount); BalanceAction[] memory action = new BalanceAction[1]; @@ -345,7 +348,7 @@ abstract contract NotionalResolver is Events, Helpers { require(depositCurrencyId != borrowCurrencyId); address tokenAddress = useUnderlying ? getUnderlyingToken(depositCurrencyId) : getAssetToken(depositCurrencyId); depositAmount = getUint(getId, depositAmount); - if (depositAmount == uint(-1)) depositAmount = ERC20(tokenAddress).balanceOf(address(this)); + if (depositAmount == uint(-1)) depositAmount = IERC20(tokenAddress).balanceOf(address(this)); approve(tokenAddress, address(notional), depositAmount); BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](2); @@ -373,20 +376,20 @@ abstract contract NotionalResolver is Events, Helpers { action[borrowIndex].redeemToUnderlying = useUnderlying; bytes32[] memory trades = new bytes32[](1); - trades[borrowIndex] = encodeBorrowTrade(marketIndex, fCashAmount, minLendRate); + trades[borrowIndex] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); action[borrowIndex].trades = trades; address borrowToken; uint balanceBefore; if (setId != 0) { address borrowToken = useUnderlying ? getUnderlyingToken(borrowCurrencyId) : getAssetToken(borrowCurrencyId); - balanceBefore = ERC20(borrowToken).balanceOf(address(this)); + balanceBefore = IERC20(borrowToken).balanceOf(address(this)); } notional.batchBalanceActionWithTrades{value: msgValue}(address(this), action); if (setId != 0) { - setUint(setId, ERC20(borrowToken).balanceOf(address(this)).sub(balanceBefore)); + setUint(setId, IERC20(borrowToken).balanceOf(address(this)).sub(balanceBefore)); } // todo: events @@ -400,6 +403,7 @@ abstract contract NotionalResolver is Events, Helpers { uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { + bool useUnderlying = currencyId != ETH_CURRENCY_ID; BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](); action[0].actionType = DepositActionType.None; action[0].currencyId = currencyId; @@ -416,13 +420,13 @@ abstract contract NotionalResolver is Events, Helpers { uint balanceBefore; if (setId != 0) { address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); - balanceBefore = ERC20(borrowToken).balanceOf(address(this)); + balanceBefore = IERC20(tokenAddress).balanceOf(address(this)); } - notional.batchBalanceActionWithTrades{value: msgValue}(address(this), action); + notional.batchBalanceActionWithTrades{value: msg.value}(address(this), action); if (setId != 0) { - setUint(setId, ERC20(borrowToken).balanceOf(address(this)).sub(balanceBefore)); + setUint(setId, IERC20(tokenAddress).balanceOf(address(this)).sub(balanceBefore)); } } @@ -444,9 +448,9 @@ abstract contract NotionalResolver is Events, Helpers { * @param actions a set of BatchActionWithTrades that will be executed for this account */ function batchActionRaw( - BatchActionWithTrades[] memory actions + BalanceActionWithTrades[] memory actions ) external payable returns (string memory _eventName, bytes memory _eventParam) { - notional.batchBalanceActionWithTrades(address(this), actions); + notional.batchBalanceAndTradeAction(address(this), actions); // todo: events } From 25eadd99201bf45d93ad44d0031d2ee81cd659ab Mon Sep 17 00:00:00 2001 From: Tianjie Wei <weitianjie@gmail.com> Date: Mon, 29 Nov 2021 15:21:52 -0800 Subject: [PATCH 04/20] Fixing contract address --- contracts/mainnet/connectors/notional/helpers.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index a029797d..bf3060b5 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -12,7 +12,7 @@ contract Helpers is Basic { int256 private constant _INT256_MIN = type(int256).min; NotionalInterface internal constant notional = - NotionalInterface(0xE592427A0AEce92De3Edee1F18E0157C05861564); + NotionalInterface(0x1344a36a1b56144c3bc62e7757377d288fde0369); function getUnderlyingToken(uint16 currencyId) internal returns (address) { (, Token memory underlyingToken) = notional.getCurrency(currencyId); From 0cf73a7258ff81bf76ef9c02bcc1527fad27b9a7 Mon Sep 17 00:00:00 2001 From: Tianjie Wei <weitianjie@gmail.com> Date: Mon, 29 Nov 2021 16:55:26 -0800 Subject: [PATCH 05/20] Fixing build --- .../mainnet/connectors/notional/helpers.sol | 25 ++--- .../mainnet/connectors/notional/main.sol | 98 +++++++++---------- 2 files changed, 57 insertions(+), 66 deletions(-) diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index bf3060b5..1086b4c2 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -12,7 +12,7 @@ contract Helpers is Basic { int256 private constant _INT256_MIN = type(int256).min; NotionalInterface internal constant notional = - NotionalInterface(0x1344a36a1b56144c3bc62e7757377d288fde0369); + NotionalInterface(0x1344A36A1B56144C3Bc62E7757377D288fDE0369); function getUnderlyingToken(uint16 currencyId) internal returns (address) { (, Token memory underlyingToken) = notional.getCurrency(currencyId); @@ -46,7 +46,6 @@ contract Helpers is Basic { function convertToInternal(uint16 currencyId, int256 amount) internal - pure returns (int256) { // If token decimals is greater than INTERNAL_TOKEN_PRECISION then this will truncate @@ -64,13 +63,10 @@ contract Helpers is Basic { uint32 minLendRate ) internal returns (bytes32) { return - abi.encodePacked( - LEND_TRADE, - marketIndex, - fCashAmount, - minLendRate, - uint120(0) - ); + (bytes32(uint256(LEND_TRADE)) << 248) | + (bytes32(uint256(marketIndex)) << 240) | + (bytes32(uint256(fCashAmount)) << 152) | + (bytes32(uint256(minLendRate)) << 120); } function encodeBorrowTrade( @@ -79,13 +75,10 @@ contract Helpers is Basic { uint32 maxBorrowRate ) internal returns (bytes32) { return - abi.encodePacked( - BORROW_TRADE, - marketIndex, - fCashAmount, - maxBorrowRate, - uint120(0) - ); + (bytes32(uint256(BORROW_TRADE)) << 248) | + (bytes32(uint256(marketIndex)) << 240) | + (bytes32(uint256(fCashAmount)) << 152) | + (bytes32(uint256(maxBorrowRate)) << 120); } function mul(int256 a, int256 b) internal pure returns (int256 c) { diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index b2dc52cb..6e69ce7c 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -5,7 +5,6 @@ import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; import { DepositActionType, BalanceActionWithTrades, BalanceAction } from "./interface.sol"; import { TokenInterface } from "../../common/interfaces.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /** * @title Notional @@ -32,9 +31,9 @@ abstract contract NotionalResolver is Events, Helpers { uint assetCashDeposited; address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); depositAmount = getUint(getId, depositAmount); - if (depositAmount == uint(-1)) depositAmount = IERC20(tokenAddress).balanceOf(address(this)); + if (depositAmount == uint(-1)) depositAmount = TokenInterface(tokenAddress).balanceOf(address(this)); - approve(tokenAddress, address(notional), depositAmount); + approve(TokenInterface(tokenAddress), address(notional), depositAmount); if (useUnderlying && currencyId == ETH_CURRENCY_ID) { assetCashDeposited = notional.depositUnderlyingToken{value: depositAmount}(address(this), currencyId, depositAmount); @@ -68,9 +67,9 @@ abstract contract NotionalResolver is Events, Helpers { uint setId ) external returns (string memory _eventName, bytes memory _eventParam) { withdrawAmount = getUint(getId, withdrawAmount); - uint amountInternalPrecision = withdrawAmount == uint(-1) ? - getCashBalance(currencyId) : - convertToInternal(currencyId, withdrawAmount); + uint88 amountInternalPrecision = withdrawAmount == uint(-1) ? + uint88(getCashBalance(currencyId)) : + uint88(convertToInternal(currencyId, int256(withdrawAmount))); uint amountWithdrawn = notional.withdraw(currencyId, amountInternalPrecision, redeemToUnderlying); // Sets the amount of tokens withdrawn to address(this) @@ -108,12 +107,12 @@ abstract contract NotionalResolver is Events, Helpers { function redeemNTokenRaw( uint16 currencyId, bool sellTokenAssets, - uint tokensToRedeem, + uint96 tokensToRedeem, uint getId, uint setId ) external returns (string memory _eventName, bytes memory _eventParam) { - tokensToRedeem = getUint(getId, tokensToRedeem); - if (tokensToRedeem == uint(-1)) tokensToRedeem = getNTokenBalance(currencyId); + tokensToRedeem = uint96(getUint(getId, tokensToRedeem)); + if (tokensToRedeem == uint96(-1)) tokensToRedeem = uint96(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 @@ -147,9 +146,9 @@ abstract contract NotionalResolver is Events, Helpers { uint setId ) external returns (string memory _eventName, bytes memory _eventParam) { tokensToRedeem = getUint(getId, tokensToRedeem); - if (tokensToRedeem == uint(-1)) tokensToRedeem = getNTokenBalance(currencyId); + if (tokensToRedeem == uint(-1)) tokensToRedeem = uint(getNTokenBalance(currencyId)); - BalanceAction[] memory action = new BalanceAction[1]; + BalanceAction[] memory action = new BalanceAction[](1); action[0].actionType = DepositActionType.RedeemNToken; action[0].currencyId = currencyId; action[0].depositActionAmount = tokensToRedeem; @@ -169,14 +168,14 @@ abstract contract NotionalResolver is Events, Helpers { getAssetToken(currencyId); // TODO: handle ETH - balanceBefore = IERC20(tokenAddress).balanceOf(address(this)); + balanceBefore = TokenInterface(tokenAddress).balanceOf(address(this)); } notional.batchBalanceAction(address(this), action); if (setId != 0) { // TODO: handle ETH - uint netBalance = sub(balanceBefore, IERC20(tokenAddress).balanceOf(address(this))); + uint netBalance = sub(balanceBefore, TokenInterface(tokenAddress).balanceOf(address(this))); // This can be used to determine the exact amount withdrawn setUint(setId, netBalance); } @@ -200,17 +199,17 @@ abstract contract NotionalResolver is Events, Helpers { */ function redeemNTokenAndDeleverage( uint16 currencyId, - uint tokensToRedeem, + uint96 tokensToRedeem, uint8 marketIndex, - uint fCashAmount, + uint88 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, true); + tokensToRedeem = uint96(getUint(getId, tokensToRedeem)); + if (tokensToRedeem == uint96(-1)) tokensToRedeem = uint96(getNTokenBalance(currencyId)); + notional.nTokenRedeem(address(this), currencyId, tokensToRedeem, true); - BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[1]; + BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](1); action[0].actionType = DepositActionType.RedeemNToken; action[0].currencyId = currencyId; action[0].depositActionAmount = tokensToRedeem; @@ -245,26 +244,26 @@ abstract contract NotionalResolver is Events, Helpers { ) 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 = IERC20(tokenAddress).balanceOf(address(this)); + if (depositAmount == uint(-1)) depositAmount = TokenInterface(tokenAddress).balanceOf(address(this)); - approve(tokenAddress, address(notional), depositAmount); - BalanceAction[] memory action = new BalanceAction[1]; + approve(TokenInterface(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; + int256 nTokenBefore; if (setId != 0) { nTokenBefore = getNTokenBalance(currencyId); } uint msgValue = currencyId == ETH_CURRENCY_ID ? depositAmount : 0; - notional.batchBalanceAndTradeAction{value: msgValue}(address(this), action); + notional.batchBalanceAction{value: msgValue}(address(this), action); if (setId != 0) { // Set the amount of nTokens minted - setUint(setId, sub(getNTokenBalance(currencyId), nTokenBefore)); + setUint(setId, uint(sub(getNTokenBalance(currencyId), nTokenBefore))); } // todo: events @@ -277,24 +276,24 @@ abstract contract NotionalResolver is Events, Helpers { uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { cashBalanceToMint = getUint(getId, cashBalanceToMint); - if (cashBalanceToMint == uint(-1)) cashBalanceToMint = getCashBalance(currencyId); + if (cashBalanceToMint == uint(-1)) cashBalanceToMint = uint(getCashBalance(currencyId)); - BalanceAction[] memory action = new BalanceAction[1]; + 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; + int256 nTokenBefore; if (setId != 0) { nTokenBefore = getNTokenBalance(currencyId); } - notional.batchBalanceActionWithTrades(address(this), action); + notional.batchBalanceAction(address(this), action); if (setId != 0) { // Set the amount of nTokens minted - setUint(setId, getNTokenBalance(currencyId).sub(nTokenBefore)); + setUint(setId, uint(sub(getNTokenBalance(currencyId), nTokenBefore))); } // todo: events @@ -305,16 +304,16 @@ abstract contract NotionalResolver is Events, Helpers { uint depositAmount, bool useUnderlying, uint8 marketIndex, - uint fCashAmount, - uint minLendRate, + uint88 fCashAmount, + uint32 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 = IERC20(tokenAddress).balanceOf(address(this)); + if (depositAmount == uint(-1)) depositAmount = TokenInterface(tokenAddress).balanceOf(address(this)); - approve(tokenAddress, address(notional), depositAmount); - BalanceAction[] memory action = new BalanceAction[1]; + approve(TokenInterface(tokenAddress), address(notional), depositAmount); + BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](1); action[0].actionType = useUnderlying ? DepositActionType.DepositUnderlying : DepositActionType.DepositAsset; action[0].currencyId = currencyId; action[0].depositActionAmount = depositAmount; @@ -328,7 +327,7 @@ abstract contract NotionalResolver is Events, Helpers { action[0].trades = trades; uint msgValue = currencyId == ETH_CURRENCY_ID ? depositAmount : 0; - notional.batchBalanceActionWithTrades{value: msgValue}(address(this), action); + notional.batchBalanceAndTradeAction{value: msgValue}(address(this), action); // todo: events } @@ -339,8 +338,8 @@ abstract contract NotionalResolver is Events, Helpers { uint depositAmount, uint16 borrowCurrencyId, uint8 marketIndex, - uint fCashAmount, - uint maxBorrowRate, + uint88 fCashAmount, + uint32 maxBorrowRate, bool redeemToUnderlying, uint getId, uint setId @@ -348,9 +347,9 @@ abstract contract NotionalResolver is Events, Helpers { require(depositCurrencyId != borrowCurrencyId); address tokenAddress = useUnderlying ? getUnderlyingToken(depositCurrencyId) : getAssetToken(depositCurrencyId); depositAmount = getUint(getId, depositAmount); - if (depositAmount == uint(-1)) depositAmount = IERC20(tokenAddress).balanceOf(address(this)); + if (depositAmount == uint(-1)) depositAmount = TokenInterface(tokenAddress).balanceOf(address(this)); - approve(tokenAddress, address(notional), depositAmount); + approve(TokenInterface(tokenAddress), address(notional), depositAmount); BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](2); uint256 depositIndex; @@ -383,13 +382,13 @@ abstract contract NotionalResolver is Events, Helpers { uint balanceBefore; if (setId != 0) { address borrowToken = useUnderlying ? getUnderlyingToken(borrowCurrencyId) : getAssetToken(borrowCurrencyId); - balanceBefore = IERC20(borrowToken).balanceOf(address(this)); + balanceBefore = TokenInterface(borrowToken).balanceOf(address(this)); } - notional.batchBalanceActionWithTrades{value: msgValue}(address(this), action); + notional.batchBalanceAndTradeAction{value: msgValue}(address(this), action); if (setId != 0) { - setUint(setId, IERC20(borrowToken).balanceOf(address(this)).sub(balanceBefore)); + setUint(setId, sub(TokenInterface(borrowToken).balanceOf(address(this)), balanceBefore)); } // todo: events @@ -398,13 +397,13 @@ abstract contract NotionalResolver is Events, Helpers { function withdrawLend( uint16 currencyId, uint8 marketIndex, - uint fCashAmount, - uint maxBorrowRate, + uint88 fCashAmount, + uint32 maxBorrowRate, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { bool useUnderlying = currencyId != ETH_CURRENCY_ID; - BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](); + BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](1); action[0].actionType = DepositActionType.None; action[0].currencyId = currencyId; // Withdraw borrowed amount to wallet @@ -420,13 +419,12 @@ abstract contract NotionalResolver is Events, Helpers { uint balanceBefore; if (setId != 0) { address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); - balanceBefore = IERC20(tokenAddress).balanceOf(address(this)); + balanceBefore = TokenInterface(tokenAddress).balanceOf(address(this)); } - - notional.batchBalanceActionWithTrades{value: msg.value}(address(this), action); + notional.batchBalanceAndTradeAction(address(this), action); if (setId != 0) { - setUint(setId, IERC20(tokenAddress).balanceOf(address(this)).sub(balanceBefore)); + setUint(setId, sub(TokenInterface(tokenAddress).balanceOf(address(this)), balanceBefore)); } } From 6de02284393cbf101e764d1a623a0ef4229624cf Mon Sep 17 00:00:00 2001 From: Tianjie Wei <weitianjie@gmail.com> Date: Wed, 15 Dec 2021 17:49:32 -0800 Subject: [PATCH 06/20] Adding tested contracts --- .../connectors/notional/SafeInt256.sol | 26 + .../mainnet/connectors/notional/events.sol | 90 +++ .../mainnet/connectors/notional/helpers.sol | 109 +++- .../mainnet/connectors/notional/interface.sol | 14 +- .../mainnet/connectors/notional/main.sol | 553 ++++++++++++------ 5 files changed, 599 insertions(+), 193 deletions(-) create mode 100644 contracts/mainnet/connectors/notional/SafeInt256.sol diff --git a/contracts/mainnet/connectors/notional/SafeInt256.sol b/contracts/mainnet/connectors/notional/SafeInt256.sol new file mode 100644 index 00000000..eb0ce7b7 --- /dev/null +++ b/contracts/mainnet/connectors/notional/SafeInt256.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.7.6; + +library SafeInt256 { + int256 private constant _INT256_MIN = type(int256).min; + + function mul(int256 a, int256 b) internal pure returns (int256 c) { + c = a * b; + if (a == -1) require(b == 0 || c / b == a); + else require(a == 0 || c / a == b); + } + + function div(int256 a, int256 b) internal pure returns (int256 c) { + require(!(b == -1 && a == _INT256_MIN)); // dev: int256 div overflow + // NOTE: solidity will automatically revert on divide by zero + c = a / b; + } + + function sub(int256 x, int256 y) internal pure returns (int256 z) { + // taken from uniswap v3 + require((z = x - y) <= x == (y >= 0)); + } + + function neg(int256 x) internal pure returns (int256 y) { + return mul(-1, x); + } +} diff --git a/contracts/mainnet/connectors/notional/events.sol b/contracts/mainnet/connectors/notional/events.sol index ea195d06..4c667956 100644 --- a/contracts/mainnet/connectors/notional/events.sol +++ b/contracts/mainnet/connectors/notional/events.sol @@ -1,5 +1,95 @@ pragma solidity ^0.7.6; contract Events { + event LogDepositCollateral( + address indexed account, + uint16 currencyId, + bool isUnderlying, + uint256 depositAmount, + uint256 assetCashDeposited + ); + event LogWithdrawCollateral( + address indexed account, + uint16 currencyId, + bool isUnderlying, + uint256 amountWithdrawn + ); + + event LogClaimNOTE(address indexed account, uint256 notesClaimed); + + event LogRedeemNTokenRaw( + address indexed account, + uint16 currencyId, + bool sellTokenAssets, + uint96 tokensToRedeem, + int256 assetCashChange + ); + + event LogRedeemNTokenWithdraw( + address indexed account, + uint16 currencyId, + uint96 tokensToRedeem, + uint256 amountToWithdraw, + bool redeemToUnderlying + ); + + event LogRedeemNTokenAndDeleverage( + address indexed account, + uint16 currencyId, + uint96 tokensToRedeem, + uint8 marketIndex, + uint88 fCashAmount + ); + + event LogDepositAndMintNToken( + address indexed account, + uint16 currencyId, + bool isUnderlying, + uint256 depositAmount, + int256 nTokenBalanceChange + ); + + event LogMintNTokenFromCash( + address indexed account, + uint16 currencyId, + uint256 cashBalanceToMint, + int256 nTokenBalanceChange + ); + + event LogDepositAndLend( + address indexed account, + uint16 currencyId, + bool isUnderlying, + uint256 depositAmount, + uint8 marketIndex, + uint88 fCashAmount, + uint32 minLendRate + ); + + event LogDepositCollateralBorrowAndWithdraw( + bool useUnderlying, + uint256 depositAmount, + uint16 borrowCurrencyId, + uint8 marketIndex, + uint88 fCashAmount, + uint32 maxBorrowRate, + bool redeemToUnderlying + ); + + event LogWithdrawLend( + uint16 currencyId, + uint8 marketIndex, + uint88 fCashAmount, + uint32 maxBorrowRate + ); + + event LogRepayBorrow( + uint16 currencyId, + uint8 marketIndex, + int88 netCashToAccount, + uint32 minLendRate + ); + + event LogBatchActionRaw(address indexed account); } diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index 1086b4c2..6f76fad3 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -1,15 +1,19 @@ pragma solidity ^0.7.6; pragma abicoder v2; -import {Token, NotionalInterface} from "./interface.sol"; +import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; +import {Token, NotionalInterface, BalanceAction, BalanceActionWithTrades} from "./interface.sol"; +import {SafeInt256} from "./SafeInt256.sol"; import {Basic} from "../../common/basic.sol"; +import {TokenInterface} from "../../common/interfaces.sol"; contract Helpers is Basic { + using SafeMath for uint256; + using SafeInt256 for int256; 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; - int256 private constant _INT256_MIN = type(int256).min; NotionalInterface internal constant notional = NotionalInterface(0x1344A36A1B56144C3Bc62E7757377D288fDE0369); @@ -54,7 +58,7 @@ contract Helpers is Basic { // end of amount and will not result in dust. (Token memory assetToken, ) = notional.getCurrency(currencyId); if (assetToken.decimals == INTERNAL_TOKEN_PRECISION) return amount; - return div(mul(amount, INTERNAL_TOKEN_PRECISION), assetToken.decimals); + return amount.mul(INTERNAL_TOKEN_PRECISION).div(assetToken.decimals); } function encodeLendTrade( @@ -81,24 +85,97 @@ contract Helpers is Basic { (bytes32(uint256(maxBorrowRate)) << 120); } - function mul(int256 a, int256 b) internal pure returns (int256 c) { - c = a * b; - if (a == -1) require(b == 0 || c / b == a); - else require(a == 0 || c / a == b); + function getDepositAmountAndSetApproval( + uint256 getId, + uint16 currencyId, + bool useUnderlying, + uint256 depositAmount + ) internal returns (uint256) { + depositAmount = getUint(getId, depositAmount); + if (currencyId == ETH_CURRENCY_ID && useUnderlying) + return + depositAmount == uint256(-1) + ? address(this).balance + : depositAmount; + + address tokenAddress = useUnderlying + ? getUnderlyingToken(currencyId) + : getAssetToken(currencyId); + if (depositAmount == uint256(-1)) { + depositAmount = TokenInterface(tokenAddress).balanceOf( + address(this) + ); + } + approve(TokenInterface(tokenAddress), address(notional), depositAmount); + return depositAmount; } - function div(int256 a, int256 b) internal pure returns (int256 c) { - require(!(b == -1 && a == _INT256_MIN)); // dev: int256 div overflow - // NOTE: solidity will automatically revert on divide by zero - c = a / b; + function getBalance(address addr) internal returns (uint256) { + if (addr == ethAddr) { + return address(this).balance; + } + + return TokenInterface(addr).balanceOf(address(this)); } - function sub(int256 x, int256 y) internal pure returns (int256 z) { - // taken from uniswap v3 - require((z = x - y) <= x == (y >= 0)); + function getAddress(uint16 currencyId, bool useUnderlying) + internal + returns (address) + { + if (currencyId == ETH_CURRENCY_ID && useUnderlying) { + return ethAddr; + } + + return + useUnderlying + ? getUnderlyingToken(currencyId) + : getAssetToken(currencyId); } - //function getDepositAmountAndSetApproval(uint16 currencyId) internal; + 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 executeActionWithBalanceChange(uint16 currencyId) internal; + notional.batchBalanceAndTradeAction{value: msgValue}( + address(this), + action + ); + + if (setId != 0) { + uint256 balanceAfter = getBalance(tokenAddress); + setUint(setId, balanceAfter.sub(balanceBefore)); + } + } + + 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)); + } + } } diff --git a/contracts/mainnet/connectors/notional/interface.sol b/contracts/mainnet/connectors/notional/interface.sol index 329b269c..e6430d1a 100644 --- a/contracts/mainnet/connectors/notional/interface.sol +++ b/contracts/mainnet/connectors/notional/interface.sol @@ -88,6 +88,13 @@ interface NotionalInterface { uint256 lastClaimTime ); + function getfCashAmountGivenCashAmount( + uint16 currencyId, + int88 netCashToAccount, + uint256 marketIndex, + uint256 blockTime + ) external view returns (int256); + function depositUnderlyingToken( address account, uint16 currencyId, @@ -120,7 +127,8 @@ interface NotionalInterface { 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 6e69ce7c..65cb2923 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -1,16 +1,18 @@ pragma solidity ^0.7.6; pragma abicoder v2; -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 {SafeInt256} from "./SafeInt256.sol"; +import {Events} from "./events.sol"; +import {DepositActionType, BalanceActionWithTrades, BalanceAction} from "./interface.sol"; +import {TokenInterface} from "../../common/interfaces.sol"; /** * @title Notional * @notice Fixed Rate Lending and Borrowing */ abstract contract NotionalResolver is Events, Helpers { + using SafeInt256 for int256; /** * @notice Deposit collateral into Notional @@ -24,29 +26,50 @@ abstract contract NotionalResolver is Events, Helpers { 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 = TokenInterface(tokenAddress).balanceOf(address(this)); - - approve(TokenInterface(tokenAddress), address(notional), depositAmount); + 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); + assetCashDeposited = notional.depositUnderlyingToken{ + value: depositAmount + }(address(this), currencyId, depositAmount); } else if (useUnderlying) { - assetCashDeposited = notional.depositUnderlyingToken{value: depositAmount}(address(this), currencyId, depositAmount); + assetCashDeposited = notional.depositUnderlyingToken( + address(this), + currencyId, + depositAmount + ); } else { - assetCashDeposited = notional.depositAssetToken(address(this), currencyId, depositAmount); + 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); + _eventParam = abi.encode( + address(this), + currencyId, + useUnderlying, + depositAmount, + assetCashDeposited + ); } /** @@ -62,31 +85,42 @@ abstract contract NotionalResolver is Events, Helpers { function withdrawCollateral( uint16 currencyId, bool redeemToUnderlying, - uint withdrawAmount, - uint getId, - uint setId + uint256 withdrawAmount, + uint256 getId, + uint256 setId ) external returns (string memory _eventName, bytes memory _eventParam) { withdrawAmount = getUint(getId, withdrawAmount); - uint88 amountInternalPrecision = withdrawAmount == uint(-1) ? - uint88(getCashBalance(currencyId)) : - uint88(convertToInternal(currencyId, int256(withdrawAmount))); + uint88 amountInternalPrecision = withdrawAmount == uint256(-1) + ? uint88(getCashBalance(currencyId)) + : uint88(convertToInternal(currencyId, int256(withdrawAmount))); - uint amountWithdrawn = notional.withdraw(currencyId, amountInternalPrecision, redeemToUnderlying); + uint256 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); + _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(); + function claimNOTE(uint256 setId) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 notesClaimed = notional.nTokenClaimIncentives(); setUint(setId, notesClaimed); _eventName = "LogClaimNOTE(address,uint256)"; @@ -108,21 +142,35 @@ abstract contract NotionalResolver is Events, Helpers { uint16 currencyId, bool sellTokenAssets, uint96 tokensToRedeem, - uint getId, - uint setId + uint256 getId, + uint256 setId ) external returns (string memory _eventName, bytes memory _eventParam) { tokensToRedeem = uint96(getUint(getId, tokensToRedeem)); - if (tokensToRedeem == uint96(-1)) tokensToRedeem = uint96(getNTokenBalance(currencyId)); - int _assetCashChange = notional.nTokenRedeem(address(this), currencyId, tokensToRedeem, sellTokenAssets); + if (tokensToRedeem == uint96(-1)) + tokensToRedeem = uint96(getNTokenBalance(currencyId)); + int256 _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; + uint256 assetCashChange = _assetCashChange > 0 + ? uint256(_assetCashChange) + : 0; setUint(setId, assetCashChange); - _eventName = "LogRedeemNTokenRaw(address,uint16,bool,uint,uint)"; - _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 + ); } /** @@ -139,49 +187,43 @@ abstract contract NotionalResolver is Events, Helpers { */ function redeemNTokenAndWithdraw( uint16 currencyId, - uint tokensToRedeem, - uint amountToWithdraw, + uint96 tokensToRedeem, + uint256 amountToWithdraw, bool redeemToUnderlying, - uint getId, - uint setId + uint256 getId, + uint256 setId ) external returns (string memory _eventName, bytes memory _eventParam) { - tokensToRedeem = getUint(getId, tokensToRedeem); - if (tokensToRedeem == uint(-1)) tokensToRedeem = uint(getNTokenBalance(currencyId)); + tokensToRedeem = uint96(getUint(getId, uint256(tokensToRedeem))); + if (tokensToRedeem == uint96(-1)) + tokensToRedeem = uint96(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)) { + if (amountToWithdraw == uint256(-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 = TokenInterface(tokenAddress).balanceOf(address(this)); - } + executeActionWithBalanceChange( + action, + 0, + currencyId, + redeemToUnderlying, + setId + ); - notional.batchBalanceAction(address(this), action); - - if (setId != 0) { - // TODO: handle ETH - uint netBalance = sub(balanceBefore, TokenInterface(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); + _eventName = "LogRedeemNTokenWithdraw(address,uint16,uint96,uint256,bool)"; + _eventParam = abi.encode( + address(this), + currencyId, + tokensToRedeem, + amountToWithdraw, + redeemToUnderlying + ); } /** @@ -203,13 +245,15 @@ abstract contract NotionalResolver is Events, Helpers { uint8 marketIndex, uint88 fCashAmount, uint32 minLendRate, - uint getId + uint256 getId ) external returns (string memory _eventName, bytes memory _eventParam) { tokensToRedeem = uint96(getUint(getId, tokensToRedeem)); - if (tokensToRedeem == uint96(-1)) tokensToRedeem = uint96(getNTokenBalance(currencyId)); - notional.nTokenRedeem(address(this), currencyId, tokensToRedeem, true); + if (tokensToRedeem == uint96(-1)) + tokensToRedeem = uint96(getNTokenBalance(currencyId)); - BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](1); + BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[]( + 1 + ); action[0].actionType = DepositActionType.RedeemNToken; action[0].currencyId = currencyId; action[0].depositActionAmount = tokensToRedeem; @@ -221,10 +265,15 @@ abstract contract NotionalResolver is Events, Helpers { notional.batchBalanceAndTradeAction(address(this), action); - _eventName = "LogRedeemNTokenAndDeleverage(address,uint16,uint,uint8,uint)"; - _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 @@ -237,46 +286,70 @@ abstract contract NotionalResolver is Events, Helpers { */ function depositAndMintNToken( uint16 currencyId, - uint depositAmount, + uint256 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 = TokenInterface(tokenAddress).balanceOf(address(this)); + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + depositAmount = getDepositAmountAndSetApproval( + getId, + currencyId, + useUnderlying, + depositAmount + ); - approve(TokenInterface(tokenAddress), address(notional), depositAmount); BalanceAction[] memory action = new BalanceAction[](1); - action[0].actionType = useUnderlying ? DepositActionType.DepositUnderlyingAndMintNToken : DepositActionType.DepositAssetAndMintNToken; + 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 - int256 nTokenBefore; - if (setId != 0) { - nTokenBefore = getNTokenBalance(currencyId); - } + int256 nTokenBefore = getNTokenBalance(currencyId); + + uint256 msgValue = 0; + if (currencyId == ETH_CURRENCY_ID && useUnderlying) + msgValue = depositAmount; - uint msgValue = currencyId == ETH_CURRENCY_ID ? depositAmount : 0; notional.batchBalanceAction{value: msgValue}(address(this), action); + int256 nTokenBalanceChange = getNTokenBalance(currencyId).sub( + nTokenBefore + ); + if (setId != 0) { // Set the amount of nTokens minted - setUint(setId, uint(sub(getNTokenBalance(currencyId), nTokenBefore))); + setUint(setId, uint256(nTokenBalanceChange)); } - // todo: events + _eventName = "LogDepositAndMintNToken(address,uint16,bool,uint256,int256)"; + _eventParam = abi.encode( + address(this), + currencyId, + useUnderlying, + depositAmount, + nTokenBalanceChange + ); } function mintNTokenFromCash( uint16 currencyId, - uint cashBalanceToMint, - uint getId, - uint setId - ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint256 cashBalanceToMint, + uint256 getId, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { cashBalanceToMint = getUint(getId, cashBalanceToMint); - if (cashBalanceToMint == uint(-1)) cashBalanceToMint = uint(getCashBalance(currencyId)); + if (cashBalanceToMint == uint256(-1)) + cashBalanceToMint = uint256(getCashBalance(currencyId)); BalanceAction[] memory action = new BalanceAction[](1); action[0].actionType = DepositActionType.ConvertCashToNToken; @@ -284,73 +357,97 @@ abstract contract NotionalResolver is Events, Helpers { action[0].depositActionAmount = cashBalanceToMint; // NOTE: withdraw amount, withdraw cash and redeem to underlying are all 0 and false - int256 nTokenBefore; - if (setId != 0) { - nTokenBefore = getNTokenBalance(currencyId); - } + int256 nTokenBefore = getNTokenBalance(currencyId); notional.batchBalanceAction(address(this), action); + int256 nTokenBalanceChange = getNTokenBalance(currencyId).sub( + nTokenBefore + ); + if (setId != 0) { // Set the amount of nTokens minted - setUint(setId, uint(sub(getNTokenBalance(currencyId), nTokenBefore))); + setUint(setId, uint256(nTokenBalanceChange)); } - // todo: events + _eventName = "LogMintNTokenFromCash(address,uint16,uint256,int256)"; + _eventParam = abi.encode( + address(this), + currencyId, + cashBalanceToMint, + nTokenBalanceChange + ); } function depositAndLend( uint16 currencyId, - uint depositAmount, + uint256 depositAmount, bool useUnderlying, uint8 marketIndex, uint88 fCashAmount, uint32 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 = TokenInterface(tokenAddress).balanceOf(address(this)); + uint256 getId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + depositAmount = getDepositAmountAndSetApproval( + getId, + currencyId, + useUnderlying, + depositAmount + ); - approve(TokenInterface(tokenAddress), address(notional), depositAmount); - BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](1); - action[0].actionType = useUnderlying ? DepositActionType.DepositUnderlying : DepositActionType.DepositAsset; + 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; - // 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.batchBalanceAndTradeAction{value: msgValue}(address(this), action); + uint256 msgValue = 0; + if (currencyId == ETH_CURRENCY_ID && useUnderlying) + msgValue = depositAmount; - // todo: events + 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 + ); } - function depositCollateralBorrowAndWithdraw( + function getDepositCollateralBorrowAndWithdrawActions( uint16 depositCurrencyId, bool useUnderlying, - uint depositAmount, + uint256 depositAmount, uint16 borrowCurrencyId, uint8 marketIndex, uint88 fCashAmount, uint32 maxBorrowRate, - bool redeemToUnderlying, - uint getId, - uint setId - ) external payable returns (string memory _eventName, bytes memory _eventParam) { - require(depositCurrencyId != borrowCurrencyId); - address tokenAddress = useUnderlying ? getUnderlyingToken(depositCurrencyId) : getAssetToken(depositCurrencyId); - depositAmount = getUint(getId, depositAmount); - if (depositAmount == uint(-1)) depositAmount = TokenInterface(tokenAddress).balanceOf(address(this)); - - approve(TokenInterface(tokenAddress), address(notional), depositAmount); - BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](2); + bool redeemToUnderlying + ) internal returns (BalanceActionWithTrades[] memory action) { + BalanceActionWithTrades[] + memory actions = new BalanceActionWithTrades[](2); uint256 depositIndex; uint256 borrowIndex; @@ -362,36 +459,85 @@ abstract contract NotionalResolver is Events, Helpers { 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; + actions[depositIndex].actionType = useUnderlying + ? DepositActionType.DepositUnderlying + : DepositActionType.DepositAsset; + actions[depositIndex].currencyId = depositCurrencyId; + actions[depositIndex].depositActionAmount = depositAmount; - action[borrowIndex].actionType = DepositActionType.None; - action[borrowIndex].currencyId = borrowCurrencyId; + actions[borrowIndex].actionType = DepositActionType.None; + actions[borrowIndex].currencyId = borrowCurrencyId; // Withdraw borrowed amount to wallet - action[borrowIndex].withdrawEntireCashBalance = true; - // TODO: will redeem underlying work with ETH? - action[borrowIndex].redeemToUnderlying = useUnderlying; + actions[borrowIndex].withdrawEntireCashBalance = true; + actions[borrowIndex].redeemToUnderlying = redeemToUnderlying; bytes32[] memory trades = new bytes32[](1); - trades[borrowIndex] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); - action[borrowIndex].trades = trades; + trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); + actions[borrowIndex].trades = trades; - address borrowToken; - uint balanceBefore; - if (setId != 0) { - address borrowToken = useUnderlying ? getUnderlyingToken(borrowCurrencyId) : getAssetToken(borrowCurrencyId); - balanceBefore = TokenInterface(borrowToken).balanceOf(address(this)); - } + return actions; + } - notional.batchBalanceAndTradeAction{value: msgValue}(address(this), action); + function depositCollateralBorrowAndWithdraw( + uint16 depositCurrencyId, + bool useUnderlying, + 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) + { + require(depositCurrencyId != borrowCurrencyId); - if (setId != 0) { - setUint(setId, sub(TokenInterface(borrowToken).balanceOf(address(this)), balanceBefore)); - } + depositAmount = getDepositAmountAndSetApproval( + getId, + depositCurrencyId, + useUnderlying, + depositAmount + ); - // todo: events + BalanceActionWithTrades[] + memory actions = getDepositCollateralBorrowAndWithdrawActions( + depositCurrencyId, + useUnderlying, + depositAmount, + borrowCurrencyId, + marketIndex, + fCashAmount, + maxBorrowRate, + redeemToUnderlying + ); + + uint256 msgValue = 0; + if (depositCurrencyId == ETH_CURRENCY_ID && useUnderlying) + msgValue = 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 + ); } function withdrawLend( @@ -399,44 +545,96 @@ abstract contract NotionalResolver is Events, Helpers { uint8 marketIndex, uint88 fCashAmount, uint32 maxBorrowRate, - uint getId, - uint setId - ) external payable returns (string memory _eventName, bytes memory _eventParam) { + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { bool useUnderlying = currencyId != ETH_CURRENCY_ID; - BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](1); + BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[]( + 1 + ); action[0].actionType = DepositActionType.None; action[0].currencyId = currencyId; // Withdraw borrowed amount to wallet action[0].withdrawEntireCashBalance = true; - // TODO: will redeem underlying work with ETH? action[0].redeemToUnderlying = useUnderlying; bytes32[] memory trades = new bytes32[](1); trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); action[0].trades = trades; - address tokenAddress; - uint balanceBefore; - if (setId != 0) { - address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); - balanceBefore = TokenInterface(tokenAddress).balanceOf(address(this)); - } - notional.batchBalanceAndTradeAction(address(this), action); + executeTradeActionWithBalanceChange( + action, + 0, + currencyId, + useUnderlying, + setId + ); - if (setId != 0) { - setUint(setId, sub(TokenInterface(tokenAddress).balanceOf(address(this)), balanceBefore)); - } + _eventName = "LogWithdrawLend(address,uint16,uint8,uint88,uint32)"; + _eventParam = abi.encode( + address(this), + currencyId, + marketIndex, + fCashAmount, + maxBorrowRate + ); } function repayBorrow( uint16 currencyId, uint8 marketIndex, - uint fCashAmount, - uint minLendRate, - uint getId, - uint setId - ) external payable returns (string memory _eventName, bytes memory _eventParam) { - // might want to use getfCashAmountGivenCashAmount + int88 netCashToAccount, + uint32 minLendRate, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + int256 fCashAmount = notional.getfCashAmountGivenCashAmount( + currencyId, + int88(int256(netCashToAccount).neg()), + marketIndex, + block.timestamp + ); + + 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] = encodeLendTrade( + marketIndex, + uint88(fCashAmount), + minLendRate + ); + action[0].trades = trades; + + executeTradeActionWithBalanceChange( + action, + 0, + currencyId, + useUnderlying, + setId + ); + + _eventName = "LogRepayBorrow(address,uint16,uint8,uint88,uint32)"; + _eventParam = abi.encode( + address(this), + currencyId, + marketIndex, + netCashToAccount, + minLendRate + ); } /** @@ -445,11 +643,18 @@ abstract contract NotionalResolver is Events, Helpers { * 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) { + function batchActionRaw(BalanceActionWithTrades[] memory actions) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { notional.batchBalanceAndTradeAction(address(this), actions); - // todo: events + _eventName = "LogBatchActionRaw(address)"; + _eventParam = abi.encode(address(this)); } -} \ No newline at end of file +} + +contract ConnectV2Notional is NotionalResolver { + string public name = "Notional-v1.1"; +} From 2bf33b524ffe4183f6e0a581f3ff6b6109ec24ae Mon Sep 17 00:00:00 2001 From: Jeff Wu <jeffywu@pm.me> Date: Fri, 17 Dec 2021 06:13:13 -0800 Subject: [PATCH 07/20] adding some additional documentation --- .../mainnet/connectors/notional/helpers.sol | 62 +++++++++++-------- .../mainnet/connectors/notional/interface.sol | 6 ++ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index 6f76fad3..2eba6522 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -14,38 +14,44 @@ contract Helpers is Basic { uint8 internal constant BORROW_TRADE = 1; int256 internal constant INTERNAL_TOKEN_PRECISION = 1e8; uint256 internal constant ETH_CURRENCY_ID = 1; + uint256 internal constant MAX_DEPOSIT = uint256(-1); + /// @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 returns (address) { - (, Token memory underlyingToken) = notional.getCurrency(currencyId); + ( + /* Token memory assetToken */, + Token memory underlyingToken + ) = notional.getCurrency(currencyId); return underlyingToken.tokenAddress; } + /// @notice Returns the address of the asset token for a given currency id function getAssetToken(uint16 currencyId) internal returns (address) { - (Token memory assetToken, ) = notional.getCurrency(currencyId); + ( + Token memory assetToken, + /* Token memory underlyingToken */ + ) = notional.getCurrency(currencyId); return assetToken.tokenAddress; } - function getCashBalance(uint16 currencyId) - internal - returns (int256 cashBalance) - { - (cashBalance, , ) = notional.getAccountBalance( - currencyId, - address(this) - ); + function getCashBalance(uint16 currencyId) internal returns (int256 cashBalance) { + ( + cashBalance, + /* int256 nTokenBalance */, + /* int256 lastClaimTime */ + ) = notional.getAccountBalance(currencyId, address(this)); } - function getNTokenBalance(uint16 currencyId) - internal - returns (int256 nTokenBalance) - { - (, nTokenBalance, ) = notional.getAccountBalance( - currencyId, - address(this) - ); + function getNTokenBalance(uint16 currencyId) internal returns (int256 nTokenBalance) { + ( + /* int256 cashBalance */, + nTokenBalance, + /* int256 lastClaimTime */ + ) = notional.getAccountBalance(currencyId, address(this)); } function convertToInternal(uint16 currencyId, int256 amount) @@ -56,7 +62,7 @@ contract Helpers is Basic { // 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. - (Token memory assetToken, ) = notional.getCurrency(currencyId); + (Token memory assetToken, /* underlyingToken */) = notional.getCurrency(currencyId); if (assetToken.decimals == INTERNAL_TOKEN_PRECISION) return amount; return amount.mul(INTERNAL_TOKEN_PRECISION).div(assetToken.decimals); } @@ -85,6 +91,8 @@ contract Helpers is Basic { (bytes32(uint256(maxBorrowRate)) << 120); } + /// @dev Uses getId to set approval for the given token up to the specified deposit + /// amount only function getDepositAmountAndSetApproval( uint256 getId, uint16 currencyId, @@ -92,16 +100,18 @@ contract Helpers is Basic { uint256 depositAmount ) internal returns (uint256) { depositAmount = getUint(getId, depositAmount); - if (currencyId == ETH_CURRENCY_ID && useUnderlying) - return - depositAmount == uint256(-1) - ? address(this).balance - : 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; + } address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); - if (depositAmount == uint256(-1)) { + + if (depositAmount == MAX_DEPOSIT) { depositAmount = TokenInterface(tokenAddress).balanceOf( address(this) ); @@ -132,6 +142,7 @@ contract Helpers is Basic { : getAssetToken(currencyId); } + /// @dev Executes a trade action and sets the balance change to setId function executeTradeActionWithBalanceChange( BalanceActionWithTrades[] memory action, uint256 msgValue, @@ -157,6 +168,7 @@ contract Helpers is Basic { } } + /// @dev Executes a balance action and sets the balance change to setId function executeActionWithBalanceChange( BalanceAction[] memory action, uint256 msgValue, diff --git a/contracts/mainnet/connectors/notional/interface.sol b/contracts/mainnet/connectors/notional/interface.sol index e6430d1a..a86c894f 100644 --- a/contracts/mainnet/connectors/notional/interface.sol +++ b/contracts/mainnet/connectors/notional/interface.sol @@ -66,10 +66,16 @@ struct BalanceAction { } 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; } From 3373ac90ff6a38e79816dc3fa651e95fe23c5b09 Mon Sep 17 00:00:00 2001 From: Jeff Wu <jeffywu@pm.me> Date: Fri, 17 Dec 2021 07:12:18 -0800 Subject: [PATCH 08/20] updating function mutability --- .../mainnet/connectors/notional/helpers.sol | 83 +++++++++++++++++-- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index 2eba6522..84f9b91c 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -2,7 +2,7 @@ pragma solidity ^0.7.6; pragma abicoder v2; import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; -import {Token, NotionalInterface, BalanceAction, BalanceActionWithTrades} from "./interface.sol"; +import {Token, NotionalInterface, BalanceAction, BalanceActionWithTrades, DepositActionType} from "./interface.sol"; import {SafeInt256} from "./SafeInt256.sol"; import {Basic} from "../../common/basic.sol"; import {TokenInterface} from "../../common/interfaces.sol"; @@ -21,7 +21,7 @@ contract Helpers is Basic { NotionalInterface(0x1344A36A1B56144C3Bc62E7757377D288fDE0369); /// @notice Returns the address of the underlying token for a given currency id, - function getUnderlyingToken(uint16 currencyId) internal returns (address) { + function getUnderlyingToken(uint16 currencyId) internal view returns (address) { ( /* Token memory assetToken */, Token memory underlyingToken @@ -30,7 +30,7 @@ contract Helpers is Basic { } /// @notice Returns the address of the asset token for a given currency id - function getAssetToken(uint16 currencyId) internal returns (address) { + function getAssetToken(uint16 currencyId) internal view returns (address) { ( Token memory assetToken, /* Token memory underlyingToken */ @@ -38,7 +38,7 @@ contract Helpers is Basic { return assetToken.tokenAddress; } - function getCashBalance(uint16 currencyId) internal returns (int256 cashBalance) { + function getCashBalance(uint16 currencyId) internal view returns (int256 cashBalance) { ( cashBalance, /* int256 nTokenBalance */, @@ -46,7 +46,7 @@ contract Helpers is Basic { ) = notional.getAccountBalance(currencyId, address(this)); } - function getNTokenBalance(uint16 currencyId) internal returns (int256 nTokenBalance) { + function getNTokenBalance(uint16 currencyId) internal view returns (int256 nTokenBalance) { ( /* int256 cashBalance */, nTokenBalance, @@ -54,8 +54,27 @@ contract Helpers is Basic { ) = notional.getAccountBalance(currencyId, address(this)); } - function convertToInternal(uint16 currencyId, int256 amount) + 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 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 returns (int256) { // If token decimals is greater than INTERNAL_TOKEN_PRECISION then this will truncate @@ -71,7 +90,7 @@ contract Helpers is Basic { uint8 marketIndex, uint88 fCashAmount, uint32 minLendRate - ) internal returns (bytes32) { + ) internal pure returns (bytes32) { return (bytes32(uint256(LEND_TRADE)) << 248) | (bytes32(uint256(marketIndex)) << 240) | @@ -83,7 +102,7 @@ contract Helpers is Basic { uint8 marketIndex, uint88 fCashAmount, uint32 maxBorrowRate - ) internal returns (bytes32) { + ) internal pure returns (bytes32) { return (bytes32(uint256(BORROW_TRADE)) << 248) | (bytes32(uint256(marketIndex)) << 240) | @@ -120,7 +139,7 @@ contract Helpers is Basic { return depositAmount; } - function getBalance(address addr) internal returns (uint256) { + function getBalance(address addr) internal view returns (uint256) { if (addr == ethAddr) { return address(this).balance; } @@ -130,6 +149,7 @@ contract Helpers is Basic { function getAddress(uint16 currencyId, bool useUnderlying) internal + view returns (address) { if (currencyId == ETH_CURRENCY_ID && useUnderlying) { @@ -190,4 +210,49 @@ contract Helpers is Basic { setUint(setId, balanceAfter.sub(balanceBefore)); } } + + function getDepositCollateralBorrowAndWithdrawActions( + uint16 depositCurrencyId, + bool useUnderlying, + uint256 depositAmount, + uint16 borrowCurrencyId, + uint8 marketIndex, + uint88 fCashAmount, + uint32 maxBorrowRate, + bool redeemToUnderlying + ) internal returns (BalanceActionWithTrades[] memory action) { + // TODO: allow minting nTokens here.... + BalanceActionWithTrades[] + memory actions = new BalanceActionWithTrades[](2); + + 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 = useUnderlying + ? DepositActionType.DepositUnderlying + : DepositActionType.DepositAsset; + 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; + + bytes32[] memory trades = new bytes32[](1); + trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); + actions[borrowIndex].trades = trades; + + return actions; + } + } From cfd1eec589733adf122c2b1b5bd654d63c6688ea Mon Sep 17 00:00:00 2001 From: Jeff Wu <jeffywu@pm.me> Date: Fri, 17 Dec 2021 07:13:17 -0800 Subject: [PATCH 09/20] updating comments --- .../connectors/notional/SafeInt256.sol | 5 + .../mainnet/connectors/notional/main.sol | 166 ++++++++++-------- 2 files changed, 99 insertions(+), 72 deletions(-) diff --git a/contracts/mainnet/connectors/notional/SafeInt256.sol b/contracts/mainnet/connectors/notional/SafeInt256.sol index eb0ce7b7..47013c05 100644 --- a/contracts/mainnet/connectors/notional/SafeInt256.sol +++ b/contracts/mainnet/connectors/notional/SafeInt256.sol @@ -23,4 +23,9 @@ library SafeInt256 { function neg(int256 x) internal pure returns (int256 y) { return mul(-1, x); } + + function toInt(uint256 x) internal pure returns (int256 y) { + require(x <= uint256(type(int256).max)); + return int256(x); + } } diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index 65cb2923..1a916e47 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -15,7 +15,9 @@ abstract contract NotionalResolver is Events, Helpers { using SafeInt256 for int256; /** - * @notice Deposit collateral into Notional + * @notice Deposit collateral into Notional, this should only be used for reducing risk of + * liquidation. 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) @@ -92,14 +94,15 @@ abstract contract NotionalResolver is Events, Helpers { withdrawAmount = getUint(getId, withdrawAmount); uint88 amountInternalPrecision = withdrawAmount == uint256(-1) ? uint88(getCashBalance(currencyId)) - : uint88(convertToInternal(currencyId, int256(withdrawAmount))); + : uint88(convertToInternal(currencyId, SafeInt256.toInt(withdrawAmount))); uint256 amountWithdrawn = notional.withdraw( currencyId, amountInternalPrecision, redeemToUnderlying ); - // Sets the amount of tokens withdrawn to address(this) + // 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)"; @@ -117,7 +120,6 @@ abstract contract NotionalResolver is Events, Helpers { */ function claimNOTE(uint256 setId) external - payable returns (string memory _eventName, bytes memory _eventParam) { uint256 notesClaimed = notional.nTokenClaimIncentives(); @@ -145,9 +147,8 @@ abstract contract NotionalResolver is Events, Helpers { uint256 getId, uint256 setId ) external returns (string memory _eventName, bytes memory _eventParam) { - tokensToRedeem = uint96(getUint(getId, tokensToRedeem)); - if (tokensToRedeem == uint96(-1)) - tokensToRedeem = uint96(getNTokenBalance(currencyId)); + tokensToRedeem = getNTokenRedeemAmount(currencyId, tokensToRedeem, getId); + int256 _assetCashChange = notional.nTokenRedeem( address(this), currencyId, @@ -193,9 +194,7 @@ abstract contract NotionalResolver is Events, Helpers { uint256 getId, uint256 setId ) external returns (string memory _eventName, bytes memory _eventParam) { - tokensToRedeem = uint96(getUint(getId, uint256(tokensToRedeem))); - if (tokensToRedeem == uint96(-1)) - tokensToRedeem = uint96(getNTokenBalance(currencyId)); + tokensToRedeem = getNTokenRedeemAmount(currencyId, tokensToRedeem, getId); BalanceAction[] memory action = new BalanceAction[](1); action[0].actionType = DepositActionType.RedeemNToken; @@ -203,6 +202,7 @@ abstract contract NotionalResolver is Events, Helpers { 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; @@ -234,8 +234,9 @@ abstract contract NotionalResolver is Events, Helpers { * @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 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 */ @@ -247,9 +248,7 @@ abstract contract NotionalResolver is Events, Helpers { uint32 minLendRate, uint256 getId ) external returns (string memory _eventName, bytes memory _eventParam) { - tokensToRedeem = uint96(getUint(getId, tokensToRedeem)); - if (tokensToRedeem == uint96(-1)) - tokensToRedeem = uint96(getNTokenBalance(currencyId)); + tokensToRedeem = getNTokenRedeemAmount(currencyId, tokensToRedeem, getId); BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[]( 1 @@ -282,7 +281,7 @@ abstract contract NotionalResolver is Events, Helpers { * @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) + * @param setId id to set the value of nToken balance change */ function depositAndMintNToken( uint16 currencyId, @@ -311,10 +310,7 @@ abstract contract NotionalResolver is Events, Helpers { // withdraw amount, withdraw cash and redeem to underlying are all 0 and false int256 nTokenBefore = getNTokenBalance(currencyId); - - uint256 msgValue = 0; - if (currencyId == ETH_CURRENCY_ID && useUnderlying) - msgValue = depositAmount; + uint256 msgValue = getMsgValue(currencyId, useUnderlying, depositAmount); notional.batchBalanceAction{value: msgValue}(address(this), action); @@ -337,6 +333,14 @@ abstract contract NotionalResolver is Events, Helpers { ); } + /** + * @notice Uses existing Notional cash balance (deposits in Notional held as cTokens) and uses them to mint + * nTokens. + * @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, @@ -344,7 +348,6 @@ abstract contract NotionalResolver is Events, Helpers { uint256 setId ) external - payable returns (string memory _eventName, bytes memory _eventParam) { cashBalanceToMint = getUint(getId, cashBalanceToMint); @@ -379,6 +382,24 @@ abstract contract NotionalResolver is Events, Helpers { ); } + /** + * @notice Deposits some amount of tokens and lends them in the specified market + * @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, @@ -415,10 +436,7 @@ abstract contract NotionalResolver is Events, Helpers { trades[0] = encodeLendTrade(marketIndex, fCashAmount, minLendRate); action[0].trades = trades; - uint256 msgValue = 0; - if (currencyId == ETH_CURRENCY_ID && useUnderlying) - msgValue = depositAmount; - + uint256 msgValue = getMsgValue(currencyId, useUnderlying, depositAmount); notional.batchBalanceAndTradeAction{value: msgValue}( address(this), action @@ -436,48 +454,28 @@ abstract contract NotionalResolver is Events, Helpers { ); } - function getDepositCollateralBorrowAndWithdrawActions( - uint16 depositCurrencyId, - bool useUnderlying, - uint256 depositAmount, - uint16 borrowCurrencyId, - uint8 marketIndex, - uint88 fCashAmount, - uint32 maxBorrowRate, - bool redeemToUnderlying - ) internal returns (BalanceActionWithTrades[] memory action) { - BalanceActionWithTrades[] - memory actions = new BalanceActionWithTrades[](2); - - uint256 depositIndex; - uint256 borrowIndex; - if (depositCurrencyId < borrowCurrencyId) { - depositIndex = 0; - borrowIndex = 1; - } else { - depositIndex = 1; - borrowIndex = 0; - } - - actions[depositIndex].actionType = useUnderlying - ? DepositActionType.DepositUnderlying - : DepositActionType.DepositAsset; - 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; - - bytes32[] memory trades = new bytes32[](1); - trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); - actions[borrowIndex].trades = trades; - - return actions; - } - + /** + * @notice Deposits some amount of tokens as collateral and borrows + * @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 useUnderlying if true, will accept a deposit collateralin the underlying currency (i.e DAI), if false + * will use the asset currency (i.e. cDAI) + * @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, bool useUnderlying, @@ -515,13 +513,9 @@ abstract contract NotionalResolver is Events, Helpers { redeemToUnderlying ); - uint256 msgValue = 0; - if (depositCurrencyId == ETH_CURRENCY_ID && useUnderlying) - msgValue = depositAmount; - executeTradeActionWithBalanceChange( actions, - msgValue, + getMsgValue(depositCurrencyId, useUnderlying, depositAmount), borrowCurrencyId, redeemToUnderlying, setId @@ -540,6 +534,19 @@ abstract contract NotionalResolver is Events, Helpers { ); } + /** + * @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, @@ -583,6 +590,19 @@ abstract contract NotionalResolver is Events, Helpers { ); } + /** + * @notice Allows an account to repay a borrow before maturity at current market rates. Equivalent to lending from the Notional + * perspective. + * @dev Setting the fCash amount and minLendRate are best calculated using the Notional SDK off chain. Similar to lending, + * setting these 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 netCashToAccount amount of fCash at the marketIndex that should be sold + * @param minLendRate the maximum interest rate that the account is willing to repay 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 repayBorrow( uint16 currencyId, uint8 marketIndex, @@ -594,8 +614,10 @@ abstract contract NotionalResolver is Events, Helpers { payable returns (string memory _eventName, bytes memory _eventParam) { + // TODO: test this a bit more, will this cause issues? int256 fCashAmount = notional.getfCashAmountGivenCashAmount( currencyId, + // NOTE: no chance of overflow here int88(int256(netCashToAccount).neg()), marketIndex, block.timestamp From 016c8d4370532fbf6927c2b741e4e180172526e0 Mon Sep 17 00:00:00 2001 From: Jeff Wu <jeffywu@pm.me> Date: Fri, 17 Dec 2021 07:57:05 -0800 Subject: [PATCH 10/20] allowing more deposit types in borrow spell --- .../mainnet/connectors/notional/helpers.sol | 36 +++++++++++++------ .../mainnet/connectors/notional/main.sol | 29 +++++++++++---- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index 84f9b91c..0aceb6e0 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -213,7 +213,7 @@ contract Helpers is Basic { function getDepositCollateralBorrowAndWithdrawActions( uint16 depositCurrencyId, - bool useUnderlying, + DepositActionType depositAction, uint256 depositAmount, uint16 borrowCurrencyId, uint8 marketIndex, @@ -221,9 +221,29 @@ contract Helpers is Basic { uint32 maxBorrowRate, bool redeemToUnderlying ) internal returns (BalanceActionWithTrades[] memory action) { - // TODO: allow minting nTokens here.... - BalanceActionWithTrades[] - memory actions = new BalanceActionWithTrades[](2); + 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); uint256 depositIndex; uint256 borrowIndex; @@ -236,9 +256,7 @@ contract Helpers is Basic { borrowIndex = 0; } - actions[depositIndex].actionType = useUnderlying - ? DepositActionType.DepositUnderlying - : DepositActionType.DepositAsset; + actions[depositIndex].actionType = depositAction; actions[depositIndex].currencyId = depositCurrencyId; actions[depositIndex].depositActionAmount = depositAmount; @@ -247,12 +265,8 @@ contract Helpers is Basic { // Withdraw borrowed amount to wallet actions[borrowIndex].withdrawEntireCashBalance = true; actions[borrowIndex].redeemToUnderlying = redeemToUnderlying; - - bytes32[] memory trades = new bytes32[](1); - trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); actions[borrowIndex].trades = trades; return actions; } - } diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index 1a916e47..d85bdfc6 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -455,14 +455,25 @@ abstract contract NotionalResolver is Events, Helpers { } /** - * @notice Deposits some amount of tokens as collateral and borrows + * @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 useUnderlying if true, will accept a deposit collateralin the underlying currency (i.e DAI), if false - * will use the asset currency (i.e. cDAI) + * @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 @@ -478,7 +489,7 @@ abstract contract NotionalResolver is Events, Helpers { */ function depositCollateralBorrowAndWithdraw( uint16 depositCurrencyId, - bool useUnderlying, + DepositActionType depositAction, uint256 depositAmount, uint16 borrowCurrencyId, uint8 marketIndex, @@ -492,7 +503,10 @@ abstract contract NotionalResolver is Events, Helpers { payable returns (string memory _eventName, bytes memory _eventParam) { - require(depositCurrencyId != borrowCurrencyId); + bool useUnderlying = ( + depositAction == DepositActionType.DepositUnderlying || + depositAction == DepositActionType.DepositUnderlyingAndMintNToken + ); depositAmount = getDepositAmountAndSetApproval( getId, @@ -504,7 +518,7 @@ abstract contract NotionalResolver is Events, Helpers { BalanceActionWithTrades[] memory actions = getDepositCollateralBorrowAndWithdrawActions( depositCurrencyId, - useUnderlying, + depositAction, depositAmount, borrowCurrencyId, marketIndex, @@ -513,9 +527,10 @@ abstract contract NotionalResolver is Events, Helpers { redeemToUnderlying ); + uint256 msgValue = getMsgValue(depositCurrencyId, useUnderlying, depositAmount); executeTradeActionWithBalanceChange( actions, - getMsgValue(depositCurrencyId, useUnderlying, depositAmount), + msgValue, borrowCurrencyId, redeemToUnderlying, setId From 0a2b38bee8bc94995e16a38b0ce3150c389b42ce Mon Sep 17 00:00:00 2001 From: Jeff Wu <jeffywu@pm.me> Date: Fri, 17 Dec 2021 08:00:55 -0800 Subject: [PATCH 11/20] removing repay borrow, depositAndLend will work --- .../mainnet/connectors/notional/interface.sol | 7 -- .../mainnet/connectors/notional/main.sol | 72 +------------------ 2 files changed, 2 insertions(+), 77 deletions(-) diff --git a/contracts/mainnet/connectors/notional/interface.sol b/contracts/mainnet/connectors/notional/interface.sol index a86c894f..779bbce7 100644 --- a/contracts/mainnet/connectors/notional/interface.sol +++ b/contracts/mainnet/connectors/notional/interface.sol @@ -94,13 +94,6 @@ interface NotionalInterface { uint256 lastClaimTime ); - function getfCashAmountGivenCashAmount( - uint16 currencyId, - int88 netCashToAccount, - uint256 marketIndex, - uint256 blockTime - ) external view returns (int256); - function depositUnderlyingToken( address account, uint16 currencyId, diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index d85bdfc6..849ee26d 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -383,7 +383,8 @@ abstract contract NotionalResolver is Events, Helpers { } /** - * @notice Deposits some amount of tokens and lends them in the specified market + * @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 @@ -605,75 +606,6 @@ abstract contract NotionalResolver is Events, Helpers { ); } - /** - * @notice Allows an account to repay a borrow before maturity at current market rates. Equivalent to lending from the Notional - * perspective. - * @dev Setting the fCash amount and minLendRate are best calculated using the Notional SDK off chain. Similar to lending, - * setting these 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 netCashToAccount amount of fCash at the marketIndex that should be sold - * @param minLendRate the maximum interest rate that the account is willing to repay 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 repayBorrow( - uint16 currencyId, - uint8 marketIndex, - int88 netCashToAccount, - uint32 minLendRate, - uint256 setId - ) - external - payable - returns (string memory _eventName, bytes memory _eventParam) - { - // TODO: test this a bit more, will this cause issues? - int256 fCashAmount = notional.getfCashAmountGivenCashAmount( - currencyId, - // NOTE: no chance of overflow here - int88(int256(netCashToAccount).neg()), - marketIndex, - block.timestamp - ); - - 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] = encodeLendTrade( - marketIndex, - uint88(fCashAmount), - minLendRate - ); - action[0].trades = trades; - - executeTradeActionWithBalanceChange( - action, - 0, - currencyId, - useUnderlying, - setId - ); - - _eventName = "LogRepayBorrow(address,uint16,uint8,uint88,uint32)"; - _eventParam = abi.encode( - address(this), - currencyId, - marketIndex, - netCashToAccount, - minLendRate - ); - } - /** * @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 From f39fa9032700f534e3064a3acdd1e7c90ad4496b Mon Sep 17 00:00:00 2001 From: weitianjie2000 <37812978+weitianjie2000@users.noreply.github.com> Date: Mon, 31 Jan 2022 13:04:27 -0800 Subject: [PATCH 12/20] Migrating Notional v2 connector tests (#2) * Migrating tests * Adding more tests * Adding more tests and helper functions * Adding more tests * Cleaning up --- scripts/constant/abis.js | 1 + test/notional/notional.contracts.js | 109 +++++++ test/notional/notional.helpers.js | 167 +++++++++++ test/notional/notional.test.js | 422 ++++++++++++++++++++++++++++ 4 files changed, 699 insertions(+) create mode 100644 test/notional/notional.contracts.js create mode 100644 test/notional/notional.helpers.js create mode 100644 test/notional/notional.test.js diff --git a/scripts/constant/abis.js b/scripts/constant/abis.js index cb62ccfb..bc1fd8f1 100644 --- a/scripts/constant/abis.js +++ b/scripts/constant/abis.js @@ -8,6 +8,7 @@ module.exports = { basic: require("./abi/connectors/basic.json"), auth: require("./abi/connectors/auth.json"), "INSTAPOOL-A": require("./abi/connectors/instapool.json"), + "BASIC-A": require("./abi/connectors/basic.json") }, basic: { erc20: require("./abi/basics/erc20.json"), diff --git a/test/notional/notional.contracts.js b/test/notional/notional.contracts.js new file mode 100644 index 00000000..f752ee06 --- /dev/null +++ b/test/notional/notional.contracts.js @@ -0,0 +1,109 @@ + +const NOTIONAL_CONTRACT_ADDRESS = '0x1344A36A1B56144C3Bc62E7757377D288fDE0369'; +const NOTIONAL_CONTRACT_ABI = [ + { + "inputs": [ + { + "internalType": "uint16", + "name": "currencyId", + "type": "uint16" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAccountBalance", + "outputs": [ + { + "internalType": "int256", + "name": "cashBalance", + "type": "int256" + }, + { + "internalType": "int256", + "name": "nTokenBalance", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "lastClaimTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getAccountPortfolio", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "currencyId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maturity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assetType", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "notional", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "storageSlot", + "type": "uint256" + }, + { + "internalType": "enum AssetStorageState", + "name": "storageState", + "type": "uint8" + } + ], + "internalType": "struct PortfolioAsset[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + } +]; + +const WETH_TOKEN_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; +const DAI_TOKEN_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; +const CDAI_TOKEN_ADDRESS = "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643"; +const CETH_TOKEN_ADDRESS = "0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5"; +const ERC20_TOKEN_ABI = [ + "function transfer(address _to, uint256 _value) public returns (bool success)", + "function balanceOf(address account) external view returns (uint256)", + "function approve(address spender, uint256 amount) external returns (bool)", +]; + +module.exports = { + NOTIONAL_CONTRACT_ADDRESS, + NOTIONAL_CONTRACT_ABI, + WETH_TOKEN_ADDRESS, + DAI_TOKEN_ADDRESS, + CDAI_TOKEN_ADDRESS, + CETH_TOKEN_ADDRESS, + ERC20_TOKEN_ABI +}; \ No newline at end of file diff --git a/test/notional/notional.helpers.js b/test/notional/notional.helpers.js new file mode 100644 index 00000000..a82e989e --- /dev/null +++ b/test/notional/notional.helpers.js @@ -0,0 +1,167 @@ +const encodeSpells = require("../../scripts/encodeSpells.js") + +const depositCollteral = async (dsa, authority, referrer, currencyId, amount, underlying) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "depositCollateral", + args: [currencyId, underlying, amount, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const depositAndMintNToken = async (dsa, authority, referrer, currencyId, amount, underlying) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "depositAndMintNToken", + args: [currencyId, amount, underlying, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +} + +const depositAndLend = async (dsa, authority, referrer, currencyId, underlying, amount, market, fcash, minRate) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "depositAndLend", + args: [currencyId, amount, underlying, market, fcash, minRate, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const withdrawCollateral = async (dsa, authority, referrer, currencyId, amount, underlying) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "withdrawCollateral", + args: [currencyId, underlying, amount, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const redeemNTokenRaw = async (dsa, authority, referrer, currencyId, sellTokenAssets, tokensToRedeem) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "redeemNTokenRaw", + args: [currencyId, sellTokenAssets, tokensToRedeem, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const redeemNTokenAndWithdraw = async (dsa, authority, referrer, currencyId, tokensToRedeem, amountToWithdraw, redeemToUnderlying) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "redeemNTokenAndWithdraw", + args: [currencyId, tokensToRedeem, amountToWithdraw, redeemToUnderlying, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const redeemNTokenAndDeleverage = async (dsa, authority, referrer, currencyId, tokensToRedeem, marketIndex, fCashAmount, minLendRate) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "redeemNTokenAndDeleverage", + args: [currencyId, tokensToRedeem, marketIndex, fCashAmount, minLendRate, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const depositCollateralBorrowAndWithdraw = async ( + dsa, + authority, + referrer, + depositCurrencyId, + depositUnderlying, + depositAmount, + borrowCurrencyId, + marketIndex, + fCashAmount, + maxBorrowRate, + redeedmUnderlying +) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "depositCollateralBorrowAndWithdraw", + args: [ + depositCurrencyId, + depositUnderlying, + depositAmount, + borrowCurrencyId, + marketIndex, + fCashAmount, + maxBorrowRate, + redeedmUnderlying, + 0, + 0 + ] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const withdrawLend = async (dsa, authority, referrer, currencyId, marketIndex, fCashAmount, maxBorrowRate) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "withdrawLend", + args: [currencyId, marketIndex, fCashAmount, maxBorrowRate, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +const depositERC20 = async (dsa, authority, referrer, token, amount) => { + const spells = [ + { + connector: "BASIC-A", + method: "deposit", + args: [token, amount, 0, 0] + } + ]; + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +}; + +module.exports = { + depositCollteral, + depositAndMintNToken, + depositAndLend, + withdrawCollateral, + withdrawLend, + redeemNTokenRaw, + redeemNTokenAndWithdraw, + redeemNTokenAndDeleverage, + depositCollateralBorrowAndWithdraw, + depositERC20 +}; diff --git a/test/notional/notional.test.js b/test/notional/notional.test.js new file mode 100644 index 00000000..f7a3a841 --- /dev/null +++ b/test/notional/notional.test.js @@ -0,0 +1,422 @@ +const { expect } = require("chai"); +const hre = require("hardhat"); +const { web3, deployments, waffle, ethers } = hre; +const { provider, deployContract } = waffle + +const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js") +const buildDSAv2 = require("../../scripts/buildDSAv2") +const encodeSpells = require("../../scripts/encodeSpells.js") +const getMasterSigner = require("../../scripts/getMasterSigner") + +const addresses = require("../../scripts/constant/addresses"); +const abis = require("../../scripts/constant/abis"); +const constants = require("../../scripts/constant/constant"); +const tokens = require("../../scripts/constant/tokens"); + +const contracts = require("./notional.contracts"); +const helpers = require("./notional.helpers"); + +const connectV2NotionalArtifacts = require("../../artifacts/contracts/mainnet/connectors/notional/main.sol/ConnectV2Notional.json"); +const { BigNumber } = require("ethers"); + +const DAI_WHALE = "0x6dfaf865a93d3b0b5cfd1b4db192d1505676645b"; +const CDAI_WHALE = "0x33b890d6574172e93e58528cd99123a88c0756e9"; +const ETH_WHALE = "0x7D24796f7dDB17d73e8B1d0A3bbD103FBA2cb2FE"; +const CETH_WHALE = "0x1a1cd9c606727a7400bb2da6e4d5c70db5b4cade"; +const MaxUint96 = BigNumber.from("0xffffffffffffffffffffffff"); +const DEPOSIT_ASSET = 1; +const DEPOSIT_UNDERLYING = 2; +const DEPOSIT_ASSET_MINT_NTOKEN = 3; +const DEPOSIT_UNDERLYING_MINT_NTOKEN = 4; +const ETH_ID = 1; +const DAI_ID = 2; +const MARKET_3M = 1; + +describe("Notional", function () { + const connectorName = "NOTIONAL-TEST-A" + + let dsaWallet0 + let masterSigner; + let instaConnectorsV2; + let connector; + let notional; + let daiToken; + let cdaiToken; + let cethToken; + let daiWhale; + let cdaiWhale; + let cethWhale; + + const wallets = provider.getWallets() + const [wallet0, wallet1, wallet2, wallet3] = wallets + beforeEach(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 13798624, + }, + }, + ], + }); + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [DAI_WHALE] + }) + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [CDAI_WHALE] + }) + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [ETH_WHALE] + }) + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [CETH_WHALE] + }) + + masterSigner = await getMasterSigner(wallet3) + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: connectV2NotionalArtifacts, + signer: masterSigner, + connectors: instaConnectorsV2 + }) + notional = new ethers.Contract( + contracts.NOTIONAL_CONTRACT_ADDRESS, + contracts.NOTIONAL_CONTRACT_ABI, + ethers.provider + ); + daiToken = new ethers.Contract( + contracts.DAI_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ); + daiWhale = await ethers.getSigner(DAI_WHALE); + cdaiToken = new ethers.Contract( + contracts.CDAI_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ); + cdaiWhale = await ethers.getSigner(CDAI_WHALE); + cethToken = new ethers.Contract( + contracts.CETH_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ); + cethWhale = await ethers.getSigner(CETH_WHALE); + weth = new ethers.Contract( + contracts.WETH_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ); + dsaWallet0 = await buildDSAv2(wallet0.address) + }); + + describe("Deposit Tests", function () { + it("test_deposit_ETH_underlying", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("1"); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, ETH_ID, depositAmount, true); + const bal = await notional.callStatic.getAccountBalance(ETH_ID, dsaWallet0.address); + // balance in internal asset precision + expect(bal[0], "expect at least 49 cETH").to.be.gte(ethers.utils.parseUnits("4900000000", 0)); + expect(bal[1], "expect 0 nETH").to.be.equal(ethers.utils.parseUnits("0", 0)); + }); + + it("test_deposit_ETH_asset", async function () { + const depositAmount = ethers.utils.parseUnits("1", 8); + await cethToken.connect(cethWhale).transfer(wallet0.address, depositAmount); + await cethToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cethToken.address, depositAmount); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, ETH_ID, depositAmount, false); + const bal = await notional.callStatic.getAccountBalance(ETH_ID, dsaWallet0.address); + // balance in internal asset precision + expect(bal[0], "expect at least 1 cETH").to.be.gte(ethers.utils.parseUnits("100000000", 0)); + expect(bal[1], "expect 0 nETH").to.be.equal(ethers.utils.parseUnits("0", 0)); + }); + + it("test_deposit_DAI_underlying", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 18); + await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); + await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, true); + const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); + // balance in internal asset precision + expect(bal[0], "expect at least 45000 cDAI").to.be.gte(ethers.utils.parseUnits("4500000000000", 0)); + expect(bal[1], "expect 0 nDAI").to.be.equal(ethers.utils.parseUnits("0", 0)); + }); + + it("test_deposit_DAI_asset", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); + const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); + // balance in internal asset precision + expect(bal[0], "expect at least 1000 cDAI").to.be.gte(ethers.utils.parseUnits("100000000000", 0)); + expect(bal[1], "expect 0 nDAI").to.be.equal(ethers.utils.parseUnits("0", 0)); + }); + + it("test_deposit_ETH_underlying_and_mint_ntoken", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("1"); + await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, ETH_ID, depositAmount, true); + const bal = await notional.callStatic.getAccountBalance(ETH_ID, dsaWallet0.address); + expect(bal[0], "expect 0 balance").to.be.equal(ethers.utils.parseUnits("0", 0)); + expect(bal[1], "expect at least 49 nETH").to.be.gte(ethers.utils.parseUnits("4900000000", 0)); + }); + }); + + describe("Lend Tests", function () { + it("test_deposit_ETH_underlying_and_lend", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("10"); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, true, depositAmount, MARKET_3M, 9e8, 0); + const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); + expect(portfolio.length, "expect 1 lending position").to.be.equal(1); + expect(portfolio[0][3], "expect 9 fETH").to.be.gte(ethers.utils.parseUnits("900000000", 0)); + }); + + it("test_deposit_ETH_asset_and_lend", async function () { + const depositAmount = ethers.utils.parseUnits("1", 8); + await cethToken.connect(cethWhale).transfer(wallet0.address, depositAmount); + await cethToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cethToken.address, depositAmount); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, false, depositAmount, MARKET_3M, 0.01e8, 0); + const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); + expect(portfolio.length, "expect 1 lending position").to.be.equal(1); + expect(portfolio[0][3], "expect 0.01 fETH").to.be.gte(ethers.utils.parseUnits("1000000", 0)); + }); + + it("test_deposit_DAI_underlying_and_lend", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 18); + await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); + await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, DAI_ID, true, depositAmount, MARKET_3M, 100e8, 0); + const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); + expect(portfolio.length, "expect 1 lending position").to.be.equal(1); + expect(portfolio[0][3], "expect 100 fDAI").to.be.gte(ethers.utils.parseUnits("10000000000", 0)); + }); + + it("test_deposit_DAI_asset_and_lend", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, DAI_ID, false, depositAmount, MARKET_3M, 10e8, 0); + const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); + expect(portfolio.length, "expect 1 lending position").to.be.equal(1); + expect(portfolio[0][3], "expect 10 fDAI").to.be.gte(ethers.utils.parseUnits("1000000000", 0)); + }); + + it("test_withdraw_lend_ETH", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("10"); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, true, depositAmount, MARKET_3M, 9e8, 0); + const before = await notional.getAccountPortfolio(dsaWallet0.address); + expect(before.length, "expect 1 lending position").to.be.equal(1); + expect(before[0][3], "expect 9 fETH").to.be.gte(ethers.utils.parseUnits("900000000", 0)); + await helpers.withdrawLend(dsaWallet0, wallet0, wallet1, ETH_ID, MARKET_3M, 9e8, 0); + const after = await notional.getAccountPortfolio(dsaWallet0.address); + expect(after.length, "expect lending position to be closed out").to.be.equal(0); + }); + }); + + describe("Borrow Tests", function () { + it("test_deposit_ETH_and_borrow_DAI_underlying", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("10"); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, ETH_ID, DEPOSIT_UNDERLYING, depositAmount, DAI_ID, MARKET_3M, 1000e8, 0, true + ); + expect( + await daiToken.balanceOf(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseEther("990")); + }); + + it("test_deposit_ETH_and_borrow_DAI_asset", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("10"); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, ETH_ID, DEPOSIT_UNDERLYING, depositAmount, DAI_ID, MARKET_3M, 1000e8, 0, false + ); + expect( + await cdaiToken.balanceOf(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseUnits("4500000000000", 0)); + }); + + it("test_deposit_DAI_underlying_and_borrow_ETH", async function () { + const depositAmount = ethers.utils.parseUnits("20000", 18); + await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); + await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + ); + expect( + await ethers.provider.getBalance(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseEther("0.99")); + }); + + it("test_deposit_DAI_asset_and_borrow_ETH", async function () { + const depositAmount = ethers.utils.parseUnits("1000000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_ASSET, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + ); + expect( + await ethers.provider.getBalance(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseEther("0.99")); + }); + + it("test_mint_nDAI_underlying_and_borrow_ETH", async function () { + const depositAmount = ethers.utils.parseUnits("20000", 18); + await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); + await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING_MINT_NTOKEN, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + ); + expect( + await ethers.provider.getBalance(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseEther("0.99")); + }); + + it("test_mint_nDAI_asset_and_borrow_ETH", async function () { + const depositAmount = ethers.utils.parseUnits("1000000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_ASSET_MINT_NTOKEN, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + ); + expect( + await ethers.provider.getBalance(dsaWallet0.address), + "expect DSA wallet to contain borrowed balance minus fees" + ).to.be.gte(ethers.utils.parseEther("0.99")); + }); + }); + + describe("Withdraw Tests", function () { + it("test_withdraw_ETH_underlying", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("1"); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, 1, depositAmount, true); + await helpers.withdrawCollateral(dsaWallet0, wallet0, wallet1, 1, ethers.constants.MaxUint256, true); + expect( + await ethers.provider.getBalance(dsaWallet0.address), + "expect DSA wallet to contain underlying funds" + ).to.be.gte(ethers.utils.parseEther("10")); + }); + + it("test_withdraw_ETH_asset", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + const depositAmount = ethers.utils.parseEther("1"); + await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, ETH_ID, depositAmount, true); + await helpers.withdrawCollateral(dsaWallet0, wallet0, wallet1, ETH_ID, ethers.constants.MaxUint256, false); + expect( + await cethToken.balanceOf(dsaWallet0.address), + "expect DSA wallet to contain cToken funds" + ).to.be.gte(ethers.utils.parseUnits("4900000000", 0)); + }); + + it("test_redeem_DAI_raw", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); + await helpers.redeemNTokenRaw(dsaWallet0, wallet0, wallet1, DAI_ID, true, MaxUint96) + const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); + expect(bal[0], "expect cDAI balance after redemption").to.be.gte(ethers.utils.parseUnits("99000000000", 0)); + expect(bal[1], "expect 0 nDAI").to.be.equal(ethers.utils.parseEther("0")); + }); + + it("test_redeem_DAI_and_withdraw_redeem", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); + await helpers.redeemNTokenAndWithdraw(dsaWallet0, wallet0, wallet1, DAI_ID, MaxUint96, ethers.constants.MaxUint256, true); + const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); + expect(bal[0], "expect 0 cDAI balance").to.be.equal(ethers.utils.parseEther("0")); + expect(bal[1], "expect 0 nDAI balance").to.be.equal(ethers.utils.parseEther("0")); + }); + + it("test_redeem_DAI_and_withdraw_no_redeem", async function () { + const depositAmount = ethers.utils.parseUnits("1000", 8); + await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); + await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); + expect(await cdaiToken.balanceOf(dsaWallet0.address)).to.be.equal(ethers.utils.parseEther("0")); + await helpers.redeemNTokenAndWithdraw(dsaWallet0, wallet0, wallet1, DAI_ID, MaxUint96, ethers.constants.MaxUint256, false); + const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); + expect(bal[0], "expect 0 cDAI balance").to.be.equal(ethers.utils.parseEther("0")); + expect(bal[1], "expect 0 nDAI balance").to.be.equal(ethers.utils.parseEther("0")); + expect( + await cdaiToken.balanceOf(dsaWallet0.address), + "expect DSA wallet to contain cToken funds" + ).to.be.gte(ethers.utils.parseUnits("99000000000", 0)); + }); + + it("test_redeem_DAI_and_deleverage", async function () { + const depositAmount = ethers.utils.parseUnits("20000", 18); + await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); + await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); + await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await helpers.depositCollateralBorrowAndWithdraw( + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + ); + const bal = await ethers.provider.getBalance(dsaWallet0.address); + await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, ETH_ID, bal, true); + const before = await notional.getAccountPortfolio(dsaWallet0.address); + expect(before.length, "expect 1 fDAI debt position").to.be.equal(1); + expect(before[0][3], "expect fDAI debt position to equal borrow amount").to.be.lte(ethers.utils.parseUnits("-100000000", 0)); + await helpers.redeemNTokenAndDeleverage(dsaWallet0, wallet0, wallet1, ETH_ID, MaxUint96, MARKET_3M, 0.98e8, 0); + const after = await notional.getAccountPortfolio(dsaWallet0.address); + expect(after.length, "expect 1 fDAI debt position after deleverage").to.be.equal(1); + expect(after[0][3], "expect fDAI debt balance to go down after deleverage").to.be.lte(ethers.utils.parseUnits("-2000000", 0)); + }); + }); +}); From 4dd1d14f0116b205cef49aaf772179ee44507171 Mon Sep 17 00:00:00 2001 From: weitianjie2000 <37812978+weitianjie2000@users.noreply.github.com> Date: Mon, 31 Jan 2022 19:23:43 -0800 Subject: [PATCH 13/20] Fixing static analysis errors (#3) --- .../mainnet/connectors/notional/events.sol | 9 +- .../mainnet/connectors/notional/main.sol | 88 ++++++++++++++----- scripts/constant/abis.js | 1 - test/notional/notional.helpers.js | 16 +--- test/notional/notional.test.js | 56 +++--------- 5 files changed, 85 insertions(+), 85 deletions(-) diff --git a/contracts/mainnet/connectors/notional/events.sol b/contracts/mainnet/connectors/notional/events.sol index 4c667956..a6c86119 100644 --- a/contracts/mainnet/connectors/notional/events.sol +++ b/contracts/mainnet/connectors/notional/events.sol @@ -68,6 +68,7 @@ contract Events { ); event LogDepositCollateralBorrowAndWithdraw( + address indexed account, bool useUnderlying, uint256 depositAmount, uint16 borrowCurrencyId, @@ -78,18 +79,12 @@ contract Events { ); event LogWithdrawLend( + address indexed account, uint16 currencyId, uint8 marketIndex, uint88 fCashAmount, uint32 maxBorrowRate ); - event LogRepayBorrow( - uint16 currencyId, - uint8 marketIndex, - int88 netCashToAccount, - uint32 minLendRate - ); - event LogBatchActionRaw(address indexed account); } diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index 849ee26d..6ebf6e91 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -9,14 +9,15 @@ import {TokenInterface} from "../../common/interfaces.sol"; /** * @title Notional - * @notice Fixed Rate Lending and Borrowing + * @dev Fixed Rate Lending and Borrowing */ abstract contract NotionalResolver is Events, Helpers { using SafeInt256 for int256; /** * @notice Deposit collateral into Notional, this should only be used for reducing risk of - * liquidation. Deposits into Notional are not earning fixed rates, they are earning the cToken + * 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 @@ -64,7 +65,7 @@ abstract contract NotionalResolver is Events, Helpers { setUint(setId, assetCashDeposited); - _eventName = "LogDepositCollateral(uint16,bool,uint256,uint256)"; + _eventName = "LogDepositCollateral(address,uint16,bool,uint256,uint256)"; _eventParam = abi.encode( address(this), currencyId, @@ -76,6 +77,7 @@ abstract contract NotionalResolver is Events, Helpers { /** * @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) @@ -90,11 +92,17 @@ abstract contract NotionalResolver is Events, Helpers { uint256 withdrawAmount, uint256 getId, uint256 setId - ) external returns (string memory _eventName, bytes memory _eventParam) { + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { withdrawAmount = getUint(getId, withdrawAmount); uint88 amountInternalPrecision = withdrawAmount == uint256(-1) ? uint88(getCashBalance(currencyId)) - : uint88(convertToInternal(currencyId, SafeInt256.toInt(withdrawAmount))); + : uint88( + convertToInternal(currencyId, SafeInt256.toInt(withdrawAmount)) + ); uint256 amountWithdrawn = notional.withdraw( currencyId, @@ -115,11 +123,13 @@ abstract contract NotionalResolver is Events, Helpers { } /** - * @dev Claims NOTE tokens and transfers to the address + * @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(); @@ -146,8 +156,16 @@ abstract contract NotionalResolver is Events, Helpers { uint96 tokensToRedeem, uint256 getId, uint256 setId - ) external returns (string memory _eventName, bytes memory _eventParam) { - tokensToRedeem = getNTokenRedeemAmount(currencyId, tokensToRedeem, getId); + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + tokensToRedeem = getNTokenRedeemAmount( + currencyId, + tokensToRedeem, + getId + ); int256 _assetCashChange = notional.nTokenRedeem( address(this), @@ -193,8 +211,16 @@ abstract contract NotionalResolver is Events, Helpers { bool redeemToUnderlying, uint256 getId, uint256 setId - ) external returns (string memory _eventName, bytes memory _eventParam) { - tokensToRedeem = getNTokenRedeemAmount(currencyId, tokensToRedeem, getId); + ) + 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; @@ -247,8 +273,16 @@ abstract contract NotionalResolver is Events, Helpers { uint88 fCashAmount, uint32 minLendRate, uint256 getId - ) external returns (string memory _eventName, bytes memory _eventParam) { - tokensToRedeem = getNTokenRedeemAmount(currencyId, tokensToRedeem, getId); + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + tokensToRedeem = getNTokenRedeemAmount( + currencyId, + tokensToRedeem, + getId + ); BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[]( 1 @@ -276,6 +310,7 @@ abstract contract NotionalResolver is Events, Helpers { /** * @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 @@ -310,7 +345,11 @@ abstract contract NotionalResolver is Events, Helpers { // withdraw amount, withdraw cash and redeem to underlying are all 0 and false int256 nTokenBefore = getNTokenBalance(currencyId); - uint256 msgValue = getMsgValue(currencyId, useUnderlying, depositAmount); + uint256 msgValue = getMsgValue( + currencyId, + useUnderlying, + depositAmount + ); notional.batchBalanceAction{value: msgValue}(address(this), action); @@ -336,6 +375,7 @@ abstract contract NotionalResolver is Events, Helpers { /** * @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 @@ -348,6 +388,7 @@ abstract contract NotionalResolver is Events, Helpers { uint256 setId ) external + payable returns (string memory _eventName, bytes memory _eventParam) { cashBalanceToMint = getUint(getId, cashBalanceToMint); @@ -437,7 +478,11 @@ abstract contract NotionalResolver is Events, Helpers { trades[0] = encodeLendTrade(marketIndex, fCashAmount, minLendRate); action[0].trades = trades; - uint256 msgValue = getMsgValue(currencyId, useUnderlying, depositAmount); + uint256 msgValue = getMsgValue( + currencyId, + useUnderlying, + depositAmount + ); notional.batchBalanceAndTradeAction{value: msgValue}( address(this), action @@ -479,7 +524,7 @@ abstract contract NotionalResolver is Events, Helpers { * @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 + * @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 @@ -504,10 +549,9 @@ abstract contract NotionalResolver is Events, Helpers { payable returns (string memory _eventName, bytes memory _eventParam) { - bool useUnderlying = ( - depositAction == DepositActionType.DepositUnderlying || - depositAction == DepositActionType.DepositUnderlyingAndMintNToken - ); + bool useUnderlying = (depositAction == + DepositActionType.DepositUnderlying || + depositAction == DepositActionType.DepositUnderlyingAndMintNToken); depositAmount = getDepositAmountAndSetApproval( getId, @@ -528,7 +572,11 @@ abstract contract NotionalResolver is Events, Helpers { redeemToUnderlying ); - uint256 msgValue = getMsgValue(depositCurrencyId, useUnderlying, depositAmount); + uint256 msgValue = getMsgValue( + depositCurrencyId, + useUnderlying, + depositAmount + ); executeTradeActionWithBalanceChange( actions, msgValue, diff --git a/scripts/constant/abis.js b/scripts/constant/abis.js index bc1fd8f1..cb62ccfb 100644 --- a/scripts/constant/abis.js +++ b/scripts/constant/abis.js @@ -8,7 +8,6 @@ module.exports = { basic: require("./abi/connectors/basic.json"), auth: require("./abi/connectors/auth.json"), "INSTAPOOL-A": require("./abi/connectors/instapool.json"), - "BASIC-A": require("./abi/connectors/basic.json") }, basic: { erc20: require("./abi/basics/erc20.json"), diff --git a/test/notional/notional.helpers.js b/test/notional/notional.helpers.js index a82e989e..a31a07c4 100644 --- a/test/notional/notional.helpers.js +++ b/test/notional/notional.helpers.js @@ -140,19 +140,6 @@ const withdrawLend = async (dsa, authority, referrer, currencyId, marketIndex, f await tx.wait() }; -const depositERC20 = async (dsa, authority, referrer, token, amount) => { - const spells = [ - { - connector: "BASIC-A", - method: "deposit", - args: [token, amount, 0, 0] - } - ]; - - const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); - await tx.wait() -}; - module.exports = { depositCollteral, depositAndMintNToken, @@ -162,6 +149,5 @@ module.exports = { redeemNTokenRaw, redeemNTokenAndWithdraw, redeemNTokenAndDeleverage, - depositCollateralBorrowAndWithdraw, - depositERC20 + depositCollateralBorrowAndWithdraw }; diff --git a/test/notional/notional.test.js b/test/notional/notional.test.js index f7a3a841..573bf307 100644 --- a/test/notional/notional.test.js +++ b/test/notional/notional.test.js @@ -133,9 +133,7 @@ describe("Notional", function () { it("test_deposit_ETH_asset", async function () { const depositAmount = ethers.utils.parseUnits("1", 8); - await cethToken.connect(cethWhale).transfer(wallet0.address, depositAmount); - await cethToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cethToken.address, depositAmount); + await cethToken.connect(cethWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, ETH_ID, depositAmount, false); const bal = await notional.callStatic.getAccountBalance(ETH_ID, dsaWallet0.address); // balance in internal asset precision @@ -145,9 +143,7 @@ describe("Notional", function () { it("test_deposit_DAI_underlying", async function () { const depositAmount = ethers.utils.parseUnits("1000", 18); - await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); - await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await daiToken.connect(daiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, true); const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); // balance in internal asset precision @@ -157,9 +153,7 @@ describe("Notional", function () { it("test_deposit_DAI_asset", async function () { const depositAmount = ethers.utils.parseUnits("1000", 8); - await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); - await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await cdaiToken.connect(cdaiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositCollteral(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); // balance in internal asset precision @@ -195,9 +189,7 @@ describe("Notional", function () { it("test_deposit_ETH_asset_and_lend", async function () { const depositAmount = ethers.utils.parseUnits("1", 8); - await cethToken.connect(cethWhale).transfer(wallet0.address, depositAmount); - await cethToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cethToken.address, depositAmount); + await cethToken.connect(cethWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, false, depositAmount, MARKET_3M, 0.01e8, 0); const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); expect(portfolio.length, "expect 1 lending position").to.be.equal(1); @@ -206,9 +198,7 @@ describe("Notional", function () { it("test_deposit_DAI_underlying_and_lend", async function () { const depositAmount = ethers.utils.parseUnits("1000", 18); - await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); - await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await daiToken.connect(daiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, DAI_ID, true, depositAmount, MARKET_3M, 100e8, 0); const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); expect(portfolio.length, "expect 1 lending position").to.be.equal(1); @@ -217,9 +207,7 @@ describe("Notional", function () { it("test_deposit_DAI_asset_and_lend", async function () { const depositAmount = ethers.utils.parseUnits("1000", 8); - await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); - await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await cdaiToken.connect(cdaiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, DAI_ID, false, depositAmount, MARKET_3M, 10e8, 0); const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); expect(portfolio.length, "expect 1 lending position").to.be.equal(1); @@ -275,9 +263,7 @@ describe("Notional", function () { it("test_deposit_DAI_underlying_and_borrow_ETH", async function () { const depositAmount = ethers.utils.parseUnits("20000", 18); - await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); - await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await daiToken.connect(daiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositCollateralBorrowAndWithdraw( dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true ); @@ -289,9 +275,7 @@ describe("Notional", function () { it("test_deposit_DAI_asset_and_borrow_ETH", async function () { const depositAmount = ethers.utils.parseUnits("1000000", 8); - await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); - await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await cdaiToken.connect(cdaiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositCollateralBorrowAndWithdraw( dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_ASSET, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true ); @@ -303,9 +287,7 @@ describe("Notional", function () { it("test_mint_nDAI_underlying_and_borrow_ETH", async function () { const depositAmount = ethers.utils.parseUnits("20000", 18); - await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); - await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await daiToken.connect(daiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositCollateralBorrowAndWithdraw( dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING_MINT_NTOKEN, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true ); @@ -317,9 +299,7 @@ describe("Notional", function () { it("test_mint_nDAI_asset_and_borrow_ETH", async function () { const depositAmount = ethers.utils.parseUnits("1000000", 8); - await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); - await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await cdaiToken.connect(cdaiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositCollateralBorrowAndWithdraw( dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_ASSET_MINT_NTOKEN, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true ); @@ -361,9 +341,7 @@ describe("Notional", function () { it("test_redeem_DAI_raw", async function () { const depositAmount = ethers.utils.parseUnits("1000", 8); - await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); - await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await cdaiToken.connect(cdaiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); await helpers.redeemNTokenRaw(dsaWallet0, wallet0, wallet1, DAI_ID, true, MaxUint96) const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); @@ -373,9 +351,7 @@ describe("Notional", function () { it("test_redeem_DAI_and_withdraw_redeem", async function () { const depositAmount = ethers.utils.parseUnits("1000", 8); - await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); - await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await cdaiToken.connect(cdaiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); await helpers.redeemNTokenAndWithdraw(dsaWallet0, wallet0, wallet1, DAI_ID, MaxUint96, ethers.constants.MaxUint256, true); const bal = await notional.callStatic.getAccountBalance(DAI_ID, dsaWallet0.address); @@ -385,9 +361,7 @@ describe("Notional", function () { it("test_redeem_DAI_and_withdraw_no_redeem", async function () { const depositAmount = ethers.utils.parseUnits("1000", 8); - await cdaiToken.connect(cdaiWhale).transfer(wallet0.address, depositAmount); - await cdaiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, cdaiToken.address, depositAmount); + await cdaiToken.connect(cdaiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, DAI_ID, depositAmount, false); expect(await cdaiToken.balanceOf(dsaWallet0.address)).to.be.equal(ethers.utils.parseEther("0")); await helpers.redeemNTokenAndWithdraw(dsaWallet0, wallet0, wallet1, DAI_ID, MaxUint96, ethers.constants.MaxUint256, false); @@ -402,9 +376,7 @@ describe("Notional", function () { it("test_redeem_DAI_and_deleverage", async function () { const depositAmount = ethers.utils.parseUnits("20000", 18); - await daiToken.connect(daiWhale).transfer(wallet0.address, depositAmount); - await daiToken.connect(wallet0).approve(dsaWallet0.address, ethers.constants.MaxUint256); - await helpers.depositERC20(dsaWallet0, wallet0, wallet1, daiToken.address, depositAmount); + await daiToken.connect(daiWhale).transfer(dsaWallet0.address, depositAmount); await helpers.depositCollateralBorrowAndWithdraw( dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true ); From bdec4650b44880b847dc94d9c37315fb022d72f6 Mon Sep 17 00:00:00 2001 From: Tianjie Wei <weitianjie@gmail.com> Date: Mon, 31 Jan 2022 19:29:02 -0800 Subject: [PATCH 14/20] Moving comment block --- contracts/mainnet/connectors/notional/main.sol | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index 6ebf6e91..5af12ec3 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -1,16 +1,17 @@ pragma solidity ^0.7.6; pragma abicoder v2; +/** + * @title Notional + * @dev Fixed Rate Lending and Borrowing + */ + import {Helpers} from "./helpers.sol"; import {SafeInt256} from "./SafeInt256.sol"; import {Events} from "./events.sol"; import {DepositActionType, BalanceActionWithTrades, BalanceAction} from "./interface.sol"; import {TokenInterface} from "../../common/interfaces.sol"; -/** - * @title Notional - * @dev Fixed Rate Lending and Borrowing - */ abstract contract NotionalResolver is Events, Helpers { using SafeInt256 for int256; From ffbdafbbc22c1c9868bab23a268f0f83e5b3fec8 Mon Sep 17 00:00:00 2001 From: Tianjie Wei <weitianjie@gmail.com> Date: Wed, 2 Feb 2022 10:22:30 -0800 Subject: [PATCH 15/20] Migrating tests to typescript --- .../notional/notional.contracts.ts} | 4 +- .../notional/notional.helpers.ts} | 114 +++++++++++++----- .../notional/notional.test.ts} | 106 ++++++++-------- 3 files changed, 144 insertions(+), 80 deletions(-) rename test/{notional/notional.contracts.js => mainnet/notional/notional.contracts.ts} (99%) rename test/{notional/notional.helpers.js => mainnet/notional/notional.helpers.ts} (61%) rename test/{notional/notional.test.js => mainnet/notional/notional.test.ts} (87%) diff --git a/test/notional/notional.contracts.js b/test/mainnet/notional/notional.contracts.ts similarity index 99% rename from test/notional/notional.contracts.js rename to test/mainnet/notional/notional.contracts.ts index f752ee06..d9f95919 100644 --- a/test/notional/notional.contracts.js +++ b/test/mainnet/notional/notional.contracts.ts @@ -98,7 +98,7 @@ const ERC20_TOKEN_ABI = [ "function approve(address spender, uint256 amount) external returns (bool)", ]; -module.exports = { +export default { NOTIONAL_CONTRACT_ADDRESS, NOTIONAL_CONTRACT_ABI, WETH_TOKEN_ADDRESS, @@ -106,4 +106,4 @@ module.exports = { CDAI_TOKEN_ADDRESS, CETH_TOKEN_ADDRESS, ERC20_TOKEN_ABI -}; \ No newline at end of file +}; diff --git a/test/notional/notional.helpers.js b/test/mainnet/notional/notional.helpers.ts similarity index 61% rename from test/notional/notional.helpers.js rename to test/mainnet/notional/notional.helpers.ts index a31a07c4..61233e44 100644 --- a/test/notional/notional.helpers.js +++ b/test/mainnet/notional/notional.helpers.ts @@ -1,6 +1,14 @@ -const encodeSpells = require("../../scripts/encodeSpells.js") +import { BigNumber } from "ethers"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells" -const depositCollteral = async (dsa, authority, referrer, currencyId, amount, underlying) => { +const depositCollteral = async ( + dsa: any, + authority: any, + referrer: any, + currencyId: number, + amount: BigNumber, + underlying: boolean +) => { const spells = [ { connector: "NOTIONAL-TEST-A", @@ -13,7 +21,14 @@ const depositCollteral = async (dsa, authority, referrer, currencyId, amount, un await tx.wait() }; -const depositAndMintNToken = async (dsa, authority, referrer, currencyId, amount, underlying) => { +const depositAndMintNToken = async ( + dsa: any, + authority: any, + referrer: any, + currencyId: number, + amount: BigNumber, + underlying: boolean +) => { const spells = [ { connector: "NOTIONAL-TEST-A", @@ -26,20 +41,36 @@ const depositAndMintNToken = async (dsa, authority, referrer, currencyId, amount await tx.wait() } -const depositAndLend = async (dsa, authority, referrer, currencyId, underlying, amount, market, fcash, minRate) => { +const depositAndLend = async ( + dsa: any, + authority: any, + referrer: any, + currencyId: number, + underlying: boolean, + amount: BigNumber, + market: number, + fcash: BigNumber +) => { const spells = [ { connector: "NOTIONAL-TEST-A", method: "depositAndLend", - args: [currencyId, amount, underlying, market, fcash, minRate, 0] + args: [currencyId, amount, underlying, market, fcash, 0, 0] } ]; const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); - await tx.wait() + await tx.wait() }; -const withdrawCollateral = async (dsa, authority, referrer, currencyId, amount, underlying) => { +const withdrawCollateral = async ( + dsa: any, + authority: any, + referrer: any, + currencyId: number, + amount: BigNumber, + underlying: boolean +) => { const spells = [ { connector: "NOTIONAL-TEST-A", @@ -52,7 +83,14 @@ const withdrawCollateral = async (dsa, authority, referrer, currencyId, amount, await tx.wait() }; -const redeemNTokenRaw = async (dsa, authority, referrer, currencyId, sellTokenAssets, tokensToRedeem) => { +const redeemNTokenRaw = async ( + dsa: any, + authority: any, + referrer: any, + currencyId: number, + sellTokenAssets: boolean, + tokensToRedeem: BigNumber +) => { const spells = [ { connector: "NOTIONAL-TEST-A", @@ -65,7 +103,15 @@ const redeemNTokenRaw = async (dsa, authority, referrer, currencyId, sellTokenAs await tx.wait() }; -const redeemNTokenAndWithdraw = async (dsa, authority, referrer, currencyId, tokensToRedeem, amountToWithdraw, redeemToUnderlying) => { +const redeemNTokenAndWithdraw = async ( + dsa: any, + authority: any, + referrer: any, + currencyId: number, + tokensToRedeem: BigNumber, + amountToWithdraw: BigNumber, + redeemToUnderlying: boolean +) => { const spells = [ { connector: "NOTIONAL-TEST-A", @@ -78,12 +124,20 @@ const redeemNTokenAndWithdraw = async (dsa, authority, referrer, currencyId, tok await tx.wait() }; -const redeemNTokenAndDeleverage = async (dsa, authority, referrer, currencyId, tokensToRedeem, marketIndex, fCashAmount, minLendRate) => { +const redeemNTokenAndDeleverage = async ( + dsa: any, + authority: any, + referrer: any, + currencyId: number, + tokensToRedeem: BigNumber, + marketIndex: number, + fCashAmount: BigNumber +) => { const spells = [ { connector: "NOTIONAL-TEST-A", method: "redeemNTokenAndDeleverage", - args: [currencyId, tokensToRedeem, marketIndex, fCashAmount, minLendRate, 0] + args: [currencyId, tokensToRedeem, marketIndex, fCashAmount, 0, 0] } ]; @@ -92,17 +146,16 @@ const redeemNTokenAndDeleverage = async (dsa, authority, referrer, currencyId, t }; const depositCollateralBorrowAndWithdraw = async ( - dsa, - authority, - referrer, - depositCurrencyId, - depositUnderlying, - depositAmount, - borrowCurrencyId, - marketIndex, - fCashAmount, - maxBorrowRate, - redeedmUnderlying + dsa: any, + authority: any, + referrer: any, + depositCurrencyId: number, + depositType: number, + depositAmount: BigNumber, + borrowCurrencyId: number, + marketIndex: number, + fCashAmount: BigNumber, + redeedmUnderlying: boolean ) => { const spells = [ { @@ -110,12 +163,12 @@ const depositCollateralBorrowAndWithdraw = async ( method: "depositCollateralBorrowAndWithdraw", args: [ depositCurrencyId, - depositUnderlying, + depositType, depositAmount, borrowCurrencyId, marketIndex, fCashAmount, - maxBorrowRate, + 0, redeedmUnderlying, 0, 0 @@ -127,12 +180,19 @@ const depositCollateralBorrowAndWithdraw = async ( await tx.wait() }; -const withdrawLend = async (dsa, authority, referrer, currencyId, marketIndex, fCashAmount, maxBorrowRate) => { +const withdrawLend = async ( + dsa: any, + authority: any, + referrer: any, + currencyId: number, + marketIndex: number, + fCashAmount: BigNumber +) => { const spells = [ { connector: "NOTIONAL-TEST-A", method: "withdrawLend", - args: [currencyId, marketIndex, fCashAmount, maxBorrowRate, 0] + args: [currencyId, marketIndex, fCashAmount, 0, 0] } ]; @@ -140,7 +200,7 @@ const withdrawLend = async (dsa, authority, referrer, currencyId, marketIndex, f await tx.wait() }; -module.exports = { +export default { depositCollteral, depositAndMintNToken, depositAndLend, diff --git a/test/notional/notional.test.js b/test/mainnet/notional/notional.test.ts similarity index 87% rename from test/notional/notional.test.js rename to test/mainnet/notional/notional.test.ts index 573bf307..967548cb 100644 --- a/test/notional/notional.test.js +++ b/test/mainnet/notional/notional.test.ts @@ -1,23 +1,18 @@ -const { expect } = require("chai"); -const hre = require("hardhat"); -const { web3, deployments, waffle, ethers } = hre; +import { expect } from "chai"; +import hre from "hardhat"; +const { waffle, ethers } = hre; const { provider, deployContract } = waffle -const deployAndEnableConnector = require("../../scripts/deployAndEnableConnector.js") -const buildDSAv2 = require("../../scripts/buildDSAv2") -const encodeSpells = require("../../scripts/encodeSpells.js") -const getMasterSigner = require("../../scripts/getMasterSigner") +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2" +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner" +import { addresses } from "../../../scripts/tests/mainnet/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import contracts from "./notional.contracts"; +import helpers from "./notional.helpers"; -const addresses = require("../../scripts/constant/addresses"); -const abis = require("../../scripts/constant/abis"); -const constants = require("../../scripts/constant/constant"); -const tokens = require("../../scripts/constant/tokens"); - -const contracts = require("./notional.contracts"); -const helpers = require("./notional.helpers"); - -const connectV2NotionalArtifacts = require("../../artifacts/contracts/mainnet/connectors/notional/main.sol/ConnectV2Notional.json"); -const { BigNumber } = require("ethers"); +import { ConnectV2Notional__factory } from "../../../typechain"; +import { BigNumber } from "ethers"; const DAI_WHALE = "0x6dfaf865a93d3b0b5cfd1b4db192d1505676645b"; const CDAI_WHALE = "0x33b890d6574172e93e58528cd99123a88c0756e9"; @@ -35,17 +30,17 @@ const MARKET_3M = 1; describe("Notional", function () { const connectorName = "NOTIONAL-TEST-A" - let dsaWallet0 - let masterSigner; - let instaConnectorsV2; - let connector; - let notional; - let daiToken; - let cdaiToken; - let cethToken; - let daiWhale; - let cdaiWhale; - let cethWhale; + let dsaWallet0: any; + let masterSigner: any; + let instaConnectorsV2: any; + let connector: any; + let notional: any; + let daiToken: any; + let cdaiToken: any; + let cethToken: any; + let daiWhale: any; + let cdaiWhale: any; + let cethWhale: any; const wallets = provider.getWallets() const [wallet0, wallet1, wallet2, wallet3] = wallets @@ -55,6 +50,7 @@ describe("Notional", function () { params: [ { forking: { + //@ts-ignore jsonRpcUrl: hre.config.networks.hardhat.forking.url, blockNumber: 13798624, }, @@ -78,11 +74,11 @@ describe("Notional", function () { params: [CETH_WHALE] }) - masterSigner = await getMasterSigner(wallet3) + masterSigner = await getMasterSigner() instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); connector = await deployAndEnableConnector({ connectorName, - contractArtifact: connectV2NotionalArtifacts, + contractArtifact: ConnectV2Notional__factory, signer: masterSigner, connectors: instaConnectorsV2 }) @@ -109,11 +105,6 @@ describe("Notional", function () { ethers.provider ); cethWhale = await ethers.getSigner(CETH_WHALE); - weth = new ethers.Contract( - contracts.WETH_TOKEN_ADDRESS, - contracts.ERC20_TOKEN_ABI, - ethers.provider - ); dsaWallet0 = await buildDSAv2(wallet0.address) }); @@ -181,7 +172,8 @@ describe("Notional", function () { value: ethers.utils.parseEther("10") }); const depositAmount = ethers.utils.parseEther("10"); - await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, true, depositAmount, MARKET_3M, 9e8, 0); + const fcash = ethers.utils.parseUnits("9", 8); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, true, depositAmount, MARKET_3M, fcash); const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); expect(portfolio.length, "expect 1 lending position").to.be.equal(1); expect(portfolio[0][3], "expect 9 fETH").to.be.gte(ethers.utils.parseUnits("900000000", 0)); @@ -190,7 +182,8 @@ describe("Notional", function () { it("test_deposit_ETH_asset_and_lend", async function () { const depositAmount = ethers.utils.parseUnits("1", 8); await cethToken.connect(cethWhale).transfer(dsaWallet0.address, depositAmount); - await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, false, depositAmount, MARKET_3M, 0.01e8, 0); + const fcash = ethers.utils.parseUnits("0.01", 8); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, false, depositAmount, MARKET_3M, fcash); const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); expect(portfolio.length, "expect 1 lending position").to.be.equal(1); expect(portfolio[0][3], "expect 0.01 fETH").to.be.gte(ethers.utils.parseUnits("1000000", 0)); @@ -199,7 +192,8 @@ describe("Notional", function () { it("test_deposit_DAI_underlying_and_lend", async function () { const depositAmount = ethers.utils.parseUnits("1000", 18); await daiToken.connect(daiWhale).transfer(dsaWallet0.address, depositAmount); - await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, DAI_ID, true, depositAmount, MARKET_3M, 100e8, 0); + const fcash = ethers.utils.parseUnits("100", 8); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, DAI_ID, true, depositAmount, MARKET_3M, fcash); const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); expect(portfolio.length, "expect 1 lending position").to.be.equal(1); expect(portfolio[0][3], "expect 100 fDAI").to.be.gte(ethers.utils.parseUnits("10000000000", 0)); @@ -208,7 +202,8 @@ describe("Notional", function () { it("test_deposit_DAI_asset_and_lend", async function () { const depositAmount = ethers.utils.parseUnits("1000", 8); await cdaiToken.connect(cdaiWhale).transfer(dsaWallet0.address, depositAmount); - await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, DAI_ID, false, depositAmount, MARKET_3M, 10e8, 0); + const fcash = ethers.utils.parseUnits("10", 8); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, DAI_ID, false, depositAmount, MARKET_3M, fcash); const portfolio = await notional.getAccountPortfolio(dsaWallet0.address); expect(portfolio.length, "expect 1 lending position").to.be.equal(1); expect(portfolio[0][3], "expect 10 fDAI").to.be.gte(ethers.utils.parseUnits("1000000000", 0)); @@ -220,11 +215,12 @@ describe("Notional", function () { value: ethers.utils.parseEther("10") }); const depositAmount = ethers.utils.parseEther("10"); - await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, true, depositAmount, MARKET_3M, 9e8, 0); + const fcash = ethers.utils.parseUnits("9", 8); + await helpers.depositAndLend(dsaWallet0, wallet0, wallet1, ETH_ID, true, depositAmount, MARKET_3M, fcash); const before = await notional.getAccountPortfolio(dsaWallet0.address); expect(before.length, "expect 1 lending position").to.be.equal(1); expect(before[0][3], "expect 9 fETH").to.be.gte(ethers.utils.parseUnits("900000000", 0)); - await helpers.withdrawLend(dsaWallet0, wallet0, wallet1, ETH_ID, MARKET_3M, 9e8, 0); + await helpers.withdrawLend(dsaWallet0, wallet0, wallet1, ETH_ID, MARKET_3M, fcash); const after = await notional.getAccountPortfolio(dsaWallet0.address); expect(after.length, "expect lending position to be closed out").to.be.equal(0); }); @@ -237,11 +233,12 @@ describe("Notional", function () { value: ethers.utils.parseEther("10") }); const depositAmount = ethers.utils.parseEther("10"); + const fcash = ethers.utils.parseUnits("1000", 8); await helpers.depositCollateralBorrowAndWithdraw( - dsaWallet0, wallet0, wallet1, ETH_ID, DEPOSIT_UNDERLYING, depositAmount, DAI_ID, MARKET_3M, 1000e8, 0, true + dsaWallet0, wallet0, wallet1, ETH_ID, DEPOSIT_UNDERLYING, depositAmount, DAI_ID, MARKET_3M, fcash, true ); expect( - await daiToken.balanceOf(dsaWallet0.address), + await daiToken.balanceOf(dsaWallet0.address), "expect DSA wallet to contain borrowed balance minus fees" ).to.be.gte(ethers.utils.parseEther("990")); }); @@ -252,8 +249,9 @@ describe("Notional", function () { value: ethers.utils.parseEther("10") }); const depositAmount = ethers.utils.parseEther("10"); + const fcash = ethers.utils.parseUnits("1000", 8); await helpers.depositCollateralBorrowAndWithdraw( - dsaWallet0, wallet0, wallet1, ETH_ID, DEPOSIT_UNDERLYING, depositAmount, DAI_ID, MARKET_3M, 1000e8, 0, false + dsaWallet0, wallet0, wallet1, ETH_ID, DEPOSIT_UNDERLYING, depositAmount, DAI_ID, MARKET_3M, fcash, false ); expect( await cdaiToken.balanceOf(dsaWallet0.address), @@ -264,8 +262,9 @@ describe("Notional", function () { it("test_deposit_DAI_underlying_and_borrow_ETH", async function () { const depositAmount = ethers.utils.parseUnits("20000", 18); await daiToken.connect(daiWhale).transfer(dsaWallet0.address, depositAmount); + const fcash = ethers.utils.parseUnits("1", 8); await helpers.depositCollateralBorrowAndWithdraw( - dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING, depositAmount, ETH_ID, MARKET_3M, fcash, true ); expect( await ethers.provider.getBalance(dsaWallet0.address), @@ -276,11 +275,12 @@ describe("Notional", function () { it("test_deposit_DAI_asset_and_borrow_ETH", async function () { const depositAmount = ethers.utils.parseUnits("1000000", 8); await cdaiToken.connect(cdaiWhale).transfer(dsaWallet0.address, depositAmount); + const fcash = ethers.utils.parseUnits("1", 8); await helpers.depositCollateralBorrowAndWithdraw( - dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_ASSET, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_ASSET, depositAmount, ETH_ID, MARKET_3M, fcash, true ); expect( - await ethers.provider.getBalance(dsaWallet0.address), + await ethers.provider.getBalance(dsaWallet0.address), "expect DSA wallet to contain borrowed balance minus fees" ).to.be.gte(ethers.utils.parseEther("0.99")); }); @@ -288,8 +288,9 @@ describe("Notional", function () { it("test_mint_nDAI_underlying_and_borrow_ETH", async function () { const depositAmount = ethers.utils.parseUnits("20000", 18); await daiToken.connect(daiWhale).transfer(dsaWallet0.address, depositAmount); + const fcash = ethers.utils.parseUnits("1", 8); await helpers.depositCollateralBorrowAndWithdraw( - dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING_MINT_NTOKEN, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING_MINT_NTOKEN, depositAmount, ETH_ID, MARKET_3M, fcash, true ); expect( await ethers.provider.getBalance(dsaWallet0.address), @@ -300,8 +301,9 @@ describe("Notional", function () { it("test_mint_nDAI_asset_and_borrow_ETH", async function () { const depositAmount = ethers.utils.parseUnits("1000000", 8); await cdaiToken.connect(cdaiWhale).transfer(dsaWallet0.address, depositAmount); + const fcash = ethers.utils.parseUnits("1", 8); await helpers.depositCollateralBorrowAndWithdraw( - dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_ASSET_MINT_NTOKEN, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_ASSET_MINT_NTOKEN, depositAmount, ETH_ID, MARKET_3M, fcash, true ); expect( await ethers.provider.getBalance(dsaWallet0.address), @@ -377,15 +379,17 @@ describe("Notional", function () { it("test_redeem_DAI_and_deleverage", async function () { const depositAmount = ethers.utils.parseUnits("20000", 18); await daiToken.connect(daiWhale).transfer(dsaWallet0.address, depositAmount); + const fcash = ethers.utils.parseUnits("1", 8); await helpers.depositCollateralBorrowAndWithdraw( - dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING, depositAmount, ETH_ID, MARKET_3M, 1e8, 0, true + dsaWallet0, wallet0, wallet1, DAI_ID, DEPOSIT_UNDERLYING, depositAmount, ETH_ID, MARKET_3M, fcash, true ); const bal = await ethers.provider.getBalance(dsaWallet0.address); await helpers.depositAndMintNToken(dsaWallet0, wallet0, wallet1, ETH_ID, bal, true); const before = await notional.getAccountPortfolio(dsaWallet0.address); expect(before.length, "expect 1 fDAI debt position").to.be.equal(1); expect(before[0][3], "expect fDAI debt position to equal borrow amount").to.be.lte(ethers.utils.parseUnits("-100000000", 0)); - await helpers.redeemNTokenAndDeleverage(dsaWallet0, wallet0, wallet1, ETH_ID, MaxUint96, MARKET_3M, 0.98e8, 0); + const fcash2 = ethers.utils.parseUnits("0.98", 8); + await helpers.redeemNTokenAndDeleverage(dsaWallet0, wallet0, wallet1, ETH_ID, MaxUint96, MARKET_3M, fcash2); const after = await notional.getAccountPortfolio(dsaWallet0.address); expect(after.length, "expect 1 fDAI debt position after deleverage").to.be.equal(1); expect(after[0][3], "expect fDAI debt balance to go down after deleverage").to.be.lte(ethers.utils.parseUnits("-2000000", 0)); From 8ac646a1a3ee11b0970ba77b3384ce692002785a Mon Sep 17 00:00:00 2001 From: Tianjie Wei <weitianjie@gmail.com> Date: Wed, 2 Feb 2022 11:14:45 -0800 Subject: [PATCH 16/20] Addressing PR comments --- contracts/mainnet/common/math.sol | 5 ++ .../connectors/notional/SafeInt256.sol | 31 -------- .../mainnet/connectors/notional/helpers.sol | 78 +++++++++++-------- .../mainnet/connectors/notional/main.sol | 17 ++-- 4 files changed, 56 insertions(+), 75 deletions(-) delete mode 100644 contracts/mainnet/connectors/notional/SafeInt256.sol diff --git a/contracts/mainnet/common/math.sol b/contracts/mainnet/common/math.sol index f6e2e6cd..f395ba84 100644 --- a/contracts/mainnet/common/math.sol +++ b/contracts/mainnet/common/math.sol @@ -43,6 +43,11 @@ contract DSMath { require(y >= 0, "int-overflow"); } + function toUint(int256 x) internal pure returns (uint256) { + require(x >= 0, "int-overflow"); + return uint256(x); + } + function toRad(uint wad) internal pure returns (uint rad) { rad = mul(wad, 10 ** 27); } diff --git a/contracts/mainnet/connectors/notional/SafeInt256.sol b/contracts/mainnet/connectors/notional/SafeInt256.sol deleted file mode 100644 index 47013c05..00000000 --- a/contracts/mainnet/connectors/notional/SafeInt256.sol +++ /dev/null @@ -1,31 +0,0 @@ -pragma solidity ^0.7.6; - -library SafeInt256 { - int256 private constant _INT256_MIN = type(int256).min; - - function mul(int256 a, int256 b) internal pure returns (int256 c) { - c = a * b; - if (a == -1) require(b == 0 || c / b == a); - else require(a == 0 || c / a == b); - } - - function div(int256 a, int256 b) internal pure returns (int256 c) { - require(!(b == -1 && a == _INT256_MIN)); // dev: int256 div overflow - // NOTE: solidity will automatically revert on divide by zero - c = a / b; - } - - function sub(int256 x, int256 y) internal pure returns (int256 z) { - // taken from uniswap v3 - require((z = x - y) <= x == (y >= 0)); - } - - function neg(int256 x) internal pure returns (int256 y) { - return mul(-1, x); - } - - function toInt(uint256 x) internal pure returns (int256 y) { - require(x <= uint256(type(int256).max)); - return int256(x); - } -} diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index 0aceb6e0..79161b4f 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -1,18 +1,15 @@ pragma solidity ^0.7.6; pragma abicoder v2; -import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; import {Token, NotionalInterface, BalanceAction, BalanceActionWithTrades, DepositActionType} from "./interface.sol"; -import {SafeInt256} from "./SafeInt256.sol"; import {Basic} from "../../common/basic.sol"; +import {DSMath} from "../../common/math.sol"; import {TokenInterface} from "../../common/interfaces.sol"; -contract Helpers is Basic { - using SafeMath for uint256; - using SafeInt256 for int256; +abstract contract Helpers is DSMath, Basic { uint8 internal constant LEND_TRADE = 0; uint8 internal constant BORROW_TRADE = 1; - int256 internal constant INTERNAL_TOKEN_PRECISION = 1e8; + uint256 internal constant INTERNAL_TOKEN_PRECISION = 1e8; uint256 internal constant ETH_CURRENCY_ID = 1; uint256 internal constant MAX_DEPOSIT = uint256(-1); @@ -20,25 +17,30 @@ contract Helpers is Basic { 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) { - ( - /* Token memory assetToken */, - Token memory underlyingToken - ) = notional.getCurrency(currencyId); + /// @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 asset token for a given currency id function getAssetToken(uint16 currencyId) internal view returns (address) { - ( - Token memory assetToken, - /* Token memory underlyingToken */ - ) = notional.getCurrency(currencyId); + // prettier-ignore + (Token memory assetToken, /* underlyingToken */) = notional.getCurrency(currencyId); return assetToken.tokenAddress; } - function getCashBalance(uint16 currencyId) internal view returns (int256 cashBalance) { + function getCashBalance(uint16 currencyId) + internal + view + returns (int256 cashBalance) + { + // prettier-ignore ( cashBalance, /* int256 nTokenBalance */, @@ -46,17 +48,25 @@ contract Helpers is Basic { ) = notional.getAccountBalance(currencyId, address(this)); } - function getNTokenBalance(uint16 currencyId) internal view returns (int256 nTokenBalance) { + function getNTokenBalance(uint16 currencyId) + internal + view + returns (uint256) + { + // prettier-ignore ( /* int256 cashBalance */, - nTokenBalance, + int256 nTokenBalance, /* int256 lastClaimTime */ ) = notional.getAccountBalance(currencyId, address(this)); + return toUint(nTokenBalance); } - function getNTokenRedeemAmount(uint16 currencyId, uint96 _tokensToRedeem, uint256 getId) - internal - returns (uint96 tokensToRedeem) { + 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)); @@ -73,17 +83,20 @@ contract Helpers is Basic { : 0; } - function convertToInternal(uint16 currencyId, int256 amount) - internal view - returns (int256) + 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); - if (assetToken.decimals == INTERNAL_TOKEN_PRECISION) return amount; - return amount.mul(INTERNAL_TOKEN_PRECISION).div(assetToken.decimals); + uint256 decimals = toUint(assetToken.decimals); + if (decimals == INTERNAL_TOKEN_PRECISION) return amount; + return div(mul(amount, INTERNAL_TOKEN_PRECISION), decimals); } function encodeLendTrade( @@ -121,9 +134,10 @@ contract Helpers is Basic { 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; + return + depositAmount == MAX_DEPOSIT + ? address(this).balance + : depositAmount; } address tokenAddress = useUnderlying @@ -184,7 +198,7 @@ contract Helpers is Basic { if (setId != 0) { uint256 balanceAfter = getBalance(tokenAddress); - setUint(setId, balanceAfter.sub(balanceBefore)); + setUint(setId, sub(balanceAfter, balanceBefore)); } } @@ -207,7 +221,7 @@ contract Helpers is Basic { if (setId != 0) { uint256 balanceAfter = getBalance(tokenAddress); - setUint(setId, balanceAfter.sub(balanceBefore)); + setUint(setId, sub(balanceAfter, balanceBefore)); } } diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index 5af12ec3..df4ac1ee 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -7,14 +7,11 @@ pragma abicoder v2; */ import {Helpers} from "./helpers.sol"; -import {SafeInt256} from "./SafeInt256.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 { - using SafeInt256 for int256; - /** * @notice Deposit collateral into Notional, this should only be used for reducing risk of * liquidation. @@ -102,7 +99,7 @@ abstract contract NotionalResolver is Events, Helpers { uint88 amountInternalPrecision = withdrawAmount == uint256(-1) ? uint88(getCashBalance(currencyId)) : uint88( - convertToInternal(currencyId, SafeInt256.toInt(withdrawAmount)) + convertToInternal(currencyId, withdrawAmount) ); uint256 amountWithdrawn = notional.withdraw( @@ -345,7 +342,7 @@ abstract contract NotionalResolver is Events, Helpers { action[0].depositActionAmount = depositAmount; // withdraw amount, withdraw cash and redeem to underlying are all 0 and false - int256 nTokenBefore = getNTokenBalance(currencyId); + uint256 nTokenBefore = getNTokenBalance(currencyId); uint256 msgValue = getMsgValue( currencyId, useUnderlying, @@ -354,9 +351,7 @@ abstract contract NotionalResolver is Events, Helpers { notional.batchBalanceAction{value: msgValue}(address(this), action); - int256 nTokenBalanceChange = getNTokenBalance(currencyId).sub( - nTokenBefore - ); + uint256 nTokenBalanceChange = sub(getNTokenBalance(currencyId), nTokenBefore); if (setId != 0) { // Set the amount of nTokens minted @@ -402,13 +397,11 @@ abstract contract NotionalResolver is Events, Helpers { action[0].depositActionAmount = cashBalanceToMint; // NOTE: withdraw amount, withdraw cash and redeem to underlying are all 0 and false - int256 nTokenBefore = getNTokenBalance(currencyId); + uint256 nTokenBefore = getNTokenBalance(currencyId); notional.batchBalanceAction(address(this), action); - int256 nTokenBalanceChange = getNTokenBalance(currencyId).sub( - nTokenBefore - ); + uint256 nTokenBalanceChange = sub(getNTokenBalance(currencyId), nTokenBefore); if (setId != 0) { // Set the amount of nTokens minted From 7534ddd4ad0a5857d1c56b40a269b81e6c59fd75 Mon Sep 17 00:00:00 2001 From: Tianjie Wei <weitianjie@gmail.com> Date: Fri, 4 Feb 2022 09:11:10 -0800 Subject: [PATCH 17/20] Fixing comment --- contracts/mainnet/connectors/notional/main.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index df4ac1ee..cb8c3447 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -15,7 +15,7 @@ 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 + * @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 From 74a7effdb4da869085e9c795f3547bacb0328f2f Mon Sep 17 00:00:00 2001 From: Tianjie Wei <weitianjie@gmail.com> Date: Thu, 31 Mar 2022 08:27:31 -0700 Subject: [PATCH 18/20] 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 () { From b7d60ca8aef671bba9e5ce8d6fc563ca4dc746cd Mon Sep 17 00:00:00 2001 From: weitianjie2000 <37812978+weitianjie2000@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:37:10 -0700 Subject: [PATCH 19/20] Adding staking to Notional DSA connector (#4) * Adding staking * Adding tests * Formatting * Reformat events * Addressing PR comments --- .../mainnet/connectors/notional/events.sol | 28 ++++ .../mainnet/connectors/notional/helpers.sol | 18 ++- .../mainnet/connectors/notional/interface.sol | 25 ++++ .../mainnet/connectors/notional/main.sol | 131 ++++++++++++++++++ test/mainnet/notional/notional.contracts.ts | 7 + test/mainnet/notional/notional.helpers.ts | 121 +++++++++++++++- test/mainnet/notional/notional.test.ts | 117 +++++++++++++++- 7 files changed, 444 insertions(+), 3 deletions(-) diff --git a/contracts/mainnet/connectors/notional/events.sol b/contracts/mainnet/connectors/notional/events.sol index 5163e18d..cf37ec1e 100644 --- a/contracts/mainnet/connectors/notional/events.sol +++ b/contracts/mainnet/connectors/notional/events.sol @@ -88,4 +88,32 @@ contract Events { ); event LogBatchActionRaw(address indexed account); + + event LogMintSNoteFromBPT(address indexed account, uint256 bptAmount); + + event LogMintSNoteFromETH( + address indexed account, + uint256 noteAmount, + uint256 ethAmount, + uint256 minBPT + ); + + event LogMintSNoteFromWETH( + address indexed account, + uint256 noteAmount, + uint256 wethAmount, + uint256 minBPT + ); + + event LogStartCoolDown(address indexed account); + + event LogStopCoolDown(address indexed account); + + event LogRedeemSNote( + address indexed account, + uint256 sNOTEAmount, + uint256 minWETH, + uint256 minNOTE, + bool redeemWETH + ); } diff --git a/contracts/mainnet/connectors/notional/helpers.sol b/contracts/mainnet/connectors/notional/helpers.sol index 1b18f12e..b551eb27 100644 --- a/contracts/mainnet/connectors/notional/helpers.sol +++ b/contracts/mainnet/connectors/notional/helpers.sol @@ -2,7 +2,7 @@ pragma solidity ^0.7.6; pragma abicoder v2; -import { Token, NotionalInterface, BalanceAction, BalanceActionWithTrades, DepositActionType } from "./interface.sol"; +import { Token, NotionalInterface, StakingInterface, BalanceAction, BalanceActionWithTrades, DepositActionType } from "./interface.sol"; import { Basic } from "../../common/basic.sol"; import { DSMath } from "../../common/math.sol"; import { TokenInterface } from "../../common/interfaces.sol"; @@ -18,6 +18,22 @@ abstract contract Helpers is DSMath, Basic { NotionalInterface internal constant notional = NotionalInterface(0x1344A36A1B56144C3Bc62E7757377D288fDE0369); + /// @dev sNOTE contract address + StakingInterface internal constant staking = + StakingInterface(0x38DE42F4BA8a35056b33A746A6b45bE9B1c3B9d2); + + /// @dev sNOTE balancer pool token address + TokenInterface internal constant bpt = + TokenInterface(0x5122E01D819E58BB2E22528c0D68D310f0AA6FD7); + + /// @dev NOTE token address + TokenInterface internal constant note = + TokenInterface(0xCFEAead4947f0705A14ec42aC3D44129E1Ef3eD5); + + /// @dev WETH token address + TokenInterface internal constant weth = + TokenInterface(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + /// @notice Returns the address of the underlying token for a given currency id, function getAssetOrUnderlyingToken(uint16 currencyId, bool underlying) internal diff --git a/contracts/mainnet/connectors/notional/interface.sol b/contracts/mainnet/connectors/notional/interface.sol index dea1ab6c..4b712527 100644 --- a/contracts/mainnet/connectors/notional/interface.sol +++ b/contracts/mainnet/connectors/notional/interface.sol @@ -2,6 +2,8 @@ pragma solidity ^0.7.6; pragma abicoder v2; +import { TokenInterface } from "../../common/interfaces.sol"; + /// @notice Different types of internal tokens /// - UnderlyingToken: underlying asset for a cToken (except for Ether) /// - cToken: Compound interest bearing token @@ -133,3 +135,26 @@ interface NotionalInterface { BalanceActionWithTrades[] calldata actions ) external payable; } + +interface StakingInterface is TokenInterface { + function mintFromETH(uint256 noteAmount, uint256 minBPT) external payable; + + function mintFromWETH( + uint256 noteAmount, + uint256 wethAmount, + uint256 minBPT + ) external; + + function mintFromBPT(uint256 bptAmount) external; + + function startCoolDown() external; + + function stopCoolDown() external; + + function redeem( + uint256 sNOTEAmount, + uint256 minWETH, + uint256 minNOTE, + bool redeemWETH + ) external; +} diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index c859b15c..575cc68f 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -655,6 +655,137 @@ abstract contract NotionalResolver is Events, Helpers { ); } + /// @notice Mints sNOTE from the underlying BPT token. + /// @dev Mints sNOTE from the underlying BPT token. + /// @param bptAmount is the amount of BPT to transfer from the msg.sender. + function mintSNoteFromBPT(uint256 bptAmount) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + if (bptAmount == type(uint256).max) + bptAmount = bpt.balanceOf(address(this)); + + approve(bpt, address(staking), bptAmount); + + staking.mintFromBPT(bptAmount); + + _eventName = "LogMintSNoteFromBPT(address,uint256)"; + _eventParam = abi.encode(address(this), bptAmount); + } + + /// @notice Mints sNOTE from some amount of NOTE and ETH + /// @dev Mints sNOTE from some amount of NOTE and ETH + /// @param noteAmount amount of NOTE to transfer into the sNOTE contract + /// @param minBPT slippage parameter to prevent front running + function mintSNoteFromETH( + uint256 noteAmount, + uint256 ethAmount, + uint256 minBPT + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + if (noteAmount == type(uint256).max) + noteAmount = note.balanceOf(address(this)); + + if (ethAmount == type(uint256).max) ethAmount = address(this).balance; + + approve(note, address(staking), noteAmount); + + staking.mintFromETH{ value: ethAmount }(noteAmount, minBPT); + + _eventName = "LogMintSNoteFromETH(address,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), ethAmount, noteAmount, minBPT); + } + + /// @notice Mints sNOTE from some amount of NOTE and WETH + /// @dev Mints sNOTE from some amount of NOTE and WETH + /// @param noteAmount amount of NOTE to transfer into the sNOTE contract + /// @param wethAmount amount of WETH to transfer into the sNOTE contract + /// @param minBPT slippage parameter to prevent front running + function mintSNoteFromWETH( + uint256 noteAmount, + uint256 wethAmount, + uint256 minBPT + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + if (noteAmount == type(uint256).max) + noteAmount = note.balanceOf(address(this)); + + if (wethAmount == type(uint256).max) + wethAmount = weth.balanceOf(address(this)); + + approve(note, address(staking), noteAmount); + approve(weth, address(staking), wethAmount); + + staking.mintFromWETH(noteAmount, wethAmount, minBPT); + + _eventName = "LogMintSNoteFromWETH(address,uint256,uint256,uint256)"; + _eventParam = abi.encode(address(this), noteAmount, wethAmount, minBPT); + } + + /// @notice Begins a cool down period for the sender + /// @dev This is required to redeem tokens + function startCoolDown() + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + staking.startCoolDown(); + + _eventName = "LogStartCoolDown(address)"; + _eventParam = abi.encode(address(this)); + } + + /// @notice Stops a cool down for the sender + /// @dev User must start another cool down period in order to call redeemSNote + function stopCoolDown() + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + staking.stopCoolDown(); + + _eventName = "LogStopCoolDown(address)"; + _eventParam = abi.encode(address(this)); + } + + /// @notice Redeems some amount of sNOTE to underlying constituent tokens (ETH and NOTE). + /// @dev An account must have passed its cool down expiration before they can redeem + /// @param sNOTEAmount amount of sNOTE to redeem + /// @param minWETH slippage protection for ETH/WETH amount + /// @param minNOTE slippage protection for NOTE amount + /// @param redeemWETH true if redeeming to WETH to ETH + function redeemSNote( + uint256 sNOTEAmount, + uint256 minWETH, + uint256 minNOTE, + bool redeemWETH + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + if (sNOTEAmount == type(uint256).max) + sNOTEAmount = staking.balanceOf(address(this)); + + staking.redeem(sNOTEAmount, minWETH, minNOTE, redeemWETH); + + _eventName = "LogRedeemSNote(address,uint256,uint256,uint256,bool)"; + _eventParam = abi.encode( + address(this), + sNOTEAmount, + minWETH, + minNOTE, + redeemWETH + ); + } + /** * @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 diff --git a/test/mainnet/notional/notional.contracts.ts b/test/mainnet/notional/notional.contracts.ts index d9f95919..54809b49 100644 --- a/test/mainnet/notional/notional.contracts.ts +++ b/test/mainnet/notional/notional.contracts.ts @@ -88,10 +88,14 @@ const NOTIONAL_CONTRACT_ABI = [ } ]; +const SNOTE_CONTRACT_ADDRESS = '0x38de42f4ba8a35056b33a746a6b45be9b1c3b9d2'; + const WETH_TOKEN_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; const DAI_TOKEN_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; const CDAI_TOKEN_ADDRESS = "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643"; const CETH_TOKEN_ADDRESS = "0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5"; +const BPT_TOKEN_ADDRESS = "0x5122E01D819E58BB2E22528c0D68D310f0AA6FD7"; +const NOTE_TOKEN_ADDRESS = "0xCFEAead4947f0705A14ec42aC3D44129E1Ef3eD5"; const ERC20_TOKEN_ABI = [ "function transfer(address _to, uint256 _value) public returns (bool success)", "function balanceOf(address account) external view returns (uint256)", @@ -101,9 +105,12 @@ const ERC20_TOKEN_ABI = [ export default { NOTIONAL_CONTRACT_ADDRESS, NOTIONAL_CONTRACT_ABI, + SNOTE_CONTRACT_ADDRESS, WETH_TOKEN_ADDRESS, + BPT_TOKEN_ADDRESS, DAI_TOKEN_ADDRESS, CDAI_TOKEN_ADDRESS, CETH_TOKEN_ADDRESS, + NOTE_TOKEN_ADDRESS, ERC20_TOKEN_ABI }; diff --git a/test/mainnet/notional/notional.helpers.ts b/test/mainnet/notional/notional.helpers.ts index 4ba8c422..ad57c8c1 100644 --- a/test/mainnet/notional/notional.helpers.ts +++ b/test/mainnet/notional/notional.helpers.ts @@ -200,6 +200,119 @@ const withdrawLend = async ( await tx.wait() }; +const mintSNoteFromETH = async ( + dsa: any, + authority: any, + referrer: any, + noteAmount: BigNumber, + ethAmount: BigNumber, + minBPT: BigNumber +) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "mintSNoteFromETH", + args: [noteAmount, ethAmount, minBPT] + } + ] + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +} + +const mintSNoteFromWETH = async ( + dsa: any, + authority: any, + referrer: any, + noteAmount: BigNumber, + wethAmount: BigNumber, + minBPT: BigNumber +) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "mintSNoteFromWETH", + args: [noteAmount, wethAmount, minBPT] + } + ] + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +} + +const mintSNoteFromBPT = async ( + dsa: any, + authority: any, + referrer: any, + bptAmount: BigNumber +) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "mintSNoteFromBPT", + args: [bptAmount] + } + ] + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +} + +const startCoolDown = async ( + dsa: any, + authority: any, + referrer: any +) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "startCoolDown", + args: [] + } + ] + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +} + +const stopCoolDown = async ( + dsa: any, + authority: any, + referrer: any +) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "stopCoolDown", + args: [] + } + ] + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +} + +const redeemSNote = async ( + dsa: any, + authority: any, + referrer: any, + sNOTEAmount: BigNumber, + minWETH: BigNumber, + minNOTE: BigNumber, + redeemWETH: boolean +) => { + const spells = [ + { + connector: "NOTIONAL-TEST-A", + method: "redeemSNote", + args: [sNOTEAmount, minWETH, minNOTE, redeemWETH] + } + ] + + const tx = await dsa.connect(authority).cast(...encodeSpells(spells), referrer.address); + await tx.wait() +} + export default { depositCollteral, depositAndMintNToken, @@ -209,5 +322,11 @@ export default { redeemNTokenRaw, redeemNTokenAndWithdraw, redeemNTokenAndDeleverage, - depositCollateralBorrowAndWithdraw + depositCollateralBorrowAndWithdraw, + mintSNoteFromETH, + mintSNoteFromWETH, + mintSNoteFromBPT, + startCoolDown, + stopCoolDown, + redeemSNote }; diff --git a/test/mainnet/notional/notional.test.ts b/test/mainnet/notional/notional.test.ts index 92c78853..304ee253 100644 --- a/test/mainnet/notional/notional.test.ts +++ b/test/mainnet/notional/notional.test.ts @@ -18,6 +18,8 @@ const DAI_WHALE = "0x6dfaf865a93d3b0b5cfd1b4db192d1505676645b"; const CDAI_WHALE = "0x33b890d6574172e93e58528cd99123a88c0756e9"; const ETH_WHALE = "0x7D24796f7dDB17d73e8B1d0A3bbD103FBA2cb2FE"; const CETH_WHALE = "0x1a1cd9c606727a7400bb2da6e4d5c70db5b4cade"; +const WETH_WHALE = "0x6555e1cc97d3cba6eaddebbcd7ca51d75771e0b8"; +const BPT_WHALE = "0x38de42f4ba8a35056b33a746a6b45be9b1c3b9d2"; const MaxUint96 = BigNumber.from("0xffffffffffffffffffffffff"); const DEPOSIT_ASSET = 1; const DEPOSIT_UNDERLYING = 2; @@ -35,12 +37,18 @@ describe("Notional", function () { let instaConnectorsV2: any; let connector: any; let notional: any; + let snote: any; let daiToken: any; let cdaiToken: any; let cethToken: any; + let wethToken: any; + let bptToken: any; + let noteToken: any; let daiWhale: any; let cdaiWhale: any; let cethWhale: any; + let wethWhale: any; + let bptWhale: any; const wallets = provider.getWallets() const [wallet0, wallet1, wallet2, wallet3] = wallets @@ -73,6 +81,14 @@ describe("Notional", function () { method: "hardhat_impersonateAccount", params: [CETH_WHALE] }) + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [WETH_WHALE] + }) + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [BPT_WHALE] + }) masterSigner = await getMasterSigner() instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); @@ -87,6 +103,11 @@ describe("Notional", function () { contracts.NOTIONAL_CONTRACT_ABI, ethers.provider ); + snote = new ethers.Contract( + contracts.SNOTE_CONTRACT_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ) daiToken = new ethers.Contract( contracts.DAI_TOKEN_ADDRESS, contracts.ERC20_TOKEN_ABI, @@ -105,7 +126,24 @@ describe("Notional", function () { ethers.provider ); cethWhale = await ethers.getSigner(CETH_WHALE); - dsaWallet0 = await buildDSAv2(wallet0.address) + wethToken = new ethers.Contract( + contracts.WETH_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ); + wethWhale = await ethers.getSigner(WETH_WHALE); + bptToken = new ethers.Contract( + contracts.BPT_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ); + bptWhale = await ethers.getSigner(BPT_WHALE); + noteToken = new ethers.Contract( + contracts.NOTE_TOKEN_ADDRESS, + contracts.ERC20_TOKEN_ABI, + ethers.provider + ) + dsaWallet0 = await buildDSAv2(wallet0.address); }); describe("Deposit Tests", function () { @@ -395,4 +433,81 @@ describe("Notional", function () { expect(after[0][3], "expect fDAI debt balance to go down after deleverage").to.be.lte(ethers.utils.parseUnits("-2000000", 0)); }); }); + + describe("Staking Tests", function () { + it("test_stake_ETH", async function () { + const depositAmount = ethers.utils.parseEther("1"); + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: depositAmount + }); + expect(await snote.balanceOf(dsaWallet0.address), "expect 0 initial sNOTE balance").to.be.equal(0); + await helpers.mintSNoteFromETH(dsaWallet0, wallet0, wallet1, BigNumber.from(0), depositAmount, BigNumber.from(0)); + expect(await snote.balanceOf(dsaWallet0.address), "expect sNOTE balance to increase").to.be.gte(ethers.utils.parseEther("297")) + }); + + it("test_stake_WETH", async function () { + const depositAmount = ethers.utils.parseEther("1"); + await wethToken.connect(wethWhale).transfer(dsaWallet0.address, depositAmount); + expect(await snote.balanceOf(dsaWallet0.address), "expect 0 initial sNOTE balance").to.be.equal(0); + await helpers.mintSNoteFromWETH(dsaWallet0, wallet0, wallet1, BigNumber.from(0), depositAmount, BigNumber.from(0)); + expect(await snote.balanceOf(dsaWallet0.address), "expect sNOTE balance to increase").to.be.gte(ethers.utils.parseEther("297")) + }); + + it("test_stake_BPT", async function () { + const depositAmount = ethers.utils.parseEther("1"); + await wallet0.sendTransaction({ + to: bptWhale.address, + value: depositAmount + }); + await bptToken.connect(bptWhale).transfer(dsaWallet0.address, depositAmount); + expect(await snote.balanceOf(dsaWallet0.address), "expect 0 initial sNOTE balance").to.be.equal(0); + await helpers.mintSNoteFromBPT(dsaWallet0, wallet0, wallet1, depositAmount); + expect(await snote.balanceOf(dsaWallet0.address), "expect sNOTE balance to increase").to.be.eq(depositAmount) + }); + + it("test_unstake_success", async function () { + const depositAmount = ethers.utils.parseEther("1"); + await wallet0.sendTransaction({ + to: bptWhale.address, + value: depositAmount + }); + await bptToken.connect(bptWhale).transfer(dsaWallet0.address, depositAmount); + await helpers.mintSNoteFromBPT(dsaWallet0, wallet0, wallet1, depositAmount); + await helpers.startCoolDown(dsaWallet0, wallet0, wallet1); + // Skip ahead 16 days + await hre.network.provider.send("evm_increaseTime", [1382400]) + await hre.network.provider.send("evm_mine") + await helpers.redeemSNote( + dsaWallet0, + wallet0, + wallet1, + ethers.constants.MaxUint256, + BigNumber.from(0), + BigNumber.from(0), + true + ); + expect(await noteToken.balanceOf(dsaWallet0.address)).to.be.gte(ethers.utils.parseUnits("50000000000", 0)); + expect(await provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseUnits("32500000000000000", 0)) + }); + + it("test_unstable_failure", async function () { + const depositAmount = ethers.utils.parseEther("1"); + await wallet0.sendTransaction({ + to: bptWhale.address, + value: depositAmount + }); + await bptToken.connect(bptWhale).transfer(dsaWallet0.address, depositAmount); + await helpers.mintSNoteFromBPT(dsaWallet0, wallet0, wallet1, depositAmount); + await expect(helpers.redeemSNote( + dsaWallet0, + wallet0, + wallet1, + ethers.constants.MaxUint256, + BigNumber.from(0), + BigNumber.from(0), + true + )).to.be.revertedWith("Not in Redemption Window"); + }); + }); }); From c04d3978da7882776d1abda477cc303204da7b9d Mon Sep 17 00:00:00 2001 From: Tianjie Wei <weitianjie@gmail.com> Date: Wed, 13 Apr 2022 21:01:59 -0700 Subject: [PATCH 20/20] Allow users to chain staking calls after claimNOTE --- contracts/mainnet/connectors/notional/main.sol | 8 ++++++-- test/mainnet/notional/notional.helpers.ts | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/mainnet/connectors/notional/main.sol b/contracts/mainnet/connectors/notional/main.sol index 575cc68f..a7b85c76 100644 --- a/contracts/mainnet/connectors/notional/main.sol +++ b/contracts/mainnet/connectors/notional/main.sol @@ -681,12 +681,14 @@ abstract contract NotionalResolver is Events, Helpers { function mintSNoteFromETH( uint256 noteAmount, uint256 ethAmount, - uint256 minBPT + uint256 minBPT, + uint256 getId ) external payable returns (string memory _eventName, bytes memory _eventParam) { + noteAmount = getUint(getId, noteAmount); if (noteAmount == type(uint256).max) noteAmount = note.balanceOf(address(this)); @@ -708,12 +710,14 @@ abstract contract NotionalResolver is Events, Helpers { function mintSNoteFromWETH( uint256 noteAmount, uint256 wethAmount, - uint256 minBPT + uint256 minBPT, + uint256 getId ) external payable returns (string memory _eventName, bytes memory _eventParam) { + noteAmount = getUint(getId, noteAmount); if (noteAmount == type(uint256).max) noteAmount = note.balanceOf(address(this)); diff --git a/test/mainnet/notional/notional.helpers.ts b/test/mainnet/notional/notional.helpers.ts index ad57c8c1..d5d04bfa 100644 --- a/test/mainnet/notional/notional.helpers.ts +++ b/test/mainnet/notional/notional.helpers.ts @@ -212,7 +212,7 @@ const mintSNoteFromETH = async ( { connector: "NOTIONAL-TEST-A", method: "mintSNoteFromETH", - args: [noteAmount, ethAmount, minBPT] + args: [noteAmount, ethAmount, minBPT, 0] } ] @@ -232,7 +232,7 @@ const mintSNoteFromWETH = async ( { connector: "NOTIONAL-TEST-A", method: "mintSNoteFromWETH", - args: [noteAmount, wethAmount, minBPT] + args: [noteAmount, wethAmount, minBPT, 0] } ]