Skip to content
This repository has been archived by the owner on Apr 28, 2022. It is now read-only.

Balancer Event Handler for WeightedPool2TokenFactory #717

Merged
merged 18 commits into from
Jun 16, 2021
Merged

Conversation

bh2smith
Copy link
Contributor

@bh2smith bh2smith commented Jun 12, 2021

Disclaimer

Not yet satisfied with the amount of code duplication and looking for some assistance. I believe the reason for the duplication stems from this issue in ethcontract-rs, but I may be wrong. Maybe we could have some sort of enum ContractRouter that can distinguish between the two different factories and somehow reduce this redundancy...

Change Summary

  • Vendoring WeightedPool2TokensFactory contract artifacts
  • Updating BalancerEventUpdater to stream events from both factories and store all pools accordingly.

Test Plan

You can try this out yourself in another branch (cf #710) by running

cargo run --bin orderbook -- --node-url YOUR-NODE-URL --log-filter shared=info

Results are as follows:

2021-06-12T07:33:36.139Z  Last updated block by type (0, 12363041) - loaded 1 balancer pools
2021-06-12T07:33:37.111Z  Last updated block by type (0, 12363659) - loaded 2 balancer pools
2021-06-12T07:33:41.937Z  Last updated block by type (0, 12367800) - loaded 3 balancer pools
2021-06-12T07:33:46.087Z  Last updated block by type (0, 12368069) - loaded 5 balancer pools
2021-06-12T07:33:48.539Z  Last updated block by type (0, 12369384) - loaded 10 balancer pools
2021-06-12T07:33:50.667Z  Last updated block by type (0, 12369886) - loaded 23 balancer pools
2021-06-12T07:33:51.912Z  Last updated block by type (0, 12370108) - loaded 32 balancer pools
2021-06-12T07:34:01.746Z  Last updated block by type (0, 12377374) - loaded 33 balancer pools
2021-06-12T07:35:16.734Z  Last updated block by type (0, 12437421) - loaded 34 balancer pools
2021-06-12T07:35:27.039Z  Last updated block by type (0, 12445005) - loaded 35 balancer pools
2021-06-12T07:35:54.637Z  Last updated block by type (0, 12454935) - loaded 36 balancer pools
2021-06-12T07:37:18.496Z  Last updated block by type (0, 12504909) - loaded 37 balancer pools
2021-06-12T07:37:24.432Z  Last updated block by type (0, 12510588) - loaded 39 balancer pools
2021-06-12T07:38:19.137Z  Last updated block by type (0, 12549331) - loaded 41 balancer pools
2021-06-12T07:40:12.793Z  Last updated block by type (0, 12616747) - loaded 44 balancer pools
2021-06-12T07:40:16.305Z  Last updated block by type (0, 12617821) - loaded 47 balancer pools
2021-06-12T07:40:32.042Z  Last updated block by type (12285749, 12617821) - loaded 48 balancer pools
2021-06-12T07:40:36.559Z  Last updated block by type (12286276, 12617821) - loaded 50 balancer pools
2021-06-12T07:40:50.000Z  Last updated block by type (12297008, 12617821) - loaded 51 balancer pools
2021-06-12T07:40:51.431Z  Last updated block by type (12299382, 12617821) - loaded 52 balancer pools
2021-06-12T07:40:57.932Z  Last updated block by type (12303593, 12617821) - loaded 53 balancer pools
2021-06-12T07:40:58.779Z  Last updated block by type (12304403, 12617821) - loaded 54 balancer pools
2021-06-12T07:41:05.838Z  Last updated block by type (12318074, 12617821) - loaded 55 balancer pools
2021-06-12T07:41:16.111Z  Last updated block by type (12331542, 12617821) - loaded 56 balancer pools
2021-06-12T07:41:16.547Z  Last updated block by type (12331765, 12617821) - loaded 57 balancer pools
2021-06-12T07:41:56.808Z  Last updated block by type (12376911, 12617821) - loaded 58 balancer pools
2021-06-12T07:44:46.556Z  Last updated block by type (12505354, 12617821) - loaded 59 balancer pools
2021-06-12T07:44:50.384Z  Last updated block by type (12509636, 12617821) - loaded 60 balancer pools
2021-06-12T07:46:04.959Z  Last updated block by type (12563369, 12617821) - loaded 61 balancer pools
2021-06-12T07:46:13.894Z  Last updated block by type (12567967, 12617821) - loaded 62 balancer pools
2021-06-12T07:46:37.792Z  Last updated block by type (12595481, 12617821) - loaded 63 balancer pools
2021-06-12T07:46:38.046Z  Last updated block by type (12595681, 12617821) - loaded 64 balancer pools
2021-06-12T07:46:41.543Z  Last updated block by type (12602530, 12617821) - loaded 67 balancer pools

@bh2smith bh2smith requested a review from a team June 12, 2021 04:59
Copy link
Contributor

@fleupold fleupold left a comment

Choose a reason for hiding this comment

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

Would it make sense to have two even handler classes (one for WeightedTwoToken pools and another one for WeightedGeneral pools)? This way we might make the class generic on the Event type (to reduce code duplication) and only implement the conversion from WeightedTwoTokenEvent to RegisteredWeightedPool and WeightedGeneralEvent to RegisteredWeightedPool. This way the event handler itself wouldn't have to take the event type as an argument any more (and do custom logic in the deletion/insertion of events)

For the consumer that wants to get all RegisteredWeightedPools, we could have an aggregator fetching all RegisteredWeightedPool from the two event processing classes.

Just a thought, will leave it up to you and @nlordell to decide on a design. It might also be worth while thinking ahead of how different pools will eventually map to Liquidity types that we pass in to the solver. Maybe that influences the choice of data structures?

#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum PoolType {
WeightedGeneral,
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the difference in how the services will handle a general vs a weighted two token pool? Asking, because it's not yet clear to me why we need to differentiate between the two (I assume they have different methods for swapping of fetching 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.

In this particular case, both types are the same as (far as I know). However, we can be certain that the other pool types will need to be differentiated for, at least, exactly the reason you mentioned (i.e. possible different transaction encoding and pool arithmetic).

In this PR, I partially kept things separate like this mostly in preparation for what it to come. However, it is very likely that both WeightedPool and WeightedPool2Tokens don't actually need to be separated.

Copy link
Contributor

Choose a reason for hiding this comment

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

How is the pool arithmetic different between general weighted pools and weighted two token pools? For swapping, I was under the impression that all we need is the poolId and the desired amounts.

Copy link
Contributor Author

@bh2smith bh2smith Jun 14, 2021

Choose a reason for hiding this comment

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

I believe I said they are not different at all.

In this particular case, both types are the same

WeightedPool and WeightedPool2Tokens don't actually need to be separated.

What I was trying to convey is that the arithmetic might be different for pools of other types.

This pool type distinction is merely introduced here in preparation for the other pool types to come.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see (sorry for misreading). My general feeling is that these other types might look much different (e.g. they may not have weights on them) and so they might have to be a different struct altogether? Maybe it would be helpful to draw up just the data model draft for how we want to represent all balancer pools eventually?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, certainly we will want to do this. However, we have decided that at the particular moment, we will remove the pool type since these two pools don't require it and only to reintroduce this when it is absolutely necessary. With this in mind, I will sketch up a description of how each are different/handled internally when the time comes.

Copy link
Contributor

@nlordell nlordell Jun 14, 2021

Choose a reason for hiding this comment

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

From a cursory glance at the Vault code, they behave the same from the caller's point of view. The difference is only done to enable internal optimiations (2Token vs MinSwapInfo pools). THis likely means we can consider them the exact same from the Baseline solver point of view.

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 ultimately we would want something like:

enum BalancerPool {
  Weighted(WeightedPool),
  Stable(StablePool),
  // ...
}

Copy link
Contributor Author

@bh2smith bh2smith Jun 15, 2021

Choose a reason for hiding this comment

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

There are only two (ish) pool types WeightedPool (including TwoTokens) and StablePool.

The transaction encoding is likely the same for both since we perform swaps via the Vault contract
While the arithmetic is different for each since Stable Pools use StableMath library while Weighted Pools uses WeightedMath.

This means we will need to introduce a PoolType enum (as @nlordell suggested above) down the road (i.e. when we include StablePools) that the price estimating will need to distinguish between.

To elaborate a bit more on Stable Pools and how they differ from our current WeightedPool struct:

Stable Pools do not have weights, but they do have an amplificationParameter (which is not part of the weighted pools) that is passed though the methods _onSwapGivenIn and _onSwapGivenOut. This amplification parameter changes on everyblock and can be fetched via a public view method getAmplificationParameter.

Specifically, these methods call into

StableMath._calcOutGivenIn(currentAmp, balances, indexIn, indexOut, swapRequest.amount)

We note that generically getAmplificationParameter could be evaluated from the value passed into the constructor and the current block height, but there are some manual overrides to the value so we probably shouldn't attempt to compute its current value manually.

With this in mind I propose that

pub struct RegisteredStablePool {
    pub pool_id: H256,
    pub pool_address: H160,
    pub tokens: Vec<H160>,
    pub scaling_exponents: Vec<u8>,
    block_created: u64,
}

Which is essentially the same as RegisteredWeightedPool without the weights.

Our PoolRegistry Would stay similar except that We would replace the values of the pools hashmap with BalancerPool where

enum BalancerPool {
  Weighted(WeightedPool),
  Stable(StablePool),
}

The PoolFetching, which is responsible for getting pool reserves, would then also need to get the current Amplification value whenever it is faced with a StablePool.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created an issue and linked it here to track the collected information #733

Copy link
Contributor

@nlordell nlordell left a comment

Choose a reason for hiding this comment

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

One thing I didn't think of was the having multiple contracts act on the same EventStoring causes issues when replacing events. I like @fleupold's suggestions.

shared/src/balancer/event_handler.rs Outdated Show resolved Hide resolved
shared/src/balancer/event_handler.rs Outdated Show resolved Hide resolved
shared/src/balancer/event_handler.rs Outdated Show resolved Hide resolved
@bh2smith
Copy link
Contributor Author

I have reworked the implementation here having one DynPoolRegistry for each event handler and then a method to fetch the merged version of the two. Unfortunately at this moment we are stuck with passing the entire BalancerEventUpdater struct onto PoolFetching. I really hope there is a better way to extract the "Database" from the event updater.

@bh2smith bh2smith requested review from fleupold and nlordell June 15, 2021 10:50
Copy link
Contributor

@nlordell nlordell left a comment

Choose a reason for hiding this comment

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

Looks great!

Just requesting changes as I believe that the use of HashSet for a collection of RegisteredWeightedPool is slightly incorrect. I think the same applies for:

pub fn pools_containing_token_pair(
&self,
token_pair: TokenPair,
) -> HashSet<RegisteredWeightedPool> {
let empty_set = HashSet::new();
let pools_0 = self
.pools_by_token
.get(&token_pair.get().0)
.unwrap_or(&empty_set);
let pools_1 = self
.pools_by_token
.get(&token_pair.get().1)
.unwrap_or(&empty_set);
pools_0
.intersection(pools_1)
.into_iter()
.map(|pool_id| {
self.pools
.get(pool_id)
.expect("failed iterating over known pools")
.clone()
})
.collect::<HashSet<RegisteredWeightedPool>>()
}

Which I missed when reviewing #698 (here, the pools are already unique by pool ID so there is no need to collect them in a HashSet and a Vec makes more sense).

shared/src/balancer/event_handler.rs Outdated Show resolved Hide resolved
shared/src/balancer/event_handler.rs Outdated Show resolved Hide resolved
@bh2smith bh2smith requested a review from nlordell June 15, 2021 15:54
@bh2smith bh2smith mentioned this pull request Jun 15, 2021
9 tasks
@bh2smith
Copy link
Contributor Author

bh2smith commented Jun 15, 2021

Also added an issue (#734) to include some documentation of this module (but I would prefer to wait until) some of the open PRs has settled (particularly this and #722). I hope this is a satisfactory approach @fleupold.

BalancerV2WeightedPoolFactoryContract(weighted_pool_factory),
PoolStorage::new(Box::new(PoolDataFetcher {
web3: web3.clone(),
token_info_fetcher: token_info_fetcher.clone(),
Copy link
Contributor Author

@bh2smith bh2smith Jun 15, 2021

Choose a reason for hiding this comment

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

Is cloning the correct way to pass the token info fetcher into both event handlers?

Copy link
Contributor

@nlordell nlordell Jun 16, 2021

Choose a reason for hiding this comment

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

It is behind an Arc (i.e. Atomically Reference Counted pointer), so cloning is a very cheap operation (it just increases the reference count - i.e. the number of "owners" to the inner data).

It is the correct way of sharing an Arc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool, now I am just waiting on @fleupold's final thoughts and I would like to ship-it.

@bh2smith bh2smith merged commit df92d98 into main Jun 16, 2021
@bh2smith bh2smith deleted the balancer-two-token branch June 16, 2021 09:12
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.

3 participants