solmate icon indicating copy to clipboard operation
solmate copied to clipboard

ERC4626 Implementation Standard Inconsistency

Open Zer0dot opened this issue 3 years ago • 2 comments

Found after discussions with @bensparkscode, @donosonaumczuk and @vicnaum.

We are not certain whether this is a lack of clarity in the standard's description or an inconsistency in the implementation. The standard states that the totalAssets() function "MUST be inclusive of any fees that are charged against assets in the Vault."

However, doing so would include the charged fees in the calculation of the asset <=> share rate in the current implementation, giving an artificially reduced share rate. This either means that, by "inclusive," the EIP means that it should include the removal of fees, or that the implementation should instead remove fees before using the totalAssets() function in conversion calculations.

Relevant code:

    function totalAssets() public view virtual returns (uint256);

    function convertToShares(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
    }

    function convertToAssets(uint256 shares) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
    }

    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return convertToShares(assets);
    }

    function previewMint(uint256 shares) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
    }

    function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
    }

    function previewRedeem(uint256 shares) public view virtual returns (uint256) {
        return convertToAssets(shares);
    }

Zer0dot avatar Dec 14 '22 21:12 Zer0dot

ugh yes this seems valid, idk why we made totalAssets account for fees

transmissions11 avatar Dec 14 '22 21:12 transmissions11

Depending on where the vault fee rake is being captured, asset side or vault share supply side, you may have to adjust your totalSupply to be a function that accounts for this, especially if lazy minting the fee rake later.

going through the spec was def a headache

androolloyd avatar Dec 15 '22 11:12 androolloyd