285 lines
13 KiB
TypeScript
285 lines
13 KiB
TypeScript
import {
|
|
DydxBridgeActionType,
|
|
dydxBridgeDataEncoder,
|
|
encodeERC20AssetData,
|
|
encodeERC20BridgeAssetData,
|
|
TestDydxBridgeContract,
|
|
} from '@0x/contracts-asset-proxy';
|
|
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
|
import { LibMathRevertErrors } from '@0x/contracts-exchange-libs';
|
|
import { blockchainTests, constants, describe, expect, toBaseUnitAmount } from '@0x/contracts-test-utils';
|
|
import { SignedOrder } from '@0x/order-utils';
|
|
import { BigNumber, RevertError } from '@0x/utils';
|
|
import { DecodedLogArgs, LogWithDecodedArgs } from 'ethereum-types';
|
|
import * as _ from 'lodash';
|
|
|
|
import { deployDydxBridgeAsync } from '../bridges/deploy_dydx_bridge';
|
|
import { Actor } from '../framework/actors/base';
|
|
import { Maker } from '../framework/actors/maker';
|
|
import { Taker } from '../framework/actors/taker';
|
|
import { DeploymentManager } from '../framework/deployment_manager';
|
|
|
|
blockchainTests.resets('Exchange fills dydx orders', env => {
|
|
let testContract: TestDydxBridgeContract;
|
|
let makerToken: DummyERC20TokenContract;
|
|
let takerToken: DummyERC20TokenContract;
|
|
const marketId = new BigNumber(3);
|
|
const dydxConversionRateNumerator = toBaseUnitAmount(6);
|
|
const dydxConversionRateDenominator = toBaseUnitAmount(1);
|
|
let maker: Maker;
|
|
let taker: Taker;
|
|
let orderConfig: Partial<SignedOrder>;
|
|
let dydxBridgeProxyAssetData: string;
|
|
let deployment: DeploymentManager;
|
|
let testTokenAddress: string;
|
|
const defaultDepositAction = {
|
|
actionType: DydxBridgeActionType.Deposit as number,
|
|
accountId: constants.ZERO_AMOUNT,
|
|
marketId,
|
|
conversionRateNumerator: dydxConversionRateNumerator,
|
|
conversionRateDenominator: dydxConversionRateDenominator,
|
|
};
|
|
const defaultWithdrawAction = {
|
|
actionType: DydxBridgeActionType.Withdraw as number,
|
|
accountId: constants.ZERO_AMOUNT,
|
|
marketId,
|
|
conversionRateNumerator: constants.ZERO_AMOUNT,
|
|
conversionRateDenominator: constants.ZERO_AMOUNT,
|
|
};
|
|
const bridgeData = {
|
|
accountNumbers: [new BigNumber(0)],
|
|
actions: [defaultDepositAction, defaultWithdrawAction],
|
|
};
|
|
|
|
before(async () => {
|
|
// Deploy contracts
|
|
deployment = await DeploymentManager.deployAsync(env, {
|
|
numErc20TokensToDeploy: 2,
|
|
});
|
|
testContract = await deployDydxBridgeAsync(deployment, env);
|
|
const encodedBridgeData = dydxBridgeDataEncoder.encode({ bridgeData });
|
|
testTokenAddress = await testContract.getTestToken().callAsync();
|
|
dydxBridgeProxyAssetData = encodeERC20BridgeAssetData(
|
|
testTokenAddress,
|
|
testContract.address,
|
|
encodedBridgeData,
|
|
);
|
|
[makerToken, takerToken] = deployment.tokens.erc20;
|
|
|
|
// Configure default order parameters.
|
|
orderConfig = {
|
|
makerAssetAmount: toBaseUnitAmount(1),
|
|
takerAssetAmount: toBaseUnitAmount(1),
|
|
makerAssetData: encodeERC20AssetData(makerToken.address),
|
|
takerAssetData: encodeERC20AssetData(takerToken.address),
|
|
// Not important for this test.
|
|
feeRecipientAddress: constants.NULL_ADDRESS,
|
|
makerFeeAssetData: encodeERC20AssetData(makerToken.address),
|
|
takerFeeAssetData: encodeERC20AssetData(takerToken.address),
|
|
makerFee: toBaseUnitAmount(1),
|
|
takerFee: toBaseUnitAmount(1),
|
|
};
|
|
|
|
// Configure maker.
|
|
maker = new Maker({
|
|
name: 'Maker',
|
|
deployment,
|
|
orderConfig,
|
|
});
|
|
await maker.configureERC20TokenAsync(makerToken, deployment.assetProxies.erc20Proxy.address);
|
|
|
|
// Configure taker.
|
|
taker = new Taker({
|
|
name: 'Taker',
|
|
deployment,
|
|
});
|
|
await taker.configureERC20TokenAsync(takerToken, deployment.assetProxies.erc20Proxy.address);
|
|
});
|
|
|
|
after(async () => {
|
|
Actor.reset();
|
|
});
|
|
|
|
describe('fillOrder', () => {
|
|
interface DydxFillResults {
|
|
makerAssetFilledAmount: BigNumber;
|
|
takerAssetFilledAmount: BigNumber;
|
|
makerFeePaid: BigNumber;
|
|
takerFeePaid: BigNumber;
|
|
amountDepositedIntoDydx: BigNumber;
|
|
amountWithdrawnFromDydx: BigNumber;
|
|
}
|
|
const fillOrder = async (
|
|
signedOrder: SignedOrder,
|
|
customTakerAssetFillAmount?: BigNumber,
|
|
): Promise<DydxFillResults> => {
|
|
// Fill order
|
|
const takerAssetFillAmount =
|
|
customTakerAssetFillAmount !== undefined ? customTakerAssetFillAmount : signedOrder.takerAssetAmount;
|
|
const tx = await taker.fillOrderAsync(signedOrder, takerAssetFillAmount);
|
|
|
|
// Extract values from fill event.
|
|
// tslint:disable no-unnecessary-type-assertion
|
|
const fillEvent = _.find(tx.logs, log => {
|
|
return (log as any).event === 'Fill';
|
|
}) as LogWithDecodedArgs<DecodedLogArgs>;
|
|
|
|
// Extract amount deposited into dydx from maker.
|
|
const dydxDepositEvent = _.find(tx.logs, log => {
|
|
return (
|
|
(log as any).event === 'OperateAction' &&
|
|
(log as any).args.actionType === DydxBridgeActionType.Deposit
|
|
);
|
|
}) as LogWithDecodedArgs<DecodedLogArgs>;
|
|
|
|
// Extract amount withdrawn from dydx to taker.
|
|
const dydxWithdrawEvent = _.find(tx.logs, log => {
|
|
return (
|
|
(log as any).event === 'OperateAction' &&
|
|
(log as any).args.actionType === DydxBridgeActionType.Withdraw
|
|
);
|
|
}) as LogWithDecodedArgs<DecodedLogArgs>;
|
|
|
|
// Return values of interest for assertions.
|
|
return {
|
|
makerAssetFilledAmount: fillEvent.args.makerAssetFilledAmount,
|
|
takerAssetFilledAmount: fillEvent.args.takerAssetFilledAmount,
|
|
makerFeePaid: fillEvent.args.makerFeePaid,
|
|
takerFeePaid: fillEvent.args.takerFeePaid,
|
|
amountDepositedIntoDydx: dydxDepositEvent.args.amountValue,
|
|
amountWithdrawnFromDydx: dydxWithdrawEvent.args.amountValue,
|
|
};
|
|
};
|
|
it('should successfully fill a dydx order (DydxBridge used in makerAssetData)', async () => {
|
|
const signedOrder = await maker.signOrderAsync({
|
|
// Invert the dydx conversion rate when using the bridge in the makerAssetData.
|
|
makerAssetData: dydxBridgeProxyAssetData,
|
|
makerAssetAmount: dydxConversionRateDenominator,
|
|
takerAssetAmount: dydxConversionRateNumerator,
|
|
});
|
|
const dydxFillResults = await fillOrder(signedOrder);
|
|
expect(
|
|
dydxFillResults.makerAssetFilledAmount,
|
|
'makerAssetFilledAmount should equal amountWithdrawnFromDydx',
|
|
).to.bignumber.equal(dydxFillResults.amountWithdrawnFromDydx);
|
|
expect(
|
|
dydxFillResults.takerAssetFilledAmount,
|
|
'takerAssetFilledAmount should equal amountDepositedIntoDydx',
|
|
).to.bignumber.equal(dydxFillResults.amountDepositedIntoDydx);
|
|
});
|
|
it('should successfully fill a dydx order (DydxBridge used in takerAssetData)', async () => {
|
|
const signedOrder = await maker.signOrderAsync({
|
|
// Match the dydx conversion rate when using the bridge in the takerAssetData.
|
|
takerAssetData: dydxBridgeProxyAssetData,
|
|
makerAssetAmount: dydxConversionRateNumerator,
|
|
takerAssetAmount: dydxConversionRateDenominator,
|
|
});
|
|
const dydxFillResults = await fillOrder(signedOrder);
|
|
expect(
|
|
dydxFillResults.makerAssetFilledAmount,
|
|
'makerAssetFilledAmount should equal amountDepositedIntoDydx',
|
|
).to.bignumber.equal(dydxFillResults.amountDepositedIntoDydx);
|
|
expect(
|
|
dydxFillResults.takerAssetFilledAmount,
|
|
'takerAssetFilledAmount should equal amountWithdrawnFromDydx',
|
|
).to.bignumber.equal(dydxFillResults.amountWithdrawnFromDydx);
|
|
});
|
|
it('should successfully fill a dydx order (DydxBridge used in makerFeeAssetData)', async () => {
|
|
const signedOrder = await maker.signOrderAsync({
|
|
// Invert the dydx conversion rate when using the bridge in the makerFeeAssetData.
|
|
makerFeeAssetData: dydxBridgeProxyAssetData,
|
|
makerFee: dydxConversionRateDenominator,
|
|
takerFee: dydxConversionRateNumerator,
|
|
});
|
|
const dydxFillResults = await fillOrder(signedOrder);
|
|
expect(
|
|
dydxFillResults.makerFeePaid,
|
|
'makerFeePaid should equal amountWithdrawnFromDydx',
|
|
).to.bignumber.equal(dydxFillResults.amountWithdrawnFromDydx);
|
|
expect(
|
|
dydxFillResults.takerFeePaid,
|
|
'takerFeePaid should equal amountDepositedIntoDydx',
|
|
).to.bignumber.equal(dydxFillResults.amountDepositedIntoDydx);
|
|
});
|
|
it('should successfully fill a dydx order (DydxBridge used in takerFeeAssetData)', async () => {
|
|
const signedOrder = await maker.signOrderAsync({
|
|
// Match the dydx conversion rate when using the bridge in the takerFeeAssetData.
|
|
takerFeeAssetData: dydxBridgeProxyAssetData,
|
|
makerFee: dydxConversionRateNumerator,
|
|
takerFee: dydxConversionRateDenominator,
|
|
});
|
|
const dydxFillResults = await fillOrder(signedOrder);
|
|
expect(
|
|
dydxFillResults.makerFeePaid,
|
|
'makerFeePaid should equal amountDepositedIntoDydx',
|
|
).to.bignumber.equal(dydxFillResults.amountDepositedIntoDydx);
|
|
expect(
|
|
dydxFillResults.takerFeePaid,
|
|
'takerFeePaid should equal amountWithdrawnFromDydx',
|
|
).to.bignumber.equal(dydxFillResults.amountWithdrawnFromDydx);
|
|
});
|
|
it('should partially fill a dydx order (DydxBridge used in makerAssetData)', async () => {
|
|
const signedOrder = await maker.signOrderAsync({
|
|
// Invert the dydx conversion rate when using the bridge in the makerAssetData.
|
|
makerAssetData: dydxBridgeProxyAssetData,
|
|
makerAssetAmount: dydxConversionRateDenominator,
|
|
takerAssetAmount: dydxConversionRateNumerator,
|
|
});
|
|
const dydxFillResults = await fillOrder(signedOrder, signedOrder.takerAssetAmount.div(2));
|
|
expect(
|
|
dydxFillResults.makerAssetFilledAmount,
|
|
'makerAssetFilledAmount should equal amountWithdrawnFromDydx',
|
|
).to.bignumber.equal(dydxFillResults.amountWithdrawnFromDydx);
|
|
expect(
|
|
dydxFillResults.takerAssetFilledAmount,
|
|
'takerAssetFilledAmount should equal amountDepositedIntoDydx',
|
|
).to.bignumber.equal(dydxFillResults.amountDepositedIntoDydx);
|
|
});
|
|
it('should revert in DydxBridge when there is a rounding error', async () => {
|
|
// This order will not trigger the rounding error in the Exchange,
|
|
// but will trigger one in the DydxBridge.
|
|
const badDepositAction = {
|
|
...defaultDepositAction,
|
|
conversionRateNumerator: new BigNumber(5318),
|
|
conversionRateDenominator: new BigNumber(47958),
|
|
};
|
|
const badBridgeData = {
|
|
accountNumbers: [new BigNumber(0)],
|
|
actions: [badDepositAction],
|
|
};
|
|
const encodedBridgeData = dydxBridgeDataEncoder.encode({ bridgeData: badBridgeData });
|
|
const badDydxBridgeProxyAssetData = encodeERC20BridgeAssetData(
|
|
testTokenAddress,
|
|
testContract.address,
|
|
encodedBridgeData,
|
|
);
|
|
const signedOrder = await maker.signOrderAsync({
|
|
makerAssetData: badDydxBridgeProxyAssetData,
|
|
makerAssetAmount: badDepositAction.conversionRateDenominator,
|
|
takerAssetAmount: badDepositAction.conversionRateNumerator,
|
|
});
|
|
const takerAssetFillAmount = new BigNumber(998);
|
|
|
|
// Compute expected error.
|
|
const target = takerAssetFillAmount
|
|
.multipliedBy(signedOrder.makerAssetAmount)
|
|
.dividedToIntegerBy(signedOrder.takerAssetAmount);
|
|
const expectedAssetProxyError = new LibMathRevertErrors.RoundingError(
|
|
signedOrder.takerAssetAmount,
|
|
signedOrder.makerAssetAmount,
|
|
target,
|
|
);
|
|
|
|
// Call fillOrder and assert the rounding error generated by the DydxBridge.
|
|
const txPromise = taker.fillOrderAsync(signedOrder, takerAssetFillAmount);
|
|
let assetProxyError;
|
|
try {
|
|
await txPromise.catch();
|
|
} catch (e) {
|
|
assetProxyError = RevertError.decode(e.values.errorData);
|
|
}
|
|
expect(assetProxyError).to.deep.equal(expectedAssetProxyError);
|
|
});
|
|
});
|
|
});
|