Skip to content
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

Base Hook Middleware #144

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

Base Hook Middleware #144

wants to merge 7 commits into from

Conversation

Jun1on
Copy link
Contributor

@Jun1on Jun1on commented Jul 10, 2024

Related Issue

Hooks can be made to do bad things. Because anyone can create their own arbitrary logic for a hook contract, it's difficult for third parties to decide which hooks are "safe" and which are "dangerous". We propose a hook middleware that performs sanity checks on the result of a hooks to block malicious actions.

Description of changes

Middleware factory creates middlewares.

hook.factory.mp4

Each middleware is the hook and points to another hook as the implementation. This allows for some convenient configurations where a pool can use a hook and another pool can use the middlewared hook.

middleware configurations

all are valid configurations

Best of all, attaching a middleware to a hook is easy and usually requires no extra coding. The main caveat1 is (because of the proxy pattern) constructors will never be called, so it may be necessary to revise the implementation contract to use an initialize function if the constructor needs to set non-immutable variables.

For now, this branch implements and tests only base contracts, which do nothing. middleware-remove and middleware-protect extend these base contracts to include specific protections. Of course, these are only proposed solutions, and anyone can create their own middleware factory design as they see fit.

Footnotes

  1. there are some more small caveats. let's say hook A calls a permissioned function on external contract E. a middleware pointing to hook A would then not be able to call contract E.

Copy link
Member

@ConjunctiveNormalForm ConjunctiveNormalForm left a comment

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 writing a more thorough PR description in this case, given we are working with a public repo here, and many (most) people stumble upon this won't have the slightest of context of what hook middleware is and can do. It's even more worth it if the goal is to have people adopt it to write 'certified safe' hooks

contracts/middleware/BaseMiddleware.sol Outdated Show resolved Hide resolved
test/utils/HookMiner.sol Outdated Show resolved Hide resolved
mapping(PoolId => uint256) public beforeDonateCount;
mapping(PoolId => uint256) public afterDonateCount;

bytes public lastHookData;

Choose a reason for hiding this comment

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

What's the use of this? And does it make sense to make it a mapping from selectors to lastHookData instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's just for testing to make sure hookData is passed through correctly

_delegate(_implementation());
}

function _ensureValidFlags(address _impl) internal view virtual {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if the underlying implementation actually even needs to support the flags in this case given the middleware already does. What are your thoughts for requiring both addresses to be mined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

initially, i did not require the implementation to be mined. however, when working on middleware-protect, i realized doing so would save a bit of gas on flag checks. as the dev would already have to mine a middleware, i don't think it's a big deal to enforce them to mine an implementation as well. also, it's likely that some implementations are already hooks and are already mined.

Copy link
Member

Choose a reason for hiding this comment

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

why does it save gas?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

there's a caveat in middleware-protect that requires it to read the implementation permissions

contracts/middleware/BaseMiddleware.sol Outdated Show resolved Hide resolved

contract BaseMiddleware is Proxy {
/// @notice The address of the pool manager
IPoolManager public immutable manager;
Copy link
Contributor

Choose a reason for hiding this comment

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

you're never using the manager immutable as far as I can tell - is this just to be referenced by the underlying impl? if so please elaborate in the comment

contracts/middleware/BaseMiddleware.sol Show resolved Hide resolved
test/middleware/Counter.sol Outdated Show resolved Hide resolved
contracts/middleware/BaseMiddleware.sol Outdated Show resolved Hide resolved
contracts/middleware/BaseMiddleware.sol Outdated Show resolved Hide resolved
contracts/middleware/BaseMiddleware.sol Outdated Show resolved Hide resolved
contracts/middleware/BaseMiddleware.sol Outdated Show resolved Hide resolved

error FlagsMismatch();

constructor(IPoolManager _manager, address _impl) {
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 purpose of this BaseMiddleware? It seems to be mostly just a proxy wrapper. Are there any useful things that we can add for downstream implementers? Some ideas -

  • Standard modifiers i.e. onlyPoolManager at least?
  • Standard implementations of some hook functions, with virtual functions that downstream implementers can override with their custom checks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's pretty much just oz proxy yeah.

  • i think it's up to the implementation to add the onlyPoolManager modifier. most hooks already use it, so adding onlyPoolManager to the middleware itself would result in a redundant check.
  • like a beforeBeforeSwap, afterBeforeSwap pattern? this sounds a bit complicated and also creates deployment size bloat for middlewares that don't implement the hook.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah onlyPoolManager is already defined in contracts we've written upstream like SafeCallback

Copy link
Member

@snreynolds snreynolds left a comment

Choose a reason for hiding this comment

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

Maybe a diagram or added test that I'm missing but.. how does a hook developer interact with these middlewear contracts to set their own hook/deploy their own middlewear ontop of their hook? Just would like to understand the DevX of using this a bit better

abstract contract BaseMiddleware is Proxy {
/// @notice The address of the pool manager
/// @dev Use in middleware implementations to access the pool manager
IPoolManager public immutable manager;
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 do need the manager var, there will soon be merged an ImmutableState contract you can use to initialize maanger


error FlagsMismatch();

constructor(IPoolManager _manager, address _impl) {
Copy link
Member

Choose a reason for hiding this comment

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

Yeah onlyPoolManager is already defined in contracts we've written upstream like SafeCallback

_delegate(_implementation());
}

function _ensureValidFlags(address _impl) internal view virtual {
Copy link
Member

Choose a reason for hiding this comment

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

why does it save gas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants