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

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

View File

@ -30,6 +30,10 @@
{
"note": "Add asset data decoding functions",
"pr": 2462
},
{
"note": "Add `setOperators()` to `IDydx`",
"pr": "TODO"
}
],
"timestamp": 1581204851

View File

@ -196,7 +196,7 @@ contract DydxBridge is
otherAddress: depositFrom, // deposit from the account owner.
// unused parameters
secondaryMarketId: 0,
otherAccountId: 0,
otherAccountIdx: 0,
data: hex''
});
}
@ -234,7 +234,7 @@ contract DydxBridge is
otherAddress: withdrawTo, // withdraw tokens to this address.
// unused parameters
secondaryMarketId: 0,
otherAccountId: 0,
otherAccountIdx: 0,
data: hex''
});
}

View File

@ -50,7 +50,7 @@ interface IDydx {
uint256 primaryMarketId;
uint256 secondaryMarketId;
address otherAddress;
uint256 otherAccountId;
uint256 otherAccountIdx;
bytes data;
}
@ -83,6 +83,11 @@ interface IDydx {
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
@ -166,4 +171,13 @@ interface IDydx {
external
view
returns (Value memory supplyValue, Value memory borrowValue);
// @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;
}

View File

@ -129,7 +129,7 @@ contract TestDydxBridge is
actions[i].primaryMarketId,
actions[i].secondaryMarketId,
actions[i].otherAddress,
actions[i].otherAccountId,
actions[i].otherAccountIdx,
actions[i].data
);
@ -216,6 +216,9 @@ contract TestDydxBridge is
returns (Value memory supplyValue, Value memory borrowValue)
{}
/// @dev Unused.
function setOperators(OperatorArg[] calldata args) external {}
/// @dev overrides `_getDydxAddress()` from `DeploymentConstants` to return this address.
function _getDydxAddress()
internal

View File

@ -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';

View File

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

View File

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

View File

@ -148,7 +148,7 @@ blockchainTests('LibDydxBalance', env => {
makerAddress: string;
makerTokenAddress: string;
takerTokenAddress: string;
orderTakerToMakerRate: BigNumber;
orderMakerToTakerRate: BigNumber;
accounts: BigNumber[];
actions: DydxBridgeAction[];
}
@ -160,8 +160,8 @@ blockchainTests('LibDydxBalance', env => {
makerAddress: ACCOUNT_OWNER,
makerTokenAddress: DYDX_CONFIG.markets[1].token,
takerTokenAddress: DYDX_CONFIG.markets[0].token,
orderTakerToMakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(5, MAKER_DECIMALS).div(fromTokenUnitAmount(10, TAKER_DECIMALS)),
orderMakerToTakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(5, MAKER_DECIMALS)),
),
accounts: [DYDX_CONFIG.accounts[SOLVENT_ACCOUNT_IDX].accountId],
actions: [],
@ -214,8 +214,9 @@ blockchainTests('LibDydxBalance', env => {
// Computes a deposit rate that is the minimum to keep an account solvent
// perpetually.
function getBalancedDepositRate(withdrawRate: BigNumber, scaling: Numberish = 1.000001): BigNumber {
return withdrawRate.times((MAKER_PRICE / TAKER_PRICE) * MARGIN_RATIO).times(scaling);
function getBalancedDepositRate(withdrawRate: BigNumber, scaling: Numberish = 1): BigNumber {
// Add a small amount to the margin ratio to stay just above insolvency.
return withdrawRate.times((MAKER_PRICE / TAKER_PRICE) * (MARGIN_RATIO + 1.1e-4)).times(scaling);
}
function takerToMakerAmount(takerAmount: BigNumber): BigNumber {
@ -537,8 +538,8 @@ blockchainTests('LibDydxBalance', env => {
blockchainTests.resets('_getDepositableMakerAmount()', () => {
it('returns infinite if no deposit action', async () => {
const checkInfo = createBalanceCheckInfo({
orderTakerToMakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(100, MAKER_DECIMALS).div(fromTokenUnitAmount(10, TAKER_DECIMALS)),
orderMakerToTakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
),
actions: [],
});
@ -548,8 +549,8 @@ blockchainTests('LibDydxBalance', env => {
it('returns infinite if deposit rate is zero', async () => {
const checkInfo = createBalanceCheckInfo({
orderTakerToMakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(100, MAKER_DECIMALS).div(fromTokenUnitAmount(10, TAKER_DECIMALS)),
orderMakerToTakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
),
actions: [
{
@ -567,8 +568,8 @@ blockchainTests('LibDydxBalance', env => {
it('returns infinite if taker tokens cover the deposit rate', async () => {
const checkInfo = createBalanceCheckInfo({
orderTakerToMakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(100, MAKER_DECIMALS).div(fromTokenUnitAmount(10, TAKER_DECIMALS)),
orderMakerToTakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
),
actions: [
{
@ -589,8 +590,8 @@ blockchainTests('LibDydxBalance', env => {
const exchangeRate = 0.1;
const depositRate = Math.random() + exchangeRate;
const checkInfo = createBalanceCheckInfo({
orderTakerToMakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(1, MAKER_DECIMALS).div(fromTokenUnitAmount(exchangeRate, TAKER_DECIMALS)),
orderMakerToTakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(exchangeRate, TAKER_DECIMALS).div(fromTokenUnitAmount(1, MAKER_DECIMALS)),
),
actions: [
{
@ -623,7 +624,7 @@ blockchainTests('LibDydxBalance', env => {
const checkInfo = createBalanceCheckInfo({
// The `takerTokenAddress` will be zero if the asset is not an ERC20.
takerTokenAddress: constants.NULL_ADDRESS,
orderTakerToMakerRate: fromTokenUnitAmount(fromTokenUnitAmount(100, MAKER_DECIMALS)),
orderMakerToTakerRate: fromTokenUnitAmount(fromTokenUnitAmount(0.1, MAKER_DECIMALS)),
actions: [
{
actionType: DydxBridgeActionType.Deposit,
@ -655,8 +656,8 @@ blockchainTests('LibDydxBalance', env => {
takerTokenAddress: randomAddress(),
// These amounts should be effectively ignored in the final computation
// because the token being deposited is not the taker token.
orderTakerToMakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(100, MAKER_DECIMALS).div(fromTokenUnitAmount(10, TAKER_DECIMALS)),
orderMakerToTakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(10, TAKER_DECIMALS).div(fromTokenUnitAmount(100, MAKER_DECIMALS)),
),
actions: [
{
@ -676,8 +677,8 @@ blockchainTests('LibDydxBalance', env => {
// The taker tokens getting exchanged in will only partially cover the deposit.
const exchangeRate = 0.1;
const checkInfo = createBalanceCheckInfo({
orderTakerToMakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(1, MAKER_DECIMALS).div(fromTokenUnitAmount(exchangeRate, TAKER_DECIMALS)),
orderMakerToTakerRate: fromTokenUnitAmount(
fromTokenUnitAmount(exchangeRate, TAKER_DECIMALS).div(fromTokenUnitAmount(1, MAKER_DECIMALS)),
),
actions: [
// Technically, deposits of the same token are not allowed, but the
@ -733,7 +734,7 @@ blockchainTests('LibDydxBalance', env => {
const exchangeRate = 0.1;
const depositRate = Math.random() + exchangeRate;
const checkInfo = createBalanceCheckInfo({
orderTakerToMakerRate: fromTokenUnitAmount(fromTokenUnitAmount(1 / exchangeRate, MAKER_DECIMALS)),
orderMakerToTakerRate: fromTokenUnitAmount(fromTokenUnitAmount(1 / exchangeRate, MAKER_DECIMALS)),
actions: [
{
actionType: DydxBridgeActionType.Deposit,
@ -760,7 +761,7 @@ blockchainTests('LibDydxBalance', env => {
const exchangeRate = 0.1;
const depositRate = Math.random() + exchangeRate;
const checkInfo = createBalanceCheckInfo({
orderTakerToMakerRate: fromTokenUnitAmount(fromTokenUnitAmount(1 / exchangeRate, MAKER_DECIMALS)),
orderMakerToTakerRate: fromTokenUnitAmount(fromTokenUnitAmount(1 / exchangeRate, MAKER_DECIMALS)),
actions: [
{
actionType: DydxBridgeActionType.Deposit,
@ -1155,8 +1156,8 @@ blockchainTests('LibDydxBalance', env => {
actionType: DydxBridgeActionType.Withdraw,
accountIdx: new BigNumber(0),
marketId: new BigNumber(1),
conversionRateNumerator: new BigNumber(9e18),
conversionRateDenominator: new BigNumber(10e18),
conversionRateNumerator: new BigNumber(0.99e18),
conversionRateDenominator: new BigNumber(1e18),
},
],
}),