From 7ede37254b60f3dc244ec3e86abeeb563488e2df Mon Sep 17 00:00:00 2001 From: Josh Levine Date: Fri, 31 Jan 2025 17:05:56 -0800 Subject: [PATCH 1/4] feat: add swap owner to allowlist, test --- script/RumpelConfig.sol | 21 +++++++- src/interfaces/external/ISafe.sol | 4 ++ test/RumpelWalletSwapOwner.t.sol | 83 +++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 test/RumpelWalletSwapOwner.t.sol diff --git a/script/RumpelConfig.sol b/script/RumpelConfig.sol index ada0657..fb77ecb 100644 --- a/script/RumpelConfig.sol +++ b/script/RumpelConfig.sol @@ -3,6 +3,7 @@ pragma solidity =0.8.24; import {RumpelGuard} from "../src/RumpelGuard.sol"; import {RumpelModule} from "../src/RumpelModule.sol"; +import {ISafe} from "../src/interfaces/external/ISafe.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {console} from "forge-std/console.sol"; @@ -254,6 +255,8 @@ library RumpelConfig { return new ProtocolGuardConfig[](0); } else if (tagHash == keccak256(bytes("pendle-usde-yts"))) { return new ProtocolGuardConfig[](0); + } else if (tagHash == keccak256(bytes("enable-swap-owner"))) { + return getEnableSwapOwnerProtocolGuard(); } revert("Unsupported tag"); @@ -308,6 +311,8 @@ library RumpelConfig { return getPermAllowMarchAndMay2025SusdeYTsTokenGuardConfigs(); } else if (tagHash == keccak256(bytes("pendle-usde-yts"))) { return getPendleUSDEYTsTokenGuardConfigs(); + } else if (tagHash == keccak256(bytes("enable-swap-owner"))) { + return new TokenGuardConfig[](0); } revert("Unsupported tag"); @@ -359,6 +364,8 @@ library RumpelConfig { return getMarchAndMay20252025SusdeYTsTokenModuleConfigs(); } else if (tagHash == keccak256(bytes("pendle-usde-yts"))) { return new TokenModuleConfig[](0); + } else if (tagHash == keccak256(bytes("enable-swap-owner"))) { + return new TokenModuleConfig[](0); } revert("Unsupported tag"); @@ -404,9 +411,11 @@ library RumpelConfig { } else if (tagHash == keccak256(bytes("remove-lrt2-claiming"))) { return new ProtocolModuleConfig[](0); } else if (tagHash == keccak256(bytes("perm-allow-march-may-2025-susde-yts"))) { - return new ProtocolModuleConfig[](0); + return new ProtocolModuleConfig[](0); } else if (tagHash == keccak256(bytes("pendle-usde-yts"))) { return new ProtocolModuleConfig[](0); + } else if (tagHash == keccak256(bytes("enable-swap-owner"))) { + return new ProtocolModuleConfig[](0); } revert("Unsupported tag"); @@ -1246,6 +1255,16 @@ library RumpelConfig { return configs; } + + function getEnableSwapOwnerProtocolGuard() internal pure returns (ProtocolGuardConfig[] memory) { + ProtocolGuardConfig[] memory configs = new ProtocolGuardConfig[](1); + + configs[0] = ProtocolGuardConfig({target: address(0), selectorStates: new SelectorState[](1)}); + configs[0].selectorStates[0] = + SelectorState({selector: ISafe.swapOwner.selector, state: RumpelGuard.AllowListState.ON}); + + return configs; + } } interface IMorphoBundler { diff --git a/src/interfaces/external/ISafe.sol b/src/interfaces/external/ISafe.sol index 80b2bc0..488bb94 100644 --- a/src/interfaces/external/ISafe.sol +++ b/src/interfaces/external/ISafe.sol @@ -76,6 +76,10 @@ interface ISafe { function addOwnerWithThreshold(address owner, uint256 _threshold) external; + function removeOwner(address prevOwner, address owner, uint256 _threshold) external; + + function swapOwner(address prevOwner, address oldOwner, address newOwner) external; + // Fallback handler functions function isValidSignature(bytes32 _dataHash, bytes calldata _signature) external view returns (bytes4); diff --git a/test/RumpelWalletSwapOwner.t.sol b/test/RumpelWalletSwapOwner.t.sol new file mode 100644 index 0000000..853f121 --- /dev/null +++ b/test/RumpelWalletSwapOwner.t.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity =0.8.24; + +import {RumpelGuard} from "../src/RumpelGuard.sol"; +import {RumpelModule} from "../src/RumpelModule.sol"; +import {InitializationScript} from "../src/InitializationScript.sol"; +import {RumpelWalletFactory} from "../src/RumpelWalletFactory.sol"; + +import {ISafe, Enum} from "../src/interfaces/external/ISafe.sol"; +import {RumpelWalletFactoryScripts} from "../script/RumpelWalletFactory.s.sol"; + +contract RumpelWalletSwapOwnerTest is Test { + RumpelModule public rumpelModule = RumpelModule(0x28c3498B4956f4aD8d4549ACA8F66260975D361a); + RumpelGuard public rumpelGuard = RumpelGuard(0x9000FeF2846A5253fD2C6ed5241De0fddb404302); + RumpelWalletFactory public rumpelWalletFactory = RumpelWalletFactory(0x5774AbCF415f34592514698EB075051E97Db2937); + + address alice; + uint256 alicePk; + + struct SafeTX { + address to; + uint256 value; + bytes data; + Enum.Operation operation; + } + + function setUp() public { + (alice, alicePk) = makeAddrAndKey("alice"); + + string memory MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); + uint256 FORK_BLOCK_NUMBER = 21748243; // Feb-01-2025 12:59:47 AM +UTC + vm.createSelectFork(MAINNET_RPC_URL, FORK_BLOCK_NUMBER); + } + + function test_SwapOwner() public { + RumpelWalletFactoryScripts scripts = new RumpelWalletFactoryScripts(); + + address[] memory owners = new address[](1); + owners[0] = address(alice); + + InitializationScript.InitCall[] memory initCalls = new InitializationScript.InitCall[](0); + address safe = rumpelWalletFactory.createWallet(owners, 1, initCalls); + + address NEW_OWNER = address(0x123); + + assertEq(ISafe(safe).getOwners().length, 1); + assertEq(ISafe(safe).getOwners()[0], address(alice)); + + bytes memory swapOwnerData = + abi.encodeWithSelector(ISafe.swapOwner.selector, address(0x1), address(alice), NEW_OWNER); + vm.expectRevert( + abi.encodeWithSelector(RumpelGuard.CallNotAllowed.selector, address(safe), bytes4(swapOwnerData)) + ); + this._execSafeTx(ISafe(safe), safe, 0, swapOwnerData, Enum.Operation.Call); + + assertEq(ISafe(safe).getOwners().length, 1); + assertEq(ISafe(safe).getOwners()[0], address(alice)); + + // Run the script to enable the swapOwner call + scripts.updateGuardAndModuleLists(rumpelGuard, rumpelModule, "enable-swap-owner"); + + this._execSafeTx(ISafe(safe), safe, 0, swapOwnerData, Enum.Operation.Call); + + assertEq(ISafe(safe).getOwners().length, 1); + assertEq(ISafe(safe).getOwners()[0], NEW_OWNER); + } + + function _execSafeTx(ISafe safe, address to, uint256 value, bytes memory data, Enum.Operation operation) public { + SafeTX memory safeTX = SafeTX({to: to, value: value, data: data, operation: operation}); + + uint256 nonce = safe.nonce(); + + bytes32 txHash = safe.getTransactionHash( + safeTX.to, safeTX.value, safeTX.data, safeTX.operation, 0, 0, 0, address(0), payable(address(0)), nonce + ); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, txHash); + bytes memory signature = abi.encodePacked(r, s, v); + + safe.execTransaction( + safeTX.to, safeTX.value, safeTX.data, safeTX.operation, 0, 0, 0, address(0), payable(address(0)), signature + ); + } +} From 8ec96b61d1efc55114433f93b79b53c12120b331 Mon Sep 17 00:00:00 2001 From: Josh Levine Date: Fri, 31 Jan 2025 17:07:43 -0800 Subject: [PATCH 2/4] chore: add test import --- test/RumpelWalletSwapOwner.t.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/RumpelWalletSwapOwner.t.sol b/test/RumpelWalletSwapOwner.t.sol index 853f121..edaa992 100644 --- a/test/RumpelWalletSwapOwner.t.sol +++ b/test/RumpelWalletSwapOwner.t.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity =0.8.24; +import {Test} from "forge-std/Test.sol"; + import {RumpelGuard} from "../src/RumpelGuard.sol"; import {RumpelModule} from "../src/RumpelModule.sol"; import {InitializationScript} from "../src/InitializationScript.sol"; From da11e5fb056bf9c10fa015f3e7ea4554f249608f Mon Sep 17 00:00:00 2001 From: Josh Levine Date: Wed, 5 Feb 2025 23:50:43 -0800 Subject: [PATCH 3/4] chore: move safe interface into config --- script/RumpelConfig.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/script/RumpelConfig.sol b/script/RumpelConfig.sol index fb77ecb..e11428f 100644 --- a/script/RumpelConfig.sol +++ b/script/RumpelConfig.sol @@ -3,7 +3,6 @@ pragma solidity =0.8.24; import {RumpelGuard} from "../src/RumpelGuard.sol"; import {RumpelModule} from "../src/RumpelModule.sol"; -import {ISafe} from "../src/interfaces/external/ISafe.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {console} from "forge-std/console.sol"; @@ -1261,7 +1260,7 @@ library RumpelConfig { configs[0] = ProtocolGuardConfig({target: address(0), selectorStates: new SelectorState[](1)}); configs[0].selectorStates[0] = - SelectorState({selector: ISafe.swapOwner.selector, state: RumpelGuard.AllowListState.ON}); + SelectorState({selector: Safe.swapOwner.selector, state: RumpelGuard.AllowListState.ON}); return configs; } @@ -1413,3 +1412,7 @@ interface ILRT2Claim { bytes32[] calldata merkleProof ) external; } + +interface Safe { + function swapOwner(address prevOwner, address oldOwner, address newOwner) external; +} From dbfb5fadb6fa48b39c350e39dd64c505909b581d Mon Sep 17 00:00:00 2001 From: Josh Levine Date: Wed, 5 Feb 2025 23:51:59 -0800 Subject: [PATCH 4/4] chore: add set owner safe batch --- safe-batches/enable-swap-owner.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 safe-batches/enable-swap-owner.json diff --git a/safe-batches/enable-swap-owner.json b/safe-batches/enable-swap-owner.json new file mode 100644 index 0000000..4d9104a --- /dev/null +++ b/safe-batches/enable-swap-owner.json @@ -0,0 +1,20 @@ +{ + "version": "1.0", + "chainId": "1", + "createdAt": 1738828295179, + "meta": { + "name": "Transactions Batch", + "description": "enable-swap-owner", + "txBuilderVersion": "1.10.0", + "createdFromSafeAddress": "0x9D89745fD63Af482ce93a9AdB8B0BbDbb98D3e06", + "createdFromOwnerAddress": "", + "checksum": "0x102a3dcd91b01f97351f36f92ab491ab09e4b218f3525dc41c30cd1e89eb820c" + }, + "transactions": [ + { + "to": "0x9000fef2846a5253fd2c6ed5241de0fddb404302", + "value": "0x0", + "data": "0x5534fa0c0000000000000000000000000000000000000000000000000000000000000000e318b52b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001" + } + ] +} \ No newline at end of file