Added fix for decimals

This commit is contained in:
Daniel Pyrathon 2020-10-05 17:20:43 -07:00
parent 1588c1f362
commit 83c942ad8c
8 changed files with 88 additions and 11 deletions

View File

@ -20,6 +20,7 @@ pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
@ -36,6 +37,19 @@ contract NativeOrderSampler {
/// @dev Gas limit for calls to `getOrderFillableTakerAmount()`.
uint256 constant internal DEFAULT_CALL_GAS = 200e3; // 200k
function getTokenDecimals(
address makerTokenAddress,
address takerTokenAddress
)
public
view
returns (uint256, uint256)
{
uint256 fromTokenDecimals = LibERC20Token.decimals(makerTokenAddress);
uint256 toTokenDecimals = LibERC20Token.decimals(takerTokenAddress);
return (fromTokenDecimals, toTokenDecimals);
}
/// @dev Queries the fillable taker asset amounts of native orders.
/// Effectively ignores orders that have empty signatures or
/// maker/taker asset amounts (returning 0).

View File

@ -382,3 +382,5 @@ export interface SamplerOverrides {
overrides: GethCallOverrides;
block: BlockParam;
}
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

View File

@ -28,6 +28,7 @@ import {
import { findOptimalPathAsync } from './path_optimizer';
import { DexOrderSampler, getSampleAmounts } from './sampler';
import { SourceFilters } from './source_filters';
import { Omit } from '../../types';
import {
AggregationError,
DexSample,
@ -41,6 +42,7 @@ import {
OrderDomain,
TokenAdjacencyGraph,
} from './types';
import { Web3Wrapper } from '@0x/dev-utils';
// tslint:disable:boolean-naming
@ -153,6 +155,7 @@ export class MarketOperationUtils {
// Call the sampler contract.
const samplerPromise = this._sampler.executeAsync(
this._sampler.getTokenDecimals(makerToken, takerToken),
// Get native order fillable amounts.
this._sampler.getOrderFillableTakerAmounts(nativeOrders, this.contractAddresses.exchange),
// Get ETH -> maker token price.
@ -205,11 +208,12 @@ export class MarketOperationUtils {
: Promise.resolve([]);
const [
[orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
[tokenDecimals, orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
offChainBalancerQuotes,
offChainBancorQuotes,
] = await Promise.all([samplerPromise, offChainBalancerPromise, offChainBancorPromise]);
const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
return {
side: MarketOperation.Sell,
inputAmount: takerAmount,
@ -223,6 +227,8 @@ export class MarketOperationUtils {
rfqtIndicativeQuotes: [],
twoHopQuotes,
quoteSourceFilters,
makerTokenDecimals: makerTokenDecimals.toNumber(),
takerTokenDecimals: takerTokenDecimals.toNumber(),
};
}
@ -259,6 +265,7 @@ export class MarketOperationUtils {
// Call the sampler contract.
const samplerPromise = this._sampler.executeAsync(
this._sampler.getTokenDecimals(makerToken, takerToken),
// Get native order fillable amounts.
this._sampler.getOrderFillableMakerAmounts(nativeOrders, this.contractAddresses.exchange),
// Get ETH -> makerToken token price.
@ -306,13 +313,14 @@ export class MarketOperationUtils {
: Promise.resolve([]);
const [
[orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
[tokenDecimals, orderFillableAmounts, ethToMakerAssetRate, ethToTakerAssetRate, dexQuotes, twoHopQuotes],
offChainBalancerQuotes,
] = await Promise.all([samplerPromise, offChainBalancerPromise]);
// Attach the MultiBridge address to the sample fillData
(dexQuotes.find(quotes => quotes[0] && quotes[0].source === ERC20BridgeSource.MultiBridge) || []).forEach(
q => (q.fillData = { poolAddress: this._multiBridge }),
);
const [makerTokenDecimals, takerTokenDecimals] = tokenDecimals;
return {
side: MarketOperation.Buy,
inputAmount: makerAmount,
@ -326,6 +334,8 @@ export class MarketOperationUtils {
rfqtIndicativeQuotes: [],
twoHopQuotes,
quoteSourceFilters,
makerTokenDecimals: makerTokenDecimals.toNumber(),
takerTokenDecimals: takerTokenDecimals.toNumber(),
};
}
@ -462,7 +472,7 @@ export class MarketOperationUtils {
}
public async _generateOptimizedOrdersAsync(
marketSideLiquidity: MarketSideLiquidity,
marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>,
opts: GenerateOptimizedOrdersOpts,
): Promise<OptimizerResult> {
const {
@ -596,8 +606,10 @@ export class MarketOperationUtils {
if (optimizerResult) {
const totalMakerAmount = BigNumber.sum(...optimizerResult.optimizedOrders.map(order => order.makerAssetAmount));
const totalTakerAmount = BigNumber.sum(...optimizerResult.optimizedOrders.map(order => order.takerAssetAmount));
if (totalTakerAmount.gt(0)) {
comparisonPrice = totalMakerAmount.div(totalTakerAmount);
if (totalMakerAmount.gt(0)) {
const totalMakerAmountUnitAmount = Web3Wrapper.toUnitAmount(totalMakerAmount, marketSideLiquidity.makerTokenDecimals);
const totalTakerAmountUnitAmount = Web3Wrapper.toUnitAmount(totalTakerAmount, marketSideLiquidity.takerTokenDecimals);
comparisonPrice = totalTakerAmountUnitAmount.div(totalMakerAmountUnitAmount);
}
}

View File

@ -4,6 +4,7 @@ import * as _ from 'lodash';
import { ZERO_AMOUNT } from './constants';
import { getTwoHopAdjustedRate } from './fills';
import { DexSample, FeeSchedule, MarketSideLiquidity, MultiHopFillData, TokenAdjacencyGraph } from './types';
import { Omit } from '../../types';
/**
* Given a token pair, returns the intermediate tokens to consider for two-hop routes.
@ -34,7 +35,7 @@ export function getIntermediateTokens(
* Returns the best two-hop quote and the fee-adjusted rate of that quote.
*/
export function getBestTwoHopQuote(
marketSideLiquidity: MarketSideLiquidity,
marketSideLiquidity: Omit<MarketSideLiquidity, 'makerTokenDecimals' | 'takerTokenDecimals'>,
feeSchedule?: FeeSchedule,
): { quote: DexSample<MultiHopFillData> | undefined; adjustedRate: BigNumber } {
const { side, inputAmount, ethToOutputRate, twoHopQuotes } = marketSideLiquidity;

View File

@ -88,6 +88,15 @@ export class SamplerOperations {
return this._bancorService;
}
public getTokenDecimals(makerTokenAddress: string, takerTokenAddress: string): BatchedOperation<BigNumber[]> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Native,
contract: this._samplerContract,
function: this._samplerContract.getTokenDecimals,
params: [makerTokenAddress, takerTokenAddress],
});
}
public getOrderFillableTakerAmounts(orders: SignedOrder[], exchangeAddress: string): BatchedOperation<BigNumber[]> {
return new SamplerContractOperation({
source: ERC20BridgeSource.Native,

View File

@ -351,6 +351,8 @@ export interface MarketSideLiquidity {
rfqtIndicativeQuotes: RFQTIndicativeQuote[];
twoHopQuotes: Array<DexSample<MultiHopFillData>>;
quoteSourceFilters: SourceFilters;
makerTokenDecimals: number;
takerTokenDecimals: number;
}
export interface TokenAdjacencyGraph {

View File

@ -1,3 +1,4 @@
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
import {
assertIntegerRoughlyEquals,
blockchainTests,
@ -130,6 +131,36 @@ blockchainTests.resets('NativeOrderSampler contract', env => {
.awaitTransactionSuccessAsync();
}
describe('getTokenDecimals()', () => {
it('correctly returns the token balances', async () => {
const newMakerToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
erc20Artifacts.DummyERC20Token,
env.provider,
env.txDefaults,
artifacts,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
new BigNumber(18),
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
);
const newTakerToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
erc20Artifacts.DummyERC20Token,
env.provider,
env.txDefaults,
artifacts,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
new BigNumber(6),
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
);
const [makerDecimals, takerDecimals] = await testContract
.getTokenDecimals(newMakerToken.address, newTakerToken.address)
.callAsync();
expect(makerDecimals.toString()).to.eql('18');
expect(takerDecimals.toString()).to.eql('6');
});
});
describe('getOrderFillableTakerAmount()', () => {
it('returns the full amount for a fully funded order', async () => {
const order = createOrder();

View File

@ -385,6 +385,10 @@ describe('MarketOperationUtils tests', () => {
};
const DEFAULT_OPS = {
getTokenDecimals(_makerAddress: string, _takerAddress: string): BigNumber[] {
const result = new BigNumber(18);
return [result, result];
},
getOrderFillableTakerAmounts(orders: SignedOrder[]): BigNumber[] {
return orders.map(o => o.takerAssetAmount);
},
@ -737,7 +741,7 @@ describe('MarketOperationUtils tests', () => {
signedOrder: createOrder({
makerAssetData: MAKER_ASSET_DATA,
takerAssetData: TAKER_ASSET_DATA,
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(321, 18),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(321, 6),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
}),
},
@ -764,7 +768,7 @@ describe('MarketOperationUtils tests', () => {
return {
dexQuotes: [],
ethToInputRate: Web3Wrapper.toBaseUnitAmount(1, 18),
ethToOutputRate: Web3Wrapper.toBaseUnitAmount(1, 18),
ethToOutputRate: Web3Wrapper.toBaseUnitAmount(1, 6),
inputAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
inputToken: MAKER_TOKEN,
outputToken: TAKER_TOKEN,
@ -772,7 +776,7 @@ describe('MarketOperationUtils tests', () => {
createOrder({
makerAssetData: MAKER_ASSET_DATA,
takerAssetData: TAKER_ASSET_DATA,
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(320, 18),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(320, 6),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 18),
}),
],
@ -781,6 +785,8 @@ describe('MarketOperationUtils tests', () => {
side: MarketOperation.Sell,
twoHopQuotes: [],
quoteSourceFilters: new SourceFilters(),
makerTokenDecimals: 6,
takerTokenDecimals: 18,
};
})
const result = await mockedMarketOpUtils.object.getMarketSellOrdersAsync(ORDERS, Web3Wrapper.toBaseUnitAmount(1, 18), {
@ -796,8 +802,8 @@ describe('MarketOperationUtils tests', () => {
}
});
expect(result.optimizedOrders.length).to.eql(1);
expect(requestedComparisonPrice!.toString()).to.eql("320");
expect(result.optimizedOrders[0].makerAssetAmount.toString()).to.eql('321000000000000000000');
expect(requestedComparisonPrice!.toString()).to.eql("0.003125");
expect(result.optimizedOrders[0].makerAssetAmount.toString()).to.eql('321000000');
expect(result.optimizedOrders[0].takerAssetAmount.toString()).to.eql('1000000000000000000');
});