Skip to content

Commit

Permalink
Merge pull request #53 from EdenBlockVC/feat/plugins
Browse files Browse the repository at this point in the history
Add Plugin support
  • Loading branch information
PaulRBerg authored Feb 17, 2023
2 parents 4e0d5d9 + 6cbb24b commit 7daea25
Show file tree
Hide file tree
Showing 28 changed files with 1,030 additions and 90 deletions.
83 changes: 83 additions & 0 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
DeployFor_Test:test_DeployFor_Event() (gas: 2220804)
DeployFor_Test:test_DeployFor_FirstProxy() (gas: 2184280)
DeployFor_Test:test_DeployFor_TxOriginNotSameAsOwner_NotFirstProxy() (gas: 3263997)
DeployFor_Test:test_DeployFor_TxOriginNotSameAsOwner_NotFirstProxy_UpdateNextSeeds() (gas: 2213426)
DeployFor_Test:test_DeployFor_TxOriginSameAsOwner() (gas: 2162143)
DeployFor_Test:test_DeployFor_UpdateProxies() (gas: 2211226)
Deploy_Test:test_Deploy() (gas: 2159907)
Deploy_Test:test_Deploy_Event() (gas: 1116934)
Deploy_Test:test_Deploy_UpdateNextSeeds() (gas: 1109399)
Deploy_Test:test_Deploy_UpdateProxies() (gas: 1107209)
Version_Test:test_Version() (gas: 5448)
DeployFor_Test:test_DeployFor() (gas: 4353379)
DeployFor_Test:test_DeployFor_CurrentProxies() (gas: 3303728)
DeployFor_Test:test_DeployFor_DeployerDidNotDeployAnotherProxyForHimselfViaFactory() (gas: 3271672)
DeployFor_Test:test_DeployFor_DeployerDidNotDeployAnotherProxyForTheOwnerViaFactory() (gas: 2189941)
DeployFor_Test:test_DeployFor_DeployerSameAsOwner() (gas: 2189954)
DeployFor_Test:test_DeployFor_OwnerTransferredOwnership() (gas: 3274494)
DeployFor_Test:test_RevertWhen_OwnerDidNotTransferOwnership() (gas: 1141550)
Deploy_Test:test_Deploy() (gas: 2187734)
Deploy_Test:test_Deploy_UpdateCurrentProxies() (gas: 1142111)
Execute_Test:testFuzz_Execute_Event(uint256) (runs: 1000, μ: 276518, ~: 276518)
Execute_Test:testFuzz_Execute_ReturnAddress(address) (runs: 1000, μ: 268879, ~: 268879)
Execute_Test:testFuzz_Execute_ReturnBytes32(bytes32) (runs: 1000, μ: 268844, ~: 268844)
Execute_Test:testFuzz_Execute_ReturnBytesArray(bytes) (runs: 1000, μ: 276945, ~: 275479)
Execute_Test:testFuzz_Execute_ReturnString(string) (runs: 1000, μ: 277319, ~: 277678)
Execute_Test:testFuzz_Execute_ReturnStruct((uint256,uint256,uint256)) (runs: 1000, μ: 272821, ~: 272821)
Execute_Test:testFuzz_Execute_ReturnUint256(uint256) (runs: 1000, μ: 268791, ~: 268791)
Execute_Test:testFuzz_Execute_ReturnUint256Array(uint256[]) (runs: 1000, μ: 554468, ~: 558356)
Execute_Test:testFuzz_Execute_ReturnUint8(uint8) (runs: 1000, μ: 268913, ~: 268913)
Execute_Test:test_Execute_EtherSent() (gas: 27365)
Execute_Test:test_Execute_TargetSelfDestructs() (gas: 31259)
Execute_Test:test_RevertWhen_Error_CustomError() (gas: 22828)
Execute_Test:test_RevertWhen_Error_EmptyRevertStatement() (gas: 22404)
Execute_Test:test_RevertWhen_Error_NoPayableModifier() (gas: 28989)
Execute_Test:test_RevertWhen_Error_ReasonString() (gas: 23945)
Execute_Test:test_RevertWhen_Error_Require() (gas: 22438)
Execute_Test:test_RevertWhen_GasStipendCalculationUnderflows() (gas: 33864)
Execute_Test:test_RevertWhen_NoPermission() (gas: 22233)
Execute_Test:test_RevertWhen_OwnerChangedDuringDelegateCall() (gas: 25377)
Execute_Test:test_RevertWhen_Panic_ArithmeticOverflow() (gas: 23762)
Execute_Test:test_RevertWhen_Panic_DivisionByZero() (gas: 23706)
Execute_Test:test_RevertWhen_Panic_FailedAssertion() (gas: 23609)
Execute_Test:test_RevertWhen_Panic_IndexOOB() (gas: 23847)
Execute_Test:test_RevertWhen_PermissionDifferentFunction() (gas: 48221)
Execute_Test:test_RevertWhen_PermissionDifferentTarget() (gas: 50166)
Execute_Test:test_RevertWhen_TargetNotContract(address) (runs: 1000, μ: 15241, ~: 15251)
InstallPlugin_Test:test_InstallPlugin() (gas: 65082)
InstallPlugin_Test:test_InstallPlugin_Event() (gas: 64149)
InstallPlugin_Test:test_InstallPlugin_PluginInstalledBefore() (gas: 70018)
InstallPlugin_Test:test_RevertWhen_CallerNotOwner() (gas: 18333)
InstallPlugin_Test:test_RevertWhen_PluginHasNoMethods() (gas: 16780)
Receive_Test:test_Receive() (gas: 12169)
Receive_Test:test_RevertWhen_CallDataNonEmpty() (gas: 14636)
RunPlugin_Test:test_RevertWhen_Error_CustomError() (gas: 138638)
RunPlugin_Test:test_RevertWhen_Error_EmptyRevertStatement() (gas: 138221)
RunPlugin_Test:test_RevertWhen_Error_ReasonString() (gas: 139781)
RunPlugin_Test:test_RevertWhen_Error_Require() (gas: 138200)
RunPlugin_Test:test_RevertWhen_GasStipendCalculationUnderflows() (gas: 81427)
RunPlugin_Test:test_RevertWhen_OwnerChangedDuringDelegateCall() (gas: 50333)
RunPlugin_Test:test_RevertWhen_Panic_ArithmeticOverflow() (gas: 116810)
RunPlugin_Test:test_RevertWhen_Panic_DivisionByZero() (gas: 116782)
RunPlugin_Test:test_RevertWhen_Panic_FailedAssertion() (gas: 116768)
RunPlugin_Test:test_RevertWhen_Panic_IndexOOB() (gas: 116867)
RunPlugin_Test:test_RevertWhen_PluginNotInstalled() (gas: 13470)
RunPlugin_Test:test_RunPlugin_EtherSent() (gas: 233597)
RunPlugin_Test:test_RunPlugin_Event() (gas: 75925)
RunPlugin_Test:test_RunPlugin_PluginSelfDestructs() (gas: 55647)
RunPlugin_Test:test_RunPlugin_Zzz() (gas: 69221)
SetPermission_Test:test_RevertWhen_CallerNotOwner() (gas: 18628)
SetPermission_Test:test_SetPermission_PermissionNotSet() (gas: 38349)
SetPermission_Test:test_SetPermission_PermissionSet_ResetPermission() (gas: 42412)
SetPermission_Test:test_SetPermission_PermissionSet_ResetPermission_Event() (gas: 46054)
SetPermission_Test:test_SetPermission_PermissionSet_UnsetPermission() (gas: 28400)
SetPermission_Test:test_SetPermission_PermissionSet_UnsetPermission_Event() (gas: 32639)
TransferOwnership_Test:test_RevertWhen_CallerNotOwner() (gas: 16340)
TransferOwnership_Test:test_TransferOwnership() (gas: 15420)
TransferOwnership_Test:test_TransferOwnership_Event() (gas: 20929)
TransferOwnership_Test:test_TransferOwnership_ToZeroAddress() (gas: 8448)
UninstallPlugin_Test:test_RevertWhen_CallerNotOwner() (gas: 18311)
UninstallPlugin_Test:test_RevertWhen_PluginHasNoMethods() (gas: 16736)
UninstallPlugin_Test:test_UninstallPlugin() (gas: 51575)
UninstallPlugin_Test:test_UninstallPlugin_Event() (gas: 51026)
UninstallPlugin_Test:test_UninstallPlugin_PluginNotInstalledBefore() (gas: 24953)
1 change: 1 addition & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"func-name-mixedcase": "off",
"func-visibility": ["error", { "ignoreConstructors": true }],
"max-line-length": ["error", 120],
"no-complex-fallback": "off",
"no-empty-blocks": "off",
"no-inline-assembly": "off",
"not-rely-on-time": "off",
Expand Down
136 changes: 121 additions & 15 deletions src/PRBProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity >=0.8.4;

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

/*
Expand Down Expand Up @@ -32,7 +33,10 @@ contract PRBProxy is IPRBProxy {
INTERNAL STORAGE
//////////////////////////////////////////////////////////////////////////*/

/// @notice Maps envoys to target contracts to function selectors to boolean flags.
/// @dev Maps plugin methods to plugin implementation.
mapping(bytes4 => IPRBProxyPlugin) internal plugins;

/// @dev Maps envoys to target contracts to function selectors to boolean flags.
mapping(address => mapping(address => mapping(bytes4 => bool))) internal permissions;

/*//////////////////////////////////////////////////////////////////////////
Expand All @@ -57,9 +61,39 @@ contract PRBProxy is IPRBProxy {
}

/*//////////////////////////////////////////////////////////////////////////
FALLBACK FUNCTION
FALLBACK FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Used for running plugins.
/// @dev Called when the call data is not empty.
fallback(bytes calldata data) external payable returns (bytes memory response) {
// Check if the function signature exists in the installed plugin methods mapping.
IPRBProxyPlugin plugin = plugins[msg.sig];
if (address(plugin) == address(0)) {
revert PRBProxy_PluginNotInstalledForMethod(msg.sender, msg.sig);
}

// Delegate call to the plugin.
bool success;
(success, response) = _safeDelegateCall(address(plugin), data);

// Log the plugin run.
emit RunPlugin(plugin, data, response);

// Check if the call was successful or not.
if (!success) {
// If there is return data, the call reverted with a reason or a custom error.
if (response.length > 0) {
assembly {
let returndata_size := mload(response)
revert(add(32, response), returndata_size)
}
} else {
revert PRBProxy_PluginReverted(plugin);
}
}
}

/// @dev Called when the call data is empty.
receive() external payable {}

Expand All @@ -76,7 +110,12 @@ contract PRBProxy is IPRBProxy {
permission = permissions[envoy][target][selector];
}

/*//////////////////////////////////////////////////////////////////////////
/// @inheritdoc IPRBProxy
function getPluginForMethod(bytes4 method) external view override returns (IPRBProxyPlugin plugin) {
plugin = plugins[method];
}

/*/////////////////////////////////////////////////////////////////////////
PUBLIC NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

Expand All @@ -103,20 +142,9 @@ contract PRBProxy is IPRBProxy {
revert PRBProxy_TargetNotContract(target);
}

// Save the owner address in memory. This local variable cannot be modified during the DELEGATECALL.
address owner_ = owner;

// Reserve some gas to ensure that the function has enough to finish the execution.
uint256 stipend = gasleft() - minGasReserve;

// Delegate call to the target contract.
bool success;
(success, response) = target.delegatecall{ gas: stipend }(data);

// Check that the owner has not been changed.
if (owner_ != owner) {
revert PRBProxy_OwnerChanged(owner_, owner);
}
(success, response) = _safeDelegateCall(target, data);

// Log the execution.
emit Execute(target, data, response);
Expand All @@ -136,6 +164,34 @@ contract PRBProxy is IPRBProxy {
}
}

/// @inheritdoc IPRBProxy
function installPlugin(IPRBProxyPlugin plugin) external override {
// Check that the caller is the owner.
if (owner != msg.sender) {
revert PRBProxy_NotOwner(owner, msg.sender);
}

// Get the method list to install.
bytes4[] memory methodList = plugin.methodList();

// The plugin must have at least one listed method.
uint256 length = methodList.length;
if (length == 0) {
revert PRBProxy_NoPluginMethods(plugin);
}

// Enable every method in the list.
for (uint256 i = 0; i < length; ) {
plugins[methodList[i]] = plugin;
unchecked {
i += 1;
}
}

// Log the plugin installation.
emit InstallPlugin(plugin);
}

/// @inheritdoc IPRBProxy
function setPermission(
address envoy,
Expand All @@ -158,4 +214,54 @@ contract PRBProxy is IPRBProxy {
// Log the transfer of the owner.
emit TransferOwnership(oldOwner, newOwner);
}

/// @inheritdoc IPRBProxy
function uninstallPlugin(IPRBProxyPlugin plugin) external override {
// Check that the caller is the owner.
if (owner != msg.sender) {
revert PRBProxy_NotOwner(owner, msg.sender);
}

// Get the method list to uninstall.
bytes4[] memory methodList = plugin.methodList();

// The plugin must have at least one listed method.
uint256 length = methodList.length;
if (length == 0) {
revert PRBProxy_NoPluginMethods(plugin);
}

// Disable every method in the list.
for (uint256 i = 0; i < length; ) {
delete plugins[methodList[i]];
unchecked {
i += 1;
}
}

// Log the plugin uninstallation.
emit UninstallPlugin(plugin);
}

/*//////////////////////////////////////////////////////////////////////////
INTERNAL NON-CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Performs a delegatecall to the given address with the given data.
/// @dev Shared logic between the {execute} and the {fallback} functions.
function _safeDelegateCall(address to, bytes memory data) internal returns (bool success, bytes memory response) {
// Save the owner address in memory. This variable cannot be modified during the DELEGATECALL.
address owner_ = owner;

// Reserve some gas to ensure that the function has enough to finish the execution.
uint256 stipend = gasleft() - minGasReserve;

// Delegate call to the given contract.
(success, response) = to.delegatecall{ gas: stipend }(data);

// Check that the owner has not been changed.
if (owner_ != owner) {
revert PRBProxy_OwnerChanged(owner_, owner);
}
}
}
Loading

0 comments on commit 7daea25

Please sign in to comment.