Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

BalancerBridge #2613

Merged
merged 24 commits into from
Jul 15, 2020
Merged

BalancerBridge #2613

merged 24 commits into from
Jul 15, 2020

Conversation

moodlezoup
Copy link
Contributor

@moodlezoup moodlezoup commented Jun 26, 2020

Description

Adds the BalancerBridge contract and corresponding AssetSwapper support.
AssetSwapper uses the Balancer SOR package to get pool addresses and compute quotes off-chain.
Also includes some related AssetSwapper refactors, most notably merging all Curves into a single ERC20BridgeSource. To account for the fact that different Curves have significantly different gas costs, we change the fee schedule so that each source is given a fee estimate function instead of a constant.

Testing instructions

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist:

  • Prefix PR title with [WIP] if necessary.
  • Add tests to cover changes as needed.
  • Update documentation as needed.
  • Add new entries to the relevant CHANGELOG.jsons.

@moodlezoup moodlezoup force-pushed the feature/balancer-bridge branch 2 times, most recently from 8948fb8 to 61d4d0b Compare July 1, 2020 08:09
Copy link
Contributor

@hysz hysz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have full context on the client-side code but the Solidity LGTM! We should probably add some basic tests before merging. Esp since simbot is not currently part of the continuous integration tests, we'll want some kind of sanity checks in here to future proof the implementation.

@moodlezoup moodlezoup force-pushed the feature/balancer-bridge branch from e848669 to f93cb28 Compare July 1, 2020 21:42
@moodlezoup moodlezoup requested a review from xianny July 1, 2020 21:58
Copy link
Contributor

@xianny xianny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice job with the multiple quotes per source!!

bytes calldata bridgeData
)
external
freesGasTokensFromCollector
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed and I should really roll back the change in Curve Bridge development. We technically downgraded the CurveBridge address to the older non-burning equivalent when we shipped DFB.

We now either free from DFB or in the future FQT.

case ERC20BridgeSource.CurveUsdcDaiUsdtTusd:
case ERC20BridgeSource.CurveUsdcDaiUsdtBusd:
case ERC20BridgeSource.CurveUsdcDaiUsdtSusd:
case ERC20BridgeSource.Curve:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johnrjj FYI Sources might start coming back as Curve instead of Curve_DAI_USDC and UniswapV2 instead of UniswapV2_ETH. Was the change made in matcha to split on _ and take the first element?

curveFillData.poolAddress,
curveFillData.fromTokenIdx,
curveFillData.toTokenIdx,
1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One day I'll remove this version hack. Then Ren BTC Curve introduces a new "version" 😂

@moodlezoup moodlezoup force-pushed the feature/balancer-bridge branch from 8316c01 to ce6b665 Compare July 2, 2020 01:02
Copy link
Contributor

@dorothy-zbornak dorothy-zbornak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really digging this refactor. Hopefully this leads to many more.

await testContract
.bridgeTransferFrom(usdcAddress, constants.NULL_ADDRESS, receiver, constants.ZERO_AMOUNT, bridgeData)
.awaitTransactionSuccessAsync({ from: CHONKY_WETH_WALLET, gasPrice: 0 }, { shouldValidate: false });
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these tests check that the receiver actually received something?

FakeBuyOpts,
SourceQuoteOperation,
UniswapV2FillData,
} from './types';

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting to feel like this needs to be a class so it can hold state like registry addresses and cached balancer pools and calls to some of these ops can be more legible. I think it'll clean up some of the tests as well.

@moodlezoup moodlezoup force-pushed the feature/balancer-bridge branch 2 times, most recently from 9272410 to 181e67d Compare July 2, 2020 06:45
@xianny xianny force-pushed the feature/balancer-bridge branch 2 times, most recently from fb7fa55 to 0b0a52d Compare July 2, 2020 22:29
@moodlezoup moodlezoup force-pushed the feature/balancer-bridge branch 2 times, most recently from 160b52b to 818fac1 Compare July 7, 2020 03:22
@moodlezoup moodlezoup marked this pull request as ready for review July 7, 2020 19:35
Copy link
Contributor

@dorothy-zbornak dorothy-zbornak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still going through tests

Comment on lines 98 to 102
this._wethAddress,
this._sampler.balancerPoolsCache,
this._liquidityProviderRegistry,
this._multiBridge,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this arg list getting ridic
def need to make DexOrderSampler ops a class during refactors

@@ -148,12 +145,12 @@ export function fillQuoteOrders(
if (remainingInput.lte(0)) {
break;
}
result.gas += getTotalGasUsedByFills(fo.order.fills, gasSchedule);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this calculation moved here? Isn't this wrong? We might not need to hit all the fills/sources.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, you're right

public async getPoolsForPairAsync(takerToken: string, makerToken: string): Promise<BalancerPool[]> {
const key = JSON.stringify([takerToken, makerToken].sort());
const value = this._cache[key];
const minTimestamp = new Date().getTime() - this.cacheExpiryMs;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const minTimestamp = new Date().getTime() - this.cacheExpiryMs;
const minTimestamp = Date.now() - this.cacheExpiryMs;

const minTimestamp = new Date().getTime() - this.cacheExpiryMs;
if (value.timestamp < minTimestamp) {
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
const timestamp = new Date().getTime();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const timestamp = new Date().getTime();
const timestamp = Date.now();

Copy link
Member

@dekz dekz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also seeing a relatively high revert rate.

Some of them are likely explained in the routing being very similar to the following:

	Uniswap: 99.99%, Uniswap_V2: 6e-16%

Definitely not worth filling Uniswap_V2 for a tiny fraction of a $600 trade.

Below is what I'm seeing locally over MKR,USDC,DAI,WETH,WBTC.

image

image

const value = this._cache[key];
const minTimestamp = Date.now() - this.cacheExpiryMs;
if (value === undefined || value.timestamp < minTimestamp) {
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep only the top 10 pools when sorted by in/out balances?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah probably –– I guess top 10 by out balances makes sense?

difference(FEE_QUOTE_SOURCES.concat(this._optionalSources()), _opts.excludedSources),
makerToken,
this._wethAddress,
ONE_ETHER,
this._wethAddress,
this._sampler.balancerPoolsCache,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh this probably doesn't need to be a parameter and can be found out of global space?
Like BalancerPoolCache.getInstance().blah( the others are parameters as they change on network or are provided as variables ALL the way down the stack. This feels less like that?

Maybe it's easier to test when it's injected in but I have a feeling we aren't testing this explicitly.

constructor(
private readonly _samplerContract: IERC20BridgeSamplerContract,
public balancerPoolsCache: BalancerPoolsCache = new BalancerPoolsCache(),
) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above imo

public cacheExpiryMs: number = THIRTY_MINUTES_MS,
) {}
public async getPoolsForPairAsync(takerToken: string, makerToken: string): Promise<BalancerPool[]> {
const key = JSON.stringify([takerToken, makerToken].sort());
Copy link
Member

@dekz dekz Jul 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const key = JSON.stringify([takerToken, makerToken].sort());
const key = JSON.stringify([takerToken, makerToken]);

is balanceIn relative to the first argument in fetchPoolsForPair? And sorting these would result in an invalid rate in the opposite direction?

E.g

query DAI, WETH, balanceIn is DAI. Result is stored as DAI, WETH.
query WETH, DAI, balanceIn is WETH. Result is already stored and retrieved from DAI, WETH`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, you're right

timestamp: number;
pools: BalancerPool[];
}
const THIRTY_MINUTES_MS = 30 * 60 * 1000; // tslint:disable-line:custom-no-magic-numbers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO 30 mins is fine for a cache of existing pools. I.e a new pool added takes 30m till it is served by AssetSwapper.

30 mins is too long for rates within the pool and determining the route based on these values. Especially with sub 1% slippage.

I'd suggest we cache for at most a minute and move towards a model where the pools are discovered (cached) but queried on-demand.

Copy link
Contributor Author

@moodlezoup moodlezoup Jul 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I do wonder if it'd be worth differentiating pool discovery vs pool state. idk if there would be a substantial difference in latency between querying the state of the cached pool addresses versus calling getPoolsWithTokens

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I assume TheGraph is fairly quick, which is fine for pool discovery.

But ultimately I think we'd want to get the rates as live as possible to reduce our potential for revert.

@moodlezoup moodlezoup force-pushed the feature/balancer-bridge branch 3 times, most recently from 3b63749 to a2fa30f Compare July 9, 2020 21:54
@@ -34,4 +34,7 @@ if (isNode) {
};
}

const orig = BigNumber.config;
BigNumber.config = (_args?: any) => orig({});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably document why we do this and also clobber BigNumber.set().

Also, maybe this is neater?

(orig => BigNumber.config = (...args: any) => orig({}))(BigNumber.config);
BigNumber.set = BigNumber.config;

Comment on lines +575 to +576
const samplerOps = subOps.filter(op => op.source !== ERC20BridgeSource.Balancer);
const nonSamplerOps = subOps.filter(op => op.source === ERC20BridgeSource.Balancer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change just to reduce the load on the sampler contract?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the sampler was reverting because batchCall didn't know what to do with empty bytes. we could change batchCall instead to skip over if callDatas[i] == 0, but 🤷

b.balanceOut.minus(a.balanceOut).toNumber(),
);
// tslint:disable-next-line:custom-no-magic-numbers
return pools.length > 10 ? pools.slice(0, 10) : pools;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth putting 10 into a constant.

@moodlezoup moodlezoup force-pushed the feature/balancer-bridge branch from dbe595c to 529905e Compare July 10, 2020 17:10
@moodlezoup moodlezoup force-pushed the feature/balancer-bridge branch from e2eb3a1 to 04839d3 Compare July 11, 2020 01:46
@moodlezoup moodlezoup force-pushed the feature/balancer-bridge branch from 77306e2 to b4bee37 Compare July 14, 2020 21:00
Copy link
Contributor

@dorothy-zbornak dorothy-zbornak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good. Can backlog my comments if you're under pressure.

import { Fill } from './types';

// tslint:disable: prefer-for-of custom-no-magic-numbers completed-docs

const RUN_LIMIT_DECAY_FACTOR = 0.8;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder how detrimental 0.5 would be...

const rfqtPromise = getRfqtIndicativeQuotesAsync(
nativeOrders[0].makerAssetData,
nativeOrders[0].takerAssetData,
MarketOperation.Sell,
takerAmount,
_opts,
);

const balancerPromise = this._sampler.executeAsync(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like balancer shouldn't belong to the sampler anymore?

@xianny xianny merged commit ff9c924 into development Jul 15, 2020
@xianny xianny deleted the feature/balancer-bridge branch July 15, 2020 02:18
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants