From 3d3967840473a6370df894e115d5914753d8ceea Mon Sep 17 00:00:00 2001 From: Leandro Faria Date: Thu, 9 Jan 2025 19:04:39 -0500 Subject: [PATCH 1/6] refactor:changed file structure --- .../external/ERC20Issuance_blacklist_v1.t.sol | 6 +++ .../FM_PC_ExternalPrice_Redeeming_v1.t.sol | 8 +++ .../ERC20Issuance_blacklist_v1_exposed.sol | 50 +++++++++++++++++++ ...M_ManualExternalPriceSetter_v1_exposed.sol | 18 +++++++ .../LM_ManualExternalPriceSetter_v1.t.sol | 23 ++++++++- 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 test/modules/fundingManager/oracle/utils/mocks/ERC20Issuance_blacklist_v1_exposed.sol create mode 100644 test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol diff --git a/test/external/ERC20Issuance_blacklist_v1.t.sol b/test/external/ERC20Issuance_blacklist_v1.t.sol index ffe2609ef..982f2b7dc 100644 --- a/test/external/ERC20Issuance_blacklist_v1.t.sol +++ b/test/external/ERC20Issuance_blacklist_v1.t.sol @@ -2,12 +2,18 @@ pragma solidity 0.8.23; import {Test} from "forge-std/Test.sol"; +<<<<<<< HEAD:test/external/ERC20Issuance_blacklist_v1.t.sol import { ERC20Issuance_Blacklist_v1, IERC20Issuance_Blacklist_v1 } from "@ex/token/ERC20Issuance_Blacklist_v1.sol"; import {ERC20Issuance_Blacklist_v1_Exposed} from "test/external/ERC20Issuance_blacklist_v1_exposed.sol"; +======= +import {ERC20Issuance_Blacklist_v1} from "@ex/token/ERC20Issuance_blacklist_v1.sol"; +import {ERC20Issuance_Blacklist_v1_Exposed} from "test/modules/fundingManager/oracle/utils/mocks/ERC20Issuance_blacklist_v1_exposed.sol"; +import {IERC20Issuance_Blacklist_v1} from "@ex/token/interfaces/IERC20Issuance_blacklist_v1.sol"; +>>>>>>> c3d3d7cd (refactor:changed file structure):test/modules/fundingManager/token/ERC20Issuance_blacklist_v1.t.sol /** * @title ERC20Issuance_Blacklist_v1_Test diff --git a/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol b/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol index 90e10f493..afcbc9e79 100644 --- a/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol +++ b/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol @@ -23,11 +23,15 @@ import {BondingCurveBase_v1} from import {RedeemingBondingCurveBase_v1} from "@fm/bondingCurve/abstracts/RedeemingBondingCurveBase_v1.sol"; import {ERC20Issuance_v1} from "@ex/token/ERC20Issuance_v1.sol"; +<<<<<<< HEAD import {LM_ManualExternalPriceSetter_v1} from "src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol"; import {OraclePrice_Mock} from "test/utils/mocks/modules/logicModules/OraclePrice_Mock.sol"; +======= +import {LM_ManualExternalPriceSetter_v1} from "src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol"; +>>>>>>> c3d3d7cd (refactor:changed file structure) import { IERC20PaymentClientBase_v1, ERC20PaymentClientBaseV1Mock, @@ -39,6 +43,10 @@ import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {RedeemingBondingCurveBaseV1Mock} from "test/modules/fundingManager/bondingCurve/utils/mocks/RedeemingBondingCurveBaseV1Mock.sol"; +<<<<<<< HEAD +======= +import {BancorFormula} from "@fm/bondingCurve/formulas/BancorFormula.sol"; +>>>>>>> c3d3d7cd (refactor:changed file structure) /** * @title FM_PC_ExternalPrice_Redeeming_v1_Test diff --git a/test/modules/fundingManager/oracle/utils/mocks/ERC20Issuance_blacklist_v1_exposed.sol b/test/modules/fundingManager/oracle/utils/mocks/ERC20Issuance_blacklist_v1_exposed.sol new file mode 100644 index 000000000..31d790f54 --- /dev/null +++ b/test/modules/fundingManager/oracle/utils/mocks/ERC20Issuance_blacklist_v1_exposed.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.23; + +import {ERC20Issuance_Blacklist_v1} from + "@ex/token/ERC20Issuance_Blacklist_v1.sol"; + +/** + * @title ERC20Issuance_Blacklist_v1_Exposed + * @dev Contract that exposes internal functions of ERC20Issuance_Blacklist_v1 for testing purposes + */ +contract ERC20Issuance_Blacklist_v1_Exposed is ERC20Issuance_Blacklist_v1 { + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_, + uint initialSupply_, + address initialAdmin_, + address initialBlacklistManager_ + ) + ERC20Issuance_Blacklist_v1( + name_, + symbol_, + decimals_, + initialSupply_, + initialAdmin_, + initialBlacklistManager_ + ) + {} + + /** + * @dev Exposes the internal _update function for testing + * @param from_ Address tokens are transferred from + * @param to_ Address tokens are transferred to + * @param amount_ Amount of tokens transferred + */ + function exposed_update(address from_, address to_, uint amount_) public { + _update(from_, to_, amount_); + } + + /** + * @dev Exposes the internal _setBlacklistManager function for testing + * @param account_ Address to set privileges for + * @param privileges_ Whether to grant or revoke privileges + */ + function exposed_setBlacklistManager(address account_, bool privileges_) + public + { + _setBlacklistManager(account_, privileges_); + } +} diff --git a/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol b/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol new file mode 100644 index 000000000..215edfa8a --- /dev/null +++ b/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.23; + +import {LM_ManualExternalPriceSetter_v1} from "src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol"; +import {IOrchestrator_v1} from "src/orchestrator/interfaces/IOrchestrator_v1.sol"; +import {IAuthorizer_v1} from "@aut/IAuthorizer_v1.sol"; + +contract LM_ManualExternalPriceSetter_v1_Exposed is LM_ManualExternalPriceSetter_v1 { + constructor() {} + + function exposed_normalizePrice(uint256 price_, uint8 tokenDecimals_) external pure returns (uint256) { + return _normalizePrice(price_, tokenDecimals_); + } + + function exposed_denormalizePrice(uint256 price_, uint8 tokenDecimals_) external view returns (uint256) { + return _denormalizePrice(price_, tokenDecimals_); + } +} diff --git a/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol b/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol index 4369ac3aa..7c8ecd025 100644 --- a/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol +++ b/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol @@ -8,7 +8,15 @@ import {LM_ManualExternalPriceSetter_v1_Exposed} from "test/modules/logicModule/LM_ManualExternalPriceSetter_v1_exposed.sol"; import {ILM_ManualExternalPriceSetter_v1} from "@lm/interfaces/ILM_ManualExternalPriceSetter_v1.sol"; +<<<<<<< HEAD import {ERC20Decimals_Mock} from "test/utils/mocks/ERC20Decimals_Mock.sol"; +======= +import {IOrchestrator_v1} from + "src/orchestrator/interfaces/IOrchestrator_v1.sol"; +import {IModule_v1} from "src/modules/base/IModule_v1.sol"; +import {MockERC20} from + "test/modules/fundingManager/oracle/utils/mocks/MockERC20.sol"; +>>>>>>> c3d3d7cd (refactor:changed file structure) import {ERC20Mock} from "test/utils/mocks/ERC20Mock.sol"; import {AuthorizerV1Mock} from "test/utils/mocks/modules/AuthorizerV1Mock.sol"; import {Clones} from "@oz/proxy/Clones.sol"; @@ -34,14 +42,20 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { address priceSetter_; address user; +<<<<<<< HEAD ERC20Decimals_Mock inputToken; ERC20Mock outputToken; +======= +>>>>>>> c3d3d7cd (refactor:changed file structure) bytes32 constant PRICE_SETTER_ROLE = "PRICE_SETTER_ROLE"; uint8 constant INTERNAL_DECIMALS = 18; string constant TOKEN_NAME = "MOCK USDC"; string constant TOKEN_SYMBOL = "M-USDC"; + MockERC20 inputToken; + MockERC20 outputToken; + // Module Constants uint constant MAJOR_VERSION = 1; uint constant MINOR_VERSION = 0; @@ -59,10 +73,17 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { user = makeAddr("user"); vm.startPrank(admin); - + // Create mock tokens with different decimals +<<<<<<< HEAD inputToken = new ERC20Decimals_Mock(TOKEN_NAME, TOKEN_SYMBOL, 6); // Like USDC outputToken = _token; // Like most ERC20s +======= + inputToken = new MockERC20(6); // Like USDC + outputToken = new MockERC20(18); // Like most ERC20s + + +>>>>>>> c3d3d7cd (refactor:changed file structure) // Setup price setter address impl = address(new LM_ManualExternalPriceSetter_v1_Exposed()); From 788015fb58126f0192be93dbbdf58f4d340e6296 Mon Sep 17 00:00:00 2001 From: Leandro Faria Date: Thu, 9 Jan 2025 19:20:08 -0500 Subject: [PATCH 2/6] fix:fee amount bug --- .../fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.sol b/src/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.sol index a4b34614f..58f3f6106 100644 --- a/src/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.sol +++ b/src/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.sol @@ -578,7 +578,7 @@ contract FM_PC_ExternalPrice_Redeeming_v1 is // Create and emit the order. _createAndEmitOrder( - _receiver, _depositAmount, collateralRedeemAmount, issuanceFeeAmount + _receiver, _depositAmount, collateralRedeemAmount, protocolFeeAmount ); return (totalCollateralTokenMovedOut, issuanceFeeAmount); From a999c10b57a49905dd4ca26fd57184ba1d127cb0 Mon Sep 17 00:00:00 2001 From: Leandro Faria Date: Thu, 9 Jan 2025 19:35:53 -0500 Subject: [PATCH 3/6] refactor:add internal func for redeem and issue --- .../LM_ManualExternalPriceSetter_v1.sol | 39 ++++++++++++++----- .../FM_PC_ExternalPrice_Redeeming_v1.t.sol | 8 ---- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol b/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol index 8b828df6c..a164c35b1 100644 --- a/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol +++ b/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol @@ -119,16 +119,26 @@ contract LM_ManualExternalPriceSetter_v1 is // ------------------------------------------------------------------------- // External Functions + /// @notice Internal function to set the issuance price + /// @param price_ The price to set + function _setIssuancePrice(uint price_) internal { + _issuancePrice = _setAndValidatePrice(price_, _collateralTokenDecimals); + emit IssuancePriceSet(price_); + } + + /// @notice Internal function to set the redemption price + /// @param price_ The price to set + function _setRedemptionPrice(uint price_) internal { + _redemptionPrice = _setAndValidatePrice(price_, _issuanceTokenDecimals); + emit RedemptionPriceSet(price_); + } + /// @inheritdoc ILM_ManualExternalPriceSetter_v1 function setIssuancePrice(uint price_) external onlyModuleRole(PRICE_SETTER_ROLE) { - if (price_ == 0) revert Module__LM_ExternalPriceSetter__InvalidPrice(); - - // Normalize price to internal decimal precision - _issuancePrice = _normalizePrice(price_, _collateralTokenDecimals); - emit IssuancePriceSet(price_); + _setIssuancePrice(price_); } /// @inheritdoc ILM_ManualExternalPriceSetter_v1 @@ -136,11 +146,7 @@ contract LM_ManualExternalPriceSetter_v1 is external onlyModuleRole(PRICE_SETTER_ROLE) { - if (price_ == 0) revert Module__LM_ExternalPriceSetter__InvalidPrice(); - - // Normalize price to internal decimal precision. - _redemptionPrice = _normalizePrice(price_, _issuanceTokenDecimals); - emit RedemptionPriceSet(price_); + _setRedemptionPrice(price_); } /// @inheritdoc ILM_ManualExternalPriceSetter_v1 @@ -223,6 +229,19 @@ contract LM_ManualExternalPriceSetter_v1 is } } + /// @notice Internal function to set and validate the price + /// @param price_ The price to set + /// @param tokenDecimals_ The decimals of the token the price is + /// denominated in. + /// @return The normalized price with INTERNAL_DECIMALS precision. + function _setAndValidatePrice(uint price_, uint8 tokenDecimals_) + internal + returns (uint) + { + if (price_ == 0) revert Module__LM_ExternalPriceSetter__InvalidPrice(); + return _normalizePrice(price_, tokenDecimals_); + } + /// @dev Storage gap for upgradeable contracts. uint[50] private __gap; } diff --git a/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol b/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol index afcbc9e79..90e10f493 100644 --- a/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol +++ b/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol @@ -23,15 +23,11 @@ import {BondingCurveBase_v1} from import {RedeemingBondingCurveBase_v1} from "@fm/bondingCurve/abstracts/RedeemingBondingCurveBase_v1.sol"; import {ERC20Issuance_v1} from "@ex/token/ERC20Issuance_v1.sol"; -<<<<<<< HEAD import {LM_ManualExternalPriceSetter_v1} from "src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol"; import {OraclePrice_Mock} from "test/utils/mocks/modules/logicModules/OraclePrice_Mock.sol"; -======= -import {LM_ManualExternalPriceSetter_v1} from "src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol"; ->>>>>>> c3d3d7cd (refactor:changed file structure) import { IERC20PaymentClientBase_v1, ERC20PaymentClientBaseV1Mock, @@ -43,10 +39,6 @@ import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {RedeemingBondingCurveBaseV1Mock} from "test/modules/fundingManager/bondingCurve/utils/mocks/RedeemingBondingCurveBaseV1Mock.sol"; -<<<<<<< HEAD -======= -import {BancorFormula} from "@fm/bondingCurve/formulas/BancorFormula.sol"; ->>>>>>> c3d3d7cd (refactor:changed file structure) /** * @title FM_PC_ExternalPrice_Redeeming_v1_Test From 5cc06f413aa0d522d95ce39bff0edda34dd98a5d Mon Sep 17 00:00:00 2001 From: Leandro Faria Date: Sun, 12 Jan 2025 19:38:17 -0500 Subject: [PATCH 4/6] fix:projectfeeAmount variable --- .../FM_PC_ExternalPrice_Redeeming_v1.sol | 2 +- .../external/ERC20Issuance_blacklist_v1.t.sol | 6 ------ .../LM_ManualExternalPriceSetter_v1.t.sol | 20 ------------------- 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/src/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.sol b/src/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.sol index 58f3f6106..64d44833d 100644 --- a/src/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.sol +++ b/src/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.sol @@ -578,7 +578,7 @@ contract FM_PC_ExternalPrice_Redeeming_v1 is // Create and emit the order. _createAndEmitOrder( - _receiver, _depositAmount, collateralRedeemAmount, protocolFeeAmount + _receiver, _depositAmount, collateralRedeemAmount, projectFeeAmount ); return (totalCollateralTokenMovedOut, issuanceFeeAmount); diff --git a/test/external/ERC20Issuance_blacklist_v1.t.sol b/test/external/ERC20Issuance_blacklist_v1.t.sol index 982f2b7dc..ffe2609ef 100644 --- a/test/external/ERC20Issuance_blacklist_v1.t.sol +++ b/test/external/ERC20Issuance_blacklist_v1.t.sol @@ -2,18 +2,12 @@ pragma solidity 0.8.23; import {Test} from "forge-std/Test.sol"; -<<<<<<< HEAD:test/external/ERC20Issuance_blacklist_v1.t.sol import { ERC20Issuance_Blacklist_v1, IERC20Issuance_Blacklist_v1 } from "@ex/token/ERC20Issuance_Blacklist_v1.sol"; import {ERC20Issuance_Blacklist_v1_Exposed} from "test/external/ERC20Issuance_blacklist_v1_exposed.sol"; -======= -import {ERC20Issuance_Blacklist_v1} from "@ex/token/ERC20Issuance_blacklist_v1.sol"; -import {ERC20Issuance_Blacklist_v1_Exposed} from "test/modules/fundingManager/oracle/utils/mocks/ERC20Issuance_blacklist_v1_exposed.sol"; -import {IERC20Issuance_Blacklist_v1} from "@ex/token/interfaces/IERC20Issuance_blacklist_v1.sol"; ->>>>>>> c3d3d7cd (refactor:changed file structure):test/modules/fundingManager/token/ERC20Issuance_blacklist_v1.t.sol /** * @title ERC20Issuance_Blacklist_v1_Test diff --git a/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol b/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol index 7c8ecd025..691b7e7e3 100644 --- a/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol +++ b/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol @@ -8,15 +8,7 @@ import {LM_ManualExternalPriceSetter_v1_Exposed} from "test/modules/logicModule/LM_ManualExternalPriceSetter_v1_exposed.sol"; import {ILM_ManualExternalPriceSetter_v1} from "@lm/interfaces/ILM_ManualExternalPriceSetter_v1.sol"; -<<<<<<< HEAD import {ERC20Decimals_Mock} from "test/utils/mocks/ERC20Decimals_Mock.sol"; -======= -import {IOrchestrator_v1} from - "src/orchestrator/interfaces/IOrchestrator_v1.sol"; -import {IModule_v1} from "src/modules/base/IModule_v1.sol"; -import {MockERC20} from - "test/modules/fundingManager/oracle/utils/mocks/MockERC20.sol"; ->>>>>>> c3d3d7cd (refactor:changed file structure) import {ERC20Mock} from "test/utils/mocks/ERC20Mock.sol"; import {AuthorizerV1Mock} from "test/utils/mocks/modules/AuthorizerV1Mock.sol"; import {Clones} from "@oz/proxy/Clones.sol"; @@ -42,19 +34,14 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { address priceSetter_; address user; -<<<<<<< HEAD ERC20Decimals_Mock inputToken; ERC20Mock outputToken; -======= ->>>>>>> c3d3d7cd (refactor:changed file structure) bytes32 constant PRICE_SETTER_ROLE = "PRICE_SETTER_ROLE"; uint8 constant INTERNAL_DECIMALS = 18; string constant TOKEN_NAME = "MOCK USDC"; string constant TOKEN_SYMBOL = "M-USDC"; - MockERC20 inputToken; - MockERC20 outputToken; // Module Constants uint constant MAJOR_VERSION = 1; @@ -75,15 +62,8 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { vm.startPrank(admin); // Create mock tokens with different decimals -<<<<<<< HEAD inputToken = new ERC20Decimals_Mock(TOKEN_NAME, TOKEN_SYMBOL, 6); // Like USDC outputToken = _token; // Like most ERC20s -======= - inputToken = new MockERC20(6); // Like USDC - outputToken = new MockERC20(18); // Like most ERC20s - - ->>>>>>> c3d3d7cd (refactor:changed file structure) // Setup price setter address impl = address(new LM_ManualExternalPriceSetter_v1_Exposed()); From 86fc0ccc59889fcdf791d0f4265863cf8ecc2845 Mon Sep 17 00:00:00 2001 From: Leandro Faria Date: Mon, 13 Jan 2025 23:38:33 -0500 Subject: [PATCH 5/6] audit fix:remove normalize prices --- .../LM_ManualExternalPriceSetter_v1.sol | 90 +---- ...M_ManualExternalPriceSetter_v1_exposed.sol | 7 - .../LM_ManualExternalPriceSetter_v1.t.sol | 354 +++++++++--------- ...M_ManualExternalPriceSetter_v1_exposed.sol | 16 +- 4 files changed, 194 insertions(+), 273 deletions(-) diff --git a/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol b/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol index a164c35b1..081b9d49a 100644 --- a/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol +++ b/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol @@ -71,21 +71,14 @@ contract LM_ManualExternalPriceSetter_v1 is /// @dev This role should be granted to trusted price feeders only. bytes32 public constant PRICE_SETTER_ROLE = "PRICE_SETTER_ROLE"; - /// @notice Number of decimal places used for internal price - /// representation - /// @dev All prices are normalized to this precision for consistent - /// calculations regardless of input/output token decimals. - uint8 private constant INTERNAL_DECIMALS = 18; // ------------------------------------------------------------------------- // State Variables - /// @notice The price for issuing tokens (normalized to - /// INTERNAL_DECIMALS). + /// @notice The price for issuing tokens (in collateral token decimals) uint private _issuancePrice; - /// @notice The price for redeeming tokens (normalized to - /// INTERNAL_DECIMALS). + /// @notice The price for redeeming tokens (in collateral token decimals) uint private _redemptionPrice; /// @notice Decimals of the collateral token (e.g., USDC with 6 decimals). @@ -119,19 +112,6 @@ contract LM_ManualExternalPriceSetter_v1 is // ------------------------------------------------------------------------- // External Functions - /// @notice Internal function to set the issuance price - /// @param price_ The price to set - function _setIssuancePrice(uint price_) internal { - _issuancePrice = _setAndValidatePrice(price_, _collateralTokenDecimals); - emit IssuancePriceSet(price_); - } - - /// @notice Internal function to set the redemption price - /// @param price_ The price to set - function _setRedemptionPrice(uint price_) internal { - _redemptionPrice = _setAndValidatePrice(price_, _issuanceTokenDecimals); - emit RedemptionPriceSet(price_); - } /// @inheritdoc ILM_ManualExternalPriceSetter_v1 function setIssuancePrice(uint price_) @@ -159,10 +139,8 @@ contract LM_ManualExternalPriceSetter_v1 is } // Normalize and set both prices atomically - _issuancePrice = - _normalizePrice(issuancePrice_, _collateralTokenDecimals); - _redemptionPrice = - _normalizePrice(redemptionPrice_, _issuanceTokenDecimals); + _setIssuancePrice(issuancePrice_); + _setRedemptionPrice(redemptionPrice_); // Emit events emit IssuancePriceSet(issuancePrice_); @@ -175,8 +153,7 @@ contract LM_ManualExternalPriceSetter_v1 is /// @dev Example: If price is 2 USDC/ISS, returns 2e18 (2 USDC needed for /// 1 ISS). function getPriceForIssuance() external view returns (uint) { - // Convert from internal precision to output token precision. - return _denormalizePrice(_issuancePrice, _issuanceTokenDecimals); + return _issuancePrice; } /// @notice Gets current price for token redemption (selling tokens). @@ -185,61 +162,26 @@ contract LM_ManualExternalPriceSetter_v1 is /// @dev Example: If price is 1.9 USDC/ISS, returns 1.9e18 (1.9 USDC /// received for 1 ISS). function getPriceForRedemption() external view returns (uint) { - // Convert from internal precision to output token precision. - return _denormalizePrice(_redemptionPrice, _issuanceTokenDecimals); + return _redemptionPrice; } //-------------------------------------------------------------------------- // Internal Functions - /// @notice Normalizes a price from token decimals to internal decimals. - /// @param price_ The price to normalize. - /// @param tokenDecimals_ The decimals of the token the price is - /// denominated in. - /// @return The normalized price with INTERNAL_DECIMALS precision. - function _normalizePrice(uint price_, uint8 tokenDecimals_) - internal - pure - returns (uint) - { - if (tokenDecimals_ == INTERNAL_DECIMALS) return price_; - - if (tokenDecimals_ > INTERNAL_DECIMALS) { - return price_ / (10 ** (tokenDecimals_ - INTERNAL_DECIMALS)); - } else { - return price_ * (10 ** (INTERNAL_DECIMALS - tokenDecimals_)); - } - } - - /// @notice Denormalizes a price from internal decimals to token decimals. - /// @param price_ The price to denormalize. - /// @param tokenDecimals_ The target token decimals. - /// @return The denormalized price with tokenDecimals_ precision. - function _denormalizePrice(uint price_, uint8 tokenDecimals_) - internal - pure - returns (uint) - { - if (tokenDecimals_ == INTERNAL_DECIMALS) return price_; - - if (tokenDecimals_ > INTERNAL_DECIMALS) { - return price_ * (10 ** (tokenDecimals_ - INTERNAL_DECIMALS)); - } else { - return price_ / (10 ** (INTERNAL_DECIMALS - tokenDecimals_)); - } + /// @notice Internal function to set the issuance price + /// @param price_ The price to set + function _setIssuancePrice(uint price_) internal { + if (price_ == 0) revert Module__LM_ExternalPriceSetter__InvalidPrice(); + _issuancePrice = price_; + emit IssuancePriceSet(price_); } - /// @notice Internal function to set and validate the price + /// @notice Internal function to set the redemption price /// @param price_ The price to set - /// @param tokenDecimals_ The decimals of the token the price is - /// denominated in. - /// @return The normalized price with INTERNAL_DECIMALS precision. - function _setAndValidatePrice(uint price_, uint8 tokenDecimals_) - internal - returns (uint) - { + function _setRedemptionPrice(uint price_) internal { if (price_ == 0) revert Module__LM_ExternalPriceSetter__InvalidPrice(); - return _normalizePrice(price_, tokenDecimals_); + _redemptionPrice = price_; + emit RedemptionPriceSet(price_); } /// @dev Storage gap for upgradeable contracts. diff --git a/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol b/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol index 215edfa8a..68e985b41 100644 --- a/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol +++ b/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol @@ -8,11 +8,4 @@ import {IAuthorizer_v1} from "@aut/IAuthorizer_v1.sol"; contract LM_ManualExternalPriceSetter_v1_Exposed is LM_ManualExternalPriceSetter_v1 { constructor() {} - function exposed_normalizePrice(uint256 price_, uint8 tokenDecimals_) external pure returns (uint256) { - return _normalizePrice(price_, tokenDecimals_); - } - - function exposed_denormalizePrice(uint256 price_, uint8 tokenDecimals_) external view returns (uint256) { - return _denormalizePrice(price_, tokenDecimals_); - } } diff --git a/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol b/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol index 691b7e7e3..25f1bbdf4 100644 --- a/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol +++ b/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol @@ -308,7 +308,7 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { vm.assume(unauthorizedUser != address(this)); vm.assume(unauthorizedUser != priceSetter_); - price = bound(price, 1e18, 1_000_000_000_000 * 1e18); + price = bound(price, 1e6, 1_000_000_000_000 * 1e6); // Test unauthorized access vm.startPrank(unauthorizedUser); @@ -342,7 +342,7 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { ); // Test price independence by setting a different issuance price - uint issuancePrice = price / 1e12 + 1; // Convert to 6 decimals for issuance + uint issuancePrice = price; // Convert to 6 decimals for issuance priceSetter.setIssuancePrice(issuancePrice); // Verify both prices maintain their values independently @@ -353,189 +353,189 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { ); assertEq( priceSetter.getPriceForIssuance(), - issuancePrice * 1e12, + issuancePrice, "Issuance price not set correctly" ); vm.stopPrank(); } - // ═══════════════════════════════════════════════════════════════════════════════════════════════════════ - // Price Normalization Tests - // ═══════════════════════════════════════════════════════════════════════════════════════════════════════ + // // ═══════════════════════════════════════════════════════════════════════════════════════════════════════ + // // Price Normalization Tests + // // ═══════════════════════════════════════════════════════════════════════════════════════════════════════ - /* testPriceNormalization_GivenDifferentDecimals() - Tests the price normalization function with different decimal configurations + // /* testPriceNormalization_GivenDifferentDecimals() + // Tests the price normalization function with different decimal configurations - Tree structure: - ├── Given a random price and decimals (1-24) - │ ├── When decimals < 18 - │ │ ├── Then should scale up price correctly - │ │ └── Then scaling should be reversible - │ ├── When decimals > 18 - │ │ ├── Then should scale down price correctly - │ │ └── Then rounding error should be within bounds - │ └── When decimals = 18 - │ └── Then price should remain unchanged - └── Given minimum price (1) - ├── When decimals < 18: should scale up to 10^(18-decimals) - ├── When decimals > 18: should round to 0 - └── When decimals = 18: should remain 1 - */ - function testPriceNormalization_GivenDifferentDecimals( - uint price, - uint8 decimals - ) public { - vm.assume(price < type(uint).max / 1e18); - decimals = uint8(bound(decimals, 1, 24)); - - uint normalizedPrice = - priceSetter.exposed_normalizePrice(price, decimals); - uint scaleFactor = - decimals < 18 ? 10 ** (18 - decimals) : 10 ** (decimals - 18); - - if (decimals < 18) { - // Scaling up (e.g., USDC-6, WBTC-8) - assertEq(normalizedPrice, price * scaleFactor, "Scaling up failed"); - assertEq(normalizedPrice / scaleFactor, price, "Not reversible"); - } else if (decimals > 18) { - // Scaling down (tokens with more than 18 decimals) - assertEq( - normalizedPrice, price / scaleFactor, "Scaling down failed" - ); - assertTrue( - price - (normalizedPrice * scaleFactor) < scaleFactor, - "High rounding error" - ); - } else { - // No scaling (18 decimals like ETH) - assertEq(normalizedPrice, price, "Price changed unnecessarily"); - } - - // Edge case: minimum price (1) - if (price == 1) { - uint minPrice = priceSetter.exposed_normalizePrice(1, decimals); - assertEq( - minPrice, - decimals < 18 ? scaleFactor : decimals > 18 ? 0 : 1, - "Minimum price handling failed" - ); - } - } - - /* testPriceDenormalization_GivenDifferentDecimals() - Tests price denormalization from internal (18) decimals to token decimals + // Tree structure: + // ├── Given a random price and decimals (1-24) + // │ ├── When decimals < 18 + // │ │ ├── Then should scale up price correctly + // │ │ └── Then scaling should be reversible + // │ ├── When decimals > 18 + // │ │ ├── Then should scale down price correctly + // │ │ └── Then rounding error should be within bounds + // │ └── When decimals = 18 + // │ └── Then price should remain unchanged + // └── Given minimum price (1) + // ├── When decimals < 18: should scale up to 10^(18-decimals) + // ├── When decimals > 18: should round to 0 + // └── When decimals = 18: should remain 1 + // */ + // function testPriceNormalization_GivenDifferentDecimals( + // uint price, + // uint8 decimals + // ) public { + // vm.assume(price < type(uint).max / 1e18); + // decimals = uint8(bound(decimals, 1, 24)); + + // uint normalizedPrice = + // priceSetter.exposed_normalizePrice(price, decimals); + // uint scaleFactor = + // decimals < 18 ? 10 ** (18 - decimals) : 10 ** (decimals - 18); + + // if (decimals < 18) { + // // Scaling up (e.g., USDC-6, WBTC-8) + // assertEq(normalizedPrice, price * scaleFactor, "Scaling up failed"); + // assertEq(normalizedPrice / scaleFactor, price, "Not reversible"); + // } else if (decimals > 18) { + // // Scaling down (tokens with more than 18 decimals) + // assertEq( + // normalizedPrice, price / scaleFactor, "Scaling down failed" + // ); + // assertTrue( + // price - (normalizedPrice * scaleFactor) < scaleFactor, + // "High rounding error" + // ); + // } else { + // // No scaling (18 decimals like ETH) + // assertEq(normalizedPrice, price, "Price changed unnecessarily"); + // } + + // // Edge case: minimum price (1) + // if (price == 1) { + // uint minPrice = priceSetter.exposed_normalizePrice(1, decimals); + // assertEq( + // minPrice, + // decimals < 18 ? scaleFactor : decimals > 18 ? 0 : 1, + // "Minimum price handling failed" + // ); + // } + // } + + // /* testPriceDenormalization_GivenDifferentDecimals() + // Tests price denormalization from internal (18) decimals to token decimals - Tree structure: - ├── Given a normalized price (18 decimals) - │ ├── When converting to lower decimals - │ │ ├── Then should scale down correctly - │ │ └── Then rounding error should be within bounds - │ ├── When converting to same decimals - │ │ └── Then price should remain unchanged - │ └── When converting to higher decimals - │ └── Then should scale up correctly - └── Given minimum price (1) - ├── When decimals < 18: should scale down to 0 or 1 - ├── When decimals = 18: should remain 1 - └── When decimals > 18: should scale up to 10^(decimals-18) - */ - function testPriceDenormalization_GivenDifferentDecimals( - uint price, - uint8 decimals - ) public { - vm.assume(price < type(uint).max / 1e18); - decimals = uint8(bound(decimals, 1, 24)); - - uint denormalizedPrice = - priceSetter.exposed_denormalizePrice(price, decimals); - uint scaleFactor = - decimals < 18 ? 10 ** (18 - decimals) : 10 ** (decimals - 18); - - if (decimals < 18) { - // Scaling down (e.g., to USDC-6, WBTC-8) - assertEq( - denormalizedPrice, price / scaleFactor, "Scaling down failed" - ); - assertTrue( - price - (denormalizedPrice * scaleFactor) < scaleFactor, - "High rounding error" - ); - } else if (decimals > 18) { - // Scaling up (to more than 18 decimals) - assertEq( - denormalizedPrice, price * scaleFactor, "Scaling up failed" - ); - assertEq(denormalizedPrice / scaleFactor, price, "Not reversible"); - } else { - // No scaling (18 decimals like ETH) - assertEq(denormalizedPrice, price, "Price changed unnecessarily"); - } - - // Edge case: minimum price (1) - if (price == 1) { - uint minPrice = priceSetter.exposed_denormalizePrice(1, decimals); - assertEq( - minPrice, - decimals < 18 ? 0 : decimals > 18 ? scaleFactor : 1, - "Minimum price handling failed" - ); - } - } - - /* testPriceNormalizationCycle_GivenDifferentDecimals() - Tests that normalizing and then denormalizing a price maintains the original value - when possible, accounting for precision loss in certain cases. + // Tree structure: + // ├── Given a normalized price (18 decimals) + // │ ├── When converting to lower decimals + // │ │ ├── Then should scale down correctly + // │ │ └── Then rounding error should be within bounds + // │ ├── When converting to same decimals + // │ │ └── Then price should remain unchanged + // │ └── When converting to higher decimals + // │ └── Then should scale up correctly + // └── Given minimum price (1) + // ├── When decimals < 18: should scale down to 0 or 1 + // ├── When decimals = 18: should remain 1 + // └── When decimals > 18: should scale up to 10^(decimals-18) + // */ + // function testPriceDenormalization_GivenDifferentDecimals( + // uint price, + // uint8 decimals + // ) public { + // vm.assume(price < type(uint).max / 1e18); + // decimals = uint8(bound(decimals, 1, 24)); + + // uint denormalizedPrice = + // priceSetter.exposed_denormalizePrice(price, decimals); + // uint scaleFactor = + // decimals < 18 ? 10 ** (18 - decimals) : 10 ** (decimals - 18); + + // if (decimals < 18) { + // // Scaling down (e.g., to USDC-6, WBTC-8) + // assertEq( + // denormalizedPrice, price / scaleFactor, "Scaling down failed" + // ); + // assertTrue( + // price - (denormalizedPrice * scaleFactor) < scaleFactor, + // "High rounding error" + // ); + // } else if (decimals > 18) { + // // Scaling up (to more than 18 decimals) + // assertEq( + // denormalizedPrice, price * scaleFactor, "Scaling up failed" + // ); + // assertEq(denormalizedPrice / scaleFactor, price, "Not reversible"); + // } else { + // // No scaling (18 decimals like ETH) + // assertEq(denormalizedPrice, price, "Price changed unnecessarily"); + // } + + // // Edge case: minimum price (1) + // if (price == 1) { + // uint minPrice = priceSetter.exposed_denormalizePrice(1, decimals); + // assertEq( + // minPrice, + // decimals < 18 ? 0 : decimals > 18 ? scaleFactor : 1, + // "Minimum price handling failed" + // ); + // } + // } + + // /* testPriceNormalizationCycle_GivenDifferentDecimals() + // Tests that normalizing and then denormalizing a price maintains the original value + // when possible, accounting for precision loss in certain cases. - Tree structure: - ├── Given a random price and decimals - │ ├── When normalizing then denormalizing - │ │ ├── Then should match original for decimals <= 18 - │ │ └── Then should be within error bounds for decimals > 18 - │ └── When denormalizing then normalizing - │ ├── Then should match original if result > minimum viable price - │ └── Then should be 0 if below minimum viable price - └── Given minimum viable price for decimals - └── When running full cycle - └── Then should preserve value if possible - */ - function testPriceNormalizationCycle_GivenDifferentDecimals( - uint price, - uint8 decimals - ) public { - vm.assume(price < type(uint).max / 1e18); - - decimals = uint8(bound(decimals, 1, 18)); - - // First cycle: normalization → denormalization - uint normalized = priceSetter.exposed_normalizePrice(price, decimals); - uint denormalized = - priceSetter.exposed_denormalizePrice(normalized, decimals); - - // Price should be exactly equal after the first cycle - assertEq(denormalized, price, "Price changed after normalization cycle"); - - // For the second cycle, use the normalized price as base - // since this is the format in which it's stored internally - uint normalized2 = - priceSetter.exposed_normalizePrice(denormalized, decimals); - uint denormalized2 = - priceSetter.exposed_denormalizePrice(normalized2, decimals); - - // Verify that the final denormalized price equals the original - assertEq( - denormalized2, - price, - "Price changed after second normalization cycle" - ); - - // Test with minimum viable price (1) - uint minPrice = 1; - normalized = priceSetter.exposed_normalizePrice(minPrice, decimals); - denormalized = - priceSetter.exposed_denormalizePrice(normalized, decimals); - - // Minimum viable price should be preserved exactly - assertEq(denormalized, minPrice, "Minimum viable price not preserved"); - } + // Tree structure: + // ├── Given a random price and decimals + // │ ├── When normalizing then denormalizing + // │ │ ├── Then should match original for decimals <= 18 + // │ │ └── Then should be within error bounds for decimals > 18 + // │ └── When denormalizing then normalizing + // │ ├── Then should match original if result > minimum viable price + // │ └── Then should be 0 if below minimum viable price + // └── Given minimum viable price for decimals + // └── When running full cycle + // └── Then should preserve value if possible + // */ + // function testPriceNormalizationCycle_GivenDifferentDecimals( + // uint price, + // uint8 decimals + // ) public { + // vm.assume(price < type(uint).max / 1e18); + + // decimals = uint8(bound(decimals, 1, 18)); + + // // First cycle: normalization → denormalization + // uint normalized = priceSetter.exposed_normalizePrice(price, decimals); + // uint denormalized = + // priceSetter.exposed_denormalizePrice(normalized, decimals); + + // // Price should be exactly equal after the first cycle + // assertEq(denormalized, price, "Price changed after normalization cycle"); + + // // For the second cycle, use the normalized price as base + // // since this is the format in which it's stored internally + // uint normalized2 = + // priceSetter.exposed_normalizePrice(denormalized, decimals); + // uint denormalized2 = + // priceSetter.exposed_denormalizePrice(normalized2, decimals); + + // // Verify that the final denormalized price equals the original + // assertEq( + // denormalized2, + // price, + // "Price changed after second normalization cycle" + // ); + + // // Test with minimum viable price (1) + // uint minPrice = 1; + // normalized = priceSetter.exposed_normalizePrice(minPrice, decimals); + // denormalized = + // priceSetter.exposed_denormalizePrice(normalized, decimals); + + // // Minimum viable price should be preserved exactly + // assertEq(denormalized, minPrice, "Minimum viable price not preserved"); + // } } diff --git a/test/modules/logicModule/LM_ManualExternalPriceSetter_v1_exposed.sol b/test/modules/logicModule/LM_ManualExternalPriceSetter_v1_exposed.sol index 0d7c015df..8c98c39b5 100644 --- a/test/modules/logicModule/LM_ManualExternalPriceSetter_v1_exposed.sol +++ b/test/modules/logicModule/LM_ManualExternalPriceSetter_v1_exposed.sol @@ -7,19 +7,5 @@ import {LM_ManualExternalPriceSetter_v1} from contract LM_ManualExternalPriceSetter_v1_Exposed is LM_ManualExternalPriceSetter_v1 { - function exposed_normalizePrice(uint price_, uint8 tokenDecimals_) - external - pure - returns (uint) - { - return _normalizePrice(price_, tokenDecimals_); - } - - function exposed_denormalizePrice(uint price_, uint8 tokenDecimals_) - external - view - returns (uint) - { - return _denormalizePrice(price_, tokenDecimals_); - } + constructor() {} } From d74527a1a7f9ee6e6ef2f68541a13e494bf4c3fc Mon Sep 17 00:00:00 2001 From: Leandro Faria Date: Mon, 13 Jan 2025 23:50:04 -0500 Subject: [PATCH 6/6] fix:failing test for issuance decimals --- .../LM_ManualExternalPriceSetter_v1.sol | 12 +- .../FM_PC_ExternalPrice_Redeeming_v1.t.sol | 8 +- ...M_ManualExternalPriceSetter_v1_exposed.sol | 11 +- .../LM_ManualExternalPriceSetter_v1.t.sol | 184 +----------------- 4 files changed, 16 insertions(+), 199 deletions(-) diff --git a/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol b/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol index 081b9d49a..ac597c1ac 100644 --- a/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol +++ b/src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol @@ -71,7 +71,6 @@ contract LM_ManualExternalPriceSetter_v1 is /// @dev This role should be granted to trusted price feeders only. bytes32 public constant PRICE_SETTER_ROLE = "PRICE_SETTER_ROLE"; - // ------------------------------------------------------------------------- // State Variables @@ -85,10 +84,6 @@ contract LM_ManualExternalPriceSetter_v1 is /// @dev This is the token used to pay/buy with. uint8 private _collateralTokenDecimals; - /// @notice Decimals of the issuance token (e.g., ISS with 18 decimals). - /// @dev This is the token being bought/sold. - uint8 private _issuanceTokenDecimals; - // ------------------------------------------------------------------------- // Initialization @@ -100,19 +95,16 @@ contract LM_ManualExternalPriceSetter_v1 is ) external override(Module_v1) initializer { __Module_init(orchestrator_, metadata_); - // Decode collateral and issuance token addresses from configData_. - (address collateralToken, address issuanceToken) = - abi.decode(configData_, (address, address)); + // Decode collateral token address from configData_. + (address collateralToken) = abi.decode(configData_, (address)); // Store token decimals for price normalization. _collateralTokenDecimals = IERC20Metadata(collateralToken).decimals(); - _issuanceTokenDecimals = IERC20Metadata(issuanceToken).decimals(); } // ------------------------------------------------------------------------- // External Functions - /// @inheritdoc ILM_ManualExternalPriceSetter_v1 function setIssuancePrice(uint price_) external diff --git a/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol b/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol index 90e10f493..37607c859 100644 --- a/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol +++ b/test/modules/fundingManager/oracle/FM_PC_ExternalPrice_Redeeming_v1.t.sol @@ -948,7 +948,8 @@ contract FM_PC_ExternalPrice_Redeeming_v1_Test is ModuleTest { vm.assume(nonWhitelisted != admin); vm.assume(nonWhitelisted != address(this)); - roleId = _authorizer.generateRoleId(address(fundingManager), WHITELIST_ROLE); + roleId = + _authorizer.generateRoleId(address(fundingManager), WHITELIST_ROLE); // Prepare buy conditions with a fixed amount uint minAmount = 1 * 10 ** _token.decimals(); uint maxAmount = 1_000_000 * 10 ** _token.decimals(); @@ -1077,7 +1078,7 @@ contract FM_PC_ExternalPrice_Redeeming_v1_Test is ModuleTest { // Given - Setup buying conditions fundingManager.grantModuleRole(WHITELIST_ROLE, buyer); - + // Given - Mint tokens and approve _token.mint(buyer, buyAmount); vm.startPrank(buyer); @@ -1093,7 +1094,8 @@ contract FM_PC_ExternalPrice_Redeeming_v1_Test is ModuleTest { // Given - Calculate expected tokens and store initial balances uint expectedTokens = _calculateExpectedIssuance(buyAmount); uint buyerBalanceBefore = _token.balanceOf(buyer); - uint projectTreasuryBalanceBefore = _token.balanceOf(fundingManager.getProjectTreasury()); + uint projectTreasuryBalanceBefore = + _token.balanceOf(fundingManager.getProjectTreasury()); uint buyerIssuedTokensBefore = issuanceToken.balanceOf(buyer); // When - Buy tokens with max amount diff --git a/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol b/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol index 68e985b41..e485c332b 100644 --- a/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol +++ b/test/modules/fundingManager/oracle/utils/mocks/LM_ManualExternalPriceSetter_v1_exposed.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.23; -import {LM_ManualExternalPriceSetter_v1} from "src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol"; -import {IOrchestrator_v1} from "src/orchestrator/interfaces/IOrchestrator_v1.sol"; +import {LM_ManualExternalPriceSetter_v1} from + "src/modules/logicModule/LM_ManualExternalPriceSetter_v1.sol"; +import {IOrchestrator_v1} from + "src/orchestrator/interfaces/IOrchestrator_v1.sol"; import {IAuthorizer_v1} from "@aut/IAuthorizer_v1.sol"; -contract LM_ManualExternalPriceSetter_v1_Exposed is LM_ManualExternalPriceSetter_v1 { +contract LM_ManualExternalPriceSetter_v1_Exposed is + LM_ManualExternalPriceSetter_v1 +{ constructor() {} - } diff --git a/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol b/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol index 25f1bbdf4..6b7d04385 100644 --- a/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol +++ b/test/modules/logicModule/LM_ManualExternalPriceSetter_v1.t.sol @@ -42,7 +42,6 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { string constant TOKEN_NAME = "MOCK USDC"; string constant TOKEN_SYMBOL = "M-USDC"; - // Module Constants uint constant MAJOR_VERSION = 1; uint constant MINOR_VERSION = 0; @@ -60,7 +59,7 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { user = makeAddr("user"); vm.startPrank(admin); - + // Create mock tokens with different decimals inputToken = new ERC20Decimals_Mock(TOKEN_NAME, TOKEN_SYMBOL, 6); // Like USDC outputToken = _token; // Like most ERC20s @@ -275,7 +274,7 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { // Price should be normalized from 6 decimals to 18 decimals assertEq( priceSetter.getPriceForIssuance(), - price * 1e12, + price, "Issuance price not set correctly" ); @@ -359,183 +358,4 @@ contract LM_ManualExternalPriceSetter_v1_Test is ModuleTest { vm.stopPrank(); } - - // // ═══════════════════════════════════════════════════════════════════════════════════════════════════════ - // // Price Normalization Tests - // // ═══════════════════════════════════════════════════════════════════════════════════════════════════════ - - // /* testPriceNormalization_GivenDifferentDecimals() - // Tests the price normalization function with different decimal configurations - - // Tree structure: - // ├── Given a random price and decimals (1-24) - // │ ├── When decimals < 18 - // │ │ ├── Then should scale up price correctly - // │ │ └── Then scaling should be reversible - // │ ├── When decimals > 18 - // │ │ ├── Then should scale down price correctly - // │ │ └── Then rounding error should be within bounds - // │ └── When decimals = 18 - // │ └── Then price should remain unchanged - // └── Given minimum price (1) - // ├── When decimals < 18: should scale up to 10^(18-decimals) - // ├── When decimals > 18: should round to 0 - // └── When decimals = 18: should remain 1 - // */ - // function testPriceNormalization_GivenDifferentDecimals( - // uint price, - // uint8 decimals - // ) public { - // vm.assume(price < type(uint).max / 1e18); - // decimals = uint8(bound(decimals, 1, 24)); - - // uint normalizedPrice = - // priceSetter.exposed_normalizePrice(price, decimals); - // uint scaleFactor = - // decimals < 18 ? 10 ** (18 - decimals) : 10 ** (decimals - 18); - - // if (decimals < 18) { - // // Scaling up (e.g., USDC-6, WBTC-8) - // assertEq(normalizedPrice, price * scaleFactor, "Scaling up failed"); - // assertEq(normalizedPrice / scaleFactor, price, "Not reversible"); - // } else if (decimals > 18) { - // // Scaling down (tokens with more than 18 decimals) - // assertEq( - // normalizedPrice, price / scaleFactor, "Scaling down failed" - // ); - // assertTrue( - // price - (normalizedPrice * scaleFactor) < scaleFactor, - // "High rounding error" - // ); - // } else { - // // No scaling (18 decimals like ETH) - // assertEq(normalizedPrice, price, "Price changed unnecessarily"); - // } - - // // Edge case: minimum price (1) - // if (price == 1) { - // uint minPrice = priceSetter.exposed_normalizePrice(1, decimals); - // assertEq( - // minPrice, - // decimals < 18 ? scaleFactor : decimals > 18 ? 0 : 1, - // "Minimum price handling failed" - // ); - // } - // } - - // /* testPriceDenormalization_GivenDifferentDecimals() - // Tests price denormalization from internal (18) decimals to token decimals - - // Tree structure: - // ├── Given a normalized price (18 decimals) - // │ ├── When converting to lower decimals - // │ │ ├── Then should scale down correctly - // │ │ └── Then rounding error should be within bounds - // │ ├── When converting to same decimals - // │ │ └── Then price should remain unchanged - // │ └── When converting to higher decimals - // │ └── Then should scale up correctly - // └── Given minimum price (1) - // ├── When decimals < 18: should scale down to 0 or 1 - // ├── When decimals = 18: should remain 1 - // └── When decimals > 18: should scale up to 10^(decimals-18) - // */ - // function testPriceDenormalization_GivenDifferentDecimals( - // uint price, - // uint8 decimals - // ) public { - // vm.assume(price < type(uint).max / 1e18); - // decimals = uint8(bound(decimals, 1, 24)); - - // uint denormalizedPrice = - // priceSetter.exposed_denormalizePrice(price, decimals); - // uint scaleFactor = - // decimals < 18 ? 10 ** (18 - decimals) : 10 ** (decimals - 18); - - // if (decimals < 18) { - // // Scaling down (e.g., to USDC-6, WBTC-8) - // assertEq( - // denormalizedPrice, price / scaleFactor, "Scaling down failed" - // ); - // assertTrue( - // price - (denormalizedPrice * scaleFactor) < scaleFactor, - // "High rounding error" - // ); - // } else if (decimals > 18) { - // // Scaling up (to more than 18 decimals) - // assertEq( - // denormalizedPrice, price * scaleFactor, "Scaling up failed" - // ); - // assertEq(denormalizedPrice / scaleFactor, price, "Not reversible"); - // } else { - // // No scaling (18 decimals like ETH) - // assertEq(denormalizedPrice, price, "Price changed unnecessarily"); - // } - - // // Edge case: minimum price (1) - // if (price == 1) { - // uint minPrice = priceSetter.exposed_denormalizePrice(1, decimals); - // assertEq( - // minPrice, - // decimals < 18 ? 0 : decimals > 18 ? scaleFactor : 1, - // "Minimum price handling failed" - // ); - // } - // } - - // /* testPriceNormalizationCycle_GivenDifferentDecimals() - // Tests that normalizing and then denormalizing a price maintains the original value - // when possible, accounting for precision loss in certain cases. - - // Tree structure: - // ├── Given a random price and decimals - // │ ├── When normalizing then denormalizing - // │ │ ├── Then should match original for decimals <= 18 - // │ │ └── Then should be within error bounds for decimals > 18 - // │ └── When denormalizing then normalizing - // │ ├── Then should match original if result > minimum viable price - // │ └── Then should be 0 if below minimum viable price - // └── Given minimum viable price for decimals - // └── When running full cycle - // └── Then should preserve value if possible - // */ - // function testPriceNormalizationCycle_GivenDifferentDecimals( - // uint price, - // uint8 decimals - // ) public { - // vm.assume(price < type(uint).max / 1e18); - - // decimals = uint8(bound(decimals, 1, 18)); - - // // First cycle: normalization → denormalization - // uint normalized = priceSetter.exposed_normalizePrice(price, decimals); - // uint denormalized = - // priceSetter.exposed_denormalizePrice(normalized, decimals); - - // // Price should be exactly equal after the first cycle - // assertEq(denormalized, price, "Price changed after normalization cycle"); - - // // For the second cycle, use the normalized price as base - // // since this is the format in which it's stored internally - // uint normalized2 = - // priceSetter.exposed_normalizePrice(denormalized, decimals); - // uint denormalized2 = - // priceSetter.exposed_denormalizePrice(normalized2, decimals); - - // // Verify that the final denormalized price equals the original - // assertEq( - // denormalized2, - // price, - // "Price changed after second normalization cycle" - // ); - - // // Test with minimum viable price (1) - // uint minPrice = 1; - // normalized = priceSetter.exposed_normalizePrice(minPrice, decimals); - // denormalized = - // priceSetter.exposed_denormalizePrice(normalized, decimals); - - // // Minimum viable price should be preserved exactly - // assertEq(denormalized, minPrice, "Minimum viable price not preserved"); - // } }