diff --git a/deployments/534352/deployments.json b/deployments/534352/deployments.json index b22634b..ed6c7c7 100644 --- a/deployments/534352/deployments.json +++ b/deployments/534352/deployments.json @@ -1,24 +1,28 @@ { "addresses": { - "cashDataProviderImpl": "0x26950904DCF8EcF21af72eA78D32Da830626820c", - "cashDataProviderProxy": "0x61D76fB1eb4645F30dE515d0483Bf3488F4a2B99", - "debtManagerAdminImpl": "0xe417E386Ba5C9EceD30e7A5A028Fd0FF616341fc", - "debtManagerCore": "0xA0f3A57B96Fd6CA31Fe02d45cB18c137dB1DD7b6", - "debtManagerInitializer": "0x15f3723655A1A3EC1C1c8fDd697262b82f5E8ED2", - "debtManagerProxy": "0x90da1ABeea650e420bd14bD28418bD613D1786Bb", - "etherFiCashMultisig": "0x7D829d50aAF400B8B29B3b311F4aD70aD819DC6E", + "cashDataProviderImpl": "0x3c73ac2A698F7b264A8a8b78a8022710aBB787cA", + "cashDataProviderProxy": "0x04238598eFFE31AB66F1fdeCbfBd59aa4117D69A", + "debtManagerAdminImpl": "0x0000000000000000000000000000000000000000", + "debtManagerCore": "0x0000000000000000000000000000000000000000", + "debtManagerInitializer": "0x0000000000000000000000000000000000000000", + "debtManagerProxy": "0x0000000000000000000000000000000000000000", "etherFiWallet": "0x7D829d50aAF400B8B29B3b311F4aD70aD819DC6E", "owner": "0x7D829d50aAF400B8B29B3b311F4aD70aD819DC6E", - "priceProvider": "0x602F0736d1DeF684fb215a6dd93c5F9085eD7E55", - "settlementDispatcherProxy": "0xC81A3cFA2CEC9B3421e13d48bB18bCE38aD1687d", - "settlementDispatcherImpl": "0x228867eB712c617b5651e70d5A6fc95B5BAD6E0A", + "priceProvider": "0x38ea1a441Ab0328b164BD1CED4e9635AbF44b890", "recoverySigner1": "0x7fEd99d0aA90423de55e238Eb5F9416FF7Cc58eF", "recoverySigner2": "0x24e311DA50784Cf9DB1abE59725e4A1A110220FA", - "swapper": "0xabCc719302602218172F8f3f3E9aCd69fF36344F", + "settlementDispatcherImpl": "0xa276232f6A87bFc9C657ec0A679d9218E0D552b6", + "settlementDispatcherProxy": "0x395a6F029D018F1A52E42220a293215b73914dF9", + "swapper": "0x00B09BcE1eb66f414F4C5caAffA9cc61b4f95dA4", "usdc": "0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4", - "userSafeFactory": "0xF89B1e64d72f7D083221586d17bC06c3fDa834e4", - "userSafeFactoryImpl": "0xa930e755cA1D2dD1ffDBB71eB17F127461D92F4C", - "userSafeImpl": "0xc6A9218DF914A513c5f2B21bcBe2B1C8DF4fD071", - "weETH": "0x01f0a31698C4d065659b9bdC21B3610292a1c506" + "userSafeCoreImpl": "0x07aCE651BA09318C6DfEeFc56066585780f62F18", + "userSafeEventEmitterImpl": "0xB48F11E55dC6EFF03f1e2DB5796fe118C527E67A", + "userSafeEventEmitterProxy": "0x0848D817157Bfe0e4d5625EB5E2cA0D3b83053FE", + "userSafeFactoryImpl": "0x8B4922B20bB259c3fB9273796622aa5DAfd641f6", + "userSafeFactoryProxy": "0x0c0B2Dfe0790790Af395dE23712e45e4C13fec57", + "userSafeSettersImpl": "0x83D75Ca3740Fd2d0f0Cdc9B97Ba1075aA45Adf38", + "weETH": "0x01f0a31698C4d065659b9bdC21B3610292a1c506", + "wrappedERC20Impl": "0x0000000000000000000000000000000000000000", + "wrapperTokenFactory": "0x0000000000000000000000000000000000000000" } } \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 19f58dd..d9e5b5c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,7 +6,7 @@ solc = "0.8.24" cbor_metadata = true ffi = true optimizer = true -optimizer_runs = 1000 +optimizer_runs = 0 evm_version = "cancun" ast = true build_info = true diff --git a/script/user-safe/DeployCashSafe.s.sol b/script/user-safe/DeploySettlementDispatcher.s.sol similarity index 97% rename from script/user-safe/DeployCashSafe.s.sol rename to script/user-safe/DeploySettlementDispatcher.s.sol index 4a72523..029590c 100644 --- a/script/user-safe/DeployCashSafe.s.sol +++ b/script/user-safe/DeploySettlementDispatcher.s.sol @@ -4,7 +4,7 @@ import {Script} from "forge-std/Script.sol"; import {SettlementDispatcher} from "../../src/settlement-dispatcher/SettlementDispatcher.sol"; import {UUPSProxy} from "../../src/UUPSProxy.sol"; import {CashDataProvider} from "../../src/utils/CashDataProvider.sol"; -contract DeployCashSafe is Script { +contract DeploySettlementDispatcher is Script { SettlementDispatcher settlementDispatcher; // Scroll address usdc = 0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4; diff --git a/script/user-safe/DeployUserSafe.s.sol b/script/user-safe/DeployUserSafe.s.sol index 062564b..b4e09f3 100644 --- a/script/user-safe/DeployUserSafe.s.sol +++ b/script/user-safe/DeployUserSafe.s.sol @@ -3,17 +3,21 @@ pragma solidity ^0.8.24; import {Utils, ChainConfig} from "./Utils.sol"; import {UserSafeFactory} from "../../src/user-safe/UserSafeFactory.sol"; -import {UserSafe} from "../../src/user-safe/UserSafe.sol"; +import {IUserSafe} from "../../src/interfaces/IUserSafe.sol"; import {stdJson} from "forge-std/StdJson.sol"; +import {UserSafeCore} from "../../src/user-safe/UserSafeCore.sol"; contract DeployUserSafe is Utils { UserSafeFactory userSafeFactory; - UserSafe ownerSafe; + IUserSafe ownerSafe; uint256 defaultDailySpendingLimit = 1000e6; uint256 defaultMonthlySpendingLimit = 10000e6; uint256 collateralLimit = 10000e6; int256 timezoneOffset = 4 * 3600; // Dubai Timezone address ownerEoa; + address recoverySigner1 = 0x7fEd99d0aA90423de55e238Eb5F9416FF7Cc58eF; + address recoverySigner2 = 0x24e311DA50784Cf9DB1abE59725e4A1A110220FA; + function run() public { // Pulling deployer info from the environment @@ -30,17 +34,25 @@ contract DeployUserSafe is Utils { userSafeFactory = UserSafeFactory( stdJson.readAddress( deployments, - string.concat(".", "addresses", ".", "userSafeFactory") + string.concat(".", "addresses", ".", "userSafeFactoryProxy") ) ); + address cashDataProvider = stdJson.readAddress( + deployments, + string.concat(".", "addresses", ".", "cashDataProviderProxy") + ); + bytes memory saltData = abi.encode("ownerSafe", block.timestamp); - ownerSafe = UserSafe( + ownerSafe = IUserSafe( userSafeFactory.createUserSafe( saltData, abi.encodeWithSelector( - UserSafe.initialize.selector, + UserSafeCore.initialize.selector, + cashDataProvider, + recoverySigner1, + recoverySigner2, abi.encode(ownerEoa), defaultDailySpendingLimit, defaultMonthlySpendingLimit, diff --git a/script/user-safe/DeployUserSafeFactory.s.sol b/script/user-safe/DeployUserSafeFactory.s.sol index fb36130..354f9c1 100644 --- a/script/user-safe/DeployUserSafeFactory.s.sol +++ b/script/user-safe/DeployUserSafeFactory.s.sol @@ -3,13 +3,17 @@ pragma solidity ^0.8.24; import {Utils, ChainConfig} from "./Utils.sol"; import {UserSafeFactory} from "../../src/user-safe/UserSafeFactory.sol"; -import {UserSafe} from "../../src/user-safe/UserSafe.sol"; import {stdJson} from "forge-std/StdJson.sol"; import {UUPSProxy} from "../../src/UUPSProxy.sol"; +import {UserSafeCore} from "../../src/user-safe/UserSafeCore.sol"; +import {UserSafeSetters} from "../../src/user-safe/UserSafeSetters.sol"; + contract DeployUserSafeFactory is Utils { using stdJson for string; + UserSafeCore userSafeCoreImpl; + UserSafeSetters userSafeSettersImpl; UserSafeFactory userSafeFactory; address userSafeImpl; address cashDataProvider; @@ -36,6 +40,8 @@ contract DeployUserSafeFactory is Utils { string.concat(".", "addresses", ".", "cashDataProviderProxy") ); + userSafeCoreImpl = new UserSafeCore(); + userSafeSettersImpl = new UserSafeSetters(); address factoryImpl = address(new UserSafeFactory()); userSafeFactory = UserSafeFactory( @@ -45,7 +51,9 @@ contract DeployUserSafeFactory is Utils { UserSafeFactory.initialize.selector, address(userSafeImpl), owner, - address(cashDataProvider) + address(cashDataProvider), + address(userSafeCoreImpl), + address(userSafeSettersImpl) )) ) ); diff --git a/script/user-safe/MockSetup.s.sol b/script/user-safe/MockSetup.s.sol index 1cb44f2..d8db7e8 100644 --- a/script/user-safe/MockSetup.s.sol +++ b/script/user-safe/MockSetup.s.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.24; import {Script} from "forge-std/Script.sol"; import {UserSafeFactory} from "../../src/user-safe/UserSafeFactory.sol"; -import {UserSafe, UserSafeEventEmitter} from "../../src/user-safe/UserSafe.sol"; import {MockERC20} from "../../src/mocks/MockERC20.sol"; import {MockPriceProvider} from "../../src/mocks/MockPriceProvider.sol"; import {MockSwapper} from "../../src/mocks/MockSwapper.sol"; @@ -16,14 +15,19 @@ import {Utils, ChainConfig} from "./Utils.sol"; import {MockAaveAdapter} from "../../src/mocks/MockAaveAdapter.sol"; import {UUPSProxy} from "../../src/UUPSProxy.sol"; import {CashTokenWrapperFactory, CashWrappedERC20} from "../../src/cash-wrapper-token/CashTokenWrapperFactory.sol"; +import {UserSafeEventEmitter} from "../../src/user-safe/UserSafeEventEmitter.sol"; +import {UserSafeCore} from "../../src/user-safe/UserSafeCore.sol"; +import {UserSafeSetters} from "../../src/user-safe/UserSafeSetters.sol"; +import {IUserSafe} from "../../src/interfaces/IUserSafe.sol"; contract DeployMockUserSafeSetup is Utils { MockERC20 usdc; MockERC20 weETH; MockPriceProvider priceProvider; MockSwapper swapper; - UserSafe userSafeImpl; UserSafeEventEmitter userSafeEventEmitter; + UserSafeCore userSafeCoreImpl; + UserSafeSetters userSafeSettersImpl; UserSafeFactory userSafeFactory; IL2DebtManager debtManager; CashDataProvider cashDataProvider; @@ -93,12 +97,8 @@ contract DeployMockUserSafeSetup is Utils { debtManager = IL2DebtManager(address(debtManagerProxy)); - userSafeImpl = new UserSafe( - address(cashDataProvider), - recoverySigner1, - recoverySigner2 - ); - + userSafeCoreImpl = new UserSafeCore(); + userSafeSettersImpl = new UserSafeSetters(); address factoryImpl = address(new UserSafeFactory()); userSafeFactory = UserSafeFactory( @@ -107,9 +107,10 @@ contract DeployMockUserSafeSetup is Utils { abi.encodeWithSelector( UserSafeFactory.initialize.selector, delay, - address(userSafeImpl), owner, - address(cashDataProvider) + address(cashDataProvider), + address(userSafeCoreImpl), + address(userSafeSettersImpl) )) ) ); @@ -169,11 +170,6 @@ contract DeployMockUserSafeSetup is Utils { address(priceProvider) ); vm.serializeAddress(deployedAddresses, "swapper", address(swapper)); - vm.serializeAddress( - deployedAddresses, - "userSafeImpl", - address(userSafeImpl) - ); vm.serializeAddress( deployedAddresses, "userSafeFactoryImpl", @@ -204,6 +200,16 @@ contract DeployMockUserSafeSetup is Utils { "userSafeFactoryProxy", address(userSafeFactory) ); + vm.serializeAddress( + deployedAddresses, + "userSafeSettersImpl", + address(userSafeSettersImpl) + ); + vm.serializeAddress( + deployedAddresses, + "userSafeCoreImpl", + address(userSafeCoreImpl) + ); vm.serializeAddress( deployedAddresses, "wrapperTokenFactory", diff --git a/script/user-safe/Setup.s.sol b/script/user-safe/Setup.s.sol index e766534..c1b7957 100644 --- a/script/user-safe/Setup.s.sol +++ b/script/user-safe/Setup.s.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.24; import {Script} from "forge-std/Script.sol"; import {UserSafeFactory} from "../../src/user-safe/UserSafeFactory.sol"; -import {UserSafe, UserSafeEventEmitter} from "../../src/user-safe/UserSafe.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {PriceProvider} from "../../src/oracle/PriceProvider.sol"; import {SwapperOpenOcean} from "../../src/utils/SwapperOpenOcean.sol"; @@ -19,14 +18,19 @@ import {CashTokenWrapperFactory, CashWrappedERC20} from "../../src/cash-wrapper- import {IWeETH} from "../../src/interfaces/IWeETH.sol"; import {IAggregatorV3} from "../../src/interfaces/IAggregatorV3.sol"; import {SettlementDispatcher} from "../../src/settlement-dispatcher/SettlementDispatcher.sol"; +import {UserSafeCore} from "../../src/user-safe/UserSafeCore.sol"; +import {UserSafeSetters} from "../../src/user-safe/UserSafeSetters.sol"; +import {UserSafeEventEmitter} from "../../src/user-safe/UserSafeEventEmitter.sol"; +import {IUserSafe} from "../../src/interfaces/IUserSafe.sol"; contract DeployUserSafeSetup is Utils { ERC20 usdc; ERC20 weETH; PriceProvider priceProvider; SwapperOpenOcean swapper; - UserSafe userSafeImpl; UserSafeEventEmitter userSafeEventEmitter; + UserSafeCore userSafeCoreImpl; + UserSafeSetters userSafeSettersImpl; UserSafeFactory userSafeFactory; IL2DebtManager debtManager; CashDataProvider cashDataProvider; @@ -140,49 +144,45 @@ contract DeployUserSafeSetup is Utils { chainConfig.swapRouterOpenOcean, supportedCollateralTokens ); - aaveV3Adapter = new EtherFiCashAaveV3Adapter( - chainConfig.aaveV3Pool, - chainConfig.aaveV3PoolDataProvider, - aaveV3ReferralCode, - interestRateMode - ); + // aaveV3Adapter = new EtherFiCashAaveV3Adapter( + // chainConfig.aaveV3Pool, + // chainConfig.aaveV3PoolDataProvider, + // aaveV3ReferralCode, + // interestRateMode + // ); - address cashWrappedERC20Impl = address(new CashWrappedERC20()); - wrapperTokenFactory = new CashTokenWrapperFactory(address(cashWrappedERC20Impl), owner); + // address cashWrappedERC20Impl = address(new CashWrappedERC20()); + // wrapperTokenFactory = new CashTokenWrapperFactory(address(cashWrappedERC20Impl), owner); cashDataProviderImpl = address(new CashDataProvider()); cashDataProvider = CashDataProvider( address(new UUPSProxy(cashDataProviderImpl, "")) ); - address[] memory collateralTokens = new address[](1); - collateralTokens[0] = address(weETH); - address[] memory borrowTokens = new address[](1); - borrowTokens[0] = address(usdc); - - DebtManagerCore.CollateralTokenConfig[] - memory collateralTokenConfig = new DebtManagerCore.CollateralTokenConfig[]( - 1 - ); + // address[] memory collateralTokens = new address[](1); + // collateralTokens[0] = address(weETH); + // address[] memory borrowTokens = new address[](1); + // borrowTokens[0] = address(usdc); - collateralTokenConfig[0].ltv = ltv; - collateralTokenConfig[0].liquidationThreshold = liquidationThreshold; - collateralTokenConfig[0].liquidationBonus = liquidationBonus; - collateralTokenConfig[0].supplyCap = supplyCap; + // DebtManagerCore.CollateralTokenConfig[] + // memory collateralTokenConfig = new DebtManagerCore.CollateralTokenConfig[]( + // 1 + // ); - debtManagerCoreImpl = address(new DebtManagerCore()); - debtManagerAdminImpl = address(new DebtManagerAdmin()); - debtManagerInitializer = address(new DebtManagerInitializer()); - address debtManagerProxy = address(new UUPSProxy(debtManagerInitializer, "")); + // collateralTokenConfig[0].ltv = ltv; + // collateralTokenConfig[0].liquidationThreshold = liquidationThreshold; + // collateralTokenConfig[0].liquidationBonus = liquidationBonus; + // collateralTokenConfig[0].supplyCap = supplyCap; - debtManager = IL2DebtManager(address(debtManagerProxy)); + // debtManagerCoreImpl = address(new DebtManagerCore()); + // debtManagerAdminImpl = address(new DebtManagerAdmin()); + // debtManagerInitializer = address(new DebtManagerInitializer()); + // address debtManagerProxy = address(new UUPSProxy(debtManagerInitializer, "")); - userSafeImpl = new UserSafe( - address(cashDataProvider), - recoverySigner1, - recoverySigner2 - ); + // debtManager = IL2DebtManager(address(debtManagerProxy)); + userSafeCoreImpl = new UserSafeCore(); + userSafeSettersImpl = new UserSafeSetters(); factoryImpl = address(new UserSafeFactory()); userSafeFactory = UserSafeFactory( @@ -191,9 +191,10 @@ contract DeployUserSafeSetup is Utils { abi.encodeWithSelector( UserSafeFactory.initialize.selector, delay, - address(userSafeImpl), owner, - address(cashDataProvider) + address(cashDataProvider), + address(userSafeCoreImpl), + address(userSafeSettersImpl) )) ) ); @@ -212,7 +213,7 @@ contract DeployUserSafeSetup is Utils { )); initializeCashDataProvider(); - initializeDebtManager(debtManagerProxy, collateralTokenConfig); + // initializeDebtManager(debtManagerProxy, collateralTokenConfig); saveDeployments(); vm.stopBroadcast(); @@ -274,11 +275,6 @@ contract DeployUserSafeSetup is Utils { address(priceProvider) ); vm.serializeAddress(deployedAddresses, "swapper", address(swapper)); - vm.serializeAddress( - deployedAddresses, - "userSafeImpl", - address(userSafeImpl) - ); vm.serializeAddress( deployedAddresses, "userSafeFactoryImpl", @@ -289,6 +285,16 @@ contract DeployUserSafeSetup is Utils { "userSafeFactoryProxy", address(userSafeFactory) ); + vm.serializeAddress( + deployedAddresses, + "userSafeCoreImpl", + address(userSafeCoreImpl) + ); + vm.serializeAddress( + deployedAddresses, + "userSafeSettersImpl", + address(userSafeSettersImpl) + ); vm.serializeAddress( deployedAddresses, "userSafeEventEmitterImpl", diff --git a/script/user-safe/UpgradeUserSafe.s.sol b/script/user-safe/UpgradeUserSafeCore.s.sol similarity index 80% rename from script/user-safe/UpgradeUserSafe.s.sol rename to script/user-safe/UpgradeUserSafeCore.s.sol index 2f10af0..5b4f7b7 100644 --- a/script/user-safe/UpgradeUserSafe.s.sol +++ b/script/user-safe/UpgradeUserSafeCore.s.sol @@ -3,14 +3,14 @@ pragma solidity ^0.8.24; import {Utils, ChainConfig} from "./Utils.sol"; import {UserSafeFactory} from "../../src/user-safe/UserSafeFactory.sol"; -import {UserSafe} from "../../src/user-safe/UserSafe.sol"; +import {UserSafeCore} from "../../src/user-safe/UserSafeCore.sol"; import {stdJson} from "forge-std/StdJson.sol"; -contract UpgradeUserSafe is Utils { +contract UpgradeUserSafeCore is Utils { using stdJson for string; UserSafeFactory userSafeFactory; - UserSafe userSafeImpl; + UserSafeCore userSafeCoreImpl; function run() public { // Pulling deployer info from the environment @@ -43,13 +43,9 @@ contract UpgradeUserSafe is Utils { ) ); - userSafeImpl = new UserSafe( - cashDataProvider, - recoverySigner1, - recoverySigner2 - ); + userSafeCoreImpl = new UserSafeCore(); - userSafeFactory.upgradeUserSafeImpl(address(userSafeImpl)); + userSafeFactory.upgradeUserSafeCoreImpl(address(userSafeCoreImpl)); vm.stopBroadcast(); } diff --git a/script/user-safe/UpgradeUserSafeSetters.s.sol b/script/user-safe/UpgradeUserSafeSetters.s.sol new file mode 100644 index 0000000..c4cea68 --- /dev/null +++ b/script/user-safe/UpgradeUserSafeSetters.s.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Utils, ChainConfig} from "./Utils.sol"; +import {UserSafeFactory} from "../../src/user-safe/UserSafeFactory.sol"; +import {UserSafeSetters} from "../../src/user-safe/UserSafeSetters.sol"; +import {stdJson} from "forge-std/StdJson.sol"; + +contract UpgradeUserSafeSetters is Utils { + using stdJson for string; + + UserSafeFactory userSafeFactory; + UserSafeSetters userSafeSettersImpl; + + function run() public { + // Pulling deployer info from the environment + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + // Start broadcast with deployer as the signer + vm.startBroadcast(deployerPrivateKey); + + string memory deployments = readDeploymentFile(); + + address cashDataProvider = stdJson.readAddress( + deployments, + string.concat(".", "addresses", ".", "cashDataProviderProxy") + ); + + address recoverySigner1 = stdJson.readAddress( + deployments, + string.concat(".", "addresses", ".", "recoverySigner1") + ); + + address recoverySigner2 = stdJson.readAddress( + deployments, + string.concat(".", "addresses", ".", "recoverySigner2") + ); + + userSafeFactory = UserSafeFactory( + stdJson.readAddress( + deployments, + string.concat(".", "addresses", ".", "userSafeFactory") + ) + ); + + userSafeSettersImpl = new UserSafeSetters(); + + userSafeFactory.setUserSafeSettersImpl(address(userSafeSettersImpl)); + + vm.stopBroadcast(); + } +} diff --git a/src/interfaces/IUserSafe.sol b/src/interfaces/IUserSafe.sol index 1f7b71a..44f3d6d 100644 --- a/src/interfaces/IUserSafe.sol +++ b/src/interfaces/IUserSafe.sol @@ -137,33 +137,6 @@ interface IUserSafe { bytes calldata signature ) external; - /** - * @notice Function to receive funds from the user. - * @param token Address of the token to receive. - * @param amount Amount of the token to receive. - */ - function receiveFunds(address token, uint256 amount) external; - - /** - * @notice Function to receive funds with permit from the user. - * @param owner Address of the owner of the token. - * @param token Address of the token to receive. - * @param amount Amount of the token to receive. - * @param deadline Must be a timestamp in the future. - * @param r Must be a valid r for the `secp256k1` signature from the user. - * @param s Must be a valid s for the `secp256k1` signature from the user. - * @param v Must be a valid v for the `secp256k1` signature from the user. - */ - function receiveFundsWithPermit( - address owner, - address token, - uint256 amount, - uint256 deadline, - bytes32 r, - bytes32 s, - uint8 v - ) external; - /** * @notice Function to request withdrawal of funds with permit from this safe. * @notice Can be withdrawn with a configurable delay. diff --git a/src/libraries/UserSafeLib.sol b/src/libraries/UserSafeLib.sol index 9dab209..2b902d3 100644 --- a/src/libraries/UserSafeLib.sol +++ b/src/libraries/UserSafeLib.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.24; import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SignatureUtils} from "./SignatureUtils.sol"; import {OwnerLib} from "./OwnerLib.sol"; -import {IUserSafe} from "../interfaces/IUserSafe.sol"; +import {UserSafeStorage} from "../user-safe/UserSafeStorage.sol"; library UserSafeLib { using SignatureUtils for bytes32; @@ -144,7 +144,7 @@ library UserSafeLib { function verifyRecoverSig( uint256 nonce, - IUserSafe.Signature[2] calldata signatures, + UserSafeStorage.Signature[2] calldata signatures, OwnerLib.OwnerObject[2] memory recoveryOwners, bytes calldata newOwner ) internal view { diff --git a/src/mocks/UserSafeV2Mock.sol b/src/mocks/UserSafeV2Mock.sol index 628b73c..ee7961b 100644 --- a/src/mocks/UserSafeV2Mock.sol +++ b/src/mocks/UserSafeV2Mock.sol @@ -1,21 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {UserSafe} from "../user-safe/UserSafe.sol"; - -contract UserSafeV2Mock is UserSafe { - constructor( - address __cashDataProvider, - address __etherFiRecoverySigner, - address __thirdPartyRecoverySigner - ) - UserSafe( - __cashDataProvider, - __etherFiRecoverySigner, - __thirdPartyRecoverySigner - ) - {} +import {UserSafeCore} from "../user-safe/UserSafeCore.sol"; +contract UserSafeV2Mock is UserSafeCore { function version() external pure returns (uint256) { return 2; } diff --git a/src/top-up/TopUpDest.sol b/src/top-up/TopUpDest.sol index 6203186..240009a 100644 --- a/src/top-up/TopUpDest.sol +++ b/src/top-up/TopUpDest.sol @@ -84,10 +84,10 @@ contract TopUpDest is Initializable, UUPSUpgradeable, EIP712Upgradeable, NoncesU } // TODO: Remove in Prod, this function is just for Testing purposes - function mapWalletToUserSafeMock( + function mapWalletToUserSafeAdmin( address wallet, address userSafe - ) external { + ) external onlyRole(DEFAULT_ADMIN_ROLE) { if (wallet == address(0)) revert WalletCannotBeAddressZero(); if (!cashDataProvider.isUserSafe(userSafe)) revert NotARegisteredUserSafe(); walletToUserSafeRegistry[wallet] = userSafe; diff --git a/src/user-safe/UserSafe.sol b/src/user-safe/UserSafeCore.sol similarity index 51% rename from src/user-safe/UserSafe.sol rename to src/user-safe/UserSafeCore.sol index 36c81a2..c819f8e 100644 --- a/src/user-safe/UserSafe.sol +++ b/src/user-safe/UserSafeCore.sol @@ -1,87 +1,42 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; +import {UserSafeStorage, OwnerLib, ArrayDeDupTransient, UserSafeEventEmitter, UserSafeLib, SpendingLimit, SpendingLimitLib} from "./UserSafeStorage.sol"; import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +import {IUserSafe} from "../interfaces/IUserSafe.sol"; import {ICashDataProvider} from "../interfaces/ICashDataProvider.sol"; -import {SignatureUtils} from "../libraries/SignatureUtils.sol"; +import {IL2DebtManager} from "../interfaces/IL2DebtManager.sol"; import {ISwapper} from "../interfaces/ISwapper.sol"; import {IPriceProvider} from "../interfaces/IPriceProvider.sol"; -import {IL2DebtManager} from "../interfaces/IL2DebtManager.sol"; -import {Initializable} from "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; -import {IUserSafe} from "../interfaces/IUserSafe.sol"; -import {UserSafeRecovery} from "./UserSafeRecovery.sol"; -import {WebAuthn} from "../libraries/WebAuthn.sol"; -import {OwnerLib} from "../libraries/OwnerLib.sol"; -import {UserSafeLib} from "../libraries/UserSafeLib.sol"; -import {ReentrancyGuardTransientUpgradeable} from "../utils/ReentrancyGuardTransientUpgradeable.sol"; -import {ArrayDeDupTransient} from "../libraries/ArrayDeDupTransientLib.sol"; -import {SpendingLimit, SpendingLimitLib} from "../libraries/SpendingLimitLib.sol"; -import {UserSafeEventEmitter} from "./UserSafeEventEmitter.sol"; - -/** - * @title UserSafe - * @author ether.fi [shivam@ether.fi] - * @notice User safe account for interactions with the EtherFi Cash contracts - */ -contract UserSafe is - IUserSafe, - Initializable, - ReentrancyGuardTransientUpgradeable, - UserSafeRecovery -{ - using SafeERC20 for IERC20; - using SignatureUtils for bytes32; +import {UserSafeFactory} from "./UserSafeFactory.sol"; + +contract UserSafeCore is UserSafeStorage { using OwnerLib for bytes; - using UserSafeLib for OwnerLib.OwnerObject; + using OwnerLib for address; using OwnerLib for OwnerLib.OwnerObject; - using ArrayDeDupTransient for address[]; + using UserSafeLib for OwnerLib.OwnerObject; using SpendingLimitLib for SpendingLimit; + using SafeERC20 for IERC20; + using ArrayDeDupTransient for address[]; - // Address of the Cash Data Provider - ICashDataProvider private immutable _cashDataProvider; - - // Owner: if ethAddr -> abi.encode(owner), if passkey -> abi.encode(x,y) - bytes private _ownerBytes; - // Owner: if ethAddr -> abi.encode(owner), if passkey -> abi.encode(x,y) - bytes private _incomingOwnerBytes; - // Time when the incoming owner becomes the owner - uint256 private _incomingOwnerStartTime; - - // Withdrawal requests pending with the contract - WithdrawalRequest private _pendingWithdrawalRequest; - // Nonce for permit operations - uint256 private _nonce; - // Current spending limit - SpendingLimit private _spendingLimit; - // Collateral limit - uint256 private _collateralLimit; - - // Incoming collateral limit -> we want a delay between collateral limit changes so we can deduct funds in between to settle account - uint256 private _incomingCollateralLimit; - // Incoming collateral limit start timestamp - uint256 private _incomingCollateralLimitStartTime; - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor( + function initialize( address __cashDataProvider, address __etherFiRecoverySigner, - address __thirdPartyRecoverySigner - ) UserSafeRecovery(__etherFiRecoverySigner, __thirdPartyRecoverySigner) { - _cashDataProvider = ICashDataProvider(__cashDataProvider); - _disableInitializers(); - } - - function initialize( + address __thirdPartyRecoverySigner, bytes calldata __owner, uint256 __dailySpendingLimit, uint256 __monthlySpendingLimit, uint256 __collateralLimit, int256 __timezoneOffset ) external initializer { + _cashDataProvider = ICashDataProvider(__cashDataProvider); + if (__etherFiRecoverySigner == __thirdPartyRecoverySigner) revert RecoverySignersCannotBeSame(); + _etherFiRecoverySigner = __etherFiRecoverySigner; + _thirdPartyRecoverySigner = __thirdPartyRecoverySigner; + __ReentrancyGuardTransient_init(); - __UserSafeRecovery_init(); + _isRecoveryActive = true; _ownerBytes = __owner; (, SpendingLimit memory newLimit) = _spendingLimit.initialize( __dailySpendingLimit, @@ -103,28 +58,10 @@ contract UserSafe is eventEmitter.emitOwnerSet(dummyOwner, __owner.getOwnerObject()); } - /** - * @inheritdoc IUserSafe - */ - function owner() public view returns (OwnerLib.OwnerObject memory) { - if ( - _incomingOwnerStartTime != 0 && - block.timestamp > _incomingOwnerStartTime - ) return _incomingOwnerBytes.getOwnerObject(); - - return _ownerBytes.getOwnerObject(); - } - - /** - * @inheritdoc IUserSafe - */ function cashDataProvider() external view returns (address) { return address(_cashDataProvider); } - /** - * @inheritdoc IUserSafe - */ function pendingWithdrawalRequest() public view @@ -133,16 +70,10 @@ contract UserSafe is return _pendingWithdrawalRequest; } - /** - * @inheritdoc IUserSafe - */ function nonce() external view returns (uint256) { return _nonce; } - /** - * @inheritdoc IUserSafe - */ function applicableSpendingLimit() external view @@ -151,9 +82,6 @@ contract UserSafe is return _spendingLimit.getCurrentLimit(); } - /** - * @inheritdoc IUserSafe - */ function applicableCollateralLimit() external view returns (uint256) { if ( _incomingCollateralLimitStartTime > 0 && @@ -163,9 +91,20 @@ contract UserSafe is return _collateralLimit; } - /** - * @inheritdoc IUserSafe - */ + function recoverySigners() + external + view + returns (OwnerLib.OwnerObject[3] memory signers) + { + signers[0] = _userRecoverySigner.getOwnerObject(); + signers[1] = _etherFiRecoverySigner.getOwnerObject(); + signers[2] = _thirdPartyRecoverySigner.getOwnerObject(); + } + + function isRecoveryActive() external view returns (bool) { + return _isRecoveryActive; + } + function canSpend( address token, uint256 amount @@ -189,125 +128,12 @@ contract UserSafe is } // If the token does not exist in withdrawal request - if (tokenIndex != len) - balance = balance - _pendingWithdrawalRequest.amounts[tokenIndex]; + if (tokenIndex != len) balance = balance - _pendingWithdrawalRequest.amounts[tokenIndex]; if (balance < amount) return (false, "Tokens pending withdrawal"); return _spendingLimit.canSpend(amount); } - /** - * @inheritdoc IUserSafe - */ - function setOwner( - bytes calldata __owner, - bytes calldata signature - ) external incrementNonce currentOwner { - // Since owner is setting a new owner, an incoming owner does not make sense - delete _incomingOwnerBytes; - delete _incomingOwnerStartTime; - - owner().verifySetOwnerSig(_nonce, __owner, signature); - - // Owner should not be zero - __owner.getOwnerObject()._ownerNotZero(); - _setOwner(__owner); - } - - /** - * @inheritdoc IUserSafe - */ - function updateSpendingLimit( - uint256 dailyLimitInUsd, - uint256 monthlyLimitInUsd, - bytes calldata signature - ) external incrementNonce currentOwner { - owner().verifyUpdateSpendingLimitSig( - _nonce, - dailyLimitInUsd, - monthlyLimitInUsd, - signature - ); - (SpendingLimit memory oldLimit, SpendingLimit memory newLimit) = _spendingLimit.updateSpendingLimit( - dailyLimitInUsd, - monthlyLimitInUsd, - _cashDataProvider.delay() - ); - - UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitSpendingLimitChanged(oldLimit, newLimit); - } - - /** - * @inheritdoc IUserSafe - */ - function setCollateralLimit( - uint256 limitInUsd, - bytes calldata signature - ) external incrementNonce currentOwner { - owner().verifySetCollateralLimitSig(_nonce, limitInUsd, signature); - _setCollateralLimit(limitInUsd); - } - - /** - * @inheritdoc IUserSafe - */ - function receiveFunds(address token, uint256 amount) external { - IERC20(token).safeTransferFrom(msg.sender, address(this), amount); - UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitDepositFunds(token, amount); - } - - /** - * @inheritdoc IUserSafe - */ - function receiveFundsWithPermit( - address fundsOwner, - address token, - uint256 amount, - uint256 deadline, - bytes32 r, - bytes32 s, - uint8 v - ) external { - try - IERC20Permit(token).permit( - fundsOwner, - address(this), - amount, - deadline, - v, - r, - s - ) - {} catch {} - - IERC20(token).safeTransferFrom(fundsOwner, address(this), amount); - UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitDepositFunds(token, amount); - } - - /** - * @inheritdoc IUserSafe - */ - function requestWithdrawal( - address[] calldata tokens, - uint256[] calldata amounts, - address recipient, - bytes calldata signature - ) external incrementNonce currentOwner { - if (tokens.length > 1) tokens.checkDuplicates(); - - owner().verifyRequestWithdrawalSig( - _nonce, - tokens, - amounts, - recipient, - signature - ); - _requestWithdrawal(tokens, amounts, recipient); - } - - /** - * @inheritdoc IUserSafe - */ function processWithdrawal() external nonReentrant { if (_pendingWithdrawalRequest.finalizeTime > block.timestamp) revert CannotWithdrawYet(); @@ -329,41 +155,6 @@ contract UserSafe is delete _pendingWithdrawalRequest; } - /** - * @inheritdoc IUserSafe - */ - function setIsRecoveryActive( - bool isActive, - bytes calldata signature - ) external incrementNonce currentOwner { - _setIsRecoveryActive(isActive, _nonce, signature); - UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitIsRecoveryActiveSet(isActive); - } - - /** - * @inheritdoc IUserSafe - */ - function setUserRecoverySigner( - address userRecoverySigner, - bytes calldata signature - ) external incrementNonce currentOwner { - address _oldSigner = _setUserRecoverySigner(userRecoverySigner, _nonce, signature); - UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitUserRecoverySignerSet(_oldSigner, userRecoverySigner); - } - - /** - * @inheritdoc IUserSafe - */ - function recoverUserSafe( - bytes calldata newOwner, - Signature[2] calldata signatures - ) external onlyWhenRecoveryActive incrementNonce currentOwner { - _recoverUserSafe(_nonce, signatures, newOwner); - } - - /** - * @inheritdoc IUserSafe - */ function transfer( address token, uint256 amount @@ -381,9 +172,6 @@ contract UserSafe is UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitTransferForSpending(token, amount); } - /** - * @inheritdoc IUserSafe - */ function swapAndTransfer( address inputTokenToSwap, address outputToken, @@ -433,9 +221,6 @@ contract UserSafe is ); } - /** - * @inheritdoc IUserSafe - */ function addCollateral( address token, uint256 amount @@ -444,9 +229,6 @@ contract UserSafe is _addCollateral(debtManager, token, amount); } - /** - * @inheritdoc IUserSafe - */ function addCollateralAndBorrow( address collateralToken, uint256 collateralAmount, @@ -458,25 +240,16 @@ contract UserSafe is _borrow(debtManager, borrowToken, borrowAmount); } - /** - * @inheritdoc IUserSafe - */ function borrow(address token, uint256 amount) external onlyEtherFiWallet { address debtManager = _cashDataProvider.etherFiCashDebtManager(); _borrow(debtManager, token, amount); } - /** - * @inheritdoc IUserSafe - */ function repay(address token, uint256 amount) external onlyEtherFiWallet { address debtManager = _cashDataProvider.etherFiCashDebtManager(); _repay(debtManager, token, amount); } - /** - * @inheritdoc IUserSafe - */ function withdrawCollateralFromDebtManager( address token, uint256 amount @@ -485,9 +258,6 @@ contract UserSafe is _withdrawCollateralFromDebtManager(debtManager, token, amount); } - /** - * @inheritdoc IUserSafe - */ function closeAccountWithDebtManager() external onlyEtherFiWallet { IL2DebtManager(_cashDataProvider.etherFiCashDebtManager()).closeAccount(); UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitCloseAccountWithDebtManager(); @@ -517,96 +287,9 @@ contract UserSafe is ); } - function _setCollateralLimit(uint256 limitInUsd) internal { - _currentCollateralLimit(); - UserSafeEventEmitter eventEmitter = UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()); - - if (limitInUsd > _collateralLimit) { - delete _incomingCollateralLimitStartTime; - delete _incomingCollateralLimit; - - eventEmitter.emitCollateralLimitSet(_collateralLimit, limitInUsd, block.timestamp); - _collateralLimit = limitInUsd; - } else { - _incomingCollateralLimitStartTime = block.timestamp + _cashDataProvider.delay(); - _incomingCollateralLimit = limitInUsd; - eventEmitter.emitCollateralLimitSet(_collateralLimit, limitInUsd, _incomingCollateralLimitStartTime); - } - } - - function _requestWithdrawal( - address[] calldata tokens, - uint256[] calldata amounts, - address recipient - ) internal { - _cancelOldWithdrawal(); - - uint256 len = tokens.length; - if (len != amounts.length) revert ArrayLengthMismatch(); - uint96 finalTime = uint96(block.timestamp) + _cashDataProvider.delay(); - for (uint256 i = 0; i < len; ) { - if (IERC20(tokens[i]).balanceOf(address(this)) < amounts[i]) - revert InsufficientBalance(); - - unchecked { - ++i; - } - } - - _pendingWithdrawalRequest = WithdrawalRequest({ - tokens: tokens, - amounts: amounts, - recipient: recipient, - finalizeTime: finalTime - }); - - UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitWithdrawalRequested(tokens, amounts, recipient, finalTime); - } - - function _cancelOldWithdrawal() internal { - if (_pendingWithdrawalRequest.tokens.length > 0) { - UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitWithdrawalCancelled( - _pendingWithdrawalRequest.tokens, - _pendingWithdrawalRequest.amounts, - _pendingWithdrawalRequest.recipient - ); - - delete _pendingWithdrawalRequest; - } - } - - function _setOwner(bytes calldata __owner) internal { - UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitOwnerSet(_ownerBytes.getOwnerObject(), __owner.getOwnerObject()); - _ownerBytes = __owner; - } - - function _setIncomingOwner(bytes calldata __owner) internal override { - _incomingOwnerStartTime = block.timestamp + _cashDataProvider.delay(); - OwnerLib.OwnerObject memory ownerObj = __owner.getOwnerObject(); - ownerObj._ownerNotZero(); - - _incomingOwnerBytes = __owner; - UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitIncomingOwnerSet(ownerObj, _incomingOwnerStartTime); - } - - function _getDecimals(address token) internal view returns (uint8) { - return IERC20Metadata(token).decimals(); - } - function _checkSpendingLimit(address token, uint256 amount) internal { uint8 tokenDecimals = _getDecimals(token); - - // in current case, token can be either collateral tokens or borrow tokens - if (_isCollateralToken(token)) { - uint256 price = IPriceProvider(_cashDataProvider.priceProvider()) - .price(token); - // token amount * price with 6 decimals / 10**decimals will convert the collateral token amount to USD amount with 6 decimals - amount = (amount * price) / 10 ** tokenDecimals; - } else { - if (tokenDecimals != 6) - amount = (amount * 1e6) / 10 ** tokenDecimals; - } - + if (tokenDecimals != 6) amount = (amount * 1e6) / 10 ** tokenDecimals; _spendingLimit.spend(amount); } @@ -707,38 +390,12 @@ contract UserSafe is } } - function _currentOwner() internal { - if ( - _incomingOwnerStartTime != 0 && - block.timestamp > _incomingOwnerStartTime - ) { - _ownerBytes = _incomingOwnerBytes; - delete _incomingOwnerBytes; - delete _incomingOwnerStartTime; - } - } - - function _currentCollateralLimit() internal { - if ( - _incomingCollateralLimitStartTime != 0 && - block.timestamp > _incomingCollateralLimitStartTime - ) { - _collateralLimit = _incomingCollateralLimit; - delete _incomingCollateralLimit; - delete _incomingCollateralLimitStartTime; - } - } - function _isCollateralToken(address token) internal view returns (bool) { - return - IL2DebtManager(_cashDataProvider.etherFiCashDebtManager()) - .isCollateralToken(token); + return IL2DebtManager(_cashDataProvider.etherFiCashDebtManager()).isCollateralToken(token); } function _isBorrowToken(address token) internal view returns (bool) { - return - IL2DebtManager(_cashDataProvider.etherFiCashDebtManager()) - .isBorrowToken(token); + return IL2DebtManager(_cashDataProvider.etherFiCashDebtManager()).isBorrowToken(token); } function _onlyEtherFiWallet() private view { @@ -751,13 +408,42 @@ contract UserSafe is _; } - modifier incrementNonce() { - _nonce++; - _; - } + /** + * @dev Falldown to the admin implementation + * @notice This is a catch all for all functions not declared in core + */ + // solhint-disable-next-line no-complex-fallback + fallback() external { + address transfersImpl = UserSafeFactory(_cashDataProvider.userSafeFactory()).userSafeSettersImpl(); + // solhint-disable-next-line no-inline-assembly + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall( + gas(), + transfersImpl, + 0, + calldatasize(), + 0, + 0 + ) - modifier currentOwner() { - _currentOwner(); - _; + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } } -} +} \ No newline at end of file diff --git a/src/user-safe/UserSafeFactory.sol b/src/user-safe/UserSafeFactory.sol index 3ee1430..f03dedd 100644 --- a/src/user-safe/UserSafeFactory.sol +++ b/src/user-safe/UserSafeFactory.sol @@ -7,6 +7,8 @@ import {CREATE3} from "solady/utils/CREATE3.sol"; import {ICashDataProvider} from "../interfaces/ICashDataProvider.sol"; import {UUPSUpgradeable, Initializable} from "openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol"; import {AccessControlDefaultAdminRulesUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; +import {UserSafeSetters} from "./UserSafeSetters.sol"; +import {UserSafeCore} from "./UserSafeCore.sol"; /** * @title UserSafeFactory @@ -17,9 +19,11 @@ contract UserSafeFactory is Initializable, UUPSUpgradeable, AccessControlDefault bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); address public cashDataProvider; address public beacon; + address public userSafeSettersImpl; event UserSafeDeployed(address indexed safe); event CashDataProviderSet(address oldProvider, address newProvider); + event UserSafeSettersImplSet(address oldUserSafeSettersImpl, address newUserSafeSettersImpl); event BeaconSet(address oldBeacon, address newBeacon); error InvalidValue(); @@ -31,14 +35,16 @@ contract UserSafeFactory is Initializable, UUPSUpgradeable, AccessControlDefault function initialize( uint48 _accessControlDelay, - address _implementation, address _owner, - address _cashDataProvider + address _cashDataProvider, + address _userSafeCoreImpl, + address _userSafeSettersImpl ) external initializer { __AccessControlDefaultAdminRules_init(_accessControlDelay, _owner); _grantRole(ADMIN_ROLE, _owner); - beacon = address(new UpgradeableBeacon(_implementation, address(this))); + beacon = address(new UpgradeableBeacon(_userSafeCoreImpl, address(this))); cashDataProvider = _cashDataProvider; + userSafeSettersImpl = _userSafeSettersImpl; } function setCashDataProvider(address _cashDataProvider) external onlyRole(ADMIN_ROLE) { @@ -47,6 +53,12 @@ contract UserSafeFactory is Initializable, UUPSUpgradeable, AccessControlDefault cashDataProvider = _cashDataProvider; } + function setUserSafeSettersImpl(address _userSafeSettersImpl) external onlyRole(ADMIN_ROLE) { + if (_userSafeSettersImpl == address(0)) revert InvalidValue(); + emit UserSafeSettersImplSet(userSafeSettersImpl, _userSafeSettersImpl); + userSafeSettersImpl = _userSafeSettersImpl; + } + function setBeacon(address _beacon) external onlyRole(ADMIN_ROLE) { if (_beacon == address(0)) revert InvalidValue(); emit BeaconSet(beacon, _beacon); @@ -82,7 +94,7 @@ contract UserSafeFactory is Initializable, UUPSUpgradeable, AccessControlDefault return safe; } - function upgradeUserSafeImpl(address newImplementation) external onlyRole(DEFAULT_ADMIN_ROLE) { + function upgradeUserSafeCoreImpl(address newImplementation) external onlyRole(DEFAULT_ADMIN_ROLE) { UpgradeableBeacon(beacon).upgradeTo(newImplementation); } diff --git a/src/user-safe/UserSafeRecovery.sol b/src/user-safe/UserSafeRecovery.sol deleted file mode 100644 index 300868c..0000000 --- a/src/user-safe/UserSafeRecovery.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ICashDataProvider} from "../interfaces/ICashDataProvider.sol"; -import {SignatureUtils} from "../libraries/SignatureUtils.sol"; -import {IUserSafe} from "../interfaces/IUserSafe.sol"; -import {OwnerLib} from "../libraries/OwnerLib.sol"; -import {UserSafeLib} from "../libraries/UserSafeLib.sol"; - -abstract contract UserSafeRecovery is IUserSafe { - using SafeERC20 for IERC20; - using SignatureUtils for bytes32; - using OwnerLib for bytes; - using OwnerLib for address; - using UserSafeLib for OwnerLib.OwnerObject; - - address private _userRecoverySigner; - // Address of the EtherFi Recovery Signer - address private immutable _etherFiRecoverySigner; - - // NOTE: Should we make it a state and then allow user's to reset this with their passkey signing? - // Address of the Third Party Recovery Signer - address private immutable _thirdPartyRecoverySigner; - // Boolean to toggle recovery mechanism on or off - bool private _isRecoveryActive; - - constructor( - address __etherFiRecoverySigner, - address __thirdPartyRecoverySigner - ) { - if (__etherFiRecoverySigner == __thirdPartyRecoverySigner) - revert RecoverySignersCannotBeSame(); - _etherFiRecoverySigner = __etherFiRecoverySigner; - _thirdPartyRecoverySigner = __thirdPartyRecoverySigner; - } - - function __UserSafeRecovery_init() internal { - _isRecoveryActive = true; - } - - /** - * @inheritdoc IUserSafe - */ - function recoverySigners() - external - view - returns (OwnerLib.OwnerObject[3] memory signers) - { - signers[0] = _userRecoverySigner.getOwnerObject(); - signers[1] = _etherFiRecoverySigner.getOwnerObject(); - signers[2] = _thirdPartyRecoverySigner.getOwnerObject(); - } - - /** - * @inheritdoc IUserSafe - */ - function isRecoveryActive() external view returns (bool) { - return _isRecoveryActive; - } - - function _setIsRecoveryActive(bool isActive) internal { - _isRecoveryActive = isActive; - } - - function _setIsRecoveryActive( - bool isActive, - uint256 _nonce, - bytes calldata signature - ) internal { - UserSafeLib.verifySetRecoverySig( - this.owner(), - _nonce, - isActive, - signature - ); - _setIsRecoveryActive(isActive); - } - - function _setUserRecoverySigner(address _recoverySigner) internal returns (address oldSigner) { - oldSigner = _userRecoverySigner; - if (_recoverySigner == address(0)) - revert InvalidRecoverySignerAddress(); - _userRecoverySigner = _recoverySigner; - } - - function _setUserRecoverySigner( - address _recoverySigner, - uint256 _nonce, - bytes calldata signature - ) internal returns (address) { - UserSafeLib.verifySetUserRecoverySigner( - this.owner(), - _nonce, - _recoverySigner, - signature - ); - return _setUserRecoverySigner(_recoverySigner); - } - - function _recoverUserSafe( - uint256 _nonce, - Signature[2] calldata signatures, - bytes calldata newOwner - ) internal { - if (signatures[0].index == signatures[1].index) - revert SignatureIndicesCannotBeSame(); - - OwnerLib.OwnerObject[2] memory recoveryOwners; - recoveryOwners[0] = _getRecoveryOwner(signatures[0].index); - recoveryOwners[1] = _getRecoveryOwner(signatures[1].index); - - UserSafeLib.verifyRecoverSig( - _nonce, - signatures, - recoveryOwners, - newOwner - ); - - _setIncomingOwner(newOwner); - } - - function _getRecoveryOwner( - uint8 index - ) internal view returns (OwnerLib.OwnerObject memory) { - if (index == 0) { - if (_userRecoverySigner == address(0)) - revert UserRecoverySignerIsUnsetCannotUseIndexZero(); - - return _userRecoverySigner.getOwnerObject(); - } else if (index == 1) return _etherFiRecoverySigner.getOwnerObject(); - else if (index == 2) return _thirdPartyRecoverySigner.getOwnerObject(); - else revert InvalidSignatureIndex(); - } - - function _onlyWhenRecoveryActive() private view { - if (!_isRecoveryActive) revert RecoveryNotActive(); - } - - function _setIncomingOwner(bytes calldata __owner) internal virtual; - - modifier onlyWhenRecoveryActive() { - _onlyWhenRecoveryActive(); - _; - } -} \ No newline at end of file diff --git a/src/user-safe/UserSafeSetters.sol b/src/user-safe/UserSafeSetters.sol new file mode 100644 index 0000000..fece772 --- /dev/null +++ b/src/user-safe/UserSafeSetters.sol @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {UserSafeStorage, OwnerLib, ArrayDeDupTransient, UserSafeEventEmitter, UserSafeLib, SpendingLimit, SpendingLimitLib} from "./UserSafeStorage.sol"; +import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IUserSafe} from "../interfaces/IUserSafe.sol"; +import {ICashDataProvider} from "../interfaces/ICashDataProvider.sol"; +import {UserSafeFactory} from "./UserSafeFactory.sol"; + +contract UserSafeSetters is UserSafeStorage { + using OwnerLib for bytes; + using OwnerLib for address; + using OwnerLib for OwnerLib.OwnerObject; + using UserSafeLib for OwnerLib.OwnerObject; + using SpendingLimitLib for SpendingLimit; + using SafeERC20 for IERC20; + using ArrayDeDupTransient for address[]; + + function setOwner( + bytes calldata __owner, + bytes calldata signature + ) external incrementNonce currentOwner { + // Since owner is setting a new owner, an incoming owner does not make sense + delete _incomingOwnerBytes; + delete _incomingOwnerStartTime; + + owner().verifySetOwnerSig(_nonce, __owner, signature); + + // Owner should not be zero + __owner.getOwnerObject()._ownerNotZero(); + _setOwner(__owner); + } + + function updateSpendingLimit( + uint256 dailyLimitInUsd, + uint256 monthlyLimitInUsd, + bytes calldata signature + ) external incrementNonce currentOwner { + owner().verifyUpdateSpendingLimitSig( + _nonce, + dailyLimitInUsd, + monthlyLimitInUsd, + signature + ); + (SpendingLimit memory oldLimit, SpendingLimit memory newLimit) = _spendingLimit.updateSpendingLimit( + dailyLimitInUsd, + monthlyLimitInUsd, + _cashDataProvider.delay() + ); + + UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitSpendingLimitChanged(oldLimit, newLimit); + } + + function setCollateralLimit( + uint256 limitInUsd, + bytes calldata signature + ) external incrementNonce currentOwner { + owner().verifySetCollateralLimitSig(_nonce, limitInUsd, signature); + _setCollateralLimit(limitInUsd); + } + + function requestWithdrawal( + address[] calldata tokens, + uint256[] calldata amounts, + address recipient, + bytes calldata signature + ) external incrementNonce currentOwner { + if (tokens.length > 1) tokens.checkDuplicates(); + + owner().verifyRequestWithdrawalSig( + _nonce, + tokens, + amounts, + recipient, + signature + ); + _requestWithdrawal(tokens, amounts, recipient); + } + + function setIsRecoveryActive( + bool isActive, + bytes calldata signature + ) external incrementNonce currentOwner { + _setIsRecoveryActive(isActive, signature); + UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitIsRecoveryActiveSet(isActive); + } + + function setUserRecoverySigner( + address userRecoverySigner, + bytes calldata signature + ) external incrementNonce currentOwner { + address _oldSigner = _setUserRecoverySigner(userRecoverySigner, signature); + UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitUserRecoverySignerSet(_oldSigner, userRecoverySigner); + } + + function recoverUserSafe( + bytes calldata newOwner, + Signature[2] calldata signatures + ) external onlyWhenRecoveryActive incrementNonce currentOwner { + _recoverUserSafe(signatures, newOwner); + } + + function _setCollateralLimit(uint256 limitInUsd) internal { + _currentCollateralLimit(); + UserSafeEventEmitter eventEmitter = UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()); + + if (limitInUsd > _collateralLimit) { + delete _incomingCollateralLimitStartTime; + delete _incomingCollateralLimit; + + eventEmitter.emitCollateralLimitSet(_collateralLimit, limitInUsd, block.timestamp); + _collateralLimit = limitInUsd; + } else { + _incomingCollateralLimitStartTime = block.timestamp + _cashDataProvider.delay(); + _incomingCollateralLimit = limitInUsd; + eventEmitter.emitCollateralLimitSet(_collateralLimit, limitInUsd, _incomingCollateralLimitStartTime); + } + } + + function _requestWithdrawal( + address[] calldata tokens, + uint256[] calldata amounts, + address recipient + ) internal { + _cancelOldWithdrawal(); + + uint256 len = tokens.length; + if (len != amounts.length) revert ArrayLengthMismatch(); + uint96 finalTime = uint96(block.timestamp) + _cashDataProvider.delay(); + for (uint256 i = 0; i < len; ) { + if (IERC20(tokens[i]).balanceOf(address(this)) < amounts[i]) + revert InsufficientBalance(); + + unchecked { + ++i; + } + } + + _pendingWithdrawalRequest = WithdrawalRequest({ + tokens: tokens, + amounts: amounts, + recipient: recipient, + finalizeTime: finalTime + }); + + UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitWithdrawalRequested(tokens, amounts, recipient, finalTime); + } + + function _cancelOldWithdrawal() internal { + if (_pendingWithdrawalRequest.tokens.length > 0) { + UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitWithdrawalCancelled( + _pendingWithdrawalRequest.tokens, + _pendingWithdrawalRequest.amounts, + _pendingWithdrawalRequest.recipient + ); + + delete _pendingWithdrawalRequest; + } + } + + function _setOwner(bytes calldata __owner) internal { + UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitOwnerSet(_ownerBytes.getOwnerObject(), __owner.getOwnerObject()); + _ownerBytes = __owner; + } + + function _setIsRecoveryActive(bool isActive) internal { + _isRecoveryActive = isActive; + } + + function _setIsRecoveryActive( + bool isActive, + bytes calldata signature + ) internal { + UserSafeLib.verifySetRecoverySig( + this.owner(), + _nonce, + isActive, + signature + ); + _setIsRecoveryActive(isActive); + } + + function _setUserRecoverySigner(address _recoverySigner) internal returns (address oldSigner) { + oldSigner = _userRecoverySigner; + if (_recoverySigner == address(0)) + revert InvalidRecoverySignerAddress(); + _userRecoverySigner = _recoverySigner; + } + + function _setUserRecoverySigner( + address _recoverySigner, + bytes calldata signature + ) internal returns (address) { + UserSafeLib.verifySetUserRecoverySigner( + this.owner(), + _nonce, + _recoverySigner, + signature + ); + return _setUserRecoverySigner(_recoverySigner); + } + + function _recoverUserSafe( + Signature[2] calldata signatures, + bytes calldata newOwner + ) internal { + if (signatures[0].index == signatures[1].index) + revert SignatureIndicesCannotBeSame(); + + OwnerLib.OwnerObject[2] memory recoveryOwners; + recoveryOwners[0] = _getRecoveryOwner(signatures[0].index); + recoveryOwners[1] = _getRecoveryOwner(signatures[1].index); + + UserSafeLib.verifyRecoverSig( + _nonce, + signatures, + recoveryOwners, + newOwner + ); + + _setIncomingOwner(newOwner); + } + + function _getRecoveryOwner( + uint8 index + ) internal view returns (OwnerLib.OwnerObject memory) { + if (index == 0) { + if (_userRecoverySigner == address(0)) + revert UserRecoverySignerIsUnsetCannotUseIndexZero(); + + return _userRecoverySigner.getOwnerObject(); + } else if (index == 1) return _etherFiRecoverySigner.getOwnerObject(); + else if (index == 2) return _thirdPartyRecoverySigner.getOwnerObject(); + else revert InvalidSignatureIndex(); + } + + function _setIncomingOwner(bytes calldata __owner) internal { + _incomingOwnerStartTime = block.timestamp + _cashDataProvider.delay(); + OwnerLib.OwnerObject memory ownerObj = __owner.getOwnerObject(); + ownerObj._ownerNotZero(); + + _incomingOwnerBytes = __owner; + UserSafeEventEmitter(_cashDataProvider.userSafeEventEmitter()).emitIncomingOwnerSet(ownerObj, _incomingOwnerStartTime); + } + + function _currentOwner() internal { + if ( + _incomingOwnerStartTime != 0 && + block.timestamp > _incomingOwnerStartTime + ) { + _ownerBytes = _incomingOwnerBytes; + delete _incomingOwnerBytes; + delete _incomingOwnerStartTime; + } + } + + function _onlyWhenRecoveryActive() private view { + if (!_isRecoveryActive) revert RecoveryNotActive(); + } + + modifier incrementNonce() { + _nonce++; + _; + } + + modifier currentOwner() { + _currentOwner(); + _; + } + + modifier onlyWhenRecoveryActive() { + _onlyWhenRecoveryActive(); + _; + } +} \ No newline at end of file diff --git a/src/user-safe/UserSafeStorage.sol b/src/user-safe/UserSafeStorage.sol new file mode 100644 index 0000000..56775aa --- /dev/null +++ b/src/user-safe/UserSafeStorage.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +import {ICashDataProvider} from "../interfaces/ICashDataProvider.sol"; +import {SignatureUtils} from "../libraries/SignatureUtils.sol"; +import {ISwapper} from "../interfaces/ISwapper.sol"; +import {IPriceProvider} from "../interfaces/IPriceProvider.sol"; +import {IL2DebtManager} from "../interfaces/IL2DebtManager.sol"; +import {Initializable} from "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; +import {IUserSafe} from "../interfaces/IUserSafe.sol"; +import {WebAuthn} from "../libraries/WebAuthn.sol"; +import {OwnerLib} from "../libraries/OwnerLib.sol"; +import {UserSafeLib} from "../libraries/UserSafeLib.sol"; +import {ReentrancyGuardTransientUpgradeable} from "../utils/ReentrancyGuardTransientUpgradeable.sol"; +import {ArrayDeDupTransient} from "../libraries/ArrayDeDupTransientLib.sol"; +import {SpendingLimit, SpendingLimitLib} from "../libraries/SpendingLimitLib.sol"; +import {UserSafeEventEmitter} from "./UserSafeEventEmitter.sol"; + +contract UserSafeStorage is Initializable, ReentrancyGuardTransientUpgradeable { + using OwnerLib for bytes; + + struct Signature { + uint8 index; + bytes signature; + } + + struct FundsDetails { + address token; + uint256 amount; + } + + struct WithdrawalRequest { + address[] tokens; + uint256[] amounts; + address recipient; + uint96 finalizeTime; + } + + error InsufficientBalance(); + error ArrayLengthMismatch(); + error CannotWithdrawYet(); + error UnauthorizedCall(); + error InvalidNonce(); + error TransferAmountGreaterThanReceived(); + error ExceededCollateralLimit(); + error UnsupportedToken(); + error RecoveryNotActive(); + error InvalidSignatureIndex(); + error SignatureIndicesCannotBeSame(); + error AmountCannotBeZero(); + error RecoverySignersCannotBeSame(); + error InvalidRecoverySignerAddress(); + error UserRecoverySignerIsUnsetCannotUseIndexZero(); + error IncorrectOutputAmount(); + error AmountZeroWithSixDecimals(); + error OnlyUserSafeFactory(); + + // Address of the Cash Data Provider + ICashDataProvider internal _cashDataProvider; + // Address of the EtherFi Recovery Signer + address internal _etherFiRecoverySigner; + // Address of the Third Party Recovery Signer + address internal _thirdPartyRecoverySigner; + // Address of the recovery signer set by the user + address internal _userRecoverySigner; + + // Owner: if ethAddr -> abi.encode(owner), if passkey -> abi.encode(x,y) + bytes internal _ownerBytes; + // Owner: if ethAddr -> abi.encode(owner), if passkey -> abi.encode(x,y) + bytes internal _incomingOwnerBytes; + // Time when the incoming owner becomes the owner + uint256 internal _incomingOwnerStartTime; + + // Withdrawal requests pending with the contract + WithdrawalRequest internal _pendingWithdrawalRequest; + // Nonce for permit operations + uint256 internal _nonce; + // Current spending limit + SpendingLimit internal _spendingLimit; + // Collateral limit + uint256 internal _collateralLimit; + + // Incoming collateral limit -> we want a delay between collateral limit changes so we can deduct funds in between to settle account + uint256 internal _incomingCollateralLimit; + // Incoming collateral limit start timestamp + uint256 internal _incomingCollateralLimitStartTime; + // Boolean to toggle recovery mechanism on or off + bool internal _isRecoveryActive; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + + function owner() public view returns (OwnerLib.OwnerObject memory) { + if ( + _incomingOwnerStartTime != 0 && + block.timestamp > _incomingOwnerStartTime + ) return _incomingOwnerBytes.getOwnerObject(); + + return _ownerBytes.getOwnerObject(); + } + + function _getDecimals(address token) internal view returns (uint8) { + return IERC20Metadata(token).decimals(); + } + + function _currentCollateralLimit() internal { + if ( + _incomingCollateralLimitStartTime != 0 && + block.timestamp > _incomingCollateralLimitStartTime + ) { + _collateralLimit = _incomingCollateralLimit; + delete _incomingCollateralLimit; + delete _incomingCollateralLimitStartTime; + } + } +} \ No newline at end of file diff --git a/test/IntegrationTest/IntegrationTest.t.sol b/test/IntegrationTest/IntegrationTest.t.sol index f3abe77..e9498a3 100644 --- a/test/IntegrationTest/IntegrationTest.t.sol +++ b/test/IntegrationTest/IntegrationTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {IUserSafe, OwnerLib, UserSafe} from "../../src/user-safe/UserSafe.sol"; +import {IUserSafe, OwnerLib} from "../../src/user-safe/UserSafeCore.sol"; import {IntegrationTestSetup, PriceProvider, MockPriceProvider, MockERC20} from "./IntegrationTestSetup.t.sol"; import {IL2DebtManager} from "../../src/interfaces/IL2DebtManager.sol"; import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; diff --git a/test/IntegrationTest/IntegrationTestSetup.t.sol b/test/IntegrationTest/IntegrationTestSetup.t.sol index f4f86e2..50fc0b8 100644 --- a/test/IntegrationTest/IntegrationTestSetup.t.sol +++ b/test/IntegrationTest/IntegrationTestSetup.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.24; import {Test, console, stdError} from "forge-std/Test.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {UserSafeFactory} from "../../src/user-safe/UserSafeFactory.sol"; -import {UserSafe, UserSafeEventEmitter, SpendingLimit} from "../../src//user-safe/UserSafe.sol"; import {IL2DebtManager} from "../../src/interfaces/IL2DebtManager.sol"; import {DebtManagerCore} from "../../src/debt-manager/DebtManagerCore.sol"; import {DebtManagerAdmin} from "../../src/debt-manager/DebtManagerAdmin.sol"; @@ -26,6 +25,9 @@ import {UUPSProxy} from "../../src/UUPSProxy.sol"; import {MockCashTokenWrapperFactory} from "../../src/mocks/MockCashTokenWrapperFactory.sol"; import {MockCashWrappedERC20} from "../../src/mocks/MockCashWrappedERC20.sol"; import {IAggregatorV3} from "../../src/interfaces/IAggregatorV3.sol"; +import {UserSafeCore, SpendingLimit, UserSafeEventEmitter} from "../../src/user-safe/UserSafeCore.sol"; +import {UserSafeSetters} from "../../src/user-safe/UserSafeSetters.sol"; +import {IUserSafe} from "../../src/interfaces/IUserSafe.sol"; contract IntegrationTestSetup is Utils { using OwnerLib for address; @@ -41,8 +43,9 @@ contract IntegrationTestSetup is Utils { uint256 thirdPartyRecoverySignerPk; address thirdPartyRecoverySigner; + UserSafeCore userSafeCoreImpl; + UserSafeSetters userSafeSettersImpl; UserSafeFactory factory; - UserSafe impl; UserSafeEventEmitter eventEmitter; ERC20 usdc; @@ -67,7 +70,7 @@ contract IntegrationTestSetup is Utils { address alice; uint256 alicePk; bytes aliceBytes; - UserSafe aliceSafe; + IUserSafe aliceSafe; IEtherFiCashAaveV3Adapter aaveV3Adapter; @@ -210,11 +213,8 @@ contract IntegrationTestSetup is Utils { "thirdPartyRecoverySigner" ); - impl = new UserSafe( - address(cashDataProvider), - etherFiRecoverySigner, - thirdPartyRecoverySigner - ); + userSafeCoreImpl = new UserSafeCore(); + userSafeSettersImpl = new UserSafeSetters(); address factoryImpl = address(new UserSafeFactory()); @@ -224,9 +224,10 @@ contract IntegrationTestSetup is Utils { abi.encodeWithSelector( UserSafeFactory.initialize.selector, uint48(delay), - address(impl), owner, - address(cashDataProvider) + address(cashDataProvider), + address(userSafeCoreImpl), + address(userSafeSettersImpl) )) ) ); @@ -278,11 +279,14 @@ contract IntegrationTestSetup is Utils { bytes memory saltData = bytes("aliceSafe"); - aliceSafe = UserSafe( + aliceSafe = IUserSafe( factory.createUserSafe( saltData, abi.encodeWithSelector( - UserSafe.initialize.selector, + UserSafeCore.initialize.selector, + address(cashDataProvider), + etherFiRecoverySigner, + thirdPartyRecoverySigner, aliceBytes, defaultDailySpendingLimit, defaultMonthlySpendingLimit, diff --git a/test/UserSafe/CanSpend.t.sol b/test/UserSafe/CanSpend.t.sol index dd8f9a4..b4d38e4 100644 --- a/test/UserSafe/CanSpend.t.sol +++ b/test/UserSafe/CanSpend.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {UserSafe, UserSafeLib, SpendingLimit, SpendingLimitLib} from "../../src/user-safe/UserSafe.sol"; +import {UserSafeLib, SpendingLimit, SpendingLimitLib} from "../../src/user-safe/UserSafeCore.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UserSafeSetup} from "./UserSafeSetup.t.sol"; diff --git a/test/UserSafe/CollateralLimit.t.sol b/test/UserSafe/CollateralLimit.t.sol index 4a52f33..ca0a35f 100644 --- a/test/UserSafe/CollateralLimit.t.sol +++ b/test/UserSafe/CollateralLimit.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {UserSafeEventEmitter, IUserSafe, OwnerLib, UserSafe, UserSafeLib} from "../../src/user-safe/UserSafe.sol"; +import {UserSafeEventEmitter, IUserSafe, OwnerLib, UserSafeLib} from "../../src/user-safe/UserSafeCore.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UserSafeSetup} from "./UserSafeSetup.t.sol"; diff --git a/test/UserSafe/Deploy.t.sol b/test/UserSafe/Deploy.t.sol index 153c218..ce57239 100644 --- a/test/UserSafe/Deploy.t.sol +++ b/test/UserSafe/Deploy.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {IUserSafe, OwnerLib, UserSafe, SpendingLimit} from "../../src/user-safe/UserSafe.sol"; +import {IUserSafe, OwnerLib, SpendingLimit, UserSafeCore} from "../../src/user-safe/UserSafeCore.sol"; import {UserSafeSetup} from "./UserSafeSetup.t.sol"; import {CREATE3} from "solady/utils/CREATE3.sol"; @@ -31,7 +31,10 @@ contract UserSafeDeployTest is UserSafeSetup { bytes memory salt = abi.encode("safe", block.timestamp); bytes memory initData = abi.encodeWithSelector( - UserSafe.initialize.selector, + UserSafeCore.initialize.selector, + address(cashDataProvider), + etherFiRecoverySigner, + thirdPartyRecoverySigner, bobBytes, defaultDailySpendingLimit, defaultMonthlySpendingLimit, @@ -50,7 +53,10 @@ contract UserSafeDeployTest is UserSafeSetup { bytes memory salt = abi.encode("safe", block.timestamp); bytes memory initData = abi.encodeWithSelector( - UserSafe.initialize.selector, + UserSafeCore.initialize.selector, + address(cashDataProvider), + etherFiRecoverySigner, + thirdPartyRecoverySigner, bobBytes, defaultDailySpendingLimit, defaultMonthlySpendingLimit, diff --git a/test/UserSafe/Owner.t.sol b/test/UserSafe/Owner.t.sol index d87f523..778ec64 100644 --- a/test/UserSafe/Owner.t.sol +++ b/test/UserSafe/Owner.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {IUserSafe, UserSafeEventEmitter, OwnerLib, UserSafe, UserSafeLib} from "../../src/user-safe/UserSafe.sol"; +import {IUserSafe, UserSafeEventEmitter, OwnerLib, UserSafeLib} from "../../src/user-safe/UserSafeCore.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UserSafeSetup} from "./UserSafeSetup.t.sol"; diff --git a/test/UserSafe/ReceiveFunds.t.sol b/test/UserSafe/ReceiveFunds.t.sol deleted file mode 100644 index d3c2b54..0000000 --- a/test/UserSafe/ReceiveFunds.t.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {IUserSafe, UserSafeEventEmitter, UserSafe} from "../../src/user-safe/UserSafe.sol"; -import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import {UserSafeSetup} from "./UserSafeSetup.t.sol"; - -// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); -bytes32 constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; - -struct Permit { - address owner; - address spender; - uint256 value; - uint256 nonce; - uint256 deadline; -} - -contract UserSafeReceiveFundsTest is UserSafeSetup { - using MessageHashUtils for bytes32; - - function test_ReceiveFunds() public { - uint256 amount = 1000e6; - vm.startPrank(alice); - usdc.approve(address(aliceSafe), amount); - vm.expectEmit(true, true, true, true); - emit UserSafeEventEmitter.DepositFunds(address(aliceSafe), address(usdc), amount); - aliceSafe.receiveFunds(address(usdc), amount); - vm.stopPrank(); - } - - function test_ReceiveFundsWithPermit() public { - if (keccak256(bytes(chainId)) == keccak256(bytes("42161"))) { - uint256 amount = 10 ether; - uint256 deadline = type(uint256).max; - bytes32 DOMAIN_SEPARATOR_WEETH = 0x2dcc2f01a01098023cfce9f6b30f72af3d7809ae69a1ea8b5ac6f012e91b3248; - - Permit memory permit = Permit({ - owner: alice, - spender: address(aliceSafe), - value: amount, - nonce: 0, - deadline: deadline - }); - - bytes32 digest = getTypedDataHash(DOMAIN_SEPARATOR_WEETH, permit); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, digest); - - vm.prank(notOwner); - vm.expectEmit(true, true, true, true); - emit UserSafeEventEmitter.DepositFunds(address(aliceSafe), address(weETH), amount); - aliceSafe.receiveFundsWithPermit( - alice, - address(weETH), - amount, - deadline, - r, - s, - v - ); - } - } - - // computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer - function getTypedDataHash( - bytes32 DOMAIN_SEPARATOR, - Permit memory _permit - ) internal pure returns (bytes32) { - return - keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - getStructHash(_permit) - ) - ); - } - - // computes the hash of a permit - function getStructHash( - Permit memory _permit - ) internal pure returns (bytes32) { - return - keccak256( - abi.encode( - PERMIT_TYPEHASH, - _permit.owner, - _permit.spender, - _permit.value, - _permit.nonce, - _permit.deadline - ) - ); - } -} diff --git a/test/UserSafe/Recovery.t.sol b/test/UserSafe/Recovery.t.sol index dc55150..3363279 100644 --- a/test/UserSafe/Recovery.t.sol +++ b/test/UserSafe/Recovery.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {IUserSafe, UserSafeEventEmitter, OwnerLib, UserSafe, UserSafeLib} from "../../src/user-safe/UserSafe.sol"; +import {IUserSafe, UserSafeEventEmitter, OwnerLib, UserSafeLib} from "../../src/user-safe/UserSafeCore.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {EIP1271SignatureUtils} from "../../src/libraries/EIP1271SignatureUtils.sol"; import {ERC20, UserSafeSetup} from "./UserSafeSetup.t.sol"; diff --git a/test/UserSafe/SpendingLimit.t.sol b/test/UserSafe/SpendingLimit.t.sol index 7d05fb4..714de1d 100644 --- a/test/UserSafe/SpendingLimit.t.sol +++ b/test/UserSafe/SpendingLimit.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {IUserSafe, UserSafeEventEmitter, OwnerLib, UserSafe, UserSafeLib, SpendingLimit, SpendingLimitLib} from "../../src/user-safe/UserSafe.sol"; +import {IUserSafe, UserSafeEventEmitter, OwnerLib, UserSafeLib, SpendingLimit, SpendingLimitLib} from "../../src/user-safe/UserSafeCore.sol"; import {TimeLib} from "../../src/libraries/TimeLib.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UserSafeSetup} from "./UserSafeSetup.t.sol"; diff --git a/test/UserSafe/Transfers.t.sol b/test/UserSafe/Transfers.t.sol index 52d93dd..3ecb6cf 100644 --- a/test/UserSafe/Transfers.t.sol +++ b/test/UserSafe/Transfers.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {IUserSafe, UserSafeEventEmitter, OwnerLib, UserSafe, UserSafeLib, SpendingLimitLib} from "../../src/user-safe/UserSafe.sol"; +import {IUserSafe, UserSafeEventEmitter, OwnerLib, UserSafeLib, SpendingLimitLib} from "../../src/user-safe/UserSafeCore.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {ERC20, UserSafeSetup} from "./UserSafeSetup.t.sol"; diff --git a/test/UserSafe/UserSafeFactory.t.sol b/test/UserSafe/UserSafeFactory.t.sol index 17005e7..fe12547 100644 --- a/test/UserSafe/UserSafeFactory.t.sol +++ b/test/UserSafe/UserSafeFactory.t.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.24; import {Test, console, stdError} from "forge-std/Test.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {UserSafeFactory} from "../../src/user-safe/UserSafeFactory.sol"; -import {UserSafe, OwnerLib} from "../../src/user-safe/UserSafe.sol"; import {UserSafeV2Mock} from "../../src/mocks/UserSafeV2Mock.sol"; import {Swapper1InchV6} from "../../src/utils/Swapper1InchV6.sol"; import {PriceProvider} from "../../src/oracle/PriceProvider.sol"; import {CashDataProvider} from "../../src/utils/CashDataProvider.sol"; -import {UserSafeSetup} from "./UserSafeSetup.t.sol"; +import {UserSafeSetup, IUserSafe, UserSafeCore} from "./UserSafeSetup.t.sol"; +import {OwnerLib} from "../../src/libraries/OwnerLib.sol"; error OwnableUnauthorizedAccount(address account); @@ -27,25 +27,24 @@ contract UserSafeFactoryTest is UserSafeSetup { address bob = makeAddr("bob"); bytes bobBytes = abi.encode(bob); - UserSafe bobSafe; + IUserSafe bobSafe; bytes saltData = bytes("bobSafe"); function setUp() public override { super.setUp(); - implV2 = new UserSafeV2Mock( - address(cashDataProvider), - etherFiRecoverySigner, - thirdPartyRecoverySigner - ); + implV2 = new UserSafeV2Mock(); vm.prank(owner); - bobSafe = UserSafe( + bobSafe = IUserSafe( factory.createUserSafe( saltData, abi.encodeWithSelector( - UserSafe.initialize.selector, + UserSafeCore.initialize.selector, + address(cashDataProvider), + etherFiRecoverySigner, + thirdPartyRecoverySigner, bobBytes, defaultDailySpendingLimit, defaultMonthlySpendingLimit, @@ -62,7 +61,10 @@ contract UserSafeFactoryTest is UserSafeSetup { address deterministicAddress = factory.getUserSafeAddress( saltData, abi.encodeWithSelector( - UserSafe.initialize.selector, + UserSafeCore.initialize.selector, + address(cashDataProvider), + etherFiRecoverySigner, + thirdPartyRecoverySigner, bobBytes, defaultDailySpendingLimit, defaultMonthlySpendingLimit, @@ -77,7 +79,7 @@ contract UserSafeFactoryTest is UserSafeSetup { function test_UpgradeUserSafeImpl() public { vm.prank(owner); - factory.upgradeUserSafeImpl(address(implV2)); + factory.upgradeUserSafeCoreImpl(address(implV2)); UserSafeV2Mock aliceSafeV2 = UserSafeV2Mock(address(aliceSafe)); UserSafeV2Mock bobSafeV2 = UserSafeV2Mock(address(bobSafe)); @@ -98,7 +100,7 @@ contract UserSafeFactoryTest is UserSafeSetup { function test_OnlyOwnerCanUpgradeUserSafeImpl() public { vm.prank(notOwner); vm.expectRevert(buildAccessControlRevertData(notOwner, DEFAULT_ADMIN_ROLE)); - factory.upgradeUserSafeImpl(address(implV2)); + factory.upgradeUserSafeCoreImpl(address(implV2)); } function test_OnlyOwnerCanUpgradeFactoryImpl() public { @@ -113,7 +115,10 @@ contract UserSafeFactoryTest is UserSafeSetup { factory.createUserSafe( saltData, abi.encodeWithSelector( - UserSafe.initialize.selector, + UserSafeCore.initialize.selector, + address(cashDataProvider), + etherFiRecoverySigner, + thirdPartyRecoverySigner, hex"112345", defaultDailySpendingLimit, defaultMonthlySpendingLimit, diff --git a/test/UserSafe/UserSafeSetup.t.sol b/test/UserSafe/UserSafeSetup.t.sol index 4c89c62..b468884 100644 --- a/test/UserSafe/UserSafeSetup.t.sol +++ b/test/UserSafe/UserSafeSetup.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.24; import {Test, console, stdError} from "forge-std/Test.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {UserSafeFactory} from "../../src/user-safe/UserSafeFactory.sol"; -import {UserSafe, UserSafeEventEmitter, SpendingLimit} from "../../src//user-safe/UserSafe.sol"; import {IL2DebtManager} from "../../src/interfaces/IL2DebtManager.sol"; import {DebtManagerCore} from "../../src/debt-manager/DebtManagerCore.sol"; import {DebtManagerAdmin} from "../../src/debt-manager/DebtManagerAdmin.sol"; @@ -27,6 +26,9 @@ import {UUPSProxy} from "../../src/UUPSProxy.sol"; import {MockCashTokenWrapperFactory} from "../../src/mocks/MockCashTokenWrapperFactory.sol"; import {MockCashWrappedERC20} from "../../src/mocks/MockCashWrappedERC20.sol"; import {IAggregatorV3} from "../../src/interfaces/IAggregatorV3.sol"; +import {UserSafeCore, UserSafeEventEmitter, SpendingLimit} from "../../src/user-safe/UserSafeCore.sol"; +import {UserSafeSetters} from "../../src/user-safe/UserSafeSetters.sol"; +import {IUserSafe} from "../../src/interfaces/IUserSafe.sol"; contract UserSafeSetup is Utils { using OwnerLib for address; @@ -41,8 +43,9 @@ contract UserSafeSetup is Utils { uint256 thirdPartyRecoverySignerPk; address thirdPartyRecoverySigner; + UserSafeCore userSafeCoreImpl; + UserSafeSetters userSafeSettersImpl; UserSafeFactory factory; - UserSafe impl; UserSafeEventEmitter eventEmitter; ERC20 usdc; @@ -68,7 +71,7 @@ contract UserSafeSetup is Utils { address alice; uint256 alicePk; bytes aliceBytes; - UserSafe aliceSafe; + IUserSafe aliceSafe; IEtherFiCashAaveV3Adapter aaveV3Adapter; @@ -209,12 +212,8 @@ contract UserSafeSetup is Utils { "thirdPartyRecoverySigner" ); - impl = new UserSafe( - address(cashDataProvider), - etherFiRecoverySigner, - thirdPartyRecoverySigner - ); - + userSafeCoreImpl = new UserSafeCore(); + userSafeSettersImpl = new UserSafeSetters(); address factoryImpl = address(new UserSafeFactory()); factory = UserSafeFactory( @@ -223,9 +222,10 @@ contract UserSafeSetup is Utils { abi.encodeWithSelector( UserSafeFactory.initialize.selector, uint48(delay), - address(impl), owner, - address(cashDataProvider) + address(cashDataProvider), + address(userSafeCoreImpl), + address(userSafeSettersImpl) )) ) ); @@ -279,11 +279,14 @@ contract UserSafeSetup is Utils { bytes memory saltData = bytes("aliceSafe"); - aliceSafe = UserSafe( + aliceSafe = IUserSafe( factory.createUserSafe( saltData, abi.encodeWithSelector( - UserSafe.initialize.selector, + UserSafeCore.initialize.selector, + address(cashDataProvider), + etherFiRecoverySigner, + thirdPartyRecoverySigner, aliceBytes, defaultDailySpendingLimit, defaultMonthlySpendingLimit, diff --git a/test/UserSafe/WebAuthn.t.sol b/test/UserSafe/WebAuthn.t.sol index f409276..ca8ba07 100644 --- a/test/UserSafe/WebAuthn.t.sol +++ b/test/UserSafe/WebAuthn.t.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {IUserSafe, OwnerLib, WebAuthn, UserSafe, UserSafeLib} from "../../src/user-safe/UserSafe.sol"; +import {IUserSafe, OwnerLib, UserSafeLib, UserSafeCore} from "../../src/user-safe/UserSafeCore.sol"; +import {WebAuthn} from "../../src/libraries/WebAuthn.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UserSafeSetup} from "./UserSafeSetup.t.sol"; import {WebAuthnInfo, WebAuthnUtils} from "../WebAuthnUtils.sol"; @@ -14,7 +15,7 @@ contract UserSafeWebAuthnSignatureTest is UserSafeSetup { bytes passkeyOwner = hex"1c05286fe694493eae33312f2d2e0d0abeda8db76238b7a204be1fb87f54ce4228fef61ef4ac300f631657635c28e59bfb2fe71bce1634c81c65642042f6dc4d"; - UserSafe passkeyOwnerSafe; + IUserSafe passkeyOwnerSafe; function setUp() public override { super.setUp(); @@ -22,11 +23,14 @@ contract UserSafeWebAuthnSignatureTest is UserSafeSetup { bytes memory saltData = bytes("passkeySafe"); vm.prank(owner); - passkeyOwnerSafe = UserSafe( + passkeyOwnerSafe = IUserSafe( factory.createUserSafe( saltData, abi.encodeWithSelector( - UserSafe.initialize.selector, + UserSafeCore.initialize.selector, + address(cashDataProvider), + etherFiRecoverySigner, + thirdPartyRecoverySigner, passkeyOwner, defaultDailySpendingLimit, defaultMonthlySpendingLimit, diff --git a/test/UserSafe/Withdrawal.t.sol b/test/UserSafe/Withdrawal.t.sol index a7d1fc8..be85f07 100644 --- a/test/UserSafe/Withdrawal.t.sol +++ b/test/UserSafe/Withdrawal.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {IUserSafe, UserSafeEventEmitter, OwnerLib, UserSafe, UserSafeLib} from "../../src/user-safe/UserSafe.sol"; +import {IUserSafe, UserSafeEventEmitter, OwnerLib, UserSafeLib} from "../../src/user-safe/UserSafeCore.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import {UserSafeSetup} from "./UserSafeSetup.t.sol"; @@ -44,7 +44,7 @@ contract UserSafeWithdrawalTest is UserSafeSetup { msgHash.toEthSignedMessageHash() ); - UserSafe.WithdrawalRequest + IUserSafe.WithdrawalRequest memory pendingWithdrawalRequestBefore = aliceSafe .pendingWithdrawalRequest(); assertEq(pendingWithdrawalRequestBefore.tokens.length, 0); @@ -62,7 +62,7 @@ contract UserSafeWithdrawalTest is UserSafeSetup { aliceSafe.requestWithdrawal(tokens, amounts, recipient, signature); - UserSafe.WithdrawalRequest + IUserSafe.WithdrawalRequest memory pendingWithdrawalRequestAfter = aliceSafe .pendingWithdrawalRequest(); @@ -207,7 +207,7 @@ contract UserSafeWithdrawalTest is UserSafeSetup { signature ); - UserSafe.WithdrawalRequest memory newWithdrawalRequest = aliceSafe + IUserSafe.WithdrawalRequest memory newWithdrawalRequest = aliceSafe .pendingWithdrawalRequest(); assertEq(newWithdrawalRequest.tokens.length, 1); assertEq(newWithdrawalRequest.tokens[0], newTokens[0]);