Adding tested contracts

This commit is contained in:
Tianjie Wei 2021-12-15 17:49:32 -08:00
parent 0cf73a7258
commit 6de0228439
5 changed files with 599 additions and 193 deletions

View File

@ -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);
}
}

View File

@ -1,5 +1,95 @@
pragma solidity ^0.7.6; pragma solidity ^0.7.6;
contract Events { 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);
} }

View File

@ -1,15 +1,19 @@
pragma solidity ^0.7.6; pragma solidity ^0.7.6;
pragma abicoder v2; 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 {Basic} from "../../common/basic.sol";
import {TokenInterface} from "../../common/interfaces.sol";
contract Helpers is Basic { contract Helpers is Basic {
using SafeMath for uint256;
using SafeInt256 for int256;
uint8 internal constant LEND_TRADE = 0; uint8 internal constant LEND_TRADE = 0;
uint8 internal constant BORROW_TRADE = 1; uint8 internal constant BORROW_TRADE = 1;
int256 internal constant INTERNAL_TOKEN_PRECISION = 1e8; int256 internal constant INTERNAL_TOKEN_PRECISION = 1e8;
uint256 internal constant ETH_CURRENCY_ID = 1; uint256 internal constant ETH_CURRENCY_ID = 1;
int256 private constant _INT256_MIN = type(int256).min;
NotionalInterface internal constant notional = NotionalInterface internal constant notional =
NotionalInterface(0x1344A36A1B56144C3Bc62E7757377D288fDE0369); NotionalInterface(0x1344A36A1B56144C3Bc62E7757377D288fDE0369);
@ -54,7 +58,7 @@ contract Helpers is Basic {
// end of amount and will not result in dust. // end of amount and will not result in dust.
(Token memory assetToken, ) = notional.getCurrency(currencyId); (Token memory assetToken, ) = notional.getCurrency(currencyId);
if (assetToken.decimals == INTERNAL_TOKEN_PRECISION) return amount; 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( function encodeLendTrade(
@ -81,24 +85,97 @@ contract Helpers is Basic {
(bytes32(uint256(maxBorrowRate)) << 120); (bytes32(uint256(maxBorrowRate)) << 120);
} }
function mul(int256 a, int256 b) internal pure returns (int256 c) { function getDepositAmountAndSetApproval(
c = a * b; uint256 getId,
if (a == -1) require(b == 0 || c / b == a); uint16 currencyId,
else require(a == 0 || c / a == b); 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) { function getBalance(address addr) internal returns (uint256) {
require(!(b == -1 && a == _INT256_MIN)); // dev: int256 div overflow if (addr == ethAddr) {
// NOTE: solidity will automatically revert on divide by zero return address(this).balance;
c = a / b;
} }
function sub(int256 x, int256 y) internal pure returns (int256 z) { return TokenInterface(addr).balanceOf(address(this));
// taken from uniswap v3
require((z = x - y) <= x == (y >= 0));
} }
//function getDepositAmountAndSetApproval(uint16 currencyId) internal; function getAddress(uint16 currencyId, bool useUnderlying)
internal
//function executeActionWithBalanceChange(uint16 currencyId) internal; returns (address)
{
if (currencyId == ETH_CURRENCY_ID && useUnderlying) {
return ethAddr;
}
return
useUnderlying
? getUnderlyingToken(currencyId)
: getAssetToken(currencyId);
}
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);
}
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));
}
}
} }

View File

@ -88,6 +88,13 @@ interface NotionalInterface {
uint256 lastClaimTime uint256 lastClaimTime
); );
function getfCashAmountGivenCashAmount(
uint16 currencyId,
int88 netCashToAccount,
uint256 marketIndex,
uint256 blockTime
) external view returns (int256);
function depositUnderlyingToken( function depositUnderlyingToken(
address account, address account,
uint16 currencyId, uint16 currencyId,
@ -120,7 +127,8 @@ interface NotionalInterface {
BalanceAction[] calldata actions BalanceAction[] calldata actions
) external payable; ) external payable;
function batchBalanceAndTradeAction(address account, BalanceActionWithTrades[] calldata actions) function batchBalanceAndTradeAction(
external address account,
payable; BalanceActionWithTrades[] calldata actions
) external payable;
} }

View File

@ -2,6 +2,7 @@ pragma solidity ^0.7.6;
pragma abicoder v2; pragma abicoder v2;
import {Helpers} from "./helpers.sol"; import {Helpers} from "./helpers.sol";
import {SafeInt256} from "./SafeInt256.sol";
import {Events} from "./events.sol"; import {Events} from "./events.sol";
import {DepositActionType, BalanceActionWithTrades, BalanceAction} from "./interface.sol"; import {DepositActionType, BalanceActionWithTrades, BalanceAction} from "./interface.sol";
import {TokenInterface} from "../../common/interfaces.sol"; import {TokenInterface} from "../../common/interfaces.sol";
@ -11,6 +12,7 @@ import { TokenInterface } from "../../common/interfaces.sol";
* @notice Fixed Rate Lending and Borrowing * @notice Fixed Rate Lending and Borrowing
*/ */
abstract contract NotionalResolver is Events, Helpers { abstract contract NotionalResolver is Events, Helpers {
using SafeInt256 for int256;
/** /**
* @notice Deposit collateral into Notional * @notice Deposit collateral into Notional
@ -24,29 +26,50 @@ abstract contract NotionalResolver is Events, Helpers {
function depositCollateral( function depositCollateral(
uint16 currencyId, uint16 currencyId,
bool useUnderlying, bool useUnderlying,
uint depositAmount, uint256 depositAmount,
uint getId, uint256 getId,
uint setId uint256 setId
) external payable returns (string memory _eventName, bytes memory _eventParam) { )
uint assetCashDeposited; external
address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); payable
depositAmount = getUint(getId, depositAmount); returns (string memory _eventName, bytes memory _eventParam)
if (depositAmount == uint(-1)) depositAmount = TokenInterface(tokenAddress).balanceOf(address(this)); {
depositAmount = getDepositAmountAndSetApproval(
approve(TokenInterface(tokenAddress), address(notional), depositAmount); getId,
currencyId,
useUnderlying,
depositAmount
);
uint256 assetCashDeposited;
if (useUnderlying && currencyId == ETH_CURRENCY_ID) { 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) { } else if (useUnderlying) {
assetCashDeposited = notional.depositUnderlyingToken{value: depositAmount}(address(this), currencyId, depositAmount); assetCashDeposited = notional.depositUnderlyingToken(
address(this),
currencyId,
depositAmount
);
} else { } else {
assetCashDeposited = notional.depositAssetToken(address(this), currencyId, depositAmount); assetCashDeposited = notional.depositAssetToken(
address(this),
currencyId,
depositAmount
);
} }
setUint(setId, assetCashDeposited); setUint(setId, assetCashDeposited);
_eventName = "LogDepositCollateral(uint16,bool,uint256,uint256)"; _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( function withdrawCollateral(
uint16 currencyId, uint16 currencyId,
bool redeemToUnderlying, bool redeemToUnderlying,
uint withdrawAmount, uint256 withdrawAmount,
uint getId, uint256 getId,
uint setId uint256 setId
) external returns (string memory _eventName, bytes memory _eventParam) { ) external returns (string memory _eventName, bytes memory _eventParam) {
withdrawAmount = getUint(getId, withdrawAmount); withdrawAmount = getUint(getId, withdrawAmount);
uint88 amountInternalPrecision = withdrawAmount == uint(-1) ? uint88 amountInternalPrecision = withdrawAmount == uint256(-1)
uint88(getCashBalance(currencyId)) : ? uint88(getCashBalance(currencyId))
uint88(convertToInternal(currencyId, int256(withdrawAmount))); : 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) // Sets the amount of tokens withdrawn to address(this)
setUint(setId, amountWithdrawn); setUint(setId, amountWithdrawn);
_eventName = "LogWithdrawCollateral(address,uint16,bool,uint256)"; _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 * @dev Claims NOTE tokens and transfers to the address
* @param setId the id to set the balance of NOTE tokens claimed * @param setId the id to set the balance of NOTE tokens claimed
*/ */
function claimNOTE( function claimNOTE(uint256 setId)
uint setId external
) external payable returns (string memory _eventName, bytes memory _eventParam) { payable
uint notesClaimed = notional.nTokenClaimIncentives(); returns (string memory _eventName, bytes memory _eventParam)
{
uint256 notesClaimed = notional.nTokenClaimIncentives();
setUint(setId, notesClaimed); setUint(setId, notesClaimed);
_eventName = "LogClaimNOTE(address,uint256)"; _eventName = "LogClaimNOTE(address,uint256)";
@ -108,21 +142,35 @@ abstract contract NotionalResolver is Events, Helpers {
uint16 currencyId, uint16 currencyId,
bool sellTokenAssets, bool sellTokenAssets,
uint96 tokensToRedeem, uint96 tokensToRedeem,
uint getId, uint256 getId,
uint setId uint256 setId
) external returns (string memory _eventName, bytes memory _eventParam) { ) external returns (string memory _eventName, bytes memory _eventParam) {
tokensToRedeem = uint96(getUint(getId, tokensToRedeem)); tokensToRedeem = uint96(getUint(getId, tokensToRedeem));
if (tokensToRedeem == uint96(-1)) tokensToRedeem = uint96(getNTokenBalance(currencyId)); if (tokensToRedeem == uint96(-1))
int _assetCashChange = notional.nTokenRedeem(address(this), currencyId, tokensToRedeem, sellTokenAssets); 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 // 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. // (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); setUint(setId, assetCashChange);
_eventName = "LogRedeemNTokenRaw(address,uint16,bool,uint,uint)"; _eventName = "LogRedeemNTokenRaw(address,uint16,bool,uint96,int256)";
_eventParam = abi.encode(address(this), currencyId, sellTokenAssets, tokensToRedeem, assetCashChange); _eventParam = abi.encode(
address(this),
currencyId,
sellTokenAssets,
tokensToRedeem,
assetCashChange
);
} }
/** /**
@ -139,49 +187,43 @@ abstract contract NotionalResolver is Events, Helpers {
*/ */
function redeemNTokenAndWithdraw( function redeemNTokenAndWithdraw(
uint16 currencyId, uint16 currencyId,
uint tokensToRedeem, uint96 tokensToRedeem,
uint amountToWithdraw, uint256 amountToWithdraw,
bool redeemToUnderlying, bool redeemToUnderlying,
uint getId, uint256 getId,
uint setId uint256 setId
) external returns (string memory _eventName, bytes memory _eventParam) { ) external returns (string memory _eventName, bytes memory _eventParam) {
tokensToRedeem = getUint(getId, tokensToRedeem); tokensToRedeem = uint96(getUint(getId, uint256(tokensToRedeem)));
if (tokensToRedeem == uint(-1)) tokensToRedeem = uint(getNTokenBalance(currencyId)); if (tokensToRedeem == uint96(-1))
tokensToRedeem = uint96(getNTokenBalance(currencyId));
BalanceAction[] memory action = new BalanceAction[](1); BalanceAction[] memory action = new BalanceAction[](1);
action[0].actionType = DepositActionType.RedeemNToken; action[0].actionType = DepositActionType.RedeemNToken;
action[0].currencyId = currencyId; action[0].currencyId = currencyId;
action[0].depositActionAmount = tokensToRedeem; action[0].depositActionAmount = tokensToRedeem;
action[0].redeemToUnderlying = redeemToUnderlying; action[0].redeemToUnderlying = redeemToUnderlying;
if (amountToWithdraw == uint(-1)) { if (amountToWithdraw == uint256(-1)) {
action[0].withdrawEntireCashBalance = true; action[0].withdrawEntireCashBalance = true;
} else { } else {
action[0].withdrawAmountInternalPrecision = amountToWithdraw; action[0].withdrawAmountInternalPrecision = amountToWithdraw;
} }
uint balanceBefore; executeActionWithBalanceChange(
address tokenAddress; action,
if (setId != 0) { 0,
// Only run this if we are going to use the balance change currencyId,
address tokenAddress = redeemToUnderlying ? redeemToUnderlying,
getUnderlyingToken(currencyId) : setId
getAssetToken(currencyId); );
// TODO: handle ETH _eventName = "LogRedeemNTokenWithdraw(address,uint16,uint96,uint256,bool)";
balanceBefore = TokenInterface(tokenAddress).balanceOf(address(this)); _eventParam = abi.encode(
} address(this),
currencyId,
notional.batchBalanceAction(address(this), action); tokensToRedeem,
amountToWithdraw,
if (setId != 0) { redeemToUnderlying
// 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);
} }
/** /**
@ -203,13 +245,15 @@ abstract contract NotionalResolver is Events, Helpers {
uint8 marketIndex, uint8 marketIndex,
uint88 fCashAmount, uint88 fCashAmount,
uint32 minLendRate, uint32 minLendRate,
uint getId uint256 getId
) external returns (string memory _eventName, bytes memory _eventParam) { ) external returns (string memory _eventName, bytes memory _eventParam) {
tokensToRedeem = uint96(getUint(getId, tokensToRedeem)); tokensToRedeem = uint96(getUint(getId, tokensToRedeem));
if (tokensToRedeem == uint96(-1)) tokensToRedeem = uint96(getNTokenBalance(currencyId)); if (tokensToRedeem == uint96(-1))
notional.nTokenRedeem(address(this), currencyId, tokensToRedeem, true); tokensToRedeem = uint96(getNTokenBalance(currencyId));
BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](1); BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](
1
);
action[0].actionType = DepositActionType.RedeemNToken; action[0].actionType = DepositActionType.RedeemNToken;
action[0].currencyId = currencyId; action[0].currencyId = currencyId;
action[0].depositActionAmount = tokensToRedeem; action[0].depositActionAmount = tokensToRedeem;
@ -221,11 +265,16 @@ abstract contract NotionalResolver is Events, Helpers {
notional.batchBalanceAndTradeAction(address(this), action); notional.batchBalanceAndTradeAction(address(this), action);
_eventName = "LogRedeemNTokenAndDeleverage(address,uint16,uint,uint8,uint)"; _eventName = "LogRedeemNTokenAndDeleverage(address,uint16,uint96,uint8,uint88)";
_eventParam = abi.encode(address(this), currencyId, tokensToRedeem, marketIndex, fCashAmount); _eventParam = abi.encode(
address(this),
currencyId,
tokensToRedeem,
marketIndex,
fCashAmount
);
} }
/** /**
* @notice Deposit asset or underlying tokens and mint nTokens in a single transaction * @notice Deposit asset or underlying tokens and mint nTokens in a single transaction
* @param currencyId notional defined currency id to deposit * @param currencyId notional defined currency id to deposit
@ -237,46 +286,70 @@ abstract contract NotionalResolver is Events, Helpers {
*/ */
function depositAndMintNToken( function depositAndMintNToken(
uint16 currencyId, uint16 currencyId,
uint depositAmount, uint256 depositAmount,
bool useUnderlying, bool useUnderlying,
uint getId, uint256 getId,
uint setId uint256 setId
) external payable returns (string memory _eventName, bytes memory _eventParam) { )
address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); external
depositAmount = getUint(getId, depositAmount); payable
if (depositAmount == uint(-1)) depositAmount = TokenInterface(tokenAddress).balanceOf(address(this)); 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); 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].currencyId = currencyId;
action[0].depositActionAmount = depositAmount; action[0].depositActionAmount = depositAmount;
// withdraw amount, withdraw cash and redeem to underlying are all 0 and false // withdraw amount, withdraw cash and redeem to underlying are all 0 and false
int256 nTokenBefore; int256 nTokenBefore = getNTokenBalance(currencyId);
if (setId != 0) {
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); notional.batchBalanceAction{value: msgValue}(address(this), action);
int256 nTokenBalanceChange = getNTokenBalance(currencyId).sub(
nTokenBefore
);
if (setId != 0) { if (setId != 0) {
// Set the amount of nTokens minted // 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( function mintNTokenFromCash(
uint16 currencyId, uint16 currencyId,
uint cashBalanceToMint, uint256 cashBalanceToMint,
uint getId, uint256 getId,
uint setId uint256 setId
) external payable returns (string memory _eventName, bytes memory _eventParam) { )
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
cashBalanceToMint = getUint(getId, cashBalanceToMint); 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); BalanceAction[] memory action = new BalanceAction[](1);
action[0].actionType = DepositActionType.ConvertCashToNToken; action[0].actionType = DepositActionType.ConvertCashToNToken;
@ -284,73 +357,97 @@ abstract contract NotionalResolver is Events, Helpers {
action[0].depositActionAmount = cashBalanceToMint; action[0].depositActionAmount = cashBalanceToMint;
// NOTE: withdraw amount, withdraw cash and redeem to underlying are all 0 and false // NOTE: withdraw amount, withdraw cash and redeem to underlying are all 0 and false
int256 nTokenBefore; int256 nTokenBefore = getNTokenBalance(currencyId);
if (setId != 0) {
nTokenBefore = getNTokenBalance(currencyId);
}
notional.batchBalanceAction(address(this), action); notional.batchBalanceAction(address(this), action);
int256 nTokenBalanceChange = getNTokenBalance(currencyId).sub(
nTokenBefore
);
if (setId != 0) { if (setId != 0) {
// Set the amount of nTokens minted // 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( function depositAndLend(
uint16 currencyId, uint16 currencyId,
uint depositAmount, uint256 depositAmount,
bool useUnderlying, bool useUnderlying,
uint8 marketIndex, uint8 marketIndex,
uint88 fCashAmount, uint88 fCashAmount,
uint32 minLendRate, uint32 minLendRate,
uint getId uint256 getId
) external payable returns (string memory _eventName, bytes memory _eventParam) { )
address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); external
depositAmount = getUint(getId, depositAmount); payable
if (depositAmount == uint(-1)) depositAmount = TokenInterface(tokenAddress).balanceOf(address(this)); returns (string memory _eventName, bytes memory _eventParam)
{
depositAmount = getDepositAmountAndSetApproval(
getId,
currencyId,
useUnderlying,
depositAmount
);
approve(TokenInterface(tokenAddress), address(notional), depositAmount); BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](
BalanceActionWithTrades[] memory action = new BalanceActionWithTrades[](1); 1
action[0].actionType = useUnderlying ? DepositActionType.DepositUnderlying : DepositActionType.DepositAsset; );
action[0].actionType = useUnderlying
? DepositActionType.DepositUnderlying
: DepositActionType.DepositAsset;
action[0].currencyId = currencyId; action[0].currencyId = currencyId;
action[0].depositActionAmount = depositAmount; action[0].depositActionAmount = depositAmount;
// Withdraw any residual cash from lending back to the token that was used // Withdraw any residual cash from lending back to the token that was used
action[0].withdrawEntireCashBalance = true; action[0].withdrawEntireCashBalance = true;
// TODO: will redeem underlying work with ETH?
action[0].redeemToUnderlying = useUnderlying; action[0].redeemToUnderlying = useUnderlying;
bytes32[] memory trades = new bytes32[](1); bytes32[] memory trades = new bytes32[](1);
trades[0] = encodeLendTrade(marketIndex, fCashAmount, minLendRate); trades[0] = encodeLendTrade(marketIndex, fCashAmount, minLendRate);
action[0].trades = trades; action[0].trades = trades;
uint msgValue = currencyId == ETH_CURRENCY_ID ? depositAmount : 0; uint256 msgValue = 0;
notional.batchBalanceAndTradeAction{value: msgValue}(address(this), action); 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, uint16 depositCurrencyId,
bool useUnderlying, bool useUnderlying,
uint depositAmount, uint256 depositAmount,
uint16 borrowCurrencyId, uint16 borrowCurrencyId,
uint8 marketIndex, uint8 marketIndex,
uint88 fCashAmount, uint88 fCashAmount,
uint32 maxBorrowRate, uint32 maxBorrowRate,
bool redeemToUnderlying, bool redeemToUnderlying
uint getId, ) internal returns (BalanceActionWithTrades[] memory action) {
uint setId BalanceActionWithTrades[]
) external payable returns (string memory _eventName, bytes memory _eventParam) { memory actions = new BalanceActionWithTrades[](2);
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);
uint256 depositIndex; uint256 depositIndex;
uint256 borrowIndex; uint256 borrowIndex;
@ -362,36 +459,85 @@ abstract contract NotionalResolver is Events, Helpers {
borrowIndex = 0; borrowIndex = 0;
} }
action[depositIndex].actionType = useUnderlying ? DepositActionType.DepositUnderlying : DepositActionType.DepositAsset; actions[depositIndex].actionType = useUnderlying
action[depositIndex].currencyId = depositCurrencyId; ? DepositActionType.DepositUnderlying
action[depositIndex].depositActionAmount = depositAmount; : DepositActionType.DepositAsset;
uint msgValue = depositCurrencyId == ETH_CURRENCY_ID ? depositAmount : 0; actions[depositIndex].currencyId = depositCurrencyId;
actions[depositIndex].depositActionAmount = depositAmount;
action[borrowIndex].actionType = DepositActionType.None; actions[borrowIndex].actionType = DepositActionType.None;
action[borrowIndex].currencyId = borrowCurrencyId; actions[borrowIndex].currencyId = borrowCurrencyId;
// Withdraw borrowed amount to wallet // Withdraw borrowed amount to wallet
action[borrowIndex].withdrawEntireCashBalance = true; actions[borrowIndex].withdrawEntireCashBalance = true;
// TODO: will redeem underlying work with ETH? actions[borrowIndex].redeemToUnderlying = redeemToUnderlying;
action[borrowIndex].redeemToUnderlying = useUnderlying;
bytes32[] memory trades = new bytes32[](1); bytes32[] memory trades = new bytes32[](1);
trades[borrowIndex] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate);
action[borrowIndex].trades = trades; actions[borrowIndex].trades = trades;
address borrowToken; return actions;
uint balanceBefore;
if (setId != 0) {
address borrowToken = useUnderlying ? getUnderlyingToken(borrowCurrencyId) : getAssetToken(borrowCurrencyId);
balanceBefore = TokenInterface(borrowToken).balanceOf(address(this));
} }
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) { depositAmount = getDepositAmountAndSetApproval(
setUint(setId, sub(TokenInterface(borrowToken).balanceOf(address(this)), balanceBefore)); 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( function withdrawLend(
@ -399,44 +545,96 @@ abstract contract NotionalResolver is Events, Helpers {
uint8 marketIndex, uint8 marketIndex,
uint88 fCashAmount, uint88 fCashAmount,
uint32 maxBorrowRate, uint32 maxBorrowRate,
uint getId, uint256 setId
uint setId )
) external payable returns (string memory _eventName, bytes memory _eventParam) { external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
bool useUnderlying = currencyId != ETH_CURRENCY_ID; 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].actionType = DepositActionType.None;
action[0].currencyId = currencyId; action[0].currencyId = currencyId;
// Withdraw borrowed amount to wallet // Withdraw borrowed amount to wallet
action[0].withdrawEntireCashBalance = true; action[0].withdrawEntireCashBalance = true;
// TODO: will redeem underlying work with ETH?
action[0].redeemToUnderlying = useUnderlying; action[0].redeemToUnderlying = useUnderlying;
bytes32[] memory trades = new bytes32[](1); bytes32[] memory trades = new bytes32[](1);
trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate); trades[0] = encodeBorrowTrade(marketIndex, fCashAmount, maxBorrowRate);
action[0].trades = trades; action[0].trades = trades;
address tokenAddress; executeTradeActionWithBalanceChange(
uint balanceBefore; action,
if (setId != 0) { 0,
address tokenAddress = useUnderlying ? getUnderlyingToken(currencyId) : getAssetToken(currencyId); currencyId,
balanceBefore = TokenInterface(tokenAddress).balanceOf(address(this)); useUnderlying,
} setId
notional.batchBalanceAndTradeAction(address(this), action); );
if (setId != 0) { _eventName = "LogWithdrawLend(address,uint16,uint8,uint88,uint32)";
setUint(setId, sub(TokenInterface(tokenAddress).balanceOf(address(this)), balanceBefore)); _eventParam = abi.encode(
} address(this),
currencyId,
marketIndex,
fCashAmount,
maxBorrowRate
);
} }
function repayBorrow( function repayBorrow(
uint16 currencyId, uint16 currencyId,
uint8 marketIndex, uint8 marketIndex,
uint fCashAmount, int88 netCashToAccount,
uint minLendRate, uint32 minLendRate,
uint getId, uint256 setId
uint setId )
) external payable returns (string memory _eventName, bytes memory _eventParam) { external
// might want to use getfCashAmountGivenCashAmount 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. * 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 * @param actions a set of BatchActionWithTrades that will be executed for this account
*/ */
function batchActionRaw( function batchActionRaw(BalanceActionWithTrades[] memory actions)
BalanceActionWithTrades[] memory actions external
) external payable returns (string memory _eventName, bytes memory _eventParam) { payable
returns (string memory _eventName, bytes memory _eventParam)
{
notional.batchBalanceAndTradeAction(address(this), actions); notional.batchBalanceAndTradeAction(address(this), actions);
// todo: events _eventName = "LogBatchActionRaw(address)";
_eventParam = abi.encode(address(this));
} }
} }
contract ConnectV2Notional is NotionalResolver {
string public name = "Notional-v1.1";
}