Rounding error tests in DydxBridgeProxy

This commit is contained in:
Greg Hysen
2019-12-19 15:53:35 -08:00
parent c1f5322d38
commit 265fa52ace
2 changed files with 148 additions and 15 deletions

View File

@@ -1,3 +1,4 @@
import { LibMathRevertErrors } from '@0x/contracts-exchange-libs';
import { blockchainTests, constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { AssetProxyId, RevertReason } from '@0x/types';
import { BigNumber } from '@0x/utils';
@@ -82,7 +83,7 @@ blockchainTests.resets('DydxBridge unit tests', env => {
.callAsync({ from: sender });
return returnValue;
};
const callBridgeTransferFromAndVerifyEvents = async (
const executeBridgeTransferFromAndVerifyEvents = async (
from: string,
to: string,
amount: BigNumber,
@@ -140,7 +141,7 @@ blockchainTests.resets('DydxBridge unit tests', env => {
accountNumbers: [defaultAccountNumber],
actions: [defaultDepositAction],
};
await callBridgeTransferFromAndVerifyEvents(
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
constants.ZERO_AMOUNT,
@@ -153,56 +154,104 @@ blockchainTests.resets('DydxBridge unit tests', env => {
accountNumbers: [],
actions: [defaultDepositAction],
};
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
defaultAmount,
bridgeData,
authorized,
);
});
it('succeeds when calling with no actions', async () => {
const bridgeData = {
accountNumbers: [defaultAccountNumber],
actions: [],
};
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
defaultAmount,
bridgeData,
authorized,
);
});
it('succeeds when calling `operate` with the `deposit` action and a single account', async () => {
const bridgeData = {
accountNumbers: [defaultAccountNumber],
actions: [defaultDepositAction],
};
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
defaultAmount,
bridgeData,
authorized,
);
});
it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => {
const bridgeData = {
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
actions: [defaultDepositAction],
};
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
defaultAmount,
bridgeData,
authorized,
);
});
it('succeeds when calling `operate` with the `withdraw` action and a single account', async () => {
const bridgeData = {
accountNumbers: [defaultAccountNumber],
actions: [defaultWithdrawAction],
};
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
defaultAmount,
bridgeData,
authorized,
);
});
it('succeeds when calling `operate` with the `withdraw` action and multiple accounts', async () => {
const bridgeData = {
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
actions: [defaultWithdrawAction],
};
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
defaultAmount,
bridgeData,
authorized,
);
});
it('succeeds when calling `operate` with the `deposit` action and multiple accounts', async () => {
const bridgeData = {
accountNumbers: [defaultAccountNumber, defaultAccountNumber.plus(1)],
actions: [defaultWithdrawAction, defaultDepositAction],
};
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
defaultAmount,
bridgeData,
authorized,
);
});
it('succeeds when calling `operate` with multiple actions under a single account', async () => {
const bridgeData = {
accountNumbers: [defaultAccountNumber],
actions: [defaultWithdrawAction, defaultDepositAction],
};
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
defaultAmount,
bridgeData,
authorized,
);
});
it('succeeds when scaling the `amount` to deposit', async () => {
const conversionRateNumerator = new BigNumber(1);
@@ -218,7 +267,13 @@ blockchainTests.resets('DydxBridge unit tests', env => {
},
],
};
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
defaultAmount,
bridgeData,
authorized,
);
});
it('succeeds when scaling the `amount` to withdraw', async () => {
const conversionRateNumerator = new BigNumber(1);
@@ -234,7 +289,13 @@ blockchainTests.resets('DydxBridge unit tests', env => {
},
],
};
await callBridgeTransferFromAndVerifyEvents(accountOwner, receiver, defaultAmount, bridgeData, authorized);
await executeBridgeTransferFromAndVerifyEvents(
accountOwner,
receiver,
defaultAmount,
bridgeData,
authorized,
);
});
it('reverts if not called by the ERC20 Bridge Proxy', async () => {
const bridgeData = {
@@ -278,6 +339,32 @@ blockchainTests.resets('DydxBridge unit tests', env => {
const expectedError = 'TestDydxBridge/SHOULD_REVERT_ON_OPERATE';
return expect(tx).to.revertWith(expectedError);
});
it('should revert when there is a rounding error', async () => {
// Setup a rounding error
const conversionRateNumerator = new BigNumber(5318);
const conversionRateDenominator = new BigNumber(47958);
const amount = new BigNumber(9000);
const bridgeData = {
accountNumbers: [defaultAccountNumber],
actions: [
defaultDepositAction,
{
...defaultWithdrawAction,
conversionRateNumerator,
conversionRateDenominator,
},
],
};
// Execute transfer and assert error.
const tx = callBridgeTransferFrom(accountOwner, receiver, amount, bridgeData, authorized);
const expectedError = new LibMathRevertErrors.RoundingError(
conversionRateNumerator,
conversionRateDenominator,
amount,
);
return expect(tx).to.revertWith(expectedError);
});
});
describe('ERC20BridgeProxy.transferFrom()', () => {

View File

@@ -1,8 +1,9 @@
import { DydxBridgeActionType, dydxBridgeDataEncoder, 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 } from '@0x/utils';
import { BigNumber, RevertError } from '@0x/utils';
import { DecodedLogArgs, LogWithDecodedArgs } from 'ethereum-types';
import * as _ from 'lodash';
@@ -23,6 +24,8 @@ blockchainTests.resets('Exchange fills dydx orders', env => {
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,
@@ -44,12 +47,12 @@ blockchainTests.resets('Exchange fills dydx orders', env => {
before(async () => {
// Deploy contracts
const deployment = await DeploymentManager.deployAsync(env, {
deployment = await DeploymentManager.deployAsync(env, {
numErc20TokensToDeploy: 2,
});
testContract = await deployDydxBridgeAsync(deployment, env);
const encodedBridgeData = dydxBridgeDataEncoder.encode({ bridgeData });
const testTokenAddress = await testContract.getTestToken().callAsync();
testTokenAddress = await testContract.getTestToken().callAsync();
dydxBridgeProxyAssetData = deployment.assetDataEncoder
.ERC20Bridge(testTokenAddress, testContract.address, encodedBridgeData)
.getABIEncodedTransactionData();
@@ -228,5 +231,48 @@ blockchainTests.resets('Exchange fills dydx orders', env => {
'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 = deployment.assetDataEncoder
.ERC20Bridge(testTokenAddress, testContract.address, encodedBridgeData)
.getABIEncodedTransactionData();
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);
});
});
});