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