From 35aa6ae72fea7f58260c2d52494455d579814d08 Mon Sep 17 00:00:00 2001 From: Doga Oztuzun Date: Fri, 27 Sep 2024 12:51:14 +0000 Subject: [PATCH 1/6] native token issuance contracts have been added --- src/external/token/IAllowList.sol | 21 ++ src/external/token/INativeIssuance_v1.sol | 42 +++ src/external/token/INativeMinter.sol | 9 + src/external/token/NativeIssuance_v1.sol | 238 ++++++++++++ ...ncor_Redeeming_VirtualSupply_Native_v1.sol | 42 +++ ...ncor_Redeeming_VirtualSupply_Native_v1.sol | 17 + test/external/NativeIssuance_v1.t.sol | 213 +++++++++++ ...or_Redeeming_VirtualSupply_Native_v1.t.sol | 355 ++++++++++++++++++ ...r_Redeeming_VirtualSupply_NativeV1Mock.sol | 61 +++ .../utils/mocks/external/NativeMinterMock.sol | 21 ++ 10 files changed, 1019 insertions(+) create mode 100644 src/external/token/IAllowList.sol create mode 100644 src/external/token/INativeIssuance_v1.sol create mode 100644 src/external/token/INativeMinter.sol create mode 100644 src/external/token/NativeIssuance_v1.sol create mode 100644 src/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol create mode 100644 src/modules/fundingManager/bondingCurve/interfaces/IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol create mode 100644 test/external/NativeIssuance_v1.t.sol create mode 100644 test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol create mode 100644 test/modules/fundingManager/bondingCurve/utils/mocks/FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock.sol create mode 100644 test/utils/mocks/external/NativeMinterMock.sol diff --git a/src/external/token/IAllowList.sol b/src/external/token/IAllowList.sol new file mode 100644 index 000000000..1ae72378b --- /dev/null +++ b/src/external/token/IAllowList.sol @@ -0,0 +1,21 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IAllowList { + event RoleSet(uint256 indexed role, address indexed account, address indexed sender, uint256 oldRole); + + // Set [addr] to have the admin role over the precompile contract. + function setAdmin(address addr) external; + + // Set [addr] to be enabled on the precompile contract. + function setEnabled(address addr) external; + + // Set [addr] to have the manager role over the precompile contract. + function setManager(address addr) external; + + // Set [addr] to have no role for the precompile contract. + function setNone(address addr) external; + + // Read the status of [addr]. + function readAllowList(address addr) external view returns (uint256 role); +} diff --git a/src/external/token/INativeIssuance_v1.sol b/src/external/token/INativeIssuance_v1.sol new file mode 100644 index 000000000..ccd5dffd6 --- /dev/null +++ b/src/external/token/INativeIssuance_v1.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity 0.8.23; + +import {IERC20Issuance_v1} from "@ex/token/IERC20Issuance_v1.sol"; + +interface INativeIssuance_v1 is IERC20Issuance_v1 { + //-------------------------------------------------------------------------- + // Events + + /** + * @dev Emitted when `value` tokens are minted to an account to (`to`). + * + * Note that `value` may be zero. + */ + event Minted(address indexed to, uint value); + + /** + * @dev Emitted when `value` tokens are burned from an account from (`from`). + * + * Note that `value` may be zero. + */ + event Burned(address indexed from, uint value); + + //-------------------------------------------------------------------------- + // Errors + + error INativeIssuance_v1__NotSupported(); + error INativeIssuance_v1__InvalidAddress(); + error INativeIssuance_v1__InvalidAmount(); + + //-------------------------------------------------------------------------- + // Functions + + // Write + + /** + * @notice Deposits native tokens to be able to burn native tokens from the specified address. + * @dev `msg.value` is the amount of native tokens to be deposited. + * @param from The address that will have native tokens deposited for. + */ + function depositNative(address from) external payable; +} diff --git a/src/external/token/INativeMinter.sol b/src/external/token/INativeMinter.sol new file mode 100644 index 000000000..f9aabd777 --- /dev/null +++ b/src/external/token/INativeMinter.sol @@ -0,0 +1,9 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +import "./IAllowList.sol"; + +interface INativeMinter is IAllowList { + event NativeCoinMinted(address indexed sender, address indexed recipient, uint256 amount); + // Mint [amount] number of native coins and send to [addr] + function mintNativeCoin(address addr, uint256 amount) external; +} diff --git a/src/external/token/NativeIssuance_v1.sol b/src/external/token/NativeIssuance_v1.sol new file mode 100644 index 000000000..06d31cbfe --- /dev/null +++ b/src/external/token/NativeIssuance_v1.sol @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity 0.8.23; + +import {Ownable} from "@oz/access/Ownable.sol"; +import {Address} from "@oz/utils/Address.sol"; +import {IERC20Metadata} from "@oz/token/ERC20/extensions/IERC20Metadata.sol"; + +import {INativeMinter} from "@ex/token/INativeMinter.sol"; +import {INativeIssuance_v1} from "@ex/token/INativeIssuance_v1.sol"; + +/** + * @title Native Token Issuance + * @notice This contract facilitates the issuance of native tokens on Avalanche L1s. It is designed to be used only by whitelisted minters, ensuring controlled token creation and destruction. + * @dev The contract adheres to the ERC20 standard for interoperability with Inverter Network contracts. + * - Maintains a list of allowed minters for managing who can call mint and burn functions. + * - Allows minting and burning of tokens by members of this whitelist. + * - Mints native tokens through Avalanche Subnet-EVM's Precompiled Native Minter contract. + * - Tokens burned are transferred to a designated burn address, with no recovery option for these tokens. + * - The `depositNative` function is used to deposit native tokens into the contract, which can then be burned by calling the `burn` function to send them to the burn address. + * @author Inverter Network + */ +contract NativeIssuance_v1 is INativeIssuance_v1, IERC20Metadata, Ownable { + using Address for address payable; + + /** + * @notice The address of the native minter precompile. + */ + INativeMinter public constant NATIVE_MINTER = + INativeMinter(0x0200000000000000000000000000000000000001); + + /** + * @notice The address where native tokens are sent in order to be burned for the bonding curve. + * + * @dev This address was chosen arbitrarily. + */ + address public constant BURNED_FOR_BONDINGCURVE_ADDRESS = + 0x0100000000000000000000000000000000010203; + + // State Variables + + /** + * @notice Total number of tokens minted by this contract through the native minter precompile. + */ + uint public totalMinted; + + /** + * @notice Mapping of the amount of native tokens deposited for burning by an address. + */ + mapping(address => uint) public depositsForBurning; + + /// @dev The mapping of allowed minters. + mapping(address => bool) public allowedMinters; + + //------------------------------------------------------------------------------ + // Modifiers + + /// @dev Modifier to guarantee the caller is a minter. + modifier onlyMinter() { + if (!allowedMinters[_msgSender()]) { + revert IERC20Issuance__CallerIsNotMinter(); + } + _; + } + + //------------------------------------------------------------------------------ + // Constructor + constructor(address initialOwner) Ownable(initialOwner) { + _setMinter(initialOwner, true); + } + + receive() external payable {} + fallback() external payable {} + + /** + * @notice Mints native tokens to the specified address. + * @param to The address that will receive the minted tokens. + * @param amount The number of tokens to be minted. + */ + function mint(address to, uint amount) external override onlyMinter { + if (to == address(0)) { + revert INativeIssuance_v1__InvalidAddress(); + } + // require(amount > 0, "Amount must be greater than zero"); + + emit Minted(to, amount); + + totalMinted += amount; + + // Calls NativeMinter precompile through INativeMinter interface. + NATIVE_MINTER.mintNativeCoin(to, amount); + } + + /** + * @notice Deposits native tokens to be able to burn native tokens from the specified address. + * @dev `msg.value` is the amount of native tokens to be deposited. + * @param from The address that will have native tokens deposited for. + */ + function depositNative(address from) external payable { + if (from == address(0)) { + revert INativeIssuance_v1__InvalidAddress(); + } + + if (msg.value == 0) { + revert INativeIssuance_v1__InvalidAmount(); + } + + depositsForBurning[from] += msg.value; + } + + /** + * @notice Burns native tokens from the specified address. + * @param from The address that will have tokens burned. + * @param amount The number of tokens to be burned. + */ + function burn(address from, uint amount) external override onlyMinter { + if (from == address(0)) { + revert INativeIssuance_v1__InvalidAddress(); + } + + if (depositsForBurning[from] < amount) { + revert INativeIssuance_v1__InvalidAmount(); + } + + emit Burned(from, amount); + + depositsForBurning[from] -= amount; + + payable(BURNED_FOR_BONDINGCURVE_ADDRESS).sendValue(amount); + } + + /** + * @notice Sets the minting rights of an address. + * @param minter The address of the minter. + * @param allowed If the address is allowed to mint or not. + */ + function setMinter(address minter, bool allowed) external onlyOwner { + _setMinter(minter, allowed); + } + + /** + * @notice Returns the total supply of native tokens minted by this contract and subtracs the balance of burned address. + */ + function totalNativeAssetSupply() external view returns (uint) { + uint burned = BURNED_FOR_BONDINGCURVE_ADDRESS.balance; + + return totalMinted - burned; + } + + /** + * @notice Returns the balance of a given account. + * @param account The address to query the balance of. + */ + function balanceOf(address account) external view returns (uint) { + return account.balance; + } + + /** + * @notice Returns the name of the token. + */ + function name() external pure returns (string memory) { + return "Native Issuance"; + } + + /** + * @notice Returns the symbol of the token. + */ + function symbol() external pure returns (string memory) { + return "NATIVE"; + } + + /** + * @notice Returns the number of decimals used. + */ + function decimals() external pure returns (uint8) { + return 18; + } + + /** + * @notice allowance is not supported + */ + function allowance(address, /* owner */ address /* spender */ ) + external + pure + returns (uint) + { + revert INativeIssuance_v1__NotSupported(); + } + + /** + * @notice approve is not supported + */ + function approve(address, /* spender */ uint /* value */ ) + external + pure + returns (bool) + { + revert INativeIssuance_v1__NotSupported(); + } + + function totalSupply() external pure returns (uint) { + revert INativeIssuance_v1__NotSupported(); + } + + /** + * @notice transfer is not supported + */ + function transfer(address, /* to */ uint /* value */ ) + external + pure + returns (bool) + { + revert INativeIssuance_v1__NotSupported(); + } + + /** + * @notice transferFrom is not supported + */ + function transferFrom( + address, /* from */ + address, /* to */ + uint /* value */ + ) external pure returns (bool) { + revert INativeIssuance_v1__NotSupported(); + } + + //------------------------------------------------------------------------------ + // Internal Functions + + /** + * @notice Sets the minting rights of an address. + * @param _minter The address of the minter. + * @param _allowed If the address is allowed to mint or not. + */ + function _setMinter(address _minter, bool _allowed) internal { + emit MinterSet(_minter, _allowed); + allowedMinters[_minter] = _allowed; + } +} diff --git a/src/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol b/src/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol new file mode 100644 index 000000000..90abb8a2d --- /dev/null +++ b/src/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity 0.8.23; + +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; + +import {RedeemingBondingCurveBase_v1} from + "@fm/bondingCurve/abstracts/RedeemingBondingCurveBase_v1.sol"; +import {BondingCurveBase_v1} from + "@fm/bondingCurve/abstracts/BondingCurveBase_v1.sol"; +import {FM_BC_Bancor_Redeeming_VirtualSupply_v1} from + "@fm/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_v1.sol"; +import {IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1} from + "./interfaces/IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol"; +import {INativeIssuance_v1} from "@ex/token/INativeIssuance_v1.sol"; + +/** + * @title Inverter Bancor Redeeming Virtual Supply Bonding Curve Funding Manager for Native Tokens + * @notice This contract enables the issuance and redeeming of the Native Tokens using a Bancor-like bonding curve mechanism, + * but with the ability to redeem native tokens directly from the contract without needing ERC20 tokens. + * @dev Inherits {FM_BC_Bancor_Redeeming_VirtualSupply_v1}, {IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1}. + * Implements payable sell (redeeming) functionality for native tokens. + * The Issuance Token should be the {INativeIssuance_v1} token. + * @author Inverter Network + */ +contract FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1 is + IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1, + FM_BC_Bancor_Redeeming_VirtualSupply_v1 +{ + function sellNative(uint _minAmountOut) public payable sellingIsEnabled { + if (msg.value <= 0) { + revert + IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1__InvalidDepositAmount( + ); + } + + INativeIssuance_v1(address(issuanceToken)).depositNative{ + value: msg.value + }(_msgSender()); + + sellTo(_msgSender(), msg.value, _minAmountOut); + } +} diff --git a/src/modules/fundingManager/bondingCurve/interfaces/IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol b/src/modules/fundingManager/bondingCurve/interfaces/IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol new file mode 100644 index 000000000..59d1b34b0 --- /dev/null +++ b/src/modules/fundingManager/bondingCurve/interfaces/IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity 0.8.23; + +import {IBondingCurveBase_v1} from + "@fm/bondingCurve/interfaces/IBondingCurveBase_v1.sol"; +import {IRedeemingBondingCurveBase_v1} from + "@fm/bondingCurve/interfaces/IRedeemingBondingCurveBase_v1.sol"; + +interface IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1 is + IRedeemingBondingCurveBase_v1, + IBondingCurveBase_v1 +{ + error IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1__InvalidDepositAmount( + ); + + function sellNative(uint _minAmountOut) external payable; +} diff --git a/test/external/NativeIssuance_v1.t.sol b/test/external/NativeIssuance_v1.t.sol new file mode 100644 index 000000000..38f26511a --- /dev/null +++ b/test/external/NativeIssuance_v1.t.sol @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: Ecosystem +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import {NativeIssuance_v1} from "@ex/token/NativeIssuance_v1.sol"; +import {INativeIssuance_v1} from "@ex/token/INativeIssuance_v1.sol"; +import {IERC20Issuance_v1} from "@ex/token/IERC20Issuance_v1.sol"; +import {Ownable} from "@oz/access/Ownable.sol"; +import {NativeMinterMock} from "test/utils/mocks/external/NativeMinterMock.sol"; + +contract NativeIssuanceTest is Test { + NativeIssuance_v1 public nativeIssuance; + address public initialOwner = address(0x1); + address public minter = address(0x2); + address public user = address(0x3); + uint public mintAmount = 100 ether; + uint public burnAmount = 50 ether; + + function setUp() public { + NativeMinterMock nativeMinter = new NativeMinterMock(); + vm.etch( + 0x0200000000000000000000000000000000000001, + address(nativeMinter).code + ); + vm.deal(0x0200000000000000000000000000000000000001, 1000 ether); + + nativeIssuance = new NativeIssuance_v1(initialOwner); + // Set the minter to the contract itself for testing purposes + vm.startPrank(initialOwner); + nativeIssuance.setMinter(address(this), true); + vm.stopPrank(); + } + + function testInitialState() public { + assertEq(nativeIssuance.totalMinted(), 0); + assertEq(nativeIssuance.balanceOf(initialOwner), 0); + assertTrue(!nativeIssuance.allowedMinters(minter)); + assertEq(nativeIssuance.owner(), initialOwner); + } + + function testMint() public { + vm.startPrank(initialOwner); + + vm.expectEmit(true, true, false, true); + emit INativeIssuance_v1.Minted(initialOwner, mintAmount); + + nativeIssuance.mint(initialOwner, mintAmount); + + vm.expectEmit(true, true, false, true); + emit INativeIssuance_v1.Minted(user, mintAmount); + + nativeIssuance.mint(user, mintAmount); + + assertEq(nativeIssuance.totalMinted(), mintAmount + mintAmount); + assertEq(nativeIssuance.balanceOf(initialOwner), mintAmount); + assertEq(nativeIssuance.balanceOf(user), mintAmount); + } + + function testMintRevertWhenNotMinter() public { + vm.startPrank(user); + vm.expectRevert( + IERC20Issuance_v1.IERC20Issuance__CallerIsNotMinter.selector + ); + nativeIssuance.mint(initialOwner, mintAmount); + } + + function testMintRevertWhenMintToZeroAddress() public { + vm.startPrank(initialOwner); + vm.expectRevert( + INativeIssuance_v1.INativeIssuance_v1__InvalidAddress.selector + ); + nativeIssuance.mint(address(0), mintAmount); + } + + function testBurn() public { + // First, mint some tokens to the initialOwner + vm.startPrank(initialOwner); + nativeIssuance.mint(initialOwner, mintAmount); + assertEq(nativeIssuance.balanceOf(initialOwner), mintAmount); + + // Deposit amount to be burned into the contract + nativeIssuance.depositNative{value: burnAmount}(initialOwner); + + // Now, burn a portion of those tokens + nativeIssuance.burn(initialOwner, burnAmount); + assertEq( + nativeIssuance.totalNativeAssetSupply(), mintAmount - burnAmount + ); + assertEq( + nativeIssuance.balanceOf(initialOwner), mintAmount - burnAmount + ); + } + + function testBurnRevertWhenNotMinter() public { + vm.startPrank(user); + vm.expectRevert( + IERC20Issuance_v1.IERC20Issuance__CallerIsNotMinter.selector + ); + nativeIssuance.burn(initialOwner, burnAmount); + } + + function testBurnRevertWhenAddressZero() public { + vm.startPrank(initialOwner); + vm.expectRevert( + INativeIssuance_v1.INativeIssuance_v1__InvalidAddress.selector + ); + nativeIssuance.burn(address(0), burnAmount); + } + + function testBurnRevertWhenInvalidAmount() public { + vm.startPrank(initialOwner); + nativeIssuance.mint(initialOwner, mintAmount); + assertEq(nativeIssuance.balanceOf(initialOwner), mintAmount); + + nativeIssuance.depositNative{value: burnAmount}(initialOwner); + + vm.expectRevert( + INativeIssuance_v1.INativeIssuance_v1__InvalidAmount.selector + ); + nativeIssuance.burn(initialOwner, burnAmount + 1); + } + + function testDepositNative() public { + nativeIssuance.mint(initialOwner, mintAmount); + nativeIssuance.depositNative{value: burnAmount}(initialOwner); + + assertEq(nativeIssuance.depositsForBurning(initialOwner), burnAmount); + } + + function testDepositNativeRevertWhenAddressZero() public { + vm.startPrank(initialOwner); + nativeIssuance.mint(initialOwner, mintAmount); + + vm.expectRevert( + INativeIssuance_v1.INativeIssuance_v1__InvalidAddress.selector + ); + nativeIssuance.depositNative{value: burnAmount}(address(0)); + + assertEq(nativeIssuance.depositsForBurning(initialOwner), 0); + } + + function testDepositNativeRevertWhenValueZero() public { + vm.startPrank(initialOwner); + vm.expectRevert( + INativeIssuance_v1.INativeIssuance_v1__InvalidAmount.selector + ); + nativeIssuance.depositNative{value: 0}(initialOwner); + + assertEq(nativeIssuance.depositsForBurning(initialOwner), 0); + } + + function testSetMinter() public { + vm.startPrank(initialOwner); + + vm.expectEmit(true, true, false, true); + emit IERC20Issuance_v1.MinterSet(minter, true); + + nativeIssuance.setMinter(minter, true); + + assertTrue(nativeIssuance.allowedMinters(minter)); + } + + function testSetMinterRevertWhenNotOwner() public { + vm.startPrank(user); + + vm.expectRevert(); + + nativeIssuance.setMinter(minter, true); + } + + function testBalanceOf() public { + vm.startPrank(initialOwner); + nativeIssuance.mint(initialOwner, mintAmount); + + assertEq(nativeIssuance.balanceOf(initialOwner), mintAmount); + } + + function testStaticMethods() public { + assertEq(nativeIssuance.name(), "Native Issuance"); + assertEq(nativeIssuance.symbol(), "NATIVE"); + assertEq(nativeIssuance.decimals(), 18); + } + + function testNotSupportedMethods() public { + vm.expectRevert( + INativeIssuance_v1.INativeIssuance_v1__NotSupported.selector + ); + nativeIssuance.allowance(address(0), address(0)); + + vm.expectRevert( + INativeIssuance_v1.INativeIssuance_v1__NotSupported.selector + ); + nativeIssuance.approve(address(0), 1); + + vm.expectRevert( + INativeIssuance_v1.INativeIssuance_v1__NotSupported.selector + ); + nativeIssuance.totalSupply(); + + vm.expectRevert( + INativeIssuance_v1.INativeIssuance_v1__NotSupported.selector + ); + nativeIssuance.transfer(address(0), 1); + + vm.expectRevert( + INativeIssuance_v1.INativeIssuance_v1__NotSupported.selector + ); + nativeIssuance.transferFrom(address(0), address(0), 1); + } + + receive() external payable {} + fallback() external payable {} +} diff --git a/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol b/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol new file mode 100644 index 000000000..4c39816bf --- /dev/null +++ b/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.0; + +import {Clones} from "@oz/proxy/Clones.sol"; +import {BancorFormula} from + "@src/modules/fundingManager/bondingCurve/formulas/BancorFormula.sol"; +import { + IFM_BC_Bancor_Redeeming_VirtualSupply_v1, + FM_BC_Bancor_Redeeming_VirtualSupply_v1, + IFundingManager_v1 +} from + "@src/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_v1.sol"; + +import { + ModuleTest, + IModule_v1, + IOrchestrator_v1 +} from "@test/modules/ModuleTest.sol"; +import {OZErrors} from "@test/utils/errors/OZErrors.sol"; + +import {IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1} from + "@fm/bondingCurve/interfaces/IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol"; +import {NativeIssuance_v1} from "@ex/token/NativeIssuance_v1.sol"; +import {FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1} from + "@fm/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol"; +import {FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock} from + "./utils/mocks/FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock.sol"; +import {NativeMinterMock} from "test/utils/mocks/external/NativeMinterMock.sol"; + +contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { + string internal constant NAME = "Bonding Curve Token"; + string internal constant SYMBOL = "BCT"; + uint8 internal constant DECIMALS = 18; + uint internal constant MAX_SUPPLY = type(uint).max; + + uint internal constant INITIAL_ISSUANCE_SUPPLY = 195_642_169e16; + uint internal constant INITIAL_COLLATERAL_SUPPLY = 39_097_931e16; + uint32 internal constant RESERVE_RATIO_FOR_BUYING = 199_800; + uint32 internal constant RESERVE_RATIO_FOR_SELLING = 199_800; + uint internal constant BUY_FEE = 0; + uint internal constant SELL_FEE = 0; + bool internal constant BUY_IS_OPEN = true; + bool internal constant SELL_IS_OPEN = true; + + address admin_address = address(0xA1BA); + address non_admin_address = address(0xB0B); + + NativeIssuance_v1 issuanceToken; + FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock bondingCurveFundingManager; + address formula; + + function setUp() public virtual { + NativeMinterMock nativeMinter = new NativeMinterMock(); + vm.etch( + 0x0200000000000000000000000000000000000001, + address(nativeMinter).code + ); + vm.deal(0x0200000000000000000000000000000000000001, 1e38); + + issuanceToken = new NativeIssuance_v1(admin_address); + BancorFormula bancorFormula = new BancorFormula(); + formula = address(bancorFormula); + + IFM_BC_Bancor_Redeeming_VirtualSupply_v1.BondingCurveProperties memory + bc_properties; + + bc_properties.formula = formula; + bc_properties.reserveRatioForBuying = RESERVE_RATIO_FOR_BUYING; + bc_properties.reserveRatioForSelling = RESERVE_RATIO_FOR_SELLING; + bc_properties.buyFee = BUY_FEE; + bc_properties.sellFee = SELL_FEE; + bc_properties.buyIsOpen = BUY_IS_OPEN; + bc_properties.sellIsOpen = SELL_IS_OPEN; + bc_properties.initialIssuanceSupply = INITIAL_ISSUANCE_SUPPLY; + bc_properties.initialCollateralSupply = INITIAL_COLLATERAL_SUPPLY; + + address impl = + address(new FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock()); + bondingCurveFundingManager = + FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock(Clones.clone(impl)); + + _setUpOrchestrator(bondingCurveFundingManager); + + _authorizer.grantRole(_authorizer.getAdminRole(), admin_address); + + bondingCurveFundingManager.init( + _orchestrator, + _METADATA, + abi.encode( + address(issuanceToken), + bc_properties, + _token // fetching from ModuleTest.sol (specifically after the _setUpOrchestrator function call) + ) + ); + + vm.prank(admin_address); + issuanceToken.setMinter(address(bondingCurveFundingManager), true); + } + + //-------------------------------------------------------------------------- + // Test: Initialization + + // This function also tests all the getters + //-------------------------------------------------------------------------- + // Tests: Initialization + + function testInit() public override { + // assertEq( + // issuanceToken.name(), + // string(abi.encodePacked(NAME)), + // "Name has not been set correctly" + // ); + // assertEq( + // issuanceToken.symbol(), + // string(abi.encodePacked(SYMBOL)), + // "Symbol has not been set correctly" + // ); + assertEq( + issuanceToken.decimals(), + DECIMALS, + "Decimals has not been set correctly" + ); + assertEq( + bondingCurveFundingManager.call_collateralTokenDecimals(), + _token.decimals(), + "Collateral token decimals has not been set correctly" + ); + assertEq( + address(bondingCurveFundingManager.formula()), + formula, + "Formula has not been set correctly" + ); + assertEq( + bondingCurveFundingManager.getVirtualIssuanceSupply(), + INITIAL_ISSUANCE_SUPPLY, + "Virtual token supply has not been set correctly" + ); + assertEq( + bondingCurveFundingManager.getVirtualCollateralSupply(), + INITIAL_COLLATERAL_SUPPLY, + "Virtual collateral supply has not been set correctly" + ); + assertEq( + bondingCurveFundingManager.call_reserveRatioForBuying(), + RESERVE_RATIO_FOR_BUYING, + "Reserve ratio for buying has not been set correctly" + ); + assertEq( + bondingCurveFundingManager.call_reserveRatioForSelling(), + RESERVE_RATIO_FOR_SELLING, + "Reserve ratio for selling has not been set correctly" + ); + assertEq( + bondingCurveFundingManager.buyFee(), + BUY_FEE, + "Buy fee has not been set correctly" + ); + assertEq( + bondingCurveFundingManager.buyIsOpen(), + BUY_IS_OPEN, + "Buy-is-open has not been set correctly" + ); + assertEq( + bondingCurveFundingManager.buyFee(), + SELL_FEE, + "Sell fee has not been set correctly" + ); + assertEq( + bondingCurveFundingManager.buyIsOpen(), + SELL_IS_OPEN, + "Sell-is-open has not been set correctly" + ); + } + + function testReinitFails() public override { + vm.expectRevert(OZErrors.Initializable__InvalidInitialization); + bondingCurveFundingManager.init(_orchestrator, _METADATA, abi.encode()); + } + + function testBuyOrderWithZeroFee(uint amount) public { + // Setup + // Above an amount of 1e38 the BancorFormula starts to revert. + amount = _bound_for_decimal_conversion( + amount, + 1e16, + 1e38, + bondingCurveFundingManager.call_collateralTokenDecimals(), + issuanceToken.decimals() + ); + + address buyer = makeAddr("buyer"); + _token.mint(buyer, amount); + vm.prank(buyer); + _token.approve(address(bondingCurveFundingManager), amount); + + // Pre-checks + uint balanceBefore = + _token.balanceOf(address(bondingCurveFundingManager)); + assertEq(_token.balanceOf(buyer), amount); + assertEq(issuanceToken.balanceOf(buyer), 0); + + // Use formula to get expected return values + uint decimalConverted_depositAmount = bondingCurveFundingManager + .call_convertAmountToRequiredDecimal(amount, _token.decimals(), 18); + uint formulaReturn = bondingCurveFundingManager.formula() + .calculatePurchaseReturn( + bondingCurveFundingManager.call_getFormulaVirtualIssuanceSupply(), + bondingCurveFundingManager.call_getFormulaVirtualCollateralSupply(), + bondingCurveFundingManager.call_reserveRatioForBuying(), + decimalConverted_depositAmount + ); + + // Execution + vm.prank(buyer); + bondingCurveFundingManager.buy(amount, formulaReturn); + // Post-checks + assertEq( + _token.balanceOf(address(bondingCurveFundingManager)), + (balanceBefore + amount) + ); + assertEq(_token.balanceOf(buyer), 0); + assertEq(issuanceToken.balanceOf(buyer), formulaReturn); + } + + function testSellOrderWithZeroFee(uint amountIn) public { + // Setup + + // We set a minimum high enough to discard most inputs that wouldn't mint even 1 token + amountIn = _bound_for_decimal_conversion( + amountIn, + 100, + 1e36, + bondingCurveFundingManager.call_collateralTokenDecimals(), + issuanceToken.decimals() + ); + // see comment in testBuyOrderWithZeroFee for information on the upper bound + + _token.mint( + address(bondingCurveFundingManager), (type(uint).max - amountIn) + ); // We mint all the other tokens to the fundingManager to make sure we'll have enough balance to pay out + + address seller = makeAddr("seller"); + + uint userSellAmount = _prepareSellConditions(seller, amountIn); + vm.assume(userSellAmount > 0); // we ensure we are discarding buy-ins so small they wouldn't cause minting + + // Set virtual supply to some number above the sell amount + // Set virtual collateral to some number + uint newVirtualIssuanceSupply = userSellAmount * 2; + uint newVirtualCollateral = amountIn * 2; + _closeCurveInteractions(); // Buy & sell needs to be closed to set supply + vm.startPrank(admin_address); + { + bondingCurveFundingManager.setVirtualIssuanceSupply( + newVirtualIssuanceSupply + ); + bondingCurveFundingManager.setVirtualCollateralSupply( + newVirtualCollateral + ); + } + vm.stopPrank(); + _openCurveInteractions(); // Open Buy & sell + + uint decimalConverted_userSellAmount = bondingCurveFundingManager + .call_convertAmountToRequiredDecimal( + userSellAmount, issuanceToken.decimals(), 18 + ); + // Use formula to get expected return values + uint formulaReturn = bondingCurveFundingManager.formula() + .calculateSaleReturn( + bondingCurveFundingManager.call_getFormulaVirtualIssuanceSupply(), + bondingCurveFundingManager.call_getFormulaVirtualCollateralSupply(), + bondingCurveFundingManager.call_reserveRatioForSelling(), + decimalConverted_userSellAmount + ); + + // normalize the formulaReturn. This is the amount in the context of the collateral token + uint normalized_formulaReturn = bondingCurveFundingManager + .call_convertAmountToRequiredDecimal( + formulaReturn, 18, _token.decimals() + ); + + // Perform the sell + vm.startPrank(seller); + { + bondingCurveFundingManager.sellNative{value: userSellAmount}( + normalized_formulaReturn + ); + } + vm.stopPrank(); + + // Check real-world token/collateral balances + assertEq(issuanceToken.balanceOf(seller), 0); + assertEq(_token.balanceOf(seller), normalized_formulaReturn); + assertEq( + _token.balanceOf(address(bondingCurveFundingManager)), + (type(uint).max - normalized_formulaReturn) + ); + + // Check virtual token/collateral balances + assertEq( + bondingCurveFundingManager.getVirtualIssuanceSupply(), + newVirtualIssuanceSupply - userSellAmount + ); + assertEq( + bondingCurveFundingManager.getVirtualCollateralSupply(), + newVirtualCollateral - normalized_formulaReturn + ); + } + + function testSellNativeRevertsWhenAmountIsZero() public { + address seller = makeAddr("seller"); + vm.startPrank(seller); + vm.expectRevert( + IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1 + .IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1__InvalidDepositAmount + .selector + ); + bondingCurveFundingManager.sellNative{value: 0}(1); + vm.stopPrank(); + } + + // Helper function that: + // - Mints collateral tokens to a seller and + // - Deposits them so they can later be sold. + // - Approves the BondingCurve contract to spend the receipt tokens + // This function assumes that we are using the Mock with a 0% buy fee, so the user will receive as many tokens as they deposit + function _prepareSellConditions(address seller, uint amount) + internal + returns (uint userSellAmount) + { + _token.mint(seller, amount); + uint minAmountOut = + bondingCurveFundingManager.calculatePurchaseReturn(amount); + vm.startPrank(seller); + { + _token.approve(address(bondingCurveFundingManager), amount); + bondingCurveFundingManager.buy(amount, minAmountOut); + userSellAmount = issuanceToken.balanceOf(seller); + } + vm.stopPrank(); + + return userSellAmount; + } + + function _closeCurveInteractions() internal { + bondingCurveFundingManager.closeBuy(); + bondingCurveFundingManager.closeSell(); + } + + function _openCurveInteractions() internal { + bondingCurveFundingManager.openBuy(); + bondingCurveFundingManager.openSell(); + } +} diff --git a/test/modules/fundingManager/bondingCurve/utils/mocks/FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock.sol b/test/modules/fundingManager/bondingCurve/utils/mocks/FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock.sol new file mode 100644 index 000000000..da672228e --- /dev/null +++ b/test/modules/fundingManager/bondingCurve/utils/mocks/FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.0; + +import "forge-std/console.sol"; + +import { + FM_BC_Bancor_Redeeming_VirtualSupply_v1, + IFM_BC_Bancor_Redeeming_VirtualSupply_v1, + FM_BC_Tools +} from "@fm/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_v1.sol"; + +import {FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1} from + "@fm/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol"; + +contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock is + FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1 +{ + function call_reserveRatioForBuying() external view returns (uint32) { + return reserveRatioForBuying; + } + + function call_reserveRatioForSelling() external view returns (uint32) { + return reserveRatioForSelling; + } + + function call_collateralTokenDecimals() external view returns (uint8) { + return collateralTokenDecimals; + } + + function call_convertAmountToRequiredDecimal( + uint _amount, + uint8 _tokenDecimals, + uint8 _requiredDecimals + ) external pure returns (uint) { + return FM_BC_Tools._convertAmountToRequiredDecimal( + _amount, _tokenDecimals, _requiredDecimals + ); + } + + // Note: this function returns the virtual issuance supply in the same format it will be fed to the Bancor formula + function call_getFormulaVirtualIssuanceSupply() + external + view + returns (uint) + { + return FM_BC_Tools._convertAmountToRequiredDecimal( + virtualIssuanceSupply, issuanceTokenDecimals, 18 + ); + } + + // Note: this function returns the virtual collateral supply in the same format it will be fed to the Bancor formula + function call_getFormulaVirtualCollateralSupply() + external + view + returns (uint) + { + return FM_BC_Tools._convertAmountToRequiredDecimal( + virtualCollateralSupply, collateralTokenDecimals, 18 + ); + } +} diff --git a/test/utils/mocks/external/NativeMinterMock.sol b/test/utils/mocks/external/NativeMinterMock.sol new file mode 100644 index 000000000..f4a4024fb --- /dev/null +++ b/test/utils/mocks/external/NativeMinterMock.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.0; +import "forge-std/console.sol"; + +// import {INativeMinter} from "@avalabs/subnet-evm-contracts@1.2.0/contracts/interfaces/INativeMinter.sol"; + +contract NativeMinterMock { + function mintNativeCoin(address to, uint256 amount) external { + // log balanche of this contract + console.log("NativeMinterMock: balance: %d", address(this).balance); + + (bool success, ) = payable(address(to)).call{value: amount}(""); + + if (!success) { + revert("NativeMinterMock: Failed to transfer native token"); + } + } + + receive() external payable {} + fallback() external payable {} +} \ No newline at end of file From 5596c0a33954e8967a0c80ec36ed4fdc7eaebbe0 Mon Sep 17 00:00:00 2001 From: Doga Oztuzun Date: Fri, 27 Sep 2024 13:15:14 +0000 Subject: [PATCH 2/6] Deployment script has been added --- script/deploymentScript/DeployPIML1.s.sol | 212 ++++++++++++++++++ ...or_Redeeming_VirtualSupply_Native_v1.t.sol | 8 +- 2 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 script/deploymentScript/DeployPIML1.s.sol diff --git a/script/deploymentScript/DeployPIML1.s.sol b/script/deploymentScript/DeployPIML1.s.sol new file mode 100644 index 000000000..75c723944 --- /dev/null +++ b/script/deploymentScript/DeployPIML1.s.sol @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0; + +import "forge-std/Script.sol"; +import {IModule_v1} from "src/modules/base/IModule_v1.sol"; +import {IGovernor_v1} from "@ex/governance/interfaces/IGovernor_v1.sol"; +import {IOrchestrator_v1} from + "src/orchestrator/interfaces/IOrchestrator_v1.sol"; +import {IOrchestratorFactory_v1} from + "src/factories/interfaces/IOrchestratorFactory_v1.sol"; +import {IInverterBeacon_v1} from "src/proxies/interfaces/IInverterBeacon_v1.sol"; +import {IERC20Issuance_v1} from "@ex/token/IERC20Issuance_v1.sol"; +import {IFM_BC_Bancor_Redeeming_VirtualSupply_v1} from + "@fm/bondingCurve/interfaces/IFM_BC_Bancor_Redeeming_VirtualSupply_v1.sol"; + +// import {InverterReverter_v1} from "@ex/reverter/InverterReverter_v1.sol"; +// import {BancorFormula} from "@fm/bondingCurve/formulas/BancorFormula.sol"; +// import {AUT_Roles_v1} from "@aut/role/AUT_Roles_v1.sol"; +// import {AUT_TokenGated_Roles_v1} from "@aut/role/AUT_TokenGated_Roles_v1.sol"; +// import {AUT_EXT_VotingRoles_v1} from +// "@aut/extensions/AUT_EXT_VotingRoles_v1.sol"; +// import {FM_BC_Bancor_Redeeming_VirtualSupply_v1} from +// "@fm/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_v1.sol"; +// import {FM_BC_Restricted_Bancor_Redeeming_VirtualSupply_v1} from +// "@fm/bondingCurve/FM_BC_Restricted_Bancor_Redeeming_VirtualSupply_v1.sol"; +// import {FM_DepositVault_v1} from "@fm/depositVault/FM_DepositVault_v1.sol"; +// import {LM_PC_Bounties_v1} from "@lm/LM_PC_Bounties_v1.sol"; +// import {LM_PC_KPIRewarder_v1} from "@lm/LM_PC_KPIRewarder_v1.sol"; +// import {LM_PC_PaymentRouter_v1} from "@lm/LM_PC_PaymentRouter_v1.sol"; +// import {LM_PC_RecurringPayments_v1} from "@lm/LM_PC_RecurringPayments_v1.sol"; +// import {PP_Simple_v1} from "@pp/PP_Simple_v1.sol"; +// import {PP_Streaming_v1} from "@pp/PP_Streaming_v1.sol"; + +import {NativeIssuance_v1} from "@ex/token/NativeIssuance_v1.sol"; + +import {TestnetDeploymentScript} from "./TestnetDeploymentScript.s.sol"; + +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; + +contract DeployPIML1 is TestnetDeploymentScript { + address public erc20TokenRemote; + address public nativeTokenHome; + + // Governor_v1 InverterBeaconProxy_v1 + IGovernor_v1 public governorProxy; + + address private fundingManager; + + // BancorRedeemingVirtualSupplyNativeFundingManager + IModule_v1.Metadata public + bancorRedeemingVirtualSupplyNativeFundingManagerMetadata = IModule_v1 + .Metadata( + 1, + 0, + 0, + "https://github.com/InverterNetwork/contracts", + "FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1" + ); + + function run(address _nativeTokenHome, address _erc20TokenRemote) public { + if (_nativeTokenHome == address(0)) { + revert("NATIVE_TOKEN_HOME not set"); + } + + if (_erc20TokenRemote == address(0)) { + revert("ERC20_TOKEN_REMOTE not set"); + } + + nativeTokenHome = _nativeTokenHome; + erc20TokenRemote = _erc20TokenRemote; + + super.run(); + + governorProxy = IGovernor_v1(governor); + + if (address(governorProxy) == address(0)) { + revert("Governor not deployed"); + } + + registerModule(); + deployPIMNative(); + } + + function registerModule() internal { + address impl_mod_FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1 = + deployAndLogWithCreate2( + "FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1", + vm.getCode( + "FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol:FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1" + ) + ); + + IInverterBeacon_v1 beacon = IInverterBeacon_v1( + proxyAndBeaconDeployer.deployInverterBeacon( + bancorRedeemingVirtualSupplyNativeFundingManagerMetadata.title, + inverterReverter, + governor, + impl_mod_FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1, + bancorRedeemingVirtualSupplyNativeFundingManagerMetadata + .majorVersion, + bancorRedeemingVirtualSupplyNativeFundingManagerMetadata + .minorVersion, + bancorRedeemingVirtualSupplyNativeFundingManagerMetadata + .patchVersion + ) + ); + + // Register the module in the Governor + vm.startBroadcast(deployerPrivateKey); + { + governorProxy.registerMetadataInModuleFactory( + bancorRedeemingVirtualSupplyNativeFundingManagerMetadata, beacon + ); + } + vm.stopBroadcast(); + } + + function deployPIMNative() internal { + uint initialIssuuanceSupply = 122_727_272_727_272_727_272_727; + uint initialCollateralSupply = 3_163_408_614_166_851_161; + uint32 reserveRatio = 160_000; + + IERC20Issuance_v1 issuanceToken; + IOrchestrator_v1 orchestrator; + IOrchestratorFactory_v1.WorkflowConfig memory workflowConfig; + IOrchestratorFactory_v1.ModuleConfig memory fundingManagerConfig; + IOrchestratorFactory_v1.ModuleConfig memory authorizerConfig; + IOrchestratorFactory_v1.ModuleConfig memory paymentProcessorConfig; + IOrchestratorFactory_v1.ModuleConfig[] memory additionalModuleConfig; + IFM_BC_Bancor_Redeeming_VirtualSupply_v1.BondingCurveProperties memory + bcProperties; + + // Deploy Native Issuance Token + vm.startBroadcast(deployerPrivateKey); + { + issuanceToken = new NativeIssuance_v1(deployer); + } + vm.stopBroadcast(); + + // Bonding Curve Properties + bcProperties = IFM_BC_Bancor_Redeeming_VirtualSupply_v1 + .BondingCurveProperties({ + formula: impl_lib_BancorFormula, + reserveRatioForBuying: reserveRatio, + reserveRatioForSelling: reserveRatio, + buyFee: 0, + sellFee: 0, + buyIsOpen: true, + sellIsOpen: true, + initialIssuanceSupply: initialIssuuanceSupply, + initialCollateralSupply: initialCollateralSupply + }); + + // Orchestrator_v1 config + workflowConfig = IOrchestratorFactory_v1.WorkflowConfig({ + independentUpdates: false, + independentUpdateAdmin: address(0) + }); + + // Funding Manager: Metadata, token address + fundingManagerConfig = IOrchestratorFactory_v1.ModuleConfig( + bancorRedeemingVirtualSupplyNativeFundingManagerMetadata, + abi.encode(address(issuanceToken), bcProperties, erc20TokenRemote) + ); + + // Payment Processor: only Metadata + paymentProcessorConfig = IOrchestratorFactory_v1.ModuleConfig( + simplePaymentProcessorMetadata, bytes("") + ); + + // Authorizer: Metadata, initial authorized addresses + authorizerConfig = IOrchestratorFactory_v1.ModuleConfig( + roleAuthorizerMetadata, abi.encode(deployer) + ); + + // Add the configuration for all the non-mandatory modules. In this case nothing added. + additionalModuleConfig = new IOrchestratorFactory_v1.ModuleConfig[](0); + + // ------------------------------------------------------------------------ + // Orchestrator_v1 Creation + + vm.startBroadcast(deployerPrivateKey); + { + orchestrator = IOrchestratorFactory_v1(orchestratorFactory) + .createOrchestrator( + workflowConfig, + fundingManagerConfig, + authorizerConfig, + paymentProcessorConfig, + additionalModuleConfig + ); + } + vm.stopBroadcast(); + + // Get the Funding Manager + fundingManager = address(orchestrator.fundingManager()); + + // Set the minter role to the Funding Manager + vm.startBroadcast(deployerPrivateKey); + { + issuanceToken.setMinter(fundingManager, true); + } + vm.stopBroadcast(); + + console2.log("Native Funding Manager: ", fundingManager); + console2.log("Native Token Issuence: ", address(issuanceToken)); + console2.log("Orchestrator Token: ", address(erc20TokenRemote)); + console2.log("Orchestrator Address: ", address(orchestrator)); + + vm.setEnv("NATIVE_TOKEN_ISSUANCE", vm.toString(address(issuanceToken))); + } +} diff --git a/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol b/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol index 4c39816bf..a0c40408f 100644 --- a/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol +++ b/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol @@ -3,20 +3,20 @@ pragma solidity ^0.8.0; import {Clones} from "@oz/proxy/Clones.sol"; import {BancorFormula} from - "@src/modules/fundingManager/bondingCurve/formulas/BancorFormula.sol"; + "src/modules/fundingManager/bondingCurve/formulas/BancorFormula.sol"; import { IFM_BC_Bancor_Redeeming_VirtualSupply_v1, FM_BC_Bancor_Redeeming_VirtualSupply_v1, IFundingManager_v1 } from - "@src/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_v1.sol"; + "src/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_v1.sol"; import { ModuleTest, IModule_v1, IOrchestrator_v1 -} from "@test/modules/ModuleTest.sol"; -import {OZErrors} from "@test/utils/errors/OZErrors.sol"; +} from "test/modules/ModuleTest.sol"; +import {OZErrors} from "test/utils/errors/OZErrors.sol"; import {IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1} from "@fm/bondingCurve/interfaces/IFM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.sol"; From 5cb96fec111d77bae02a56431078935fe49e41ea Mon Sep 17 00:00:00 2001 From: Doga Oztuzun Date: Fri, 27 Sep 2024 17:23:51 +0000 Subject: [PATCH 3/6] fix forge formatter --- src/external/token/IAllowList.sol | 27 +++++++++++-------- src/external/token/INativeMinter.sol | 10 ++++--- .../utils/mocks/external/NativeMinterMock.sol | 11 ++++---- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/external/token/IAllowList.sol b/src/external/token/IAllowList.sol index 1ae72378b..933c6eadd 100644 --- a/src/external/token/IAllowList.sol +++ b/src/external/token/IAllowList.sol @@ -2,20 +2,25 @@ pragma solidity ^0.8.0; interface IAllowList { - event RoleSet(uint256 indexed role, address indexed account, address indexed sender, uint256 oldRole); + event RoleSet( + uint indexed role, + address indexed account, + address indexed sender, + uint oldRole + ); - // Set [addr] to have the admin role over the precompile contract. - function setAdmin(address addr) external; + // Set [addr] to have the admin role over the precompile contract. + function setAdmin(address addr) external; - // Set [addr] to be enabled on the precompile contract. - function setEnabled(address addr) external; + // Set [addr] to be enabled on the precompile contract. + function setEnabled(address addr) external; - // Set [addr] to have the manager role over the precompile contract. - function setManager(address addr) external; + // Set [addr] to have the manager role over the precompile contract. + function setManager(address addr) external; - // Set [addr] to have no role for the precompile contract. - function setNone(address addr) external; + // Set [addr] to have no role for the precompile contract. + function setNone(address addr) external; - // Read the status of [addr]. - function readAllowList(address addr) external view returns (uint256 role); + // Read the status of [addr]. + function readAllowList(address addr) external view returns (uint role); } diff --git a/src/external/token/INativeMinter.sol b/src/external/token/INativeMinter.sol index f9aabd777..396105c81 100644 --- a/src/external/token/INativeMinter.sol +++ b/src/external/token/INativeMinter.sol @@ -1,9 +1,13 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.8.0; + import "./IAllowList.sol"; interface INativeMinter is IAllowList { - event NativeCoinMinted(address indexed sender, address indexed recipient, uint256 amount); - // Mint [amount] number of native coins and send to [addr] - function mintNativeCoin(address addr, uint256 amount) external; + event NativeCoinMinted( + address indexed sender, address indexed recipient, uint amount + ); + // Mint [amount] number of native coins and send to [addr] + + function mintNativeCoin(address addr, uint amount) external; } diff --git a/test/utils/mocks/external/NativeMinterMock.sol b/test/utils/mocks/external/NativeMinterMock.sol index f4a4024fb..a5f7a755b 100644 --- a/test/utils/mocks/external/NativeMinterMock.sol +++ b/test/utils/mocks/external/NativeMinterMock.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; + import "forge-std/console.sol"; // import {INativeMinter} from "@avalabs/subnet-evm-contracts@1.2.0/contracts/interfaces/INativeMinter.sol"; contract NativeMinterMock { - function mintNativeCoin(address to, uint256 amount) external { - // log balanche of this contract + function mintNativeCoin(address to, uint amount) external { + // log balanche of this contract console.log("NativeMinterMock: balance: %d", address(this).balance); - (bool success, ) = payable(address(to)).call{value: amount}(""); - + (bool success,) = payable(address(to)).call{value: amount}(""); + if (!success) { revert("NativeMinterMock: Failed to transfer native token"); } @@ -18,4 +19,4 @@ contract NativeMinterMock { receive() external payable {} fallback() external payable {} -} \ No newline at end of file +} From e20ac0997b553b61b0ecfbc77992480cb0a5d51a Mon Sep 17 00:00:00 2001 From: Doga Oztuzun Date: Thu, 3 Oct 2024 12:26:06 +0000 Subject: [PATCH 4/6] import contracts to be added to built artifacts --- script/deploymentScript/DeployPIML1.s.sol | 40 +++++++++++------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/script/deploymentScript/DeployPIML1.s.sol b/script/deploymentScript/DeployPIML1.s.sol index 75c723944..abfae2996 100644 --- a/script/deploymentScript/DeployPIML1.s.sol +++ b/script/deploymentScript/DeployPIML1.s.sol @@ -12,30 +12,28 @@ import {IInverterBeacon_v1} from "src/proxies/interfaces/IInverterBeacon_v1.sol" import {IERC20Issuance_v1} from "@ex/token/IERC20Issuance_v1.sol"; import {IFM_BC_Bancor_Redeeming_VirtualSupply_v1} from "@fm/bondingCurve/interfaces/IFM_BC_Bancor_Redeeming_VirtualSupply_v1.sol"; - -// import {InverterReverter_v1} from "@ex/reverter/InverterReverter_v1.sol"; -// import {BancorFormula} from "@fm/bondingCurve/formulas/BancorFormula.sol"; -// import {AUT_Roles_v1} from "@aut/role/AUT_Roles_v1.sol"; -// import {AUT_TokenGated_Roles_v1} from "@aut/role/AUT_TokenGated_Roles_v1.sol"; -// import {AUT_EXT_VotingRoles_v1} from -// "@aut/extensions/AUT_EXT_VotingRoles_v1.sol"; -// import {FM_BC_Bancor_Redeeming_VirtualSupply_v1} from -// "@fm/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_v1.sol"; -// import {FM_BC_Restricted_Bancor_Redeeming_VirtualSupply_v1} from -// "@fm/bondingCurve/FM_BC_Restricted_Bancor_Redeeming_VirtualSupply_v1.sol"; -// import {FM_DepositVault_v1} from "@fm/depositVault/FM_DepositVault_v1.sol"; -// import {LM_PC_Bounties_v1} from "@lm/LM_PC_Bounties_v1.sol"; -// import {LM_PC_KPIRewarder_v1} from "@lm/LM_PC_KPIRewarder_v1.sol"; -// import {LM_PC_PaymentRouter_v1} from "@lm/LM_PC_PaymentRouter_v1.sol"; -// import {LM_PC_RecurringPayments_v1} from "@lm/LM_PC_RecurringPayments_v1.sol"; -// import {PP_Simple_v1} from "@pp/PP_Simple_v1.sol"; -// import {PP_Streaming_v1} from "@pp/PP_Streaming_v1.sol"; - +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {NativeIssuance_v1} from "@ex/token/NativeIssuance_v1.sol"; - import {TestnetDeploymentScript} from "./TestnetDeploymentScript.s.sol"; -import {IERC20} from "@oz/token/ERC20/IERC20.sol"; +// Import the contracts so they will be added to the built artifacts +import {InverterReverter_v1} from "@ex/reverter/InverterReverter_v1.sol"; +import {BancorFormula} from "@fm/bondingCurve/formulas/BancorFormula.sol"; +import {AUT_Roles_v1} from "@aut/role/AUT_Roles_v1.sol"; +import {AUT_TokenGated_Roles_v1} from "@aut/role/AUT_TokenGated_Roles_v1.sol"; +import {AUT_EXT_VotingRoles_v1} from + "@aut/extensions/AUT_EXT_VotingRoles_v1.sol"; +import {FM_BC_Bancor_Redeeming_VirtualSupply_v1} from + "@fm/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_v1.sol"; +import {FM_BC_Restricted_Bancor_Redeeming_VirtualSupply_v1} from + "@fm/bondingCurve/FM_BC_Restricted_Bancor_Redeeming_VirtualSupply_v1.sol"; +import {FM_DepositVault_v1} from "@fm/depositVault/FM_DepositVault_v1.sol"; +import {LM_PC_Bounties_v1} from "@lm/LM_PC_Bounties_v1.sol"; +import {LM_PC_KPIRewarder_v1} from "@lm/LM_PC_KPIRewarder_v1.sol"; +import {LM_PC_PaymentRouter_v1} from "@lm/LM_PC_PaymentRouter_v1.sol"; +import {LM_PC_RecurringPayments_v1} from "@lm/LM_PC_RecurringPayments_v1.sol"; +import {PP_Simple_v1} from "@pp/PP_Simple_v1.sol"; +import {PP_Streaming_v1} from "@pp/PP_Streaming_v1.sol"; contract DeployPIML1 is TestnetDeploymentScript { address public erc20TokenRemote; From 3287788d7482786edeb177e0d4fcbaafdcdd45e6 Mon Sep 17 00:00:00 2001 From: Doga Oztuzun Date: Wed, 16 Oct 2024 11:22:59 +0000 Subject: [PATCH 5/6] BTT comments for test scenarios have been added --- test/external/NativeIssuance_v1.t.sol | 45 +++++++++++++++++++ ...or_Redeeming_VirtualSupply_Native_v1.t.sol | 45 ++++++++++++++----- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/test/external/NativeIssuance_v1.t.sol b/test/external/NativeIssuance_v1.t.sol index 38f26511a..4610e91c1 100644 --- a/test/external/NativeIssuance_v1.t.sol +++ b/test/external/NativeIssuance_v1.t.sol @@ -38,6 +38,15 @@ contract NativeIssuanceTest is Test { assertEq(nativeIssuance.owner(), initialOwner); } + /* + test mint + ├── When the mint amount is valid and the caller is allowed + │ └── It should mint the tokens + ├── When the caller is not the Minter + │ └── It should revert + └── When the address to mint to is zero address + └── It should revert + */ function testMint() public { vm.startPrank(initialOwner); @@ -72,6 +81,17 @@ contract NativeIssuanceTest is Test { nativeIssuance.mint(address(0), mintAmount); } + /* + test burn + ├── When the caller is the Minter + │ └── It should burn + ├── When the caller is not the Minter + │ └── It should revert + ├── When the address to burn from is zero address + │ └── It should revert + └── When the amount to burn is invalid + └── It should revert + */ function testBurn() public { // First, mint some tokens to the initialOwner vm.startPrank(initialOwner); @@ -120,6 +140,15 @@ contract NativeIssuanceTest is Test { nativeIssuance.burn(initialOwner, burnAmount + 1); } + /* + test depositNative + ├── When it is called with a valid amount and valid address + │ └── It should return the correct balance of the caller after the deposit + ├── When it is called with an invalid address + │ └── It should revert + └── When it is called with zero value + └── It should revert + */ function testDepositNative() public { nativeIssuance.mint(initialOwner, mintAmount); nativeIssuance.depositNative{value: burnAmount}(initialOwner); @@ -149,6 +178,13 @@ contract NativeIssuanceTest is Test { assertEq(nativeIssuance.depositsForBurning(initialOwner), 0); } + /* + test setMinter + ├── When it is called with a valid minter address + │ └── It should update the minter + └── When it is called by an unauthorized address + └── It should revert + */ function testSetMinter() public { vm.startPrank(initialOwner); @@ -168,6 +204,15 @@ contract NativeIssuanceTest is Test { nativeIssuance.setMinter(minter, true); } + /* + test readOnly functions + ├── When balanceOf is called with a valid address + │ └── It should return the correct balance + ├── When static functions called + │ └── Return the expected values + └── When functions called that not supported in this contract + └── It should revert `NotSupported` error. + */ function testBalanceOf() public { vm.startPrank(initialOwner); nativeIssuance.mint(initialOwner, mintAmount); diff --git a/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol b/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol index a0c40408f..c91ca0288 100644 --- a/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol +++ b/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol @@ -28,8 +28,8 @@ import {FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock} from import {NativeMinterMock} from "test/utils/mocks/external/NativeMinterMock.sol"; contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { - string internal constant NAME = "Bonding Curve Token"; - string internal constant SYMBOL = "BCT"; + string internal constant NAME = "Native Issuance"; + string internal constant SYMBOL = "NATIVE"; uint8 internal constant DECIMALS = 18; uint internal constant MAX_SUPPLY = type(uint).max; @@ -105,16 +105,16 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { // Tests: Initialization function testInit() public override { - // assertEq( - // issuanceToken.name(), - // string(abi.encodePacked(NAME)), - // "Name has not been set correctly" - // ); - // assertEq( - // issuanceToken.symbol(), - // string(abi.encodePacked(SYMBOL)), - // "Symbol has not been set correctly" - // ); + assertEq( + issuanceToken.name(), + string(abi.encodePacked(NAME)), + "Name has not been set correctly" + ); + assertEq( + issuanceToken.symbol(), + string(abi.encodePacked(SYMBOL)), + "Symbol has not been set correctly" + ); assertEq( issuanceToken.decimals(), DECIMALS, @@ -177,6 +177,16 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { bondingCurveFundingManager.init(_orchestrator, _METADATA, abi.encode()); } + //-------------------------------------------------------------------------- + // Public Functions + + /* Test `buy` function + └── when the fee is 0 + ├── it should pull the buy amount from the caller + ├── it should determine the mint amount of tokens to mint + ├── it should mint the tokens to the receiver + */ + function testBuyOrderWithZeroFee(uint amount) public { // Setup // Above an amount of 1e38 the BancorFormula starts to revert. @@ -222,6 +232,17 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { assertEq(issuanceToken.balanceOf(buyer), formulaReturn); } + /* Test `sell` function + ├── when the sell amount is 0 or msg.value is 0 + │ └── it should revert + └── when the sell amount is not 0 + └── when the fee is 0 + ├── it should take the sell amount from the caller + ├── it should determine the redeem amount of the sent tokens + └── When there IS enough collateral in the contract to cover the redeem amount + └── When the amount of redeemed tokens does not exceed the virtual issuance supply + └── it should send the rest to the receiver + */ function testSellOrderWithZeroFee(uint amountIn) public { // Setup From 18c6a499569c8346811b796f7a9753afee3f13ea Mon Sep 17 00:00:00 2001 From: Doga Oztuzun Date: Wed, 16 Oct 2024 14:47:56 +0000 Subject: [PATCH 6/6] call__ prefix on the mock renamed to exposed_ --- ...or_Redeeming_VirtualSupply_Native_v1.t.sol | 30 ++++++++++--------- ...r_Redeeming_VirtualSupply_NativeV1Mock.sol | 12 ++++---- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol b/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol index c91ca0288..c5d18d1cc 100644 --- a/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol +++ b/test/modules/fundingManager/bondingCurve/FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1.t.sol @@ -121,7 +121,7 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { "Decimals has not been set correctly" ); assertEq( - bondingCurveFundingManager.call_collateralTokenDecimals(), + bondingCurveFundingManager.exposed_collateralTokenDecimals(), _token.decimals(), "Collateral token decimals has not been set correctly" ); @@ -141,12 +141,12 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { "Virtual collateral supply has not been set correctly" ); assertEq( - bondingCurveFundingManager.call_reserveRatioForBuying(), + bondingCurveFundingManager.exposed_reserveRatioForBuying(), RESERVE_RATIO_FOR_BUYING, "Reserve ratio for buying has not been set correctly" ); assertEq( - bondingCurveFundingManager.call_reserveRatioForSelling(), + bondingCurveFundingManager.exposed_reserveRatioForSelling(), RESERVE_RATIO_FOR_SELLING, "Reserve ratio for selling has not been set correctly" ); @@ -194,7 +194,7 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { amount, 1e16, 1e38, - bondingCurveFundingManager.call_collateralTokenDecimals(), + bondingCurveFundingManager.exposed_collateralTokenDecimals(), issuanceToken.decimals() ); @@ -211,12 +211,13 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { // Use formula to get expected return values uint decimalConverted_depositAmount = bondingCurveFundingManager - .call_convertAmountToRequiredDecimal(amount, _token.decimals(), 18); + .exposed_convertAmountToRequiredDecimal(amount, _token.decimals(), 18); uint formulaReturn = bondingCurveFundingManager.formula() .calculatePurchaseReturn( - bondingCurveFundingManager.call_getFormulaVirtualIssuanceSupply(), - bondingCurveFundingManager.call_getFormulaVirtualCollateralSupply(), - bondingCurveFundingManager.call_reserveRatioForBuying(), + bondingCurveFundingManager.exposed_getFormulaVirtualIssuanceSupply(), + bondingCurveFundingManager.exposed_getFormulaVirtualCollateralSupply( + ), + bondingCurveFundingManager.exposed_reserveRatioForBuying(), decimalConverted_depositAmount ); @@ -251,7 +252,7 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { amountIn, 100, 1e36, - bondingCurveFundingManager.call_collateralTokenDecimals(), + bondingCurveFundingManager.exposed_collateralTokenDecimals(), issuanceToken.decimals() ); // see comment in testBuyOrderWithZeroFee for information on the upper bound @@ -283,21 +284,22 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Test is ModuleTest { _openCurveInteractions(); // Open Buy & sell uint decimalConverted_userSellAmount = bondingCurveFundingManager - .call_convertAmountToRequiredDecimal( + .exposed_convertAmountToRequiredDecimal( userSellAmount, issuanceToken.decimals(), 18 ); // Use formula to get expected return values uint formulaReturn = bondingCurveFundingManager.formula() .calculateSaleReturn( - bondingCurveFundingManager.call_getFormulaVirtualIssuanceSupply(), - bondingCurveFundingManager.call_getFormulaVirtualCollateralSupply(), - bondingCurveFundingManager.call_reserveRatioForSelling(), + bondingCurveFundingManager.exposed_getFormulaVirtualIssuanceSupply(), + bondingCurveFundingManager.exposed_getFormulaVirtualCollateralSupply( + ), + bondingCurveFundingManager.exposed_reserveRatioForSelling(), decimalConverted_userSellAmount ); // normalize the formulaReturn. This is the amount in the context of the collateral token uint normalized_formulaReturn = bondingCurveFundingManager - .call_convertAmountToRequiredDecimal( + .exposed_convertAmountToRequiredDecimal( formulaReturn, 18, _token.decimals() ); diff --git a/test/modules/fundingManager/bondingCurve/utils/mocks/FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock.sol b/test/modules/fundingManager/bondingCurve/utils/mocks/FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock.sol index da672228e..ad5fdd512 100644 --- a/test/modules/fundingManager/bondingCurve/utils/mocks/FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock.sol +++ b/test/modules/fundingManager/bondingCurve/utils/mocks/FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock.sol @@ -15,19 +15,19 @@ import {FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1} from contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock is FM_BC_Bancor_Redeeming_VirtualSupply_Native_v1 { - function call_reserveRatioForBuying() external view returns (uint32) { + function exposed_reserveRatioForBuying() external view returns (uint32) { return reserveRatioForBuying; } - function call_reserveRatioForSelling() external view returns (uint32) { + function exposed_reserveRatioForSelling() external view returns (uint32) { return reserveRatioForSelling; } - function call_collateralTokenDecimals() external view returns (uint8) { + function exposed_collateralTokenDecimals() external view returns (uint8) { return collateralTokenDecimals; } - function call_convertAmountToRequiredDecimal( + function exposed_convertAmountToRequiredDecimal( uint _amount, uint8 _tokenDecimals, uint8 _requiredDecimals @@ -38,7 +38,7 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock is } // Note: this function returns the virtual issuance supply in the same format it will be fed to the Bancor formula - function call_getFormulaVirtualIssuanceSupply() + function exposed_getFormulaVirtualIssuanceSupply() external view returns (uint) @@ -49,7 +49,7 @@ contract FM_BC_Bancor_Redeeming_VirtualSupply_NativeV1Mock is } // Note: this function returns the virtual collateral supply in the same format it will be fed to the Bancor formula - function call_getFormulaVirtualCollateralSupply() + function exposed_getFormulaVirtualCollateralSupply() external view returns (uint)