-
-
Notifications
You must be signed in to change notification settings - Fork 48
Plugins
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.
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)
- 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)
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 |
Be careful what plugins you install. Let me repeat what I said above: anyone can run any installed plugin on any user's proxy.
Here's how to implement PRBProxy Plugins:
- Implement whatever custom logic you want your plugin to have
- Inherit from
IPRBProxyRegistry
- 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.
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.