diff --git a/.gitmodules b/.gitmodules index 094a6999..ecfb3aad 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "lib/solidity-bytes-utils"] path = lib/solidity-bytes-utils url = https://github.com/GNSPS/solidity-bytes-utils +[submodule "lib/weiroll"] + path = lib/weiroll + url = https://github.com/weiroll/weiroll \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index c350f4b0..03789d48 100644 --- a/foundry.toml +++ b/foundry.toml @@ -13,7 +13,8 @@ remappings = [ 'openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/', 'openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/', 'solmate/=lib/solmate/src/', - 'safe-smart-account/=lib/safe-smart-account/', + 'safe-smart-account/=lib/safe-smart-account/contracts/', + 'weiroll/=lib/weiroll/src/', 'solady/=lib/solady/src/', 'bitlib/=lib/solidity-bytes-utils/contracts/', 'ERC-7540/=lib/ERC-7540-Reference/src/' diff --git a/lib/v3-core b/lib/v3-core deleted file mode 160000 index 2c51d119..00000000 --- a/lib/v3-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2c51d1192d45e4e74efbca6283e6947db7b3cf22 diff --git a/lib/v3-periphery b/lib/v3-periphery deleted file mode 160000 index 80f26c86..00000000 --- a/lib/v3-periphery +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 80f26c86c57b8a5e4b913f42844d4c8bd274d058 diff --git a/src/peripheral/gnosis/controllerModule/DrawdownModule.sol b/src/peripheral/gnosis/controllerModule/DrawdownModule.sol index 7d4edf09..252eb599 100644 --- a/src/peripheral/gnosis/controllerModule/DrawdownModule.sol +++ b/src/peripheral/gnosis/controllerModule/DrawdownModule.sol @@ -1,12 +1,16 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.25; -import {ControllerModule, ModuleCall} from "src/peripheral/gnosis/controllerModule/MainControllerModule.sol"; +import {ControllerModule, ModuleCall, ISafe, Operation} from "src/peripheral/gnosis/controllerModule/MainControllerModule.sol"; import {MultisigVault} from "src/vaults/multisig/phase1/MultisigVault.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {Owned} from "src/utils/Owned.sol"; +import {OwnerManager} from "safe-smart-account/base/OwnerManager.sol"; +import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; contract DrawdownModule is Owned { + using FixedPointMathLib for uint256; + MultisigVault public vault; ControllerModule public controller; ModuleCall[] public tokenBalanceCalls; @@ -39,7 +43,7 @@ contract DrawdownModule is Owned { if (abi.decode(data, (uint256)) > 0) revert("Leftover token"); } - address asset = vault.asset(); + address asset = address(vault.asset()); uint256 assetBalance = ERC20(asset).balanceOf(controller.gnosisSafe()); uint256 totalAssets = vault.totalAssets(); // TODO add a drawdown parameter @@ -66,25 +70,25 @@ contract DrawdownModule is Owned { ERC20(asset).transfer(address(vault), assetBalance - bounty); // Put DAO in control of the safe - _takeoverSafe(); + _takeoverSafe(newOwners, newThreshold); } } function _takeoverSafe( - address[] memory newOwners, - uint256 newThreshold + address[] memory newOwners_, + uint256 newThreshold_ ) internal { - address _gnosisSafe = gnosisSafe; - ISafe safe = ISafe(_gnosisSafe); + address gnosisSafe = controller.gnosisSafe(); + ISafe safe = ISafe(gnosisSafe); address[] memory owners = safe.getOwners(); // remove owners for (uint256 i = (owners.length - 1); i > 0; --i) { bool success = safe.execTransactionFromModule({ - to: _gnosisSafe, + to: gnosisSafe, value: 0, data: abi.encodeCall( - IOwnerManager.removeOwner, + OwnerManager.removeOwner, (owners[i - 1], owners[i], 1) ), operation: Operation.Call @@ -94,16 +98,16 @@ contract DrawdownModule is Owned { } } - for (uint256 i = 0; i < newOwners.length; i++) { + for (uint256 i = 0; i < newOwners_.length; i++) { bool success; if (i == 0) { - if (newOwners[i] == owners[i]) continue; + if (newOwners_[i] == owners[i]) continue; success = safe.execTransactionFromModule({ - to: _gnosisSafe, + to: gnosisSafe, value: 0, data: abi.encodeCall( - IOwnerManager.swapOwner, - (SENTINEL_OWNERS, owners[i], newOwners[i]) + OwnerManager.swapOwner, + (address(0x1), owners[i], newOwners_[i]) ), operation: Operation.Call }); @@ -113,11 +117,11 @@ contract DrawdownModule is Owned { continue; } success = safe.execTransactionFromModule({ - to: _gnosisSafe, + to: gnosisSafe, value: 0, data: abi.encodeCall( - IOwnerManager.addOwnerWithThreshold, - (newOwners[i], 1) + OwnerManager.addOwnerWithThreshold, + (newOwners_[i], 1) ), operation: Operation.Call }); @@ -126,13 +130,13 @@ contract DrawdownModule is Owned { } } - if (newThreshold > 1) { + if (newThreshold_ > 1) { bool success = safe.execTransactionFromModule({ - to: _gnosisSafe, + to: gnosisSafe, value: 0, data: abi.encodeCall( - IOwnerManager.changeThreshold, - (newThreshold) + OwnerManager.changeThreshold, + (newThreshold_) ), operation: Operation.Call }); @@ -153,7 +157,7 @@ contract DrawdownModule is Owned { revert("Invalid call operation"); // We want to get the balance of all tokens in the safe that are not the vault asset - if (calls[i].to == vault.asset()) revert("Invalid call"); + if (calls[i].to == address(vault.asset())) revert("Invalid call"); delete tokenBalanceCalls; diff --git a/src/peripheral/gnosis/controllerModule/WithdrawalModule.sol b/src/peripheral/gnosis/controllerModule/WithdrawalModule.sol index 76535de0..cdca94c9 100644 --- a/src/peripheral/gnosis/controllerModule/WithdrawalModule.sol +++ b/src/peripheral/gnosis/controllerModule/WithdrawalModule.sol @@ -10,7 +10,7 @@ interface IAsyncVault { function redeemRequests( address recipient, address multisig - ) external view returns (RedeemRequest); + ) external view returns (RedeemRequest memory); } contract WithdrawalModule { diff --git a/src/peripheral/gnosis/transactionGuard/LoggerGuard.sol b/src/peripheral/gnosis/transactionGuard/LoggerGuard.sol index 7acabc6b..ba38caa2 100644 --- a/src/peripheral/gnosis/transactionGuard/LoggerGuard.sol +++ b/src/peripheral/gnosis/transactionGuard/LoggerGuard.sol @@ -1,4 +1,4 @@ -import {DebugTransactionGuard, Enum} from "safe-smart-account/contracts/examples/guards/DebugTransactionGuard.sol"; +import {DebugTransactionGuard, Enum} from "safe-smart-account/examples/guards/DebugTransactionGuard.sol"; contract LoggerGuard is DebugTransactionGuard { event ModuleTransactionDetails( diff --git a/src/peripheral/gnosis/transactionGuard/MainTransactionGuard.sol b/src/peripheral/gnosis/transactionGuard/MainTransactionGuard.sol index a09e6668..349a2a97 100644 --- a/src/peripheral/gnosis/transactionGuard/MainTransactionGuard.sol +++ b/src/peripheral/gnosis/transactionGuard/MainTransactionGuard.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.12 <0.9.0; -import {BaseGuard, Guard, Enum} from "safe-smart-account/contracts/base/GuardManager.sol"; +import {BaseGuard, Guard, Enum} from "safe-smart-account/base/GuardManager.sol"; +import {IModuleGuard} from "safe-smart-account/base/ModuleManager.sol"; import {Owned} from "src/utils/Owned.sol"; contract MainTransactionGuard is BaseGuard, Owned { diff --git a/src/peripheral/gnosis/transactionGuard/ScopeGuard.sol b/src/peripheral/gnosis/transactionGuard/ScopeGuard.sol index b9356ee6..42dbb78b 100644 --- a/src/peripheral/gnosis/transactionGuard/ScopeGuard.sol +++ b/src/peripheral/gnosis/transactionGuard/ScopeGuard.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.12 <0.9.0; -import {BaseGuard, Guard, Enum} from "safe-smart-account/contracts/base/GuardManager.sol"; +import {BaseGuard, Guard, Enum} from "safe-smart-account/base/GuardManager.sol"; import {Owned} from "src/utils/Owned.sol"; contract ScopeGuard is BaseGuard, Owned { @@ -181,7 +181,7 @@ contract ScopeGuard is BaseGuard, Owned { bytes memory data, Enum.Operation operation, address - ) external view override { + ) external view { _checkTransaction(to, value, data, operation); } @@ -221,5 +221,5 @@ contract ScopeGuard is BaseGuard, Owned { function checkAfterExecution(bytes32, bool) external view override {} - function checkAfterModuleExecution(bytes32, bool) external view override {} + function checkAfterModuleExecution(bytes32, bool) external view {} } diff --git a/src/vaults/multisig/phase1/MultisigVault.sol b/src/vaults/multisig/phase1/MultisigVault.sol index b160ca96..6d011c09 100644 --- a/src/vaults/multisig/phase1/MultisigVault.sol +++ b/src/vaults/multisig/phase1/MultisigVault.sol @@ -7,9 +7,30 @@ import {BaseControlledAsyncRedeem} from "./BaseControlledAsyncRedeem.sol"; import {BaseERC7540} from "./BaseERC7540.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; -import {IPriceOracle} from "src/interfaces/IPriceOracle.sol"; -contract MultisigVault is BaseControlledAsyncRedeem { +struct InitializeParams { + address asset; + address multisig; + address owner; + Limits limits; + Fees fees; +} + +struct Limits { + uint256 depositLimit; + uint256 minAmount; +} + +struct Fees { + uint64 performanceFee; + uint64 managementFee; + uint64 withdrawalIncentive; + uint64 feesUpdatedAt; + uint256 highWaterMark; + address feeRecipient; +} + +abstract contract MultisigVault is BaseControlledAsyncRedeem { using FixedPointMathLib for uint256; address public multisig; @@ -17,14 +38,6 @@ contract MultisigVault is BaseControlledAsyncRedeem { error ZeroAmount(); error Misconfigured(); - struct InitializeParams { - address asset; - address multisig; - address owner; - Limits limits; - Fees fees; - } - constructor( InitializeParams memory params ) BaseERC7540(params.owner, params.asset, "Multisig Vault", "mVault") { @@ -39,21 +52,6 @@ contract MultisigVault is BaseControlledAsyncRedeem { ACCOUNTING LOGIC //////////////////////////////////////////////////////////////*/ - uint256 internal totalAssets_; - uint256 public lastUpdateTime; - - /// @return Total amount of underlying `asset` token managed by vault. Delegates to adapter. - function totalAssets() public view override returns (uint256) { - return totalAssets_ + accruedYield(); - } - - function accruedYield() public view returns (uint256) { - return - (rate * (block.timestamp - lastUpdateTime) * totalAssets_) / - 365.25 days / - 1e18; - } - // Override to add minAmount check (Which is used in mint and will revert the function) function previewDeposit( uint256 assets @@ -73,12 +71,14 @@ contract MultisigVault is BaseControlledAsyncRedeem { return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply); } - function convertToLowBoundAssets(uint256 shares) public view returns (uint256) { + function convertToLowBoundAssets( + uint256 shares + ) public view returns (uint256) { uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. uint256 assets = totalAssets().mulDivDown(bounds.lower, 1e18); return supply == 0 ? shares : shares.mulDivDown(assets, supply); - } + } /*////////////////////////////////////////////////////////////// DEPOSIT/WITHDRAWAL LIMIT LOGIC @@ -155,7 +155,7 @@ contract MultisigVault is BaseControlledAsyncRedeem { function fulfillMultipleRedeems( uint256[] memory shares, address[] memory controllers - ) external override returns (uint256) { + ) external returns (uint256) { if (shares.length != controllers.length) revert Misconfigured(); uint256 withdrawalIncentive = uint256(fees.withdrawalIncentive); @@ -177,17 +177,13 @@ contract MultisigVault is BaseControlledAsyncRedeem { ERC-4626 OVERRIDES //////////////////////////////////////////////////////////////*/ - function beforeWithdraw(uint256 assets, uint256) internal override { + function beforeWithdraw(uint256 assets, uint256) internal virtual override { _takeFees(); - - totalAssets_ -= assets; } - function afterDeposit(uint256 assets, uint256) internal override { + function afterDeposit(uint256 assets, uint256) internal virtual override { _takeFees(); - totalAssets_ += assets; - SafeTransferLib.safeTransfer(asset, multisig, assets); } @@ -201,39 +197,15 @@ contract MultisigVault is BaseControlledAsyncRedeem { } Bounds public bounds; - uint256 public rate; - - function setRate(uint256 rate_) external onlyOwner { - rate = rate_; - } function setBounds(Bounds memory bounds_) external onlyOwner { bounds = bounds_; } - modifier updateTotalAssets() { - uint256 yieldEarned = accruedYield(); - - if (yieldEarned > 0) { - totalAssets_ = _totalAssets + yieldEarned; - lastUpdateTime = block.timestamp; - } - _; - } - /*////////////////////////////////////////////////////////////// FEE LOGIC //////////////////////////////////////////////////////////////*/ - struct Fees { - uint64 performanceFee; - uint64 managementFee; - uint64 withdrawalIncentive; - uint64 feesUpdatedAt; - uint256 highWaterMark; - address feeRecipient; - } - Fees public fees; event FeesUpdated(Fees prev, Fees next); @@ -309,7 +281,7 @@ contract MultisigVault is BaseControlledAsyncRedeem { if (fees_.feeRecipient == address(0)) revert Misconfigured(); // Dont rely on user input here - fees_.feesUpdatedAt = block.timestamp; + fees_.feesUpdatedAt = uint64(block.timestamp); fees_.highWaterMark = convertToAssets(1e18); emit FeesUpdated(fees, fees_); @@ -337,11 +309,6 @@ contract MultisigVault is BaseControlledAsyncRedeem { LIMIT LOGIC //////////////////////////////////////////////////////////////*/ - struct Limits { - uint256 depositLimit; - uint256 minAmount; - } - Limits public limits; event LimitsUpdated(Limits prev, Limits next); diff --git a/src/vaults/multisig/phase1/OracleVault.sol b/src/vaults/multisig/phase1/OracleVault.sol new file mode 100644 index 00000000..823a1554 --- /dev/null +++ b/src/vaults/multisig/phase1/OracleVault.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.25 + +pragma solidity ^0.8.25; + +import {MultisigVault, InitializeParams} from "./MultisigVault.sol"; +import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; +import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; +import {IPriceOracle} from "src/interfaces/IPriceOracle.sol"; + +contract OracleVault is MultisigVault { + constructor( + InitializeParams memory params, + address oracle + ) MultisigVault(params) { + oracle = IPriceOracle(oracle); + } + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + IPriceOracle public oracle; + + /// @return Total amount of underlying `asset` token managed by vault. Delegates to adapter. + function totalAssets() public view override returns (uint256) { + return IPriceOracle(oracle).getQuote(totalSupply(), share, asset); + } +} diff --git a/src/vaults/multisig/phase1/RateVault.sol b/src/vaults/multisig/phase1/RateVault.sol new file mode 100644 index 00000000..a08a5ff0 --- /dev/null +++ b/src/vaults/multisig/phase1/RateVault.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0 +// Docgen-SOLC: 0.8.25 + +pragma solidity ^0.8.25; + +import {MultisigVault, InitializeParams} from "./MultisigVault.sol"; +import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; +import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; + +contract RateVault is MultisigVault { + constructor(InitializeParams memory params) MultisigVault(params) {} + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + uint256 internal totalAssets_; + uint256 public lastUpdateTime; + + /// @return Total amount of underlying `asset` token managed by vault. Delegates to adapter. + function totalAssets() public view override returns (uint256) { + return totalAssets_ + accruedYield(); + } + + function accruedYield() public view returns (uint256) { + return + (rate * (block.timestamp - lastUpdateTime) * totalAssets_) / + 365.25 days / + 1e18; + } + + /*////////////////////////////////////////////////////////////// + YIELD RATE LOGIC + //////////////////////////////////////////////////////////////*/ + + uint256 public rate; + + function setRate(uint256 rate_) external onlyOwner { + rate = rate_; + } + + modifier updateTotalAssets() { + uint256 yieldEarned = accruedYield(); + + if (yieldEarned > 0) { + totalAssets_ = totalAssets_ + yieldEarned; + lastUpdateTime = block.timestamp; + } + _; + } + + /*////////////////////////////////////////////////////////////// + ERC-4626 OVERRIDES + //////////////////////////////////////////////////////////////*/ + + function beforeWithdraw(uint256 assets, uint256) internal override { + _takeFees(); + + totalAssets_ -= assets; + } + + function afterDeposit(uint256 assets, uint256) internal override { + _takeFees(); + + totalAssets_ += assets; + + SafeTransferLib.safeTransfer(asset, multisig, assets); + } +}