Merge pull request #2466 from 0xProject/feat/contracts/dev-utils/dydx-validation
DevUtils: DydxBridge validation
This commit is contained in:
commit
cbb23a42e2
@ -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
|
||||
@ -22,6 +30,10 @@
|
||||
{
|
||||
"note": "Add asset data decoding functions",
|
||||
"pr": 2462
|
||||
},
|
||||
{
|
||||
"note": "Add `setOperators()` to `IDydx`",
|
||||
"pr": "TODO"
|
||||
}
|
||||
],
|
||||
"timestamp": 1581204851
|
||||
|
@ -191,12 +191,12 @@ 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
|
||||
secondaryMarketId: 0,
|
||||
otherAccountId: 0,
|
||||
otherAccountIdx: 0,
|
||||
data: hex''
|
||||
});
|
||||
}
|
||||
@ -229,12 +229,12 @@ 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
|
||||
secondaryMarketId: 0,
|
||||
otherAccountId: 0,
|
||||
otherAccountIdx: 0,
|
||||
data: hex''
|
||||
});
|
||||
}
|
||||
|
@ -45,12 +45,12 @@ interface IDydx {
|
||||
/// parsed into before being processed.
|
||||
struct ActionArgs {
|
||||
ActionType actionType;
|
||||
uint256 accountId;
|
||||
uint256 accountIdx;
|
||||
AssetAmount amount;
|
||||
uint256 primaryMarketId;
|
||||
uint256 secondaryMarketId;
|
||||
address otherAddress;
|
||||
uint256 otherAccountId;
|
||||
uint256 otherAccountIdx;
|
||||
bytes data;
|
||||
}
|
||||
|
||||
@ -71,6 +71,36 @@ interface IDydx {
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
struct D256 {
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
struct Value {
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
struct Price {
|
||||
uint256 value;
|
||||
}
|
||||
|
||||
struct OperatorArg {
|
||||
address operator;
|
||||
bool trusted;
|
||||
}
|
||||
|
||||
/// @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 +116,77 @@ interface IDydx {
|
||||
ActionArgs[] calldata actions
|
||||
)
|
||||
external;
|
||||
|
||||
// @dev Approves/disapproves any number of operators. An operator is an external address that has the
|
||||
// same permissions to manipulate an account as the owner of the account. Operators are simply
|
||||
// addresses and therefore may either be externally-owned Ethereum accounts OR smart contracts.
|
||||
// Operators are also able to act as AutoTrader contracts on behalf of the account owner if the
|
||||
// operator is a smart contract and implements the IAutoTrader interface.
|
||||
// @param args A list of OperatorArgs which have an address and a boolean. The boolean value
|
||||
// denotes whether to approve (true) or revoke approval (false) for that address.
|
||||
function setOperators(OperatorArg[] calldata args) 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 margin premium for a market. A margin premium makes it so that any positions that
|
||||
/// include the market require a higher collateralization to avoid being liquidated.
|
||||
/// @param marketId The market to query
|
||||
/// @return premium The market's margin premium
|
||||
function getMarketMarginPremium(uint256 marketId)
|
||||
external
|
||||
view
|
||||
returns (D256 memory premium);
|
||||
|
||||
/// @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,
|
||||
@ -128,7 +129,7 @@ contract TestDydxBridge is
|
||||
actions[i].primaryMarketId,
|
||||
actions[i].secondaryMarketId,
|
||||
actions[i].otherAddress,
|
||||
actions[i].otherAccountId,
|
||||
actions[i].otherAccountIdx,
|
||||
actions[i].data
|
||||
);
|
||||
|
||||
@ -171,6 +172,60 @@ contract TestDydxBridge is
|
||||
return _testTokenAddress;
|
||||
}
|
||||
|
||||
/// @dev Unused.
|
||||
function setOperators(OperatorArg[] calldata args) external {}
|
||||
|
||||
/// @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 Unsused
|
||||
function getMarketMarginPremium(uint256 marketId)
|
||||
external
|
||||
view
|
||||
returns (IDydx.D256 memory premium)
|
||||
{}
|
||||
|
||||
/// @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' },
|
||||
|
@ -1,21 +1,22 @@
|
||||
export { artifacts } from './artifacts';
|
||||
export {
|
||||
ChaiBridgeContract,
|
||||
ERC1155ProxyContract,
|
||||
ERC20BridgeProxyContract,
|
||||
ERC20ProxyContract,
|
||||
ERC721ProxyContract,
|
||||
Eth2DaiBridgeContract,
|
||||
DydxBridgeContract,
|
||||
TestDydxBridgeContract,
|
||||
IAssetDataContract,
|
||||
IAssetProxyContract,
|
||||
IChaiContract,
|
||||
IDydxContract,
|
||||
KyberBridgeContract,
|
||||
MultiAssetProxyContract,
|
||||
StaticCallProxyContract,
|
||||
TestDydxBridgeContract,
|
||||
TestStaticCallTargetContract,
|
||||
UniswapBridgeContract,
|
||||
KyberBridgeContract,
|
||||
ChaiBridgeContract,
|
||||
IChaiContract,
|
||||
} from './wrappers';
|
||||
|
||||
export { ERC20Wrapper } from './erc20_wrapper';
|
||||
|
@ -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,
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `DydxBridge` order validation",
|
||||
"pr": 2466
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1581748629,
|
||||
"version": "1.1.1",
|
||||
|
@ -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
|
||||
@ -269,11 +269,14 @@ contract AssetBalance is
|
||||
|
||||
} else if (assetProxyId == IAssetData(address(0)).ERC20Bridge.selector) {
|
||||
// Get address of ERC20 token and bridge contract
|
||||
(, address tokenAddress, address bridgeAddress,) = LibAssetData.decodeERC20BridgeAssetData(assetData);
|
||||
(, address tokenAddress, address bridgeAddress,) =
|
||||
LibAssetData.decodeERC20BridgeAssetData(assetData);
|
||||
if (tokenAddress == _getDaiAddress() && bridgeAddress == chaiBridgeAddress) {
|
||||
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) {
|
||||
allowance = LibDydxBalance.getDydxMakerAllowance(ownerAddress, bridgeAddress, _getDydxAddress());
|
||||
}
|
||||
// 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, _getDydxAddress()),
|
||||
getAssetProxyAllowance(order.makerAddress, order.makerAssetData)
|
||||
);
|
||||
}
|
||||
}
|
||||
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
|
||||
{}
|
||||
|
436
contracts/dev-utils/contracts/src/LibDydxBalance.sol
Normal file
436
contracts/dev-utils/contracts/src/LibDydxBalance.sol
Normal file
@ -0,0 +1,436 @@
|
||||
/*
|
||||
|
||||
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/LibOrder.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/D18.sol";
|
||||
import "./LibAssetData.sol";
|
||||
|
||||
|
||||
library LibDydxBalance {
|
||||
|
||||
using LibBytes for bytes;
|
||||
using LibSafeMath for uint256;
|
||||
|
||||
/// @dev Padding % added to the minimum collateralization ratio to
|
||||
/// prevent withdrawing exactly the amount that would make an account
|
||||
/// insolvent. 1 bps.
|
||||
int256 private constant MARGIN_RATIO_PADDING = 0.0001e18;
|
||||
|
||||
/// @dev Structure that holds all pertinent info needed to perform a balance
|
||||
/// check.
|
||||
struct BalanceCheckInfo {
|
||||
IDydx dydx;
|
||||
address bridgeAddress;
|
||||
address makerAddress;
|
||||
address makerTokenAddress;
|
||||
address takerTokenAddress;
|
||||
int256 orderMakerToTakerRate;
|
||||
uint256[] accounts;
|
||||
IDydxBridge.BridgeAction[] actions;
|
||||
}
|
||||
|
||||
/// @dev Gets the maker asset allowance for a Dydx bridge order.
|
||||
/// @param makerAddress The maker of the order.
|
||||
/// @param bridgeAddress The address of the Dydx bridge.
|
||||
/// @param dydx The Dydx contract address.
|
||||
/// @return allowance The maker asset allowance.
|
||||
function getDydxMakerAllowance(address makerAddress, address bridgeAddress, address dydx)
|
||||
public
|
||||
view
|
||||
returns (uint256 allowance)
|
||||
{
|
||||
// Allowance is infinite if the dydx bridge is an operator for the maker.
|
||||
return IDydx(dydx).getIsLocalOperator(makerAddress, bridgeAddress)
|
||||
? uint256(-1) : 0;
|
||||
}
|
||||
|
||||
/// @dev Gets the maker allowance for a
|
||||
/// @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);
|
||||
// Actions must be well-formed.
|
||||
if (!_areActionsWellFormed(info)) {
|
||||
return 0;
|
||||
}
|
||||
// If the rate we withdraw maker tokens is less than one, the asset
|
||||
// proxy will throw because we will always transfer less maker tokens
|
||||
// than asked.
|
||||
if (_getMakerTokenWithdrawRate(info) < D18.one()) {
|
||||
return 0;
|
||||
}
|
||||
// The maker balance is the smaller of:
|
||||
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 (int256 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);
|
||||
// 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;
|
||||
}
|
||||
// `depositRate` is the rate at which we convert a maker token into
|
||||
// a taker token for deposit.
|
||||
int256 depositRate = _getActionRate(action);
|
||||
// Taker tokens will be transferred to the maker for every fill, so
|
||||
// we reduce the effective deposit rate if we're depositing the taker
|
||||
// token.
|
||||
address depositToken = info.dydx.getMarketTokenAddress(action.marketId);
|
||||
if (info.takerTokenAddress != address(0) && depositToken == info.takerTokenAddress) {
|
||||
depositRate = D18.sub(depositRate, info.orderMakerToTakerRate);
|
||||
}
|
||||
// If the deposit rate is > 0, we are limited by the transferrable
|
||||
// token balance of the maker.
|
||||
if (depositRate > 0) {
|
||||
uint256 supply = _getTransferabeTokenAmount(
|
||||
depositToken,
|
||||
info.makerAddress,
|
||||
address(info.dydx)
|
||||
);
|
||||
depositableMakerAmount = LibSafeMath.min256(
|
||||
depositableMakerAmount,
|
||||
uint256(D18.div(supply, depositRate))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @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);
|
||||
int256 minCr = D18.add(_getMinimumCollateralizationRatio(info.dydx), MARGIN_RATIO_PADDING);
|
||||
// Loop through the accounts.
|
||||
for (uint256 accountIdx = 0; accountIdx < info.accounts.length; ++accountIdx) {
|
||||
(uint256 supplyValue, uint256 borrowValue) =
|
||||
_getAccountMarketValues(info, info.accounts[accountIdx]);
|
||||
// All accounts must currently be solvent.
|
||||
if (borrowValue != 0 && D18.div(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.
|
||||
int256 dd = 0;
|
||||
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 = D18.add(
|
||||
dd,
|
||||
_getActionRateValue(
|
||||
info,
|
||||
deposit
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
// Compute the borrow/withdraw rate, which is the rate at which
|
||||
// (USD) value is deducted from the account.
|
||||
int256 db = _getActionRateValue(
|
||||
info,
|
||||
withdraw
|
||||
);
|
||||
// If the deposit to withdraw ratio is >= the minimum collateralization
|
||||
// ratio, then we will never become insolvent at these prices.
|
||||
if (D18.div(dd, db) >= minCr) {
|
||||
continue;
|
||||
}
|
||||
// If the adjusted deposit rates are equal, the account will remain
|
||||
// at the same level of collateralization.
|
||||
if (D18.mul(minCr, db) == dd) {
|
||||
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)`
|
||||
int256 t = D18.div(
|
||||
D18.sub(supplyValue, D18.mul(minCr, borrowValue)),
|
||||
D18.sub(D18.mul(minCr, db), dd)
|
||||
);
|
||||
solventMakerAmount = LibSafeMath.min256(
|
||||
solventMakerAmount,
|
||||
// `t` is in maker token units, so convert it to maker wei.
|
||||
_toWei(info.makerTokenAddress, uint256(D18.clip(t)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @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.length == 36) {
|
||||
if (order.takerAssetData.readBytes4(0) == IAssetData(0).ERC20Token.selector) {
|
||||
(, info.takerTokenAddress) =
|
||||
LibAssetData.decodeERC20AssetData(order.takerAssetData);
|
||||
}
|
||||
}
|
||||
info.orderMakerToTakerRate = D18.div(order.takerAssetAmount, order.makerAssetAmount);
|
||||
(IDydxBridge.BridgeData memory bridgeData) =
|
||||
abi.decode(rawBridgeData, (IDydxBridge.BridgeData));
|
||||
info.accounts = bridgeData.accountNumbers;
|
||||
info.actions = bridgeData.actions;
|
||||
}
|
||||
|
||||
/// @dev Returns the conversion rate for an action.
|
||||
/// @param action A `BridgeAction`.
|
||||
function _getActionRate(IDydxBridge.BridgeAction memory action)
|
||||
private
|
||||
pure
|
||||
returns (int256 rate)
|
||||
{
|
||||
rate = action.conversionRateDenominator == 0
|
||||
? D18.one()
|
||||
: D18.div(
|
||||
action.conversionRateNumerator,
|
||||
action.conversionRateDenominator
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Returns the USD value of an action based on its conversion rate
|
||||
/// and market prices.
|
||||
/// @param info State from `_getBalanceCheckInfo()`.
|
||||
/// @param action A `BridgeAction`.
|
||||
function _getActionRateValue(
|
||||
BalanceCheckInfo memory info,
|
||||
IDydxBridge.BridgeAction memory action
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (int256 value)
|
||||
{
|
||||
address toToken = info.dydx.getMarketTokenAddress(action.marketId);
|
||||
uint256 fromTokenDecimals = LibERC20Token.decimals(info.makerTokenAddress);
|
||||
uint256 toTokenDecimals = LibERC20Token.decimals(toToken);
|
||||
// First express the rate as 18-decimal units.
|
||||
value = toTokenDecimals > fromTokenDecimals
|
||||
? int256(
|
||||
uint256(_getActionRate(action))
|
||||
.safeDiv(10 ** (toTokenDecimals - fromTokenDecimals))
|
||||
)
|
||||
: int256(
|
||||
uint256(_getActionRate(action))
|
||||
.safeMul(10 ** (fromTokenDecimals - toTokenDecimals))
|
||||
);
|
||||
// Prices have 18 + (18 - TOKEN_DECIMALS) decimal places because
|
||||
// consistency is stupid.
|
||||
uint256 price = info.dydx.getMarketPrice(action.marketId).value;
|
||||
// Make prices have 18 decimals.
|
||||
if (toTokenDecimals > 18) {
|
||||
price = price.safeMul(10 ** (toTokenDecimals - 18));
|
||||
} else {
|
||||
price = price.safeDiv(10 ** (18 - toTokenDecimals));
|
||||
}
|
||||
// The action value is the action rate times the price.
|
||||
value = D18.mul(price, value);
|
||||
// Scale by the market premium.
|
||||
int256 marketPremium = D18.add(
|
||||
D18.one(),
|
||||
info.dydx.getMarketMarginPremium(action.marketId).value
|
||||
);
|
||||
if (action.actionType == IDydxBridge.BridgeActionType.Deposit) {
|
||||
value = D18.div(value, marketPremium);
|
||||
} else {
|
||||
value = D18.mul(value, marketPremium);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Convert a `D18` fraction of 1 token to the equivalent integer wei.
|
||||
/// @param token Address the of the token.
|
||||
/// @param units Token units expressed with 18 digit precision.
|
||||
function _toWei(address token, uint256 units)
|
||||
private
|
||||
view
|
||||
returns (uint256 rate)
|
||||
{
|
||||
uint256 decimals = LibERC20Token.decimals(token);
|
||||
rate = decimals > 18
|
||||
? units.safeMul(10 ** (decimals - 18))
|
||||
: units.safeDiv(10 ** (18 - decimals));
|
||||
}
|
||||
|
||||
/// @dev Get the global minimum collateralization ratio required for
|
||||
/// an account to be considered solvent.
|
||||
/// @param dydx The Dydx interface.
|
||||
function _getMinimumCollateralizationRatio(IDydx dydx)
|
||||
private
|
||||
view
|
||||
returns (int256 ratio)
|
||||
{
|
||||
IDydx.RiskParams memory riskParams = dydx.getRiskParams();
|
||||
return D18.add(D18.one(), D18.toSigned(riskParams.marginRatio.value));
|
||||
}
|
||||
|
||||
/// @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 _getAccountMarketValues(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
|
||||
));
|
||||
// Account values have 36 decimal places because dydx likes to make sure
|
||||
// you're paying attention.
|
||||
return (supplyValue_.value / 1e18, borrowValue_.value / 1e18);
|
||||
}
|
||||
|
||||
/// @dev Get the amount of an ERC20 token held by `owner` that can be transferred
|
||||
/// 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)
|
||||
);
|
||||
}
|
||||
}
|
@ -199,7 +199,7 @@ contract OrderValidationUtils is
|
||||
{
|
||||
(uint256 balance, uint256 allowance) = _getConvertibleMakerBalanceAndAssetProxyAllowance(order);
|
||||
transferableAssetAmount = LibSafeMath.min256(balance, allowance);
|
||||
return transferableAssetAmount;
|
||||
return LibSafeMath.min256(transferableAssetAmount, order.makerAssetAmount);
|
||||
}
|
||||
|
||||
/// @dev Checks that the asset data contained in a ZeroEx is valid and returns
|
||||
|
181
contracts/dev-utils/contracts/test/TestDydx.sol
Normal file
181
contracts/dev-utils/contracts/test/TestDydx.sol
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
|
||||
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 getAdjustedAccountValues(
|
||||
IDydx.AccountInfo calldata account
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (IDydx.Value memory supplyValue, IDydx.Value memory borrowValue)
|
||||
{
|
||||
for (uint256 marketId = 0; marketId < _markets.length; ++marketId) {
|
||||
int256 balance =
|
||||
_balance[_getBalanceHash(account.owner, account.number, marketId)];
|
||||
// Account values have 36 decimal places.
|
||||
// `getMarketPrice()` returns a unit with
|
||||
// 18 + (18 - TOKEN_DECIMALS) decimal places so multiplying the price
|
||||
// with the wei balance will result in a 36 decimal value.
|
||||
balance = balance * int256(getMarketPrice(marketId).value);
|
||||
if (balance >= 0) {
|
||||
supplyValue.value += uint256(balance);
|
||||
} else {
|
||||
borrowValue.value += uint256(-balance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getMarketMarginPremium(uint256)
|
||||
external
|
||||
view
|
||||
returns (IDydx.D256 memory premium)
|
||||
{
|
||||
// Return 0.
|
||||
return premium;
|
||||
}
|
||||
|
||||
function getMarketPrice(
|
||||
uint256 marketId
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (IDydx.Price memory price)
|
||||
{
|
||||
MarketInfo memory market = _markets[marketId];
|
||||
uint256 decimals = LibERC20Token.decimals(market.token);
|
||||
price.value = _markets[marketId].price;
|
||||
// Market prices have 18 + (18 - TOKEN_DECIMALS)
|
||||
if (decimals > 18) {
|
||||
price.value /= 10 ** (decimals - 18);
|
||||
} else {
|
||||
price.value *= 10 ** (18 - decimals);
|
||||
}
|
||||
}
|
||||
|
||||
function _getOperatorHash(address owner, address operator)
|
||||
private
|
||||
pure
|
||||
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,
|
||||
};
|
||||
|
1170
contracts/dev-utils/test/lib_dydx_balance_test.ts
Normal file
1170
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,14 @@
|
||||
{
|
||||
"note": "Update tests for refactored `DevUtils`",
|
||||
"pr": 2464
|
||||
},
|
||||
{
|
||||
"note": "Add DydxBridge validation",
|
||||
"pr": 2466
|
||||
},
|
||||
{
|
||||
"note": "Add DevUtils DydxBridge validation mainnet tests",
|
||||
"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 });
|
||||
|
@ -0,0 +1,454 @@
|
||||
import {
|
||||
artifacts as assetProxyArtifacts,
|
||||
DydxBridgeActionType,
|
||||
DydxBridgeContract,
|
||||
DydxBridgeData,
|
||||
dydxBridgeDataEncoder,
|
||||
encodeERC20AssetData,
|
||||
encodeERC20BridgeAssetData,
|
||||
IDydxContract,
|
||||
} from '@0x/contracts-asset-proxy';
|
||||
import { artifacts as devUtilsArtifacts, DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { ERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { blockchainTests, constants, expect, Numberish } from '@0x/contracts-test-utils';
|
||||
import { Order } from '@0x/types';
|
||||
import { BigNumber, fromTokenUnitAmount, hexUtils, toTokenUnitAmount } from '@0x/utils';
|
||||
|
||||
import { contractAddresses } from '../mainnet_fork_utils';
|
||||
|
||||
enum DydxActionType {
|
||||
Deposit = 0,
|
||||
Withdraw = 1,
|
||||
}
|
||||
|
||||
enum DydxAssetDenomination {
|
||||
Wei = 0,
|
||||
Par = 1,
|
||||
}
|
||||
|
||||
enum DydxAssetReference {
|
||||
Delta = 0,
|
||||
Target = 1,
|
||||
}
|
||||
|
||||
const CHONKY_DAI_WALLET = '0x3a9F7C8cA36C42d7035E87C3304eE5cBd353a532';
|
||||
const CHONKY_USDC_WALLET = '0x1EDA7056fF11C9817038E0020C3a6F1d6A8Ec32e';
|
||||
|
||||
blockchainTests.configure({
|
||||
fork: {
|
||||
unlockedAccounts: [CHONKY_DAI_WALLET, CHONKY_USDC_WALLET],
|
||||
},
|
||||
});
|
||||
|
||||
blockchainTests.fork('DevUtils dydx order validation tests', env => {
|
||||
const { ZERO_AMOUNT: ZERO } = constants;
|
||||
const SIGNATURE = '0x01'; // Invalid signature. Doesn't matter.
|
||||
const DAI_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
|
||||
const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
|
||||
const DYDX_ADDRESS = '0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e';
|
||||
const TOKEN_INFO: { [addr: string]: { decimals: number; marketId: number } } = {
|
||||
[DAI_ADDRESS]: {
|
||||
decimals: 18,
|
||||
marketId: 3,
|
||||
},
|
||||
[USDC_ADDRESS]: {
|
||||
decimals: 6,
|
||||
marketId: 2,
|
||||
},
|
||||
};
|
||||
const DAI_DECIMALS = TOKEN_INFO[DAI_ADDRESS].decimals;
|
||||
const USDC_DECIMALS = TOKEN_INFO[USDC_ADDRESS].decimals;
|
||||
let bridge: DydxBridgeContract;
|
||||
let dydx: IDydxContract;
|
||||
let dai: ERC20TokenContract;
|
||||
let usdc: ERC20TokenContract;
|
||||
let devUtils: DevUtilsContract;
|
||||
let accountOwner: string;
|
||||
let minMarginRatio: number;
|
||||
|
||||
before(async () => {
|
||||
[accountOwner] = await env.getAccountAddressesAsync();
|
||||
dydx = new IDydxContract(DYDX_ADDRESS, env.provider, env.txDefaults);
|
||||
dai = new ERC20TokenContract(DAI_ADDRESS, env.provider, env.txDefaults);
|
||||
usdc = new ERC20TokenContract(USDC_ADDRESS, env.provider, env.txDefaults);
|
||||
bridge = await DydxBridgeContract.deployFrom0xArtifactAsync(
|
||||
assetProxyArtifacts.DydxBridge,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
{},
|
||||
);
|
||||
devUtils = await DevUtilsContract.deployWithLibrariesFrom0xArtifactAsync(
|
||||
devUtilsArtifacts.DevUtils,
|
||||
devUtilsArtifacts,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
devUtilsArtifacts,
|
||||
contractAddresses.exchange,
|
||||
contractAddresses.chaiBridge,
|
||||
bridge.address,
|
||||
);
|
||||
minMarginRatio = toTokenUnitAmount((await dydx.getRiskParams().callAsync()).marginRatio.value)
|
||||
.plus(1)
|
||||
.toNumber();
|
||||
// Set approvals and operators.
|
||||
await dai
|
||||
.approve(DYDX_ADDRESS, constants.MAX_UINT256)
|
||||
.awaitTransactionSuccessAsync({ from: CHONKY_DAI_WALLET });
|
||||
await usdc
|
||||
.approve(DYDX_ADDRESS, constants.MAX_UINT256)
|
||||
.awaitTransactionSuccessAsync({ from: CHONKY_USDC_WALLET });
|
||||
await dydx
|
||||
.setOperators([{ operator: bridge.address, trusted: true }])
|
||||
.awaitTransactionSuccessAsync({ from: CHONKY_DAI_WALLET });
|
||||
await dydx
|
||||
.setOperators([{ operator: bridge.address, trusted: true }])
|
||||
.awaitTransactionSuccessAsync({ from: CHONKY_USDC_WALLET });
|
||||
});
|
||||
|
||||
async function depositAndWithdrawAsync(
|
||||
makerAddress: string,
|
||||
accountId: BigNumber,
|
||||
depositSize: Numberish = 0,
|
||||
withdrawSize: Numberish = 0,
|
||||
): Promise<void> {
|
||||
const fromToken = makerAddress === CHONKY_DAI_WALLET ? DAI_ADDRESS : USDC_ADDRESS;
|
||||
const toToken = fromToken === DAI_ADDRESS ? USDC_ADDRESS : DAI_ADDRESS;
|
||||
const fromDecimals = TOKEN_INFO[fromToken].decimals;
|
||||
const fromMarketId = TOKEN_INFO[fromToken].marketId;
|
||||
const toDecimals = TOKEN_INFO[toToken].decimals;
|
||||
const toMarketId = TOKEN_INFO[toToken].marketId;
|
||||
await dydx
|
||||
.operate(
|
||||
[{ owner: makerAddress, number: accountId }],
|
||||
[
|
||||
...(depositSize > 0
|
||||
? [
|
||||
{
|
||||
actionType: DydxActionType.Deposit,
|
||||
accountIdx: ZERO,
|
||||
amount: {
|
||||
sign: true,
|
||||
denomination: DydxAssetDenomination.Wei,
|
||||
ref: DydxAssetReference.Delta,
|
||||
value: fromTokenUnitAmount(depositSize, fromDecimals),
|
||||
},
|
||||
primaryMarketId: new BigNumber(fromMarketId),
|
||||
secondaryMarketId: new BigNumber(constants.NULL_ADDRESS),
|
||||
otherAddress: makerAddress,
|
||||
otherAccountIdx: ZERO,
|
||||
data: constants.NULL_BYTES,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(withdrawSize > 0
|
||||
? [
|
||||
{
|
||||
actionType: DydxActionType.Withdraw,
|
||||
accountIdx: ZERO,
|
||||
amount: {
|
||||
sign: false,
|
||||
denomination: DydxAssetDenomination.Wei,
|
||||
ref: DydxAssetReference.Delta,
|
||||
value: fromTokenUnitAmount(withdrawSize, toDecimals),
|
||||
},
|
||||
primaryMarketId: new BigNumber(toMarketId),
|
||||
secondaryMarketId: new BigNumber(constants.NULL_ADDRESS),
|
||||
otherAddress: makerAddress,
|
||||
otherAccountIdx: ZERO,
|
||||
data: constants.NULL_BYTES,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: makerAddress });
|
||||
}
|
||||
|
||||
const SECONDS_IN_ONE_YEAR = 365 * 24 * 60 * 60;
|
||||
|
||||
function createOrder(fields: Partial<Order> = {}): Order {
|
||||
return {
|
||||
chainId: 1,
|
||||
exchangeAddress: contractAddresses.exchange,
|
||||
expirationTimeSeconds: new BigNumber(Math.floor(Date.now() / 1000 + SECONDS_IN_ONE_YEAR)),
|
||||
makerAddress: CHONKY_DAI_WALLET,
|
||||
takerAddress: constants.NULL_ADDRESS,
|
||||
senderAddress: constants.NULL_ADDRESS,
|
||||
feeRecipientAddress: constants.NULL_ADDRESS,
|
||||
salt: new BigNumber(hexUtils.random()),
|
||||
makerAssetAmount: fromTokenUnitAmount(100, USDC_DECIMALS),
|
||||
takerAssetAmount: fromTokenUnitAmount(200, DAI_DECIMALS),
|
||||
makerFee: ZERO,
|
||||
takerFee: ZERO,
|
||||
makerAssetData: encodeDydxBridgeAssetData(),
|
||||
takerAssetData: encodeERC20AssetData(DAI_ADDRESS),
|
||||
makerFeeAssetData: constants.NULL_BYTES,
|
||||
takerFeeAssetData: constants.NULL_BYTES,
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
|
||||
function encodeDydxBridgeAssetData(
|
||||
fields: Partial<{
|
||||
fromToken: string;
|
||||
toToken: string;
|
||||
depositRate: number;
|
||||
withdrawRate: number;
|
||||
accountId: BigNumber;
|
||||
}> = {},
|
||||
): string {
|
||||
const { fromToken, toToken, depositRate, withdrawRate, accountId } = {
|
||||
fromToken: DAI_ADDRESS,
|
||||
toToken: USDC_ADDRESS,
|
||||
depositRate: 1,
|
||||
withdrawRate: 1,
|
||||
accountId: ZERO,
|
||||
...fields,
|
||||
};
|
||||
const fromTokenMarketId = new BigNumber(TOKEN_INFO[fromToken].marketId);
|
||||
const toTokenMarketId = new BigNumber(TOKEN_INFO[toToken].marketId);
|
||||
const bridgeData: DydxBridgeData = {
|
||||
accountNumbers: [accountId],
|
||||
actions: [
|
||||
...(depositRate > 0
|
||||
? [
|
||||
{
|
||||
actionType: DydxBridgeActionType.Deposit,
|
||||
accountIdx: ZERO,
|
||||
marketId: fromTokenMarketId,
|
||||
...createConversionFraction(toToken, fromToken, depositRate),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(withdrawRate > 0
|
||||
? [
|
||||
{
|
||||
actionType: DydxBridgeActionType.Withdraw,
|
||||
accountIdx: ZERO,
|
||||
marketId: toTokenMarketId,
|
||||
...createConversionFraction(toToken, toToken, withdrawRate),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
return encodeERC20BridgeAssetData(toToken, bridge.address, dydxBridgeDataEncoder.encode({ bridgeData }));
|
||||
}
|
||||
|
||||
// Create fraction with default 18 decimal precision.
|
||||
function createConversionFraction(
|
||||
fromToken: string,
|
||||
toToken: string,
|
||||
rate: number,
|
||||
): {
|
||||
conversionRateNumerator: BigNumber;
|
||||
conversionRateDenominator: BigNumber;
|
||||
} {
|
||||
const fromDecimals = TOKEN_INFO[fromToken].decimals;
|
||||
const toDecimals = TOKEN_INFO[toToken].decimals;
|
||||
return {
|
||||
conversionRateNumerator: fromTokenUnitAmount(rate, toDecimals),
|
||||
conversionRateDenominator: fromTokenUnitAmount(1, fromDecimals),
|
||||
};
|
||||
}
|
||||
|
||||
function randomAccountId(): BigNumber {
|
||||
return new BigNumber(hexUtils.random());
|
||||
}
|
||||
|
||||
describe('DAI -> USDC', () => {
|
||||
const makerAddress = CHONKY_DAI_WALLET;
|
||||
function _createOrder(fields: Partial<Order> = {}): Order {
|
||||
return createOrder(fields);
|
||||
}
|
||||
|
||||
it('validates a fully solvent order', async () => {
|
||||
// This account is collateralized enough to fill the order with just
|
||||
// withdraws.
|
||||
const accountId = randomAccountId();
|
||||
await depositAndWithdrawAsync(makerAddress, accountId, 200, 0);
|
||||
const order = _createOrder({
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
accountId,
|
||||
depositRate: 0,
|
||||
}),
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
|
||||
expect(fillableTakerAssetAmount).to.bignumber.eq(order.takerAssetAmount);
|
||||
});
|
||||
|
||||
it('validates a perpetually solvent order', async () => {
|
||||
// This account is not very well collateralized, but the deposit rate
|
||||
// will keep the collateralization ratio the same or better.
|
||||
const accountId = randomAccountId();
|
||||
await depositAndWithdrawAsync(makerAddress, accountId, 1, 0);
|
||||
const order = _createOrder({
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
accountId,
|
||||
depositRate: minMarginRatio,
|
||||
}),
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
|
||||
expect(fillableTakerAssetAmount).to.bignumber.eq(order.takerAssetAmount);
|
||||
});
|
||||
|
||||
it('validates a partially solvent order with an inadequate deposit', async () => {
|
||||
// This account is not very well collateralized and the deposit rate is
|
||||
// also too low to sustain the collateralization ratio for the full order.
|
||||
const accountId = randomAccountId();
|
||||
await depositAndWithdrawAsync(makerAddress, accountId, 1, 0);
|
||||
const order = _createOrder({
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
accountId,
|
||||
depositRate: minMarginRatio * 0.95,
|
||||
}),
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
|
||||
expect(fillableTakerAssetAmount).to.bignumber.gt(0);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.lt(order.takerAssetAmount);
|
||||
});
|
||||
|
||||
it('validates a partially solvent order with no deposit', async () => {
|
||||
// This account is not very well collateralized and there is no deposit
|
||||
// to keep the collateralization ratio up.
|
||||
const accountId = randomAccountId();
|
||||
await depositAndWithdrawAsync(makerAddress, accountId, 1, 0);
|
||||
const order = _createOrder({
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
accountId,
|
||||
depositRate: 0,
|
||||
}),
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
|
||||
expect(fillableTakerAssetAmount).to.bignumber.gt(0);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.lt(order.takerAssetAmount);
|
||||
});
|
||||
|
||||
// TODO(dorothy-zbornak): We can't actually create an account that's below
|
||||
// the margin ratio without replacing the price oracles.
|
||||
it('invalidates a virtually insolvent order', async () => {
|
||||
// This account has a collateralization ratio JUST above the
|
||||
// minimum margin ratio, so it can only withdraw nearly zero maker tokens.
|
||||
const accountId = randomAccountId();
|
||||
await depositAndWithdrawAsync(makerAddress, accountId, 1, 1 / (minMarginRatio + 3e-4));
|
||||
const order = _createOrder({
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
accountId,
|
||||
depositRate: 0,
|
||||
}),
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
|
||||
// Price fluctuations will cause this to be a little above zero, so we
|
||||
// don't compare to zero.
|
||||
expect(fillableTakerAssetAmount).to.bignumber.lt(fromTokenUnitAmount(1e-3, DAI_DECIMALS));
|
||||
});
|
||||
});
|
||||
|
||||
describe('USDC -> DAI', () => {
|
||||
const makerAddress = CHONKY_USDC_WALLET;
|
||||
function _createOrder(fields: Partial<Order> = {}): Order {
|
||||
return createOrder({
|
||||
makerAddress,
|
||||
takerAssetData: encodeERC20AssetData(USDC_ADDRESS),
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
fromToken: USDC_ADDRESS,
|
||||
toToken: DAI_ADDRESS,
|
||||
}),
|
||||
makerAssetAmount: fromTokenUnitAmount(100, DAI_DECIMALS),
|
||||
takerAssetAmount: fromTokenUnitAmount(100, USDC_DECIMALS),
|
||||
...fields,
|
||||
});
|
||||
}
|
||||
|
||||
it('validates a fully solvent order', async () => {
|
||||
// This account is collateralized enough to fill the order with just
|
||||
// withdraws.
|
||||
const accountId = randomAccountId();
|
||||
await depositAndWithdrawAsync(makerAddress, accountId, 200, 0);
|
||||
const order = _createOrder({
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
accountId,
|
||||
depositRate: 0,
|
||||
fromToken: USDC_ADDRESS,
|
||||
toToken: DAI_ADDRESS,
|
||||
}),
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
|
||||
expect(fillableTakerAssetAmount).to.bignumber.eq(order.takerAssetAmount);
|
||||
});
|
||||
|
||||
it('validates a perpetually solvent order', async () => {
|
||||
// This account is not very well collateralized, but the deposit rate
|
||||
// will keep the collateralization ratio the same or better.
|
||||
const accountId = randomAccountId();
|
||||
await depositAndWithdrawAsync(makerAddress, accountId, 1, 0);
|
||||
const order = _createOrder({
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
accountId,
|
||||
depositRate: minMarginRatio,
|
||||
fromToken: USDC_ADDRESS,
|
||||
toToken: DAI_ADDRESS,
|
||||
}),
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
|
||||
expect(fillableTakerAssetAmount).to.bignumber.eq(order.takerAssetAmount);
|
||||
});
|
||||
|
||||
it('validates a partially solvent order with an inadequate deposit', async () => {
|
||||
// This account is not very well collateralized and the deposit rate is
|
||||
// also too low to sustain the collateralization ratio for the full order.
|
||||
const accountId = randomAccountId();
|
||||
await depositAndWithdrawAsync(makerAddress, accountId, 1, 0);
|
||||
const order = _createOrder({
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
accountId,
|
||||
depositRate: minMarginRatio * 0.95,
|
||||
fromToken: USDC_ADDRESS,
|
||||
toToken: DAI_ADDRESS,
|
||||
}),
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
|
||||
expect(fillableTakerAssetAmount).to.bignumber.gt(0);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.lt(order.takerAssetAmount);
|
||||
});
|
||||
|
||||
it('validates a partially solvent order with no deposit', async () => {
|
||||
// This account is not very well collateralized and there is no deposit
|
||||
// to keep the collateralization ratio up.
|
||||
const accountId = randomAccountId();
|
||||
await depositAndWithdrawAsync(makerAddress, accountId, 1, 0);
|
||||
const order = _createOrder({
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
accountId,
|
||||
depositRate: 0,
|
||||
fromToken: USDC_ADDRESS,
|
||||
toToken: DAI_ADDRESS,
|
||||
}),
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
|
||||
expect(fillableTakerAssetAmount).to.bignumber.gt(0);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.lt(order.takerAssetAmount);
|
||||
});
|
||||
|
||||
// TODO(dorothy-zbornak): We can't actually create an account that's below
|
||||
// the margin ratio without replacing the price oracles.
|
||||
it('invalidates a virtually insolvent order', async () => {
|
||||
// This account has a collateralization ratio JUST above the
|
||||
// minimum margin ratio, so it can only withdraw nearly zero maker tokens.
|
||||
const accountId = randomAccountId();
|
||||
await depositAndWithdrawAsync(makerAddress, accountId, 1, 1 / (minMarginRatio + 3e-4));
|
||||
const order = _createOrder({
|
||||
makerAssetData: encodeDydxBridgeAssetData({
|
||||
accountId,
|
||||
depositRate: 0,
|
||||
fromToken: USDC_ADDRESS,
|
||||
toToken: DAI_ADDRESS,
|
||||
}),
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
|
||||
// Price fluctuations will cause this to be a little above zero, so we
|
||||
// don't compare to zero.
|
||||
expect(fillableTakerAssetAmount).to.bignumber.lt(fromTokenUnitAmount(1e-3, USDC_DECIMALS));
|
||||
});
|
||||
});
|
||||
});
|
@ -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.
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "5.3.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `blockchainTests.config`",
|
||||
"pr": 2466
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1581748629,
|
||||
"version": "5.1.5",
|
||||
|
@ -20,11 +20,24 @@ export interface ContextDefinition extends mocha.IContextDefinition {
|
||||
optional: ContextDefinitionCallback<ISuite | void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* `blockchainTests()` config options.
|
||||
*/
|
||||
export interface BlockchainContextConfig {
|
||||
fork: Partial<{
|
||||
// Accounts to unlock on ganache.
|
||||
unlockedAccounts: string[];
|
||||
}>;
|
||||
}
|
||||
|
||||
let TEST_ENV_CONFIG: Partial<BlockchainContextConfig> = {};
|
||||
|
||||
/**
|
||||
* Interface for `blockchainTests()`.
|
||||
*/
|
||||
export interface BlockchainContextDefinition {
|
||||
(description: string, callback: BlockchainSuiteCallback): ISuite;
|
||||
configure: (config?: Partial<BlockchainContextConfig>) => void;
|
||||
only: BlockchainContextDefinitionCallback<ISuite>;
|
||||
skip: BlockchainContextDefinitionCallback<void>;
|
||||
optional: BlockchainContextDefinitionCallback<ISuite | void>;
|
||||
@ -91,6 +104,11 @@ export class StandardBlockchainTestsEnvironmentSingleton extends BlockchainTests
|
||||
return StandardBlockchainTestsEnvironmentSingleton._instance;
|
||||
}
|
||||
|
||||
// Reset the singleton.
|
||||
public static reset(): void {
|
||||
StandardBlockchainTestsEnvironmentSingleton._instance = undefined;
|
||||
}
|
||||
|
||||
// Get the singleton instance of this class.
|
||||
public static getInstance(): StandardBlockchainTestsEnvironmentSingleton | undefined {
|
||||
return StandardBlockchainTestsEnvironmentSingleton._instance;
|
||||
@ -119,11 +137,19 @@ export class ForkedBlockchainTestsEnvironmentSingleton extends BlockchainTestsEn
|
||||
return ForkedBlockchainTestsEnvironmentSingleton._instance;
|
||||
}
|
||||
|
||||
// Reset the singleton.
|
||||
public static reset(): void {
|
||||
ForkedBlockchainTestsEnvironmentSingleton._instance = undefined;
|
||||
}
|
||||
|
||||
protected static _createWeb3Provider(forkHost: string): Web3ProviderEngine {
|
||||
const forkConfig = TEST_ENV_CONFIG.fork || {};
|
||||
const unlockedAccounts = forkConfig.unlockedAccounts;
|
||||
return web3Factory.getRpcProvider({
|
||||
...providerConfigs,
|
||||
fork: forkHost,
|
||||
blockTime: 0,
|
||||
...(unlockedAccounts ? { unlocked_accounts: unlockedAccounts } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@ -158,6 +184,11 @@ export class LiveBlockchainTestsEnvironmentSingleton extends BlockchainTestsEnvi
|
||||
return LiveBlockchainTestsEnvironmentSingleton._instance;
|
||||
}
|
||||
|
||||
// Reset the singleton.
|
||||
public static reset(): void {
|
||||
LiveBlockchainTestsEnvironmentSingleton._instance = undefined;
|
||||
}
|
||||
|
||||
protected static _createWeb3Provider(rpcHost: string): Web3ProviderEngine {
|
||||
const providerEngine = new Web3ProviderEngine();
|
||||
providerEngine.addProvider(new RPCSubprovider(rpcHost));
|
||||
@ -209,6 +240,16 @@ export const blockchainTests: BlockchainContextDefinition = _.assign(
|
||||
return defineBlockchainSuite(StandardBlockchainTestsEnvironmentSingleton, description, callback, describe);
|
||||
},
|
||||
{
|
||||
configure(config?: Partial<BlockchainContextConfig>): void {
|
||||
// Update the global config and reset all environment singletons.
|
||||
TEST_ENV_CONFIG = {
|
||||
...TEST_ENV_CONFIG,
|
||||
...config,
|
||||
};
|
||||
ForkedBlockchainTestsEnvironmentSingleton.reset();
|
||||
StandardBlockchainTestsEnvironmentSingleton.reset();
|
||||
LiveBlockchainTestsEnvironmentSingleton.reset();
|
||||
},
|
||||
only(description: string, callback: BlockchainSuiteCallback): ISuite {
|
||||
return defineBlockchainSuite(
|
||||
StandardBlockchainTestsEnvironmentSingleton,
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "4.4.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `D18` library",
|
||||
"pr": 2466
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1581748629,
|
||||
"version": "4.3.1",
|
||||
|
248
contracts/utils/contracts/src/D18.sol
Normal file
248
contracts/utils/contracts/src/D18.sol
Normal file
@ -0,0 +1,248 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
|
||||
/// @dev A library for working with 18 digit, base 10 decimals.
|
||||
library D18 {
|
||||
|
||||
/// @dev Decimal places for dydx value quantities.
|
||||
uint256 private constant PRECISION = 18;
|
||||
/// @dev 1.0 in base-18 decimal.
|
||||
int256 private constant DECIMAL_ONE = int256(10 ** PRECISION);
|
||||
/// @dev Minimum signed integer value.
|
||||
int256 private constant MIN_INT256_VALUE = int256(0x8000000000000000000000000000000000000000000000000000000000000000);
|
||||
|
||||
/// @dev Return `1.0`
|
||||
function one()
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
r = DECIMAL_ONE;
|
||||
}
|
||||
|
||||
/// @dev Add two decimals.
|
||||
function add(int256 a, int256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
r = _add(a, b);
|
||||
}
|
||||
|
||||
/// @dev Add two decimals.
|
||||
function add(uint256 a, int256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _add(int256(a), b);
|
||||
}
|
||||
|
||||
/// @dev Add two decimals.
|
||||
function add(int256 a, uint256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _add(a, int256(b));
|
||||
}
|
||||
|
||||
/// @dev Add two decimals.
|
||||
function add(uint256 a, uint256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _add(int256(a), int256(b));
|
||||
}
|
||||
|
||||
/// @dev Subract two decimals.
|
||||
function sub(int256 a, int256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
r = _add(a, -b);
|
||||
}
|
||||
|
||||
/// @dev Subract two decimals.
|
||||
function sub(uint256 a, int256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _add(int256(a), -b);
|
||||
}
|
||||
|
||||
/// @dev Subract two decimals.
|
||||
function sub(uint256 a, uint256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _add(int256(a), -int256(b));
|
||||
}
|
||||
|
||||
/// @dev Multiply two decimals.
|
||||
function mul(int256 a, int256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
r = _div(_mul(a, b), DECIMAL_ONE);
|
||||
}
|
||||
|
||||
/// @dev Multiply two decimals.
|
||||
function mul(uint256 a, int256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _div(_mul(int256(a), b), DECIMAL_ONE);
|
||||
}
|
||||
|
||||
/// @dev Multiply two decimals.
|
||||
function mul(int256 a, uint256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _div(_mul(a, int256(b)), DECIMAL_ONE);
|
||||
}
|
||||
|
||||
/// @dev Multiply two decimals.
|
||||
function mul(uint256 a, uint256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _div(_mul(int256(a), int256(b)), DECIMAL_ONE);
|
||||
}
|
||||
|
||||
/// @dev Divide two decimals.
|
||||
function div(int256 a, int256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
r = _div(_mul(a, DECIMAL_ONE), b);
|
||||
}
|
||||
|
||||
/// @dev Divide two decimals.
|
||||
function div(uint256 a, int256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _div(_mul(int256(a), DECIMAL_ONE), b);
|
||||
}
|
||||
|
||||
/// @dev Divide two decimals.
|
||||
function div(int256 a, uint256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _div(_mul(a, DECIMAL_ONE), int256(b));
|
||||
}
|
||||
|
||||
/// @dev Divide two decimals.
|
||||
function div(uint256 a, uint256 b)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = _div(_mul(int256(a), DECIMAL_ONE), int256(b));
|
||||
}
|
||||
|
||||
/// @dev Safely convert an unsigned integer into a signed integer.
|
||||
function toSigned(uint256 a)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG");
|
||||
r = int256(a);
|
||||
}
|
||||
|
||||
/// @dev Clip a signed value to be positive.
|
||||
function clip(int256 a)
|
||||
internal
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
r = a < 0 ? 0 : a;
|
||||
}
|
||||
|
||||
/// @dev Safely multiply two signed integers.
|
||||
function _mul(int256 a, int256 b)
|
||||
private
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
if (a == 0 || b == 0) {
|
||||
return 0;
|
||||
}
|
||||
r = a * b;
|
||||
require(r / a == b && r / b == a, "D18/DECIMAL_MUL_OVERFLOW");
|
||||
return r;
|
||||
}
|
||||
|
||||
/// @dev Safely divide two signed integers.
|
||||
function _div(int256 a, int256 b)
|
||||
private
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
require(b != 0, "D18/DECIMAL_DIV_BY_ZERO");
|
||||
require(a != MIN_INT256_VALUE || b != -1, "D18/DECIMAL_DIV_OVERFLOW");
|
||||
r = a / b;
|
||||
}
|
||||
|
||||
/// @dev Safely add two signed integers.
|
||||
function _add(int256 a, int256 b)
|
||||
private
|
||||
pure
|
||||
returns (int256 r)
|
||||
{
|
||||
r = a + b;
|
||||
require(
|
||||
!((a < 0 && b < 0 && r > a) || (a > 0 && b > 0 && r < a)),
|
||||
"D18/DECIMAL_ADD_OVERFLOW"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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",
|
||||
|
16
packages/contract-artifacts/artifacts/DevUtils.json
generated
16
packages/contract-artifacts/artifacts/DevUtils.json
generated
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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