Skip to content

Commit c24e576

Browse files
committed
magic address permissions
1 parent 955c131 commit c24e576

File tree

2 files changed

+322
-4
lines changed

2 files changed

+322
-4
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.8.18;
3+
4+
import { IPRBProxy } from "@prb/proxy/src/interfaces/IPRBProxy.sol";
5+
import { IPRBProxyPlugin } from "@prb/proxy/src/interfaces/IPRBProxyPlugin.sol";
6+
import { IPRBProxyRegistry } from "@prb/proxy/src/interfaces/IPRBProxyRegistry.sol";
7+
import { PRBProxy } from "@prb/proxy/src/PRBProxy.sol";
8+
9+
/// @author Modified from prb-proxy (https://github.com/PaulRBerg/prb-proxy/blob/main/src/PRBProxyRegistry.sol)
10+
/// @title PRBProxyRegistry
11+
/// @dev See the documentation in {IPRBProxyRegistry}.
12+
contract PRBProxyRegistryModified is IPRBProxyRegistry {
13+
/*//////////////////////////////////////////////////////////////////////////
14+
CONSTANTS
15+
//////////////////////////////////////////////////////////////////////////*/
16+
17+
/// @inheritdoc IPRBProxyRegistry
18+
string public constant override VERSION = "4.0.2";
19+
20+
/// @dev Magic value to override target permissions. Holders can execute on any target.
21+
address public constant MAGIC_TARGET = address(0x42);
22+
23+
/*//////////////////////////////////////////////////////////////////////////
24+
USER-FACING STORAGE
25+
//////////////////////////////////////////////////////////////////////////*/
26+
27+
/// @inheritdoc IPRBProxyRegistry
28+
ConstructorParams public override constructorParams;
29+
30+
/*//////////////////////////////////////////////////////////////////////////
31+
INTERNAL STORAGE
32+
//////////////////////////////////////////////////////////////////////////*/
33+
34+
mapping(address owner => mapping(IPRBProxyPlugin plugin => bytes4[] methods)) internal _methods;
35+
36+
mapping(address owner => mapping(address envoy => mapping(address target => bool permission)))
37+
internal _permissions;
38+
39+
mapping(address owner => mapping(bytes4 method => IPRBProxyPlugin plugin)) internal _plugins;
40+
41+
mapping(address owner => IPRBProxy proxy) internal _proxies;
42+
43+
/*//////////////////////////////////////////////////////////////////////////
44+
MODIFIERS
45+
//////////////////////////////////////////////////////////////////////////*/
46+
47+
/// @notice Checks that the caller has a proxy.
48+
modifier onlyCallerWithProxy() {
49+
if (address(_proxies[msg.sender]) == address(0)) {
50+
revert PRBProxyRegistry_UserDoesNotHaveProxy(msg.sender);
51+
}
52+
_;
53+
}
54+
55+
/// @notice Check that the user does not have a proxy.
56+
modifier onlyNonProxyOwner(address user) {
57+
IPRBProxy proxy = _proxies[user];
58+
if (address(proxy) != address(0)) {
59+
revert PRBProxyRegistry_UserHasProxy(user, proxy);
60+
}
61+
_;
62+
}
63+
64+
/*//////////////////////////////////////////////////////////////////////////
65+
USER-FACING CONSTANT FUNCTIONS
66+
//////////////////////////////////////////////////////////////////////////*/
67+
68+
/// @inheritdoc IPRBProxyRegistry
69+
function getMethodsByOwner(address owner, IPRBProxyPlugin plugin) external view returns (bytes4[] memory methods) {
70+
methods = _methods[owner][plugin];
71+
}
72+
73+
/// @inheritdoc IPRBProxyRegistry
74+
function getMethodsByProxy(
75+
IPRBProxy proxy,
76+
IPRBProxyPlugin plugin
77+
) external view returns (bytes4[] memory methods) {
78+
methods = _methods[proxy.owner()][plugin];
79+
}
80+
81+
/// @inheritdoc IPRBProxyRegistry
82+
function getPermissionByOwner(
83+
address owner,
84+
address envoy,
85+
address target
86+
) external view returns (bool permission) {
87+
permission = _permissions[owner][envoy][target] || _permissions[owner][envoy][MAGIC_TARGET];
88+
}
89+
90+
/// @inheritdoc IPRBProxyRegistry
91+
function getPermissionByProxy(
92+
IPRBProxy proxy,
93+
address envoy,
94+
address target
95+
) external view returns (bool permission) {
96+
permission = _permissions[proxy.owner()][envoy][target] || _permissions[proxy.owner()][envoy][MAGIC_TARGET];
97+
}
98+
99+
/// @inheritdoc IPRBProxyRegistry
100+
function getPluginByOwner(address owner, bytes4 method) external view returns (IPRBProxyPlugin plugin) {
101+
plugin = _plugins[owner][method];
102+
}
103+
104+
/// @inheritdoc IPRBProxyRegistry
105+
function getPluginByProxy(IPRBProxy proxy, bytes4 method) external view returns (IPRBProxyPlugin plugin) {
106+
plugin = _plugins[proxy.owner()][method];
107+
}
108+
109+
/// @inheritdoc IPRBProxyRegistry
110+
function getProxy(address user) external view returns (IPRBProxy proxy) {
111+
proxy = _proxies[user];
112+
}
113+
114+
/*//////////////////////////////////////////////////////////////////////////
115+
USER-FACING NON-CONSTANT FUNCTIONS
116+
//////////////////////////////////////////////////////////////////////////*/
117+
118+
/// @inheritdoc IPRBProxyRegistry
119+
function deploy() external override onlyNonProxyOwner(msg.sender) returns (IPRBProxy proxy) {
120+
proxy = _deploy({ owner: msg.sender, target: address(0), data: "" });
121+
}
122+
123+
/// @inheritdoc IPRBProxyRegistry
124+
function deployAndExecute(
125+
address target,
126+
bytes calldata data
127+
) external override onlyNonProxyOwner(msg.sender) returns (IPRBProxy proxy) {
128+
proxy = _deploy({ owner: msg.sender, target: target, data: data });
129+
}
130+
131+
/// @inheritdoc IPRBProxyRegistry
132+
function deployFor(address user) external override onlyNonProxyOwner(user) returns (IPRBProxy proxy) {
133+
proxy = _deploy({ owner: user, target: address(0), data: "" });
134+
}
135+
136+
/// @inheritdoc IPRBProxyRegistry
137+
function deployAndExecuteAndInstallPlugin(
138+
address target,
139+
bytes calldata data,
140+
IPRBProxyPlugin plugin
141+
) external override onlyNonProxyOwner(msg.sender) returns (IPRBProxy proxy) {
142+
proxy = _deploy({ owner: msg.sender, target: target, data: data });
143+
_installPlugin(plugin);
144+
}
145+
146+
/// @inheritdoc IPRBProxyRegistry
147+
function deployAndInstallPlugin(
148+
IPRBProxyPlugin plugin
149+
) external onlyNonProxyOwner(msg.sender) returns (IPRBProxy proxy) {
150+
proxy = _deploy({ owner: msg.sender, target: address(0), data: "" });
151+
_installPlugin(plugin);
152+
}
153+
154+
/// @inheritdoc IPRBProxyRegistry
155+
function installPlugin(IPRBProxyPlugin plugin) external override onlyCallerWithProxy {
156+
_installPlugin(plugin);
157+
}
158+
159+
/// @inheritdoc IPRBProxyRegistry
160+
function setPermission(address envoy, address target, bool permission) external override onlyCallerWithProxy {
161+
address owner = msg.sender;
162+
_permissions[owner][envoy][target] = permission;
163+
emit SetPermission(owner, _proxies[owner], envoy, target, permission);
164+
}
165+
166+
/// @inheritdoc IPRBProxyRegistry
167+
function uninstallPlugin(IPRBProxyPlugin plugin) external override onlyCallerWithProxy {
168+
// Retrieve the methods originally installed by this plugin.
169+
address owner = msg.sender;
170+
bytes4[] memory methods = _methods[owner][plugin];
171+
172+
// The plugin must be a known, previously installed plugin.
173+
uint256 length = methods.length;
174+
if (length == 0) {
175+
revert PRBProxyRegistry_PluginUnknown(plugin);
176+
}
177+
178+
// Uninstall every method in the list.
179+
for (uint256 i = 0; i < length; ) {
180+
delete _plugins[owner][methods[i]];
181+
unchecked {
182+
i += 1;
183+
}
184+
}
185+
186+
// Remove the methods from the reverse mapping.
187+
delete _methods[owner][plugin];
188+
189+
// Log the plugin uninstallation.
190+
emit UninstallPlugin(owner, _proxies[owner], plugin, methods);
191+
}
192+
193+
/*//////////////////////////////////////////////////////////////////////////
194+
INTERNAL NON-CONSTANT FUNCTIONS
195+
//////////////////////////////////////////////////////////////////////////*/
196+
197+
/// @dev See the documentation for the user-facing functions that call this internal function.
198+
function _deploy(address owner, address target, bytes memory data) internal returns (IPRBProxy proxy) {
199+
// Use the address of the owner as the CREATE2 salt.
200+
bytes32 salt = bytes32(abi.encodePacked(owner));
201+
202+
// Set the owner and empty out the target and the data to prevent reentrancy.
203+
constructorParams = ConstructorParams({ owner: owner, target: target, data: data });
204+
205+
// Deploy the proxy with CREATE2.
206+
proxy = new PRBProxy{ salt: salt }();
207+
delete constructorParams;
208+
209+
// Associate the owner and the proxy.
210+
_proxies[owner] = proxy;
211+
212+
// Log the creation of the proxy.
213+
emit DeployProxy({ operator: msg.sender, owner: owner, proxy: proxy });
214+
}
215+
216+
/// @dev See the documentation for the user-facing functions that call this internal function.
217+
function _installPlugin(IPRBProxyPlugin plugin) internal {
218+
// Retrieve the methods to install.
219+
bytes4[] memory methods = plugin.getMethods();
220+
221+
// The plugin must implement at least one method.
222+
uint256 length = methods.length;
223+
if (length == 0) {
224+
revert PRBProxyRegistry_PluginWithZeroMethods(plugin);
225+
}
226+
227+
// Install every method in the list.
228+
address owner = msg.sender;
229+
for (uint256 i = 0; i < length; ) {
230+
// Check for collisions.
231+
bytes4 method = methods[i];
232+
if (address(_plugins[owner][method]) != address(0)) {
233+
revert PRBProxyRegistry_PluginMethodCollision({
234+
currentPlugin: _plugins[owner][method],
235+
newPlugin: plugin,
236+
method: method
237+
});
238+
}
239+
_plugins[owner][method] = plugin;
240+
unchecked {
241+
i += 1;
242+
}
243+
}
244+
245+
// Set the methods in the reverse mapping.
246+
_methods[owner][plugin] = methods;
247+
248+
// Log the plugin installation.
249+
emit InstallPlugin(owner, _proxies[owner], plugin, methods);
250+
}
251+
}

src/test/checkout/PluginCheckout.t.sol

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import { IPRBProxyPlugin } from "@prb/proxy/src/interfaces/IPRBProxyPlugin.sol";
1111
import { IPRBProxy } from "@prb/proxy/src/interfaces/IPRBProxy.sol";
1212
import { IPRBProxyRegistry } from "@prb/proxy/src/interfaces/IPRBProxyRegistry.sol";
1313
import { PRBProxy } from "@prb/proxy/src/PRBProxy.sol";
14-
import { PRBProxyRegistry } from "@prb/proxy/src/PRBProxyRegistry.sol";
14+
import { PRBProxyRegistryModified } from "contracts/prebuilts/unaudited/checkout/PRBProxyRegistryModified.sol";
1515

1616
import "./IQuoter.sol";
1717
import "./ISwapRouter.sol";
1818

1919
contract PluginCheckoutTest is BaseTest {
2020
PluginCheckout internal checkoutPlugin;
2121
PRBProxy internal proxy;
22-
PRBProxyRegistry internal proxyRegistry;
22+
PRBProxyRegistryModified internal proxyRegistry;
2323

2424
address internal owner;
2525
address internal alice;
@@ -72,7 +72,7 @@ contract PluginCheckoutTest is BaseTest {
7272

7373
// deploy contracts
7474
checkoutPlugin = new PluginCheckout();
75-
proxyRegistry = new PRBProxyRegistry();
75+
proxyRegistry = new PRBProxyRegistryModified();
7676

7777
vm.prank(owner);
7878
proxy = PRBProxy(
@@ -126,7 +126,7 @@ contract PluginCheckoutTest is BaseTest {
126126

127127
// deploy checkout contracts
128128
checkoutPlugin = new PluginCheckout();
129-
proxyRegistry = new PRBProxyRegistry();
129+
proxyRegistry = new PRBProxyRegistryModified();
130130
vm.prank(owner);
131131
proxy = PRBProxy(
132132
payable(address(proxyRegistry.deployAndInstallPlugin(IPRBProxyPlugin(address(checkoutPlugin)))))
@@ -247,6 +247,51 @@ contract PluginCheckoutTest is BaseTest {
247247
assertEq(mainCurrency.balanceOf(address(saleRecipient)), _totalPrice - (_totalPrice * platformFeeBps) / 10_000);
248248
}
249249

250+
function test_executeOp_permittedEOA_magicAddress() public {
251+
// deposit currencies in vault
252+
vm.startPrank(owner);
253+
mainCurrency.transfer(address(proxy), 10 ether);
254+
proxyRegistry.setPermission(alice, proxyRegistry.MAGIC_TARGET(), true); // permit other EOA
255+
vm.stopPrank();
256+
257+
// create user op -- claim tokens on targetDrop
258+
uint256 _quantityToClaim = 5;
259+
uint256 _totalPrice = 5 * 10; // claim condition price is set as 10 above in setup
260+
DropERC721.AllowlistProof memory alp;
261+
bytes memory callData = abi.encodeWithSelector(
262+
IDrop.claim.selector,
263+
receiver,
264+
_quantityToClaim,
265+
address(mainCurrency),
266+
10,
267+
alp,
268+
""
269+
);
270+
IPluginCheckout.UserOp memory op = IPluginCheckout.UserOp({
271+
target: address(targetDrop),
272+
currency: address(mainCurrency),
273+
approvalRequired: true,
274+
valueToSend: _totalPrice,
275+
data: callData
276+
});
277+
278+
// check state before
279+
assertEq(targetDrop.balanceOf(receiver), 0);
280+
assertEq(targetDrop.nextTokenIdToClaim(), 0);
281+
assertEq(mainCurrency.balanceOf(address(proxy)), 10 ether);
282+
assertEq(mainCurrency.balanceOf(address(saleRecipient)), 0);
283+
284+
// execute
285+
vm.prank(alice); // non-owner EOA
286+
PluginCheckout(address(proxy)).execute(op);
287+
288+
// check state after
289+
assertEq(targetDrop.balanceOf(receiver), _quantityToClaim);
290+
assertEq(targetDrop.nextTokenIdToClaim(), _quantityToClaim);
291+
assertEq(mainCurrency.balanceOf(address(proxy)), 10 ether - _totalPrice);
292+
assertEq(mainCurrency.balanceOf(address(saleRecipient)), _totalPrice - (_totalPrice * platformFeeBps) / 10_000);
293+
}
294+
250295
function test_revert_executeOp_notAuthorized() public {
251296
// create user op -- claim tokens on targetDrop
252297
bytes memory callData;
@@ -264,6 +309,28 @@ contract PluginCheckoutTest is BaseTest {
264309
PluginCheckout(address(proxy)).execute(op);
265310
}
266311

312+
function test_getPermissions_magicAddress() public {
313+
// give all-target permission to alice
314+
vm.startPrank(owner);
315+
proxyRegistry.setPermission(alice, proxyRegistry.MAGIC_TARGET(), true); // permit other EOA
316+
vm.stopPrank();
317+
318+
address randomTargetOne = address(0x7890);
319+
address randomTargetTwo = address(0x4567);
320+
321+
// check permissions
322+
assertTrue(proxyRegistry.getPermissionByOwner(owner, alice, randomTargetOne));
323+
assertTrue(proxyRegistry.getPermissionByOwner(owner, alice, randomTargetTwo));
324+
assertTrue(proxyRegistry.getPermissionByProxy(proxy, alice, randomTargetOne));
325+
assertTrue(proxyRegistry.getPermissionByProxy(proxy, alice, randomTargetTwo));
326+
327+
// should be false for other address that doesn't have magic permission
328+
assertFalse(proxyRegistry.getPermissionByOwner(owner, bob, randomTargetOne));
329+
assertFalse(proxyRegistry.getPermissionByOwner(owner, bob, randomTargetTwo));
330+
assertFalse(proxyRegistry.getPermissionByProxy(proxy, bob, randomTargetOne));
331+
assertFalse(proxyRegistry.getPermissionByProxy(proxy, bob, randomTargetTwo));
332+
}
333+
267334
function test_withdraw_owner() public {
268335
// add currency
269336
vm.prank(owner);

0 commit comments

Comments
 (0)