diff --git a/src/governance/treasury/ITreasury.sol b/src/governance/treasury/ITreasury.sol index 84e84a9..4772de4 100644 --- a/src/governance/treasury/ITreasury.sol +++ b/src/governance/treasury/ITreasury.sol @@ -27,6 +27,11 @@ interface ITreasury is IUUPS, IOwnable { /// @notice Emitted when the grace period is updated event GracePeriodUpdated(uint256 prevGracePeriod, uint256 newGracePeriod); + /// @notice Event emitted when ETH is withdrawn from the Treasury + /// @param to The address that received the ETH + /// @param amount The amount of ETH withdrawn + event RecoverFunds(address indexed to, uint256 amount); + /// /// /// ERRORS /// /// /// @@ -114,4 +119,9 @@ interface ITreasury is IUUPS, IOwnable { /// @notice Updates the grace period /// @param newGracePeriod The grace period function updateGracePeriod(uint256 newGracePeriod) external; + + /// @notice Allows the owner to withdraw ETH from the Treasury + /// @param to The address to send the withdrawn ETH to + /// @param amount The amount of ETH to withdraw + function recoverFunds(address payable to, uint256 amount) external; } diff --git a/src/governance/treasury/Treasury.sol b/src/governance/treasury/Treasury.sol index efdba99..d47af77 100644 --- a/src/governance/treasury/Treasury.sol +++ b/src/governance/treasury/Treasury.sol @@ -15,7 +15,7 @@ import { VersionedContract } from "../../VersionedContract.sol"; /// @title Treasury /// @author Rohan Kulkarni /// @notice A DAO's treasury and transaction executor -/// @custom:repo github.com/ourzora/nouns-protocol +/// @custom:repo github.com/ourzora/nouns-protocol /// Modified from: /// - OpenZeppelin Contracts v4.7.3 (governance/TimelockController.sol) /// - NounsDAOExecutor.sol commit 2cbe6c7 - licensed under the BSD-3-Clause license. @@ -276,4 +276,17 @@ contract Treasury is ITreasury, VersionedContract, UUPS, Ownable, ProposalHasher // Ensure the new implementation is a registered upgrade if (!manager.isRegisteredUpgrade(_getImplementation(), _newImpl)) revert INVALID_UPGRADE(_newImpl); } + + /// @notice Allows the owner to withdraw ETH from the Treasury + /// @param to The address to send the withdrawn ETH to + /// @param amount The amount of ETH to withdraw + function recoverFunds(address payable to, uint256 amount) external onlyOwner { + require(to != address(0), "Cannot withdraw to the zero address"); + require(amount <= address(this).balance, "Insufficient balance"); + + (bool success, ) = to.call{ value: amount }(""); + require(success, "Transfer failed"); + + emit RecoverFunds(to, amount); + } } diff --git a/src/manager/IManager.sol b/src/manager/IManager.sol index 173bed2..81d9ca0 100644 --- a/src/manager/IManager.sol +++ b/src/manager/IManager.sol @@ -170,4 +170,17 @@ interface IManager is IUUPS, IOwnable { /// @param baseImpl The base implementation address /// @param upgradeImpl The upgrade implementation address function removeUpgrade(address baseImpl, address upgradeImpl) external; + + /// @notice Deploys and initializes a Treasury contract using CREATE2 with the specified bytecode, salt, and initialization parameters. + /// @param bytecode The bytecode of the Treasury contract to deploy. + /// @param salt The salt used to create the deterministic contract address. + /// @param governor The address to set as the governor of the Treasury. + /// @param timelockDelay The timelock delay to be set in the Treasury. + /// @return treasury The address of the deployed Treasury contract. + function deployAndInitializeTreasuryUsingCreate2( + bytes memory bytecode, + bytes32 salt, + address governor, + uint256 timelockDelay + ) external returns (address treasury); } diff --git a/src/manager/Manager.sol b/src/manager/Manager.sol index 5a1f243..78f6d69 100644 --- a/src/manager/Manager.sol +++ b/src/manager/Manager.sol @@ -293,4 +293,39 @@ contract Manager is IManager, VersionedContract, UUPS, Ownable, ManagerStorageV1 /// @dev This function is called in `upgradeTo` & `upgradeToAndCall` /// @param _newImpl The new implementation address function _authorizeUpgrade(address _newImpl) internal override onlyOwner {} + + + + /// @notice Deploy a Treasury contract using CREATE2 at a predetermined address and initialize it + /// @param salt The salt used for CREATE2 deployment + /// @param bytecode The bytecode of the Treasury contract + /// @param governor The EOA to be set as the governor of the Treasury + /// @param timelockDelay The timelock delay to be set in the Treasury + function deployAndInitializeTreasuryUsingCreate2( + bytes32 salt, + bytes memory bytecode, + address governor, + uint256 timelockDelay + ) external onlyOwner returns (address treasury) { + // Compute the address where the contract will be deployed + address predictedAddress = address(uint160(uint256(keccak256(abi.encodePacked( + bytes1(0xff), + address(this), + salt, + keccak256(bytecode) + ))))); + + // Deploy the Treasury contract using CREATE2 + assembly { + treasury := create2(0, add(bytecode, 0x20), mload(bytecode), salt) + if iszero(extcodesize(treasury)) { + revert(0, 0) + } + } + + require(treasury == predictedAddress, "Unexpected deployed address"); + + // Initialize the Treasury + ITreasury(treasury).initialize(governor, timelockDelay); + } }