diff --git a/lib/prb-proxy b/lib/prb-proxy index 75ef9beb..82d14b9d 160000 --- a/lib/prb-proxy +++ b/lib/prb-proxy @@ -1 +1 @@ -Subproject commit 75ef9bebf4ca95b1d5ef5368f71eba48bbb261fc +Subproject commit 82d14b9d0b0f6be3df43268c41cffec4701368f8 diff --git a/src/SablierV2ProxyPlugin.sol b/src/SablierV2ProxyPlugin.sol index f15e596c..a9be57c2 100644 --- a/src/SablierV2ProxyPlugin.sol +++ b/src/SablierV2ProxyPlugin.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.19; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { PRBProxyPlugin } from "@prb/proxy/abstracts/PRBProxyPlugin.sol"; +import { IPRBProxy } from "@prb/proxy/interfaces/IPRBProxy.sol"; import { IPRBProxyPlugin } from "@prb/proxy/interfaces/IPRBProxyPlugin.sol"; import { ISablierV2Lockup } from "@sablier/v2-core/interfaces/ISablierV2Lockup.sol"; import { ISablierV2LockupSender } from "@sablier/v2-core/interfaces/hooks/ISablierV2LockupSender.sol"; @@ -35,8 +35,7 @@ import { Errors } from "./libraries/Errors.sol"; /// @notice See the documentation in {ISablierV2ProxyPlugin}. contract SablierV2ProxyPlugin is OnlyDelegateCall, // 0 inherited components - ISablierV2ProxyPlugin, // 3 inherited components - PRBProxyPlugin // 3 inherited components + ISablierV2ProxyPlugin // 2 inherited components { using SafeERC20 for IERC20; @@ -60,7 +59,7 @@ contract SablierV2ProxyPlugin is //////////////////////////////////////////////////////////////////////////*/ /// @inheritdoc IPRBProxyPlugin - function methodList() external pure returns (bytes4[] memory methods) { + function getMethods() external pure returns (bytes4[] memory methods) { methods = new bytes4[](1); methods[0] = this.onStreamCanceled.selector; } @@ -96,6 +95,7 @@ contract SablierV2ProxyPlugin is // Effects: forward the refunded assets to the proxy owner. IERC20 asset = lockup.getAsset(streamId); + address owner = IPRBProxy(address(this)).owner(); asset.safeTransfer({ to: owner, value: senderAmount }); } } diff --git a/src/SablierV2ProxyTarget.sol b/src/SablierV2ProxyTarget.sol index ceba01b5..d828df98 100644 --- a/src/SablierV2ProxyTarget.sol +++ b/src/SablierV2ProxyTarget.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.19; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { PRBProxyStorage } from "@prb/proxy/abstracts/PRBProxyStorage.sol"; +import { IPRBProxy } from "@prb/proxy/interfaces/IPRBProxy.sol"; import { ISablierV2Lockup } from "@sablier/v2-core/interfaces/ISablierV2Lockup.sol"; import { ISablierV2LockupLinear } from "@sablier/v2-core/interfaces/ISablierV2LockupLinear.sol"; import { ISablierV2LockupDynamic } from "@sablier/v2-core/interfaces/ISablierV2LockupDynamic.sol"; @@ -38,8 +38,7 @@ import { Batch, Permit2Params } from "./types/DataTypes.sol"; /// @notice See the documentation in {ISablierV2ProxyTarget}. contract SablierV2ProxyTarget is ISablierV2ProxyTarget, // 0 inherited components - OnlyDelegateCall, // 0 inherited components - PRBProxyStorage // 1 inherited component + OnlyDelegateCall // 0 inherited components { using SafeERC20 for IERC20; @@ -113,7 +112,7 @@ contract SablierV2ProxyTarget is uint256 deltaBalance = finalBalance - initialBalance; // Forward the delta to the proxy owner. This cannot be zero because settled streams cannot be canceled. - asset.safeTransfer({ to: owner, value: deltaBalance }); + asset.safeTransfer({ to: _getOwner(), value: deltaBalance }); } /// @inheritdoc ISablierV2ProxyTarget @@ -650,7 +649,12 @@ contract SablierV2ProxyTarget is } } - /// @dev Shared logic between `cancelMultiple` and `batchCancelMultiple`. + /// @dev Helper function to retrieve the proxy's owner, which is stored as an immutable variable in the proxy. + function _getOwner() internal view returns (address) { + return IPRBProxy(address(this)).owner(); + } + + /// @dev Shared logic between {cancelMultiple} and {batchCancelMultiple}. function _postMultipleCancellations(uint256[] memory initialBalances, IERC20[] calldata assets) internal { uint256 assetCount = assets.length; uint256 finalBalance; @@ -661,7 +665,7 @@ contract SablierV2ProxyTarget is deltaBalance = finalBalance - initialBalances[i]; // Forward the delta to the proxy owner. This cannot be zero because settled streams cannot be canceled. - assets[i].safeTransfer({ to: owner, value: deltaBalance }); + assets[i].safeTransfer({ to: _getOwner(), value: deltaBalance }); // Increment the for loop iterator. unchecked { @@ -700,13 +704,13 @@ contract SablierV2ProxyTarget is internal { // Retrieve the proxy owner. - address owner_ = owner; + address owner = _getOwner(); // Permit the proxy to spend funds from the proxy owner. - PERMIT2.permit({ owner: owner_, permitSingle: permit2Params.permitSingle, signature: permit2Params.signature }); + PERMIT2.permit({ owner: owner, permitSingle: permit2Params.permitSingle, signature: permit2Params.signature }); // Transfer funds from the proxy owner to the proxy. - PERMIT2.transferFrom({ from: owner_, to: address(this), amount: amount, token: address(asset) }); + PERMIT2.transferFrom({ from: owner, to: address(this), amount: amount, token: address(asset) }); // Approve the Sablier contract to spend funds. _approve(sablierContract, asset, amount); diff --git a/src/interfaces/ISablierV2ProxyPlugin.sol b/src/interfaces/ISablierV2ProxyPlugin.sol index a7b7b6a3..84d9ab3a 100644 --- a/src/interfaces/ISablierV2ProxyPlugin.sol +++ b/src/interfaces/ISablierV2ProxyPlugin.sol @@ -7,14 +7,14 @@ import { ISablierV2LockupSender } from "@sablier/v2-core/interfaces/hooks/ISabli import { ISablierV2Archive } from "./ISablierV2Archive.sol"; /// @title ISablierV2ProxyPlugin -/// @notice Proxy plugin for forwarding the refunded assets to the proxy owner when the recipient cancels a stream +/// @notice Proxy plugin that forwards the refunded assets to the proxy owner when the recipient cancels a stream /// whose sender is the proxy contract. /// @dev Requirements: -/// - The call must not be a standard call. +/// - The call must be a delegate call. /// - The caller must be Sablier. interface ISablierV2ProxyPlugin is ISablierV2LockupSender, // 0 inherited components - IPRBProxyPlugin // 1 inherited component + IPRBProxyPlugin // 0 inherited components { /// @notice Retrieves the address of the archive contract. function archive() external view returns (ISablierV2Archive); diff --git a/test/Base.t.sol b/test/Base.t.sol index 7c6ce798..79dbb4fe 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -4,7 +4,6 @@ pragma solidity >=0.8.19 <0.9.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { IPRBProxy } from "@prb/proxy/interfaces/IPRBProxy.sol"; -import { IPRBProxyAnnex } from "@prb/proxy/interfaces/IPRBProxyAnnex.sol"; import { IPRBProxyRegistry } from "@prb/proxy/interfaces/IPRBProxyRegistry.sol"; import { ISablierV2Lockup } from "@sablier/v2-core/interfaces/ISablierV2Lockup.sol"; import { ISablierV2LockupDynamic } from "@sablier/v2-core/interfaces/ISablierV2LockupDynamic.sol"; @@ -49,7 +48,6 @@ abstract contract Base_Test is Assertions, Events, StdCheats, V2CoreUtils { ISablierV2LockupLinear internal lockupLinear; IAllowanceTransfer internal permit2; ISablierV2ProxyPlugin internal plugin; - IPRBProxyAnnex internal proxyAnnex; IPRBProxyRegistry internal proxyRegistry; ISablierV2ProxyTarget internal target; IWrappedNativeAsset internal weth; @@ -299,15 +297,6 @@ abstract contract Base_Test is Assertions, Events, StdCheats, V2CoreUtils { vm.expectCall({ callee: asset_, count: count, data: abi.encodeCall(IERC20.transferFrom, (from, to, amount)) }); } - /*////////////////////////////////////////////////////////////////////////// - PLUGIN - //////////////////////////////////////////////////////////////////////////*/ - - function installPlugin() internal { - bytes memory data = abi.encodeCall(proxyAnnex.installPlugin, (plugin)); - aliceProxy.execute(address(proxyAnnex), data); - } - /*////////////////////////////////////////////////////////////////////////// TARGET //////////////////////////////////////////////////////////////////////////*/ diff --git a/test/fork/Fork.t.sol b/test/fork/Fork.t.sol index b7abd79f..e689ddb9 100644 --- a/test/fork/Fork.t.sol +++ b/test/fork/Fork.t.sol @@ -3,7 +3,6 @@ pragma solidity >=0.8.19 <0.9.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IPRBProxy } from "@prb/proxy/interfaces/IPRBProxy.sol"; -import { IPRBProxyAnnex } from "@prb/proxy/interfaces/IPRBProxyAnnex.sol"; import { IPRBProxyRegistry } from "@prb/proxy/interfaces/IPRBProxyRegistry.sol"; import { ISablierV2LockupDynamic } from "@sablier/v2-core/interfaces/ISablierV2LockupDynamic.sol"; import { ISablierV2LockupLinear } from "@sablier/v2-core/interfaces/ISablierV2LockupLinear.sol"; @@ -38,7 +37,7 @@ abstract contract Fork_Test is Base_Test, V2CoreFuzzers { function setUp() public virtual override { // Fork the Goerli testnet. - vm.createSelectFork({ blockNumber: 9_093_100, urlOrAlias: "goerli" }); + vm.createSelectFork({ blockNumber: 9_261_300, urlOrAlias: "goerli" }); // Set up the base test contract. Base_Test.setUp(); @@ -85,8 +84,7 @@ abstract contract Fork_Test is Base_Test, V2CoreFuzzers { /// @dev Loads all dependencies pre-deployed on Goerli. function loadDependencies() private { weth = IWrappedNativeAsset(WETH_ADDRESS); - proxyAnnex = IPRBProxyAnnex(0x0254C4467cBbdbe8d5E01e68de0DF7b20dD2A167); - proxyRegistry = IPRBProxyRegistry(0xa87bc4C1Bc54E1C1B28d2dD942A094A6B665B8C9); + proxyRegistry = IPRBProxyRegistry(0x33e200B5fb5e0C57d370d5202c26A35d07A46B98); aliceProxy = loadOrDeployProxy(users.alice.addr); permit2 = IAllowanceTransfer(0x000000000022D473030F116dDEE9F6B43aC78BA3); lockupDynamic = ISablierV2LockupDynamic(0xB2CF57EdDEf081b97A4F2a02f5f6DF1271d0071E); @@ -95,7 +93,7 @@ abstract contract Fork_Test is Base_Test, V2CoreFuzzers { /// @dev Retrieves the proxy and deploys one if none is found. function loadOrDeployProxy(address user) internal returns (IPRBProxy proxy) { - proxy = proxyRegistry.proxies(user); + proxy = proxyRegistry.getProxy({ owner: user }); if (address(proxy) == address(0)) { proxy = proxyRegistry.deployFor(user); } diff --git a/test/fork/plugin/onStreamCanceled.t.sol b/test/fork/plugin/onStreamCanceled.t.sol index c4f5b7e6..15aec5b3 100644 --- a/test/fork/plugin/onStreamCanceled.t.sol +++ b/test/fork/plugin/onStreamCanceled.t.sol @@ -15,7 +15,7 @@ abstract contract OnStreamCanceled_Fork_Test is Fork_Test, PermitSignature { function setUp() public virtual override { Fork_Test.setUp(); - installPlugin(); + proxyRegistry.installPlugin(plugin); if (!archive.isListed(address(lockupLinear))) { address archiveAdmin = archive.admin(); diff --git a/test/integration/Integration.t.sol b/test/integration/Integration.t.sol index 6d338a9d..5840844f 100644 --- a/test/integration/Integration.t.sol +++ b/test/integration/Integration.t.sol @@ -53,7 +53,7 @@ abstract contract Integration_Test is Base_Test { function deployDependencies() private { weth = new WETH(); wlc = new WLC(); - (proxyAnnex, proxyRegistry) = new PRBProxyPrecompiles().deploySystem(); + proxyRegistry = new PRBProxyPrecompiles().deployRegistry(); aliceProxy = proxyRegistry.deployFor(users.alice.addr); permit2 = IAllowanceTransfer(new DeployPermit2().run()); (, lockupDynamic, lockupLinear,) = new V2CorePrecompiles().deployCore(users.admin.addr); diff --git a/test/integration/plugin/get-methods/getMethods.t.sol b/test/integration/plugin/get-methods/getMethods.t.sol new file mode 100644 index 00000000..4e780ac4 --- /dev/null +++ b/test/integration/plugin/get-methods/getMethods.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19 <0.9.0; + +import { SablierV2ProxyPlugin } from "src/SablierV2ProxyPlugin.sol"; + +import { Integration_Test } from "../../Integration.t.sol"; + +contract GetMethods_Integration_Test is Integration_Test { + function test_GetMethods() external { + bytes4[] memory actualMethods = plugin.getMethods(); + bytes4[] memory expectedMethods = new bytes4[](1); + expectedMethods[0] = SablierV2ProxyPlugin.onStreamCanceled.selector; + assertEq(actualMethods, expectedMethods, "methods do not match"); + } +} diff --git a/test/integration/plugin/method-list/methodList.t.sol b/test/integration/plugin/method-list/methodList.t.sol deleted file mode 100644 index 82011b21..00000000 --- a/test/integration/plugin/method-list/methodList.t.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.19 <0.9.0; - -import { SablierV2ProxyPlugin } from "src/SablierV2ProxyPlugin.sol"; - -import { Integration_Test } from "../../Integration.t.sol"; - -contract MethodList_Integration_Test is Integration_Test { - function test_MethodList() external { - bytes4[] memory actualMethodList = plugin.methodList(); - bytes4[] memory expectedMethodList = new bytes4[](1); - expectedMethodList[0] = SablierV2ProxyPlugin.onStreamCanceled.selector; - assertEq(actualMethodList, expectedMethodList, "method list does not match"); - } -} diff --git a/test/integration/plugin/on-stream-canceled/onStreamCanceled.t.sol b/test/integration/plugin/on-stream-canceled/onStreamCanceled.t.sol index 091c7dce..e6ff7938 100644 --- a/test/integration/plugin/on-stream-canceled/onStreamCanceled.t.sol +++ b/test/integration/plugin/on-stream-canceled/onStreamCanceled.t.sol @@ -11,7 +11,7 @@ contract OnStreamCanceled_Integration_Test is Integration_Test { function setUp() public virtual override { Integration_Test.setUp(); - installPlugin(); + proxyRegistry.installPlugin(plugin); streamId = createWithRange(); // List the {LockupLinear} contract in the archive.