Cherry pick dydx validation from #2456
This commit is contained in:
parent
d89243a0d3
commit
9b3781abf1
@ -11,6 +11,14 @@
|
||||
{
|
||||
"version": "3.2.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add more types and functions to `IDydx`",
|
||||
"pr": 2466
|
||||
},
|
||||
{
|
||||
"note": "Rename `DydxBrigeAction.accountId` to `DydxBridgeAction.accountIdx`",
|
||||
"pr": 2466
|
||||
},
|
||||
{
|
||||
"note": "Fix broken tests.",
|
||||
"pr": 2462
|
||||
|
@ -191,7 +191,7 @@ contract DydxBridge is
|
||||
depositAction = IDydx.ActionArgs({
|
||||
actionType: IDydx.ActionType.Deposit, // deposit tokens.
|
||||
amount: dydxAmount, // amount to deposit.
|
||||
accountId: bridgeAction.accountId, // index in the `accounts` when calling `operate`.
|
||||
accountIdx: bridgeAction.accountIdx, // index in the `accounts` when calling `operate`.
|
||||
primaryMarketId: bridgeAction.marketId, // indicates which token to deposit.
|
||||
otherAddress: depositFrom, // deposit from the account owner.
|
||||
// unused parameters
|
||||
@ -229,7 +229,7 @@ contract DydxBridge is
|
||||
withdrawAction = IDydx.ActionArgs({
|
||||
actionType: IDydx.ActionType.Withdraw, // withdraw tokens.
|
||||
amount: amountToWithdraw, // amount to withdraw.
|
||||
accountId: bridgeAction.accountId, // index in the `accounts` when calling `operate`.
|
||||
accountIdx: bridgeAction.accountIdx, // index in the `accounts` when calling `operate`.
|
||||
primaryMarketId: bridgeAction.marketId, // indicates which token to withdraw.
|
||||
otherAddress: withdrawTo, // withdraw tokens to this address.
|
||||
// unused parameters
|
||||
|
@ -45,7 +45,7 @@ interface IDydx {
|
||||
/// parsed into before being processed.
|
||||
struct ActionArgs {
|
||||
ActionType actionType;
|
||||
uint256 accountId;
|
||||
uint256 accountIdx;
|
||||
AssetAmount amount;
|
||||
uint256 primaryMarketId;
|
||||
uint256 secondaryMarketId;
|
||||
@ -71,6 +71,31 @@ interface IDydx {
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
struct D256 {
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
struct Value {
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
struct Price {
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
/// @dev The global risk parameters that govern the health and security of the system
|
||||
struct RiskParams {
|
||||
// Required ratio of over-collateralization
|
||||
D256 marginRatio;
|
||||
// Percentage penalty incurred by liquidated accounts
|
||||
D256 liquidationSpread;
|
||||
// Percentage of the borrower's interest fee that gets passed to the suppliers
|
||||
D256 earningsRate;
|
||||
// The minimum absolute borrow value of an account
|
||||
// There must be sufficient incentivize to liquidate undercollateralized accounts
|
||||
Value minBorrowedValue;
|
||||
}
|
||||
|
||||
/// @dev The main entry-point to Solo that allows users and contracts to manage accounts.
|
||||
/// Take one or more actions on one or more accounts. The msg.sender must be the owner or
|
||||
/// operator of all accounts except for those being liquidated, vaporized, or traded with.
|
||||
@ -86,4 +111,59 @@ interface IDydx {
|
||||
ActionArgs[] calldata actions
|
||||
)
|
||||
external;
|
||||
|
||||
/// @dev Return true if a particular address is approved as an operator for an owner's accounts.
|
||||
/// Approved operators can act on the accounts of the owner as if it were the operator's own.
|
||||
/// @param owner The owner of the accounts
|
||||
/// @param operator The possible operator
|
||||
/// @return isLocalOperator True if operator is approved for owner's accounts
|
||||
function getIsLocalOperator(
|
||||
address owner,
|
||||
address operator
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (bool isLocalOperator);
|
||||
|
||||
/// @dev Get the ERC20 token address for a market.
|
||||
/// @param marketId The market to query
|
||||
/// @return tokenAddress The token address
|
||||
function getMarketTokenAddress(
|
||||
uint256 marketId
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (address tokenAddress);
|
||||
|
||||
/// @dev Get all risk parameters in a single struct.
|
||||
/// @return riskParams All global risk parameters
|
||||
function getRiskParams()
|
||||
external
|
||||
view
|
||||
returns (RiskParams memory riskParams);
|
||||
|
||||
/// @dev Get the price of the token for a market.
|
||||
/// @param marketId The market to query
|
||||
/// @return price The price of each atomic unit of the token
|
||||
function getMarketPrice(
|
||||
uint256 marketId
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (Price memory price);
|
||||
|
||||
/// @dev Get the total supplied and total borrowed values of an account adjusted by the marginPremium
|
||||
/// of each market. Supplied values are divided by (1 + marginPremium) for each market and
|
||||
/// borrowed values are multiplied by (1 + marginPremium) for each market. Comparing these
|
||||
/// adjusted values gives the margin-ratio of the account which will be compared to the global
|
||||
/// margin-ratio when determining if the account can be liquidated.
|
||||
/// @param account The account to query
|
||||
/// @return supplyValue The supplied value of the account (adjusted for marginPremium)
|
||||
/// @return borrowValue The borrowed value of the account (adjusted for marginPremium)
|
||||
function getAdjustedAccountValues(
|
||||
AccountInfo calldata account
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (Value memory supplyValue, Value memory borrowValue);
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ interface IDydxBridge {
|
||||
|
||||
struct BridgeAction {
|
||||
BridgeActionType actionType; // Action to run on dydx account.
|
||||
uint256 accountId; // Index in `BridgeData.accountNumbers` for this action.
|
||||
uint256 accountIdx; // Index in `BridgeData.accountNumbers` for this action.
|
||||
uint256 marketId; // Market to operate on.
|
||||
uint256 conversionRateNumerator; // Optional. If set, transfer amount is scaled by (conversionRateNumerator/conversionRateDenominator).
|
||||
uint256 conversionRateDenominator; // Optional. If set, transfer amount is scaled by (conversionRateNumerator/conversionRateDenominator).
|
||||
|
@ -23,6 +23,7 @@ import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
||||
import "../src/bridges/DydxBridge.sol";
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
contract TestDydxBridgeToken {
|
||||
|
||||
uint256 private constant INIT_HOLDER_BALANCE = 10 * 10**18; // 10 tokens
|
||||
@ -79,7 +80,7 @@ contract TestDydxBridge is
|
||||
|
||||
event OperateAction(
|
||||
ActionType actionType,
|
||||
uint256 accountId,
|
||||
uint256 accountIdx,
|
||||
bool amountSign,
|
||||
AssetDenomination amountDenomination,
|
||||
AssetReference amountRef,
|
||||
@ -120,7 +121,7 @@ contract TestDydxBridge is
|
||||
for (uint i = 0; i < actions.length; ++i) {
|
||||
emit OperateAction(
|
||||
actions[i].actionType,
|
||||
actions[i].accountId,
|
||||
actions[i].accountIdx,
|
||||
actions[i].amount.sign,
|
||||
actions[i].amount.denomination,
|
||||
actions[i].amount.ref,
|
||||
@ -171,6 +172,50 @@ contract TestDydxBridge is
|
||||
return _testTokenAddress;
|
||||
}
|
||||
|
||||
/// @dev Unused.
|
||||
function getIsLocalOperator(
|
||||
address owner,
|
||||
address operator
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (bool isLocalOperator)
|
||||
{}
|
||||
|
||||
/// @dev Unused.
|
||||
function getMarketTokenAddress(
|
||||
uint256 marketId
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (address tokenAddress)
|
||||
{}
|
||||
|
||||
/// @dev Unused.
|
||||
function getRiskParams()
|
||||
external
|
||||
view
|
||||
returns (RiskParams memory riskParams)
|
||||
{}
|
||||
|
||||
/// @dev Unsused.
|
||||
function getMarketPrice(
|
||||
uint256 marketId
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (Price memory price)
|
||||
{}
|
||||
|
||||
/// @dev Unused.
|
||||
function getAdjustedAccountValues(
|
||||
AccountInfo calldata account
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (Value memory supplyValue, Value memory borrowValue)
|
||||
{}
|
||||
|
||||
/// @dev overrides `_getDydxAddress()` from `DeploymentConstants` to return this address.
|
||||
function _getDydxAddress()
|
||||
internal
|
||||
|
@ -7,7 +7,7 @@ export enum DydxBridgeActionType {
|
||||
|
||||
export interface DydxBridgeAction {
|
||||
actionType: DydxBridgeActionType;
|
||||
accountId: BigNumber;
|
||||
accountIdx: BigNumber;
|
||||
marketId: BigNumber;
|
||||
conversionRateNumerator: BigNumber;
|
||||
conversionRateDenominator: BigNumber;
|
||||
@ -29,7 +29,7 @@ export const dydxBridgeDataEncoder = AbiEncoder.create([
|
||||
type: 'tuple[]',
|
||||
components: [
|
||||
{ name: 'actionType', type: 'uint8' },
|
||||
{ name: 'accountId', type: 'uint256' },
|
||||
{ name: 'accountIdx', type: 'uint256' },
|
||||
{ name: 'marketId', type: 'uint256' },
|
||||
{ name: 'conversionRateNumerator', type: 'uint256' },
|
||||
{ name: 'conversionRateDenominator', type: 'uint256' },
|
||||
|
@ -17,14 +17,14 @@ blockchainTests.resets('DydxBridge unit tests', env => {
|
||||
const notAuthorized = '0x0000000000000000000000000000000000000001';
|
||||
const defaultDepositAction = {
|
||||
actionType: DydxBridgeActionType.Deposit,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
accountIdx: constants.ZERO_AMOUNT,
|
||||
marketId,
|
||||
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||
};
|
||||
const defaultWithdrawAction = {
|
||||
actionType: DydxBridgeActionType.Withdraw,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
accountIdx: constants.ZERO_AMOUNT,
|
||||
marketId,
|
||||
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||
@ -118,7 +118,7 @@ blockchainTests.resets('DydxBridge unit tests', env => {
|
||||
for (const action of bridgeData.actions) {
|
||||
expectedOperateActionEvents.push({
|
||||
actionType: action.actionType as number,
|
||||
accountId: action.accountId,
|
||||
accountIdx: action.accountIdx,
|
||||
amountSign: action.actionType === DydxBridgeActionType.Deposit ? true : false,
|
||||
amountDenomination: weiDenomination,
|
||||
amountRef: deltaAmountRef,
|
||||
|
@ -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"]
|
||||
}
|
||||
|
@ -31,6 +31,10 @@
|
||||
{
|
||||
"note": "Update tests for refactored `DevUtils`",
|
||||
"pr": 2464
|
||||
},
|
||||
{
|
||||
"note": "Add DydxBridge validation",
|
||||
"pr": 2466
|
||||
}
|
||||
],
|
||||
"timestamp": 1581204851
|
||||
|
@ -23,14 +23,14 @@ blockchainTests.fork.resets('Mainnet dydx bridge tests', env => {
|
||||
const defaultAmount = toBaseUnitAmount(0.01);
|
||||
const defaultDepositAction = {
|
||||
actionType: DydxBridgeActionType.Deposit as number,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
accountIdx: constants.ZERO_AMOUNT,
|
||||
marketId: daiMarketId,
|
||||
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||
};
|
||||
const defaultWithdrawAction = {
|
||||
actionType: DydxBridgeActionType.Withdraw as number,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
accountIdx: constants.ZERO_AMOUNT,
|
||||
marketId: daiMarketId,
|
||||
// This ratio must be less than the `1` to account
|
||||
// for interest in dydx balances because the test
|
||||
@ -71,7 +71,7 @@ blockchainTests.fork.resets('Mainnet dydx bridge tests', env => {
|
||||
case DydxBridgeActionType.Deposit:
|
||||
expectedDepositEvents.push({
|
||||
accountOwner: dydxAccountOwner,
|
||||
accountNumber: bridgeData.accountNumbers[action.accountId.toNumber()],
|
||||
accountNumber: bridgeData.accountNumbers[action.accountIdx.toNumber()],
|
||||
market: action.marketId,
|
||||
update: { deltaWei: { sign: true, value: scaledAmount } },
|
||||
from: dydxAccountOwner,
|
||||
@ -81,7 +81,7 @@ blockchainTests.fork.resets('Mainnet dydx bridge tests', env => {
|
||||
case DydxBridgeActionType.Withdraw:
|
||||
expectedWithdrawEvents.push({
|
||||
accountOwner: dydxAccountOwner,
|
||||
accountNumber: bridgeData.accountNumbers[action.accountId.toNumber()],
|
||||
accountNumber: bridgeData.accountNumbers[action.accountIdx.toNumber()],
|
||||
market: action.marketId,
|
||||
update: { deltaWei: { sign: false, value: scaledAmount } },
|
||||
to: receiver,
|
||||
|
@ -34,6 +34,7 @@ blockchainTests.fork.resets('DevUtils mainnet tests', env => {
|
||||
devUtilsArtifacts,
|
||||
contractAddresses.exchange,
|
||||
contractAddresses.chaiBridge,
|
||||
contractAddresses.dydxBridge,
|
||||
);
|
||||
await dai.approve(chai.address, constants.MAX_UINT256).awaitTransactionSuccessAsync({ from: daiHolder });
|
||||
await chai.join(daiHolder, daiDepositAmount).awaitTransactionSuccessAsync({ from: daiHolder });
|
||||
|
@ -27,6 +27,7 @@ blockchainTests('DevUtils.getOrderHash', env => {
|
||||
artifacts,
|
||||
exchange.address,
|
||||
constants.NULL_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -81,6 +81,7 @@ blockchainTests.resets('LibAssetData', env => {
|
||||
artifacts,
|
||||
deployment.exchange.address,
|
||||
constants.NULL_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
);
|
||||
|
||||
staticCallTarget = await TestStaticCallTargetContract.deployFrom0xArtifactAsync(
|
||||
|
@ -43,6 +43,7 @@ blockchainTests('LibTransactionDecoder', env => {
|
||||
artifacts,
|
||||
exchange.address,
|
||||
constants.NULL_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -34,14 +34,14 @@ blockchainTests.resets('Exchange fills dydx orders', env => {
|
||||
let testTokenAddress: string;
|
||||
const defaultDepositAction = {
|
||||
actionType: DydxBridgeActionType.Deposit as number,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
accountIdx: constants.ZERO_AMOUNT,
|
||||
marketId,
|
||||
conversionRateNumerator: dydxConversionRateNumerator,
|
||||
conversionRateDenominator: dydxConversionRateDenominator,
|
||||
};
|
||||
const defaultWithdrawAction = {
|
||||
actionType: DydxBridgeActionType.Withdraw as number,
|
||||
accountId: constants.ZERO_AMOUNT,
|
||||
accountIdx: constants.ZERO_AMOUNT,
|
||||
marketId,
|
||||
conversionRateNumerator: constants.ZERO_AMOUNT,
|
||||
conversionRateDenominator: constants.ZERO_AMOUNT,
|
||||
|
@ -203,6 +203,7 @@ export class DeploymentManager {
|
||||
devUtilsArtifacts,
|
||||
exchange.address,
|
||||
constants.NULL_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
);
|
||||
|
||||
// Construct the new instance and return it.
|
||||
|
@ -37,6 +37,10 @@
|
||||
{
|
||||
"note": "Export `EvmBytecodeOutputLinkReferences` type.",
|
||||
"pr": 2462
|
||||
},
|
||||
{
|
||||
"note": "Add more functions to `LibFractions`.",
|
||||
"pr": 2466
|
||||
}
|
||||
],
|
||||
"timestamp": 1580811564
|
||||
|
@ -37,7 +37,101 @@ library LibFractions {
|
||||
.safeMul(d2)
|
||||
.safeAdd(n2.safeMul(d1));
|
||||
denominator = d1.safeMul(d2);
|
||||
return (numerator, denominator);
|
||||
return normalize(numerator, denominator);
|
||||
}
|
||||
|
||||
/// @dev Safely subracts two fractions `n1/d1 - n2/d2`
|
||||
/// @param n1 numerator of `1`
|
||||
/// @param d1 denominator of `1`
|
||||
/// @param n2 numerator of `2`
|
||||
/// @param d2 denominator of `2`
|
||||
/// @return numerator Numerator of sum
|
||||
/// @return denominator Denominator of sum
|
||||
function sub(
|
||||
uint256 n1,
|
||||
uint256 d1,
|
||||
uint256 n2,
|
||||
uint256 d2
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (
|
||||
uint256 numerator,
|
||||
uint256 denominator
|
||||
)
|
||||
{
|
||||
if (n2 == 0) {
|
||||
return (numerator = n1, denominator = d1);
|
||||
}
|
||||
numerator = n1
|
||||
.safeMul(d2)
|
||||
.safeSub(n2.safeMul(d1));
|
||||
denominator = d1.safeMul(d2);
|
||||
return normalize(numerator, denominator);
|
||||
}
|
||||
|
||||
/// @dev Multiply two fractions.
|
||||
/// @param n1 numerator of `1`
|
||||
/// @param d1 denominator of `1`
|
||||
/// @param n2 numerator of `2`
|
||||
/// @param d2 denominator of `2`
|
||||
/// @return numerator numerator of product.
|
||||
/// @return denominator numerator of product.
|
||||
function mul(
|
||||
uint256 n1,
|
||||
uint256 d1,
|
||||
uint256 n2,
|
||||
uint256 d2
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (
|
||||
uint256 numerator,
|
||||
uint256 denominator
|
||||
)
|
||||
{
|
||||
return normalize(n1.safeMul(n2), d1.safeMul(d2));
|
||||
}
|
||||
|
||||
/// @dev Compares two fractions.
|
||||
/// @param n1 numerator of `1`
|
||||
/// @param d1 denominator of `1`
|
||||
/// @param n2 numerator of `2`
|
||||
/// @param d2 denominator of `2`
|
||||
/// @return compareResult
|
||||
/// `-1` if `n1/d1 < n2/d2`.
|
||||
/// `0` if `n1/d1 == n2/d2`.
|
||||
/// `1` if `n1/d1 > n2/d2`.
|
||||
function cmp(
|
||||
uint256 n1,
|
||||
uint256 d1,
|
||||
uint256 n2,
|
||||
uint256 d2
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (int8 compareResult)
|
||||
{
|
||||
// Handle infinities.
|
||||
if (d1 == 0) {
|
||||
if (d2 == 0) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
} else {
|
||||
if (d2 == 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
uint256 nd1 = n1.safeMul(d2);
|
||||
uint256 nd2 = n2.safeMul(d1);
|
||||
if (nd1 > nd2) {
|
||||
return 1;
|
||||
}
|
||||
if (nd1 < nd2) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// @dev Rescales a fraction to prevent overflows during addition if either
|
||||
|
@ -5,6 +5,10 @@
|
||||
{
|
||||
"note": "Support deploying contracts with unliked libraries through `deployWithLibrariesFrom0xArtifactAsync()`",
|
||||
"pr": 2463
|
||||
},
|
||||
{
|
||||
"note": "Update reference outputs",
|
||||
"pr": 2466
|
||||
}
|
||||
],
|
||||
"timestamp": 1581204851
|
||||
|
@ -60,7 +60,7 @@
|
||||
},
|
||||
{
|
||||
"note": "Update snapshot addresses",
|
||||
"pr": 2464
|
||||
"pr": 2466
|
||||
}
|
||||
],
|
||||
"timestamp": 1580811564
|
||||
|
@ -131,18 +131,18 @@
|
||||
"etherToken": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082",
|
||||
"exchange": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
|
||||
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeProxy": "0x038f9b392fb9a9676dbaddf78ea5fdbf6c7d9710",
|
||||
"erc20BridgeProxy": "0x371b13d97f4bf77d724e78c16b7dc74099f40e84",
|
||||
"zeroExGovernor": "0x0000000000000000000000000000000000000000",
|
||||
"forwarder": "0xe704967449b57b2382b7fa482718748c13c63190",
|
||||
"coordinatorRegistry": "0xaa86dda78e9434aca114b6676fc742a18d15a1cc",
|
||||
"coordinator": "0x4d3d5c850dd5bd9d6f4adda3dd039a3c8054ca29",
|
||||
"multiAssetProxy": "0xcfc18cec799fbd1793b5c43e773c98d4d61cc2db",
|
||||
"staticCallProxy": "0x6dfff22588be9b3ef8cf0ad6dc9b84796f9fb45f",
|
||||
"devUtils": "0x74341e87b1c4db7d5ed95f92b37509f2525a7a90",
|
||||
"devUtils": "0xb23672f74749bf7916ba6827c64111a4d6de7f11",
|
||||
"exchangeV2": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
|
||||
"zrxVault": "0xc4df27466183c0fe2a5924d6ea56e334deff146a",
|
||||
"staking": "0xf23276778860e420acfc18ebeebf7e829b06965c",
|
||||
"stakingProxy": "0x8a063452f7df2614db1bca3a85ef35da40cf0835",
|
||||
"zrxVault": "0xf23276778860e420acfc18ebeebf7e829b06965c",
|
||||
"staking": "0x8a063452f7df2614db1bca3a85ef35da40cf0835",
|
||||
"stakingProxy": "0x59adefa01843c627ba5d6aa350292b4b7ccae67a",
|
||||
"uniswapBridge": "0x0000000000000000000000000000000000000000",
|
||||
"eth2DaiBridge": "0x0000000000000000000000000000000000000000",
|
||||
"erc20BridgeSampler": "0x0000000000000000000000000000000000000000",
|
||||
|
@ -18,7 +18,7 @@
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update `DevUtils` artifact",
|
||||
"pr": 2464
|
||||
"pr": 2466
|
||||
},
|
||||
{
|
||||
"note": "Remove `LibTransactionDecoder` artifact",
|
||||
|
@ -23,6 +23,10 @@
|
||||
{
|
||||
"note": "Use contract package artifacts in ganache migrations",
|
||||
"pr": 2456
|
||||
},
|
||||
{
|
||||
"note": "Update deployment for new DevUtils",
|
||||
"pr": 2466
|
||||
}
|
||||
],
|
||||
"timestamp": 1581204851
|
||||
|
@ -198,6 +198,7 @@ export async function runMigrationsAsync(
|
||||
allArtifacts,
|
||||
exchange.address,
|
||||
constants.NULL_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
);
|
||||
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
|
@ -120,7 +120,7 @@ export async function runMigrationsAsync(supportedProvider: SupportedProvider, t
|
||||
assetProxyArtifacts,
|
||||
);
|
||||
|
||||
await DydxBridgeContract.deployFrom0xArtifactAsync(
|
||||
const dydxBridge = await DydxBridgeContract.deployFrom0xArtifactAsync(
|
||||
assetProxyArtifacts.DydxBridge,
|
||||
provider,
|
||||
txDefaults,
|
||||
@ -253,6 +253,7 @@ export async function runMigrationsAsync(supportedProvider: SupportedProvider, t
|
||||
devUtilsArtifacts,
|
||||
exchange.address,
|
||||
chaiBridge.address,
|
||||
dydxBridge.address,
|
||||
);
|
||||
|
||||
await CoordinatorContract.deployFrom0xArtifactAsync(
|
||||
|
Loading…
x
Reference in New Issue
Block a user