Skip to content

Conversation

@nicholaspai
Copy link
Member

@nicholaspai nicholaspai commented Mar 14, 2022

Issue:

  • [M04] whitelistedRoutes for Ethereum_SpokePool affect other routes
    • When in HubPool's executeRootBundle function, tokens are moved between spokes in order to
      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.
    • This means that if other routes are to be enabled for rebalancing, the route from the
      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.
    • Also note that whitelisting a route does not necessarily whitelist the route from Ethereum to the same
      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.
    • Consider modifying the whitelist scheme so that rebalances to specific chains are automatically
      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.
  • [L03] No good way to disable routes in HubPool
    • Within the SpokePool there exists the enabledDepositRoutes mapping, which lists routes that have
      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.
    • Assuming that there may be cases in the future where it would be desirable to disable a certain deposit
      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.
  • [N16] whitelistedRoute can be external
    • The whitelistedRoute function within HubPool is marked as public. However, it is not called
      anywhere within the codebase.Consider restricting the function to external to reduce the surface for error and better reflect its intent.

Resolution:

  • This PR removes the HubPool's conflation of two different meanings of what a “whitelisted route” is. We now separate poolRebalanceRoutes, stored on the HubPool, from enabledDepositRoutes, stored on the SpokePool.
  • The PR means that the Ethereum_SpokePool deposit routes no longer are affected by pool rebalance routes stored on the HubPool.
  • This PR also makes it possible to disable both poolRebalanceRoutes (by zero-ing out destination tokens on the HubPool) and deposit routes (by flipping enabled bool to false).
  • Replaces whitelistedRoute with poolRebalanceRoute on the HubPool and makes it external.

Copy link
Contributor

@mrice32 mrice32 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 a few minor comments and questions.

) public override onlyOwner {
whitelistedRoutes[_whitelistedRouteKey(originChainId, originToken, destinationChainId)] = Route({
destinationToken: destinationToken,
depositsEnabled: depositsEnabled
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 just meant for offchain applications to read?

Copy link
Member Author

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.

@nicholaspai nicholaspai changed the title refactor: whitelistedRoutes should distinguish between enabled deposit and poolRebalance paths refactor: [M04] whitelistedRoutes should distinguish between enabled deposit and poolRebalance paths Mar 15, 2022
@nicholaspai nicholaspai changed the title refactor: [M04] whitelistedRoutes should distinguish between enabled deposit and poolRebalance paths refactor: [M04/L03/N16] whitelistedRoutes should distinguish between enabled deposit and poolRebalance paths Mar 15, 2022
@nicholaspai nicholaspai added the OZ Audit - March Resolves issue discovered in March 2022 OZ Audit label Mar 15, 2022
@nicholaspai nicholaspai requested a review from mrice32 March 15, 2022 11:49
Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

Looks good!

* @param originToken Token sent from origin chain.
* @param depositsEnabled Set to True/False to enable/disable route on SpokePool for deposits.
*/
function relayWhitelistRoute(
Copy link
Contributor

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.

Copy link
Member Author

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

Copy link
Contributor

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.

Copy link
Member Author

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

Copy link
Member Author

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.

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. The hub pool would still need an address map, right?

Copy link
Member

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?

Copy link
Member Author

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?

Copy link
Member Author

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.

@nicholaspai nicholaspai requested a review from mrice32 March 15, 2022 17:01
Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

@nicholaspai nicholaspai requested a review from mrice32 March 15, 2022 18:06
* 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(
Copy link
Member Author

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!

)
);

emit SetPoolRebalanceRoute(depositRouteChainId_1, ethereumCounterpartToken, l2Token_1);
Copy link
Member

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?

Copy link
Member Author

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)

Copy link
Member Author

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

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);
Copy link
Member Author

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?

Copy link
Member

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

Copy link
Member

@chrismaree chrismaree left a 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>
Signed-off-by: chrismaree <christopher.maree@gmail.com>
Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

OZ Audit - March Resolves issue discovered in March 2022 OZ Audit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants