Cherry pick dydx validation from #2456
This commit is contained in:
@@ -18,6 +18,10 @@
|
||||
{
|
||||
"note": "Remove `LibTransactionDecoder` export",
|
||||
"pr": 2464
|
||||
},
|
||||
{
|
||||
"note": "Add `DydxBridge` order validation",
|
||||
"pr": 2466
|
||||
}
|
||||
],
|
||||
"timestamp": 1581204851
|
||||
|
||||
@@ -34,15 +34,18 @@ contract Addresses is
|
||||
address public erc1155ProxyAddress;
|
||||
address public staticCallProxyAddress;
|
||||
address public chaiBridgeAddress;
|
||||
address public dydxBridgeAddress;
|
||||
|
||||
constructor (
|
||||
address exchange_,
|
||||
address chaiBridge_
|
||||
address chaiBridge_,
|
||||
address dydxBridge_
|
||||
)
|
||||
public
|
||||
{
|
||||
exchangeAddress = exchange_;
|
||||
chaiBridgeAddress = chaiBridge_;
|
||||
dydxBridgeAddress = dydxBridge_;
|
||||
erc20ProxyAddress = IExchange(exchange_).getAssetProxy(IAssetData(address(0)).ERC20Token.selector);
|
||||
erc721ProxyAddress = IExchange(exchange_).getAssetProxy(IAssetData(address(0)).ERC721Token.selector);
|
||||
erc1155ProxyAddress = IExchange(exchange_).getAssetProxy(IAssetData(address(0)).ERC1155Assets.selector);
|
||||
|
||||
@@ -28,7 +28,7 @@ import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IChai.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "./Addresses.sol";
|
||||
import "./LibAssetData.sol";
|
||||
import "./LibDydxBalance.sol";
|
||||
|
||||
|
||||
contract AssetBalance is
|
||||
@@ -274,6 +274,9 @@ contract AssetBalance is
|
||||
uint256 chaiAllowance = LibERC20Token.allowance(_getChaiAddress(), ownerAddress, chaiBridgeAddress);
|
||||
// Dai allowance is unlimited if Chai allowance is unlimited
|
||||
allowance = chaiAllowance == _MAX_UINT256 ? _MAX_UINT256 : _convertChaiToDaiAmount(chaiAllowance);
|
||||
} else if (bridgeAddress == dydxBridgeAddress) {
|
||||
// Dydx bridges always have infinite allowance.
|
||||
allowance = _MAX_UINT256;
|
||||
}
|
||||
// Allowance will be 0 if bridge is not supported
|
||||
}
|
||||
@@ -366,6 +369,17 @@ contract AssetBalance is
|
||||
if (order.makerAssetData.length < 4) {
|
||||
return (0, 0);
|
||||
}
|
||||
bytes4 assetProxyId = order.makerAssetData.readBytes4(0);
|
||||
// Handle dydx bridge assets.
|
||||
if (assetProxyId == IAssetData(address(0)).ERC20Bridge.selector) {
|
||||
(, , address bridgeAddress, ) = LibAssetData.decodeERC20BridgeAssetData(order.makerAssetData);
|
||||
if (bridgeAddress == dydxBridgeAddress) {
|
||||
return (
|
||||
LibDydxBalance.getDydxMakerBalance(order, dydxBridgeAddress),
|
||||
_MAX_UINT256
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
getBalance(order.makerAddress, order.makerAssetData),
|
||||
getAssetProxyAllowance(order.makerAddress, order.makerAssetData)
|
||||
|
||||
@@ -40,12 +40,14 @@ contract DevUtils is
|
||||
{
|
||||
constructor (
|
||||
address exchange_,
|
||||
address chaiBridge_
|
||||
address chaiBridge_,
|
||||
address dydxBridge_
|
||||
)
|
||||
public
|
||||
Addresses(
|
||||
exchange_,
|
||||
chaiBridge_
|
||||
chaiBridge_,
|
||||
dydxBridge_
|
||||
)
|
||||
LibEIP712ExchangeDomain(uint256(0), address(0)) // null args because because we only use constants
|
||||
{}
|
||||
|
||||
483
contracts/dev-utils/contracts/src/LibDydxBalance.sol
Normal file
483
contracts/dev-utils/contracts/src/LibDydxBalance.sol
Normal file
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IDydxBridge.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IDydx.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibFractions.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "./LibAssetData.sol";
|
||||
|
||||
|
||||
// solhint-disable separate-by-one-line-in-contract
|
||||
library LibDydxBalance {
|
||||
|
||||
using LibBytes for bytes;
|
||||
using LibSafeMath for uint256;
|
||||
|
||||
/// @dev Decimal places for dydx value quantities.
|
||||
uint256 private constant DYDX_UNITS_DECIMALS = 18;
|
||||
/// @dev Base units for dydx value quantities.
|
||||
uint256 private constant DYDX_UNITS_BASE = 10 ** DYDX_UNITS_DECIMALS;
|
||||
|
||||
/// @dev A fraction/rate.
|
||||
struct Fraction {
|
||||
uint256 n;
|
||||
uint256 d;
|
||||
}
|
||||
|
||||
/// @dev Structure that holds all pertinent info needed to perform a balance
|
||||
/// check.
|
||||
struct BalanceCheckInfo {
|
||||
IDydx dydx;
|
||||
address bridgeAddress;
|
||||
address makerAddress;
|
||||
address makerTokenAddress;
|
||||
address takerTokenAddress;
|
||||
uint256 makerAssetAmount;
|
||||
uint256 takerAssetAmount;
|
||||
uint256[] accounts;
|
||||
IDydxBridge.BridgeAction[] actions;
|
||||
}
|
||||
|
||||
/// @dev Get the maker asset balance of an order with a `DydxBridge` maker asset.
|
||||
/// @param order An order with a dydx maker asset.
|
||||
/// @param dydx The address of the dydx contract.
|
||||
/// @return balance The maker asset balance.
|
||||
function getDydxMakerBalance(LibOrder.Order memory order, address dydx)
|
||||
public
|
||||
view
|
||||
returns (uint256 balance)
|
||||
{
|
||||
BalanceCheckInfo memory info = _getBalanceCheckInfo(order, dydx);
|
||||
// The Dydx bridge must be an operator for the maker.
|
||||
if (!info.dydx.getIsLocalOperator(info.makerAddress, info.bridgeAddress)) {
|
||||
return 0;
|
||||
}
|
||||
// Actions must be well-formed.
|
||||
if (!_areActionsWellFormed(info)) {
|
||||
return 0;
|
||||
}
|
||||
// If the rate we withdraw maker tokens is < 1, the asset proxy will
|
||||
// throw because we will always transfer less maker tokens than asked.
|
||||
if (_ltf(_getMakerTokenWithdrawRate(info), Fraction(1, 1))) {
|
||||
return 0;
|
||||
}
|
||||
// The maker balance is the smaller of:
|
||||
return LibSafeMath.min256(
|
||||
// How many times we can execute all the deposit actions.
|
||||
_getDepositableMakerAmount(info),
|
||||
// How many times we can execute all the actions before the an
|
||||
// account becomes undercollateralized.
|
||||
_getSolventMakerAmount(info)
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Checks that:
|
||||
/// 1. Actions are arranged as [...deposits, withdraw].
|
||||
/// 2. There is only one deposit for each market ID.
|
||||
/// 3. Every action has a valid account index.
|
||||
/// 4. There is exactly one withdraw at the end and it is for the
|
||||
/// maker token.
|
||||
/// @param info State from `_getBalanceCheckInfo()`.
|
||||
/// @return areWellFormed Whether the actions are well-formed.
|
||||
function _areActionsWellFormed(BalanceCheckInfo memory info)
|
||||
internal
|
||||
view
|
||||
returns (bool areWellFormed)
|
||||
{
|
||||
if (info.actions.length == 0) {
|
||||
return false;
|
||||
}
|
||||
uint256 depositCount = 0;
|
||||
// Count the number of deposits.
|
||||
for (; depositCount < info.actions.length; ++depositCount) {
|
||||
IDydxBridge.BridgeAction memory action = info.actions[depositCount];
|
||||
if (action.actionType != IDydxBridge.BridgeActionType.Deposit) {
|
||||
break;
|
||||
}
|
||||
// Search all prior actions for the same market ID.
|
||||
uint256 marketId = action.marketId;
|
||||
for (uint256 j = 0; j < depositCount; ++j) {
|
||||
if (info.actions[j].marketId == marketId) {
|
||||
// Market ID is not unique.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check that the account index is within the valid range.
|
||||
if (action.accountIdx >= info.accounts.length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// There must be exactly one withdraw action at the end.
|
||||
if (depositCount + 1 != info.actions.length) {
|
||||
return false;
|
||||
}
|
||||
IDydxBridge.BridgeAction memory withdraw = info.actions[depositCount];
|
||||
if (withdraw.actionType != IDydxBridge.BridgeActionType.Withdraw) {
|
||||
return false;
|
||||
}
|
||||
// And it must be for the maker token.
|
||||
if (info.dydx.getMarketTokenAddress(withdraw.marketId) != info.makerTokenAddress) {
|
||||
return false;
|
||||
}
|
||||
// Check the account index.
|
||||
return withdraw.accountIdx < info.accounts.length;
|
||||
}
|
||||
|
||||
/// @dev Returns the rate at which we withdraw maker tokens.
|
||||
/// @param info State from `_getBalanceCheckInfo()`.
|
||||
/// @return makerTokenWithdrawRate Maker token withdraw rate.
|
||||
function _getMakerTokenWithdrawRate(BalanceCheckInfo memory info)
|
||||
internal
|
||||
pure
|
||||
returns (Fraction memory makerTokenWithdrawRate)
|
||||
{
|
||||
// The last action is always a withdraw for the maker token.
|
||||
IDydxBridge.BridgeAction memory withdraw = info.actions[info.actions.length - 1];
|
||||
return _getActionRate(withdraw);
|
||||
}
|
||||
|
||||
/// @dev Get how much maker asset we can transfer before a deposit fails.
|
||||
/// @param info State from `_getBalanceCheckInfo()`.
|
||||
function _getDepositableMakerAmount(BalanceCheckInfo memory info)
|
||||
internal
|
||||
view
|
||||
returns (uint256 depositableMakerAmount)
|
||||
{
|
||||
depositableMakerAmount = uint256(-1);
|
||||
// The conversion rate from maker -> taker.
|
||||
Fraction memory makerToTakerRate = Fraction(
|
||||
info.takerAssetAmount,
|
||||
info.makerAssetAmount
|
||||
);
|
||||
// Take the minimum maker amount from all deposits.
|
||||
for (uint256 i = 0; i < info.actions.length; ++i) {
|
||||
IDydxBridge.BridgeAction memory action = info.actions[i];
|
||||
// Only looking at deposit actions.
|
||||
if (action.actionType != IDydxBridge.BridgeActionType.Deposit) {
|
||||
continue;
|
||||
}
|
||||
Fraction memory 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 = max(0, depositRate - makerToTakerRate)`
|
||||
if (_ltf(makerToTakerRate, depositRate)) {
|
||||
depositRate = _subf(depositRate, makerToTakerRate);
|
||||
} else {
|
||||
depositRate = Fraction(0, 1);
|
||||
}
|
||||
}
|
||||
// If the deposit rate is > 0, we are limited by the transferrable
|
||||
// token balance of the maker.
|
||||
if (_gtf(depositRate, Fraction(0, 1))) {
|
||||
uint256 supply = _getTransferabeTokenAmount(
|
||||
depositToken,
|
||||
info.makerAddress,
|
||||
address(info.dydx)
|
||||
);
|
||||
depositableMakerAmount = LibSafeMath.min256(
|
||||
depositableMakerAmount,
|
||||
LibMath.getPartialAmountFloor(
|
||||
depositRate.d,
|
||||
depositRate.n,
|
||||
supply
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Get how much maker asset we can transfer before an account
|
||||
/// becomes insolvent.
|
||||
/// @param info State from `_getBalanceCheckInfo()`.
|
||||
function _getSolventMakerAmount(BalanceCheckInfo memory info)
|
||||
internal
|
||||
view
|
||||
returns (uint256 solventMakerAmount)
|
||||
{
|
||||
solventMakerAmount = uint256(-1);
|
||||
assert(info.actions.length >= 1);
|
||||
IDydxBridge.BridgeAction memory withdraw = info.actions[info.actions.length - 1];
|
||||
assert(withdraw.actionType == IDydxBridge.BridgeActionType.Withdraw);
|
||||
Fraction memory minCr = _getMinimumCollateralizationRatio(info.dydx);
|
||||
// CR < 1 will cause math underflows.
|
||||
require(_gtef(minCr, Fraction(1, 1)), "DevUtils/MIN_CR_MUST_BE_GTE_ONE");
|
||||
// Loop through the accounts.
|
||||
for (uint256 accountIdx = 0; accountIdx < info.accounts.length; ++accountIdx) {
|
||||
(uint256 supplyValue, uint256 borrowValue) =
|
||||
_getAccountValues(info, info.accounts[accountIdx]);
|
||||
// All accounts must currently be solvent.
|
||||
if (borrowValue != 0 && _ltf(Fraction(supplyValue, borrowValue), minCr)) {
|
||||
return 0;
|
||||
}
|
||||
// If this is the same account used to in the withdraw/borrow action,
|
||||
// compute the maker amount at which it will become insolvent.
|
||||
if (accountIdx != withdraw.accountIdx) {
|
||||
continue;
|
||||
}
|
||||
// Compute the deposit/collateralization rate, which is the rate at
|
||||
// which (USD) value is added to the account across all markets.
|
||||
Fraction memory dd = Fraction(0, 1);
|
||||
for (uint256 i = 0; i < info.actions.length - 1; ++i) {
|
||||
IDydxBridge.BridgeAction memory deposit = info.actions[i];
|
||||
assert(deposit.actionType == IDydxBridge.BridgeActionType.Deposit);
|
||||
if (deposit.accountIdx == accountIdx) {
|
||||
dd = _addf(
|
||||
dd,
|
||||
_toQuoteValue(
|
||||
info.dydx,
|
||||
deposit.marketId,
|
||||
_getActionRate(deposit)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
// Compute the borrow/withdraw rate, which is the rate at which
|
||||
// (USD) value is deducted from the account.
|
||||
Fraction memory db = _toQuoteValue(
|
||||
info.dydx,
|
||||
withdraw.marketId,
|
||||
_getActionRate(withdraw)
|
||||
);
|
||||
// If the deposit to withdraw ratio is >= the minimum collateralization
|
||||
// rate, then we will never become insolvent at these prices.
|
||||
if (_gtef(_divf(dd, db), minCr)) {
|
||||
continue;
|
||||
}
|
||||
// The collateralization ratio for this account, parameterized by
|
||||
// `t` (maker amount), is given by:
|
||||
// `cr = (supplyValue + t * dd) / (borrowValue + t * db)`
|
||||
// Solving for `t` gives us:
|
||||
// `t = (supplyValue - cr * borrowValue) / (cr * db - dd)`
|
||||
// TODO(dorothy-zbornak): It'll also revert when getting extremely
|
||||
// close to the minimum collateralization ratio.
|
||||
Fraction memory t = _divf(
|
||||
_subf(
|
||||
Fraction(supplyValue, DYDX_UNITS_BASE),
|
||||
_mulf(minCr, Fraction(borrowValue, DYDX_UNITS_BASE))
|
||||
),
|
||||
_subf(_mulf(minCr, db), dd)
|
||||
);
|
||||
solventMakerAmount = LibSafeMath.min256(
|
||||
solventMakerAmount,
|
||||
t.n.safeDiv(t.d)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Create a `BalanceCheckInfo` struct.
|
||||
/// @param order An order with a `DydxBridge` maker asset.
|
||||
/// @param dydx The address of the Dydx contract.
|
||||
/// @return info The `BalanceCheckInfo` struct.
|
||||
function _getBalanceCheckInfo(LibOrder.Order memory order, address dydx)
|
||||
private
|
||||
pure
|
||||
returns (BalanceCheckInfo memory info)
|
||||
{
|
||||
bytes memory rawBridgeData;
|
||||
(, info.makerTokenAddress, info.bridgeAddress, rawBridgeData) =
|
||||
LibAssetData.decodeERC20BridgeAssetData(order.makerAssetData);
|
||||
info.dydx = IDydx(dydx);
|
||||
info.makerAddress = order.makerAddress;
|
||||
if (order.takerAssetData.readBytes4(0) == IAssetData(0).ERC20Token.selector) {
|
||||
(, info.takerTokenAddress) =
|
||||
LibAssetData.decodeERC20AssetData(order.takerAssetData);
|
||||
}
|
||||
info.makerAssetAmount = order.makerAssetAmount;
|
||||
info.takerAssetAmount = order.takerAssetAmount;
|
||||
(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.
|
||||
/// @param action A `BridgeAction`.
|
||||
function _getActionRate(IDydxBridge.BridgeAction memory action)
|
||||
private
|
||||
pure
|
||||
returns (Fraction memory rate)
|
||||
{
|
||||
rate = action.conversionRateDenominator == 0
|
||||
? Fraction(1, 1)
|
||||
: _normalizef(
|
||||
Fraction(
|
||||
action.conversionRateNumerator,
|
||||
action.conversionRateDenominator
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Get the global minimum collateralization ratio required for
|
||||
/// an account to be considered solvent.
|
||||
/// @param dydx The Dydx interface.
|
||||
function _getMinimumCollateralizationRatio(IDydx dydx)
|
||||
private
|
||||
view
|
||||
returns (Fraction memory ratio)
|
||||
{
|
||||
IDydx.RiskParams memory riskParams = dydx.getRiskParams();
|
||||
return _normalizef(
|
||||
Fraction(
|
||||
riskParams.marginRatio.value,
|
||||
DYDX_UNITS_BASE
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// @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, Fraction memory rate)
|
||||
private
|
||||
view
|
||||
returns (Fraction memory quotedRate)
|
||||
{
|
||||
IDydx.Price memory price = dydx.getMarketPrice(marketId);
|
||||
uint8 tokenDecimals = LibERC20Token.decimals(dydx.getMarketTokenAddress(marketId));
|
||||
return _mulf(
|
||||
Fraction(
|
||||
price.value,
|
||||
10 ** uint256(DYDX_UNITS_DECIMALS + 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)
|
||||
private
|
||||
view
|
||||
returns (uint256 supplyValue, uint256 borrowValue)
|
||||
{
|
||||
(IDydx.Value memory supplyValue_, IDydx.Value memory borrowValue_) =
|
||||
info.dydx.getAdjustedAccountValues(IDydx.AccountInfo(
|
||||
info.makerAddress,
|
||||
account
|
||||
));
|
||||
return (supplyValue_.value, borrowValue_.value);
|
||||
}
|
||||
|
||||
/// @dev Get the amount of an ERC20 token held by `owner` that can be transferred
|
||||
/// by `spender`.
|
||||
/// @param tokenAddress The address of the ERC20 token.
|
||||
/// @param owner The address of the token holder.
|
||||
/// @param spender The address of the token spender.
|
||||
function _getTransferabeTokenAmount(
|
||||
address tokenAddress,
|
||||
address owner,
|
||||
address spender
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 transferableAmount)
|
||||
{
|
||||
return LibSafeMath.min256(
|
||||
LibERC20Token.allowance(tokenAddress, owner, spender),
|
||||
LibERC20Token.balanceOf(tokenAddress, owner)
|
||||
);
|
||||
}
|
||||
|
||||
/*** Fraction helpers ***/
|
||||
|
||||
/// @dev Check if `a < b`.
|
||||
function _ltf(Fraction memory a, Fraction memory b)
|
||||
private
|
||||
pure
|
||||
returns (bool isLessThan)
|
||||
{
|
||||
return LibFractions.cmp(a.n, a.d, b.n, b.d) == -1;
|
||||
}
|
||||
|
||||
/// @dev Check if `a > b`.
|
||||
function _gtf(Fraction memory a, Fraction memory b)
|
||||
private
|
||||
pure
|
||||
returns (bool isGreaterThan)
|
||||
{
|
||||
return LibFractions.cmp(a.n, a.d, b.n, b.d) == 1;
|
||||
}
|
||||
|
||||
/// @dev Check if `a >= b`.
|
||||
function _gtef(Fraction memory a, Fraction memory b)
|
||||
private
|
||||
pure
|
||||
returns (bool isGreaterThanOrEqual)
|
||||
{
|
||||
return !_ltf(a, b);
|
||||
}
|
||||
|
||||
/// @dev Compute `a + b`.
|
||||
function _addf(Fraction memory a, Fraction memory b)
|
||||
private
|
||||
pure
|
||||
returns (Fraction memory r)
|
||||
{
|
||||
(r.n, r.d) = LibFractions.add(a.n, a.d, b.n, b.d);
|
||||
}
|
||||
|
||||
/// @dev Compute `a - b`.
|
||||
function _subf(Fraction memory a, Fraction memory b)
|
||||
private
|
||||
pure
|
||||
returns (Fraction memory r)
|
||||
{
|
||||
(r.n, r.d) = LibFractions.sub(a.n, a.d, b.n, b.d);
|
||||
}
|
||||
|
||||
/// @dev Compute `a * b`.
|
||||
function _mulf(Fraction memory a, Fraction memory b)
|
||||
private
|
||||
pure
|
||||
returns (Fraction memory r)
|
||||
{
|
||||
(r.n, r.d) = LibFractions.mul(a.n, a.d, b.n, b.d);
|
||||
}
|
||||
|
||||
/// @dev Compute `a / b`.
|
||||
function _divf(Fraction memory a, Fraction memory b)
|
||||
private
|
||||
pure
|
||||
returns (Fraction memory r)
|
||||
{
|
||||
(r.n, r.d) = LibFractions.mul(a.n, a.d, b.d, b.n);
|
||||
}
|
||||
|
||||
/// @dev Normalize a fraction to prevent arithmetic overflows.
|
||||
function _normalizef(Fraction memory f)
|
||||
private
|
||||
pure
|
||||
returns (Fraction memory r)
|
||||
{
|
||||
(r.n, r.d) = LibFractions.normalize(f.n, f.d);
|
||||
}
|
||||
}
|
||||
162
contracts/dev-utils/contracts/test/TestDydx.sol
Normal file
162
contracts/dev-utils/contracts/test/TestDydx.sol
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IDydx.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||
|
||||
|
||||
// solhint-disable separate-by-one-line-in-contract
|
||||
contract TestDydx {
|
||||
|
||||
struct OperatorConfig {
|
||||
address owner;
|
||||
address operator;
|
||||
}
|
||||
|
||||
struct AccountConfig {
|
||||
address owner;
|
||||
uint256 accountId;
|
||||
int256[] balances;
|
||||
}
|
||||
|
||||
struct MarketInfo {
|
||||
address token;
|
||||
uint256 price;
|
||||
}
|
||||
|
||||
struct TestConfig {
|
||||
uint256 marginRatio;
|
||||
OperatorConfig[] operators;
|
||||
AccountConfig[] accounts;
|
||||
MarketInfo[] markets;
|
||||
}
|
||||
|
||||
mapping (bytes32 => bool) private _operators;
|
||||
mapping (bytes32 => int256) private _balance;
|
||||
MarketInfo[] private _markets;
|
||||
uint256 private _marginRatio;
|
||||
|
||||
constructor(TestConfig memory config) public {
|
||||
_marginRatio = config.marginRatio;
|
||||
for (uint256 marketId = 0; marketId < config.markets.length; ++marketId) {
|
||||
_markets.push(config.markets[marketId]);
|
||||
}
|
||||
for (uint256 i = 0; i < config.operators.length; ++i) {
|
||||
OperatorConfig memory op = config.operators[i];
|
||||
_operators[_getOperatorHash(op.owner, op.operator)] = true;
|
||||
}
|
||||
for (uint256 i = 0; i < config.accounts.length; ++i) {
|
||||
AccountConfig memory acct = config.accounts[i];
|
||||
for (uint256 marketId = 0; marketId < acct.balances.length; ++marketId) {
|
||||
_balance[_getBalanceHash(acct.owner, acct.accountId, marketId)] =
|
||||
acct.balances[marketId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getIsLocalOperator(
|
||||
address owner,
|
||||
address operator
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (bool isLocalOperator)
|
||||
{
|
||||
return _operators[_getOperatorHash(owner, operator)];
|
||||
}
|
||||
|
||||
function getMarketTokenAddress(
|
||||
uint256 marketId
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (address tokenAddress)
|
||||
{
|
||||
return _markets[marketId].token;
|
||||
}
|
||||
|
||||
function getRiskParams()
|
||||
external
|
||||
view
|
||||
returns (IDydx.RiskParams memory riskParams)
|
||||
{
|
||||
return IDydx.RiskParams({
|
||||
marginRatio: IDydx.D256(_marginRatio),
|
||||
liquidationSpread: IDydx.D256(0),
|
||||
earningsRate: IDydx.D256(0),
|
||||
minBorrowedValue: IDydx.Value(0)
|
||||
});
|
||||
}
|
||||
|
||||
function getMarketPrice(
|
||||
uint256 marketId
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (IDydx.Price memory price)
|
||||
{
|
||||
return IDydx.Price(_markets[marketId].price);
|
||||
}
|
||||
|
||||
function getAdjustedAccountValues(
|
||||
IDydx.AccountInfo calldata account
|
||||
)
|
||||
external
|
||||
view
|
||||
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);
|
||||
if (balance >= 0) {
|
||||
supplyValue.value += uint256(balance);
|
||||
} else {
|
||||
borrowValue.value += uint256(-balance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _getOperatorHash(address owner, address operator)
|
||||
private
|
||||
pure
|
||||
returns (bytes32 operatorHash)
|
||||
{
|
||||
return keccak256(abi.encode(
|
||||
owner,
|
||||
operator
|
||||
));
|
||||
}
|
||||
|
||||
function _getBalanceHash(address owner, uint256 accountId, uint256 marketId)
|
||||
private
|
||||
pure
|
||||
returns (bytes32 balanceHash)
|
||||
{
|
||||
return keccak256(abi.encode(
|
||||
owner,
|
||||
accountId,
|
||||
marketId
|
||||
));
|
||||
}
|
||||
}
|
||||
116
contracts/dev-utils/contracts/test/TestLibDydxBalance.sol
Normal file
116
contracts/dev-utils/contracts/test/TestLibDydxBalance.sol
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.16;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../src/LibDydxBalance.sol";
|
||||
|
||||
|
||||
contract TestLibDydxBalanceToken {
|
||||
|
||||
uint8 public decimals;
|
||||
mapping (address => uint256) public balanceOf;
|
||||
mapping (address => mapping (address => uint256)) public allowance;
|
||||
|
||||
constructor(uint8 decimals_) public {
|
||||
decimals = decimals_;
|
||||
}
|
||||
|
||||
function setBalance(address owner, uint256 balance) external {
|
||||
balanceOf[owner] = balance;
|
||||
}
|
||||
|
||||
function setApproval(
|
||||
address owner,
|
||||
address spender,
|
||||
uint256 allowance_
|
||||
)
|
||||
external
|
||||
{
|
||||
allowance[owner][spender] = allowance_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contract TestLibDydxBalance {
|
||||
|
||||
mapping (address => TestLibDydxBalanceToken) private tokens;
|
||||
|
||||
function createToken(uint8 decimals) external returns (address) {
|
||||
TestLibDydxBalanceToken token = new TestLibDydxBalanceToken(decimals);
|
||||
return address(tokens[address(token)] = token);
|
||||
}
|
||||
|
||||
function setTokenBalance(
|
||||
address tokenAddress,
|
||||
address owner,
|
||||
uint256 balance
|
||||
)
|
||||
external
|
||||
{
|
||||
tokens[tokenAddress].setBalance(owner, balance);
|
||||
}
|
||||
|
||||
function setTokenApproval(
|
||||
address tokenAddress,
|
||||
address owner,
|
||||
address spender,
|
||||
uint256 allowance
|
||||
)
|
||||
external
|
||||
{
|
||||
tokens[tokenAddress].setApproval(owner, spender, allowance);
|
||||
}
|
||||
|
||||
function getDydxMakerBalance(LibOrder.Order memory order, address dydx)
|
||||
public
|
||||
view
|
||||
returns (uint256 balance)
|
||||
{
|
||||
return LibDydxBalance.getDydxMakerBalance(order, dydx);
|
||||
}
|
||||
|
||||
function getSolventMakerAmount(
|
||||
LibDydxBalance.BalanceCheckInfo memory info
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256 solventMakerAmount)
|
||||
{
|
||||
return LibDydxBalance._getSolventMakerAmount(info);
|
||||
}
|
||||
|
||||
function getDepositableMakerAmount(
|
||||
LibDydxBalance.BalanceCheckInfo memory info
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256 depositableMakerAmount)
|
||||
{
|
||||
return LibDydxBalance._getDepositableMakerAmount(info);
|
||||
}
|
||||
|
||||
function areActionsWellFormed(LibDydxBalance.BalanceCheckInfo memory info)
|
||||
public
|
||||
view
|
||||
returns (bool areWellFormed)
|
||||
{
|
||||
return LibDydxBalance._areActionsWellFormed(info);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"main": "lib/src/index.js",
|
||||
"scripts": {
|
||||
"build": "yarn pre_build && tsc -b",
|
||||
"test": "yarn assert_deployable",
|
||||
"test": "yarn assert_deployable && yarn mocha -t 10000 -b ./lib/test/**_test.js",
|
||||
"assert_deployable": "node -e \"const bytecodeLen = (require('./generated-artifacts/DevUtils.json').compilerOutput.evm.bytecode.object.length-2)/2; assert(bytecodeLen<=0x6000,'DevUtils contract is too big to deploy, per EIP-170. '+bytecodeLen+'>'+0x6000)\"",
|
||||
"build:ci": "yarn build",
|
||||
"pre_build": "run-s compile quantify_bytecode contracts:gen generate_contract_wrappers contracts:copy",
|
||||
@@ -27,8 +27,8 @@
|
||||
"docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "DevUtils,LibAssetData,LibOrderTransferSimulation,LibTransactionDecoder",
|
||||
"abis": "./test/generated-artifacts/@(Addresses|AssetBalance|DevUtils|EthBalanceChecker|ExternalFunctions|LibAssetData|LibOrderTransferSimulation|LibTransactionDecoder|OrderTransferSimulationUtils|OrderValidationUtils).json",
|
||||
"publicInterfaceContracts": "DevUtils,LibAssetData,LibDydxBalance,LibOrderTransferSimulation,LibTransactionDecoder",
|
||||
"abis": "./test/generated-artifacts/@(Addresses|AssetBalance|DevUtils|EthBalanceChecker|ExternalFunctions|LibAssetData|LibDydxBalance|LibOrderTransferSimulation|LibTransactionDecoder|OrderTransferSimulationUtils|OrderValidationUtils|TestDydx|TestLibDydxBalance).json",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -7,11 +7,13 @@ import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as DevUtils from '../generated-artifacts/DevUtils.json';
|
||||
import * as LibAssetData from '../generated-artifacts/LibAssetData.json';
|
||||
import * as LibDydxBalance from '../generated-artifacts/LibDydxBalance.json';
|
||||
import * as LibOrderTransferSimulation from '../generated-artifacts/LibOrderTransferSimulation.json';
|
||||
import * as LibTransactionDecoder from '../generated-artifacts/LibTransactionDecoder.json';
|
||||
export const artifacts = {
|
||||
DevUtils: DevUtils as ContractArtifact,
|
||||
LibAssetData: LibAssetData as ContractArtifact,
|
||||
LibDydxBalance: LibDydxBalance as ContractArtifact,
|
||||
LibOrderTransferSimulation: LibOrderTransferSimulation as ContractArtifact,
|
||||
LibTransactionDecoder: LibTransactionDecoder as ContractArtifact,
|
||||
};
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
*/
|
||||
export * from '../generated-wrappers/dev_utils';
|
||||
export * from '../generated-wrappers/lib_asset_data';
|
||||
export * from '../generated-wrappers/lib_dydx_balance';
|
||||
export * from '../generated-wrappers/lib_order_transfer_simulation';
|
||||
export * from '../generated-wrappers/lib_transaction_decoder';
|
||||
|
||||
@@ -11,10 +11,13 @@ import * as DevUtils from '../test/generated-artifacts/DevUtils.json';
|
||||
import * as EthBalanceChecker from '../test/generated-artifacts/EthBalanceChecker.json';
|
||||
import * as ExternalFunctions from '../test/generated-artifacts/ExternalFunctions.json';
|
||||
import * as LibAssetData from '../test/generated-artifacts/LibAssetData.json';
|
||||
import * as LibDydxBalance from '../test/generated-artifacts/LibDydxBalance.json';
|
||||
import * as LibOrderTransferSimulation from '../test/generated-artifacts/LibOrderTransferSimulation.json';
|
||||
import * as LibTransactionDecoder from '../test/generated-artifacts/LibTransactionDecoder.json';
|
||||
import * as OrderTransferSimulationUtils from '../test/generated-artifacts/OrderTransferSimulationUtils.json';
|
||||
import * as OrderValidationUtils from '../test/generated-artifacts/OrderValidationUtils.json';
|
||||
import * as TestDydx from '../test/generated-artifacts/TestDydx.json';
|
||||
import * as TestLibDydxBalance from '../test/generated-artifacts/TestLibDydxBalance.json';
|
||||
export const artifacts = {
|
||||
Addresses: Addresses as ContractArtifact,
|
||||
AssetBalance: AssetBalance as ContractArtifact,
|
||||
@@ -22,8 +25,11 @@ export const artifacts = {
|
||||
EthBalanceChecker: EthBalanceChecker as ContractArtifact,
|
||||
ExternalFunctions: ExternalFunctions as ContractArtifact,
|
||||
LibAssetData: LibAssetData as ContractArtifact,
|
||||
LibDydxBalance: LibDydxBalance as ContractArtifact,
|
||||
LibOrderTransferSimulation: LibOrderTransferSimulation as ContractArtifact,
|
||||
LibTransactionDecoder: LibTransactionDecoder as ContractArtifact,
|
||||
OrderTransferSimulationUtils: OrderTransferSimulationUtils as ContractArtifact,
|
||||
OrderValidationUtils: OrderValidationUtils as ContractArtifact,
|
||||
TestDydx: TestDydx as ContractArtifact,
|
||||
TestLibDydxBalance: TestLibDydxBalance as ContractArtifact,
|
||||
};
|
||||
|
||||
1166
contracts/dev-utils/test/lib_dydx_balance_test.ts
Normal file
1166
contracts/dev-utils/test/lib_dydx_balance_test.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,10 @@ export * from '../test/generated-wrappers/dev_utils';
|
||||
export * from '../test/generated-wrappers/eth_balance_checker';
|
||||
export * from '../test/generated-wrappers/external_functions';
|
||||
export * from '../test/generated-wrappers/lib_asset_data';
|
||||
export * from '../test/generated-wrappers/lib_dydx_balance';
|
||||
export * from '../test/generated-wrappers/lib_order_transfer_simulation';
|
||||
export * from '../test/generated-wrappers/lib_transaction_decoder';
|
||||
export * from '../test/generated-wrappers/order_transfer_simulation_utils';
|
||||
export * from '../test/generated-wrappers/order_validation_utils';
|
||||
export * from '../test/generated-wrappers/test_dydx';
|
||||
export * from '../test/generated-wrappers/test_lib_dydx_balance';
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"files": [
|
||||
"generated-artifacts/DevUtils.json",
|
||||
"generated-artifacts/LibAssetData.json",
|
||||
"generated-artifacts/LibDydxBalance.json",
|
||||
"generated-artifacts/LibOrderTransferSimulation.json",
|
||||
"generated-artifacts/LibTransactionDecoder.json",
|
||||
"test/generated-artifacts/Addresses.json",
|
||||
@@ -13,10 +14,13 @@
|
||||
"test/generated-artifacts/EthBalanceChecker.json",
|
||||
"test/generated-artifacts/ExternalFunctions.json",
|
||||
"test/generated-artifacts/LibAssetData.json",
|
||||
"test/generated-artifacts/LibDydxBalance.json",
|
||||
"test/generated-artifacts/LibOrderTransferSimulation.json",
|
||||
"test/generated-artifacts/LibTransactionDecoder.json",
|
||||
"test/generated-artifacts/OrderTransferSimulationUtils.json",
|
||||
"test/generated-artifacts/OrderValidationUtils.json"
|
||||
"test/generated-artifacts/OrderValidationUtils.json",
|
||||
"test/generated-artifacts/TestDydx.json",
|
||||
"test/generated-artifacts/TestLibDydxBalance.json"
|
||||
],
|
||||
"exclude": ["./deploy/solc/solc_bin"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user