From 45f6aa1d757c7d3f72f7bf9be5f6320908eb9e90 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 3 Dec 2025 22:32:37 +0530 Subject: [PATCH 01/25] WIP: ERC4626Facet --- .vscode/settings.json | 7 +- src/token/ERC20/ERC4626/ERC4626Facet.sol | 232 +++++++++++++++++++++++ 2 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 src/token/ERC20/ERC4626/ERC4626Facet.sol diff --git a/.vscode/settings.json b/.vscode/settings.json index e71fb1d0..f0a9b5a4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,12 @@ // Enable GitHub Copilot "github.copilot.enable": { - "*": true + "*": false, + "plaintext": false, + "markdown": false, + "scminput": false, + "rust": false, + "solidity": false }, "github.copilot.inlineSuggest.enable": true, "editor.inlineSuggest.enabled": true, diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol new file mode 100644 index 00000000..1a1fe490 --- /dev/null +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/** + * @dev Implementation of the ERC-4626 + */ +contract ERC4626Facet { + event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); + event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); + event Approval(address indexed owner, address indexed spender, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); + + error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); + error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); + error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); + error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); + error ERC4626ZeroBalance(); + + bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); + bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); + + struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; + mapping(address owner => mapping(address spender => uint256 allowance)) allowances; + uint8 decimals; + string name; + string symbol; + } + + struct ERC4626Storage { + IERC20 asset; + } + + function getStorage() internal pure returns (ERC4626Storage storage s) { + bytes32 position = STORAGE_POSITION; + assembly { + s.slot := position + } + } + + function getERC20Storage() internal pure returns (ERC20Storage storage s) { + bytes32 position = ERC20_STORAGE_POSITION; + assembly { + s.slot := position + } + } + + function asset() public view returns (address) { + return address(getStorage().asset); + } + + function totalAssets() public view returns (uint256) { + return getStorage().asset.balanceOf(address(this)); + } + + function decimals() public view returns (uint8) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.decimals; + } + + function balanceOf(address account) public view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.balanceOf[account]; + } + + function totalSupply() public view returns (uint256) { + ERC20Storage storage s = getERC20Storage(); + return s.totalSupply; + } + + function convertToShares(uint256 assets) public view returns (uint256) { + uint256 supply = totalSupply(); + if (supply == 0) { + revert ERC4626ZeroBalance(); + } + return assets * supply / totalAssets(); + } + + function convertToAssets(uint256 shares) public view returns (uint256) { + uint256 supply = totalSupply(); + if (supply == 0) { + return shares; + } + return shares * totalAssets() / supply; + } + + + function previewDeposit(uint256 assets) public view returns (uint256) { + return convertToShares(assets); + } + + function previewMint(uint256 shares) public view returns (uint256) { + uint256 supply = totalSupply(); + uint256 assets = totalAssets(); + if (supply == 0) { + return shares; + } + return (shares * assets + supply - 1) / supply; + } + + function previewWithdraw(uint256 assets) public view returns (uint256) { + uint256 supply = totalSupply(); + uint256 total = totalAssets(); + if (supply == 0) { + return assets; + } + return (assets * supply + total - 1) / total; + } + + function previewRedeem(uint256 shares) public view returns (uint256) { + return convertToAssets(shares); + } + + function maxDeposit(address) public view returns (uint256) { + return type(uint256).max; + } + + function maxMint(address) public view returns (uint256) { + return type(uint256).max; + } + + function maxWithdraw(address owner) public view returns (uint256) { + return previewRedeem(maxRedeem(owner)); + } + + function maxRedeem(address owner) public view returns (uint256) { + return balanceOf(owner); + } + + + function deposit(uint256 assets, address receiver) public returns (uint256) { + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } + uint256 shares = previewDeposit(assets); + require(shares != 0, "ZERO_SHARES"); + + _deposit(_msgSender(), receiver, assets, shares); + return shares; + } + + function mint(uint256 shares, address receiver) public returns (uint256) { + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } + uint256 assets = previewMint(shares); + require(assets != 0, "ZERO_ASSETS"); + + _deposit(_msgSender(), receiver, assets, shares); + return assets; + } + + // ========== Withdraw/Redeem ========== + + function withdraw(uint256 assets, address receiver, address owner) public returns (uint256) { + uint256 maxAssets = maxWithdraw(owner); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } + uint256 shares = previewWithdraw(assets); + require(shares != 0, "ZERO_SHARES"); + + _withdraw(_msgSender(), receiver, owner, assets, shares); + return shares; + } + + function redeem(uint256 shares, address receiver, address owner) public returns (uint256) { + uint256 maxShares = maxRedeem(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } + uint256 assets = previewRedeem(shares); + require(assets != 0, "ZERO_ASSETS"); + + _withdraw(_msgSender(), receiver, owner, assets, shares); + return assets; + } + + + // ========== Internal Deposit ========== + + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal { + require(receiver != address(0), "RECEIVER_ZERO"); + ERC20Storage storage erc20s = getERC20Storage(); + getStorage().asset.transferFrom(caller, address(this), assets); + + erc20s.totalSupply += shares; + erc20s.balanceOf[receiver] += shares; + + emit Transfer(address(0), receiver, shares); + emit Deposit(caller, receiver, assets, shares); + } + + // ========== Internal Withdraw ========== + + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal { + ERC20Storage storage erc20s = getERC20Storage(); + + if (caller != owner) { + uint256 allowed = erc20s.allowances[owner][caller]; + require(allowed >= shares, "INSUFFICIENT_ALLOWANCE"); + if (allowed != type(uint256).max) { + erc20s.allowances[owner][caller] = allowed - shares; + } + } + require(receiver != address(0), "RECEIVER_ZERO"); + + erc20s.balanceOf[owner] -= shares; + erc20s.totalSupply -= shares; + + emit Transfer(owner, address(0), shares); + + getStorage().asset.transfer(receiver, assets); + + emit Withdraw(caller, receiver, owner, assets, shares); + } + + // ========== Utility Functions ========== + + function _msgSender() internal view returns (address) { + return msg.sender; + } +} From c1f274c485aeeca79bceeac95d0413df8e3c122d Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 3 Dec 2025 23:36:54 +0530 Subject: [PATCH 02/25] some other changes --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 77 ++++++++++++++---------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 1a1fe490..e0804de4 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -14,7 +14,8 @@ contract ERC4626Facet { error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); - error ERC4626ZeroBalance(); + error ERC4626ZeroAmount(); + error ZeroAddress(); bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); @@ -64,25 +65,31 @@ contract ERC4626Facet { return erc20s.balanceOf[account]; } - function totalSupply() public view returns (uint256) { + function totalShares() public view returns (uint256) { ERC20Storage storage s = getERC20Storage(); return s.totalSupply; } function convertToShares(uint256 assets) public view returns (uint256) { - uint256 supply = totalSupply(); - if (supply == 0) { - revert ERC4626ZeroBalance(); + uint256 totalShare = totalSupply(); + /** This is the state when the vault was used for the first time. + * The ratio between the assets and the shares is 1:1 when the vault is in initial state. + */ + if (totalShare == 0) { + return assets; } - return assets * supply / totalAssets(); + return assets * totalShare / totalAssets(); } function convertToAssets(uint256 shares) public view returns (uint256) { - uint256 supply = totalSupply(); - if (supply == 0) { + uint256 totalShare = totalShares(); + /** This is the state when the vault was used for the first time. + * The ratio between the assets and the shares is 1:1 when the vault is in initial state. + */ + if (totalShare == 0) { return shares; } - return shares * totalAssets() / supply; + return shares * totalAssets() / totalShare; } @@ -91,21 +98,23 @@ contract ERC4626Facet { } function previewMint(uint256 shares) public view returns (uint256) { - uint256 supply = totalSupply(); - uint256 assets = totalAssets(); - if (supply == 0) { + uint256 totalShare = totalShares(); + uint256 totalAsset = totalAssets(); + if (totalShare == 0) { return shares; } - return (shares * assets + supply - 1) / supply; + + // rounded up + return (shares * totalAssets + totalShare - 1) / totalShare; } function previewWithdraw(uint256 assets) public view returns (uint256) { - uint256 supply = totalSupply(); - uint256 total = totalAssets(); - if (supply == 0) { + uint256 totalShare = totalShares(); + uint256 totalAsset = totalAssets(); + if (totalShare == 0) { return assets; } - return (assets * supply + total - 1) / total; + return (assets * totalShare + totalAsset - 1) / totalAsset; } function previewRedeem(uint256 shares) public view returns (uint256) { @@ -135,7 +144,9 @@ contract ERC4626Facet { revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); } uint256 shares = previewDeposit(assets); - require(shares != 0, "ZERO_SHARES"); + if (shares == 0) { + revert ERC4626ZeroAmount(); + } _deposit(_msgSender(), receiver, assets, shares); return shares; @@ -147,13 +158,14 @@ contract ERC4626Facet { revert ERC4626ExceededMaxMint(receiver, shares, maxShares); } uint256 assets = previewMint(shares); - require(assets != 0, "ZERO_ASSETS"); + if (assets == 0) { + revert ERC4626ZeroAmount(); + } _deposit(_msgSender(), receiver, assets, shares); return assets; } - // ========== Withdraw/Redeem ========== function withdraw(uint256 assets, address receiver, address owner) public returns (uint256) { uint256 maxAssets = maxWithdraw(owner); @@ -161,7 +173,9 @@ contract ERC4626Facet { revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); } uint256 shares = previewWithdraw(assets); - require(shares != 0, "ZERO_SHARES"); + if (shares == 0) { + revert ERC4626ZeroAmount(); + } _withdraw(_msgSender(), receiver, owner, assets, shares); return shares; @@ -173,17 +187,20 @@ contract ERC4626Facet { revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); } uint256 assets = previewRedeem(shares); - require(assets != 0, "ZERO_ASSETS"); + if (assets == 0) { + revert ERC4626ZeroAmount(); + } _withdraw(_msgSender(), receiver, owner, assets, shares); return assets; } - // ========== Internal Deposit ========== function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal { - require(receiver != address(0), "RECEIVER_ZERO"); + if (receiver == address(0)) { + revert ERC4626ZeroAddress(); + } ERC20Storage storage erc20s = getERC20Storage(); getStorage().asset.transferFrom(caller, address(this), assets); @@ -194,8 +211,6 @@ contract ERC4626Facet { emit Deposit(caller, receiver, assets, shares); } - // ========== Internal Withdraw ========== - function _withdraw( address caller, address receiver, @@ -212,7 +227,9 @@ contract ERC4626Facet { erc20s.allowances[owner][caller] = allowed - shares; } } - require(receiver != address(0), "RECEIVER_ZERO"); + if (receiver == address(0)) { + revert ERC4626ZeroAddress(); + } erc20s.balanceOf[owner] -= shares; erc20s.totalSupply -= shares; @@ -223,10 +240,4 @@ contract ERC4626Facet { emit Withdraw(caller, receiver, owner, assets, shares); } - - // ========== Utility Functions ========== - - function _msgSender() internal view returns (address) { - return msg.sender; - } } From 5b499d5608010b970968d3159a09cc003a60f118 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 3 Dec 2025 23:55:34 +0530 Subject: [PATCH 03/25] fix --- .vscode/settings.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f0a9b5a4..e71fb1d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,12 +8,7 @@ // Enable GitHub Copilot "github.copilot.enable": { - "*": false, - "plaintext": false, - "markdown": false, - "scminput": false, - "rust": false, - "solidity": false + "*": true }, "github.copilot.inlineSuggest.enable": true, "editor.inlineSuggest.enabled": true, From c7f57dacbe56ceccc3332332b21aa9c964a4abda Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Thu, 4 Dec 2025 01:24:55 +0530 Subject: [PATCH 04/25] error named match to the convention --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index e0804de4..7fbd1a82 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -15,7 +15,7 @@ contract ERC4626Facet { error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); error ERC4626ZeroAmount(); - error ZeroAddress(); + error ERC4626ZeroAddress(); bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); From 79629cb380677a15e8253b27a8b7426c6561adaa Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Thu, 4 Dec 2025 20:51:45 +0530 Subject: [PATCH 05/25] make build ready --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 129 ++++++++++++++--------- 1 file changed, 81 insertions(+), 48 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 7fbd1a82..65c0ee48 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -1,21 +1,32 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; +/// @notice Minimal IERC20 interface +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address from, address to, uint256 amount) external returns (bool); +} + /** * @dev Implementation of the ERC-4626 */ contract ERC4626Facet { event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); - event Approval(address indexed owner, address indexed spender, uint256 value); event Transfer(address indexed from, address indexed to, uint256 value); error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); - error ERC4626ZeroAmount(); - error ERC4626ZeroAddress(); + error ERC4626InsufficientAllowance(address owner, address caller, uint256 allowed, uint256 required); + error ERC4626TransferFailed(address from, address to, uint256 amount); + error ERC4626ZeroAmount(uint256 amount); + error ERC4626ZeroAddress(address addr); bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); @@ -71,7 +82,7 @@ contract ERC4626Facet { } function convertToShares(uint256 assets) public view returns (uint256) { - uint256 totalShare = totalSupply(); + uint256 totalShare = totalShares(); /** This is the state when the vault was used for the first time. * The ratio between the assets and the shares is 1:1 when the vault is in initial state. */ @@ -92,7 +103,6 @@ contract ERC4626Facet { return shares * totalAssets() / totalShare; } - function previewDeposit(uint256 assets) public view returns (uint256) { return convertToShares(assets); } @@ -105,7 +115,7 @@ contract ERC4626Facet { } // rounded up - return (shares * totalAssets + totalShare - 1) / totalShare; + return (shares * totalAsset + totalShare - 1) / totalShare; } function previewWithdraw(uint256 assets) public view returns (uint256) { @@ -137,7 +147,6 @@ contract ERC4626Facet { return balanceOf(owner); } - function deposit(uint256 assets, address receiver) public returns (uint256) { uint256 maxAssets = maxDeposit(receiver); if (assets > maxAssets) { @@ -145,10 +154,20 @@ contract ERC4626Facet { } uint256 shares = previewDeposit(assets); if (shares == 0) { - revert ERC4626ZeroAmount(); + revert ERC4626ZeroAmount(shares); + } + + if (receiver == address(0)) { + revert ERC4626ZeroAddress(receiver); + } + ERC20Storage storage erc20s = getERC20Storage(); + if (!getStorage().asset.transferFrom(msg.sender, address(this), assets)) { + revert ERC4626TransferFailed(msg.sender, address(this), assets); } + erc20s.totalSupply += shares; + erc20s.balanceOf[receiver] += shares; + emit Deposit(msg.sender, receiver, assets, shares); - _deposit(_msgSender(), receiver, assets, shares); return shares; } @@ -159,14 +178,23 @@ contract ERC4626Facet { } uint256 assets = previewMint(shares); if (assets == 0) { - revert ERC4626ZeroAmount(); + revert ERC4626ZeroAmount(assets); } - _deposit(_msgSender(), receiver, assets, shares); + if (receiver == address(0)) { + revert ERC4626ZeroAddress(receiver); + } + ERC20Storage storage erc20s = getERC20Storage(); + if (!getStorage().asset.transferFrom(msg.sender, address(this), assets)) { + revert ERC4626TransferFailed(msg.sender, address(this), assets); + } + erc20s.totalSupply += shares; + erc20s.balanceOf[receiver] += shares; + emit Deposit(msg.sender, receiver, assets, shares); + return assets; } - function withdraw(uint256 assets, address receiver, address owner) public returns (uint256) { uint256 maxAssets = maxWithdraw(owner); if (assets > maxAssets) { @@ -174,10 +202,35 @@ contract ERC4626Facet { } uint256 shares = previewWithdraw(assets); if (shares == 0) { - revert ERC4626ZeroAmount(); + revert ERC4626ZeroAmount(shares); + } + + ERC20Storage storage erc20s = getERC20Storage(); + + if (msg.sender != owner) { + uint256 allowed = erc20s.allowances[owner][msg.sender]; + if (allowed < shares) { + revert ERC4626InsufficientAllowance(owner, msg.sender, allowed, shares); + } + if (allowed != type(uint256).max) { + erc20s.allowances[owner][msg.sender] = allowed - shares; + } } + if (receiver == address(0)) { + revert ERC4626ZeroAddress(receiver); + } + + erc20s.balanceOf[owner] -= shares; + erc20s.totalSupply -= shares; + + emit Transfer(owner, address(0), shares); + + if (!getStorage().asset.transfer(receiver, assets)) { + revert ERC4626TransferFailed(address(this), receiver, assets); + } + + emit Withdraw(msg.sender, receiver, owner, assets, shares); - _withdraw(_msgSender(), receiver, owner, assets, shares); return shares; } @@ -188,47 +241,23 @@ contract ERC4626Facet { } uint256 assets = previewRedeem(shares); if (assets == 0) { - revert ERC4626ZeroAmount(); + revert ERC4626ZeroAmount(assets); } - _withdraw(_msgSender(), receiver, owner, assets, shares); - return assets; - } - - - function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal { - if (receiver == address(0)) { - revert ERC4626ZeroAddress(); - } ERC20Storage storage erc20s = getERC20Storage(); - getStorage().asset.transferFrom(caller, address(this), assets); - - erc20s.totalSupply += shares; - erc20s.balanceOf[receiver] += shares; - - emit Transfer(address(0), receiver, shares); - emit Deposit(caller, receiver, assets, shares); - } - function _withdraw( - address caller, - address receiver, - address owner, - uint256 assets, - uint256 shares - ) internal { - ERC20Storage storage erc20s = getERC20Storage(); - - if (caller != owner) { - uint256 allowed = erc20s.allowances[owner][caller]; - require(allowed >= shares, "INSUFFICIENT_ALLOWANCE"); + if (msg.sender != owner) { + uint256 allowed = erc20s.allowances[owner][msg.sender]; + if (allowed < shares) { + revert ERC4626InsufficientAllowance(owner, msg.sender, allowed, shares); + } if (allowed != type(uint256).max) { - erc20s.allowances[owner][caller] = allowed - shares; + erc20s.allowances[owner][msg.sender] = allowed - shares; } } if (receiver == address(0)) { - revert ERC4626ZeroAddress(); + revert ERC4626ZeroAddress(receiver); } erc20s.balanceOf[owner] -= shares; @@ -236,8 +265,12 @@ contract ERC4626Facet { emit Transfer(owner, address(0), shares); - getStorage().asset.transfer(receiver, assets); + if (!getStorage().asset.transfer(receiver, assets)) { + revert ERC4626TransferFailed(address(this), receiver, assets); + } + + emit Withdraw(msg.sender, receiver, owner, assets, shares); - emit Withdraw(caller, receiver, owner, assets, shares); + return assets; } } From 904b67e408cb66ccb49f7eb64e5b2217384cf06b Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Thu, 4 Dec 2025 20:52:55 +0530 Subject: [PATCH 06/25] ran forge fmt --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 65c0ee48..665bd10f 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -16,7 +16,9 @@ interface IERC20 { */ contract ERC4626Facet { event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); - event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); + event Withdraw( + address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares + ); event Transfer(address indexed from, address indexed to, uint256 value); error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); @@ -83,9 +85,10 @@ contract ERC4626Facet { function convertToShares(uint256 assets) public view returns (uint256) { uint256 totalShare = totalShares(); - /** This is the state when the vault was used for the first time. + /** + * This is the state when the vault was used for the first time. * The ratio between the assets and the shares is 1:1 when the vault is in initial state. - */ + */ if (totalShare == 0) { return assets; } @@ -94,9 +97,10 @@ contract ERC4626Facet { function convertToAssets(uint256 shares) public view returns (uint256) { uint256 totalShare = totalShares(); - /** This is the state when the vault was used for the first time. + /** + * This is the state when the vault was used for the first time. * The ratio between the assets and the shares is 1:1 when the vault is in initial state. - */ + */ if (totalShare == 0) { return shares; } @@ -244,7 +248,6 @@ contract ERC4626Facet { revert ERC4626ZeroAmount(assets); } - ERC20Storage storage erc20s = getERC20Storage(); if (msg.sender != owner) { From 75d513757b79689d2e8024e25574bcab151b44ea Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Thu, 4 Dec 2025 20:54:38 +0530 Subject: [PATCH 07/25] enforce solidity comments --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 665bd10f..bb48fab5 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; -/// @notice Minimal IERC20 interface +/** + * @notice Minimal IERC20 interface + */ interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); @@ -118,7 +120,9 @@ contract ERC4626Facet { return shares; } - // rounded up + /** + * rounded up + */ return (shares * totalAsset + totalShare - 1) / totalShare; } From 1dae9a2f3bbec7c8c8779e34ee9dca5bf13b7f9d Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Fri, 5 Dec 2025 02:05:29 +0530 Subject: [PATCH 08/25] added libERC4626 --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 239 +++++++++- src/token/ERC20/ERC4626/LibERC4626Facet.sol | 498 ++++++++++++++++++++ 2 files changed, 723 insertions(+), 14 deletions(-) create mode 100644 src/token/ERC20/ERC4626/LibERC4626Facet.sol diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index bb48fab5..c6a085c6 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -1,40 +1,142 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; -/** - * @notice Minimal IERC20 interface +/** + * @title Minimal IERC20 interface + * @notice Minimal interface to interact with ERC20 tokens */ interface IERC20 { + /** + * @notice Returns the total supply of the token. + */ function totalSupply() external view returns (uint256); + + /** + * @notice Returns the balance of a given account. + * @param account The account address to query. + */ function balanceOf(address account) external view returns (uint256); + + /** + * @notice Transfers tokens to a specified address. + * @param to The recipient address. + * @param amount The amount of tokens to transfer. + * @return success True if transfer succeeded, false otherwise. + */ function transfer(address to, uint256 amount) external returns (bool); + + /** + * @notice Returns the allowance granted to a spender by the owner. + * @param owner The owner's address. + * @param spender The spender's address. + */ function allowance(address owner, address spender) external view returns (uint256); + + /** + * @notice Approves a spender to spend a given amount. + * @param spender The spender address. + * @param amount The allowance amount. + * @return success True if approval succeeded, false otherwise. + */ function approve(address spender, uint256 amount) external returns (bool); + + /** + * @notice Transfers tokens from one address to another using allowance. + * @param from The sender address. + * @param to The recipient address. + * @param amount The amount to transfer. + * @return success True if transfer succeeded, false otherwise. + */ function transferFrom(address from, address to, uint256 amount) external returns (bool); } -/** - * @dev Implementation of the ERC-4626 +/** + * @title ERC4626Facet + * @notice Implementation of the ERC-4626 Tokenized Vault standard */ contract ERC4626Facet { + /** + * @notice Emitted after a deposit of `assets` and minting of `shares` + * @param caller Address making the deposit call + * @param owner Receives the minted shares + * @param assets Amount of asset tokens deposited + * @param shares Amount of vault shares minted + */ event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); + + /** + * @notice Emitted after a withdrawal or redemption of `assets` and burning of `shares` + * @param caller Address making the withdrawal call + * @param receiver Receives the withdrawn assets + * @param owner Owner of the withdrawn assets and burned shares + * @param assets Amount of asset tokens withdrawn + * @param shares Amount of vault shares burned + */ event Withdraw( address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); + + /** + * @notice Emitted when vault shares are transferred (including minting/burning) + * @param from Sender address + * @param to Recipient address (zero address means burn) + * @param value Amount of shares transferred + */ event Transfer(address indexed from, address indexed to, uint256 value); + /** + * @notice Reverts when the deposit exceeds maximum allowed assets for receiver + */ error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); + + /** + * @notice Reverts when the mint exceeds maximum allowed shares for receiver + */ error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); + + /** + * @notice Reverts when the withdraw exceeds maximum allowed assets for owner + */ error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); + + /** + * @notice Reverts when the redeem exceeds maximum allowed shares for owner + */ error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); + + /** + * @notice Reverts when allowance is insufficient for withdraw/redeem + */ error ERC4626InsufficientAllowance(address owner, address caller, uint256 allowed, uint256 required); + + /** + * @notice Reverts when an asset transfer fails + */ error ERC4626TransferFailed(address from, address to, uint256 amount); + + /** + * @notice Reverts when zero amount is involved where not valid + */ error ERC4626ZeroAmount(uint256 amount); + + /** + * @notice Reverts on zero address input where not valid + */ error ERC4626ZeroAddress(address addr); + /** + * @notice Storage slot used for ERC4626 data + */ bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); + + /** + * @notice Storage slot used for ERC20 data (shares) + */ bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); + /** + * @notice ERC20 share vault storage + */ struct ERC20Storage { mapping(address owner => uint256 balance) balanceOf; uint256 totalSupply; @@ -44,10 +146,17 @@ contract ERC4626Facet { string symbol; } + /** + * @notice Storage containing vault's asset + */ struct ERC4626Storage { IERC20 asset; } + /** + * @notice Returns storage struct for the ERC4626 position + * @return s The storage reference for ERC4626Storage + */ function getStorage() internal pure returns (ERC4626Storage storage s) { bytes32 position = STORAGE_POSITION; assembly { @@ -55,6 +164,10 @@ contract ERC4626Facet { } } + /** + * @notice Returns storage struct for ERC20 shares + * @return s The storage reference for ERC20Storage + */ function getERC20Storage() internal pure returns (ERC20Storage storage s) { bytes32 position = ERC20_STORAGE_POSITION; assembly { @@ -62,34 +175,59 @@ contract ERC4626Facet { } } + /** + * @notice The address of the asset managed by the vault. + * @return The asset token address. + */ function asset() public view returns (address) { return address(getStorage().asset); } + /** + * @notice Returns the total amount of the underlying asset managed by the vault. + * @return The total assets held by the vault. + */ function totalAssets() public view returns (uint256) { return getStorage().asset.balanceOf(address(this)); } + /** + * @notice Returns the number of decimals of the vault share token. + * @return Number of decimals. + */ function decimals() public view returns (uint8) { ERC20Storage storage erc20s = getERC20Storage(); return erc20s.decimals; } + /** + * @notice Returns the share balance of an account. + * @param account The address to query. + * @return The balance of shares owned by `account`. + */ function balanceOf(address account) public view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); return erc20s.balanceOf[account]; } + /** + * @notice Returns the total supply of vault shares. + * @return The total shares (ERC20 tokens) in existence. + */ function totalShares() public view returns (uint256) { ERC20Storage storage s = getERC20Storage(); return s.totalSupply; } + /** + * @notice Converts an amount of assets to the equivalent amount of shares. + * @param assets Amount of asset tokens. + * @return The computed amount of shares for `assets`. + */ function convertToShares(uint256 assets) public view returns (uint256) { uint256 totalShare = totalShares(); - /** - * This is the state when the vault was used for the first time. - * The ratio between the assets and the shares is 1:1 when the vault is in initial state. + /** + * If no shares exist, 1:1 ratio between asset and shares */ if (totalShare == 0) { return assets; @@ -97,11 +235,15 @@ contract ERC4626Facet { return assets * totalShare / totalAssets(); } + /** + * @notice Converts an amount of shares to the equivalent amount of assets. + * @param shares Amount of vault shares. + * @return Amount of asset tokens equivalent to `shares`. + */ function convertToAssets(uint256 shares) public view returns (uint256) { uint256 totalShare = totalShares(); - /** - * This is the state when the vault was used for the first time. - * The ratio between the assets and the shares is 1:1 when the vault is in initial state. + /** + * If no shares exist, 1:1 ratio between asset and shares */ if (totalShare == 0) { return shares; @@ -109,10 +251,20 @@ contract ERC4626Facet { return shares * totalAssets() / totalShare; } + /** + * @notice Returns the amount of shares that would be minted for a deposit of `assets`. + * @param assets Amount of assets to deposit. + * @return shares Amount of shares previewed. + */ function previewDeposit(uint256 assets) public view returns (uint256) { return convertToShares(assets); } + /** + * @notice Returns the amount of assets required for a mint of `shares`. + * @param shares Amount of shares to mint. + * @return assets Amount of assets previewed. + */ function previewMint(uint256 shares) public view returns (uint256) { uint256 totalShare = totalShares(); uint256 totalAsset = totalAssets(); @@ -120,41 +272,80 @@ contract ERC4626Facet { return shares; } - /** - * rounded up + /** + * Rounds up the result */ return (shares * totalAsset + totalShare - 1) / totalShare; } + /** + * @notice Returns the number of shares needed to withdraw `assets` assets. + * @param assets Amount of assets to withdraw. + * @return shares Number of shares required. + */ function previewWithdraw(uint256 assets) public view returns (uint256) { uint256 totalShare = totalShares(); uint256 totalAsset = totalAssets(); if (totalShare == 0) { return assets; } + /** + * Rounds up the result + */ return (assets * totalShare + totalAsset - 1) / totalAsset; } + /** + * @notice Returns the amount of assets redeemed for a given amount of shares. + * @param shares Amount of shares to redeem. + * @return assets Amount of assets previewed. + */ function previewRedeem(uint256 shares) public view returns (uint256) { return convertToAssets(shares); } - function maxDeposit(address) public view returns (uint256) { + /** + * @notice Returns the maximum amount of assets that can be deposited for `receiver`. + * @param receiver Address of the receiver (ignored, always max). + * @return max Maximum deposit amount allowed. + */ + function maxDeposit(address receiver) public view returns (uint256) { return type(uint256).max; } - function maxMint(address) public view returns (uint256) { + /** + * @notice Returns the maximum number of shares that can be minted for `receiver`. + * @param receiver Address of the receiver (ignored, always max). + * @return max Maximum mint amount allowed. + */ + function maxMint(address receiver) public view returns (uint256) { return type(uint256).max; } + /** + * @notice Returns the maximum amount of assets that can be withdrawn for `owner`. + * @param owner The address to query. + * @return max Maximum amount of assets withdrawable. + */ function maxWithdraw(address owner) public view returns (uint256) { return previewRedeem(maxRedeem(owner)); } + /** + * @notice Returns the maximum number of shares that can be redeemed for `owner`. + * @param owner The address to query. + * @return max Maximum shares redeemable (equal to owner's balance). + */ function maxRedeem(address owner) public view returns (uint256) { return balanceOf(owner); } + /** + * @notice Deposits asset tokens and mints corresponding shares to `receiver`. + * @param assets Amount of asset tokens to deposit. + * @param receiver Address to receive minted shares. + * @return shares Amount of shares minted. + */ function deposit(uint256 assets, address receiver) public returns (uint256) { uint256 maxAssets = maxDeposit(receiver); if (assets > maxAssets) { @@ -179,6 +370,12 @@ contract ERC4626Facet { return shares; } + /** + * @notice Mints `shares` vault shares to `receiver` by transferring corresponding asset amount from caller. + * @param shares Amount of shares to mint. + * @param receiver Address to receive minted shares. + * @return assets Amount of assets transferred from caller. + */ function mint(uint256 shares, address receiver) public returns (uint256) { uint256 maxShares = maxMint(receiver); if (shares > maxShares) { @@ -203,6 +400,13 @@ contract ERC4626Facet { return assets; } + /** + * @notice Burns shares from `owner` and transfers corresponding assets to `receiver`. + * @param assets Amount of assets to withdraw. + * @param receiver Address receiving the withdrawn assets. + * @param owner The address whose shares are burned. + * @return shares Amount of shares burned. + */ function withdraw(uint256 assets, address receiver, address owner) public returns (uint256) { uint256 maxAssets = maxWithdraw(owner); if (assets > maxAssets) { @@ -242,6 +446,13 @@ contract ERC4626Facet { return shares; } + /** + * @notice Redeems `shares` from `owner` and transfers corresponding assets to `receiver`. + * @param shares Amount of shares to redeem. + * @param receiver Address to receive redeemed assets. + * @param owner Address whose shares are redeemed. + * @return assets Amount of assets transferred to receiver. + */ function redeem(uint256 shares, address receiver, address owner) public returns (uint256) { uint256 maxShares = maxRedeem(owner); if (shares > maxShares) { diff --git a/src/token/ERC20/ERC4626/LibERC4626Facet.sol b/src/token/ERC20/ERC4626/LibERC4626Facet.sol new file mode 100644 index 00000000..9d5438f6 --- /dev/null +++ b/src/token/ERC20/ERC4626/LibERC4626Facet.sol @@ -0,0 +1,498 @@ + + + + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/** + * @title Minimal IERC20 interface + * @notice Minimal interface to interact with ERC20 tokens + */ +interface IERC20 { + /** + * @notice Returns the total supply of the token. + */ + function totalSupply() external view returns (uint256); + + /** + * @notice Returns the balance of a given account. + * @param account The account address to query. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @notice Transfers tokens to a specified address. + * @param to The recipient address. + * @param amount The amount of tokens to transfer. + * @return success True if transfer succeeded, false otherwise. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @notice Returns the allowance granted to a spender by the owner. + * @param owner The owner's address. + * @param spender The spender's address. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @notice Approves a spender to spend a given amount. + * @param spender The spender address. + * @param amount The allowance amount. + * @return success True if approval succeeded, false otherwise. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @notice Transfers tokens from one address to another using allowance. + * @param from The sender address. + * @param to The recipient address. + * @param amount The amount to transfer. + * @return success True if transfer succeeded, false otherwise. + */ + function transferFrom(address from, address to, uint256 amount) external returns (bool); +} + +/** + * @title ERC4626Facet + * @notice Implementation of the ERC-4626 Tokenized Vault standard + */ +contract ERC4626Facet { + /** + * @notice Emitted after a deposit of `assets` and minting of `shares` + * @param caller Address making the deposit call + * @param owner Receives the minted shares + * @param assets Amount of asset tokens deposited + * @param shares Amount of vault shares minted + */ + event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); + + /** + * @notice Emitted after a withdrawal or redemption of `assets` and burning of `shares` + * @param caller Address making the withdrawal call + * @param receiver Receives the withdrawn assets + * @param owner Owner of the withdrawn assets and burned shares + * @param assets Amount of asset tokens withdrawn + * @param shares Amount of vault shares burned + */ + event Withdraw( + address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares + ); + + /** + * @notice Emitted when vault shares are transferred (including minting/burning) + * @param from Sender address + * @param to Recipient address (zero address means burn) + * @param value Amount of shares transferred + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @notice Reverts when the deposit exceeds maximum allowed assets for receiver + */ + error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); + + /** + * @notice Reverts when the mint exceeds maximum allowed shares for receiver + */ + error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); + + /** + * @notice Reverts when the withdraw exceeds maximum allowed assets for owner + */ + error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); + + /** + * @notice Reverts when the redeem exceeds maximum allowed shares for owner + */ + error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); + + /** + * @notice Reverts when allowance is insufficient for withdraw/redeem + */ + error ERC4626InsufficientAllowance(address owner, address caller, uint256 allowed, uint256 required); + + /** + * @notice Reverts when an asset transfer fails + */ + error ERC4626TransferFailed(address from, address to, uint256 amount); + + /** + * @notice Reverts when zero amount is involved where not valid + */ + error ERC4626ZeroAmount(uint256 amount); + + /** + * @notice Reverts on zero address input where not valid + */ + error ERC4626ZeroAddress(address addr); + + /** + * @notice Storage slot used for ERC4626 data + */ + bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); + + /** + * @notice Storage slot used for ERC20 data (shares) + */ + bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); + + /** + * @notice ERC20 share vault storage + */ + struct ERC20Storage { + mapping(address owner => uint256 balance) balanceOf; + uint256 totalSupply; + mapping(address owner => mapping(address spender => uint256 allowance)) allowances; + uint8 decimals; + string name; + string symbol; + } + + /** + * @notice Storage containing vault's asset + */ + struct ERC4626Storage { + IERC20 asset; + } + + /** + * @notice Returns storage struct for the ERC4626 position + * @return s The storage reference for ERC4626Storage + */ + function getStorage() internal pure returns (ERC4626Storage storage s) { + bytes32 position = STORAGE_POSITION; + assembly { + s.slot := position + } + } + + /** + * @notice Returns storage struct for ERC20 shares + * @return s The storage reference for ERC20Storage + */ + function getERC20Storage() internal pure returns (ERC20Storage storage s) { + bytes32 position = ERC20_STORAGE_POSITION; + assembly { + s.slot := position + } + } + + /** + * @notice The address of the asset managed by the vault. + * @return The asset token address. + */ + function asset() public view returns (address) { + return address(getStorage().asset); + } + + /** + * @notice Returns the total amount of the underlying asset managed by the vault. + * @return The total assets held by the vault. + */ + function totalAssets() public view returns (uint256) { + return getStorage().asset.balanceOf(address(this)); + } + + /** + * @notice Returns the number of decimals of the vault share token. + * @return Number of decimals. + */ + function decimals() public view returns (uint8) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.decimals; + } + + /** + * @notice Returns the share balance of an account. + * @param account The address to query. + * @return The balance of shares owned by `account`. + */ + function balanceOf(address account) public view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.balanceOf[account]; + } + + /** + * @notice Returns the total supply of vault shares. + * @return The total shares (ERC20 tokens) in existence. + */ + function totalShares() public view returns (uint256) { + ERC20Storage storage s = getERC20Storage(); + return s.totalSupply; + } + + /** + * @notice Converts an amount of assets to the equivalent amount of shares. + * @param assets Amount of asset tokens. + * @return The computed amount of shares for `assets`. + */ + function convertToShares(uint256 assets) public view returns (uint256) { + uint256 totalShare = totalShares(); + /** + * If no shares exist, 1:1 ratio between asset and shares + */ + if (totalShare == 0) { + return assets; + } + return assets * totalShare / totalAssets(); + } + + /** + * @notice Converts an amount of shares to the equivalent amount of assets. + * @param shares Amount of vault shares. + * @return Amount of asset tokens equivalent to `shares`. + */ + function convertToAssets(uint256 shares) public view returns (uint256) { + uint256 totalShare = totalShares(); + /** + * If no shares exist, 1:1 ratio between asset and shares + */ + if (totalShare == 0) { + return shares; + } + return shares * totalAssets() / totalShare; + } + + /** + * @notice Returns the amount of shares that would be minted for a deposit of `assets`. + * @param assets Amount of assets to deposit. + * @return shares Amount of shares previewed. + */ + function previewDeposit(uint256 assets) public view returns (uint256) { + return convertToShares(assets); + } + + /** + * @notice Returns the amount of assets required for a mint of `shares`. + * @param shares Amount of shares to mint. + * @return assets Amount of assets previewed. + */ + function previewMint(uint256 shares) public view returns (uint256) { + uint256 totalShare = totalShares(); + uint256 totalAsset = totalAssets(); + if (totalShare == 0) { + return shares; + } + + /** + * Rounds up the result + */ + return (shares * totalAsset + totalShare - 1) / totalShare; + } + + /** + * @notice Returns the number of shares needed to withdraw `assets` assets. + * @param assets Amount of assets to withdraw. + * @return shares Number of shares required. + */ + function previewWithdraw(uint256 assets) public view returns (uint256) { + uint256 totalShare = totalShares(); + uint256 totalAsset = totalAssets(); + if (totalShare == 0) { + return assets; + } + /** + * Rounds up the result + */ + return (assets * totalShare + totalAsset - 1) / totalAsset; + } + + /** + * @notice Returns the amount of assets redeemed for a given amount of shares. + * @param shares Amount of shares to redeem. + * @return assets Amount of assets previewed. + */ + function previewRedeem(uint256 shares) public view returns (uint256) { + return convertToAssets(shares); + } + + /** + * @notice Returns the maximum amount of assets that can be deposited for `receiver`. + * @param receiver Address of the receiver (ignored, always max). + * @return max Maximum deposit amount allowed. + */ + function maxDeposit(address receiver) public view returns (uint256) { + return type(uint256).max; + } + + /** + * @notice Returns the maximum number of shares that can be minted for `receiver`. + * @param receiver Address of the receiver (ignored, always max). + * @return max Maximum mint amount allowed. + */ + function maxMint(address receiver) public view returns (uint256) { + return type(uint256).max; + } + + /** + * @notice Returns the maximum amount of assets that can be withdrawn for `owner`. + * @param owner The address to query. + * @return max Maximum amount of assets withdrawable. + */ + function maxWithdraw(address owner) public view returns (uint256) { + return previewRedeem(maxRedeem(owner)); + } + + /** + * @notice Returns the maximum number of shares that can be redeemed for `owner`. + * @param owner The address to query. + * @return max Maximum shares redeemable (equal to owner's balance). + */ + function maxRedeem(address owner) public view returns (uint256) { + return balanceOf(owner); + } + + /** + * @notice Deposits asset tokens and mints corresponding shares to `receiver`. + * @param assets Amount of asset tokens to deposit. + * @param receiver Address to receive minted shares. + * @return shares Amount of shares minted. + */ + function deposit(uint256 assets, address receiver) public returns (uint256) { + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } + uint256 shares = previewDeposit(assets); + if (shares == 0) { + revert ERC4626ZeroAmount(shares); + } + + if (receiver == address(0)) { + revert ERC4626ZeroAddress(receiver); + } + ERC20Storage storage erc20s = getERC20Storage(); + if (!getStorage().asset.transferFrom(msg.sender, address(this), assets)) { + revert ERC4626TransferFailed(msg.sender, address(this), assets); + } + erc20s.totalSupply += shares; + erc20s.balanceOf[receiver] += shares; + emit Deposit(msg.sender, receiver, assets, shares); + + return shares; + } + + /** + * @notice Mints `shares` vault shares to `receiver` by transferring corresponding asset amount from caller. + * @param shares Amount of shares to mint. + * @param receiver Address to receive minted shares. + * @return assets Amount of assets transferred from caller. + */ + function mint(uint256 shares, address receiver) public returns (uint256) { + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } + uint256 assets = previewMint(shares); + if (assets == 0) { + revert ERC4626ZeroAmount(assets); + } + + if (receiver == address(0)) { + revert ERC4626ZeroAddress(receiver); + } + ERC20Storage storage erc20s = getERC20Storage(); + if (!getStorage().asset.transferFrom(msg.sender, address(this), assets)) { + revert ERC4626TransferFailed(msg.sender, address(this), assets); + } + erc20s.totalSupply += shares; + erc20s.balanceOf[receiver] += shares; + emit Deposit(msg.sender, receiver, assets, shares); + + return assets; + } + + /** + * @notice Burns shares from `owner` and transfers corresponding assets to `receiver`. + * @param assets Amount of assets to withdraw. + * @param receiver Address receiving the withdrawn assets. + * @param owner The address whose shares are burned. + * @return shares Amount of shares burned. + */ + function withdraw(uint256 assets, address receiver, address owner) public returns (uint256) { + uint256 maxAssets = maxWithdraw(owner); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } + uint256 shares = previewWithdraw(assets); + if (shares == 0) { + revert ERC4626ZeroAmount(shares); + } + + ERC20Storage storage erc20s = getERC20Storage(); + + if (msg.sender != owner) { + uint256 allowed = erc20s.allowances[owner][msg.sender]; + if (allowed < shares) { + revert ERC4626InsufficientAllowance(owner, msg.sender, allowed, shares); + } + if (allowed != type(uint256).max) { + erc20s.allowances[owner][msg.sender] = allowed - shares; + } + } + if (receiver == address(0)) { + revert ERC4626ZeroAddress(receiver); + } + + erc20s.balanceOf[owner] -= shares; + erc20s.totalSupply -= shares; + + emit Transfer(owner, address(0), shares); + + if (!getStorage().asset.transfer(receiver, assets)) { + revert ERC4626TransferFailed(address(this), receiver, assets); + } + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + + return shares; + } + + /** + * @notice Redeems `shares` from `owner` and transfers corresponding assets to `receiver`. + * @param shares Amount of shares to redeem. + * @param receiver Address to receive redeemed assets. + * @param owner Address whose shares are redeemed. + * @return assets Amount of assets transferred to receiver. + */ + function redeem(uint256 shares, address receiver, address owner) public returns (uint256) { + uint256 maxShares = maxRedeem(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } + uint256 assets = previewRedeem(shares); + if (assets == 0) { + revert ERC4626ZeroAmount(assets); + } + + ERC20Storage storage erc20s = getERC20Storage(); + + if (msg.sender != owner) { + uint256 allowed = erc20s.allowances[owner][msg.sender]; + if (allowed < shares) { + revert ERC4626InsufficientAllowance(owner, msg.sender, allowed, shares); + } + if (allowed != type(uint256).max) { + erc20s.allowances[owner][msg.sender] = allowed - shares; + } + } + if (receiver == address(0)) { + revert ERC4626ZeroAddress(receiver); + } + + erc20s.balanceOf[owner] -= shares; + erc20s.totalSupply -= shares; + + emit Transfer(owner, address(0), shares); + + if (!getStorage().asset.transfer(receiver, assets)) { + revert ERC4626TransferFailed(address(this), receiver, assets); + } + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + + return assets; + } +} From 94a10e34e39b59ff2263511904ec8bd476014c75 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Fri, 5 Dec 2025 02:07:46 +0530 Subject: [PATCH 09/25] format --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 96 ++++++++--------- .../{LibERC4626Facet.sol => LibERC4626.sol} | 100 +++++++++--------- 2 files changed, 96 insertions(+), 100 deletions(-) rename src/token/ERC20/ERC4626/{LibERC4626Facet.sol => LibERC4626.sol} (97%) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index c6a085c6..7ab2517f 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -1,23 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; -/** +/** * @title Minimal IERC20 interface * @notice Minimal interface to interact with ERC20 tokens */ interface IERC20 { - /** + /** * @notice Returns the total supply of the token. */ function totalSupply() external view returns (uint256); - /** + /** * @notice Returns the balance of a given account. * @param account The account address to query. */ function balanceOf(address account) external view returns (uint256); - /** + /** * @notice Transfers tokens to a specified address. * @param to The recipient address. * @param amount The amount of tokens to transfer. @@ -25,14 +25,14 @@ interface IERC20 { */ function transfer(address to, uint256 amount) external returns (bool); - /** + /** * @notice Returns the allowance granted to a spender by the owner. * @param owner The owner's address. * @param spender The spender's address. */ function allowance(address owner, address spender) external view returns (uint256); - /** + /** * @notice Approves a spender to spend a given amount. * @param spender The spender address. * @param amount The allowance amount. @@ -40,7 +40,7 @@ interface IERC20 { */ function approve(address spender, uint256 amount) external returns (bool); - /** + /** * @notice Transfers tokens from one address to another using allowance. * @param from The sender address. * @param to The recipient address. @@ -50,12 +50,12 @@ interface IERC20 { function transferFrom(address from, address to, uint256 amount) external returns (bool); } -/** +/** * @title ERC4626Facet * @notice Implementation of the ERC-4626 Tokenized Vault standard */ contract ERC4626Facet { - /** + /** * @notice Emitted after a deposit of `assets` and minting of `shares` * @param caller Address making the deposit call * @param owner Receives the minted shares @@ -64,7 +64,7 @@ contract ERC4626Facet { */ event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); - /** + /** * @notice Emitted after a withdrawal or redemption of `assets` and burning of `shares` * @param caller Address making the withdrawal call * @param receiver Receives the withdrawn assets @@ -76,7 +76,7 @@ contract ERC4626Facet { address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); - /** + /** * @notice Emitted when vault shares are transferred (including minting/burning) * @param from Sender address * @param to Recipient address (zero address means burn) @@ -84,57 +84,57 @@ contract ERC4626Facet { */ event Transfer(address indexed from, address indexed to, uint256 value); - /** + /** * @notice Reverts when the deposit exceeds maximum allowed assets for receiver */ error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); - /** + /** * @notice Reverts when the mint exceeds maximum allowed shares for receiver */ error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); - /** + /** * @notice Reverts when the withdraw exceeds maximum allowed assets for owner */ error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); - /** + /** * @notice Reverts when the redeem exceeds maximum allowed shares for owner */ error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); - /** + /** * @notice Reverts when allowance is insufficient for withdraw/redeem */ error ERC4626InsufficientAllowance(address owner, address caller, uint256 allowed, uint256 required); - /** + /** * @notice Reverts when an asset transfer fails */ error ERC4626TransferFailed(address from, address to, uint256 amount); - /** + /** * @notice Reverts when zero amount is involved where not valid */ error ERC4626ZeroAmount(uint256 amount); - /** + /** * @notice Reverts on zero address input where not valid */ error ERC4626ZeroAddress(address addr); - /** + /** * @notice Storage slot used for ERC4626 data */ bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); - /** + /** * @notice Storage slot used for ERC20 data (shares) */ bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); - /** + /** * @notice ERC20 share vault storage */ struct ERC20Storage { @@ -146,14 +146,14 @@ contract ERC4626Facet { string symbol; } - /** + /** * @notice Storage containing vault's asset */ struct ERC4626Storage { IERC20 asset; } - /** + /** * @notice Returns storage struct for the ERC4626 position * @return s The storage reference for ERC4626Storage */ @@ -164,7 +164,7 @@ contract ERC4626Facet { } } - /** + /** * @notice Returns storage struct for ERC20 shares * @return s The storage reference for ERC20Storage */ @@ -175,7 +175,7 @@ contract ERC4626Facet { } } - /** + /** * @notice The address of the asset managed by the vault. * @return The asset token address. */ @@ -183,7 +183,7 @@ contract ERC4626Facet { return address(getStorage().asset); } - /** + /** * @notice Returns the total amount of the underlying asset managed by the vault. * @return The total assets held by the vault. */ @@ -191,7 +191,7 @@ contract ERC4626Facet { return getStorage().asset.balanceOf(address(this)); } - /** + /** * @notice Returns the number of decimals of the vault share token. * @return Number of decimals. */ @@ -200,7 +200,7 @@ contract ERC4626Facet { return erc20s.decimals; } - /** + /** * @notice Returns the share balance of an account. * @param account The address to query. * @return The balance of shares owned by `account`. @@ -210,7 +210,7 @@ contract ERC4626Facet { return erc20s.balanceOf[account]; } - /** + /** * @notice Returns the total supply of vault shares. * @return The total shares (ERC20 tokens) in existence. */ @@ -219,14 +219,14 @@ contract ERC4626Facet { return s.totalSupply; } - /** + /** * @notice Converts an amount of assets to the equivalent amount of shares. * @param assets Amount of asset tokens. * @return The computed amount of shares for `assets`. */ function convertToShares(uint256 assets) public view returns (uint256) { uint256 totalShare = totalShares(); - /** + /** * If no shares exist, 1:1 ratio between asset and shares */ if (totalShare == 0) { @@ -235,14 +235,14 @@ contract ERC4626Facet { return assets * totalShare / totalAssets(); } - /** + /** * @notice Converts an amount of shares to the equivalent amount of assets. * @param shares Amount of vault shares. * @return Amount of asset tokens equivalent to `shares`. */ function convertToAssets(uint256 shares) public view returns (uint256) { uint256 totalShare = totalShares(); - /** + /** * If no shares exist, 1:1 ratio between asset and shares */ if (totalShare == 0) { @@ -251,7 +251,7 @@ contract ERC4626Facet { return shares * totalAssets() / totalShare; } - /** + /** * @notice Returns the amount of shares that would be minted for a deposit of `assets`. * @param assets Amount of assets to deposit. * @return shares Amount of shares previewed. @@ -260,7 +260,7 @@ contract ERC4626Facet { return convertToShares(assets); } - /** + /** * @notice Returns the amount of assets required for a mint of `shares`. * @param shares Amount of shares to mint. * @return assets Amount of assets previewed. @@ -272,13 +272,13 @@ contract ERC4626Facet { return shares; } - /** + /** * Rounds up the result */ return (shares * totalAsset + totalShare - 1) / totalShare; } - /** + /** * @notice Returns the number of shares needed to withdraw `assets` assets. * @param assets Amount of assets to withdraw. * @return shares Number of shares required. @@ -289,13 +289,13 @@ contract ERC4626Facet { if (totalShare == 0) { return assets; } - /** + /** * Rounds up the result */ return (assets * totalShare + totalAsset - 1) / totalAsset; } - /** + /** * @notice Returns the amount of assets redeemed for a given amount of shares. * @param shares Amount of shares to redeem. * @return assets Amount of assets previewed. @@ -304,7 +304,7 @@ contract ERC4626Facet { return convertToAssets(shares); } - /** + /** * @notice Returns the maximum amount of assets that can be deposited for `receiver`. * @param receiver Address of the receiver (ignored, always max). * @return max Maximum deposit amount allowed. @@ -313,7 +313,7 @@ contract ERC4626Facet { return type(uint256).max; } - /** + /** * @notice Returns the maximum number of shares that can be minted for `receiver`. * @param receiver Address of the receiver (ignored, always max). * @return max Maximum mint amount allowed. @@ -322,7 +322,7 @@ contract ERC4626Facet { return type(uint256).max; } - /** + /** * @notice Returns the maximum amount of assets that can be withdrawn for `owner`. * @param owner The address to query. * @return max Maximum amount of assets withdrawable. @@ -331,7 +331,7 @@ contract ERC4626Facet { return previewRedeem(maxRedeem(owner)); } - /** + /** * @notice Returns the maximum number of shares that can be redeemed for `owner`. * @param owner The address to query. * @return max Maximum shares redeemable (equal to owner's balance). @@ -340,7 +340,7 @@ contract ERC4626Facet { return balanceOf(owner); } - /** + /** * @notice Deposits asset tokens and mints corresponding shares to `receiver`. * @param assets Amount of asset tokens to deposit. * @param receiver Address to receive minted shares. @@ -370,7 +370,7 @@ contract ERC4626Facet { return shares; } - /** + /** * @notice Mints `shares` vault shares to `receiver` by transferring corresponding asset amount from caller. * @param shares Amount of shares to mint. * @param receiver Address to receive minted shares. @@ -400,7 +400,7 @@ contract ERC4626Facet { return assets; } - /** + /** * @notice Burns shares from `owner` and transfers corresponding assets to `receiver`. * @param assets Amount of assets to withdraw. * @param receiver Address receiving the withdrawn assets. @@ -446,7 +446,7 @@ contract ERC4626Facet { return shares; } - /** + /** * @notice Redeems `shares` from `owner` and transfers corresponding assets to `receiver`. * @param shares Amount of shares to redeem. * @param receiver Address to receive redeemed assets. diff --git a/src/token/ERC20/ERC4626/LibERC4626Facet.sol b/src/token/ERC20/ERC4626/LibERC4626.sol similarity index 97% rename from src/token/ERC20/ERC4626/LibERC4626Facet.sol rename to src/token/ERC20/ERC4626/LibERC4626.sol index 9d5438f6..7ab2517f 100644 --- a/src/token/ERC20/ERC4626/LibERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/LibERC4626.sol @@ -1,27 +1,23 @@ - - - - // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; -/** +/** * @title Minimal IERC20 interface * @notice Minimal interface to interact with ERC20 tokens */ interface IERC20 { - /** + /** * @notice Returns the total supply of the token. */ function totalSupply() external view returns (uint256); - /** + /** * @notice Returns the balance of a given account. * @param account The account address to query. */ function balanceOf(address account) external view returns (uint256); - /** + /** * @notice Transfers tokens to a specified address. * @param to The recipient address. * @param amount The amount of tokens to transfer. @@ -29,14 +25,14 @@ interface IERC20 { */ function transfer(address to, uint256 amount) external returns (bool); - /** + /** * @notice Returns the allowance granted to a spender by the owner. * @param owner The owner's address. * @param spender The spender's address. */ function allowance(address owner, address spender) external view returns (uint256); - /** + /** * @notice Approves a spender to spend a given amount. * @param spender The spender address. * @param amount The allowance amount. @@ -44,7 +40,7 @@ interface IERC20 { */ function approve(address spender, uint256 amount) external returns (bool); - /** + /** * @notice Transfers tokens from one address to another using allowance. * @param from The sender address. * @param to The recipient address. @@ -54,12 +50,12 @@ interface IERC20 { function transferFrom(address from, address to, uint256 amount) external returns (bool); } -/** +/** * @title ERC4626Facet * @notice Implementation of the ERC-4626 Tokenized Vault standard */ contract ERC4626Facet { - /** + /** * @notice Emitted after a deposit of `assets` and minting of `shares` * @param caller Address making the deposit call * @param owner Receives the minted shares @@ -68,7 +64,7 @@ contract ERC4626Facet { */ event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); - /** + /** * @notice Emitted after a withdrawal or redemption of `assets` and burning of `shares` * @param caller Address making the withdrawal call * @param receiver Receives the withdrawn assets @@ -80,7 +76,7 @@ contract ERC4626Facet { address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); - /** + /** * @notice Emitted when vault shares are transferred (including minting/burning) * @param from Sender address * @param to Recipient address (zero address means burn) @@ -88,57 +84,57 @@ contract ERC4626Facet { */ event Transfer(address indexed from, address indexed to, uint256 value); - /** + /** * @notice Reverts when the deposit exceeds maximum allowed assets for receiver */ error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); - /** + /** * @notice Reverts when the mint exceeds maximum allowed shares for receiver */ error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); - /** + /** * @notice Reverts when the withdraw exceeds maximum allowed assets for owner */ error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); - /** + /** * @notice Reverts when the redeem exceeds maximum allowed shares for owner */ error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); - /** + /** * @notice Reverts when allowance is insufficient for withdraw/redeem */ error ERC4626InsufficientAllowance(address owner, address caller, uint256 allowed, uint256 required); - /** + /** * @notice Reverts when an asset transfer fails */ error ERC4626TransferFailed(address from, address to, uint256 amount); - /** + /** * @notice Reverts when zero amount is involved where not valid */ error ERC4626ZeroAmount(uint256 amount); - /** + /** * @notice Reverts on zero address input where not valid */ error ERC4626ZeroAddress(address addr); - /** + /** * @notice Storage slot used for ERC4626 data */ bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); - /** + /** * @notice Storage slot used for ERC20 data (shares) */ bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); - /** + /** * @notice ERC20 share vault storage */ struct ERC20Storage { @@ -150,14 +146,14 @@ contract ERC4626Facet { string symbol; } - /** + /** * @notice Storage containing vault's asset */ struct ERC4626Storage { IERC20 asset; } - /** + /** * @notice Returns storage struct for the ERC4626 position * @return s The storage reference for ERC4626Storage */ @@ -168,7 +164,7 @@ contract ERC4626Facet { } } - /** + /** * @notice Returns storage struct for ERC20 shares * @return s The storage reference for ERC20Storage */ @@ -179,7 +175,7 @@ contract ERC4626Facet { } } - /** + /** * @notice The address of the asset managed by the vault. * @return The asset token address. */ @@ -187,7 +183,7 @@ contract ERC4626Facet { return address(getStorage().asset); } - /** + /** * @notice Returns the total amount of the underlying asset managed by the vault. * @return The total assets held by the vault. */ @@ -195,7 +191,7 @@ contract ERC4626Facet { return getStorage().asset.balanceOf(address(this)); } - /** + /** * @notice Returns the number of decimals of the vault share token. * @return Number of decimals. */ @@ -204,7 +200,7 @@ contract ERC4626Facet { return erc20s.decimals; } - /** + /** * @notice Returns the share balance of an account. * @param account The address to query. * @return The balance of shares owned by `account`. @@ -214,7 +210,7 @@ contract ERC4626Facet { return erc20s.balanceOf[account]; } - /** + /** * @notice Returns the total supply of vault shares. * @return The total shares (ERC20 tokens) in existence. */ @@ -223,14 +219,14 @@ contract ERC4626Facet { return s.totalSupply; } - /** + /** * @notice Converts an amount of assets to the equivalent amount of shares. * @param assets Amount of asset tokens. * @return The computed amount of shares for `assets`. */ function convertToShares(uint256 assets) public view returns (uint256) { uint256 totalShare = totalShares(); - /** + /** * If no shares exist, 1:1 ratio between asset and shares */ if (totalShare == 0) { @@ -239,14 +235,14 @@ contract ERC4626Facet { return assets * totalShare / totalAssets(); } - /** + /** * @notice Converts an amount of shares to the equivalent amount of assets. * @param shares Amount of vault shares. * @return Amount of asset tokens equivalent to `shares`. */ function convertToAssets(uint256 shares) public view returns (uint256) { uint256 totalShare = totalShares(); - /** + /** * If no shares exist, 1:1 ratio between asset and shares */ if (totalShare == 0) { @@ -255,7 +251,7 @@ contract ERC4626Facet { return shares * totalAssets() / totalShare; } - /** + /** * @notice Returns the amount of shares that would be minted for a deposit of `assets`. * @param assets Amount of assets to deposit. * @return shares Amount of shares previewed. @@ -264,7 +260,7 @@ contract ERC4626Facet { return convertToShares(assets); } - /** + /** * @notice Returns the amount of assets required for a mint of `shares`. * @param shares Amount of shares to mint. * @return assets Amount of assets previewed. @@ -276,13 +272,13 @@ contract ERC4626Facet { return shares; } - /** + /** * Rounds up the result */ return (shares * totalAsset + totalShare - 1) / totalShare; } - /** + /** * @notice Returns the number of shares needed to withdraw `assets` assets. * @param assets Amount of assets to withdraw. * @return shares Number of shares required. @@ -293,13 +289,13 @@ contract ERC4626Facet { if (totalShare == 0) { return assets; } - /** + /** * Rounds up the result */ return (assets * totalShare + totalAsset - 1) / totalAsset; } - /** + /** * @notice Returns the amount of assets redeemed for a given amount of shares. * @param shares Amount of shares to redeem. * @return assets Amount of assets previewed. @@ -308,7 +304,7 @@ contract ERC4626Facet { return convertToAssets(shares); } - /** + /** * @notice Returns the maximum amount of assets that can be deposited for `receiver`. * @param receiver Address of the receiver (ignored, always max). * @return max Maximum deposit amount allowed. @@ -317,7 +313,7 @@ contract ERC4626Facet { return type(uint256).max; } - /** + /** * @notice Returns the maximum number of shares that can be minted for `receiver`. * @param receiver Address of the receiver (ignored, always max). * @return max Maximum mint amount allowed. @@ -326,7 +322,7 @@ contract ERC4626Facet { return type(uint256).max; } - /** + /** * @notice Returns the maximum amount of assets that can be withdrawn for `owner`. * @param owner The address to query. * @return max Maximum amount of assets withdrawable. @@ -335,7 +331,7 @@ contract ERC4626Facet { return previewRedeem(maxRedeem(owner)); } - /** + /** * @notice Returns the maximum number of shares that can be redeemed for `owner`. * @param owner The address to query. * @return max Maximum shares redeemable (equal to owner's balance). @@ -344,7 +340,7 @@ contract ERC4626Facet { return balanceOf(owner); } - /** + /** * @notice Deposits asset tokens and mints corresponding shares to `receiver`. * @param assets Amount of asset tokens to deposit. * @param receiver Address to receive minted shares. @@ -374,7 +370,7 @@ contract ERC4626Facet { return shares; } - /** + /** * @notice Mints `shares` vault shares to `receiver` by transferring corresponding asset amount from caller. * @param shares Amount of shares to mint. * @param receiver Address to receive minted shares. @@ -404,7 +400,7 @@ contract ERC4626Facet { return assets; } - /** + /** * @notice Burns shares from `owner` and transfers corresponding assets to `receiver`. * @param assets Amount of assets to withdraw. * @param receiver Address receiving the withdrawn assets. @@ -450,7 +446,7 @@ contract ERC4626Facet { return shares; } - /** + /** * @notice Redeems `shares` from `owner` and transfers corresponding assets to `receiver`. * @param shares Amount of shares to redeem. * @param receiver Address to receive redeemed assets. From e507ee2133f24aff5a744a03fd283710aec7599c Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 10 Dec 2025 12:53:34 +0530 Subject: [PATCH 10/25] refactored the logic --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 647 ++++++++++------------- src/token/ERC20/ERC4626/LibERC4626.sol | 494 ----------------- 2 files changed, 283 insertions(+), 858 deletions(-) delete mode 100644 src/token/ERC20/ERC4626/LibERC4626.sol diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 7ab2517f..985ffbea 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -1,494 +1,413 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; -/** - * @title Minimal IERC20 interface - * @notice Minimal interface to interact with ERC20 tokens - */ interface IERC20 { - /** - * @notice Returns the total supply of the token. - */ function totalSupply() external view returns (uint256); - /** - * @notice Returns the balance of a given account. - * @param account The account address to query. - */ function balanceOf(address account) external view returns (uint256); - /** - * @notice Transfers tokens to a specified address. - * @param to The recipient address. - * @param amount The amount of tokens to transfer. - * @return success True if transfer succeeded, false otherwise. - */ function transfer(address to, uint256 amount) external returns (bool); - /** - * @notice Returns the allowance granted to a spender by the owner. - * @param owner The owner's address. - * @param spender The spender's address. - */ function allowance(address owner, address spender) external view returns (uint256); - /** - * @notice Approves a spender to spend a given amount. - * @param spender The spender address. - * @param amount The allowance amount. - * @return success True if approval succeeded, false otherwise. - */ function approve(address spender, uint256 amount) external returns (bool); - /** - * @notice Transfers tokens from one address to another using allowance. - * @param from The sender address. - * @param to The recipient address. - * @param amount The amount to transfer. - * @return success True if transfer succeeded, false otherwise. - */ function transferFrom(address from, address to, uint256 amount) external returns (bool); } -/** - * @title ERC4626Facet - * @notice Implementation of the ERC-4626 Tokenized Vault standard - */ contract ERC4626Facet { - /** - * @notice Emitted after a deposit of `assets` and minting of `shares` - * @param caller Address making the deposit call - * @param owner Receives the minted shares - * @param assets Amount of asset tokens deposited - * @param shares Amount of vault shares minted - */ - event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); - - /** - * @notice Emitted after a withdrawal or redemption of `assets` and burning of `shares` - * @param caller Address making the withdrawal call - * @param receiver Receives the withdrawn assets - * @param owner Owner of the withdrawn assets and burned shares - * @param assets Amount of asset tokens withdrawn - * @param shares Amount of vault shares burned - */ + error ERC4626InvalidAmount(); + error ERC4626InvalidAddress(); + error ERC4626TransferFailed(); + error ERC4626InsufficientShares(); + error ERC4626InsufficientAssets(); + + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); event Withdraw( - address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares + address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); - /** - * @notice Emitted when vault shares are transferred (including minting/burning) - * @param from Sender address - * @param to Recipient address (zero address means burn) - * @param value Amount of shares transferred - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @notice Reverts when the deposit exceeds maximum allowed assets for receiver - */ - error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); - - /** - * @notice Reverts when the mint exceeds maximum allowed shares for receiver - */ - error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); - - /** - * @notice Reverts when the withdraw exceeds maximum allowed assets for owner - */ - error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); - - /** - * @notice Reverts when the redeem exceeds maximum allowed shares for owner - */ - error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); - - /** - * @notice Reverts when allowance is insufficient for withdraw/redeem - */ - error ERC4626InsufficientAllowance(address owner, address caller, uint256 allowed, uint256 required); - - /** - * @notice Reverts when an asset transfer fails - */ - error ERC4626TransferFailed(address from, address to, uint256 amount); - - /** - * @notice Reverts when zero amount is involved where not valid - */ - error ERC4626ZeroAmount(uint256 amount); - - /** - * @notice Reverts on zero address input where not valid - */ - error ERC4626ZeroAddress(address addr); - - /** - * @notice Storage slot used for ERC4626 data - */ + bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); - /** - * @notice Storage slot used for ERC20 data (shares) - */ - bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); + uint256 constant VIRTUAL_ASSET = 1; + uint256 constant VIRTUAL_SHARE = 1; - /** - * @notice ERC20 share vault storage - */ struct ERC20Storage { - mapping(address owner => uint256 balance) balanceOf; + mapping(address => uint256) balanceOf; uint256 totalSupply; - mapping(address owner => mapping(address spender => uint256 allowance)) allowances; + mapping(address => mapping(address => uint256)) allowances; uint8 decimals; string name; string symbol; } - /** - * @notice Storage containing vault's asset - */ struct ERC4626Storage { IERC20 asset; } - /** - * @notice Returns storage struct for the ERC4626 position - * @return s The storage reference for ERC4626Storage - */ - function getStorage() internal pure returns (ERC4626Storage storage s) { - bytes32 position = STORAGE_POSITION; + function getERC20Storage() internal pure returns (ERC20Storage storage s) { + bytes32 position = ERC20_STORAGE_POSITION; assembly { s.slot := position } } - /** - * @notice Returns storage struct for ERC20 shares - * @return s The storage reference for ERC20Storage - */ - function getERC20Storage() internal pure returns (ERC20Storage storage s) { - bytes32 position = ERC20_STORAGE_POSITION; + function getStorage() internal pure returns (ERC4626Storage storage s) { + bytes32 position = STORAGE_POSITION; assembly { s.slot := position } } - /** - * @notice The address of the asset managed by the vault. - * @return The asset token address. - */ - function asset() public view returns (address) { - return address(getStorage().asset); + function asset() external view returns (address) { + ERC4626Storage storage s = getStorage(); + return address(s.asset); } - /** - * @notice Returns the total amount of the underlying asset managed by the vault. - * @return The total assets held by the vault. - */ - function totalAssets() public view returns (uint256) { - return getStorage().asset.balanceOf(address(this)); + function realTotalAssets() external view returns (uint256) { + ERC4626Storage storage s = getStorage(); + return s.asset.balanceOf(address(this)); } - /** - * @notice Returns the number of decimals of the vault share token. - * @return Number of decimals. - */ - function decimals() public view returns (uint8) { - ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.decimals; + function totalAssets() external view returns (uint256) { + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + return realAssets + VIRTUAL_ASSET; } - /** - * @notice Returns the share balance of an account. - * @param account The address to query. - * @return The balance of shares owned by `account`. - */ - function balanceOf(address account) public view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.balanceOf[account]; - } + function muldiv(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { + require(denominator > 0); - /** - * @notice Returns the total supply of vault shares. - * @return The total shares (ERC20 tokens) in existence. - */ - function totalShares() public view returns (uint256) { - ERC20Storage storage s = getERC20Storage(); - return s.totalSupply; - } + uint256 prod0; + uint256 prod1; + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } - /** - * @notice Converts an amount of assets to the equivalent amount of shares. - * @param assets Amount of asset tokens. - * @return The computed amount of shares for `assets`. - */ - function convertToShares(uint256 assets) public view returns (uint256) { - uint256 totalShare = totalShares(); - /** - * If no shares exist, 1:1 ratio between asset and shares - */ - if (totalShare == 0) { - return assets; + if (prod1 == 0) { + assembly { + result := div(prod0, denominator) + } + return result; } - return assets * totalShare / totalAssets(); - } - /** - * @notice Converts an amount of shares to the equivalent amount of assets. - * @param shares Amount of vault shares. - * @return Amount of asset tokens equivalent to `shares`. - */ - function convertToAssets(uint256 shares) public view returns (uint256) { - uint256 totalShare = totalShares(); - /** - * If no shares exist, 1:1 ratio between asset and shares - */ - if (totalShare == 0) { - return shares; + require(prod1 < denominator); + + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) + } + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) } - return shares * totalAssets() / totalShare; - } - /** - * @notice Returns the amount of shares that would be minted for a deposit of `assets`. - * @param assets Amount of assets to deposit. - * @return shares Amount of shares previewed. - */ - function previewDeposit(uint256 assets) public view returns (uint256) { - return convertToShares(assets); - } + uint256 twos = (~denominator + 1) & denominator; + assembly { + denominator := div(denominator, twos) + } - /** - * @notice Returns the amount of assets required for a mint of `shares`. - * @param shares Amount of shares to mint. - * @return assets Amount of assets previewed. - */ - function previewMint(uint256 shares) public view returns (uint256) { - uint256 totalShare = totalShares(); - uint256 totalAsset = totalAssets(); - if (totalShare == 0) { - return shares; + assembly { + prod0 := div(prod0, twos) } - /** - * Rounds up the result - */ - return (shares * totalAsset + totalShare - 1) / totalShare; + assembly { + twos := add(div(sub(0, twos), twos), 1) + } + prod0 |= prod1 * twos; + + uint256 inv = (3 * denominator) ^ 2; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + + result = prod0 * inv; + return result; } - /** - * @notice Returns the number of shares needed to withdraw `assets` assets. - * @param assets Amount of assets to withdraw. - * @return shares Number of shares required. - */ - function previewWithdraw(uint256 assets) public view returns (uint256) { - uint256 totalShare = totalShares(); - uint256 totalAsset = totalAssets(); - if (totalShare == 0) { - return assets; + function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { + result = muldiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; } - /** - * Rounds up the result - */ - return (assets * totalShare + totalAsset - 1) / totalAsset; } - /** - * @notice Returns the amount of assets redeemed for a given amount of shares. - * @param shares Amount of shares to redeem. - * @return assets Amount of assets previewed. - */ - function previewRedeem(uint256 shares) public view returns (uint256) { - return convertToAssets(shares); + function realTotalShares() external view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.totalSupply; } - /** - * @notice Returns the maximum amount of assets that can be deposited for `receiver`. - * @param receiver Address of the receiver (ignored, always max). - * @return max Maximum deposit amount allowed. - */ - function maxDeposit(address receiver) public view returns (uint256) { - return type(uint256).max; + function totalShares() external view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.totalSupply + VIRTUAL_SHARE; } - /** - * @notice Returns the maximum number of shares that can be minted for `receiver`. - * @param receiver Address of the receiver (ignored, always max). - * @return max Maximum mint amount allowed. - */ - function maxMint(address receiver) public view returns (uint256) { - return type(uint256).max; + function convertToShares(uint256 assets) external view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + + return muldiv(totalShares_, assets, totalAssets_); } - /** - * @notice Returns the maximum amount of assets that can be withdrawn for `owner`. - * @param owner The address to query. - * @return max Maximum amount of assets withdrawable. - */ - function maxWithdraw(address owner) public view returns (uint256) { - return previewRedeem(maxRedeem(owner)); + function convertToAssets(uint256 shares) external view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + + return muldiv(totalAssets_, shares, totalShares_); } - /** - * @notice Returns the maximum number of shares that can be redeemed for `owner`. - * @param owner The address to query. - * @return max Maximum shares redeemable (equal to owner's balance). - */ - function maxRedeem(address owner) public view returns (uint256) { - return balanceOf(owner); + function maxDeposit() external pure returns (uint256) { + return type(uint256).max; } - /** - * @notice Deposits asset tokens and mints corresponding shares to `receiver`. - * @param assets Amount of asset tokens to deposit. - * @param receiver Address to receive minted shares. - * @return shares Amount of shares minted. - */ - function deposit(uint256 assets, address receiver) public returns (uint256) { - uint256 maxAssets = maxDeposit(receiver); - if (assets > maxAssets) { - revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); - } - uint256 shares = previewDeposit(assets); - if (shares == 0) { - revert ERC4626ZeroAmount(shares); - } + function previewDeposit(uint256 assets) external view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + return muldiv(totalShares_, assets, totalAssets_); + } + + function deposit(uint256 assets, address receiver) external returns (uint256) { if (receiver == address(0)) { - revert ERC4626ZeroAddress(receiver); + revert ERC4626InvalidAddress(); + } + + if (assets > type(uint256).max) { + revert ERC4626InvalidAmount(); } + ERC20Storage storage erc20s = getERC20Storage(); - if (!getStorage().asset.transferFrom(msg.sender, address(this), assets)) { - revert ERC4626TransferFailed(msg.sender, address(this), assets); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + + uint256 shares; + if (totalShares_ == 0 || totalAssets_ == 0) { + shares = assets; + } else { + shares = muldiv(totalShares_, assets, totalAssets_); + } + + if (shares == 0) { + revert ERC4626InsufficientShares(); } + + bool success = s.asset.transferFrom(msg.sender, address(this), assets); + if (!success) { + revert ERC4626TransferFailed(); + } + erc20s.totalSupply += shares; erc20s.balanceOf[receiver] += shares; - emit Deposit(msg.sender, receiver, assets, shares); + emit Deposit(msg.sender, receiver, assets, shares); return shares; } - /** - * @notice Mints `shares` vault shares to `receiver` by transferring corresponding asset amount from caller. - * @param shares Amount of shares to mint. - * @param receiver Address to receive minted shares. - * @return assets Amount of assets transferred from caller. - */ - function mint(uint256 shares, address receiver) public returns (uint256) { - uint256 maxShares = maxMint(receiver); - if (shares > maxShares) { - revert ERC4626ExceededMaxMint(receiver, shares, maxShares); - } - uint256 assets = previewMint(shares); - if (assets == 0) { - revert ERC4626ZeroAmount(assets); + function maxMint() external pure returns (uint256) { + return type(uint256).max; + } + + function previewMint(uint256 shares) external view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + + if (totalAssets_ == 0 || totalShares_ == 0) { + return shares; } + return mulDivRoundingUp(totalAssets_, shares, totalShares_); + } + function mint(uint256 shares, address receiver) external returns (uint256) { if (receiver == address(0)) { - revert ERC4626ZeroAddress(receiver); + revert ERC4626InvalidAddress(); + } + + if (shares > type(uint256).max) { + revert ERC4626InvalidAmount(); } + ERC20Storage storage erc20s = getERC20Storage(); - if (!getStorage().asset.transferFrom(msg.sender, address(this), assets)) { - revert ERC4626TransferFailed(msg.sender, address(this), assets); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + + uint256 assets; + if (totalAssets_ == 0 || totalShares_ == 0) { + assets = shares; + } else { + assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); + } + + if (assets == 0) { + revert ERC4626InsufficientAssets(); + } + + bool success = s.asset.transferFrom(msg.sender, address(this), assets); + if (!success) { + revert ERC4626TransferFailed(); } + erc20s.totalSupply += shares; erc20s.balanceOf[receiver] += shares; - emit Deposit(msg.sender, receiver, assets, shares); + emit Deposit(msg.sender, receiver, assets, shares); return assets; } - /** - * @notice Burns shares from `owner` and transfers corresponding assets to `receiver`. - * @param assets Amount of assets to withdraw. - * @param receiver Address receiving the withdrawn assets. - * @param owner The address whose shares are burned. - * @return shares Amount of shares burned. - */ - function withdraw(uint256 assets, address receiver, address owner) public returns (uint256) { - uint256 maxAssets = maxWithdraw(owner); - if (assets > maxAssets) { - revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); - } - uint256 shares = previewWithdraw(assets); - if (shares == 0) { - revert ERC4626ZeroAmount(shares); + function maxWithdraw(address owner) external view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 balance = erc20s.balanceOf[owner]; + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + return muldiv(totalAssets_, balance, totalShares_); + } + + function previewWithdraw(uint256 assets) external view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + + if (totalAssets_ == 0 || totalShares_ == 0) { + return assets; + } + return mulDivRoundingUp(totalShares_, assets, totalAssets_); + } + + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256) { + if (receiver == address(0) || owner == address(0)) { + revert ERC4626InvalidAddress(); } ERC20Storage storage erc20s = getERC20Storage(); + uint256 balance = erc20s.balanceOf[owner]; + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + + uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); + if (assets > maxWithdrawVal) { + revert ERC4626InvalidAmount(); + } + + uint256 shares; + if (totalAssets_ == 0 || totalShares_ == 0) { + shares = assets; + } else { + shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); + } + + if (shares == 0) { + revert ERC4626InsufficientShares(); + } + + bool success = s.asset.transfer(receiver, assets); + if (!success) { + revert ERC4626TransferFailed(); + } if (msg.sender != owner) { uint256 allowed = erc20s.allowances[owner][msg.sender]; if (allowed < shares) { - revert ERC4626InsufficientAllowance(owner, msg.sender, allowed, shares); - } - if (allowed != type(uint256).max) { - erc20s.allowances[owner][msg.sender] = allowed - shares; + revert ERC4626InvalidAmount(); } + erc20s.allowances[owner][msg.sender] = allowed - shares; } - if (receiver == address(0)) { - revert ERC4626ZeroAddress(receiver); - } - erc20s.balanceOf[owner] -= shares; erc20s.totalSupply -= shares; - emit Transfer(owner, address(0), shares); + emit Withdraw(msg.sender, receiver, owner, assets, shares); + return shares; + } - if (!getStorage().asset.transfer(receiver, assets)) { - revert ERC4626TransferFailed(address(this), receiver, assets); - } + function maxRedeem(address owner) external view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.balanceOf[owner]; + } - emit Withdraw(msg.sender, receiver, owner, assets, shares); + function previewRedeem(uint256 shares) external view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; - return shares; + if (totalAssets_ == 0 || totalShares_ == 0) { + return shares; + } + return muldiv(totalAssets_, shares, totalShares_); } - /** - * @notice Redeems `shares` from `owner` and transfers corresponding assets to `receiver`. - * @param shares Amount of shares to redeem. - * @param receiver Address to receive redeemed assets. - * @param owner Address whose shares are redeemed. - * @return assets Amount of assets transferred to receiver. - */ - function redeem(uint256 shares, address receiver, address owner) public returns (uint256) { - uint256 maxShares = maxRedeem(owner); - if (shares > maxShares) { - revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); - } - uint256 assets = previewRedeem(shares); - if (assets == 0) { - revert ERC4626ZeroAmount(assets); + function redeem(uint256 shares, address receiver, address owner) external returns (uint256) { + if (receiver == address(0) || owner == address(0)) { + revert ERC4626InvalidAddress(); } ERC20Storage storage erc20s = getERC20Storage(); - if (msg.sender != owner) { - uint256 allowed = erc20s.allowances[owner][msg.sender]; - if (allowed < shares) { - revert ERC4626InsufficientAllowance(owner, msg.sender, allowed, shares); - } - if (allowed != type(uint256).max) { - erc20s.allowances[owner][msg.sender] = allowed - shares; - } + if (shares > erc20s.balanceOf[owner]) { + revert ERC4626InvalidAmount(); } - if (receiver == address(0)) { - revert ERC4626ZeroAddress(receiver); + + ERC4626Storage storage s = getStorage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + uint256 realAssets = s.asset.balanceOf(address(this)); + uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 assets; + + if (totalAssets_ == 0 || totalShares_ == 0) { + assets = shares; + } else { + assets = muldiv(totalAssets_, shares, totalShares_); } - erc20s.balanceOf[owner] -= shares; - erc20s.totalSupply -= shares; + if (assets == 0) { + revert ERC4626InsufficientAssets(); + } - emit Transfer(owner, address(0), shares); + bool success = s.asset.transfer(receiver, assets); + if (!success) { + revert ERC4626TransferFailed(); + } - if (!getStorage().asset.transfer(receiver, assets)) { - revert ERC4626TransferFailed(address(this), receiver, assets); + if (msg.sender != owner) { + uint256 allowed = erc20s.allowances[owner][msg.sender]; + if (allowed < shares) { + revert ERC4626InvalidAmount(); + } + erc20s.allowances[owner][msg.sender] = allowed - shares; } + erc20s.totalSupply -= shares; + erc20s.balanceOf[owner] -= shares; emit Withdraw(msg.sender, receiver, owner, assets, shares); - return assets; } } diff --git a/src/token/ERC20/ERC4626/LibERC4626.sol b/src/token/ERC20/ERC4626/LibERC4626.sol deleted file mode 100644 index 7ab2517f..00000000 --- a/src/token/ERC20/ERC4626/LibERC4626.sol +++ /dev/null @@ -1,494 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/** - * @title Minimal IERC20 interface - * @notice Minimal interface to interact with ERC20 tokens - */ -interface IERC20 { - /** - * @notice Returns the total supply of the token. - */ - function totalSupply() external view returns (uint256); - - /** - * @notice Returns the balance of a given account. - * @param account The account address to query. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * @notice Transfers tokens to a specified address. - * @param to The recipient address. - * @param amount The amount of tokens to transfer. - * @return success True if transfer succeeded, false otherwise. - */ - function transfer(address to, uint256 amount) external returns (bool); - - /** - * @notice Returns the allowance granted to a spender by the owner. - * @param owner The owner's address. - * @param spender The spender's address. - */ - function allowance(address owner, address spender) external view returns (uint256); - - /** - * @notice Approves a spender to spend a given amount. - * @param spender The spender address. - * @param amount The allowance amount. - * @return success True if approval succeeded, false otherwise. - */ - function approve(address spender, uint256 amount) external returns (bool); - - /** - * @notice Transfers tokens from one address to another using allowance. - * @param from The sender address. - * @param to The recipient address. - * @param amount The amount to transfer. - * @return success True if transfer succeeded, false otherwise. - */ - function transferFrom(address from, address to, uint256 amount) external returns (bool); -} - -/** - * @title ERC4626Facet - * @notice Implementation of the ERC-4626 Tokenized Vault standard - */ -contract ERC4626Facet { - /** - * @notice Emitted after a deposit of `assets` and minting of `shares` - * @param caller Address making the deposit call - * @param owner Receives the minted shares - * @param assets Amount of asset tokens deposited - * @param shares Amount of vault shares minted - */ - event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); - - /** - * @notice Emitted after a withdrawal or redemption of `assets` and burning of `shares` - * @param caller Address making the withdrawal call - * @param receiver Receives the withdrawn assets - * @param owner Owner of the withdrawn assets and burned shares - * @param assets Amount of asset tokens withdrawn - * @param shares Amount of vault shares burned - */ - event Withdraw( - address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares - ); - - /** - * @notice Emitted when vault shares are transferred (including minting/burning) - * @param from Sender address - * @param to Recipient address (zero address means burn) - * @param value Amount of shares transferred - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @notice Reverts when the deposit exceeds maximum allowed assets for receiver - */ - error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); - - /** - * @notice Reverts when the mint exceeds maximum allowed shares for receiver - */ - error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); - - /** - * @notice Reverts when the withdraw exceeds maximum allowed assets for owner - */ - error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); - - /** - * @notice Reverts when the redeem exceeds maximum allowed shares for owner - */ - error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); - - /** - * @notice Reverts when allowance is insufficient for withdraw/redeem - */ - error ERC4626InsufficientAllowance(address owner, address caller, uint256 allowed, uint256 required); - - /** - * @notice Reverts when an asset transfer fails - */ - error ERC4626TransferFailed(address from, address to, uint256 amount); - - /** - * @notice Reverts when zero amount is involved where not valid - */ - error ERC4626ZeroAmount(uint256 amount); - - /** - * @notice Reverts on zero address input where not valid - */ - error ERC4626ZeroAddress(address addr); - - /** - * @notice Storage slot used for ERC4626 data - */ - bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); - - /** - * @notice Storage slot used for ERC20 data (shares) - */ - bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); - - /** - * @notice ERC20 share vault storage - */ - struct ERC20Storage { - mapping(address owner => uint256 balance) balanceOf; - uint256 totalSupply; - mapping(address owner => mapping(address spender => uint256 allowance)) allowances; - uint8 decimals; - string name; - string symbol; - } - - /** - * @notice Storage containing vault's asset - */ - struct ERC4626Storage { - IERC20 asset; - } - - /** - * @notice Returns storage struct for the ERC4626 position - * @return s The storage reference for ERC4626Storage - */ - function getStorage() internal pure returns (ERC4626Storage storage s) { - bytes32 position = STORAGE_POSITION; - assembly { - s.slot := position - } - } - - /** - * @notice Returns storage struct for ERC20 shares - * @return s The storage reference for ERC20Storage - */ - function getERC20Storage() internal pure returns (ERC20Storage storage s) { - bytes32 position = ERC20_STORAGE_POSITION; - assembly { - s.slot := position - } - } - - /** - * @notice The address of the asset managed by the vault. - * @return The asset token address. - */ - function asset() public view returns (address) { - return address(getStorage().asset); - } - - /** - * @notice Returns the total amount of the underlying asset managed by the vault. - * @return The total assets held by the vault. - */ - function totalAssets() public view returns (uint256) { - return getStorage().asset.balanceOf(address(this)); - } - - /** - * @notice Returns the number of decimals of the vault share token. - * @return Number of decimals. - */ - function decimals() public view returns (uint8) { - ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.decimals; - } - - /** - * @notice Returns the share balance of an account. - * @param account The address to query. - * @return The balance of shares owned by `account`. - */ - function balanceOf(address account) public view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.balanceOf[account]; - } - - /** - * @notice Returns the total supply of vault shares. - * @return The total shares (ERC20 tokens) in existence. - */ - function totalShares() public view returns (uint256) { - ERC20Storage storage s = getERC20Storage(); - return s.totalSupply; - } - - /** - * @notice Converts an amount of assets to the equivalent amount of shares. - * @param assets Amount of asset tokens. - * @return The computed amount of shares for `assets`. - */ - function convertToShares(uint256 assets) public view returns (uint256) { - uint256 totalShare = totalShares(); - /** - * If no shares exist, 1:1 ratio between asset and shares - */ - if (totalShare == 0) { - return assets; - } - return assets * totalShare / totalAssets(); - } - - /** - * @notice Converts an amount of shares to the equivalent amount of assets. - * @param shares Amount of vault shares. - * @return Amount of asset tokens equivalent to `shares`. - */ - function convertToAssets(uint256 shares) public view returns (uint256) { - uint256 totalShare = totalShares(); - /** - * If no shares exist, 1:1 ratio between asset and shares - */ - if (totalShare == 0) { - return shares; - } - return shares * totalAssets() / totalShare; - } - - /** - * @notice Returns the amount of shares that would be minted for a deposit of `assets`. - * @param assets Amount of assets to deposit. - * @return shares Amount of shares previewed. - */ - function previewDeposit(uint256 assets) public view returns (uint256) { - return convertToShares(assets); - } - - /** - * @notice Returns the amount of assets required for a mint of `shares`. - * @param shares Amount of shares to mint. - * @return assets Amount of assets previewed. - */ - function previewMint(uint256 shares) public view returns (uint256) { - uint256 totalShare = totalShares(); - uint256 totalAsset = totalAssets(); - if (totalShare == 0) { - return shares; - } - - /** - * Rounds up the result - */ - return (shares * totalAsset + totalShare - 1) / totalShare; - } - - /** - * @notice Returns the number of shares needed to withdraw `assets` assets. - * @param assets Amount of assets to withdraw. - * @return shares Number of shares required. - */ - function previewWithdraw(uint256 assets) public view returns (uint256) { - uint256 totalShare = totalShares(); - uint256 totalAsset = totalAssets(); - if (totalShare == 0) { - return assets; - } - /** - * Rounds up the result - */ - return (assets * totalShare + totalAsset - 1) / totalAsset; - } - - /** - * @notice Returns the amount of assets redeemed for a given amount of shares. - * @param shares Amount of shares to redeem. - * @return assets Amount of assets previewed. - */ - function previewRedeem(uint256 shares) public view returns (uint256) { - return convertToAssets(shares); - } - - /** - * @notice Returns the maximum amount of assets that can be deposited for `receiver`. - * @param receiver Address of the receiver (ignored, always max). - * @return max Maximum deposit amount allowed. - */ - function maxDeposit(address receiver) public view returns (uint256) { - return type(uint256).max; - } - - /** - * @notice Returns the maximum number of shares that can be minted for `receiver`. - * @param receiver Address of the receiver (ignored, always max). - * @return max Maximum mint amount allowed. - */ - function maxMint(address receiver) public view returns (uint256) { - return type(uint256).max; - } - - /** - * @notice Returns the maximum amount of assets that can be withdrawn for `owner`. - * @param owner The address to query. - * @return max Maximum amount of assets withdrawable. - */ - function maxWithdraw(address owner) public view returns (uint256) { - return previewRedeem(maxRedeem(owner)); - } - - /** - * @notice Returns the maximum number of shares that can be redeemed for `owner`. - * @param owner The address to query. - * @return max Maximum shares redeemable (equal to owner's balance). - */ - function maxRedeem(address owner) public view returns (uint256) { - return balanceOf(owner); - } - - /** - * @notice Deposits asset tokens and mints corresponding shares to `receiver`. - * @param assets Amount of asset tokens to deposit. - * @param receiver Address to receive minted shares. - * @return shares Amount of shares minted. - */ - function deposit(uint256 assets, address receiver) public returns (uint256) { - uint256 maxAssets = maxDeposit(receiver); - if (assets > maxAssets) { - revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); - } - uint256 shares = previewDeposit(assets); - if (shares == 0) { - revert ERC4626ZeroAmount(shares); - } - - if (receiver == address(0)) { - revert ERC4626ZeroAddress(receiver); - } - ERC20Storage storage erc20s = getERC20Storage(); - if (!getStorage().asset.transferFrom(msg.sender, address(this), assets)) { - revert ERC4626TransferFailed(msg.sender, address(this), assets); - } - erc20s.totalSupply += shares; - erc20s.balanceOf[receiver] += shares; - emit Deposit(msg.sender, receiver, assets, shares); - - return shares; - } - - /** - * @notice Mints `shares` vault shares to `receiver` by transferring corresponding asset amount from caller. - * @param shares Amount of shares to mint. - * @param receiver Address to receive minted shares. - * @return assets Amount of assets transferred from caller. - */ - function mint(uint256 shares, address receiver) public returns (uint256) { - uint256 maxShares = maxMint(receiver); - if (shares > maxShares) { - revert ERC4626ExceededMaxMint(receiver, shares, maxShares); - } - uint256 assets = previewMint(shares); - if (assets == 0) { - revert ERC4626ZeroAmount(assets); - } - - if (receiver == address(0)) { - revert ERC4626ZeroAddress(receiver); - } - ERC20Storage storage erc20s = getERC20Storage(); - if (!getStorage().asset.transferFrom(msg.sender, address(this), assets)) { - revert ERC4626TransferFailed(msg.sender, address(this), assets); - } - erc20s.totalSupply += shares; - erc20s.balanceOf[receiver] += shares; - emit Deposit(msg.sender, receiver, assets, shares); - - return assets; - } - - /** - * @notice Burns shares from `owner` and transfers corresponding assets to `receiver`. - * @param assets Amount of assets to withdraw. - * @param receiver Address receiving the withdrawn assets. - * @param owner The address whose shares are burned. - * @return shares Amount of shares burned. - */ - function withdraw(uint256 assets, address receiver, address owner) public returns (uint256) { - uint256 maxAssets = maxWithdraw(owner); - if (assets > maxAssets) { - revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); - } - uint256 shares = previewWithdraw(assets); - if (shares == 0) { - revert ERC4626ZeroAmount(shares); - } - - ERC20Storage storage erc20s = getERC20Storage(); - - if (msg.sender != owner) { - uint256 allowed = erc20s.allowances[owner][msg.sender]; - if (allowed < shares) { - revert ERC4626InsufficientAllowance(owner, msg.sender, allowed, shares); - } - if (allowed != type(uint256).max) { - erc20s.allowances[owner][msg.sender] = allowed - shares; - } - } - if (receiver == address(0)) { - revert ERC4626ZeroAddress(receiver); - } - - erc20s.balanceOf[owner] -= shares; - erc20s.totalSupply -= shares; - - emit Transfer(owner, address(0), shares); - - if (!getStorage().asset.transfer(receiver, assets)) { - revert ERC4626TransferFailed(address(this), receiver, assets); - } - - emit Withdraw(msg.sender, receiver, owner, assets, shares); - - return shares; - } - - /** - * @notice Redeems `shares` from `owner` and transfers corresponding assets to `receiver`. - * @param shares Amount of shares to redeem. - * @param receiver Address to receive redeemed assets. - * @param owner Address whose shares are redeemed. - * @return assets Amount of assets transferred to receiver. - */ - function redeem(uint256 shares, address receiver, address owner) public returns (uint256) { - uint256 maxShares = maxRedeem(owner); - if (shares > maxShares) { - revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); - } - uint256 assets = previewRedeem(shares); - if (assets == 0) { - revert ERC4626ZeroAmount(assets); - } - - ERC20Storage storage erc20s = getERC20Storage(); - - if (msg.sender != owner) { - uint256 allowed = erc20s.allowances[owner][msg.sender]; - if (allowed < shares) { - revert ERC4626InsufficientAllowance(owner, msg.sender, allowed, shares); - } - if (allowed != type(uint256).max) { - erc20s.allowances[owner][msg.sender] = allowed - shares; - } - } - if (receiver == address(0)) { - revert ERC4626ZeroAddress(receiver); - } - - erc20s.balanceOf[owner] -= shares; - erc20s.totalSupply -= shares; - - emit Transfer(owner, address(0), shares); - - if (!getStorage().asset.transfer(receiver, assets)) { - revert ERC4626TransferFailed(address(this), receiver, assets); - } - - emit Withdraw(msg.sender, receiver, owner, assets, shares); - - return assets; - } -} From 276fb87473f9daccf7f0d38927c0300f6203b9cb Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 10 Dec 2025 13:02:58 +0530 Subject: [PATCH 11/25] fix stack too deep error --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 83 ++++++------------------ 1 file changed, 19 insertions(+), 64 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 985ffbea..0a07b5c1 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -65,15 +65,9 @@ contract ERC4626Facet { return address(s.asset); } - function realTotalAssets() external view returns (uint256) { - ERC4626Storage storage s = getStorage(); - return s.asset.balanceOf(address(this)); - } - function totalAssets() external view returns (uint256) { ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - return realAssets + VIRTUAL_ASSET; + return s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; } function muldiv(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { @@ -139,11 +133,6 @@ contract ERC4626Facet { } } - function realTotalShares() external view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.totalSupply; - } - function totalShares() external view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); return erc20s.totalSupply + VIRTUAL_SHARE; @@ -154,8 +143,7 @@ contract ERC4626Facet { uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -164,8 +152,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } @@ -178,8 +165,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -196,15 +182,11 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 shares; - if (totalShares_ == 0 || totalAssets_ == 0) { - shares = assets; - } else { - shares = muldiv(totalShares_, assets, totalAssets_); - } + + shares = muldiv(totalShares_, assets, totalAssets_); if (shares == 0) { revert ERC4626InsufficientShares(); @@ -230,12 +212,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; - - if (totalAssets_ == 0 || totalShares_ == 0) { - return shares; - } + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; return mulDivRoundingUp(totalAssets_, shares, totalShares_); } @@ -251,15 +228,11 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 assets; - if (totalAssets_ == 0 || totalShares_ == 0) { - assets = shares; - } else { - assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); - } + + assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); if (assets == 0) { revert ERC4626InsufficientAssets(); @@ -281,8 +254,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; return muldiv(totalAssets_, balance, totalShares_); } @@ -291,12 +263,8 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - if (totalAssets_ == 0 || totalShares_ == 0) { - return assets; - } return mulDivRoundingUp(totalShares_, assets, totalAssets_); } @@ -308,8 +276,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); @@ -318,11 +285,8 @@ contract ERC4626Facet { } uint256 shares; - if (totalAssets_ == 0 || totalShares_ == 0) { - shares = assets; - } else { - shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); - } + + shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); if (shares == 0) { revert ERC4626InsufficientShares(); @@ -356,12 +320,8 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - if (totalAssets_ == 0 || totalShares_ == 0) { - return shares; - } return muldiv(totalAssets_, shares, totalShares_); } @@ -378,15 +338,10 @@ contract ERC4626Facet { ERC4626Storage storage s = getStorage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - uint256 realAssets = s.asset.balanceOf(address(this)); - uint256 totalAssets_ = realAssets + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 assets; - if (totalAssets_ == 0 || totalShares_ == 0) { - assets = shares; - } else { - assets = muldiv(totalAssets_, shares, totalShares_); - } + assets = muldiv(totalAssets_, shares, totalShares_); if (assets == 0) { revert ERC4626InsufficientAssets(); From 45441ac72ea9bc3a8af05fa2c4324c1f2009d434 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 10 Dec 2025 23:05:37 +0530 Subject: [PATCH 12/25] added comments --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 300 +++++++++++++++-------- 1 file changed, 196 insertions(+), 104 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 0a07b5c1..52e423a0 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -1,20 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.30; +/** + * @dev Minimal ERC20 interface. + */ interface IERC20 { function totalSupply() external view returns (uint256); - function balanceOf(address account) external view returns (uint256); - function transfer(address to, uint256 amount) external returns (bool); - function allowance(address owner, address spender) external view returns (uint256); - function approve(address spender, uint256 amount) external returns (bool); - function transferFrom(address from, address to, uint256 amount) external returns (bool); } +/** + * @title ERC4626 Vault Facet + * @notice Composable vault logic based on the ERC4626 tokenized vault standard, + * with storage, conversion logic, and safe math handling. + */ contract ERC4626Facet { error ERC4626InvalidAmount(); error ERC4626InvalidAddress(); @@ -22,7 +25,14 @@ contract ERC4626Facet { error ERC4626InsufficientShares(); error ERC4626InsufficientAssets(); + /** + * @notice Emitted when assets are deposited and shares issued. + */ event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + /** + * @notice Emitted when assets are withdrawn and shares burned. + */ event Withdraw( address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); @@ -33,6 +43,9 @@ contract ERC4626Facet { uint256 constant VIRTUAL_ASSET = 1; uint256 constant VIRTUAL_SHARE = 1; + /** + * @dev ERC20 storage layout. + */ struct ERC20Storage { mapping(address => uint256) balanceOf; uint256 totalSupply; @@ -42,10 +55,16 @@ contract ERC4626Facet { string symbol; } + /** + * @dev ERC4626-specific storage layout. + */ struct ERC4626Storage { IERC20 asset; } + /** + * @dev Returns the ERC20 storage struct. + */ function getERC20Storage() internal pure returns (ERC20Storage storage s) { bytes32 position = ERC20_STORAGE_POSITION; assembly { @@ -53,6 +72,9 @@ contract ERC4626Facet { } } + /** + * @dev Returns the ERC4626 storage struct. + */ function getStorage() internal pure returns (ERC4626Storage storage s) { bytes32 position = STORAGE_POSITION; assembly { @@ -60,27 +82,61 @@ contract ERC4626Facet { } } + /** + * @notice Returns the address of the underlying asset. + */ function asset() external view returns (address) { ERC4626Storage storage s = getStorage(); return address(s.asset); } + /** + * @notice Returns the total amount of underlying assets in the vault. + */ function totalAssets() external view returns (uint256) { ERC4626Storage storage s = getStorage(); return s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; } + /** + * @notice Performs full precision (a * b) / denominator computation. + * @dev Inspired from - https://xn--2-umb.com/21/muldiv/ + * @dev Handles intermediate overflow using 512-bit math. + * - Computes 512-bit multiplication to detect and handle overflow. + * - If result fits in 256 bits, just divide. + * - Otherwise, adjust to make division exact, factor out powers of two, and compute inverse for precise division. + * @param a First operand. + * @param b Second operand. + * @param denominator Denominator. + * @return result The result of (a * b) / denominator. + */ function muldiv(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { + // Step 1: Safety check to prevent division by zero, which would otherwise revert require(denominator > 0); uint256 prod0; uint256 prod1; + /** + * Step 2: Calculate a 512-bit product of a and b. + * - prod0 contains the least significant 256 bits of the product (a * b % 2**256). + * - prod1 contains the most significant 256 bits. This is the "overflow" portion from 256-bit multiplication. + * - Assembly is used for efficiency. + */ assembly { - let mm := mulmod(a, b, not(0)) - prod0 := mul(a, b) + // Compute (a * b) in two parts: + // mm: full (modulo not(0)), which is 2**256 - 1 + // prod0: a * b (mod 2**256) + // prod1: (a * b - prod0)/2**256 + let mm := mulmod(a, b, not(0)) // Full-width mulmod for high bits + prod0 := mul(a, b) // Standard multiplication for low bits + // Derive prod1 using differences and underflow detection (see muldiv reference). prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } + /** + * Step 3: Shortcut if there is no overflow (the high 256 bits are zero). + * - Division fits in 256-bits, so we can safely divide. + */ if (prod1 == 0) { assembly { result := div(prod0, denominator) @@ -88,8 +144,18 @@ contract ERC4626Facet { return result; } + /** + * Step 4: Now we know (a * b) didn't fit in 256 bits (prod1 != 0), + * but it must fit into 256 *bits* after dividing by denominator. + * Check that denominator is large enough to prevent result overflow. + */ require(prod1 < denominator); + /** + * Step 5: Compute and subtract remainder from [prod1 prod0] to make the division exact. + * - Calculate the remainder of (a * b) % denominator. + * - Remove the remainder from the [prod1 prod0] 512-bit product so division will be exact. + */ uint256 remainder; assembly { remainder := mulmod(a, b, denominator) @@ -99,32 +165,53 @@ contract ERC4626Facet { prod0 := sub(prod0, remainder) } + /** + * Step 6: Remove all powers of two from the denominator, shift bits from prod0 and prod1 accordingly. + * - Find the largest power of two divisor of denominator using bitwise tricks. + * - Divide denominator by this, and also adjust prod0 and prod1 to compensate. + */ uint256 twos = (~denominator + 1) & denominator; assembly { + // Divide denominator by its largest power of two divisor. denominator := div(denominator, twos) - } - - assembly { + // Divide prod0 by the same power of two, shifting low bits right prod0 := div(prod0, twos) - } - - assembly { + // Compute 2^256 / twos, prepares for condensing the top bits: twos := add(div(sub(0, twos), twos), 1) } + + /** + * Step 7: Condense the 512 bit result into 256 bits. + * - Move the high bits (prod1) down by multiplying by (2^256 / twos) and combining. + */ prod0 |= prod1 * twos; + /** + * Step 8: Compute modular inverse of denominator to enable division modulo 2**256. + * - Newton-Raphson iterations are used to compute the inverse efficiently. + * - The result is now: prod0 * inverse(denominator) mod 2**256 is the answer. + * - Unrolling the iterations since denominator is odd here (twos were factored out). + */ uint256 inv = (3 * denominator) ^ 2; - inv *= 2 - denominator * inv; - inv *= 2 - denominator * inv; - inv *= 2 - denominator * inv; - inv *= 2 - denominator * inv; - inv *= 2 - denominator * inv; - inv *= 2 - denominator * inv; - + inv *= 2 - denominator * inv; // inverse mod 2^8 + inv *= 2 - denominator * inv; // inverse mod 2^16 + inv *= 2 - denominator * inv; // inverse mod 2^32 + inv *= 2 - denominator * inv; // inverse mod 2^64 + inv *= 2 - denominator * inv; // inverse mod 2^128 + inv *= 2 - denominator * inv; // inverse mod 2^256 + + /** + * Step 9: Multiply prod0 by the modular inverse of denominator to get the final division result. + * - Since all powers of two are removed from denominator, and all high-bits are handled, + * this multiplication cannot overflow and yields the exact solution. + */ result = prod0 * inv; return result; } + /** + * @notice Same as muldiv, but rounds up if there is a remainder. + */ function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { result = muldiv(a, b, denominator); if (mulmod(a, b, denominator) > 0) { @@ -133,69 +220,76 @@ contract ERC4626Facet { } } + /** + * @notice Returns the total supply of shares (including virtual share). + */ function totalShares() external view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); return erc20s.totalSupply + VIRTUAL_SHARE; } + /** + * @notice Converts a given amount of assets to the equivalent shares. + * @param assets Amount of assets to convert. + */ function convertToShares(uint256 assets) external view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return muldiv(totalShares_, assets, totalAssets_); } + /** + * @notice Converts a given amount of shares to the equivalent assets. + * @param shares Amount of shares to convert. + */ function convertToAssets(uint256 shares) external view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return muldiv(totalAssets_, shares, totalShares_); } + /** + * @notice Returns the maximum depositable amount. + */ function maxDeposit() external pure returns (uint256) { return type(uint256).max; } + /** + * @notice Returns the number of shares that would be received for `assets` deposited. + * @param assets Amount of assets to preview. + */ function previewDeposit(uint256 assets) external view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return muldiv(totalShares_, assets, totalAssets_); } + /** + * @notice Deposits assets and mints shares to receiver. + * @param assets Amount of assets to deposit. + * @param receiver Address to receive shares. + * @return shares Amount of shares minted. + */ function deposit(uint256 assets, address receiver) external returns (uint256) { - if (receiver == address(0)) { - revert ERC4626InvalidAddress(); - } - - if (assets > type(uint256).max) { - revert ERC4626InvalidAmount(); - } + if (receiver == address(0)) revert ERC4626InvalidAddress(); + if (assets > type(uint256).max) revert ERC4626InvalidAmount(); ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 shares = muldiv(totalShares_, assets, totalAssets_); - uint256 shares; - - shares = muldiv(totalShares_, assets, totalAssets_); - - if (shares == 0) { - revert ERC4626InsufficientShares(); - } - + if (shares == 0) revert ERC4626InsufficientShares(); bool success = s.asset.transferFrom(msg.sender, address(this), assets); - if (!success) { - revert ERC4626TransferFailed(); - } + if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; erc20s.balanceOf[receiver] += shares; @@ -204,10 +298,17 @@ contract ERC4626Facet { return shares; } + /** + * @notice Returns the maximum shares that can be minted. + */ function maxMint() external pure returns (uint256) { return type(uint256).max; } + /** + * @notice Returns the asset cost of minting `shares` shares. + * @param shares Amount of shares to preview. + */ function previewMint(uint256 shares) external view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; @@ -216,32 +317,25 @@ contract ERC4626Facet { return mulDivRoundingUp(totalAssets_, shares, totalShares_); } + /** + * @notice Mints shares to receiver by depositing assets. + * @param shares Amount of shares to mint. + * @param receiver Address to receive shares. + * @return assets Amount of assets deposited. + */ function mint(uint256 shares, address receiver) external returns (uint256) { - if (receiver == address(0)) { - revert ERC4626InvalidAddress(); - } - - if (shares > type(uint256).max) { - revert ERC4626InvalidAmount(); - } + if (receiver == address(0)) revert ERC4626InvalidAddress(); + if (shares > type(uint256).max) revert ERC4626InvalidAmount(); ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); - uint256 assets; - - assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); - - if (assets == 0) { - revert ERC4626InsufficientAssets(); - } - + if (assets == 0) revert ERC4626InsufficientAssets(); bool success = s.asset.transferFrom(msg.sender, address(this), assets); - if (!success) { - revert ERC4626TransferFailed(); - } + if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; erc20s.balanceOf[receiver] += shares; @@ -250,6 +344,10 @@ contract ERC4626Facet { return assets; } + /** + * @notice Returns the max withdrawable amount of assets for an owner. + * @param owner The address to check. + */ function maxWithdraw(address owner) external view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; @@ -259,49 +357,44 @@ contract ERC4626Facet { return muldiv(totalAssets_, balance, totalShares_); } + /** + * @notice Returns the required shares needed to withdraw a given amount of assets. + * @param assets Amount of assets to withdraw. + */ function previewWithdraw(uint256 assets) external view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return mulDivRoundingUp(totalShares_, assets, totalAssets_); } + /** + * @notice Burns shares from owner to withdraw assets to receiver. + * @param assets Amount of assets to withdraw. + * @param receiver Address to receive assets. + * @param owner Address whose shares are burned. + * @return shares Amount of shares burned. + */ function withdraw(uint256 assets, address receiver, address owner) external returns (uint256) { - if (receiver == address(0) || owner == address(0)) { - revert ERC4626InvalidAddress(); - } + if (receiver == address(0) || owner == address(0)) revert ERC4626InvalidAddress(); ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); - if (assets > maxWithdrawVal) { - revert ERC4626InvalidAmount(); - } - - uint256 shares; - - shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); - - if (shares == 0) { - revert ERC4626InsufficientShares(); - } + if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); + uint256 shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); + if (shares == 0) revert ERC4626InsufficientShares(); bool success = s.asset.transfer(receiver, assets); - if (!success) { - revert ERC4626TransferFailed(); - } + if (!success) revert ERC4626TransferFailed(); if (msg.sender != owner) { uint256 allowed = erc20s.allowances[owner][msg.sender]; - if (allowed < shares) { - revert ERC4626InvalidAmount(); - } + if (allowed < shares) revert ERC4626InvalidAmount(); erc20s.allowances[owner][msg.sender] = allowed - shares; } erc20s.balanceOf[owner] -= shares; @@ -311,52 +404,51 @@ contract ERC4626Facet { return shares; } + /** + * @notice Returns how many shares can be redeemed by an owner. + * @param owner The address to check. + */ function maxRedeem(address owner) external view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); return erc20s.balanceOf[owner]; } + /** + * @notice Returns the amount of assets that would be received for redeeming shares. + * @param shares Amount of shares to preview. + */ function previewRedeem(uint256 shares) external view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return muldiv(totalAssets_, shares, totalShares_); } + /** + * @notice Redeems shares from owner for assets to receiver. + * @param shares Amount of shares to redeem. + * @param receiver Address to receive assets. + * @param owner Address whose shares are redeemed. + * @return assets Amount of assets withdrawn. + */ function redeem(uint256 shares, address receiver, address owner) external returns (uint256) { - if (receiver == address(0) || owner == address(0)) { - revert ERC4626InvalidAddress(); - } - + if (receiver == address(0) || owner == address(0)) revert ERC4626InvalidAddress(); ERC20Storage storage erc20s = getERC20Storage(); - - if (shares > erc20s.balanceOf[owner]) { - revert ERC4626InvalidAmount(); - } + if (shares > erc20s.balanceOf[owner]) revert ERC4626InvalidAmount(); ERC4626Storage storage s = getStorage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - uint256 assets; - - assets = muldiv(totalAssets_, shares, totalShares_); - - if (assets == 0) { - revert ERC4626InsufficientAssets(); - } + uint256 assets = muldiv(totalAssets_, shares, totalShares_); + if (assets == 0) revert ERC4626InsufficientAssets(); bool success = s.asset.transfer(receiver, assets); - if (!success) { - revert ERC4626TransferFailed(); - } + if (!success) revert ERC4626TransferFailed(); if (msg.sender != owner) { uint256 allowed = erc20s.allowances[owner][msg.sender]; - if (allowed < shares) { - revert ERC4626InvalidAmount(); - } + if (allowed < shares) revert ERC4626InvalidAmount(); erc20s.allowances[owner][msg.sender] = allowed - shares; } erc20s.totalSupply -= shares; From 672d8d7dae99f0a7142bc2e3373c2947f676a235 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 10 Dec 2025 23:10:02 +0530 Subject: [PATCH 13/25] Enforced Solidity Comment Style --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 62 +++++++++++++++++------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 52e423a0..146674e3 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -111,7 +111,9 @@ contract ERC4626Facet { * @return result The result of (a * b) / denominator. */ function muldiv(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { - // Step 1: Safety check to prevent division by zero, which would otherwise revert + /** + * Step 1: Safety check to prevent division by zero, which would otherwise revert + */ require(denominator > 0); uint256 prod0; @@ -123,13 +125,21 @@ contract ERC4626Facet { * - Assembly is used for efficiency. */ assembly { - // Compute (a * b) in two parts: - // mm: full (modulo not(0)), which is 2**256 - 1 - // prod0: a * b (mod 2**256) - // prod1: (a * b - prod0)/2**256 - let mm := mulmod(a, b, not(0)) // Full-width mulmod for high bits - prod0 := mul(a, b) // Standard multiplication for low bits - // Derive prod1 using differences and underflow detection (see muldiv reference). + /** + * b) in two parts: + * mm: full (modulo not(0)), which is 2**256 - 1 + * prod0: a * b (mod 2**256) + * prod1: (a * b - prod0)/2**256 + */ + let mm := mulmod(a, b, not(0)) /** + * Full-width mulmod for high bits + */ + prod0 := mul(a, b) /** + * Standard multiplication for low bits + */ + /** + * Derive prod1 using differences and underflow detection (see muldiv reference). + */ prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } @@ -172,11 +182,17 @@ contract ERC4626Facet { */ uint256 twos = (~denominator + 1) & denominator; assembly { - // Divide denominator by its largest power of two divisor. + /** + * Divide denominator by its largest power of two divisor. + */ denominator := div(denominator, twos) - // Divide prod0 by the same power of two, shifting low bits right + /** + * Divide prod0 by the same power of two, shifting low bits right + */ prod0 := div(prod0, twos) - // Compute 2^256 / twos, prepares for condensing the top bits: + /** + * Compute 2^256 / twos, prepares for condensing the top bits: + */ twos := add(div(sub(0, twos), twos), 1) } @@ -193,12 +209,24 @@ contract ERC4626Facet { * - Unrolling the iterations since denominator is odd here (twos were factored out). */ uint256 inv = (3 * denominator) ^ 2; - inv *= 2 - denominator * inv; // inverse mod 2^8 - inv *= 2 - denominator * inv; // inverse mod 2^16 - inv *= 2 - denominator * inv; // inverse mod 2^32 - inv *= 2 - denominator * inv; // inverse mod 2^64 - inv *= 2 - denominator * inv; // inverse mod 2^128 - inv *= 2 - denominator * inv; // inverse mod 2^256 + inv *= 2 - denominator * inv; /** + * inverse mod 2^8 + */ + inv *= 2 - denominator * inv; /** + * inverse mod 2^16 + */ + inv *= 2 - denominator * inv; /** + * inverse mod 2^32 + */ + inv *= 2 - denominator * inv; /** + * inverse mod 2^64 + */ + inv *= 2 - denominator * inv; /** + * inverse mod 2^128 + */ + inv *= 2 - denominator * inv; /** + * nverse mod 2^256 + */ /** * Step 9: Multiply prod0 by the modular inverse of denominator to get the final division result. From b55919e03a0db2c8f9fb43e29da3525cb5cdbc99 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 10 Dec 2025 23:25:19 +0530 Subject: [PATCH 14/25] ERC4626Mod --- src/token/ERC20/ERC4626/ERC4626Mod.sol | 420 +++++++++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100644 src/token/ERC20/ERC4626/ERC4626Mod.sol diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol new file mode 100644 index 00000000..51381cae --- /dev/null +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/** + * @dev Simplified ERC20 interface. + */ +interface IERC20 { + function totalSupply() external view returns (uint256); + + function balanceOf(address account) external view returns (uint256); + + function transfer(address to, uint256 amount) external returns (bool); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 amount) external returns (bool); + + function transferFrom(address from, address to, uint256 amount) external returns (bool); +} + +/** + * @title ERC4626 Vault Facet + * @notice ERC4626 vault core mechanics with storage, conversions, and safe computations. + */ +contract ERC4626Facet { + error ERC4626InvalidAmount(); + error ERC4626InvalidAddress(); + error ERC4626TransferFailed(); + error ERC4626InsufficientShares(); + error ERC4626InsufficientAssets(); + + /** + * @dev Fires on deposits (assets in, shares out). + */ + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + /** + * @dev Fires on withdrawals (assets out, shares burned). + */ + event Withdraw( + address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares + ); + + bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); + bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); + + uint256 constant VIRTUAL_ASSET = 1; + uint256 constant VIRTUAL_SHARE = 1; + + /** + * @dev Storage for ERC20 logic. + */ + struct ERC20Storage { + mapping(address => uint256) balanceOf; + uint256 totalSupply; + mapping(address => mapping(address => uint256)) allowances; + uint8 decimals; + string name; + string symbol; + } + + /** + * @dev Storage for ERC4626-specific data. + */ + struct ERC4626Storage { + IERC20 asset; + } + + /** + * @dev Access ERC20Storage struct instance. + */ + function getERC20Storage() internal pure returns (ERC20Storage storage s) { + bytes32 position = ERC20_STORAGE_POSITION; + assembly { + s.slot := position + } + } + + /** + * @dev Access ERC4626Storage struct instance. + */ + function getStorage() internal pure returns (ERC4626Storage storage s) { + bytes32 position = STORAGE_POSITION; + assembly { + s.slot := position + } + } + + /** + * @dev Get the address of the asset used by the vault. + */ + function asset() internal view returns (address) { + ERC4626Storage storage s = getStorage(); + return address(s.asset); + } + + /** + * @dev Get the current total assets in the vault contract. + */ + function totalAssets() internal view returns (uint256) { + ERC4626Storage storage s = getStorage(); + return s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + } + + /** + * @dev Compute (a * b) / denominator, using full precision and safe for overflow. + * Reference: https://xn--2-umb.com/21/muldiv/ + */ + function muldiv(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { + // Guard: denominator can't be zero + require(denominator > 0); + + uint256 prod0; + uint256 prod1; + // Calculate 512-bit multiplication [prod1 prod0] = a * b + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // If high 256 bits are zero, use simple division + if (prod1 == 0) { + assembly { + result := div(prod0, denominator) + } + return result; + } + + // Ensure denominator exceeds high bits for exact division after reduction + require(prod1 < denominator); + + // Subtract the modulus to enable exact division + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) + } + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Remove factors of two from denominator and product + uint256 twos = (~denominator + 1) & denominator; + assembly { + denominator := div(denominator, twos) + prod0 := div(prod0, twos) + twos := add(div(sub(0, twos), twos), 1) + } + + // Move high bits into low using bit shifts + prod0 |= prod1 * twos; + + // Compute modular inverse for denominator using Newton-Raphson + uint256 inv = (3 * denominator) ^ 2; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + + // Complete division + result = prod0 * inv; + return result; + } + + /** + * @dev Compute muldiv and round up the result if remainder exists. + */ + function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { + result = muldiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; + } + } + + /** + * @dev Return the sum of true shares and the virtual share value. + */ + function totalShares() internal view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.totalSupply + VIRTUAL_SHARE; + } + + /** + * @dev Convert an asset amount to shares using the vault's accountings. + * @param assets The number of assets to convert to shares. + */ + function convertToShares(uint256 assets) internal view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + return muldiv(totalShares_, assets, totalAssets_); + } + + /** + * @dev Convert shares to the corresponding asset amount. + * @param shares The number of shares to convert. + */ + function convertToAssets(uint256 shares) internal view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + return muldiv(totalAssets_, shares, totalShares_); + } + + /** + * @dev Maximum possible deposit allowed for this vault. + */ + function maxDeposit() internal pure returns (uint256) { + return type(uint256).max; + } + + /** + * @dev Calculate shares to issue for a potential deposit of given assets. + * @param assets Assets input for previewing shares minted. + */ + function previewDeposit(uint256 assets) internal view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + return muldiv(totalShares_, assets, totalAssets_); + } + + /** + * @dev Effect actual deposit, minting shares for the receiver. + * @param assets Asset amount sent in. + * @param receiver Address to receive the minted shares. + * @return shares The number of shares minted as a result. + */ + function deposit(uint256 assets, address receiver) internal returns (uint256) { + if (receiver == address(0)) revert ERC4626InvalidAddress(); + if (assets > type(uint256).max) revert ERC4626InvalidAmount(); + + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 shares = muldiv(totalShares_, assets, totalAssets_); + + if (shares == 0) revert ERC4626InsufficientShares(); + bool success = s.asset.transferFrom(msg.sender, address(this), assets); + if (!success) revert ERC4626TransferFailed(); + + erc20s.totalSupply += shares; + erc20s.balanceOf[receiver] += shares; + + emit Deposit(msg.sender, receiver, assets, shares); + return shares; + } + + /** + * @dev Return the max number of shares that can be minted. + */ + function maxMint() internal pure returns (uint256) { + return type(uint256).max; + } + + /** + * @dev Preview the asset amount required for minting a number of shares. + * @param shares The desired number of shares to mint. + */ + function previewMint(uint256 shares) internal view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + return mulDivRoundingUp(totalAssets_, shares, totalShares_); + } + + /** + * @dev Mint exact shares in exchange for assets, assigning to receiver. + * @param shares Number of shares to mint. + * @param receiver Who receives these shares. + * @return assets Asset quantity paid for minting. + */ + function mint(uint256 shares, address receiver) internal returns (uint256) { + if (receiver == address(0)) revert ERC4626InvalidAddress(); + if (shares > type(uint256).max) revert ERC4626InvalidAmount(); + + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); + + if (assets == 0) revert ERC4626InsufficientAssets(); + bool success = s.asset.transferFrom(msg.sender, address(this), assets); + if (!success) revert ERC4626TransferFailed(); + + erc20s.totalSupply += shares; + erc20s.balanceOf[receiver] += shares; + + emit Deposit(msg.sender, receiver, assets, shares); + return assets; + } + + /** + * @dev Get the max asset withdrawal allowed for the given owner. + * @param owner Account address to check. + */ + function maxWithdraw(address owner) internal view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 balance = erc20s.balanceOf[owner]; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + return muldiv(totalAssets_, balance, totalShares_); + } + + /** + * @dev Preview required shares for a withdrawal of the given asset amount. + * @param assets Desired withdrawal quantity. + */ + function previewWithdraw(uint256 assets) internal view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + return mulDivRoundingUp(totalShares_, assets, totalAssets_); + } + + /** + * @dev Burn owner's shares to release assets to the given receiver address. + * @param assets Number of assets to withdraw. + * @param receiver Address to receive assets. + * @param owner The address whose shares are spent. + * @return shares Amount of shares burned. + */ + function withdraw(uint256 assets, address receiver, address owner) internal returns (uint256) { + if (receiver == address(0) || owner == address(0)) { + revert ERC4626InvalidAddress(); + } + + ERC20Storage storage erc20s = getERC20Storage(); + uint256 balance = erc20s.balanceOf[owner]; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); + if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); + + uint256 shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); + if (shares == 0) revert ERC4626InsufficientShares(); + bool success = s.asset.transfer(receiver, assets); + if (!success) revert ERC4626TransferFailed(); + + if (msg.sender != owner) { + uint256 allowed = erc20s.allowances[owner][msg.sender]; + if (allowed < shares) revert ERC4626InvalidAmount(); + erc20s.allowances[owner][msg.sender] = allowed - shares; + } + erc20s.balanceOf[owner] -= shares; + erc20s.totalSupply -= shares; + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + return shares; + } + + /** + * @dev Find how many shares can currently be redeemed by the owner. + * @param owner Whose shares are inquired. + */ + function maxRedeem(address owner) internal view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.balanceOf[owner]; + } + + /** + * @dev Show the resulting assets for redeeming the given share count. + * @param shares Share count to be redeemed. + */ + function previewRedeem(uint256 shares) internal view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + return muldiv(totalAssets_, shares, totalShares_); + } + + /** + * @dev Redeem shares from given owner, transferring assets to receiver. + * @param shares Number of shares to redeem. + * @param receiver Destination address for asset withdrawal. + * @param owner User whose shares are spent in redemption. + * @return assets Amount of assets delivered to receiver. + */ + function redeem(uint256 shares, address receiver, address owner) internal returns (uint256) { + if (receiver == address(0) || owner == address(0)) { + revert ERC4626InvalidAddress(); + } + ERC20Storage storage erc20s = getERC20Storage(); + if (shares > erc20s.balanceOf[owner]) revert ERC4626InvalidAmount(); + + ERC4626Storage storage s = getStorage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 assets = muldiv(totalAssets_, shares, totalShares_); + + if (assets == 0) revert ERC4626InsufficientAssets(); + bool success = s.asset.transfer(receiver, assets); + if (!success) revert ERC4626TransferFailed(); + + if (msg.sender != owner) { + uint256 allowed = erc20s.allowances[owner][msg.sender]; + if (allowed < shares) revert ERC4626InvalidAmount(); + erc20s.allowances[owner][msg.sender] = allowed - shares; + } + erc20s.totalSupply -= shares; + erc20s.balanceOf[owner] -= shares; + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + return assets; + } +} From dfc01cbad64a463f4fb7bfc882a5f7207d0c461d Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 10 Dec 2025 23:27:02 +0530 Subject: [PATCH 15/25] enforced comment style --- src/token/ERC20/ERC4626/ERC4626Mod.sol | 36 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index 51381cae..ceebe20a 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -107,19 +107,25 @@ contract ERC4626Facet { * Reference: https://xn--2-umb.com/21/muldiv/ */ function muldiv(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { - // Guard: denominator can't be zero + /** + * Guard: denominator can't be zero + */ require(denominator > 0); uint256 prod0; uint256 prod1; - // Calculate 512-bit multiplication [prod1 prod0] = a * b + /** + * b + */ assembly { let mm := mulmod(a, b, not(0)) prod0 := mul(a, b) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } - // If high 256 bits are zero, use simple division + /** + * If high 256 bits are zero, use simple division + */ if (prod1 == 0) { assembly { result := div(prod0, denominator) @@ -127,10 +133,14 @@ contract ERC4626Facet { return result; } - // Ensure denominator exceeds high bits for exact division after reduction + /** + * Ensure denominator exceeds high bits for exact division after reduction + */ require(prod1 < denominator); - // Subtract the modulus to enable exact division + /** + * Subtract the modulus to enable exact division + */ uint256 remainder; assembly { remainder := mulmod(a, b, denominator) @@ -140,7 +150,9 @@ contract ERC4626Facet { prod0 := sub(prod0, remainder) } - // Remove factors of two from denominator and product + /** + * Remove factors of two from denominator and product + */ uint256 twos = (~denominator + 1) & denominator; assembly { denominator := div(denominator, twos) @@ -148,10 +160,14 @@ contract ERC4626Facet { twos := add(div(sub(0, twos), twos), 1) } - // Move high bits into low using bit shifts + /** + * Move high bits into low using bit shifts + */ prod0 |= prod1 * twos; - // Compute modular inverse for denominator using Newton-Raphson + /** + * Compute modular inverse for denominator using Newton-Raphson + */ uint256 inv = (3 * denominator) ^ 2; inv *= 2 - denominator * inv; inv *= 2 - denominator * inv; @@ -160,7 +176,9 @@ contract ERC4626Facet { inv *= 2 - denominator * inv; inv *= 2 - denominator * inv; - // Complete division + /** + * Complete division + */ result = prod0 * inv; return result; } From d7762f09f5e4f5c8ae1ca8bf6e208c44acc4eac7 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Fri, 12 Dec 2025 14:47:54 +0530 Subject: [PATCH 16/25] addressed the transfer for all tokens --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 40 +++++++++++++++++++++--- src/token/ERC20/ERC4626/ERC4626Mod.sol | 40 +++++++++++++++++++++--- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 146674e3..236cc977 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -248,6 +248,38 @@ contract ERC4626Facet { } } + /** + * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. + */ + function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal returns (bool) { + bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, amount); + (bool success, bytes memory returndata) = address(token).call(data); + if (!success) return false; + if (returndata.length == 0) { + return true; + } else if (returndata.length == 32) { + return abi.decode(returndata, (bool)); + } else { + return false; + } + } + + /** + * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. + */ + function _safeTransfer(IERC20 token, address to, uint256 amount) internal returns (bool) { + bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, amount); + (bool success, bytes memory returndata) = address(token).call(data); + if (!success) return false; + if (returndata.length == 0) { + return true; + } else if (returndata.length == 32) { + return abi.decode(returndata, (bool)); + } else { + return false; + } + } + /** * @notice Returns the total supply of shares (including virtual share). */ @@ -316,7 +348,7 @@ contract ERC4626Facet { uint256 shares = muldiv(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); - bool success = s.asset.transferFrom(msg.sender, address(this), assets); + bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -362,7 +394,7 @@ contract ERC4626Facet { uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = s.asset.transferFrom(msg.sender, address(this), assets); + bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -417,7 +449,7 @@ contract ERC4626Facet { uint256 shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); - bool success = s.asset.transfer(receiver, assets); + bool success = _safeTransfer(s.asset, receiver, assets); if (!success) revert ERC4626TransferFailed(); if (msg.sender != owner) { @@ -471,7 +503,7 @@ contract ERC4626Facet { uint256 assets = muldiv(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = s.asset.transfer(receiver, assets); + bool success = _safeTransfer(s.asset, receiver, assets); if (!success) revert ERC4626TransferFailed(); if (msg.sender != owner) { diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index ceebe20a..6b37db1e 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -245,6 +245,38 @@ contract ERC4626Facet { return muldiv(totalShares_, assets, totalAssets_); } + /** + * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. + */ + function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal returns (bool) { + bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, amount); + (bool success, bytes memory returndata) = address(token).call(data); + if (!success) return false; + if (returndata.length == 0) { + return true; + } else if (returndata.length == 32) { + return abi.decode(returndata, (bool)); + } else { + return false; + } + } + + /** + * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. + */ + function _safeTransfer(IERC20 token, address to, uint256 amount) internal returns (bool) { + bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, amount); + (bool success, bytes memory returndata) = address(token).call(data); + if (!success) return false; + if (returndata.length == 0) { + return true; + } else if (returndata.length == 32) { + return abi.decode(returndata, (bool)); + } else { + return false; + } + } + /** * @dev Effect actual deposit, minting shares for the receiver. * @param assets Asset amount sent in. @@ -262,7 +294,7 @@ contract ERC4626Facet { uint256 shares = muldiv(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); - bool success = s.asset.transferFrom(msg.sender, address(this), assets); + bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -308,7 +340,7 @@ contract ERC4626Facet { uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = s.asset.transferFrom(msg.sender, address(this), assets); + bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -365,7 +397,7 @@ contract ERC4626Facet { uint256 shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); - bool success = s.asset.transfer(receiver, assets); + bool success = _safeTransfer(s.asset, receiver, assets); if (!success) revert ERC4626TransferFailed(); if (msg.sender != owner) { @@ -421,7 +453,7 @@ contract ERC4626Facet { uint256 assets = muldiv(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = s.asset.transfer(receiver, assets); + bool success = _safeTransfer(s.asset, receiver, assets); if (!success) revert ERC4626TransferFailed(); if (msg.sender != owner) { From 62b2ed6e60c8989002aa1834edb01b4a532129c3 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Fri, 12 Dec 2025 14:55:04 +0530 Subject: [PATCH 17/25] fix ci --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 2 +- src/token/ERC20/ERC4626/ERC4626Mod.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 236cc977..ae4f71e1 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -14,7 +14,7 @@ interface IERC20 { } /** - * @title ERC4626 Vault Facet + * @title ERC4626Mod * @notice Composable vault logic based on the ERC4626 tokenized vault standard, * with storage, conversion logic, and safe math handling. */ diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index 6b37db1e..2a4b2661 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -19,7 +19,7 @@ interface IERC20 { } /** - * @title ERC4626 Vault Facet + * @title ERC4626 Facet * @notice ERC4626 vault core mechanics with storage, conversions, and safe computations. */ contract ERC4626Facet { From 314e598b16a0caf9ea5fbe3585fbaababe5854fe Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 17 Dec 2025 23:25:19 +0530 Subject: [PATCH 18/25] introduced vaultAddress variable in ERC4626 struct & changed the lib to mod --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 196 +++++++++++++++++------ src/token/ERC20/ERC4626/ERC4626Mod.sol | 81 +++++----- 2 files changed, 181 insertions(+), 96 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index ae4f71e1..cd5e18fd 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -6,11 +6,23 @@ pragma solidity >=0.8.30; */ interface IERC20 { function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 amount) external returns (bool); - function allowance(address owner, address spender) external view returns (uint256); + + function allowance( + address owner, + address spender + ) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); - function transferFrom(address from, address to, uint256 amount) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); } /** @@ -28,13 +40,22 @@ contract ERC4626Facet { /** * @notice Emitted when assets are deposited and shares issued. */ - event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + event Deposit( + address indexed sender, + address indexed owner, + uint256 assets, + uint256 shares + ); /** * @notice Emitted when assets are withdrawn and shares burned. */ event Withdraw( - address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares ); bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); @@ -60,6 +81,7 @@ contract ERC4626Facet { */ struct ERC4626Storage { IERC20 asset; + address vaultAddress; } /** @@ -95,7 +117,7 @@ contract ERC4626Facet { */ function totalAssets() external view returns (uint256) { ERC4626Storage storage s = getStorage(); - return s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + return s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; } /** @@ -110,7 +132,11 @@ contract ERC4626Facet { * @param denominator Denominator. * @return result The result of (a * b) / denominator. */ - function muldiv(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { + function muldiv( + uint256 a, + uint256 b, + uint256 denominator + ) internal view returns (uint256 result) { /** * Step 1: Safety check to prevent division by zero, which would otherwise revert */ @@ -131,12 +157,14 @@ contract ERC4626Facet { * prod0: a * b (mod 2**256) * prod1: (a * b - prod0)/2**256 */ - let mm := mulmod(a, b, not(0)) /** - * Full-width mulmod for high bits - */ - prod0 := mul(a, b) /** - * Standard multiplication for low bits - */ + let mm := mulmod(a, b, not(0)) + /** + * Full-width mulmod for high bits + */ + prod0 := mul(a, b) + /** + * Standard multiplication for low bits + */ /** * Derive prod1 using differences and underflow detection (see muldiv reference). */ @@ -209,24 +237,30 @@ contract ERC4626Facet { * - Unrolling the iterations since denominator is odd here (twos were factored out). */ uint256 inv = (3 * denominator) ^ 2; - inv *= 2 - denominator * inv; /** - * inverse mod 2^8 - */ - inv *= 2 - denominator * inv; /** - * inverse mod 2^16 - */ - inv *= 2 - denominator * inv; /** - * inverse mod 2^32 - */ - inv *= 2 - denominator * inv; /** - * inverse mod 2^64 - */ - inv *= 2 - denominator * inv; /** - * inverse mod 2^128 - */ - inv *= 2 - denominator * inv; /** - * nverse mod 2^256 - */ + inv *= 2 - denominator * inv; + /** + * inverse mod 2^8 + */ + inv *= 2 - denominator * inv; + /** + * inverse mod 2^16 + */ + inv *= 2 - denominator * inv; + /** + * inverse mod 2^32 + */ + inv *= 2 - denominator * inv; + /** + * inverse mod 2^64 + */ + inv *= 2 - denominator * inv; + /** + * inverse mod 2^128 + */ + inv *= 2 - denominator * inv; + /** + * nverse mod 2^256 + */ /** * Step 9: Multiply prod0 by the modular inverse of denominator to get the final division result. @@ -240,7 +274,11 @@ contract ERC4626Facet { /** * @notice Same as muldiv, but rounds up if there is a remainder. */ - function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { + function mulDivRoundingUp( + uint256 a, + uint256 b, + uint256 denominator + ) internal view returns (uint256 result) { result = muldiv(a, b, denominator); if (mulmod(a, b, denominator) > 0) { require(result < type(uint256).max); @@ -251,8 +289,18 @@ contract ERC4626Facet { /** * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. */ - function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal returns (bool) { - bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, amount); + function _safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 amount + ) internal returns (bool) { + bytes memory data = abi.encodeWithSelector( + token.transferFrom.selector, + from, + to, + amount + ); (bool success, bytes memory returndata) = address(token).call(data); if (!success) return false; if (returndata.length == 0) { @@ -267,8 +315,16 @@ contract ERC4626Facet { /** * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. */ - function _safeTransfer(IERC20 token, address to, uint256 amount) internal returns (bool) { - bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, amount); + function _safeTransfer( + IERC20 token, + address to, + uint256 amount + ) internal returns (bool) { + bytes memory data = abi.encodeWithSelector( + token.transfer.selector, + to, + amount + ); (bool success, bytes memory returndata) = address(token).call(data); if (!success) return false; if (returndata.length == 0) { @@ -296,7 +352,8 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -308,7 +365,8 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } @@ -327,7 +385,8 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -337,18 +396,27 @@ contract ERC4626Facet { * @param receiver Address to receive shares. * @return shares Amount of shares minted. */ - function deposit(uint256 assets, address receiver) external returns (uint256) { + function deposit( + uint256 assets, + address receiver + ) external returns (uint256) { if (receiver == address(0)) revert ERC4626InvalidAddress(); if (assets > type(uint256).max) revert ERC4626InvalidAmount(); ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; uint256 shares = muldiv(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); - bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); + bool success = _safeTransferFrom( + s.asset, + msg.sender, + s.vaultAddress, + assets + ); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -373,7 +441,8 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; return mulDivRoundingUp(totalAssets_, shares, totalShares_); } @@ -390,11 +459,17 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); + bool success = _safeTransferFrom( + s.asset, + msg.sender, + s.vaultAddress, + assets + ); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -412,7 +487,8 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; return muldiv(totalAssets_, balance, totalShares_); } @@ -425,7 +501,8 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; return mulDivRoundingUp(totalShares_, assets, totalAssets_); } @@ -436,13 +513,19 @@ contract ERC4626Facet { * @param owner Address whose shares are burned. * @return shares Amount of shares burned. */ - function withdraw(uint256 assets, address receiver, address owner) external returns (uint256) { - if (receiver == address(0) || owner == address(0)) revert ERC4626InvalidAddress(); + function withdraw( + uint256 assets, + address receiver, + address owner + ) external returns (uint256) { + if (receiver == address(0) || owner == address(0)) + revert ERC4626InvalidAddress(); ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); @@ -481,7 +564,8 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } @@ -492,14 +576,20 @@ contract ERC4626Facet { * @param owner Address whose shares are redeemed. * @return assets Amount of assets withdrawn. */ - function redeem(uint256 shares, address receiver, address owner) external returns (uint256) { - if (receiver == address(0) || owner == address(0)) revert ERC4626InvalidAddress(); + function redeem( + uint256 shares, + address receiver, + address owner + ) external returns (uint256) { + if (receiver == address(0) || owner == address(0)) + revert ERC4626InvalidAddress(); ERC20Storage storage erc20s = getERC20Storage(); if (shares > erc20s.balanceOf[owner]) revert ERC4626InvalidAmount(); ERC4626Storage storage s = getStorage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + + VIRTUAL_ASSET; uint256 assets = muldiv(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index 2a4b2661..73cf61b7 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -18,11 +18,6 @@ interface IERC20 { function transferFrom(address from, address to, uint256 amount) external returns (bool); } -/** - * @title ERC4626 Facet - * @notice ERC4626 vault core mechanics with storage, conversions, and safe computations. - */ -contract ERC4626Facet { error ERC4626InvalidAmount(); error ERC4626InvalidAddress(); error ERC4626TransferFailed(); @@ -64,12 +59,13 @@ contract ERC4626Facet { */ struct ERC4626Storage { IERC20 asset; + address vaultAddress; } /** * @dev Access ERC20Storage struct instance. */ - function getERC20Storage() internal pure returns (ERC20Storage storage s) { + function getERC20Storage() pure returns (ERC20Storage storage s) { bytes32 position = ERC20_STORAGE_POSITION; assembly { s.slot := position @@ -79,7 +75,7 @@ contract ERC4626Facet { /** * @dev Access ERC4626Storage struct instance. */ - function getStorage() internal pure returns (ERC4626Storage storage s) { + function getStorage() pure returns (ERC4626Storage storage s) { bytes32 position = STORAGE_POSITION; assembly { s.slot := position @@ -89,7 +85,7 @@ contract ERC4626Facet { /** * @dev Get the address of the asset used by the vault. */ - function asset() internal view returns (address) { + function asset() view returns (address) { ERC4626Storage storage s = getStorage(); return address(s.asset); } @@ -97,16 +93,16 @@ contract ERC4626Facet { /** * @dev Get the current total assets in the vault contract. */ - function totalAssets() internal view returns (uint256) { + function totalAssets() view returns (uint256) { ERC4626Storage storage s = getStorage(); - return s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + return s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; } /** * @dev Compute (a * b) / denominator, using full precision and safe for overflow. * Reference: https://xn--2-umb.com/21/muldiv/ */ - function muldiv(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { + function muldiv(uint256 a, uint256 b, uint256 denominator) view returns (uint256 result) { /** * Guard: denominator can't be zero */ @@ -186,7 +182,7 @@ contract ERC4626Facet { /** * @dev Compute muldiv and round up the result if remainder exists. */ - function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { + function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) view returns (uint256 result) { result = muldiv(a, b, denominator); if (mulmod(a, b, denominator) > 0) { require(result < type(uint256).max); @@ -197,7 +193,7 @@ contract ERC4626Facet { /** * @dev Return the sum of true shares and the virtual share value. */ - function totalShares() internal view returns (uint256) { + function totalShares() view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); return erc20s.totalSupply + VIRTUAL_SHARE; } @@ -206,11 +202,11 @@ contract ERC4626Facet { * @dev Convert an asset amount to shares using the vault's accountings. * @param assets The number of assets to convert to shares. */ - function convertToShares(uint256 assets) internal view returns (uint256) { + function convertToShares(uint256 assets) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -218,18 +214,18 @@ contract ERC4626Facet { * @dev Convert shares to the corresponding asset amount. * @param shares The number of shares to convert. */ - function convertToAssets(uint256 shares) internal view returns (uint256) { + function convertToAssets(uint256 shares) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } /** * @dev Maximum possible deposit allowed for this vault. */ - function maxDeposit() internal pure returns (uint256) { + function maxDeposit() pure returns (uint256) { return type(uint256).max; } @@ -237,18 +233,18 @@ contract ERC4626Facet { * @dev Calculate shares to issue for a potential deposit of given assets. * @param assets Assets input for previewing shares minted. */ - function previewDeposit(uint256 assets) internal view returns (uint256) { + function previewDeposit(uint256 assets) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } /** * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. */ - function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal returns (bool) { + function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) returns (bool) { bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, amount); (bool success, bytes memory returndata) = address(token).call(data); if (!success) return false; @@ -264,7 +260,7 @@ contract ERC4626Facet { /** * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. */ - function _safeTransfer(IERC20 token, address to, uint256 amount) internal returns (bool) { + function _safeTransfer(IERC20 token, address to, uint256 amount) returns (bool) { bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, amount); (bool success, bytes memory returndata) = address(token).call(data); if (!success) return false; @@ -283,18 +279,18 @@ contract ERC4626Facet { * @param receiver Address to receive the minted shares. * @return shares The number of shares minted as a result. */ - function deposit(uint256 assets, address receiver) internal returns (uint256) { + function deposit(uint256 assets, address receiver) returns (uint256) { if (receiver == address(0)) revert ERC4626InvalidAddress(); if (assets > type(uint256).max) revert ERC4626InvalidAmount(); ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; uint256 shares = muldiv(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); - bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); + bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -307,7 +303,7 @@ contract ERC4626Facet { /** * @dev Return the max number of shares that can be minted. */ - function maxMint() internal pure returns (uint256) { + function maxMint() pure returns (uint256) { return type(uint256).max; } @@ -315,11 +311,11 @@ contract ERC4626Facet { * @dev Preview the asset amount required for minting a number of shares. * @param shares The desired number of shares to mint. */ - function previewMint(uint256 shares) internal view returns (uint256) { + function previewMint(uint256 shares) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return mulDivRoundingUp(totalAssets_, shares, totalShares_); } @@ -329,18 +325,18 @@ contract ERC4626Facet { * @param receiver Who receives these shares. * @return assets Asset quantity paid for minting. */ - function mint(uint256 shares, address receiver) internal returns (uint256) { + function mint(uint256 shares, address receiver) returns (uint256) { if (receiver == address(0)) revert ERC4626InvalidAddress(); if (shares > type(uint256).max) revert ERC4626InvalidAmount(); ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); + bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -354,11 +350,11 @@ contract ERC4626Facet { * @dev Get the max asset withdrawal allowed for the given owner. * @param owner Account address to check. */ - function maxWithdraw(address owner) internal view returns (uint256) { + function maxWithdraw(address owner) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; return muldiv(totalAssets_, balance, totalShares_); } @@ -367,11 +363,11 @@ contract ERC4626Facet { * @dev Preview required shares for a withdrawal of the given asset amount. * @param assets Desired withdrawal quantity. */ - function previewWithdraw(uint256 assets) internal view returns (uint256) { + function previewWithdraw(uint256 assets) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return mulDivRoundingUp(totalShares_, assets, totalAssets_); } @@ -382,7 +378,7 @@ contract ERC4626Facet { * @param owner The address whose shares are spent. * @return shares Amount of shares burned. */ - function withdraw(uint256 assets, address receiver, address owner) internal returns (uint256) { + function withdraw(uint256 assets, address receiver, address owner) returns (uint256) { if (receiver == address(0) || owner == address(0)) { revert ERC4626InvalidAddress(); } @@ -390,7 +386,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); @@ -416,7 +412,7 @@ contract ERC4626Facet { * @dev Find how many shares can currently be redeemed by the owner. * @param owner Whose shares are inquired. */ - function maxRedeem(address owner) internal view returns (uint256) { + function maxRedeem(address owner) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); return erc20s.balanceOf[owner]; } @@ -425,11 +421,11 @@ contract ERC4626Facet { * @dev Show the resulting assets for redeeming the given share count. * @param shares Share count to be redeemed. */ - function previewRedeem(uint256 shares) internal view returns (uint256) { + function previewRedeem(uint256 shares) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } @@ -440,7 +436,7 @@ contract ERC4626Facet { * @param owner User whose shares are spent in redemption. * @return assets Amount of assets delivered to receiver. */ - function redeem(uint256 shares, address receiver, address owner) internal returns (uint256) { + function redeem(uint256 shares, address receiver, address owner) returns (uint256) { if (receiver == address(0) || owner == address(0)) { revert ERC4626InvalidAddress(); } @@ -449,7 +445,7 @@ contract ERC4626Facet { ERC4626Storage storage s = getStorage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; uint256 assets = muldiv(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); @@ -467,4 +463,3 @@ contract ERC4626Facet { emit Withdraw(msg.sender, receiver, owner, assets, shares); return assets; } -} From 0ab962deec03c81d7d1a65f72e35269ea5377ed3 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Wed, 17 Dec 2025 23:25:32 +0530 Subject: [PATCH 19/25] fmt --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 132 +--- src/token/ERC20/ERC4626/ERC4626Mod.sol | 742 +++++++++++------------ 2 files changed, 400 insertions(+), 474 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index cd5e18fd..cbae14c2 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -11,18 +11,11 @@ interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); - function allowance( - address owner, - address spender - ) external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool); + function transferFrom(address from, address to, uint256 amount) external returns (bool); } /** @@ -40,22 +33,13 @@ contract ERC4626Facet { /** * @notice Emitted when assets are deposited and shares issued. */ - event Deposit( - address indexed sender, - address indexed owner, - uint256 assets, - uint256 shares - ); + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); /** * @notice Emitted when assets are withdrawn and shares burned. */ event Withdraw( - address indexed sender, - address indexed receiver, - address indexed owner, - uint256 assets, - uint256 shares + address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); @@ -132,11 +116,7 @@ contract ERC4626Facet { * @param denominator Denominator. * @return result The result of (a * b) / denominator. */ - function muldiv( - uint256 a, - uint256 b, - uint256 denominator - ) internal view returns (uint256 result) { + function muldiv(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { /** * Step 1: Safety check to prevent division by zero, which would otherwise revert */ @@ -274,11 +254,7 @@ contract ERC4626Facet { /** * @notice Same as muldiv, but rounds up if there is a remainder. */ - function mulDivRoundingUp( - uint256 a, - uint256 b, - uint256 denominator - ) internal view returns (uint256 result) { + function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal view returns (uint256 result) { result = muldiv(a, b, denominator); if (mulmod(a, b, denominator) > 0) { require(result < type(uint256).max); @@ -289,18 +265,8 @@ contract ERC4626Facet { /** * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. */ - function _safeTransferFrom( - IERC20 token, - address from, - address to, - uint256 amount - ) internal returns (bool) { - bytes memory data = abi.encodeWithSelector( - token.transferFrom.selector, - from, - to, - amount - ); + function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal returns (bool) { + bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, amount); (bool success, bytes memory returndata) = address(token).call(data); if (!success) return false; if (returndata.length == 0) { @@ -315,16 +281,8 @@ contract ERC4626Facet { /** * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. */ - function _safeTransfer( - IERC20 token, - address to, - uint256 amount - ) internal returns (bool) { - bytes memory data = abi.encodeWithSelector( - token.transfer.selector, - to, - amount - ); + function _safeTransfer(IERC20 token, address to, uint256 amount) internal returns (bool) { + bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, amount); (bool success, bytes memory returndata) = address(token).call(data); if (!success) return false; if (returndata.length == 0) { @@ -352,8 +310,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -365,8 +322,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } @@ -385,8 +341,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -396,27 +351,18 @@ contract ERC4626Facet { * @param receiver Address to receive shares. * @return shares Amount of shares minted. */ - function deposit( - uint256 assets, - address receiver - ) external returns (uint256) { + function deposit(uint256 assets, address receiver) external returns (uint256) { if (receiver == address(0)) revert ERC4626InvalidAddress(); if (assets > type(uint256).max) revert ERC4626InvalidAmount(); ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; uint256 shares = muldiv(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); - bool success = _safeTransferFrom( - s.asset, - msg.sender, - s.vaultAddress, - assets - ); + bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -441,8 +387,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return mulDivRoundingUp(totalAssets_, shares, totalShares_); } @@ -459,17 +404,11 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = _safeTransferFrom( - s.asset, - msg.sender, - s.vaultAddress, - assets - ); + bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -487,8 +426,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; return muldiv(totalAssets_, balance, totalShares_); } @@ -501,8 +439,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return mulDivRoundingUp(totalShares_, assets, totalAssets_); } @@ -513,19 +450,15 @@ contract ERC4626Facet { * @param owner Address whose shares are burned. * @return shares Amount of shares burned. */ - function withdraw( - uint256 assets, - address receiver, - address owner - ) external returns (uint256) { - if (receiver == address(0) || owner == address(0)) + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256) { + if (receiver == address(0) || owner == address(0)) { revert ERC4626InvalidAddress(); + } ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); @@ -564,8 +497,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } @@ -576,20 +508,16 @@ contract ERC4626Facet { * @param owner Address whose shares are redeemed. * @return assets Amount of assets withdrawn. */ - function redeem( - uint256 shares, - address receiver, - address owner - ) external returns (uint256) { - if (receiver == address(0) || owner == address(0)) + function redeem(uint256 shares, address receiver, address owner) external returns (uint256) { + if (receiver == address(0) || owner == address(0)) { revert ERC4626InvalidAddress(); + } ERC20Storage storage erc20s = getERC20Storage(); if (shares > erc20s.balanceOf[owner]) revert ERC4626InvalidAmount(); ERC4626Storage storage s = getStorage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + - VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; uint256 assets = muldiv(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index 73cf61b7..d582de45 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -18,448 +18,446 @@ interface IERC20 { function transferFrom(address from, address to, uint256 amount) external returns (bool); } - error ERC4626InvalidAmount(); - error ERC4626InvalidAddress(); - error ERC4626TransferFailed(); - error ERC4626InsufficientShares(); - error ERC4626InsufficientAssets(); +error ERC4626InvalidAmount(); +error ERC4626InvalidAddress(); +error ERC4626TransferFailed(); +error ERC4626InsufficientShares(); +error ERC4626InsufficientAssets(); - /** - * @dev Fires on deposits (assets in, shares out). - */ - event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); +/** + * @dev Fires on deposits (assets in, shares out). + */ +event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); - /** - * @dev Fires on withdrawals (assets out, shares burned). - */ - event Withdraw( - address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares - ); +/** + * @dev Fires on withdrawals (assets out, shares burned). + */ +event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); - bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); - bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); +bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); +bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); - uint256 constant VIRTUAL_ASSET = 1; - uint256 constant VIRTUAL_SHARE = 1; +uint256 constant VIRTUAL_ASSET = 1; +uint256 constant VIRTUAL_SHARE = 1; - /** - * @dev Storage for ERC20 logic. - */ - struct ERC20Storage { - mapping(address => uint256) balanceOf; - uint256 totalSupply; - mapping(address => mapping(address => uint256)) allowances; - uint8 decimals; - string name; - string symbol; - } +/** + * @dev Storage for ERC20 logic. + */ +struct ERC20Storage { + mapping(address => uint256) balanceOf; + uint256 totalSupply; + mapping(address => mapping(address => uint256)) allowances; + uint8 decimals; + string name; + string symbol; +} - /** - * @dev Storage for ERC4626-specific data. - */ - struct ERC4626Storage { - IERC20 asset; - address vaultAddress; - } +/** + * @dev Storage for ERC4626-specific data. + */ +struct ERC4626Storage { + IERC20 asset; + address vaultAddress; +} - /** - * @dev Access ERC20Storage struct instance. - */ - function getERC20Storage() pure returns (ERC20Storage storage s) { - bytes32 position = ERC20_STORAGE_POSITION; - assembly { - s.slot := position - } +/** + * @dev Access ERC20Storage struct instance. + */ +function getERC20Storage() pure returns (ERC20Storage storage s) { + bytes32 position = ERC20_STORAGE_POSITION; + assembly { + s.slot := position } +} - /** - * @dev Access ERC4626Storage struct instance. - */ - function getStorage() pure returns (ERC4626Storage storage s) { - bytes32 position = STORAGE_POSITION; - assembly { - s.slot := position - } +/** + * @dev Access ERC4626Storage struct instance. + */ +function getStorage() pure returns (ERC4626Storage storage s) { + bytes32 position = STORAGE_POSITION; + assembly { + s.slot := position } +} + +/** + * @dev Get the address of the asset used by the vault. + */ +function asset() view returns (address) { + ERC4626Storage storage s = getStorage(); + return address(s.asset); +} +/** + * @dev Get the current total assets in the vault contract. + */ +function totalAssets() view returns (uint256) { + ERC4626Storage storage s = getStorage(); + return s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; +} + +/** + * @dev Compute (a * b) / denominator, using full precision and safe for overflow. + * Reference: https://xn--2-umb.com/21/muldiv/ + */ +function muldiv(uint256 a, uint256 b, uint256 denominator) view returns (uint256 result) { /** - * @dev Get the address of the asset used by the vault. + * Guard: denominator can't be zero */ - function asset() view returns (address) { - ERC4626Storage storage s = getStorage(); - return address(s.asset); - } + require(denominator > 0); + uint256 prod0; + uint256 prod1; /** - * @dev Get the current total assets in the vault contract. + * b */ - function totalAssets() view returns (uint256) { - ERC4626Storage storage s = getStorage(); - return s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } /** - * @dev Compute (a * b) / denominator, using full precision and safe for overflow. - * Reference: https://xn--2-umb.com/21/muldiv/ + * If high 256 bits are zero, use simple division */ - function muldiv(uint256 a, uint256 b, uint256 denominator) view returns (uint256 result) { - /** - * Guard: denominator can't be zero - */ - require(denominator > 0); - - uint256 prod0; - uint256 prod1; - /** - * b - */ - assembly { - let mm := mulmod(a, b, not(0)) - prod0 := mul(a, b) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - } - - /** - * If high 256 bits are zero, use simple division - */ - if (prod1 == 0) { - assembly { - result := div(prod0, denominator) - } - return result; - } - - /** - * Ensure denominator exceeds high bits for exact division after reduction - */ - require(prod1 < denominator); - - /** - * Subtract the modulus to enable exact division - */ - uint256 remainder; + if (prod1 == 0) { assembly { - remainder := mulmod(a, b, denominator) + result := div(prod0, denominator) } - assembly { - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - /** - * Remove factors of two from denominator and product - */ - uint256 twos = (~denominator + 1) & denominator; - assembly { - denominator := div(denominator, twos) - prod0 := div(prod0, twos) - twos := add(div(sub(0, twos), twos), 1) - } - - /** - * Move high bits into low using bit shifts - */ - prod0 |= prod1 * twos; - - /** - * Compute modular inverse for denominator using Newton-Raphson - */ - uint256 inv = (3 * denominator) ^ 2; - inv *= 2 - denominator * inv; - inv *= 2 - denominator * inv; - inv *= 2 - denominator * inv; - inv *= 2 - denominator * inv; - inv *= 2 - denominator * inv; - inv *= 2 - denominator * inv; - - /** - * Complete division - */ - result = prod0 * inv; return result; } /** - * @dev Compute muldiv and round up the result if remainder exists. + * Ensure denominator exceeds high bits for exact division after reduction */ - function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) view returns (uint256 result) { - result = muldiv(a, b, denominator); - if (mulmod(a, b, denominator) > 0) { - require(result < type(uint256).max); - result++; - } - } + require(prod1 < denominator); /** - * @dev Return the sum of true shares and the virtual share value. + * Subtract the modulus to enable exact division */ - function totalShares() view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.totalSupply + VIRTUAL_SHARE; + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) } - - /** - * @dev Convert an asset amount to shares using the vault's accountings. - * @param assets The number of assets to convert to shares. - */ - function convertToShares(uint256 assets) view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - return muldiv(totalShares_, assets, totalAssets_); + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) } /** - * @dev Convert shares to the corresponding asset amount. - * @param shares The number of shares to convert. + * Remove factors of two from denominator and product */ - function convertToAssets(uint256 shares) view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - return muldiv(totalAssets_, shares, totalShares_); + uint256 twos = (~denominator + 1) & denominator; + assembly { + denominator := div(denominator, twos) + prod0 := div(prod0, twos) + twos := add(div(sub(0, twos), twos), 1) } /** - * @dev Maximum possible deposit allowed for this vault. + * Move high bits into low using bit shifts */ - function maxDeposit() pure returns (uint256) { - return type(uint256).max; - } + prod0 |= prod1 * twos; /** - * @dev Calculate shares to issue for a potential deposit of given assets. - * @param assets Assets input for previewing shares minted. + * Compute modular inverse for denominator using Newton-Raphson */ - function previewDeposit(uint256 assets) view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - return muldiv(totalShares_, assets, totalAssets_); - } + uint256 inv = (3 * denominator) ^ 2; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; + inv *= 2 - denominator * inv; /** - * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. + * Complete division */ - function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) returns (bool) { - bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, amount); - (bool success, bytes memory returndata) = address(token).call(data); - if (!success) return false; - if (returndata.length == 0) { - return true; - } else if (returndata.length == 32) { - return abi.decode(returndata, (bool)); - } else { - return false; - } - } + result = prod0 * inv; + return result; +} - /** - * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. - */ - function _safeTransfer(IERC20 token, address to, uint256 amount) returns (bool) { - bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, amount); - (bool success, bytes memory returndata) = address(token).call(data); - if (!success) return false; - if (returndata.length == 0) { - return true; - } else if (returndata.length == 32) { - return abi.decode(returndata, (bool)); - } else { - return false; - } +/** + * @dev Compute muldiv and round up the result if remainder exists. + */ +function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) view returns (uint256 result) { + result = muldiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; } +} - /** - * @dev Effect actual deposit, minting shares for the receiver. - * @param assets Asset amount sent in. - * @param receiver Address to receive the minted shares. - * @return shares The number of shares minted as a result. - */ - function deposit(uint256 assets, address receiver) returns (uint256) { - if (receiver == address(0)) revert ERC4626InvalidAddress(); - if (assets > type(uint256).max) revert ERC4626InvalidAmount(); +/** + * @dev Return the sum of true shares and the virtual share value. + */ +function totalShares() view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.totalSupply + VIRTUAL_SHARE; +} - ERC20Storage storage erc20s = getERC20Storage(); - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - uint256 shares = muldiv(totalShares_, assets, totalAssets_); +/** + * @dev Convert an asset amount to shares using the vault's accountings. + * @param assets The number of assets to convert to shares. + */ +function convertToShares(uint256 assets) view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + return muldiv(totalShares_, assets, totalAssets_); +} - if (shares == 0) revert ERC4626InsufficientShares(); - bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); - if (!success) revert ERC4626TransferFailed(); +/** + * @dev Convert shares to the corresponding asset amount. + * @param shares The number of shares to convert. + */ +function convertToAssets(uint256 shares) view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + return muldiv(totalAssets_, shares, totalShares_); +} - erc20s.totalSupply += shares; - erc20s.balanceOf[receiver] += shares; +/** + * @dev Maximum possible deposit allowed for this vault. + */ +function maxDeposit() pure returns (uint256) { + return type(uint256).max; +} - emit Deposit(msg.sender, receiver, assets, shares); - return shares; - } +/** + * @dev Calculate shares to issue for a potential deposit of given assets. + * @param assets Assets input for previewing shares minted. + */ +function previewDeposit(uint256 assets) view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + return muldiv(totalShares_, assets, totalAssets_); +} - /** - * @dev Return the max number of shares that can be minted. - */ - function maxMint() pure returns (uint256) { - return type(uint256).max; +/** + * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. + */ +function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) returns (bool) { + bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, amount); + (bool success, bytes memory returndata) = address(token).call(data); + if (!success) return false; + if (returndata.length == 0) { + return true; + } else if (returndata.length == 32) { + return abi.decode(returndata, (bool)); + } else { + return false; } +} - /** - * @dev Preview the asset amount required for minting a number of shares. - * @param shares The desired number of shares to mint. - */ - function previewMint(uint256 shares) view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - return mulDivRoundingUp(totalAssets_, shares, totalShares_); +/** + * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. + */ +function _safeTransfer(IERC20 token, address to, uint256 amount) returns (bool) { + bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, amount); + (bool success, bytes memory returndata) = address(token).call(data); + if (!success) return false; + if (returndata.length == 0) { + return true; + } else if (returndata.length == 32) { + return abi.decode(returndata, (bool)); + } else { + return false; } +} - /** - * @dev Mint exact shares in exchange for assets, assigning to receiver. - * @param shares Number of shares to mint. - * @param receiver Who receives these shares. - * @return assets Asset quantity paid for minting. - */ - function mint(uint256 shares, address receiver) returns (uint256) { - if (receiver == address(0)) revert ERC4626InvalidAddress(); - if (shares > type(uint256).max) revert ERC4626InvalidAmount(); +/** + * @dev Effect actual deposit, minting shares for the receiver. + * @param assets Asset amount sent in. + * @param receiver Address to receive the minted shares. + * @return shares The number of shares minted as a result. + */ +function deposit(uint256 assets, address receiver) returns (uint256) { + if (receiver == address(0)) revert ERC4626InvalidAddress(); + if (assets > type(uint256).max) revert ERC4626InvalidAmount(); - ERC20Storage storage erc20s = getERC20Storage(); - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 shares = muldiv(totalShares_, assets, totalAssets_); - if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); - if (!success) revert ERC4626TransferFailed(); + if (shares == 0) revert ERC4626InsufficientShares(); + bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); + if (!success) revert ERC4626TransferFailed(); - erc20s.totalSupply += shares; - erc20s.balanceOf[receiver] += shares; + erc20s.totalSupply += shares; + erc20s.balanceOf[receiver] += shares; - emit Deposit(msg.sender, receiver, assets, shares); - return assets; - } + emit Deposit(msg.sender, receiver, assets, shares); + return shares; +} - /** - * @dev Get the max asset withdrawal allowed for the given owner. - * @param owner Account address to check. - */ - function maxWithdraw(address owner) view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - uint256 balance = erc20s.balanceOf[owner]; - ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - return muldiv(totalAssets_, balance, totalShares_); - } +/** + * @dev Return the max number of shares that can be minted. + */ +function maxMint() pure returns (uint256) { + return type(uint256).max; +} - /** - * @dev Preview required shares for a withdrawal of the given asset amount. - * @param assets Desired withdrawal quantity. - */ - function previewWithdraw(uint256 assets) view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - return mulDivRoundingUp(totalShares_, assets, totalAssets_); - } +/** + * @dev Preview the asset amount required for minting a number of shares. + * @param shares The desired number of shares to mint. + */ +function previewMint(uint256 shares) view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + return mulDivRoundingUp(totalAssets_, shares, totalShares_); +} - /** - * @dev Burn owner's shares to release assets to the given receiver address. - * @param assets Number of assets to withdraw. - * @param receiver Address to receive assets. - * @param owner The address whose shares are spent. - * @return shares Amount of shares burned. - */ - function withdraw(uint256 assets, address receiver, address owner) returns (uint256) { - if (receiver == address(0) || owner == address(0)) { - revert ERC4626InvalidAddress(); - } +/** + * @dev Mint exact shares in exchange for assets, assigning to receiver. + * @param shares Number of shares to mint. + * @param receiver Who receives these shares. + * @return assets Asset quantity paid for minting. + */ +function mint(uint256 shares, address receiver) returns (uint256) { + if (receiver == address(0)) revert ERC4626InvalidAddress(); + if (shares > type(uint256).max) revert ERC4626InvalidAmount(); - ERC20Storage storage erc20s = getERC20Storage(); - uint256 balance = erc20s.balanceOf[owner]; - ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); - if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); - - uint256 shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); - if (shares == 0) revert ERC4626InsufficientShares(); - bool success = _safeTransfer(s.asset, receiver, assets); - if (!success) revert ERC4626TransferFailed(); - - if (msg.sender != owner) { - uint256 allowed = erc20s.allowances[owner][msg.sender]; - if (allowed < shares) revert ERC4626InvalidAmount(); - erc20s.allowances[owner][msg.sender] = allowed - shares; - } - erc20s.balanceOf[owner] -= shares; - erc20s.totalSupply -= shares; + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); - emit Withdraw(msg.sender, receiver, owner, assets, shares); - return shares; - } + if (assets == 0) revert ERC4626InsufficientAssets(); + bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); + if (!success) revert ERC4626TransferFailed(); - /** - * @dev Find how many shares can currently be redeemed by the owner. - * @param owner Whose shares are inquired. - */ - function maxRedeem(address owner) view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.balanceOf[owner]; + erc20s.totalSupply += shares; + erc20s.balanceOf[receiver] += shares; + + emit Deposit(msg.sender, receiver, assets, shares); + return assets; +} + +/** + * @dev Get the max asset withdrawal allowed for the given owner. + * @param owner Account address to check. + */ +function maxWithdraw(address owner) view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 balance = erc20s.balanceOf[owner]; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + return muldiv(totalAssets_, balance, totalShares_); +} + +/** + * @dev Preview required shares for a withdrawal of the given asset amount. + * @param assets Desired withdrawal quantity. + */ +function previewWithdraw(uint256 assets) view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + return mulDivRoundingUp(totalShares_, assets, totalAssets_); +} + +/** + * @dev Burn owner's shares to release assets to the given receiver address. + * @param assets Number of assets to withdraw. + * @param receiver Address to receive assets. + * @param owner The address whose shares are spent. + * @return shares Amount of shares burned. + */ +function withdraw(uint256 assets, address receiver, address owner) returns (uint256) { + if (receiver == address(0) || owner == address(0)) { + revert ERC4626InvalidAddress(); } - /** - * @dev Show the resulting assets for redeeming the given share count. - * @param shares Share count to be redeemed. - */ - function previewRedeem(uint256 shares) view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - return muldiv(totalAssets_, shares, totalShares_); + ERC20Storage storage erc20s = getERC20Storage(); + uint256 balance = erc20s.balanceOf[owner]; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); + if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); + + uint256 shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); + if (shares == 0) revert ERC4626InsufficientShares(); + bool success = _safeTransfer(s.asset, receiver, assets); + if (!success) revert ERC4626TransferFailed(); + + if (msg.sender != owner) { + uint256 allowed = erc20s.allowances[owner][msg.sender]; + if (allowed < shares) revert ERC4626InvalidAmount(); + erc20s.allowances[owner][msg.sender] = allowed - shares; } + erc20s.balanceOf[owner] -= shares; + erc20s.totalSupply -= shares; - /** - * @dev Redeem shares from given owner, transferring assets to receiver. - * @param shares Number of shares to redeem. - * @param receiver Destination address for asset withdrawal. - * @param owner User whose shares are spent in redemption. - * @return assets Amount of assets delivered to receiver. - */ - function redeem(uint256 shares, address receiver, address owner) returns (uint256) { - if (receiver == address(0) || owner == address(0)) { - revert ERC4626InvalidAddress(); - } - ERC20Storage storage erc20s = getERC20Storage(); - if (shares > erc20s.balanceOf[owner]) revert ERC4626InvalidAmount(); - - ERC4626Storage storage s = getStorage(); - uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; - uint256 assets = muldiv(totalAssets_, shares, totalShares_); - - if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = _safeTransfer(s.asset, receiver, assets); - if (!success) revert ERC4626TransferFailed(); - - if (msg.sender != owner) { - uint256 allowed = erc20s.allowances[owner][msg.sender]; - if (allowed < shares) revert ERC4626InvalidAmount(); - erc20s.allowances[owner][msg.sender] = allowed - shares; - } - erc20s.totalSupply -= shares; - erc20s.balanceOf[owner] -= shares; + emit Withdraw(msg.sender, receiver, owner, assets, shares); + return shares; +} + +/** + * @dev Find how many shares can currently be redeemed by the owner. + * @param owner Whose shares are inquired. + */ +function maxRedeem(address owner) view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + return erc20s.balanceOf[owner]; +} + +/** + * @dev Show the resulting assets for redeeming the given share count. + * @param shares Share count to be redeemed. + */ +function previewRedeem(uint256 shares) view returns (uint256) { + ERC20Storage storage erc20s = getERC20Storage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + ERC4626Storage storage s = getStorage(); + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + return muldiv(totalAssets_, shares, totalShares_); +} - emit Withdraw(msg.sender, receiver, owner, assets, shares); - return assets; +/** + * @dev Redeem shares from given owner, transferring assets to receiver. + * @param shares Number of shares to redeem. + * @param receiver Destination address for asset withdrawal. + * @param owner User whose shares are spent in redemption. + * @return assets Amount of assets delivered to receiver. + */ +function redeem(uint256 shares, address receiver, address owner) returns (uint256) { + if (receiver == address(0) || owner == address(0)) { + revert ERC4626InvalidAddress(); + } + ERC20Storage storage erc20s = getERC20Storage(); + if (shares > erc20s.balanceOf[owner]) revert ERC4626InvalidAmount(); + + ERC4626Storage storage s = getStorage(); + uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; + uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 assets = muldiv(totalAssets_, shares, totalShares_); + + if (assets == 0) revert ERC4626InsufficientAssets(); + bool success = _safeTransfer(s.asset, receiver, assets); + if (!success) revert ERC4626TransferFailed(); + + if (msg.sender != owner) { + uint256 allowed = erc20s.allowances[owner][msg.sender]; + if (allowed < shares) revert ERC4626InvalidAmount(); + erc20s.allowances[owner][msg.sender] = allowed - shares; } + erc20s.totalSupply -= shares; + erc20s.balanceOf[owner] -= shares; + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + return assets; +} From 25b953d2185e67f93e17a83b0c2913fa7a55e4f3 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Thu, 18 Dec 2025 14:47:02 +0530 Subject: [PATCH 20/25] requested changes --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 10 +++++----- src/token/ERC20/ERC4626/ERC4626Mod.sol | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index cbae14c2..b3ff1b45 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -137,14 +137,14 @@ contract ERC4626Facet { * prod0: a * b (mod 2**256) * prod1: (a * b - prod0)/2**256 */ - let mm := mulmod(a, b, not(0)) /** * Full-width mulmod for high bits */ - prod0 := mul(a, b) + let mm := mulmod(a, b, not(0)) /** * Standard multiplication for low bits */ + prod0 := mul(a, b) /** * Derive prod1 using differences and underflow detection (see muldiv reference). */ @@ -239,7 +239,7 @@ contract ERC4626Facet { */ inv *= 2 - denominator * inv; /** - * nverse mod 2^256 + * inverse mod 2^256 */ /** @@ -329,7 +329,7 @@ contract ERC4626Facet { /** * @notice Returns the maximum depositable amount. */ - function maxDeposit() external pure returns (uint256) { + function maxDeposit(address receiver) external pure returns (uint256) { return type(uint256).max; } @@ -375,7 +375,7 @@ contract ERC4626Facet { /** * @notice Returns the maximum shares that can be minted. */ - function maxMint() external pure returns (uint256) { + function maxMint(address receiver) external pure returns (uint256) { return type(uint256).max; } diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index d582de45..b368fece 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -223,7 +223,7 @@ function convertToAssets(uint256 shares) view returns (uint256) { /** * @dev Maximum possible deposit allowed for this vault. */ -function maxDeposit() pure returns (uint256) { +function maxDeposit(address receiver) pure returns (uint256) { return type(uint256).max; } @@ -301,7 +301,7 @@ function deposit(uint256 assets, address receiver) returns (uint256) { /** * @dev Return the max number of shares that can be minted. */ -function maxMint() pure returns (uint256) { +function maxMint(address receiver) pure returns (uint256) { return type(uint256).max; } From 2db2acc1447bca68d6cdc139efff46eed8efa401 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Thu, 18 Dec 2025 15:00:38 +0530 Subject: [PATCH 21/25] changed the value of offset to mitigate the attack --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 2 +- src/token/ERC20/ERC4626/ERC4626Mod.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index b3ff1b45..4bfdbcb5 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -46,7 +46,7 @@ contract ERC4626Facet { bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); uint256 constant VIRTUAL_ASSET = 1; - uint256 constant VIRTUAL_SHARE = 1; + uint256 constant VIRTUAL_SHARE = 1e1; /** * @dev ERC20 storage layout. diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index b368fece..bbb259ad 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -38,7 +38,7 @@ bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20"); bytes32 constant STORAGE_POSITION = keccak256("compose.erc4626"); uint256 constant VIRTUAL_ASSET = 1; -uint256 constant VIRTUAL_SHARE = 1; +uint256 constant VIRTUAL_SHARE = 1e1; /** * @dev Storage for ERC20 logic. From fe78f6c74fd5ac3dbc12e764fb2aab584f570e7a Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Fri, 19 Dec 2025 01:59:12 +0530 Subject: [PATCH 22/25] fixes in mod related to address --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 49 +++++++---- src/token/ERC20/ERC4626/ERC4626Mod.sol | 103 ++++++++++++++++++----- 2 files changed, 113 insertions(+), 39 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 4bfdbcb5..1c6e8f8a 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -65,7 +65,6 @@ contract ERC4626Facet { */ struct ERC4626Storage { IERC20 asset; - address vaultAddress; } /** @@ -101,7 +100,7 @@ contract ERC4626Facet { */ function totalAssets() external view returns (uint256) { ERC4626Storage storage s = getStorage(); - return s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + return s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; } /** @@ -310,7 +309,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -322,14 +321,22 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } /** * @notice Returns the maximum depositable amount. */ - function maxDeposit(address receiver) external pure returns (uint256) { + function maxDeposit( + /** + * address receiver + */ + ) + external + pure + returns (uint256) + { return type(uint256).max; } @@ -341,7 +348,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -358,11 +365,11 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 shares = muldiv(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); - bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); + bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -375,7 +382,15 @@ contract ERC4626Facet { /** * @notice Returns the maximum shares that can be minted. */ - function maxMint(address receiver) external pure returns (uint256) { + function maxMint( + /** + * address receiver + */ + ) + external + pure + returns (uint256) + { return type(uint256).max; } @@ -387,7 +402,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; return mulDivRoundingUp(totalAssets_, shares, totalShares_); } @@ -404,11 +419,11 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); + bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -426,7 +441,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; return muldiv(totalAssets_, balance, totalShares_); } @@ -439,7 +454,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; return mulDivRoundingUp(totalShares_, assets, totalAssets_); } @@ -458,7 +473,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); @@ -497,7 +512,7 @@ contract ERC4626Facet { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } @@ -517,7 +532,7 @@ contract ERC4626Facet { ERC4626Storage storage s = getStorage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 assets = muldiv(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index bbb259ad..c396baf6 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -57,7 +57,6 @@ struct ERC20Storage { */ struct ERC4626Storage { IERC20 asset; - address vaultAddress; } /** @@ -93,14 +92,18 @@ function asset() view returns (address) { */ function totalAssets() view returns (uint256) { ERC4626Storage storage s = getStorage(); - return s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + return s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; } /** * @dev Compute (a * b) / denominator, using full precision and safe for overflow. * Reference: https://xn--2-umb.com/21/muldiv/ */ -function muldiv(uint256 a, uint256 b, uint256 denominator) view returns (uint256 result) { +function muldiv(uint256 a, uint256 b, uint256 denominator) pure returns (uint256 result) { /** * Guard: denominator can't be zero */ @@ -108,9 +111,7 @@ function muldiv(uint256 a, uint256 b, uint256 denominator) view returns (uint256 uint256 prod0; uint256 prod1; - /** - * b - */ + assembly { let mm := mulmod(a, b, not(0)) prod0 := mul(a, b) @@ -180,7 +181,7 @@ function muldiv(uint256 a, uint256 b, uint256 denominator) view returns (uint256 /** * @dev Compute muldiv and round up the result if remainder exists. */ -function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) view returns (uint256 result) { +function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) pure returns (uint256 result) { result = muldiv(a, b, denominator); if (mulmod(a, b, denominator) > 0) { require(result < type(uint256).max); @@ -204,7 +205,11 @@ function convertToShares(uint256 assets) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -216,14 +221,25 @@ function convertToAssets(uint256 shares) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } /** * @dev Maximum possible deposit allowed for this vault. */ -function maxDeposit(address receiver) pure returns (uint256) { +function maxDeposit( + /** + * address receiver + */ +) + pure + returns (uint256) +{ return type(uint256).max; } @@ -235,7 +251,11 @@ function previewDeposit(uint256 assets) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; return muldiv(totalShares_, assets, totalAssets_); } @@ -284,11 +304,15 @@ function deposit(uint256 assets, address receiver) returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; uint256 shares = muldiv(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); - bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); + bool success = _safeTransferFrom(s.asset, msg.sender, diamondAddress, assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -301,7 +325,14 @@ function deposit(uint256 assets, address receiver) returns (uint256) { /** * @dev Return the max number of shares that can be minted. */ -function maxMint(address receiver) pure returns (uint256) { +function maxMint( + /** + * address receiver + */ +) + pure + returns (uint256) +{ return type(uint256).max; } @@ -313,7 +344,11 @@ function previewMint(uint256 shares) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; return mulDivRoundingUp(totalAssets_, shares, totalShares_); } @@ -330,11 +365,15 @@ function mint(uint256 shares, address receiver) returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); - bool success = _safeTransferFrom(s.asset, msg.sender, s.vaultAddress, assets); + bool success = _safeTransferFrom(s.asset, msg.sender, diamondAddress, assets); if (!success) revert ERC4626TransferFailed(); erc20s.totalSupply += shares; @@ -352,7 +391,11 @@ function maxWithdraw(address owner) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; return muldiv(totalAssets_, balance, totalShares_); } @@ -365,7 +408,11 @@ function previewWithdraw(uint256 assets) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; return mulDivRoundingUp(totalShares_, assets, totalAssets_); } @@ -384,7 +431,11 @@ function withdraw(uint256 assets, address receiver, address owner) returns (uint ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); @@ -423,7 +474,11 @@ function previewRedeem(uint256 shares) view returns (uint256) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; return muldiv(totalAssets_, shares, totalShares_); } @@ -443,7 +498,11 @@ function redeem(uint256 shares, address receiver, address owner) returns (uint25 ERC4626Storage storage s = getStorage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - uint256 totalAssets_ = s.asset.balanceOf(s.vaultAddress) + VIRTUAL_ASSET; + address diamondAddress; + assembly { + diamondAddress := address() + } + uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; uint256 assets = muldiv(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); From 498dc4e44fdfbc800b9f8703b4b2cd70d314acf3 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Tue, 23 Dec 2025 13:57:32 +0530 Subject: [PATCH 23/25] fixes --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 92 ++++++++++++------------ src/token/ERC20/ERC4626/ERC4626Mod.sol | 92 ++++++++++++------------ 2 files changed, 96 insertions(+), 88 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 1c6e8f8a..98958861 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -90,17 +90,19 @@ contract ERC4626Facet { /** * @notice Returns the address of the underlying asset. */ - function asset() external view returns (address) { + function asset() external view returns (address assetTokenAddress) { ERC4626Storage storage s = getStorage(); - return address(s.asset); + assetTokenAddress = address(s.asset); + return assetTokenAddress; } /** * @notice Returns the total amount of underlying assets in the vault. */ - function totalAssets() external view returns (uint256) { + function totalAssets() external view returns (uint256 totalManagedAssets) { ERC4626Storage storage s = getStorage(); - return s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + totalManagedAssets = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; + return totalManagedAssets; } /** @@ -265,6 +267,7 @@ contract ERC4626Facet { * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. */ function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal returns (bool) { + if (address(token).code.length == 0) return false; bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, amount); (bool success, bytes memory returndata) = address(token).call(data); if (!success) return false; @@ -281,6 +284,7 @@ contract ERC4626Facet { * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. */ function _safeTransfer(IERC20 token, address to, uint256 amount) internal returns (bool) { + if (address(token).code.length == 0) return false; bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, amount); (bool success, bytes memory returndata) = address(token).call(data); if (!success) return false; @@ -293,36 +297,30 @@ contract ERC4626Facet { } } - /** - * @notice Returns the total supply of shares (including virtual share). - */ - function totalShares() external view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.totalSupply + VIRTUAL_SHARE; - } - /** * @notice Converts a given amount of assets to the equivalent shares. * @param assets Amount of assets to convert. */ - function convertToShares(uint256 assets) external view returns (uint256) { + function convertToShares(uint256 assets) external view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return muldiv(totalShares_, assets, totalAssets_); + shares = muldiv(totalShares_, assets, totalAssets_); + return shares; } /** * @notice Converts a given amount of shares to the equivalent assets. * @param shares Amount of shares to convert. */ - function convertToAssets(uint256 shares) external view returns (uint256) { + function convertToAssets(uint256 shares) external view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return muldiv(totalAssets_, shares, totalShares_); + assets = muldiv(totalAssets_, shares, totalShares_); + return assets; } /** @@ -334,22 +332,24 @@ contract ERC4626Facet { */ ) external - pure - returns (uint256) + view + returns (uint256 maxAssets) { - return type(uint256).max; + maxAssets = type(uint256).max; + return maxAssets; } /** * @notice Returns the number of shares that would be received for `assets` deposited. * @param assets Amount of assets to preview. */ - function previewDeposit(uint256 assets) external view returns (uint256) { + function previewDeposit(uint256 assets) external view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return muldiv(totalShares_, assets, totalAssets_); + shares = muldiv(totalShares_, assets, totalAssets_); + return shares; } /** @@ -358,15 +358,14 @@ contract ERC4626Facet { * @param receiver Address to receive shares. * @return shares Amount of shares minted. */ - function deposit(uint256 assets, address receiver) external returns (uint256) { + function deposit(uint256 assets, address receiver) external returns (uint256 shares) { if (receiver == address(0)) revert ERC4626InvalidAddress(); - if (assets > type(uint256).max) revert ERC4626InvalidAmount(); ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - uint256 shares = muldiv(totalShares_, assets, totalAssets_); + shares = muldiv(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); @@ -388,22 +387,24 @@ contract ERC4626Facet { */ ) external - pure - returns (uint256) + view + returns (uint256 maxShares) { - return type(uint256).max; + maxShares = type(uint256).max; + return maxShares; } /** * @notice Returns the asset cost of minting `shares` shares. * @param shares Amount of shares to preview. */ - function previewMint(uint256 shares) external view returns (uint256) { + function previewMint(uint256 shares) external view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return mulDivRoundingUp(totalAssets_, shares, totalShares_); + assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); + return assets; } /** @@ -412,15 +413,14 @@ contract ERC4626Facet { * @param receiver Address to receive shares. * @return assets Amount of assets deposited. */ - function mint(uint256 shares, address receiver) external returns (uint256) { + function mint(uint256 shares, address receiver) external returns (uint256 assets) { if (receiver == address(0)) revert ERC4626InvalidAddress(); - if (shares > type(uint256).max) revert ERC4626InvalidAmount(); ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); + assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); bool success = _safeTransferFrom(s.asset, msg.sender, address(this), assets); @@ -437,25 +437,27 @@ contract ERC4626Facet { * @notice Returns the max withdrawable amount of assets for an owner. * @param owner The address to check. */ - function maxWithdraw(address owner) external view returns (uint256) { + function maxWithdraw(address owner) external view returns (uint256 maxAssets) { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - return muldiv(totalAssets_, balance, totalShares_); + maxAssets = muldiv(totalAssets_, balance, totalShares_); + return maxAssets; } /** * @notice Returns the required shares needed to withdraw a given amount of assets. * @param assets Amount of assets to withdraw. */ - function previewWithdraw(uint256 assets) external view returns (uint256) { + function previewWithdraw(uint256 assets) external view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return mulDivRoundingUp(totalShares_, assets, totalAssets_); + shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); + return shares; } /** @@ -465,7 +467,7 @@ contract ERC4626Facet { * @param owner Address whose shares are burned. * @return shares Amount of shares burned. */ - function withdraw(uint256 assets, address receiver, address owner) external returns (uint256) { + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares) { if (receiver == address(0) || owner == address(0)) { revert ERC4626InvalidAddress(); } @@ -478,7 +480,7 @@ contract ERC4626Facet { uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); - uint256 shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); + shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); bool success = _safeTransfer(s.asset, receiver, assets); if (!success) revert ERC4626TransferFailed(); @@ -499,21 +501,23 @@ contract ERC4626Facet { * @notice Returns how many shares can be redeemed by an owner. * @param owner The address to check. */ - function maxRedeem(address owner) external view returns (uint256) { + function maxRedeem(address owner) external view returns (uint256 maxShares) { ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.balanceOf[owner]; + maxShares = erc20s.balanceOf[owner]; + return maxShares; } /** * @notice Returns the amount of assets that would be received for redeeming shares. * @param shares Amount of shares to preview. */ - function previewRedeem(uint256 shares) external view returns (uint256) { + function previewRedeem(uint256 shares) external view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - return muldiv(totalAssets_, shares, totalShares_); + assets = muldiv(totalAssets_, shares, totalShares_); + return assets; } /** @@ -523,7 +527,7 @@ contract ERC4626Facet { * @param owner Address whose shares are redeemed. * @return assets Amount of assets withdrawn. */ - function redeem(uint256 shares, address receiver, address owner) external returns (uint256) { + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) { if (receiver == address(0) || owner == address(0)) { revert ERC4626InvalidAddress(); } @@ -533,7 +537,7 @@ contract ERC4626Facet { ERC4626Storage storage s = getStorage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; uint256 totalAssets_ = s.asset.balanceOf(address(this)) + VIRTUAL_ASSET; - uint256 assets = muldiv(totalAssets_, shares, totalShares_); + assets = muldiv(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); bool success = _safeTransfer(s.asset, receiver, assets); diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index c396baf6..a933d2b2 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -82,21 +82,23 @@ function getStorage() pure returns (ERC4626Storage storage s) { /** * @dev Get the address of the asset used by the vault. */ -function asset() view returns (address) { +function asset() view returns (address assetTokenAddress) { ERC4626Storage storage s = getStorage(); - return address(s.asset); + assetTokenAddress = address(s.asset); + return assetTokenAddress; } /** * @dev Get the current total assets in the vault contract. */ -function totalAssets() view returns (uint256) { +function totalAssets() view returns (uint256 totalManagedAssets) { ERC4626Storage storage s = getStorage(); address diamondAddress; assembly { diamondAddress := address() } - return s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; + totalManagedAssets = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; + return totalManagedAssets; } /** @@ -189,19 +191,11 @@ function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) pure return } } -/** - * @dev Return the sum of true shares and the virtual share value. - */ -function totalShares() view returns (uint256) { - ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.totalSupply + VIRTUAL_SHARE; -} - /** * @dev Convert an asset amount to shares using the vault's accountings. * @param assets The number of assets to convert to shares. */ -function convertToShares(uint256 assets) view returns (uint256) { +function convertToShares(uint256 assets) view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); @@ -210,14 +204,15 @@ function convertToShares(uint256 assets) view returns (uint256) { diamondAddress := address() } uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; - return muldiv(totalShares_, assets, totalAssets_); + shares = muldiv(totalShares_, assets, totalAssets_); + return shares; } /** * @dev Convert shares to the corresponding asset amount. * @param shares The number of shares to convert. */ -function convertToAssets(uint256 shares) view returns (uint256) { +function convertToAssets(uint256 shares) view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); @@ -226,7 +221,8 @@ function convertToAssets(uint256 shares) view returns (uint256) { diamondAddress := address() } uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; - return muldiv(totalAssets_, shares, totalShares_); + assets = muldiv(totalAssets_, shares, totalShares_); + return assets; } /** @@ -237,17 +233,18 @@ function maxDeposit( * address receiver */ ) - pure - returns (uint256) + view + returns (uint256 maxAssets) { - return type(uint256).max; + maxAssets = type(uint256).max; + return maxAssets; } /** * @dev Calculate shares to issue for a potential deposit of given assets. * @param assets Assets input for previewing shares minted. */ -function previewDeposit(uint256 assets) view returns (uint256) { +function previewDeposit(uint256 assets) view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); @@ -256,13 +253,15 @@ function previewDeposit(uint256 assets) view returns (uint256) { diamondAddress := address() } uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; - return muldiv(totalShares_, assets, totalAssets_); + shares = muldiv(totalShares_, assets, totalAssets_); + return shares; } /** * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. */ function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) returns (bool) { + if (address(token).code.length == 0) return false; bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, from, to, amount); (bool success, bytes memory returndata) = address(token).call(data); if (!success) return false; @@ -279,6 +278,7 @@ function _safeTransferFrom(IERC20 token, address from, address to, uint256 amoun * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. */ function _safeTransfer(IERC20 token, address to, uint256 amount) returns (bool) { + if (address(token).code.length == 0) return false; bytes memory data = abi.encodeWithSelector(token.transfer.selector, to, amount); (bool success, bytes memory returndata) = address(token).call(data); if (!success) return false; @@ -297,9 +297,8 @@ function _safeTransfer(IERC20 token, address to, uint256 amount) returns (bool) * @param receiver Address to receive the minted shares. * @return shares The number of shares minted as a result. */ -function deposit(uint256 assets, address receiver) returns (uint256) { +function deposit(uint256 assets, address receiver) returns (uint256 shares) { if (receiver == address(0)) revert ERC4626InvalidAddress(); - if (assets > type(uint256).max) revert ERC4626InvalidAmount(); ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; @@ -309,7 +308,7 @@ function deposit(uint256 assets, address receiver) returns (uint256) { diamondAddress := address() } uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; - uint256 shares = muldiv(totalShares_, assets, totalAssets_); + shares = muldiv(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); bool success = _safeTransferFrom(s.asset, msg.sender, diamondAddress, assets); @@ -330,17 +329,18 @@ function maxMint( * address receiver */ ) - pure - returns (uint256) + view + returns (uint256 maxShares) { - return type(uint256).max; + maxShares =type(uint256).max; + return maxShares; } /** * @dev Preview the asset amount required for minting a number of shares. * @param shares The desired number of shares to mint. */ -function previewMint(uint256 shares) view returns (uint256) { +function previewMint(uint256 shares) view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); @@ -349,7 +349,8 @@ function previewMint(uint256 shares) view returns (uint256) { diamondAddress := address() } uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; - return mulDivRoundingUp(totalAssets_, shares, totalShares_); + assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); + return assets; } /** @@ -358,9 +359,8 @@ function previewMint(uint256 shares) view returns (uint256) { * @param receiver Who receives these shares. * @return assets Asset quantity paid for minting. */ -function mint(uint256 shares, address receiver) returns (uint256) { +function mint(uint256 shares, address receiver) returns (uint256 assets) { if (receiver == address(0)) revert ERC4626InvalidAddress(); - if (shares > type(uint256).max) revert ERC4626InvalidAmount(); ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; @@ -370,7 +370,7 @@ function mint(uint256 shares, address receiver) returns (uint256) { diamondAddress := address() } uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; - uint256 assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); + assets = mulDivRoundingUp(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); bool success = _safeTransferFrom(s.asset, msg.sender, diamondAddress, assets); @@ -387,7 +387,7 @@ function mint(uint256 shares, address receiver) returns (uint256) { * @dev Get the max asset withdrawal allowed for the given owner. * @param owner Account address to check. */ -function maxWithdraw(address owner) view returns (uint256) { +function maxWithdraw(address owner) view returns (uint256 maxAssets) { ERC20Storage storage erc20s = getERC20Storage(); uint256 balance = erc20s.balanceOf[owner]; ERC4626Storage storage s = getStorage(); @@ -397,14 +397,15 @@ function maxWithdraw(address owner) view returns (uint256) { } uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; - return muldiv(totalAssets_, balance, totalShares_); + maxAssets = muldiv(totalAssets_, balance, totalShares_); + return maxAssets; } /** * @dev Preview required shares for a withdrawal of the given asset amount. * @param assets Desired withdrawal quantity. */ -function previewWithdraw(uint256 assets) view returns (uint256) { +function previewWithdraw(uint256 assets) view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); @@ -413,7 +414,8 @@ function previewWithdraw(uint256 assets) view returns (uint256) { diamondAddress := address() } uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; - return mulDivRoundingUp(totalShares_, assets, totalAssets_); + shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); + return shares; } /** @@ -423,7 +425,7 @@ function previewWithdraw(uint256 assets) view returns (uint256) { * @param owner The address whose shares are spent. * @return shares Amount of shares burned. */ -function withdraw(uint256 assets, address receiver, address owner) returns (uint256) { +function withdraw(uint256 assets, address receiver, address owner) returns (uint256 shares) { if (receiver == address(0) || owner == address(0)) { revert ERC4626InvalidAddress(); } @@ -440,7 +442,7 @@ function withdraw(uint256 assets, address receiver, address owner) returns (uint uint256 maxWithdrawVal = muldiv(totalAssets_, balance, totalShares_); if (assets > maxWithdrawVal) revert ERC4626InvalidAmount(); - uint256 shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); + shares = mulDivRoundingUp(totalShares_, assets, totalAssets_); if (shares == 0) revert ERC4626InsufficientShares(); bool success = _safeTransfer(s.asset, receiver, assets); if (!success) revert ERC4626TransferFailed(); @@ -461,16 +463,17 @@ function withdraw(uint256 assets, address receiver, address owner) returns (uint * @dev Find how many shares can currently be redeemed by the owner. * @param owner Whose shares are inquired. */ -function maxRedeem(address owner) view returns (uint256) { +function maxRedeem(address owner) view returns (uint256 maxShares) { ERC20Storage storage erc20s = getERC20Storage(); - return erc20s.balanceOf[owner]; + maxShares = erc20s.balanceOf[owner]; + return maxShares; } /** * @dev Show the resulting assets for redeeming the given share count. * @param shares Share count to be redeemed. */ -function previewRedeem(uint256 shares) view returns (uint256) { +function previewRedeem(uint256 shares) view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); uint256 totalShares_ = erc20s.totalSupply + VIRTUAL_SHARE; ERC4626Storage storage s = getStorage(); @@ -479,7 +482,8 @@ function previewRedeem(uint256 shares) view returns (uint256) { diamondAddress := address() } uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; - return muldiv(totalAssets_, shares, totalShares_); + assets = muldiv(totalAssets_, shares, totalShares_); + return assets; } /** @@ -489,7 +493,7 @@ function previewRedeem(uint256 shares) view returns (uint256) { * @param owner User whose shares are spent in redemption. * @return assets Amount of assets delivered to receiver. */ -function redeem(uint256 shares, address receiver, address owner) returns (uint256) { +function redeem(uint256 shares, address receiver, address owner) returns (uint256 assets) { if (receiver == address(0) || owner == address(0)) { revert ERC4626InvalidAddress(); } @@ -503,7 +507,7 @@ function redeem(uint256 shares, address receiver, address owner) returns (uint25 diamondAddress := address() } uint256 totalAssets_ = s.asset.balanceOf(diamondAddress) + VIRTUAL_ASSET; - uint256 assets = muldiv(totalAssets_, shares, totalShares_); + assets = muldiv(totalAssets_, shares, totalShares_); if (assets == 0) revert ERC4626InsufficientAssets(); bool success = _safeTransfer(s.asset, receiver, assets); From 50eb7be51777be5639662efcbfbf7101c8d9c149 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Tue, 23 Dec 2025 13:57:44 +0530 Subject: [PATCH 24/25] format --- src/token/ERC20/ERC4626/ERC4626Mod.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index a933d2b2..64c594ac 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -332,7 +332,7 @@ function maxMint( view returns (uint256 maxShares) { - maxShares =type(uint256).max; + maxShares = type(uint256).max; return maxShares; } From 5bf178057ec702362fbc9c4b6ab8920e969ee798 Mon Sep 17 00:00:00 2001 From: Abhivansh <31abhivanshj@gmail.com> Date: Tue, 23 Dec 2025 14:19:23 +0530 Subject: [PATCH 25/25] updated natspec --- src/token/ERC20/ERC4626/ERC4626Facet.sol | 83 ++++++++++++----- src/token/ERC20/ERC4626/ERC4626Mod.sol | 108 +++++++++++++++-------- 2 files changed, 131 insertions(+), 60 deletions(-) diff --git a/src/token/ERC20/ERC4626/ERC4626Facet.sol b/src/token/ERC20/ERC4626/ERC4626Facet.sol index 98958861..0030f9f0 100644 --- a/src/token/ERC20/ERC4626/ERC4626Facet.sol +++ b/src/token/ERC20/ERC4626/ERC4626Facet.sol @@ -31,12 +31,21 @@ contract ERC4626Facet { error ERC4626InsufficientAssets(); /** - * @notice Emitted when assets are deposited and shares issued. + * @notice Emitted when assets are deposited into the vault and corresponding shares issued. + * @param sender The address initiating the deposit (msg.sender) + * @param owner The account that will receive the shares + * @param assets The amount of asset tokens deposited + * @param shares The amount of vault shares minted */ event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); /** - * @notice Emitted when assets are withdrawn and shares burned. + * @notice Emitted when shares are withdrawn (burned) from the vault in exchange for assets. + * @param sender The address initiating the withdrawal (msg.sender) + * @param receiver The address receiving the withdrawn assets + * @param owner The account whose shares were burned + * @param assets The amount of asset tokens withdrawn + * @param shares The amount of shares burned for the withdrawal */ event Withdraw( address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares @@ -88,7 +97,8 @@ contract ERC4626Facet { } /** - * @notice Returns the address of the underlying asset. + * @notice Returns the address of the vault's underlying asset. + * @return assetTokenAddress The ERC20 token used for deposits/withdrawals */ function asset() external view returns (address assetTokenAddress) { ERC4626Storage storage s = getStorage(); @@ -97,7 +107,9 @@ contract ERC4626Facet { } /** - * @notice Returns the total amount of underlying assets in the vault. + * @notice Returns the total underlying assets managed by the vault. + * @return totalManagedAssets Assets (in asset token) currently held by the vault, + * including virtual buffering. */ function totalAssets() external view returns (uint256 totalManagedAssets) { ERC4626Storage storage s = getStorage(); @@ -264,7 +276,12 @@ contract ERC4626Facet { } /** - * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. + * @dev Safely calls IERC20.transferFrom accounting for non-standard ERC20s. + * @param token Token to transfer + * @param from Source address + * @param to Target address + * @param amount Value to transfer + * @return Whether the call succeeded (true for success) */ function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal returns (bool) { if (address(token).code.length == 0) return false; @@ -281,7 +298,11 @@ contract ERC4626Facet { } /** - * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. + * @dev Safely calls IERC20.transfer accounting for non-standard ERC20s. + * @param token Token to transfer + * @param to Target address + * @param amount Value to transfer + * @return Whether the call succeeded (true for success) */ function _safeTransfer(IERC20 token, address to, uint256 amount) internal returns (bool) { if (address(token).code.length == 0) return false; @@ -298,8 +319,10 @@ contract ERC4626Facet { } /** - * @notice Converts a given amount of assets to the equivalent shares. - * @param assets Amount of assets to convert. + * @notice Converts a specified amount of assets to the corresponding amount of shares, + * using current total assets and shares. + * @param assets The amount of asset tokens to convert + * @return shares Amount of vault shares representing the supplied assets */ function convertToShares(uint256 assets) external view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); @@ -311,8 +334,10 @@ contract ERC4626Facet { } /** - * @notice Converts a given amount of shares to the equivalent assets. - * @param shares Amount of shares to convert. + * @notice Converts a specified amount of shares to the corresponding amount of underlying assets, + * using current total assets and shares. + * @param shares Amount of vault shares to convert + * @return assets Amount of asset tokens represented by the shares */ function convertToAssets(uint256 shares) external view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); @@ -324,7 +349,9 @@ contract ERC4626Facet { } /** - * @notice Returns the maximum depositable amount. + * @notice Returns the maximum amount of assets that may be deposited for an account. + * @dev Per ERC4626, this may be unlimited. No per-account logic here. + * @return maxAssets The maximum deposit amount (uint256 max) */ function maxDeposit( /** @@ -340,8 +367,9 @@ contract ERC4626Facet { } /** - * @notice Returns the number of shares that would be received for `assets` deposited. - * @param assets Amount of assets to preview. + * @notice Returns the number of shares that would be minted by depositing the given `assets`. + * @param assets Amount of asset tokens to deposit + * @return shares Amount of vault shares that would be minted */ function previewDeposit(uint256 assets) external view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); @@ -379,7 +407,9 @@ contract ERC4626Facet { } /** - * @notice Returns the maximum shares that can be minted. + * @notice Returns the maximum number of shares that may be minted for an account. + * @dev Per ERC4626, this may be unlimited. No per-account logic here. + * @return maxShares The maximum mintable shares (uint256 max) */ function maxMint( /** @@ -395,8 +425,9 @@ contract ERC4626Facet { } /** - * @notice Returns the asset cost of minting `shares` shares. - * @param shares Amount of shares to preview. + * @notice Returns the amount of assets required to mint `shares` shares, rounded up if necessary. + * @param shares Amount of shares to mint + * @return assets Asset tokens that must be deposited to mint the given shares */ function previewMint(uint256 shares) external view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); @@ -434,8 +465,9 @@ contract ERC4626Facet { } /** - * @notice Returns the max withdrawable amount of assets for an owner. - * @param owner The address to check. + * @notice Returns the maximum amount of assets that can be withdrawn for the given owner. + * @param owner The address to check withdrawable assets for + * @return maxAssets Max withdrawable amount in asset tokens for the owner */ function maxWithdraw(address owner) external view returns (uint256 maxAssets) { ERC20Storage storage erc20s = getERC20Storage(); @@ -448,8 +480,9 @@ contract ERC4626Facet { } /** - * @notice Returns the required shares needed to withdraw a given amount of assets. - * @param assets Amount of assets to withdraw. + * @notice Returns the number of shares that must be burned to withdraw `assets`, rounded up. + * @param assets Amount of asset tokens to be withdrawn + * @return shares Shares required to burn for withdrawal */ function previewWithdraw(uint256 assets) external view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); @@ -498,8 +531,9 @@ contract ERC4626Facet { } /** - * @notice Returns how many shares can be redeemed by an owner. - * @param owner The address to check. + * @notice Returns the maximum number of shares that an owner may redeem from the vault. + * @param owner The address to check redeemable shares for + * @return maxShares Number of redeemable shares for the owner */ function maxRedeem(address owner) external view returns (uint256 maxShares) { ERC20Storage storage erc20s = getERC20Storage(); @@ -508,8 +542,9 @@ contract ERC4626Facet { } /** - * @notice Returns the amount of assets that would be received for redeeming shares. - * @param shares Amount of shares to preview. + * @notice Returns the amount of assets that would be received for redeeming the given `shares`. + * @param shares Amount of shares to redeem + * @return assets Asset tokens returned by redeeming the shares */ function previewRedeem(uint256 shares) external view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); diff --git a/src/token/ERC20/ERC4626/ERC4626Mod.sol b/src/token/ERC20/ERC4626/ERC4626Mod.sol index 64c594ac..6d64a3ef 100644 --- a/src/token/ERC20/ERC4626/ERC4626Mod.sol +++ b/src/token/ERC20/ERC4626/ERC4626Mod.sol @@ -25,12 +25,21 @@ error ERC4626InsufficientShares(); error ERC4626InsufficientAssets(); /** - * @dev Fires on deposits (assets in, shares out). + * @dev Emitted when assets are deposited and shares are minted. + * @param sender The address that initiated the deposit. + * @param owner The address receiving the shares. + * @param assets Amount of assets deposited. + * @param shares Amount of shares minted. */ event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); /** - * @dev Fires on withdrawals (assets out, shares burned). + * @dev Emitted when assets are withdrawn and shares are burned. + * @param sender The address that initiated the withdrawal. + * @param receiver The address receiving the withdrawn assets. + * @param owner The address whose shares are burnt. + * @param assets Amount of assets withdrawn. + * @param shares Amount of shares burned. */ event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); @@ -80,7 +89,8 @@ function getStorage() pure returns (ERC4626Storage storage s) { } /** - * @dev Get the address of the asset used by the vault. + * @notice Returns the address of the underlying ERC20 asset token. + * @return assetTokenAddress The asset's contract address. */ function asset() view returns (address assetTokenAddress) { ERC4626Storage storage s = getStorage(); @@ -89,7 +99,8 @@ function asset() view returns (address assetTokenAddress) { } /** - * @dev Get the current total assets in the vault contract. + * @notice Returns the total amount of the underlying asset managed by the vault. + * @return totalManagedAssets The total managed assets, including virtual assets. */ function totalAssets() view returns (uint256 totalManagedAssets) { ERC4626Storage storage s = getStorage(); @@ -211,6 +222,7 @@ function convertToShares(uint256 assets) view returns (uint256 shares) { /** * @dev Convert shares to the corresponding asset amount. * @param shares The number of shares to convert. + * @return assets The equivalent asset amount. */ function convertToAssets(uint256 shares) view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); @@ -226,7 +238,9 @@ function convertToAssets(uint256 shares) view returns (uint256 assets) { } /** - * @dev Maximum possible deposit allowed for this vault. + * @notice Returns the maximum assets allowed for deposit by a receiver. + * @dev Always returns the maximum possible uint256 value. + * @return maxAssets The max depositable asset amount. */ function maxDeposit( /** @@ -241,8 +255,9 @@ function maxDeposit( } /** - * @dev Calculate shares to issue for a potential deposit of given assets. - * @param assets Assets input for previewing shares minted. + * @notice Preview the number of shares that would be minted by depositing a given amount of assets. + * @param assets Asset quantity to preview. + * @return shares Estimated shares that would be minted. */ function previewDeposit(uint256 assets) view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); @@ -258,7 +273,13 @@ function previewDeposit(uint256 assets) view returns (uint256 shares) { } /** - * @dev Safe ERC20 transferFrom wrapper supporting non-standard tokens. + * @notice Executes a safe ERC20 transferFrom, supporting tokens that do not return a value. + * @dev Returns true if the call succeeded, false otherwise. + * @param token The ERC20 token contract. + * @param from Source address. + * @param to Destination address. + * @param amount Value to transfer. + * @return True if the operation succeeded, false otherwise. */ function _safeTransferFrom(IERC20 token, address from, address to, uint256 amount) returns (bool) { if (address(token).code.length == 0) return false; @@ -275,7 +296,12 @@ function _safeTransferFrom(IERC20 token, address from, address to, uint256 amoun } /** - * @dev Safe ERC20 transfer wrapper supporting non-standard tokens. + * @notice Executes a safe ERC20 transfer, supporting tokens that do not return a value. + * @dev Returns true if the call succeeded, false otherwise. + * @param token The ERC20 token contract. + * @param to Destination address. + * @param amount Value to transfer. + * @return True if the operation succeeded, false otherwise. */ function _safeTransfer(IERC20 token, address to, uint256 amount) returns (bool) { if (address(token).code.length == 0) return false; @@ -292,10 +318,11 @@ function _safeTransfer(IERC20 token, address to, uint256 amount) returns (bool) } /** - * @dev Effect actual deposit, minting shares for the receiver. - * @param assets Asset amount sent in. - * @param receiver Address to receive the minted shares. - * @return shares The number of shares minted as a result. + * @notice Deposit assets into the vault and mint shares for a receiver. + * @dev Transfers assets from msg.sender and mints shares for receiver. + * @param assets Amount of underlying assets to deposit. + * @param receiver Account to receive shares. + * @return shares Number of shares minted for the deposit. */ function deposit(uint256 assets, address receiver) returns (uint256 shares) { if (receiver == address(0)) revert ERC4626InvalidAddress(); @@ -322,7 +349,9 @@ function deposit(uint256 assets, address receiver) returns (uint256 shares) { } /** - * @dev Return the max number of shares that can be minted. + * @notice Returns the maximum number of shares that can be minted to a receiver. + * @dev Always returns the maximum possible uint256 value. + * @return maxShares The maximum shares that may be minted. */ function maxMint( /** @@ -337,8 +366,9 @@ function maxMint( } /** - * @dev Preview the asset amount required for minting a number of shares. - * @param shares The desired number of shares to mint. + * @notice Preview the required asset amount to mint a specific number of shares. + * @param shares Number of shares to mint. + * @return assets Assets needed to mint the given shares (rounded up). */ function previewMint(uint256 shares) view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); @@ -356,8 +386,8 @@ function previewMint(uint256 shares) view returns (uint256 assets) { /** * @dev Mint exact shares in exchange for assets, assigning to receiver. * @param shares Number of shares to mint. - * @param receiver Who receives these shares. - * @return assets Asset quantity paid for minting. + * @param receiver Account to receive minted shares. + * @return assets Amount of assets provided for minting. */ function mint(uint256 shares, address receiver) returns (uint256 assets) { if (receiver == address(0)) revert ERC4626InvalidAddress(); @@ -384,8 +414,9 @@ function mint(uint256 shares, address receiver) returns (uint256 assets) { } /** - * @dev Get the max asset withdrawal allowed for the given owner. - * @param owner Account address to check. + * @notice Returns the maximum amount of assets that an owner can withdraw. + * @param owner Address for which the maximum withdrawal is calculated. + * @return maxAssets Maximum withdrawable assets for the given owner. */ function maxWithdraw(address owner) view returns (uint256 maxAssets) { ERC20Storage storage erc20s = getERC20Storage(); @@ -402,8 +433,9 @@ function maxWithdraw(address owner) view returns (uint256 maxAssets) { } /** - * @dev Preview required shares for a withdrawal of the given asset amount. - * @param assets Desired withdrawal quantity. + * @notice Preview the number of shares required to withdraw a given asset amount. + * @param assets Amount of underlying assets to withdraw. + * @return shares Number of shares that would be burned for the withdrawal (rounded up). */ function previewWithdraw(uint256 assets) view returns (uint256 shares) { ERC20Storage storage erc20s = getERC20Storage(); @@ -419,11 +451,12 @@ function previewWithdraw(uint256 assets) view returns (uint256 shares) { } /** - * @dev Burn owner's shares to release assets to the given receiver address. - * @param assets Number of assets to withdraw. - * @param receiver Address to receive assets. - * @param owner The address whose shares are spent. - * @return shares Amount of shares burned. + * @notice Withdraws a given amount of assets to receiver, burning appropriate shares from owner. + * @dev Transfers assets and burns shares; handles allowances if needed. + * @param assets Amount of assets to withdraw. + * @param receiver Address to receive withdrawn assets. + * @param owner Address whose shares will be burned. + * @return shares Number of shares burned as a result of withdrawal. */ function withdraw(uint256 assets, address receiver, address owner) returns (uint256 shares) { if (receiver == address(0) || owner == address(0)) { @@ -460,8 +493,9 @@ function withdraw(uint256 assets, address receiver, address owner) returns (uint } /** - * @dev Find how many shares can currently be redeemed by the owner. - * @param owner Whose shares are inquired. + * @notice Returns the maximum number of shares an owner can redeem at the current time. + * @param owner The address to query. + * @return maxShares Current share balance of the owner. */ function maxRedeem(address owner) view returns (uint256 maxShares) { ERC20Storage storage erc20s = getERC20Storage(); @@ -470,8 +504,9 @@ function maxRedeem(address owner) view returns (uint256 maxShares) { } /** - * @dev Show the resulting assets for redeeming the given share count. - * @param shares Share count to be redeemed. + * @notice Preview assets that would be returned for redeeming a given number of shares. + * @param shares Amount of shares to redeem. + * @return assets Amount of assets to be returned for given shares. */ function previewRedeem(uint256 shares) view returns (uint256 assets) { ERC20Storage storage erc20s = getERC20Storage(); @@ -487,11 +522,12 @@ function previewRedeem(uint256 shares) view returns (uint256 assets) { } /** - * @dev Redeem shares from given owner, transferring assets to receiver. - * @param shares Number of shares to redeem. - * @param receiver Destination address for asset withdrawal. - * @param owner User whose shares are spent in redemption. - * @return assets Amount of assets delivered to receiver. + * @notice Redeems shares from an owner and sends the corresponding assets to a receiver. + * @dev Transfers assets and burns shares; handles allowances if needed. + * @param shares Amount of shares to redeem. + * @param receiver Address to receive redeemed assets. + * @param owner Shares owner's address to burn from. + * @return assets Amount of assets received for redemption. */ function redeem(uint256 shares, address receiver, address owner) returns (uint256 assets) { if (receiver == address(0) || owner == address(0)) {