-
Notifications
You must be signed in to change notification settings - Fork 75
refactor: [M04/L03/N16] whitelistedRoutes should distinguish between enabled deposit and poolRebalance paths #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
mrice32
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Just a few minor comments and questions.
contracts/HubPool.sol
Outdated
| ) public override onlyOwner { | ||
| whitelistedRoutes[_whitelistedRouteKey(originChainId, originToken, destinationChainId)] = Route({ | ||
| destinationToken: destinationToken, | ||
| depositsEnabled: depositsEnabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just meant for offchain applications to read?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
depositsEnabled is meant for off-chain agents only yes.
mrice32
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
contracts/HubPool.sol
Outdated
| * @param originToken Token sent from origin chain. | ||
| * @param depositsEnabled Set to True/False to enable/disable route on SpokePool for deposits. | ||
| */ | ||
| function relayWhitelistRoute( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure of a better way to do this, but it does seem a bit clunky and error-prone to have two separate methods for setting the mapping and sending that setting to the spoke since we would almost always want to do these together. No real suggestion here, just thinking that this might add frustration to the management of the protocol as the number of chains and tokens increases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we added a third function to combine the two methods? The other way is to add flags as function params to fine tune the logic. I can see how we'd want to only whitelist on the HubPool/SpokePool in certain situations so I think this flexibility is important
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically we could call both simultaneously with multicall, it just might be annoying, especially if this is called via a gnosis multisig or governance system.
I could see a flag param also making sense.
Curious what @chrismaree thinks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trade off is that the flag param adds gas, but condenses three functions into one. I probably lean towards usability and adding the following bool params to the whitelistRoute call: bool relayToSpokePool, bool setInHub
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had another idea. What if we didn't store the depositsEnabled boolean on HubPool and only stored it on SpokePools? This would force relayers to use SpokePool whitelisted routes as the source of truth for determining which deposit routes are enabled, while the HubPool whitelist would only store pool rebalance routes.
The advantage is that the separation between pool rebalance and deposit routes would be more cleanly defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. The hub pool would still need an address map, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that your proposal for adding a bool to whitelistRoute that, when set, also enables the whitelist route makes a lot of sence as I agree having these separate as two functions is a pain as we will almost always be calling both.
had another idea. What if we didn't store the depositsEnabled boolean on HubPool and only stored it on SpokePools? This would force relayers to use SpokePool whitelisted routes as the source of truth for determining which deposit routes are enabled, while the HubPool whitelist would only store pool rebalance routes.
I like this idea but I worry that it drastically increases the L1->L1 calls we need to do as well as conflating the information on the L2 side. This might not be as bad as I think though...There are some other nice benefits to this as well. When making a deposit the FundsDeposited will then also be able to contain the DestinationToken which right now it does not as this information is not there.
your proposal would look something like this, right?
change setEnableRoute to have destinationToken as well and then modify enabledDepositRoutes to be originToken->destinationChainId->destinationStruct where destinationStruct contains destinationToken and depositsEnabledenabled. Then, we can remove this bit of logic from the HubPool as the only place that needs to know if a route is enabled is the deposit side (the hub pool does not care!).
In fact in this pattern what is required to be left on the HubPool? almost nothing, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually I was proposing to store originToken -> destinationChainId -> depositsEnabled on the SpokePool (i.e. keep it as is now) and then store hash(originChainId, originToken, destinationChainId) -> destinationToken on the HubPool since we need the destination token for pool rebalances.
But I see why you now recommend we also store destinationToken on the SpokePool. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chrismaree , @mrice32 and I talked off-line and we decided to remove the destinationToken from the SpokePool as we think the information is redundant and could lead to a situation where we set up the pairwise mappings incorrectly between SpokePools. This could cause routes between SpokePools to be inconsistent. Instead, we can use the poolRebalanceRoute on the HubPool as the source of truth for L1 --> L2 token mappings.
mrice32
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…sit routes atomically
contracts/HubPool.sol
Outdated
| * this value is false, the pool rebalance destination tokens will be set to 0x0 and the deposit routes will be | ||
| * disabled on the SpokePools. | ||
| */ | ||
| function setDepositAndPoolRebalanceRoute( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setDepositRoute and setPoolRebalanceRoute both average ~50,000 gas, while this method averages ~100,000 gas, so calling this method instead of calling setDepositRoute 2x and setPoolRebalanceRoute 2x saves 50% gas!
contracts/HubPool.sol
Outdated
| ) | ||
| ); | ||
|
|
||
| emit SetPoolRebalanceRoute(depositRouteChainId_1, ethereumCounterpartToken, l2Token_1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are going to whitelist/enable routes in both directions would it make sence to create some kind of condensed event rather than emitting multiple times?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think clients should be able to reliably query SetEnableDepositRoute and SetPoolRebalanceRoute without caring how they were emitted (i.e. via a batch method or not)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did end up putting these emits into internal functions so you won't see multiple emits in a single method anymore
contracts/SpokePool.sol
Outdated
| event SetXDomainAdmin(address indexed newAdmin); | ||
| event SetHubPool(address indexed newHubPool); | ||
| event EnabledDepositRoute(address indexed originToken, uint256 indexed destinationChainId, bool enabled); | ||
| event EnabledDepositRoute(uint256 indexed destinationChainId, address indexed originToken, bool enabled); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this packs variables better? or maybe it doesn't matter for events?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dont think it makes a big diff
chrismaree
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. let's get this in.
Signed-off-by: chrismaree <christopher.maree@gmail.com>
Signed-off-by: chrismaree <christopher.maree@gmail.com>
mrice32
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Issue:
complete rebalances of the different spoke pools. These token transfers happen within the
_sendTokensToChainAndUpdatePooledTokenTrackers function, but in order to complete a rebalance
the route from the chainId of the HubPool to the destination chain must be whitelisted.
The issue comes from the conflation of two slightly different requirements. When whitelisting a route, a
combination of origin chain, destination chain, and origin token are whitelisted. However, when
rebalancing tokens, the specific route where origin chain is the HubPool's chain must be whitelisted for
that token and destination chain pairing.
Ethereum_SpokePool to some destination chain's SpokePool must be enabled as well. This may allow
undesired transfers to the Ethereum_SpokePool. Additionally, it may cause problems if some token is
to be allowed to move between chains aside from Ethereum, but specifically not Ethereum. It would be
impossible to disable transfers to the Ethereum_SpokePool without also disabling transfers between
separate spoke pools for the same token.
destination chain. This means that a separate transaction may need to be sent to enable rebalances to/
from that destination, by whitelisting the Ethereum-as-origin route. This is confusing and could lead to
unexpected reversions if forgotten about.
enabled when enabling certain routes. For example, if the route for some token to move from Arbitrum
to Optimism is enabled, then the route from the Hub to Optimism should also be enabled. Additionally,
consider implementing some special logic to differentiate routes from the HubPool and routes from the
Ethereum_SpokePool, so that either route can be enabled independently of the other.
been approved for deposits (allowing a user to deposit in one spoke pool and withdraw the deposit from
another). The setEnableRoute function can be used to enable or disable these routes.
Within the HubPool, there is a separate whitelistedRoutes mapping, which determines whether
tokens can be sent to a certain spoke during rebalances. The only way to affect the
whitelistedRoutes mapping is by calling whitelistRoute, which includes a call to enable the
originToken/destinationChainId pair within the Spoke. This means that there is no good way to
disable a whitelisted route in the hub without "enabling" the same route in the enabledDepositRoutes
mapping in the SpokePool.
route, consider adding a function which can disable a whitelistedRoutes element (by setting the
value in the mapping to address(0)) without enabling the route in the SpokePool. It may be desirable
to disable both atomically from the HubPool, or to establish a procedure to disable them independently
in a specific order. Consider designing a procedure for valid cross-chain token transfers in the case that
only one mapping has a certain route marked as "disabled", and including this in the UMIP for dispute
resolution. Finally, note that any "atomic" cancellations will still include a delay between when the
message is initiated on the hub chain and when execution can be considered finalized on the spoke
chain.
anywhere within the codebase.Consider restricting the function to external to reduce the surface for error and better reflect its intent.
Resolution:
poolRebalanceRoutes, stored on the HubPool, fromenabledDepositRoutes, stored on the SpokePool.Ethereum_SpokePooldeposit routes no longer are affected by pool rebalance routes stored on the HubPool.enabledbool to false).whitelistedRoutewithpoolRebalanceRouteon the HubPool and makes it external.