@0x/contracts-integrations: Add USDC->DAI forked dydx bridge order validation.

This commit is contained in:
Lawrence Forman 2020-02-19 15:44:04 -05:00
parent f9a7857a90
commit a04722b612

View File

@ -31,11 +31,12 @@ enum DydxAssetReference {
Target = 1, Target = 1,
} }
const MAKER_ADDRESS = '0x3a9F7C8cA36C42d7035E87C3304eE5cBd353a532'; const CHONKY_DAI_WALLET = '0x3a9F7C8cA36C42d7035E87C3304eE5cBd353a532';
const CHONKY_USDC_WALLET = '0x1EDA7056fF11C9817038E0020C3a6F1d6A8Ec32e';
blockchainTests.configure({ blockchainTests.configure({
fork: { fork: {
unlockedAccounts: [MAKER_ADDRESS], unlockedAccounts: [CHONKY_DAI_WALLET, CHONKY_USDC_WALLET],
}, },
}); });
@ -57,11 +58,10 @@ blockchainTests.fork('DevUtils dydx order validation tests', env => {
}; };
const DAI_DECIMALS = TOKEN_INFO[DAI_ADDRESS].decimals; const DAI_DECIMALS = TOKEN_INFO[DAI_ADDRESS].decimals;
const USDC_DECIMALS = TOKEN_INFO[USDC_ADDRESS].decimals; const USDC_DECIMALS = TOKEN_INFO[USDC_ADDRESS].decimals;
const DAI_MARKET_ID = TOKEN_INFO[DAI_ADDRESS].marketId;
const USDC_MARKET_ID = TOKEN_INFO[USDC_ADDRESS].marketId;
let bridge: DydxBridgeContract; let bridge: DydxBridgeContract;
let dydx: IDydxContract; let dydx: IDydxContract;
let dai: ERC20TokenContract; let dai: ERC20TokenContract;
let usdc: ERC20TokenContract;
let devUtils: DevUtilsContract; let devUtils: DevUtilsContract;
let accountOwner: string; let accountOwner: string;
let minMarginRatio: number; let minMarginRatio: number;
@ -70,6 +70,7 @@ blockchainTests.fork('DevUtils dydx order validation tests', env => {
[accountOwner] = await env.getAccountAddressesAsync(); [accountOwner] = await env.getAccountAddressesAsync();
dydx = new IDydxContract(DYDX_ADDRESS, env.provider, env.txDefaults); dydx = new IDydxContract(DYDX_ADDRESS, env.provider, env.txDefaults);
dai = new ERC20TokenContract(DAI_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( bridge = await DydxBridgeContract.deployFrom0xArtifactAsync(
assetProxyArtifacts.DydxBridge, assetProxyArtifacts.DydxBridge,
env.provider, env.provider,
@ -89,21 +90,36 @@ blockchainTests.fork('DevUtils dydx order validation tests', env => {
minMarginRatio = toTokenUnitAmount((await dydx.getRiskParams().callAsync()).marginRatio.value) minMarginRatio = toTokenUnitAmount((await dydx.getRiskParams().callAsync()).marginRatio.value)
.plus(1) .plus(1)
.toNumber(); .toNumber();
// Deposit Dai collateral. // Set approvals and operators.
await dai.approve(DYDX_ADDRESS, constants.MAX_UINT256).awaitTransactionSuccessAsync({ from: MAKER_ADDRESS }); 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 await dydx
.setOperators([{ operator: bridge.address, trusted: true }]) .setOperators([{ operator: bridge.address, trusted: true }])
.awaitTransactionSuccessAsync({ from: MAKER_ADDRESS }); .awaitTransactionSuccessAsync({ from: CHONKY_DAI_WALLET });
await dydx
.setOperators([{ operator: bridge.address, trusted: true }])
.awaitTransactionSuccessAsync({ from: CHONKY_USDC_WALLET });
}); });
async function depositAndWithdrawAsync( async function depositAndWithdrawAsync(
makerAddress: string,
accountId: BigNumber, accountId: BigNumber,
depositSize: Numberish = 0, depositSize: Numberish = 0,
withdrawSize: Numberish = 0, withdrawSize: Numberish = 0,
): Promise<void> { ): 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 await dydx
.operate( .operate(
[{ owner: MAKER_ADDRESS, number: accountId }], [{ owner: makerAddress, number: accountId }],
[ [
...(depositSize > 0 ...(depositSize > 0
? [ ? [
@ -114,11 +130,11 @@ blockchainTests.fork('DevUtils dydx order validation tests', env => {
sign: true, sign: true,
denomination: DydxAssetDenomination.Wei, denomination: DydxAssetDenomination.Wei,
ref: DydxAssetReference.Delta, ref: DydxAssetReference.Delta,
value: fromTokenUnitAmount(depositSize, DAI_DECIMALS), value: fromTokenUnitAmount(depositSize, fromDecimals),
}, },
primaryMarketId: new BigNumber(DAI_MARKET_ID), primaryMarketId: new BigNumber(fromMarketId),
secondaryMarketId: new BigNumber(constants.NULL_ADDRESS), secondaryMarketId: new BigNumber(constants.NULL_ADDRESS),
otherAddress: MAKER_ADDRESS, otherAddress: makerAddress,
otherAccountIdx: ZERO, otherAccountIdx: ZERO,
data: constants.NULL_BYTES, data: constants.NULL_BYTES,
}, },
@ -133,11 +149,11 @@ blockchainTests.fork('DevUtils dydx order validation tests', env => {
sign: false, sign: false,
denomination: DydxAssetDenomination.Wei, denomination: DydxAssetDenomination.Wei,
ref: DydxAssetReference.Delta, ref: DydxAssetReference.Delta,
value: fromTokenUnitAmount(withdrawSize, USDC_DECIMALS), value: fromTokenUnitAmount(withdrawSize, toDecimals),
}, },
primaryMarketId: new BigNumber(USDC_MARKET_ID), primaryMarketId: new BigNumber(toMarketId),
secondaryMarketId: new BigNumber(constants.NULL_ADDRESS), secondaryMarketId: new BigNumber(constants.NULL_ADDRESS),
otherAddress: MAKER_ADDRESS, otherAddress: makerAddress,
otherAccountIdx: ZERO, otherAccountIdx: ZERO,
data: constants.NULL_BYTES, data: constants.NULL_BYTES,
}, },
@ -145,7 +161,7 @@ blockchainTests.fork('DevUtils dydx order validation tests', env => {
: []), : []),
], ],
) )
.awaitTransactionSuccessAsync({ from: MAKER_ADDRESS }); .awaitTransactionSuccessAsync({ from: makerAddress });
} }
const SECONDS_IN_ONE_YEAR = 365 * 24 * 60 * 60; const SECONDS_IN_ONE_YEAR = 365 * 24 * 60 * 60;
@ -155,7 +171,7 @@ blockchainTests.fork('DevUtils dydx order validation tests', env => {
chainId: 1, chainId: 1,
exchangeAddress: contractAddresses.exchange, exchangeAddress: contractAddresses.exchange,
expirationTimeSeconds: new BigNumber(Math.floor(Date.now() / 1000 + SECONDS_IN_ONE_YEAR)), expirationTimeSeconds: new BigNumber(Math.floor(Date.now() / 1000 + SECONDS_IN_ONE_YEAR)),
makerAddress: MAKER_ADDRESS, makerAddress: CHONKY_DAI_WALLET,
takerAddress: constants.NULL_ADDRESS, takerAddress: constants.NULL_ADDRESS,
senderAddress: constants.NULL_ADDRESS, senderAddress: constants.NULL_ADDRESS,
feeRecipientAddress: constants.NULL_ADDRESS, feeRecipientAddress: constants.NULL_ADDRESS,
@ -240,84 +256,199 @@ blockchainTests.fork('DevUtils dydx order validation tests', env => {
return new BigNumber(hexUtils.random()); return new BigNumber(hexUtils.random());
} }
it('validates a fully solvent order', async () => { describe('DAI -> USDC', () => {
// This account is collateralized enough to fill the order with just const makerAddress = CHONKY_DAI_WALLET;
// withdraws. function _createOrder(fields: Partial<Order> = {}): Order {
const accountId = randomAccountId(); return createOrder(fields);
await depositAndWithdrawAsync(accountId, 200, 0); }
const order = createOrder({
makerAssetData: encodeDydxBridgeAssetData({ it('validates a fully solvent order', async () => {
accountId, // This account is collateralized enough to fill the order with just
depositRate: 0, // 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));
}); });
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
expect(fillableTakerAssetAmount).to.bignumber.eq(order.takerAssetAmount);
}); });
it.only('validates a perpetually solvent order', async () => { describe('USDC -> DAI', () => {
// This account is not very well collateralized, but the deposit rate const makerAddress = CHONKY_USDC_WALLET;
// will keep the collateralization ratio the same or better. function _createOrder(fields: Partial<Order> = {}): Order {
const accountId = randomAccountId(); return createOrder({
await depositAndWithdrawAsync(accountId, 1, 0); makerAddress,
const order = createOrder({ takerAssetData: encodeERC20AssetData(USDC_ADDRESS),
makerAssetData: encodeDydxBridgeAssetData({ makerAssetData: encodeDydxBridgeAssetData({
accountId, fromToken: USDC_ADDRESS,
depositRate: minMarginRatio, toToken: DAI_ADDRESS,
}), }),
}); makerAssetAmount: fromTokenUnitAmount(100, DAI_DECIMALS),
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync(); takerAssetAmount: fromTokenUnitAmount(100, USDC_DECIMALS),
expect(fillableTakerAssetAmount).to.bignumber.eq(order.takerAssetAmount); ...fields,
}); });
}
it('validates a partially solvent order with an inadequate deposit', async () => { it('validates a fully solvent order', async () => {
// This account is not very well collateralized and the deposit rate is // This account is collateralized enough to fill the order with just
// also too low to sustain the collateralization ratio for the full order. // withdraws.
const accountId = randomAccountId(); const accountId = randomAccountId();
await depositAndWithdrawAsync(accountId, 1, 0); await depositAndWithdrawAsync(makerAddress, accountId, 200, 0);
const order = createOrder({ const order = _createOrder({
makerAssetData: encodeDydxBridgeAssetData({ makerAssetData: encodeDydxBridgeAssetData({
accountId, accountId,
depositRate: minMarginRatio * 0.95, depositRate: 0,
}), fromToken: USDC_ADDRESS,
toToken: DAI_ADDRESS,
}),
});
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
expect(fillableTakerAssetAmount).to.bignumber.eq(order.takerAssetAmount);
}); });
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 () => { it('validates a perpetually solvent order', async () => {
// This account is not very well collateralized and there is no deposit // This account is not very well collateralized, but the deposit rate
// to keep the collateralization ratio up. // will keep the collateralization ratio the same or better.
const accountId = randomAccountId(); const accountId = randomAccountId();
await depositAndWithdrawAsync(accountId, 1, 0); await depositAndWithdrawAsync(makerAddress, accountId, 1, 0);
const order = createOrder({ const order = _createOrder({
makerAssetData: encodeDydxBridgeAssetData({ makerAssetData: encodeDydxBridgeAssetData({
accountId, accountId,
depositRate: 0, depositRate: minMarginRatio,
}), fromToken: USDC_ADDRESS,
toToken: DAI_ADDRESS,
}),
});
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState(order, SIGNATURE).callAsync();
expect(fillableTakerAssetAmount).to.bignumber.eq(order.takerAssetAmount);
}); });
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 it('validates a partially solvent order with an inadequate deposit', async () => {
// the margin ratio without replacing the price oracles. // This account is not very well collateralized and the deposit rate is
it('invalidates a virtually insolvent order', async () => { // also too low to sustain the collateralization ratio for the full order.
// This account has a collateralization ratio JUST above the const accountId = randomAccountId();
// minimum margin ratio, so it can only withdraw nearly zero maker tokens. await depositAndWithdrawAsync(makerAddress, accountId, 1, 0);
const accountId = randomAccountId(); const order = _createOrder({
await depositAndWithdrawAsync(accountId, 1, 1 / (minMarginRatio + 3e-4)); makerAssetData: encodeDydxBridgeAssetData({
const order = createOrder({ accountId,
makerAssetData: encodeDydxBridgeAssetData({ depositRate: minMarginRatio * 0.95,
accountId, fromToken: USDC_ADDRESS,
depositRate: 0, 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));
}); });
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-7, DAI_DECIMALS));
}); });
}); });