@0x/asset-proxy: Add more functions to IDydx.

`@0x/dev-utils`: Fix all the weird dydx base unit madness.
This commit is contained in:
Lawrence Forman
2020-02-06 01:01:03 -05:00
parent 162b6f1a74
commit 651e94bd94
8 changed files with 159 additions and 79 deletions

View File

@@ -33,6 +33,12 @@ import "./D18.sol";
library LibDydxBalance {
using LibBytes for bytes;
using LibSafeMath for uint256;
/// @dev Padding % added to the minimum collateralization ratio to
/// prevent withdrawing exactly the amount that would make an account
/// insolvent. 1 bps.
int256 private constant MARGIN_RATIO_PADDING = 0.0001e18;
/// @dev Structure that holds all pertinent info needed to perform a balance
/// check.
@@ -42,7 +48,7 @@ library LibDydxBalance {
address makerAddress;
address makerTokenAddress;
address takerTokenAddress;
int256 orderTakerToMakerRate;
int256 orderMakerToTakerRate;
uint256[] accounts;
IDydxBridge.BridgeAction[] actions;
}
@@ -65,10 +71,10 @@ library LibDydxBalance {
if (!_areActionsWellFormed(info)) {
return 0;
}
// If the rate we withdraw maker tokens is less than the order conversion
// rate , the asset proxy will throw because we will always transfer
// less maker tokens than asked.
if (_getMakerTokenWithdrawRate(info) < info.orderTakerToMakerRate) {
// If the rate we withdraw maker tokens is less than one, the asset
// proxy will throw because we will always transfer less maker tokens
// than asked.
if (_getMakerTokenWithdrawRate(info) < D18.one()) {
return 0;
}
// The maker balance is the smaller of:
@@ -154,7 +160,6 @@ library LibDydxBalance {
returns (uint256 depositableMakerAmount)
{
depositableMakerAmount = uint256(-1);
int256 orderMakerToTakerRate = D18.div(D18.one(), info.orderTakerToMakerRate);
// Take the minimum maker amount from all deposits.
for (uint256 i = 0; i < info.actions.length; ++i) {
IDydxBridge.BridgeAction memory action = info.actions[i];
@@ -162,13 +167,15 @@ library LibDydxBalance {
if (action.actionType != IDydxBridge.BridgeActionType.Deposit) {
continue;
}
// `depositRate` is the rate at which we convert a maker token into
// a taker token for deposit.
int256 depositRate = _getActionRate(action);
// Taker tokens will be transferred to the maker for every fill, so
// we reduce the effective deposit rate if we're depositing the taker
// token.
address depositToken = info.dydx.getMarketTokenAddress(action.marketId);
if (info.takerTokenAddress != address(0) && depositToken == info.takerTokenAddress) {
depositRate = D18.sub(depositRate, orderMakerToTakerRate);
depositRate = D18.sub(depositRate, info.orderMakerToTakerRate);
}
// If the deposit rate is > 0, we are limited by the transferrable
// token balance of the maker.
@@ -198,11 +205,11 @@ library LibDydxBalance {
assert(info.actions.length >= 1);
IDydxBridge.BridgeAction memory withdraw = info.actions[info.actions.length - 1];
assert(withdraw.actionType == IDydxBridge.BridgeActionType.Withdraw);
int256 minCr = _getMinimumCollateralizationRatio(info.dydx);
int256 minCr = D18.add(_getMinimumCollateralizationRatio(info.dydx), MARGIN_RATIO_PADDING);
// Loop through the accounts.
for (uint256 accountIdx = 0; accountIdx < info.accounts.length; ++accountIdx) {
(uint256 supplyValue, uint256 borrowValue) =
_getAccountValues(info, info.accounts[accountIdx]);
_getAccountMarketValues(info, info.accounts[accountIdx]);
// All accounts must currently be solvent.
if (borrowValue != 0 && D18.div(supplyValue, borrowValue) < minCr) {
return 0;
@@ -221,20 +228,18 @@ library LibDydxBalance {
if (deposit.accountIdx == accountIdx) {
dd = D18.add(
dd,
_toQuoteValue(
info.dydx,
deposit.marketId,
_getActionRate(deposit)
_getActionRateValue(
info,
deposit
)
);
}
}
// Compute the borrow/withdraw rate, which is the rate at which
// (USD) value is deducted from the account.
int256 db = _toQuoteValue(
info.dydx,
withdraw.marketId,
_getActionRate(withdraw)
int256 db = _getActionRateValue(
info,
withdraw
);
// If the deposit to withdraw ratio is >= the minimum collateralization
// ratio, then we will never become insolvent at these prices.
@@ -252,7 +257,8 @@ library LibDydxBalance {
);
solventMakerAmount = LibSafeMath.min256(
solventMakerAmount,
uint256(D18.clip(t))
// `t` is in maker token units, so convert it to maker wei.
_toWei(info.makerTokenAddress, uint256(D18.clip(t)))
);
}
}
@@ -275,14 +281,14 @@ library LibDydxBalance {
(, info.takerTokenAddress) =
LibAssetData.decodeERC20AssetData(order.takerAssetData);
}
info.orderTakerToMakerRate = D18.div(order.makerAssetAmount, order.takerAssetAmount);
info.orderMakerToTakerRate = D18.div(order.takerAssetAmount, order.makerAssetAmount);
(IDydxBridge.BridgeData memory bridgeData) =
abi.decode(rawBridgeData, (IDydxBridge.BridgeData));
info.accounts = bridgeData.accountNumbers;
info.actions = bridgeData.actions;
}
/// @dev Returns the conversion rate for an action, treating infinites as 1.
/// @dev Returns the conversion rate for an action.
/// @param action A `BridgeAction`.
function _getActionRate(IDydxBridge.BridgeAction memory action)
private
@@ -297,6 +303,59 @@ library LibDydxBalance {
);
}
/// @dev Returns the USD value of an action based on its conversion rate
/// and market prices.
/// @param info State from `_getBalanceCheckInfo()`.
/// @param action A `BridgeAction`.
function _getActionRateValue(
BalanceCheckInfo memory info,
IDydxBridge.BridgeAction memory action
)
private
view
returns (int256 value)
{
address toToken = info.dydx.getMarketTokenAddress(action.marketId);
uint256 fromTokenDecimals = LibERC20Token.decimals(info.makerTokenAddress);
uint256 toTokenDecimals = LibERC20Token.decimals(toToken);
// First express the rate as 18-decimal units.
value = toTokenDecimals > fromTokenDecimals
? int256(
uint256(_getActionRate(action))
.safeDiv(10 ** (toTokenDecimals - fromTokenDecimals))
)
: int256(
uint256(_getActionRate(action))
.safeMul(10 ** (fromTokenDecimals - toTokenDecimals))
);
// Prices have 18 + (18 - TOKEN_DECIMALS) decimal places because
// consistency is stupid.
uint256 price = info.dydx.getMarketPrice(action.marketId).value;
// Make prices have 18 decimals.
if (toTokenDecimals > 18) {
price = price.safeMul(10 ** (toTokenDecimals - 18));
} else {
price = price.safeDiv(10 ** (18 - toTokenDecimals));
}
// The action value is the action rate times the price.
value = D18.mul(price, value);
}
/// @dev Returns the conversion rate for an action, expressed as units
/// of the market token.
/// @param token Address the of the token.
/// @param units Token units expressed with 18 digit precision.
function _toWei(address token, uint256 units)
private
view
returns (uint256 rate)
{
uint256 decimals = LibERC20Token.decimals(token);
rate = decimals > 18
? units.safeMul(10 ** (decimals - 18))
: units.safeDiv(10 ** (18 - decimals));
}
/// @dev Get the global minimum collateralization ratio required for
/// an account to be considered solvent.
/// @param dydx The Dydx interface.
@@ -309,24 +368,10 @@ library LibDydxBalance {
return D18.add(D18.one(), D18.toSigned(riskParams.marginRatio.value));
}
/// @dev Get the quote (USD) value of a rate within a market.
/// @param dydx The Dydx interface.
/// @param marketId Dydx market ID.
/// @param rate Rate to scale by price.
function _toQuoteValue(IDydx dydx, uint256 marketId, int256 rate)
private
view
returns (int256 quotedRate)
{
IDydx.Price memory price = dydx.getMarketPrice(marketId);
uint8 tokenDecimals = LibERC20Token.decimals(dydx.getMarketTokenAddress(marketId));
return D18.mul(D18.div(price.value, 10 ** uint256(tokenDecimals)), rate);
}
/// @dev Get the total supply and borrow values for an account across all markets.
/// @param info State from `_getBalanceCheckInfo()`.
/// @param account The Dydx account identifier.
function _getAccountValues(BalanceCheckInfo memory info, uint256 account)
function _getAccountMarketValues(BalanceCheckInfo memory info, uint256 account)
private
view
returns (uint256 supplyValue, uint256 borrowValue)
@@ -336,7 +381,9 @@ library LibDydxBalance {
info.makerAddress,
account
));
return (supplyValue_.value, borrowValue_.value);
// Account values have 36 decimal places because dydx likes to make sure
// you're paying attention.
return (supplyValue_.value / 1e18, borrowValue_.value / 1e18);
}
/// @dev Get the amount of an ERC20 token held by `owner` that can be transferred

View File

@@ -106,16 +106,6 @@ contract TestDydx {
});
}
function getMarketPrice(
uint256 marketId
)
external
view
returns (IDydx.Price memory price)
{
return IDydx.Price(_markets[marketId].price);
}
function getAdjustedAccountValues(
IDydx.AccountInfo calldata account
)
@@ -124,11 +114,13 @@ contract TestDydx {
returns (IDydx.Value memory supplyValue, IDydx.Value memory borrowValue)
{
for (uint256 marketId = 0; marketId < _markets.length; ++marketId) {
MarketInfo memory market = _markets[marketId];
int256 balance =
_balance[_getBalanceHash(account.owner, account.number, marketId)];
uint256 decimals = LibERC20Token.decimals(market.token);
balance = balance * int256(market.price) / int256(10 ** decimals);
// Account values have 36 decimal places.
// `getMarketPrice()` returns a unit with
// 18 + (18 - TOKEN_DECIMALS) decimal places so multiplying the price
// with the wei balance will result in a 36 decimal value.
balance = balance * int256(getMarketPrice(marketId).value);
if (balance >= 0) {
supplyValue.value += uint256(balance);
} else {
@@ -137,6 +129,24 @@ contract TestDydx {
}
}
function getMarketPrice(
uint256 marketId
)
public
view
returns (IDydx.Price memory price)
{
MarketInfo memory market = _markets[marketId];
uint256 decimals = LibERC20Token.decimals(market.token);
price.value = _markets[marketId].price;
// Market prices have 18 + (18 - TOKEN_DECIMALS)
if (decimals > 18) {
price.value /= 10 ** (decimals - 18);
} else {
price.value *= 10 ** (18 - decimals);
}
}
function _getOperatorHash(address owner, address operator)
private
pure