From 6bbfa348f0262e4187de7ac6e2928c3fabb11284 Mon Sep 17 00:00:00 2001 From: Tianjie Wei Date: Mon, 29 Nov 2021 12:38:43 -0800 Subject: [PATCH] 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 }