From a7af2d642446d7d0b441e296855d9876b3c98c49 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Fri, 8 Nov 2024 11:01:08 -0500 Subject: [PATCH] add --- .../LenderCommitmentGroup_Factory.sol | 129 +++++++++++++ .../oracleprotection/HypernativeOracle.sol | 147 +++++++++++++++ .../oracleprotection/OracleProtectedChild.sol | 28 +++ .../OracleProtectionManager.sol | 155 ++++++++++++++++ .../pausing/HasProtocolPausingManager.sol | 63 +++++++ .../pausing/ProtocolPausingManager.sol | 175 ++++++++++++++++++ .../LenderGroupMockInteract.sol | 125 +++++++++++++ 7 files changed, 822 insertions(+) create mode 100644 packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Factory.sol create mode 100644 packages/contracts/contracts/oracleprotection/HypernativeOracle.sol create mode 100644 packages/contracts/contracts/oracleprotection/OracleProtectedChild.sol create mode 100644 packages/contracts/contracts/oracleprotection/OracleProtectionManager.sol create mode 100644 packages/contracts/contracts/pausing/HasProtocolPausingManager.sol create mode 100644 packages/contracts/contracts/pausing/ProtocolPausingManager.sol create mode 100644 packages/contracts/contracts/penetrationtesting/LenderGroupMockInteract.sol diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Factory.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Factory.sol new file mode 100644 index 000000000..75bd51eba --- /dev/null +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Factory.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Contracts +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +// Interfaces +import "../../../interfaces/ITellerV2.sol"; +import "../../../interfaces/IProtocolFee.sol"; +import "../../../interfaces/ITellerV2Storage.sol"; +import "../../../libraries/NumbersLib.sol"; + +import {IUniswapPricingLibrary} from "../../../interfaces/IUniswapPricingLibrary.sol"; + +import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; + +import { ILenderCommitmentGroup } from "../../../interfaces/ILenderCommitmentGroup.sol"; + +contract LenderCommitmentGroupFactory is OwnableUpgradeable { + using AddressUpgradeable for address; + using NumbersLib for uint256; + + + //this is the beacon proxy + address public lenderGroupBeacon; + + + mapping(address => uint256) public deployedLenderGroupContracts; + + event DeployedLenderGroupContract(address indexed groupContract); + + + + function initialize(address _lenderGroupBeacon ) + external + initializer + { + lenderGroupBeacon = _lenderGroupBeacon; + __Ownable_init_unchained(); + } + + + /* + This should deploy a new lender commitment group pool contract. + It will use create commitment args in order to define the pool contracts parameters such as its primary principal token. + Shares will be distributed at a 1:1 ratio of the primary principal token so if 1e18 raw WETH are deposited, the depositor gets 1e18 shares for the group pool. + */ + function deployLenderCommitmentGroupPool( + uint256 _initialPrincipalAmount, + ILenderCommitmentGroup.CommitmentGroupConfig calldata _commitmentGroupConfig, + IUniswapPricingLibrary.PoolRouteConfig[] calldata _poolOracleRoutes + ) external returns (address newGroupContract_) { + + + + BeaconProxy newGroupContract_ = new BeaconProxy( + lenderGroupBeacon, + abi.encodeWithSelector( + ILenderCommitmentGroup.initialize.selector, //this initializes + _commitmentGroupConfig, + _poolOracleRoutes + + ) + ); + + deployedLenderGroupContracts[address(newGroupContract_)] = block.number; //consider changing this ? + emit DeployedLenderGroupContract(address(newGroupContract_)); + + + + //it is not absolutely necessary to have this call here but it allows the user to potentially save a tx step so it is nice to have . + if (_initialPrincipalAmount > 0) { + //should pull in the creators initial committed principal tokens . + + //send the initial principal tokens to _newgroupcontract here ! + // so it will have them for addPrincipalToCommitmentGroup which will pull them from here + + _addPrincipalToCommitmentGroup( + address(newGroupContract_), + _initialPrincipalAmount, + _commitmentGroupConfig.principalTokenAddress + + ); + + + } + + + //transfer ownership to msg.sender + OwnableUpgradeable(address(newGroupContract_)) + .transferOwnership(msg.sender); + } + + + + function _addPrincipalToCommitmentGroup( + address _newGroupContract, + uint256 _initialPrincipalAmount, + address _principalTokenAddress + ) internal { + + + IERC20(_principalTokenAddress).transferFrom( + msg.sender, + address(this), + _initialPrincipalAmount + ); + IERC20(_principalTokenAddress).approve( + _newGroupContract, + _initialPrincipalAmount + ); + + address sharesRecipient = msg.sender; + + uint256 sharesAmount_ = ILenderCommitmentGroup(address(_newGroupContract)) + .addPrincipalToCommitmentGroup( + _initialPrincipalAmount, + sharesRecipient, + 0 //_minShares + ); + + + } + +} diff --git a/packages/contracts/contracts/oracleprotection/HypernativeOracle.sol b/packages/contracts/contracts/oracleprotection/HypernativeOracle.sol new file mode 100644 index 000000000..a7b523362 --- /dev/null +++ b/packages/contracts/contracts/oracleprotection/HypernativeOracle.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +contract HypernativeOracle is AccessControl { + struct OracleRecord { + uint256 registrationTime; + bool isPotentialRisk; + } + + bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); + bytes32 public constant CONSUMER_ROLE = keccak256("CONSUMER_ROLE"); + uint256 internal threshold = 2 minutes; + + mapping(bytes32 /*hashedAccount*/ => OracleRecord) internal accountHashToRecord; + + event ConsumerAdded(address consumer); + event ConsumerRemoved(address consumer); + event Registered(address consumer, address account); + event Whitelisted(bytes32[] hashedAccounts); + event Allowed(bytes32[] hashedAccounts); + event Blacklisted(bytes32[] hashedAccounts); + event TimeThresholdChanged(uint256 threshold); + + modifier onlyAdmin() { + require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Hypernative Oracle error: admin required"); + _; + } + + modifier onlyOperator() { + require(hasRole(OPERATOR_ROLE, msg.sender), "Hypernative Oracle error: operator required"); + _; + } + + modifier onlyConsumer { + require(hasRole(CONSUMER_ROLE, msg.sender), "Hypernative Oracle error: consumer required"); + _; + } + + constructor(address _admin) { + _grantRole(DEFAULT_ADMIN_ROLE, _admin); + } + + function register(address _account) external onlyConsumer() { + bytes32 _hashedAccount = keccak256(abi.encodePacked(_account, address(this))); + require(accountHashToRecord[_hashedAccount].registrationTime == 0, "Account already registered"); + accountHashToRecord[_hashedAccount].registrationTime = block.timestamp; + emit Registered(msg.sender, _account); + } + + function registerStrict(address _account) external onlyConsumer() { + bytes32 _hashedAccount = keccak256(abi.encodePacked(_account, address(this))); + require(accountHashToRecord[_hashedAccount].registrationTime == 0, "Account already registered"); + accountHashToRecord[_hashedAccount].registrationTime = block.timestamp; + accountHashToRecord[_hashedAccount].isPotentialRisk = true; + emit Registered(msg.sender, _account); + } + + // ** + // * @dev Operator only function, can be used to whitelist accounts in order to pre-approve them + // * @param hashedAccounts array of hashed accounts + // */ + function whitelist(bytes32[] calldata hashedAccounts) public onlyOperator() { + for (uint256 i; i < hashedAccounts.length; i++) { + accountHashToRecord[hashedAccounts[i]].registrationTime = 1; + accountHashToRecord[hashedAccounts[i]].isPotentialRisk = false; + } + emit Whitelisted(hashedAccounts); + } + + // ** + // * @dev Operator only function, can be used to blacklist accounts in order to prevent them from interacting with the protocol + // * @param hashedAccounts array of hashed accounts + // */ + function blacklist(bytes32[] calldata hashedAccounts) public onlyOperator() { + for (uint256 i; i < hashedAccounts.length; i++) { + accountHashToRecord[hashedAccounts[i]].isPotentialRisk = true; + } + emit Blacklisted(hashedAccounts); + } + + // ** + // * @dev Operator only function, when strict mode is being used, this function can be used to allow accounts to interact with the protocol + // * @param hashedAccounts array of hashed accounts + // */ + function allow(bytes32[] calldata hashedAccounts) public onlyOperator() { + for (uint256 i; i < hashedAccounts.length; i++) { + accountHashToRecord[hashedAccounts[i]].isPotentialRisk = false; + } + emit Allowed(hashedAccounts); + } + + /** + * @dev Admin only function, can be used to block any interaction with the protocol, meassured in seconds + */ + function changeTimeThreshold(uint256 _newThreshold) public onlyAdmin() { + require(_newThreshold >= 2 minutes, "Threshold must be greater than 2 minutes"); + threshold = _newThreshold; + emit TimeThresholdChanged(threshold); + } + + function addConsumers(address[] memory consumers) public onlyAdmin() { + for (uint256 i; i < consumers.length; i++) { + _grantRole(CONSUMER_ROLE, consumers[i]); + emit ConsumerAdded(consumers[i]); + } + } + + function revokeConsumers(address[] memory consumers) public onlyAdmin() { + for (uint256 i; i < consumers.length; i++) { + _revokeRole(CONSUMER_ROLE, consumers[i]); + emit ConsumerRemoved(consumers[i]); + } + } + + function addOperator(address operator) public onlyAdmin() { + _grantRole(OPERATOR_ROLE, operator); + } + + function revokeOperator(address operator) public onlyAdmin() { + _revokeRole(OPERATOR_ROLE, operator); + } + + function changeAdmin(address _newAdmin) public onlyAdmin() { + _grantRole(DEFAULT_ADMIN_ROLE, _newAdmin); + _revokeRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + //if true, the account has been registered for two minutes + function isTimeExceeded(address account) external onlyConsumer() view returns (bool) { + bytes32 hashedAccount = keccak256(abi.encodePacked(account, address(this))); + require(accountHashToRecord[hashedAccount].registrationTime != 0, "Account not registered"); + return block.timestamp - accountHashToRecord[hashedAccount].registrationTime > threshold; + } + + function isBlacklistedContext(address _origin, address _sender) external onlyConsumer() view returns (bool) { + bytes32 hashedOrigin = keccak256(abi.encodePacked(_origin, address(this))); + bytes32 hashedSender = keccak256(abi.encodePacked(_sender, address(this))); + return accountHashToRecord[hashedOrigin].isPotentialRisk || accountHashToRecord[hashedSender].isPotentialRisk; + } + + function isBlacklistedAccount(address _account) external onlyConsumer() view returns (bool) { + bytes32 hashedAccount = keccak256(abi.encodePacked(_account, address(this))); + return accountHashToRecord[hashedAccount].isPotentialRisk; + } +} diff --git a/packages/contracts/contracts/oracleprotection/OracleProtectedChild.sol b/packages/contracts/contracts/oracleprotection/OracleProtectedChild.sol new file mode 100644 index 000000000..8bafb3275 --- /dev/null +++ b/packages/contracts/contracts/oracleprotection/OracleProtectedChild.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IOracleProtectionManager} from "../interfaces/oracleprotection/IOracleProtectionManager.sol"; + +abstract contract OracleProtectedChild { + address public immutable ORACLE_MANAGER; + + + modifier onlyOracleApproved() { + + IOracleProtectionManager oracleManager = IOracleProtectionManager(ORACLE_MANAGER); + require( oracleManager .isOracleApproved(msg.sender ) , "Oracle: Not Approved"); + _; + } + + modifier onlyOracleApprovedAllowEOA() { + + IOracleProtectionManager oracleManager = IOracleProtectionManager(ORACLE_MANAGER); + require( oracleManager .isOracleApprovedAllowEOA(msg.sender ) , "Oracle: Not Approved"); + _; + } + + constructor(address oracleManager){ + ORACLE_MANAGER = oracleManager; + } + +} \ No newline at end of file diff --git a/packages/contracts/contracts/oracleprotection/OracleProtectionManager.sol b/packages/contracts/contracts/oracleprotection/OracleProtectionManager.sol new file mode 100644 index 000000000..30766ef90 --- /dev/null +++ b/packages/contracts/contracts/oracleprotection/OracleProtectionManager.sol @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + +import {IHypernativeOracle} from "../interfaces/oracleprotection/IHypernativeOracle.sol"; + +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + + +import {IOracleProtectionManager} from "../interfaces/oracleprotection/IOracleProtectionManager.sol"; + +//can go on the SCF +abstract contract OracleProtectionManager is +IOracleProtectionManager, OwnableUpgradeable +{ + bytes32 private constant HYPERNATIVE_ORACLE_STORAGE_SLOT = bytes32(uint256(keccak256("eip1967.hypernative.oracle")) - 1); + bytes32 private constant HYPERNATIVE_MODE_STORAGE_SLOT = bytes32(uint256(keccak256("eip1967.hypernative.is_strict_mode")) - 1); + + // event OracleAdminChanged(address indexed previousAdmin, address indexed newAdmin); + event OracleAddressChanged(address indexed previousOracle, address indexed newOracle); + + + + modifier onlyOracleApproved() { + + require( isOracleApproved(msg.sender ) , "Oracle: Not Approved"); + _; + } + + modifier onlyOracleApprovedAllowEOA() { + + require( isOracleApprovedAllowEOA(msg.sender ) , "Oracle: Not Approved"); + _; + } + + + + + function oracleRegister(address _account) public virtual { + address oracleAddress = _hypernativeOracle(); + IHypernativeOracle oracle = IHypernativeOracle(oracleAddress); + if (hypernativeOracleIsStrictMode()) { + oracle.registerStrict(_account); + } + else { + oracle.register(_account); + } + } + + + + + function isOracleApproved(address _sender) public returns (bool) { + address oracleAddress = _hypernativeOracle(); + if (oracleAddress == address(0)) { + return true; + } + IHypernativeOracle oracle = IHypernativeOracle(oracleAddress); + if (oracle.isBlacklistedContext( tx.origin,_sender) || !oracle.isTimeExceeded(_sender)) { + return false; + } + return true; + } + + // only allow EOA to interact + function isOracleApprovedOnlyAllowEOA(address _sender) public returns (bool){ + address oracleAddress = _hypernativeOracle(); + if (oracleAddress == address(0)) { + + return true; + } + + IHypernativeOracle oracle = IHypernativeOracle(oracleAddress); + if (oracle.isBlacklistedAccount(_sender) || _sender != tx.origin) { + return false; + } + return true ; + } + + function isOracleApprovedAllowEOA(address _sender) public returns (bool){ + address oracleAddress = _hypernativeOracle(); + + //without an oracle address set, allow all through + if (oracleAddress == address(0)) { + return true; + } + + // any accounts are blocked if blacklisted + IHypernativeOracle oracle = IHypernativeOracle(oracleAddress); + if (oracle.isBlacklistedContext( tx.origin,_sender) ){ + return false; + } + + //smart contracts (delegate calls) are blocked if they havent registered and waited + if ( _sender != tx.origin && !oracle.isTimeExceeded(_sender)) { + return false; + } + + return true ; + } + + + + function _setOracle(address _oracle) internal { + address oldOracle = _hypernativeOracle(); + _setAddressBySlot(HYPERNATIVE_ORACLE_STORAGE_SLOT, _oracle); + emit OracleAddressChanged(oldOracle, _oracle); + } + + + function _setIsStrictMode(bool _mode) internal { + _setValueBySlot(HYPERNATIVE_MODE_STORAGE_SLOT, _mode ? 1 : 0); + } + + + + function _setAddressBySlot(bytes32 slot, address newAddress) internal { + assembly { + sstore(slot, newAddress) + } + } + + function _setValueBySlot(bytes32 _slot, uint256 _value) internal { + assembly { + sstore(_slot, _value) + } + } + + + /* function hypernativeOracleAdmin() public view returns (address) { + return _getAddressBySlot(HYPERNATIVE_ADMIN_STORAGE_SLOT); + }*/ + + function hypernativeOracleIsStrictMode() public view returns (bool) { + return _getValueBySlot(HYPERNATIVE_MODE_STORAGE_SLOT) == 1; + } + + function _getAddressBySlot(bytes32 slot) internal view returns (address addr) { + assembly { + addr := sload(slot) + } + } + + function _getValueBySlot(bytes32 _slot) internal view returns (uint256 _value) { + assembly { + _value := sload(_slot) + } + } + + function _hypernativeOracle() private view returns (address) { + return _getAddressBySlot(HYPERNATIVE_ORACLE_STORAGE_SLOT); + } + + +} \ No newline at end of file diff --git a/packages/contracts/contracts/pausing/HasProtocolPausingManager.sol b/packages/contracts/contracts/pausing/HasProtocolPausingManager.sol new file mode 100644 index 000000000..64ab5127f --- /dev/null +++ b/packages/contracts/contracts/pausing/HasProtocolPausingManager.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol) + +pragma solidity ^0.8.0; + + + +//import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + + +import "../interfaces/IHasProtocolPausingManager.sol"; + +import "../interfaces/IProtocolPausingManager.sol"; + + +abstract contract HasProtocolPausingManager + is + IHasProtocolPausingManager + { + + + //Both the bool and the address together take one storage slot + bool private __paused;// .. Deprecated , handled by pausing manager now + + address private _protocolPausingManager; // 20 bytes, gap will start at new slot + + + modifier whenLiquidationsNotPaused() { + require(! IProtocolPausingManager(_protocolPausingManager). liquidationsPaused(), "Liquidations paused" ); + + _; + } + + + modifier whenProtocolNotPaused() { + require(! IProtocolPausingManager(_protocolPausingManager). protocolPaused(), "Protocol paused" ); + + _; + } + + + + function _setProtocolPausingManager(address protocolPausingManager) internal { + _protocolPausingManager = protocolPausingManager ; + } + + + function getProtocolPausingManager() public view returns (address){ + + return _protocolPausingManager; + } + + + + + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; +} diff --git a/packages/contracts/contracts/pausing/ProtocolPausingManager.sol b/packages/contracts/contracts/pausing/ProtocolPausingManager.sol new file mode 100644 index 000000000..63e447e21 --- /dev/null +++ b/packages/contracts/contracts/pausing/ProtocolPausingManager.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol) + +pragma solidity ^0.8.0; + + + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; + +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + + + +import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; + + + +import "../interfaces/IProtocolPausingManager.sol"; + + +import "../interfaces/IPausableTimestamp.sol"; + +/** + + TODO: + + Move SCF pausing into here ?? + + + */ +contract ProtocolPausingManager is ContextUpgradeable, OwnableUpgradeable, IProtocolPausingManager , IPausableTimestamp{ + using MathUpgradeable for uint256; + + bool private _protocolPaused; + bool private _liquidationsPaused; + //bool private _liquidityPoolsPaused; + + + // u8 private _currentPauseState; //use an enum !!! + + mapping(address => bool) public pauserRoleBearer; + + + uint256 private lastPausedAt; + uint256 private lastUnpausedAt; + + + // Events + event PausedProtocol(address indexed account); + event UnpausedProtocol(address indexed account); + event PausedLiquidations(address indexed account); + event UnpausedLiquidations(address indexed account); + event PauserAdded(address indexed account); + event PauserRemoved(address indexed account); + + + modifier onlyPauser() { + require( isPauser( _msgSender()) ); + _; + } + + + //need to initialize so owner is owner (transfer ownership to safe) + + function initialize( + + ) external initializer { + + __Ownable_init(); + + } + + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function protocolPaused() public view virtual returns (bool) { + return _protocolPaused; + } + + + function liquidationsPaused() public view virtual returns (bool) { + return _liquidationsPaused; + } + + + /* + function _requireNotPaused() internal view virtual { + require(!paused(), "Pausable: paused"); + } + + function _requirePaused() internal view virtual { + require(paused(), "Pausable: not paused"); + } + */ + + + + + + function pauseProtocol() public virtual onlyPauser { + require( _protocolPaused == false); + _protocolPaused = true; + lastPausedAt = block.timestamp; + emit PausedProtocol(_msgSender()); + } + + + function unpauseProtocol() public virtual onlyPauser { + + require( _protocolPaused == true); + _protocolPaused = false; + lastUnpausedAt = block.timestamp; + emit UnpausedProtocol(_msgSender()); + } + + function pauseLiquidations() public virtual onlyPauser { + + require( _liquidationsPaused == false); + _liquidationsPaused = true; + lastPausedAt = block.timestamp; + emit PausedLiquidations(_msgSender()); + } + + + function unpauseLiquidations() public virtual onlyPauser { + require( _liquidationsPaused == true); + _liquidationsPaused = false; + lastUnpausedAt = block.timestamp; + emit UnpausedLiquidations(_msgSender()); + } + + function getLastPausedAt() + external view + returns (uint256) { + + return lastPausedAt; + + } + + + function getLastUnpausedAt() + external view + returns (uint256) { + + return lastUnpausedAt; + + } + + + + + // Role Management + + + function addPauser(address _pauser) public virtual onlyOwner { + pauserRoleBearer[_pauser] = true; + emit PauserAdded(_pauser); + } + + + function removePauser(address _pauser) public virtual onlyOwner { + pauserRoleBearer[_pauser] = false; + emit PauserRemoved(_pauser); + } + + + function isPauser(address _account) public view returns(bool){ + return pauserRoleBearer[_account] || _account == owner(); + } + + +} diff --git a/packages/contracts/contracts/penetrationtesting/LenderGroupMockInteract.sol b/packages/contracts/contracts/penetrationtesting/LenderGroupMockInteract.sol new file mode 100644 index 000000000..0b1b317ce --- /dev/null +++ b/packages/contracts/contracts/penetrationtesting/LenderGroupMockInteract.sol @@ -0,0 +1,125 @@ +pragma solidity >=0.8.0 <0.9.0; +// SPDX-License-Identifier: MIT + + +import "../interfaces/ILenderCommitmentGroup.sol"; + +import "../libraries/NumbersLib.sol"; + + import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract LenderGroupMockInteract { + + + + function addPrincipalToCommitmentGroup( + address _target, + address _principalToken, + address _sharesToken, + uint256 _amount, + address _sharesRecipient, + uint256 _minSharesAmountOut ) public { + + // Transfer the ERC20 tokens to this contract + IERC20(_principalToken).transferFrom(msg.sender, address(this), _amount); + + // Approve the target contract to spend tokens on behalf of this contract + IERC20(_principalToken).approve(_target, _amount); + + + //need to suck in ERC 20 and approve them ? + + uint256 sharesAmount = ILenderCommitmentGroup(_target).addPrincipalToCommitmentGroup( + _amount, + _sharesRecipient, + _minSharesAmountOut + + ); + + + IERC20(_sharesToken).transfer(msg.sender, sharesAmount); + } + + /** + * @notice Allows the contract owner to prepare shares for withdrawal in the specified LenderCommitmentGroup contract. + * @param _target The LenderCommitmentGroup contract address. + * @param _amountPoolSharesTokens The amount of share tokens to prepare for withdrawal. + */ + function prepareSharesForWithdraw( + address _target, + uint256 _amountPoolSharesTokens + ) external { + ILenderCommitmentGroup(_target).prepareSharesForWithdraw( + _amountPoolSharesTokens + ); + } + + /** + * @notice Allows the contract owner to burn shares and withdraw principal tokens from the specified LenderCommitmentGroup contract. + * @param _target The LenderCommitmentGroup contract address. + * @param _amountPoolSharesTokens The amount of share tokens to burn. + * @param _recipient The address to receive the withdrawn principal tokens. + * @param _minAmountOut The minimum amount of principal tokens expected from the withdrawal. + */ + function burnSharesToWithdrawEarnings( + address _target, + address _principalToken, + address _poolSharesToken, + uint256 _amountPoolSharesTokens, + address _recipient, + uint256 _minAmountOut + ) external { + + + + // Transfer the ERC20 tokens to this contract + IERC20(_poolSharesToken).transferFrom(msg.sender, address(this), _amountPoolSharesTokens); + + // Approve the target contract to spend tokens on behalf of this contract + IERC20(_poolSharesToken).approve(_target, _amountPoolSharesTokens); + + + + uint256 amountOut = ILenderCommitmentGroup(_target).burnSharesToWithdrawEarnings( + _amountPoolSharesTokens, + _recipient, + _minAmountOut + ); + + IERC20(_principalToken).transfer(msg.sender, amountOut); + } + + /** + * @notice Allows the contract owner to liquidate a defaulted loan with incentive in the specified LenderCommitmentGroup contract. + * @param _target The LenderCommitmentGroup contract address. + * @param _bidId The ID of the defaulted loan bid to liquidate. + * @param _tokenAmountDifference The incentive amount required for liquidation. + */ + function liquidateDefaultedLoanWithIncentive( + address _target, + address _principalToken, + uint256 _bidId, + int256 _tokenAmountDifference, + int256 loanAmount + ) external { + + // Transfer the ERC20 tokens to this contract + IERC20(_principalToken).transferFrom(msg.sender, address(this), uint256(_tokenAmountDifference + loanAmount)); + + // Approve the target contract to spend tokens on behalf of this contract + IERC20(_principalToken).approve(_target, uint256(_tokenAmountDifference + loanAmount)); + + + ILenderCommitmentGroup(_target).liquidateDefaultedLoanWithIncentive( + _bidId, + _tokenAmountDifference + ); + } + + + + + + +}