Skip to content

Commit

Permalink
Merge pull request #134 from PaulRBerg/feat/deploy-and-execute-and-in…
Browse files Browse the repository at this point in the history
…stall-plugin

feat: deployAndExecuteAndInstallPlugin
  • Loading branch information
PaulRBerg authored Jun 29, 2023
2 parents 95f798c + d9afa29 commit 34af4d2
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 39 deletions.
44 changes: 23 additions & 21 deletions src/PRBProxyRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ contract PRBProxyRegistry is IPRBProxyRegistry {

/// @inheritdoc IPRBProxyRegistry
function deploy() external override noProxy(msg.sender) returns (IPRBProxy proxy) {
proxy = _deploy({ owner: msg.sender });
proxy = _deploy({ owner: msg.sender, target: address(0), data: "" });
}

/// @inheritdoc IPRBProxyRegistry
Expand All @@ -155,35 +155,37 @@ contract PRBProxyRegistry is IPRBProxyRegistry {
noProxy(msg.sender)
returns (IPRBProxy proxy)
{
// Use the address of the owner as the CREATE2 salt.
address owner = msg.sender;
bytes32 salt = bytes32(abi.encodePacked(owner));

// Deploy the proxy with CREATE2, and execute the delegate call in the constructor.
constructorParams = ConstructorParams({ owner: owner, target: target, data: data });
proxy = new PRBProxy{ salt: salt }();
delete constructorParams;

// Associate the owner and the proxy.
_proxies[owner] = proxy;

// Log the creation of the proxy.
emit DeployProxy({ operator: msg.sender, owner: owner, proxy: proxy });
proxy = _deploy({ owner: msg.sender, target: target, data: data });
}

/// @inheritdoc IPRBProxyRegistry
function deployFor(address owner) public override noProxy(owner) returns (IPRBProxy proxy) {
proxy = _deploy(owner);
function deployFor(address owner) external override noProxy(owner) returns (IPRBProxy proxy) {
proxy = _deploy({ owner: owner, target: address(0), data: "" });
}

/// @inheritdoc IPRBProxyRegistry
function installPlugin(IPRBProxyPlugin plugin) external override onlyCallerWithProxy {
function deployAndExecuteAndInstallPlugin(
address target,
bytes calldata data,
IPRBProxyPlugin plugin
)
external
override
noProxy(msg.sender)
returns (IPRBProxy proxy)
{
proxy = _deploy({ owner: msg.sender, target: target, data: data });
_installPlugin(plugin);
}

/// @inheritdoc IPRBProxyRegistry
function deployAndInstallPlugin(IPRBProxyPlugin plugin) external returns (IPRBProxy proxy) {
proxy = _deploy({ owner: msg.sender });
proxy = _deploy({ owner: msg.sender, target: address(0), data: "" });
_installPlugin(plugin);
}

/// @inheritdoc IPRBProxyRegistry
function installPlugin(IPRBProxyPlugin plugin) external override onlyCallerWithProxy {
_installPlugin(plugin);
}

Expand Down Expand Up @@ -226,12 +228,12 @@ contract PRBProxyRegistry is IPRBProxyRegistry {
//////////////////////////////////////////////////////////////////////////*/

/// @dev See the documentation for the user-facing functions that call this internal function.
function _deploy(address owner) internal returns (IPRBProxy proxy) {
function _deploy(address owner, address target, bytes memory data) internal returns (IPRBProxy proxy) {
// Use the address of the owner as the CREATE2 salt.
bytes32 salt = bytes32(abi.encodePacked(owner));

// Set the owner and empty out the target and the data to prevent reentrancy.
constructorParams = ConstructorParams({ owner: owner, target: address(0), data: "" });
constructorParams = ConstructorParams({ owner: owner, target: target, data: data });

// Deploy the proxy with CREATE2.
proxy = new PRBProxy{ salt: salt }();
Expand Down
31 changes: 28 additions & 3 deletions src/interfaces/IPRBProxyRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,9 @@ interface IPRBProxyRegistry {
/// @return proxy The address of the newly deployed proxy.
function deploy() external returns (IPRBProxy proxy);

/// @notice Deploys a new proxy for the caller, and delegate calls to the provided target by forwarding the data.
/// Then, it returns the data it gets back, and bubbles up any potential revert.
/// @notice This function performs two actions:
/// 1. Deploys a new proxy for the caller
/// 2. Delegate calls to the provided target, returning the data it gets back, and bubbling up any potential revert.
///
/// @dev Emits a {DeployProxy} and an {Execute} event.
///
Expand All @@ -169,7 +170,31 @@ interface IPRBProxyRegistry {
/// @return proxy The address of the newly deployed proxy.
function deployAndExecute(address target, bytes calldata data) external returns (IPRBProxy proxy);

/// @notice Deploys a new proxy for the caller, and installs the provided plugin on the newly deployed proxy.
/// @notice This function performs three actions:
/// 1. Deploys a new proxy for the caller
/// 2. Delegate calls to the provided target, returning the data it gets back, and bubbling up any potential revert.
/// 3. Installs the provided plugin on the newly deployed proxy.
///
/// @dev Emits a {DeployProxy} and an {InstallPlugin} event.
///
/// Requirements:
/// - The caller must not have a proxy.
/// - See the requirements in `installPlugin`.
/// - See the requirements in `execute`.
///
/// @param plugin The address of the plugin to install.
/// @return proxy The address of the newly deployed proxy.
function deployAndExecuteAndInstallPlugin(
address target,
bytes calldata data,
IPRBProxyPlugin plugin
)
external
returns (IPRBProxy proxy);

/// @notice This function performs two actions:
/// 1. Deploys a new proxy for the caller.
/// 2. Installs the provided plugin on the newly deployed proxy.
///
/// @dev Emits a {DeployProxy} and an {InstallPlugin} event.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19 <=0.9.0;

import { IPRBProxy } from "src/interfaces/IPRBProxy.sol";
import { IPRBProxyPlugin } from "src/interfaces/IPRBProxyPlugin.sol";
import { IPRBProxyRegistry } from "src/interfaces/IPRBProxyRegistry.sol";

import { Registry_Test } from "../Registry.t.sol";

contract DeployAndExecuteAndInstallPlugin_Test is Registry_Test {
bytes internal data;
uint256 internal input = 1729;
address internal target;

function setUp() public override {
Registry_Test.setUp();

data = abi.encodeWithSelector(targets.echo.echoUint256.selector, input);
target = address(targets.echo);
}

function test_RevertWhen_OwnerHasProxy() external {
IPRBProxy proxy = registry.deploy();
vm.expectRevert(
abi.encodeWithSelector(IPRBProxyRegistry.PRBProxyRegistry_OwnerHasProxy.selector, users.alice, proxy)
);
registry.deployAndExecuteAndInstallPlugin(target, data, plugins.basic);
}

modifier whenOwnerDoesNotHaveProxy() {
_;
}

function testFuzz_DeployAndExecuteAndInstallPlugin_ProxyAddress(address owner) external whenOwnerDoesNotHaveProxy {
changePrank({ msgSender: owner });
IPRBProxy actualProxy = registry.deployAndExecuteAndInstallPlugin(target, data, plugins.basic);
address expectedProxy = computeProxyAddress(owner);
assertEq(address(actualProxy), expectedProxy, "deployed proxy address mismatch");
}

function testFuzz_DeployAndExecuteAndInstallPlugin_ProxyOwner(address owner) external whenOwnerDoesNotHaveProxy {
changePrank({ msgSender: owner });
IPRBProxy proxy = registry.deployAndExecuteAndInstallPlugin(target, data, plugins.basic);
address actualOwner = proxy.owner();
address expectedOwner = owner;
assertEq(actualOwner, expectedOwner, "proxy owner mismatch");
}

function testFuzz_DeployAndExecuteAndInstallPlugin_UpdateProxies(address owner)
external
whenOwnerDoesNotHaveProxy
{
changePrank({ msgSender: owner });
registry.deployAndExecuteAndInstallPlugin(target, data, plugins.basic);

address actualProxyAddress = address(registry.getProxy(owner));
address expectedProxyAddress = computeProxyAddress(owner);
assertEq(actualProxyAddress, expectedProxyAddress, "proxy address mismatch");
}

function testFuzz_DeployAndExecuteAndInstallPlugin_Plugin() external whenOwnerDoesNotHaveProxy {
registry.deployAndExecuteAndInstallPlugin(target, data, plugins.basic);
bytes4[] memory pluginMethods = plugins.basic.getMethods();
for (uint256 i = 0; i < pluginMethods.length; ++i) {
IPRBProxyPlugin actualPlugin = registry.getPluginByOwner({ owner: users.alice, method: pluginMethods[i] });
IPRBProxyPlugin expectedPlugin = plugins.basic;
assertEq(actualPlugin, expectedPlugin, "plugin method not installed");
}
}

function testFuzz_DeployAndExecuteAndInstallPlugin_PluginReverseMapping() external whenOwnerDoesNotHaveProxy {
registry.deployAndExecuteAndInstallPlugin(target, data, plugins.basic);
bytes4[] memory actualMethods = registry.getMethodsByOwner({ owner: users.alice, plugin: plugins.basic });
bytes4[] memory expectedMethods = plugins.basic.getMethods();
assertEq(actualMethods, expectedMethods, "methods not saved in reverse mapping");
}

function testFuzz_DeployAndExecuteAndInstallPlugin_Event_DeployProxy(address owner)
external
whenOwnerDoesNotHaveProxy
{
changePrank({ msgSender: owner });

vm.expectEmit({ emitter: address(registry) });
emit DeployProxy({ operator: owner, owner: owner, proxy: IPRBProxy(computeProxyAddress(owner)) });
registry.deployAndExecuteAndInstallPlugin(target, data, plugins.basic);
}

function testFuzz_DeployAndExecuteAndInstallPlugin_Event_Execute(address owner)
external
whenOwnerDoesNotHaveProxy
{
changePrank({ msgSender: owner });

vm.expectEmit({ emitter: computeProxyAddress(owner) });
emit Execute({ target: address(targets.echo), data: data, response: abi.encode(input) });
registry.deployAndExecuteAndInstallPlugin(target, data, plugins.basic);
}

function testFuzz_DeployAndExecuteAndInstallPlugin_Event_InstallPlugin(address owner)
external
whenOwnerDoesNotHaveProxy
{
changePrank({ msgSender: owner });

vm.expectEmit({ emitter: address(registry) });
emit InstallPlugin({
owner: owner,
proxy: IPRBProxy(computeProxyAddress(owner)),
plugin: plugins.basic,
methods: plugins.basic.getMethods()
});
registry.deployAndExecuteAndInstallPlugin(target, data, plugins.basic);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
deployAndExecuteAndInstallPlugin.t.sol
├── when the owner has a proxy
│ └── it should revert
└── when the owner does not have a proxy
├── it should generate the correct proxy address
├── it should set the owner
├── it should update the proxies mapping
├── it should install the plugin
├── it should save the plugin methods in the reverse mapping
├── it should emit a {DeployProxy} event
├── it should emit a {Execute} event
└── it should emit an {InstallPlugin} event
4 changes: 1 addition & 3 deletions test/registry/deploy-and-execute/deployAndExecute.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { IPRBProxyRegistry } from "src/interfaces/IPRBProxyRegistry.sol";

import { Registry_Test } from "../Registry.t.sol";

/// @dev User roles:
/// - Bob is the origin, operator, and owner of the proxy
contract DeployAndExecute_Test is Registry_Test {
bytes internal data;
uint256 internal input = 1729;
Expand All @@ -21,7 +19,7 @@ contract DeployAndExecute_Test is Registry_Test {
}

function test_RevertWhen_OwnerHasProxy() external {
IPRBProxy proxy = registry.deployAndExecute(target, data);
IPRBProxy proxy = registry.deploy();
vm.expectRevert(
abi.encodeWithSelector(IPRBProxyRegistry.PRBProxyRegistry_OwnerHasProxy.selector, users.alice, proxy)
);
Expand Down
3 changes: 1 addition & 2 deletions test/registry/deploy-and-execute/deployAndExecute.tree
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ deployAndExecute.t.sol
│ └── it should revert
└── when the owner does not have a proxy
├── it should generate the correct proxy address
├── it should initialize the owner
├── it should set the owner
├── it should update the proxies mapping
├── it should delegate call to the target contract
├── it should emit a {DeployProxy} event
└── it should emit an {Execute} event
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { IPRBProxyRegistry } from "src/interfaces/IPRBProxyRegistry.sol";

import { Registry_Test } from "../Registry.t.sol";

/// @dev User roles:
/// - Bob is the origin, operator, and owner of the proxy
contract DeployAndInstallPlugin_Test is Registry_Test {
function setUp() public override {
Registry_Test.setUp();
Expand All @@ -33,7 +31,24 @@ contract DeployAndInstallPlugin_Test is Registry_Test {
assertEq(actualProxy, expectedProxy, "deployed proxy address mismatch");
}

function testFuzz_DeployAndInstallPlugin_PluginMethods() external whenOwnerDoesNotHaveProxy {
function testFuzz_DeployAndInstallPlugin_ProxyOwner(address owner) external whenOwnerDoesNotHaveProxy {
changePrank({ msgSender: owner });
IPRBProxy proxy = registry.deployAndInstallPlugin(plugins.basic);
address actualOwner = proxy.owner();
address expectedOwner = owner;
assertEq(actualOwner, expectedOwner, "proxy owner mismatch");
}

function testFuzz_DeployAndInstallPlugin_UpdateProxies(address owner) external whenOwnerDoesNotHaveProxy {
changePrank({ msgSender: owner });
registry.deployAndInstallPlugin(plugins.basic);

address actualProxyAddress = address(registry.getProxy(owner));
address expectedProxyAddress = computeProxyAddress(owner);
assertEq(actualProxyAddress, expectedProxyAddress, "proxy address mismatch");
}

function testFuzz_DeployAndInstallPlugin_Plugin() external whenOwnerDoesNotHaveProxy {
registry.deployAndInstallPlugin(plugins.basic);
bytes4[] memory pluginMethods = plugins.basic.getMethods();
for (uint256 i = 0; i < pluginMethods.length; ++i) {
Expand All @@ -43,17 +58,29 @@ contract DeployAndInstallPlugin_Test is Registry_Test {
}
}

function testFuzz_DeployAndInstallPlugin_ReverseMapping() external whenOwnerDoesNotHaveProxy {
function testFuzz_DeployAndInstallPlugin_PluginReverseMapping() external whenOwnerDoesNotHaveProxy {
registry.deployAndInstallPlugin(plugins.basic);
bytes4[] memory actualMethods = registry.getMethodsByOwner({ owner: users.alice, plugin: plugins.basic });
bytes4[] memory expectedMethods = plugins.basic.getMethods();
assertEq(actualMethods, expectedMethods, "methods not saved in reverse mapping");
}

function testFuzz_DeployAndInstallPlugin_Event(address owner) external whenOwnerDoesNotHaveProxy {
function testFuzz_DeployAndInstallPlugin_Event_DeployProxy(address owner) external whenOwnerDoesNotHaveProxy {
changePrank({ msgSender: owner });
vm.expectEmit({ emitter: address(registry) });
emit DeployProxy({ operator: owner, owner: owner, proxy: IPRBProxy(computeProxyAddress(owner)) });
registry.deploy();
registry.deployAndInstallPlugin(plugins.basic);
}

function testFuzz_DeployAndInstallPlugin_Event_InstallPlugin(address owner) external whenOwnerDoesNotHaveProxy {
changePrank({ msgSender: owner });
vm.expectEmit({ emitter: address(registry) });
emit InstallPlugin({
owner: owner,
proxy: IPRBProxy(computeProxyAddress(owner)),
plugin: plugins.basic,
methods: plugins.basic.getMethods()
});
registry.deployAndInstallPlugin(plugins.basic);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ deployAndInstallPlugin.t.sol
│ └── it should revert
└── when the owner does not have a proxy
├── it should generate the correct proxy address
├── it should set the owner
├── it should update the proxies mapping
├── it should install the plugin
├── it should save the methods in the reverse mapping
├── it should save the plugin methods in the reverse mapping
├── it should emit a {DeployProxy} event
└── it should emit an {InstallPlugin} event
2 changes: 1 addition & 1 deletion test/registry/deploy-for/deployFor.tree
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ deployFor.t.sol
│ └── it should revert
└── when the owner does not have a proxy
├── it should generate the correct proxy address
├── it should initialize the owner
├── it should set the owner
├── it should update the proxies mapping
└── it should emit a {DeployProxy} event
2 changes: 1 addition & 1 deletion test/registry/deploy/deploy.tree
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ deploy.t.sol
│ └── it should revert
└── when the owner does not have a proxy
├── it should generate the correct proxy address
├── it should initialize the owner
├── it should set the owner
├── it should update the proxies mapping
└── it should emit a {DeployProxy} event
2 changes: 1 addition & 1 deletion test/utils/Precompiles.sol

Large diffs are not rendered by default.

0 comments on commit 34af4d2

Please sign in to comment.