Skip to content

Plugins

Paul Razvan Berg edited this page Aug 8, 2023 · 3 revisions

Plugins are similar to Targets in that both are stateless contracts, but their execution models differ significantly. The proxy's fallback function checks whether the 4-byte selector (msg.sig) corresponds with any installed plugin, delegating calls accordingly when there is a match.

While Targets require users to be proactive, Plugins enable the proxy to be reactive, allowing it to respond to calls made by other contracts. However, this also means that users should exercise caution when installing plugins since anyone can run any installed plugin on any user's proxy.

Diagram

Here's a diagram that shows a typical user flow for interacting with a proxy plugin:

flowchart LR;
  P[Proxy];
  PP(Delegate Call to ProxyPlugin);
  IMI(Is foo installed?)
  DP((DeFi Protocol));

  DP-- "foo" -->P;
  P-- "fallback" -->IMI;
  IMI -->|Yes| PP
  IMI -->|No| R(Revert)
Loading

Notes

  • To make the flow work, the proxy owner must have previously installed the ProxyPlugin
  • Unlike with Targets, the entity that triggers the running of Plugins is not the proxy owner, but some third-party smart contract (typically)

Installing Plugins

Plugins can be installed via PRBProxyRegistry using one of the following functions:

Function Description
installPlugin Install a plugin on an already deployed proxy
deployAndInstallPlugin Deploy a proxy for msg.sender, and installs the provided plugin
deployAndExecuteAndInstall Deploy a proxy for msg.sender, delegate calls to the provided target, and installs the provided plugin

Warning ⚠️

Be careful what plugins you install. Let me repeat what I said above: anyone can run any installed plugin on any user's proxy.

Implementation

Here's how to implement PRBProxy Plugins:

  1. Implement whatever custom logic you want your plugin to have
  2. Inherit from IPRBProxyRegistry
  3. Implement the getMethods function and return all 4-byte selectors that you want to be installed on the proxy

If your plugin does not implement all 4-byte selectors it says it does in getMethods, the function execution will revert when the missing function is called.

Example

On-chain callbacks are the primary use case for Plugins. I imagine that liquidation bots are a potentially interesting application. Generally, the idea is to let the proxy arbitrarily react to any smart contract protocol.

Sablier V2 Periphery has been designed to work with PRBProxy. Let's use the SablierV2ProxyPlugin contract as an example:

  • The goal is to implement the Sablier V2 onStreamCanceled hook
  • When a recipient cancels a Sablier stream created by a PRBProxy contract, Sablier refunds the unstreamed assets to the proxy
  • Thanks to the plugin below, the refunded assets are auto-forwarded back to the original user
  • Notice that the plugin allows only certain contracts to call it; this is to prevent malicious interactions
contract SablierV2ProxyPlugin is OnlyDelegateCall, ISablierV2ProxyPlugin {
    using SafeERC20 for IERC20;

    ISablierV2Archive public immutable override archive;

    constructor(ISablierV2Archive archive_) {
        archive = archive_;
    }

    function getMethods() external pure returns (bytes4[] memory methods) {
        methods = new bytes4[](1);
        methods[0] = this.onStreamCanceled.selector;
    }

    function onStreamCanceled(
        uint256 streamId,
        address,
        uint128 senderAmount,
        uint128
    )
        external
        onlyDelegateCall
    {
        if (!archive.isListed(msg.sender)) {
            revert Errors.SablierV2ProxyPlugin_UnknownCaller(msg.sender);
        }

        ISablierV2Lockup lockup = ISablierV2Lockup(msg.sender);
        address streamSender = lockup.getSender(streamId);
        assert(streamSender == address(this));

        IERC20 asset = lockup.getAsset(streamId);
        address owner = IPRBProxy(address(this)).owner();
        asset.safeTransfer({ to: owner, value: senderAmount });
    }
}

For more details about Sablier and how Sablier uses PRBProxy, see the docs.

Clone this wiki locally