From 6de02284393cbf101e764d1a623a0ef4229624cf Mon Sep 17 00:00:00 2001 From: Tianjie Wei Date: Wed, 15 Dec 2021 17:49:32 -0800 Subject: [PATCH] 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"; +}