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]
         }        
     ]