From 51613c5ac6f3a676aee46dd4d1810699d75e06e6 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Fri, 8 Nov 2024 11:08:13 -0500 Subject: [PATCH] add --- .../contracts/contracts/CollateralManager.sol | 54 +- .../LenderCommitmentForwarder_G2.sol | 2 +- .../LenderCommitmentForwarder_U1.sol | 55 +- .../LoanReferralForwarder.sol | 234 +++++++++ .../SmartCommitmentForwarder.sol | 66 +-- .../extensions/FlashRolloverLoan.sol | 11 +- .../extensions/FlashRolloverLoanWidget.sol | 16 + .../extensions/FlashRolloverLoan_G3.sol | 2 +- .../extensions/FlashRolloverLoan_G4.sol | 3 +- .../extensions/FlashRolloverLoan_G5.sol | 14 - .../extensions/FlashRolloverLoan_G6.sol | 495 ++++++++++++++++++ .../LenderCommitmentGroup_Factory.sol | 129 +++++ .../LenderCommitmentGroup_Smart.sol | 460 ++++------------ .../contracts/contracts/MarketRegistry.sol | 8 +- packages/contracts/contracts/TellerV2.sol | 141 ++--- .../contracts/contracts/TellerV2Context.sol | 2 +- .../contracts/TellerV2MarketForwarder_G3.sol | 2 +- .../contracts/contracts/TellerV2Storage.sol | 7 +- .../contracts/escrow/CollateralEscrowV1.sol | 14 +- .../interfaces/ICollateralManager.sol | 13 +- .../interfaces/IFlashRolloverLoan.sol | 2 +- .../interfaces/IFlashRolloverLoan_G4.sol | 3 +- .../interfaces/IFlashRolloverLoan_G6.sol | 14 + .../interfaces/IHasProtocolPausingManager.sol | 19 + .../interfaces/ILenderCommitmentForwarder.sol | 1 + .../ILenderCommitmentForwarder_U1.sol | 13 + .../interfaces/ILenderCommitmentGroup.sol | 5 +- .../interfaces/IPausableTimestamp.sol | 22 + .../interfaces/IProtocolPausingManager.sol | 19 + .../contracts/interfaces/ISmartCommitment.sol | 4 +- .../interfaces/ISmartCommitmentForwarder.sol | 9 + .../contracts/interfaces/ITellerV2.sol | 8 +- .../interfaces/IUniswapPricingLibrary.sol | 26 + .../interfaces/escrow/ICollateralEscrowV1.sol | 5 +- .../oracleprotection/IHypernativeOracle.sol | 10 + .../IOracleProtectionManager.sol | 9 + .../libraries/UniswapPricingLibrary.sol | 134 +++++ .../contracts/libraries/V2Calculations.sol | 8 +- .../contracts/mock/TellerV2SolMock.sol | 17 +- .../contracts/contracts/mock/WethMock.sol | 3 + .../openzeppelin/ERC1967/ERC1967Proxy.sol | 40 ++ .../openzeppelin/ERC1967/ERC1967Utils.sol | 191 +++++++ .../openzeppelin/ERC1967/IERC1967.sol | 24 + .../contracts/openzeppelin/Ownable.sol | 100 ++++ .../contracts/openzeppelin/Proxy.sol | 69 +++ .../contracts/openzeppelin/ProxyAdmin.sol | 45 ++ .../TransparentUpgradeableProxy.sol | 117 +++++ .../contracts/openzeppelin/beacon/IBeacon.sol | 16 + .../contracts/openzeppelin/utils/Address.sol | 151 ++++++ .../contracts/openzeppelin/utils/Context.sol | 28 + .../contracts/openzeppelin/utils/Errors.sol | 26 + .../openzeppelin/utils/StorageSlot.sol | 134 +++++ .../oracleprotection/HypernativeOracle.sol | 147 ++++++ .../oracleprotection/OracleProtectedChild.sol | 28 + .../OracleProtectionManager.sol | 155 ++++++ .../pausing/HasProtocolPausingManager.sol | 63 +++ .../pausing/ProtocolPausingManager.sol | 175 +++++++ .../LenderGroupMockInteract.sol | 125 +++++ 58 files changed, 3034 insertions(+), 659 deletions(-) create mode 100644 packages/contracts/contracts/LenderCommitmentForwarder/LoanReferralForwarder.sol create mode 100644 packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoanWidget.sol create mode 100644 packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G6.sol create mode 100644 packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Factory.sol create mode 100644 packages/contracts/contracts/interfaces/IFlashRolloverLoan_G6.sol create mode 100644 packages/contracts/contracts/interfaces/IHasProtocolPausingManager.sol create mode 100644 packages/contracts/contracts/interfaces/IPausableTimestamp.sol create mode 100644 packages/contracts/contracts/interfaces/IProtocolPausingManager.sol create mode 100644 packages/contracts/contracts/interfaces/IUniswapPricingLibrary.sol create mode 100644 packages/contracts/contracts/interfaces/oracleprotection/IHypernativeOracle.sol create mode 100644 packages/contracts/contracts/interfaces/oracleprotection/IOracleProtectionManager.sol create mode 100644 packages/contracts/contracts/libraries/UniswapPricingLibrary.sol create mode 100644 packages/contracts/contracts/openzeppelin/ERC1967/ERC1967Proxy.sol create mode 100644 packages/contracts/contracts/openzeppelin/ERC1967/ERC1967Utils.sol create mode 100644 packages/contracts/contracts/openzeppelin/ERC1967/IERC1967.sol create mode 100644 packages/contracts/contracts/openzeppelin/Ownable.sol create mode 100644 packages/contracts/contracts/openzeppelin/Proxy.sol create mode 100644 packages/contracts/contracts/openzeppelin/ProxyAdmin.sol create mode 100644 packages/contracts/contracts/openzeppelin/TransparentUpgradeableProxy.sol create mode 100644 packages/contracts/contracts/openzeppelin/beacon/IBeacon.sol create mode 100644 packages/contracts/contracts/openzeppelin/utils/Address.sol create mode 100644 packages/contracts/contracts/openzeppelin/utils/Context.sol create mode 100644 packages/contracts/contracts/openzeppelin/utils/Errors.sol create mode 100644 packages/contracts/contracts/openzeppelin/utils/StorageSlot.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/CollateralManager.sol b/packages/contracts/contracts/CollateralManager.sol index 5d1b5a83b..0a7e8d9ea 100644 --- a/packages/contracts/contracts/CollateralManager.sol +++ b/packages/contracts/contracts/CollateralManager.sol @@ -7,7 +7,7 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; // Libraries import "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; + // Interfaces import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; @@ -70,19 +70,6 @@ contract CollateralManager is OwnableUpgradeable, ICollateralManager { _; } - modifier onlyProtocolOwner() { - - address protocolOwner = OwnableUpgradeable(address(tellerV2)).owner(); - - require(_msgSender() == protocolOwner, "Sender not authorized"); - _; - } - - modifier whenProtocolNotPaused() { - require( PausableUpgradeable(address(tellerV2)).paused() == false , "Protocol is paused"); - _; - } - /* External Functions */ /** @@ -267,7 +254,7 @@ contract CollateralManager is OwnableUpgradeable, ICollateralManager { * @notice Withdraws deposited collateral from the created escrow of a bid that has been successfully repaid. * @param _bidId The id of the bid to withdraw collateral for. */ - function withdraw(uint256 _bidId) external whenProtocolNotPaused { + function withdraw(uint256 _bidId) external { BidState bidState = tellerV2.getBidState(_bidId); require(bidState == BidState.PAID, "collateral cannot be withdrawn"); @@ -277,25 +264,11 @@ contract CollateralManager is OwnableUpgradeable, ICollateralManager { emit CollateralClaimed(_bidId); } - function withdrawDustTokens( - uint256 _bidId, - address _tokenAddress, - uint256 _amount, - address _recipientAddress - ) external onlyProtocolOwner whenProtocolNotPaused { - - ICollateralEscrowV1(_escrows[_bidId]).withdrawDustTokens( - _tokenAddress, - _amount, - _recipientAddress - ); - } - /** * @notice Withdraws deposited collateral from the created escrow of a bid that has been CLOSED after being defaulted. * @param _bidId The id of the bid to withdraw collateral for. */ - function lenderClaimCollateral(uint256 _bidId) external onlyTellerV2 whenProtocolNotPaused { + function lenderClaimCollateral(uint256 _bidId) external onlyTellerV2 { if (isBidCollateralBacked(_bidId)) { BidState bidState = tellerV2.getBidState(_bidId); @@ -309,24 +282,6 @@ contract CollateralManager is OwnableUpgradeable, ICollateralManager { } } - /** - * @notice Withdraws deposited collateral from the created escrow of a bid that has been CLOSED after being defaulted. - * @param _bidId The id of the bid to withdraw collateral for. - */ - function lenderClaimCollateralWithRecipient(uint256 _bidId, address _collateralRecipient) external onlyTellerV2 whenProtocolNotPaused { - if (isBidCollateralBacked(_bidId)) { - BidState bidState = tellerV2.getBidState(_bidId); - - require( - bidState == BidState.CLOSED, - "Loan has not been liquidated" - ); - - _withdraw(_bidId, _collateralRecipient); - emit CollateralClaimed(_bidId); - } - } - /** * @notice Sends the deposited collateral to a liquidator of a bid. * @notice Can only be called by the protocol. @@ -336,7 +291,6 @@ contract CollateralManager is OwnableUpgradeable, ICollateralManager { function liquidateCollateral(uint256 _bidId, address _liquidatorAddress) external onlyTellerV2 - whenProtocolNotPaused { if (isBidCollateralBacked(_bidId)) { BidState bidState = tellerV2.getBidState(_bidId); @@ -632,4 +586,4 @@ contract CollateralManager is OwnableUpgradeable, ICollateralManager { ) ); } -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/LenderCommitmentForwarder_G2.sol b/packages/contracts/contracts/LenderCommitmentForwarder/LenderCommitmentForwarder_G2.sol index c500484d0..57bdfbc07 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/LenderCommitmentForwarder_G2.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/LenderCommitmentForwarder_G2.sol @@ -477,7 +477,7 @@ contract LenderCommitmentForwarder_G2 is ); require( - commitmentPrincipalAccepted[_commitmentId] <= commitment.maxPrincipal, + commitmentPrincipalAccepted[bidId] <= commitment.maxPrincipal, "Invalid loan max principal" ); diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/LenderCommitmentForwarder_U1.sol b/packages/contracts/contracts/LenderCommitmentForwarder/LenderCommitmentForwarder_U1.sol index 0f9a9a936..d73184563 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/LenderCommitmentForwarder_U1.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/LenderCommitmentForwarder_U1.sol @@ -33,6 +33,14 @@ import "./extensions/ExtensionsContextUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + + +/* + +Only do decimal expansion if it is an ERC20 not anything else !! + +*/ + contract LenderCommitmentForwarder_U1 is ExtensionsContextUpgradeable, //this should always be first for upgradeability TellerV2MarketForwarder_G2, @@ -186,7 +194,7 @@ contract LenderCommitmentForwarder_U1 is uint16 _poolOracleLtvRatio //generally always between 0 and 100 % , 0 to 10000 ) public returns (uint256 commitmentId_) { commitmentId_ = commitmentCount++; - + require( _commitment.lender == _msgSender(), "unauthorized commitment creator" @@ -194,9 +202,15 @@ contract LenderCommitmentForwarder_U1 is commitments[commitmentId_] = _commitment; + require(_poolRoutes.length == 0 || _commitment.collateralTokenType == CommitmentCollateralType.ERC20 , "can only use pool routes with ERC20 collateral"); + + //routes length of 0 means ignore price oracle limits require(_poolRoutes.length <= 2, "invalid pool routes length"); + + + for (uint256 i = 0; i < _poolRoutes.length; i++) { commitmentUniswapPoolRoutes[commitmentId_].push(_poolRoutes[i]); } @@ -521,7 +535,7 @@ contract LenderCommitmentForwarder_U1 is ); require( - commitmentPrincipalAccepted[_commitmentId] <= commitment.maxPrincipal, + commitmentPrincipalAccepted[bidId] <= commitment.maxPrincipal, "Invalid loan max principal" ); @@ -648,13 +662,26 @@ contract LenderCommitmentForwarder_U1 is return 0; } - return + if (_collateralTokenType == CommitmentCollateralType.ERC20) { + return MathUpgradeable.mulDiv( _principalAmount, STANDARD_EXPANSION_FACTOR, _maxPrincipalPerCollateralAmount, MathUpgradeable.Rounding.Up ); + } + + //for NFTs, do not use the uniswap expansion factor + return + MathUpgradeable.mulDiv( + _principalAmount, + 1, + _maxPrincipalPerCollateralAmount, + MathUpgradeable.Rounding.Up + ); + + } /** @@ -787,8 +814,8 @@ contract LenderCommitmentForwarder_U1 is (sqrtPriceX96, , , , , , ) = IUniswapV3Pool(uniswapV3Pool).slot0(); } else { uint32[] memory secondsAgos = new uint32[](2); - secondsAgos[0] = twapInterval; // from (before) - secondsAgos[1] = 0; // to (now) + secondsAgos[0] = twapInterval + 1; // from (before) + secondsAgos[1] = 1; // one block prior (int56[] memory tickCumulatives, ) = IUniswapV3Pool(uniswapV3Pool) .observe(secondsAgos); @@ -888,6 +915,24 @@ contract LenderCommitmentForwarder_U1 is return commitments[_commitmentId].maxPrincipal; } + function getCommitmentPrincipalTokenAddress(uint256 _commitmentId) + external + view + returns (address) + { + return commitments[_commitmentId].principalTokenAddress; + } + + function getCommitmentCollateralTokenAddress(uint256 _commitmentId) + external + view + returns (address) + { + return commitments[_commitmentId].collateralTokenAddress; + } + + + //Overrides function _msgSender() internal diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/LoanReferralForwarder.sol b/packages/contracts/contracts/LenderCommitmentForwarder/LoanReferralForwarder.sol new file mode 100644 index 000000000..eb625bbdf --- /dev/null +++ b/packages/contracts/contracts/LenderCommitmentForwarder/LoanReferralForwarder.sol @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Contracts +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 "../interfaces/IMarketRegistry.sol"; +import "../interfaces/ISmartCommitment.sol"; +import "../interfaces/ISmartCommitmentForwarder.sol"; +import "../interfaces/IFlashRolloverLoan_G4.sol"; +import "../libraries/NumbersLib.sol"; + +import { ILenderCommitmentForwarder } from "../interfaces/ILenderCommitmentForwarder.sol"; + +import { ILenderCommitmentForwarder_U1 } from "../interfaces/ILenderCommitmentForwarder_U1.sol"; + + + + +contract LoanReferralForwarder + { + using AddressUpgradeable for address; + using NumbersLib for uint256; + + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + ITellerV2 public immutable TELLER_V2; + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + + + + struct AcceptCommitmentArgs { + uint256 commitmentId; + address smartCommitmentAddress; //if this is not address(0), we will use this ! leave empty if not used. + uint256 principalAmount; + uint256 collateralAmount; + uint256 collateralTokenId; + address collateralTokenAddress; + uint16 interestRate; + uint32 loanDuration; + bytes32[] merkleProof; //empty array if not used + } + + + event CommitmentAcceptedWithReferral( + uint256 indexed bidId, + address indexed recipient, + uint256 fundsRemaining, + uint256 reward, + address rewardRecipient + ); + + /** + * + * @notice Initializes the FlashRolloverLoan with necessary contract addresses. + * + * @dev Using a custom OpenZeppelin upgrades tag. Ensure the constructor logic is safe for upgrades. + * + * @param _tellerV2 The address of the TellerV2 contract. + + */ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor( + address _tellerV2 + + ) { + TELLER_V2 = ITellerV2(_tellerV2); + + } + + + + /* + + */ + function acceptCommitmentWithReferral( + address _commitmentForwarder, //leave 0 if using smart commitment address + + + AcceptCommitmentArgs calldata _acceptCommitmentArgs , + + + address _recipient, + uint256 _reward, + address _rewardRecipient + + ) external returns (uint256 bidId_) { + + + address principalTokenAddress = address(0); + uint256 balanceBefore; + + + require(_reward <= _acceptCommitmentArgs.principalAmount / 10, "Reward can be no more than 10% of principal"); + + if (_acceptCommitmentArgs.smartCommitmentAddress != address(0)) { + + + principalTokenAddress = ISmartCommitment(_acceptCommitmentArgs.smartCommitmentAddress).getPrincipalTokenAddress (); + + // Accept commitment and receive funds to this contract + balanceBefore = IERC20(principalTokenAddress).balanceOf(address(this)); + + bidId_ = _acceptSmartCommitmentWithRecipient( + _commitmentForwarder, + _acceptCommitmentArgs + + ); + + + }else{ + principalTokenAddress = ILenderCommitmentForwarder_U1(_commitmentForwarder) + .getCommitmentPrincipalTokenAddress (_acceptCommitmentArgs.commitmentId); + + // Accept commitment and receive funds to this contract + balanceBefore = IERC20(principalTokenAddress).balanceOf(address(this)); + + + bidId_ = _acceptCommitmentWithRecipient( + _commitmentForwarder, + _acceptCommitmentArgs + + ); + + + + } + + uint256 balanceAfter = IERC20(principalTokenAddress).balanceOf(address(this)); + + uint256 fundsRemaining = balanceAfter - balanceBefore; + + // require( fundsRemaining >= _minAmountReceived, "Insufficient funds received" ); + + + IERC20Upgradeable(principalTokenAddress).transfer( + _rewardRecipient, + _reward + ); + + IERC20Upgradeable(principalTokenAddress).transfer( + _recipient, + fundsRemaining - _reward + ); + + + emit CommitmentAcceptedWithReferral(bidId_, _recipient, fundsRemaining, _reward, _rewardRecipient); + + + } + + + function _acceptSmartCommitmentWithRecipient( + address _smartCommitmentForwarder, + AcceptCommitmentArgs calldata _acceptCommitmentArgs + + + ) internal returns (uint256 bidId_) { + + bytes memory responseData = address(_smartCommitmentForwarder) + .functionCall( + abi.encodePacked( + abi.encodeWithSelector( + ISmartCommitmentForwarder + .acceptSmartCommitmentWithRecipient + .selector, + _acceptCommitmentArgs.smartCommitmentAddress, + _acceptCommitmentArgs.principalAmount, + _acceptCommitmentArgs.collateralAmount, + _acceptCommitmentArgs.collateralTokenId, + _acceptCommitmentArgs.collateralTokenAddress, + address(this), + _acceptCommitmentArgs.interestRate, + _acceptCommitmentArgs.loanDuration + ), + msg.sender // borrower + ) + ); + + + (bidId_) = abi.decode(responseData, (uint256)); + + + + } + + + + function _acceptCommitmentWithRecipient( + address _commitmentForwarder, + AcceptCommitmentArgs calldata _acceptCommitmentArgs + + + ) internal returns (uint256 bidId_) { + + bytes memory responseData = address(_commitmentForwarder) + .functionCall( + abi.encodePacked( + abi.encodeWithSelector( + ILenderCommitmentForwarder + .acceptCommitmentWithRecipient + .selector, + _acceptCommitmentArgs.commitmentId, + _acceptCommitmentArgs.principalAmount, + _acceptCommitmentArgs.collateralAmount, + _acceptCommitmentArgs.collateralTokenId, + _acceptCommitmentArgs.collateralTokenAddress, + address(this), + _acceptCommitmentArgs.interestRate, + _acceptCommitmentArgs.loanDuration + ), + msg.sender //borrower + ) + ); + + (bidId_) = abi.decode(responseData, (uint256)); + + + } + + + /** + * @notice Fetches the protocol fee percentage from the Teller V2 protocol. + * @return The protocol fee percentage as defined in the Teller V2 protocol. + */ + function _getProtocolFeePct() internal view returns (uint16) { + return IProtocolFee(address(TELLER_V2)).protocolFee(); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/SmartCommitmentForwarder.sol b/packages/contracts/contracts/LenderCommitmentForwarder/SmartCommitmentForwarder.sol index de1a39c1d..d8b986426 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/SmartCommitmentForwarder.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/SmartCommitmentForwarder.sol @@ -2,22 +2,13 @@ pragma solidity ^0.8.0; import "../TellerV2MarketForwarder_G3.sol"; -import "./extensions/ExtensionsContextUpgradeable.sol"; + import "../interfaces/ILenderCommitmentForwarder.sol"; -import "../interfaces/ISmartCommitmentForwarder.sol"; import "./LenderCommitmentForwarder_G1.sol"; -import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; - import { CommitmentCollateralType, ISmartCommitment } from "../interfaces/ISmartCommitment.sol"; - -contract SmartCommitmentForwarder is - ExtensionsContextUpgradeable, //this should always be first for upgradeability - TellerV2MarketForwarder_G3, - PausableUpgradeable, //this does add some storage but AFTER all other storage - ISmartCommitmentForwarder - { +contract SmartCommitmentForwarder is TellerV2MarketForwarder_G3 { event ExercisedSmartCommitment( address indexed smartCommitmentAddress, address borrower, @@ -27,23 +18,9 @@ contract SmartCommitmentForwarder is error InsufficientBorrowerCollateral(uint256 required, uint256 actual); - - - modifier onlyProtocolPauser() { - require( ITellerV2( _tellerV2 ).isPauser(_msgSender()) , "Sender not authorized"); - _; - } - - - constructor(address _protocolAddress, address _marketRegistry) TellerV2MarketForwarder_G3(_protocolAddress, _marketRegistry) - { } - - function initialize() public initializer { - __Pausable_init(); - } - + {} /** * @notice Accept the commitment to submitBid and acceptBid using the funds @@ -58,7 +35,7 @@ contract SmartCommitmentForwarder is * @param _loanDuration The overall duration for the loan. Must be longer than market payment cycle duration. * @return bidId The ID of the loan that was created on TellerV2 */ - function acceptSmartCommitmentWithRecipient( + function acceptCommitmentWithRecipient( address _smartCommitmentAddress, uint256 _principalAmount, uint256 _collateralAmount, @@ -67,7 +44,7 @@ contract SmartCommitmentForwarder is address _recipient, uint16 _interestRate, uint32 _loanDuration - ) public whenNotPaused returns (uint256 bidId) { + ) public returns (uint256 bidId) { require( ISmartCommitment(_smartCommitmentAddress) .getCollateralTokenType() <= @@ -176,35 +153,4 @@ contract SmartCommitmentForwarder is revert("Unknown Collateral Type"); } - - - - /** - * @notice Lets the DAO/owner of the protocol implement an emergency stop mechanism. - */ - function pause() public virtual onlyProtocolPauser whenNotPaused { - _pause(); - } - - /** - * @notice Lets the DAO/owner of the protocol undo a previously implemented emergency stop. - */ - function unpause() public virtual onlyProtocolPauser whenPaused { - _unpause(); - } - - - // ----- - - //Overrides - function _msgSender() - internal - view - virtual - override(ContextUpgradeable, ExtensionsContextUpgradeable) - returns (address sender) - { - return ExtensionsContextUpgradeable._msgSender(); - } - -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan.sol index 0bfa92e15..ba2d15edf 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan.sol @@ -1,18 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; + +import "./FlashRolloverLoan_G5.sol"; -import "../../interfaces/IFlashRolloverLoan.sol"; -import "./FlashRolloverLoan_G3.sol"; - -contract FlashRolloverLoan is IFlashRolloverLoan, FlashRolloverLoan_G3 { +contract FlashRolloverLoan is FlashRolloverLoan_G5 { constructor( address _tellerV2, - address _lenderCommitmentForwarder, address _poolAddressesProvider ) - FlashRolloverLoan_G3( + FlashRolloverLoan_G5( _tellerV2, - _lenderCommitmentForwarder, _poolAddressesProvider ) {} diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoanWidget.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoanWidget.sol new file mode 100644 index 000000000..37e8b9ddc --- /dev/null +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoanWidget.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "./FlashRolloverLoan_G6.sol"; + +contract FlashRolloverLoanWidget is FlashRolloverLoan_G6 { + constructor( + address _tellerV2, + address _poolAddressesProvider + ) + FlashRolloverLoan_G6( + _tellerV2, + _poolAddressesProvider + ) + {} +} diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G3.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G3.sol index c4a1bce0a..fa9777c25 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G3.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G3.sol @@ -431,4 +431,4 @@ contract FlashRolloverLoan_G3 is IFlashLoanSimpleReceiver, IFlashRolloverLoan { function _getProtocolFeePct() internal view returns (uint16) { return IProtocolFee(address(TELLER_V2)).protocolFee(); } -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G4.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G4.sol index a677676c7..b79315926 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G4.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G4.sol @@ -432,4 +432,5 @@ contract FlashRolloverLoan_G4 is IFlashLoanSimpleReceiver, IFlashRolloverLoan_G4 function _getProtocolFeePct() internal view returns (uint16) { return IProtocolFee(address(TELLER_V2)).protocolFee(); } -} \ No newline at end of file +} + diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G5.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G5.sol index b6b7cf733..7156068fc 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G5.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G5.sol @@ -115,7 +115,6 @@ contract FlashRolloverLoan_G5 is IFlashLoanSimpleReceiver, IFlashRolloverLoan_G4 ); } - // Call 'Flash' on the vault to borrow funds and call tellerV2FlashCallback // This ultimately calls executeOperation IPool(POOL()).flashLoanSimple( @@ -133,14 +132,6 @@ contract FlashRolloverLoan_G5 is IFlashLoanSimpleReceiver, IFlashRolloverLoan_G4 ), 0 //referral code ); - - - ///have to set approval to zero AFTER we repay the flash loan to aave - IERC20Upgradeable(lendingToken).approve( - address(POOL()), - 0 - ); - } /** @@ -255,11 +246,6 @@ contract FlashRolloverLoan_G5 is IFlashLoanSimpleReceiver, IFlashRolloverLoan_G4 ); TELLER_V2.repayLoanFull(_bidId); - IERC20Upgradeable(_principalToken).approve( - address(TELLER_V2), - 0 - ); - uint256 fundsAfterRepayment = IERC20Upgradeable(_principalToken) .balanceOf(address(this)); diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G6.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G6.sol new file mode 100644 index 000000000..2d401a80c --- /dev/null +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/FlashRolloverLoan_G6.sol @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Contracts +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 "../../interfaces/IMarketRegistry.sol"; +import "../../interfaces/ILenderCommitmentForwarder.sol"; +import "../../interfaces/ISmartCommitmentForwarder.sol"; +import "../../interfaces/IFlashRolloverLoan_G6.sol"; +import "../../libraries/NumbersLib.sol"; + +import { IPool } from "../../interfaces/aave/IPool.sol"; +import { IFlashLoanSimpleReceiver } from "../../interfaces/aave/IFlashLoanSimpleReceiver.sol"; +import { IPoolAddressesProvider } from "../../interfaces/aave/IPoolAddressesProvider.sol"; + + + +/* + G6: Additionally incorporates referral rewards +*/ + + +contract FlashRolloverLoan_G6 is IFlashLoanSimpleReceiver, IFlashRolloverLoan_G6 { + using AddressUpgradeable for address; + using NumbersLib for uint256; + + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + ITellerV2 public immutable TELLER_V2; + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + + + address public immutable POOL_ADDRESSES_PROVIDER; + + event RolloverLoanComplete( + address borrower, + uint256 originalLoanId, + uint256 newLoanId, + uint256 fundsRemaining + ); + + struct AcceptCommitmentArgs { + uint256 commitmentId; + address smartCommitmentAddress; //if this is not address(0), we will use this ! leave empty if not used. + uint256 principalAmount; + uint256 collateralAmount; + uint256 collateralTokenId; + address collateralTokenAddress; + uint16 interestRate; + uint32 loanDuration; + bytes32[] merkleProof; //empty array if not used + } + + /** + * + * @notice Initializes the FlashRolloverLoan with necessary contract addresses. + * + * @dev Using a custom OpenZeppelin upgrades tag. Ensure the constructor logic is safe for upgrades. + * + * @param _tellerV2 The address of the TellerV2 contract. + * @param _lenderCommitmentForwarder The address of the LenderCommitmentForwarder contract. + * @param _poolAddressesProvider The address of the PoolAddressesProvider. + */ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor( + address _tellerV2, + address _poolAddressesProvider + ) { + TELLER_V2 = ITellerV2(_tellerV2); + POOL_ADDRESSES_PROVIDER = _poolAddressesProvider; + } + + modifier onlyFlashLoanPool() { + require( + msg.sender == address(POOL()), + "FlashRolloverLoan: Must be called by FlashLoanPool" + ); + + _; + } + + /** + * + * @notice Allows the borrower to rollover their existing loan using a flash loan mechanism. + * The borrower might also provide an additional amount during the rollover. + * + * @dev The function first verifies that the caller is the borrower of the loan. + * It then optionally transfers the additional amount specified by the borrower. + * A flash loan is then taken from the pool to facilitate the rollover and + * a callback is executed for further operations. + * + * @param _loanId Identifier of the existing loan to be rolled over. + * @param _flashLoanAmount Amount of flash loan to be borrowed for the rollover. + * @param _borrowerAmount Additional amount that the borrower may want to add during rollover. + * @param _acceptCommitmentArgs Commitment arguments that might be necessary for internal operations. + * + */ + function rolloverLoanWithFlash( + address _lenderCommitmentForwarder, + uint256 _loanId, + uint256 _flashLoanAmount, + uint256 _borrowerAmount, //an additional amount borrower may have to add + uint256 _rewardAmount, + address _rewardRecipient, + AcceptCommitmentArgs calldata _acceptCommitmentArgs + ) external { + address borrower = TELLER_V2.getLoanBorrower(_loanId); + require(borrower == msg.sender, "CommitmentRolloverLoan: not borrower"); + + // Get lending token and balance before + address lendingToken = TELLER_V2.getLoanLendingToken(_loanId); + + require( _rewardAmount <= _flashLoanAmount/ 10, "Reward amount may only be up to 1/10 of flash loan amount" ); + + if (_borrowerAmount > 0) { + IERC20(lendingToken).transferFrom( + borrower, + address(this), + _borrowerAmount + ); + } + + // Call 'Flash' on the vault to borrow funds and call tellerV2FlashCallback + // This ultimately calls executeOperation + IPool(POOL()).flashLoanSimple( + address(this), + lendingToken, + _flashLoanAmount, + abi.encode( + RolloverCallbackArgs({ + lenderCommitmentForwarder :_lenderCommitmentForwarder, + loanId: _loanId, + borrower: borrower, + borrowerAmount: _borrowerAmount, + rewardRecipient: _rewardRecipient, + rewardAmount: _rewardAmount, + acceptCommitmentArgs: abi.encode(_acceptCommitmentArgs) + }) + ), + 0 //referral code + ); + } + + /** + * + * @notice Callback function that is triggered by Aave during the flash loan process. + * This function handles the logic to use the borrowed funds to rollover the loan, + * make necessary repayments, and manage the loan commitments. + * + * @dev The function ensures the initiator is this contract, decodes the data provided by + * the flash loan call, repays the original loan in full, accepts new loan commitments, + * approves the repayment for the flash loan and then handles any remaining funds. + * This function should only be called by the FlashLoanPool as ensured by the `onlyFlashLoanPool` modifier. + * + * @param _flashToken The token in which the flash loan is borrowed. + * @param _flashAmount The amount of tokens borrowed via the flash loan. + * @param _flashFees The fees associated with the flash loan to be repaid to Aave. + * @param _initiator The address initiating the flash loan (must be this contract). + * @param _data Encoded data containing necessary information for loan rollover. + * + * @return Returns true if the operation was successful. + */ + function executeOperation( + address _flashToken, + uint256 _flashAmount, + uint256 _flashFees, + address _initiator, + bytes calldata _data + ) external virtual onlyFlashLoanPool returns (bool) { + require( + _initiator == address(this), + "This contract must be the initiator" + ); + + RolloverCallbackArgs memory _rolloverArgs = abi.decode( + _data, + (RolloverCallbackArgs) + ); + + uint256 repaymentAmount = _repayLoanFull( + _rolloverArgs.loanId, + _flashToken, + _flashAmount + ); + + AcceptCommitmentArgs memory acceptCommitmentArgs = abi.decode( + _rolloverArgs.acceptCommitmentArgs, + (AcceptCommitmentArgs) + ); + + // Accept commitment and receive funds to this contract + + (uint256 newLoanId, uint256 acceptCommitmentAmount) = _acceptCommitment( + _rolloverArgs.lenderCommitmentForwarder, + _rolloverArgs.borrower, + _flashToken, + acceptCommitmentArgs + ); + + //approve the repayment for the flash loan + IERC20Upgradeable(_flashToken).approve( + address(POOL()), + _flashAmount + _flashFees + ); + + uint256 fundsRemaining = acceptCommitmentAmount + + _rolloverArgs.borrowerAmount - + repaymentAmount - + _flashFees; + + if (fundsRemaining > 0) { + + if (_rolloverArgs.rewardAmount > 0){ + + //make sure reward amount isnt TOO much here ? + + fundsRemaining -= _rolloverArgs.rewardAmount; + IERC20Upgradeable(_flashToken).transfer( + _rolloverArgs.rewardRecipient, + _rolloverArgs.rewardAmount + ); + + } + + IERC20Upgradeable(_flashToken).transfer( + _rolloverArgs.borrower, + fundsRemaining + ); + } + + emit RolloverLoanComplete( + _rolloverArgs.borrower, + _rolloverArgs.loanId, + newLoanId, + fundsRemaining + ); + + return true; + } + + /** + * + * + * @notice Internal function that repays a loan in full on behalf of this contract. + * + * @dev The function first calculates the funds held by the contract before repayment, then approves + * the repayment amount to the TellerV2 contract and finally repays the loan in full. + * + * @param _bidId Identifier of the loan to be repaid. + * @param _principalToken The token in which the loan was originated. + * @param _repayAmount The amount to be repaid. + * + * @return repayAmount_ The actual amount that was used for repayment. + */ + function _repayLoanFull( + uint256 _bidId, + address _principalToken, + uint256 _repayAmount + ) internal returns (uint256 repayAmount_) { + uint256 fundsBeforeRepayment = IERC20Upgradeable(_principalToken) + .balanceOf(address(this)); + + IERC20Upgradeable(_principalToken).approve( + address(TELLER_V2), + _repayAmount + ); + TELLER_V2.repayLoanFull(_bidId); + + uint256 fundsAfterRepayment = IERC20Upgradeable(_principalToken) + .balanceOf(address(this)); + + repayAmount_ = fundsBeforeRepayment - fundsAfterRepayment; + } + + /** + * + * + * @notice Accepts a loan commitment using either a Merkle proof or standard method. + * + * @dev The function first checks if a Merkle proof is provided, based on which it calls the relevant + * `acceptCommitment` function in the LenderCommitmentForwarder contract. + * + * @param borrower The address of the borrower for whom the commitment is being accepted. + * @param principalToken The token in which the loan is being accepted. + * @param _commitmentArgs The arguments necessary for accepting the commitment. + * + * @return bidId_ Identifier of the accepted loan. + * @return acceptCommitmentAmount_ The amount received from accepting the commitment. + */ + function _acceptCommitment( + address lenderCommitmentForwarder, + address borrower, + address principalToken, + AcceptCommitmentArgs memory _commitmentArgs + ) + internal + virtual + returns (uint256 bidId_, uint256 acceptCommitmentAmount_) + { + uint256 fundsBeforeAcceptCommitment = IERC20Upgradeable(principalToken) + .balanceOf(address(this)); + + + + if (_commitmentArgs.smartCommitmentAddress != address(0)) { + + bytes memory responseData = address(lenderCommitmentForwarder) + .functionCall( + abi.encodePacked( + abi.encodeWithSelector( + ISmartCommitmentForwarder + .acceptSmartCommitmentWithRecipient + .selector, + _commitmentArgs.smartCommitmentAddress, + _commitmentArgs.principalAmount, + _commitmentArgs.collateralAmount, + _commitmentArgs.collateralTokenId, + _commitmentArgs.collateralTokenAddress, + address(this), + _commitmentArgs.interestRate, + _commitmentArgs.loanDuration + ), + borrower //cant be msg.sender because of the flash flow + ) + ); + + (bidId_) = abi.decode(responseData, (uint256)); + + + }else { + + bool usingMerkleProof = _commitmentArgs.merkleProof.length > 0; + + if (usingMerkleProof) { + bytes memory responseData = address(lenderCommitmentForwarder) + .functionCall( + abi.encodePacked( + abi.encodeWithSelector( + ILenderCommitmentForwarder + .acceptCommitmentWithRecipientAndProof + .selector, + _commitmentArgs.commitmentId, + _commitmentArgs.principalAmount, + _commitmentArgs.collateralAmount, + _commitmentArgs.collateralTokenId, + _commitmentArgs.collateralTokenAddress, + address(this), + _commitmentArgs.interestRate, + _commitmentArgs.loanDuration, + _commitmentArgs.merkleProof + ), + borrower //cant be msg.sender because of the flash flow + ) + ); + + (bidId_) = abi.decode(responseData, (uint256)); + } else { + bytes memory responseData = address(lenderCommitmentForwarder) + .functionCall( + abi.encodePacked( + abi.encodeWithSelector( + ILenderCommitmentForwarder + .acceptCommitmentWithRecipient + .selector, + _commitmentArgs.commitmentId, + _commitmentArgs.principalAmount, + _commitmentArgs.collateralAmount, + _commitmentArgs.collateralTokenId, + _commitmentArgs.collateralTokenAddress, + address(this), + _commitmentArgs.interestRate, + _commitmentArgs.loanDuration + ), + borrower //cant be msg.sender because of the flash flow + ) + ); + + (bidId_) = abi.decode(responseData, (uint256)); + } + + } + + uint256 fundsAfterAcceptCommitment = IERC20Upgradeable(principalToken) + .balanceOf(address(this)); + acceptCommitmentAmount_ = + fundsAfterAcceptCommitment - + fundsBeforeAcceptCommitment; + } + + function ADDRESSES_PROVIDER() public view returns (IPoolAddressesProvider) { + return IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER); + } + + function POOL() public view returns (IPool) { + return IPool(ADDRESSES_PROVIDER().getPool()); + } + + /** + * @notice Calculates the amount for loan rollover, determining if the borrower owes or receives funds. + * @param _loanId The ID of the loan to calculate the rollover amount for. + * @param _commitmentArgs Arguments for the commitment. + * @param _timestamp The timestamp for when the calculation is executed. + + */ + function calculateRolloverAmount( + address _lenderCommitmentForwarder, + uint256 _loanId, + AcceptCommitmentArgs calldata _commitmentArgs, + uint256 _rewardAmount, + uint16 _flashloanPremiumPct, + uint256 _timestamp + ) external view returns (uint256 _flashAmount, int256 _borrowerAmount) { + Payment memory repayAmountOwed = TELLER_V2.calculateAmountOwed( + _loanId, + _timestamp + ); + + uint256 _marketId = _getMarketIdForCommitment(_lenderCommitmentForwarder, + _commitmentArgs.commitmentId + ); + uint16 marketFeePct = _getMarketFeePct(_marketId); + uint16 protocolFeePct = _getProtocolFeePct(); + + uint256 commitmentPrincipalRequested = _commitmentArgs.principalAmount; + uint256 amountToMarketplace = commitmentPrincipalRequested.percent( + marketFeePct + ); + uint256 amountToProtocol = commitmentPrincipalRequested.percent( + protocolFeePct + ); + + uint256 commitmentPrincipalReceived = commitmentPrincipalRequested - + amountToMarketplace - + amountToProtocol; + + // by default, we will flash exactly what we need to do relayLoanFull + uint256 repayFullAmount = repayAmountOwed.principal + + repayAmountOwed.interest; + + _flashAmount = repayFullAmount; + uint256 _flashLoanFee = _flashAmount.percent(_flashloanPremiumPct); + + _borrowerAmount = + int256(commitmentPrincipalReceived) - + int256(repayFullAmount) - + int256(_flashLoanFee) - + int256(_rewardAmount); + } + + /** + * @notice Retrieves the market ID associated with a given commitment. + * @param _commitmentId The ID of the commitment for which to fetch the market ID. + * @return The ID of the market associated with the provided commitment. + */ + function _getMarketIdForCommitment(address _lenderCommitmentForwarder, uint256 _commitmentId) + internal + view + returns (uint256) + { + return ILenderCommitmentForwarder(_lenderCommitmentForwarder).getCommitmentMarketId(_commitmentId); + } + + /** + * @notice Fetches the marketplace fee percentage for a given market ID. + * @param _marketId The ID of the market for which to fetch the fee percentage. + * @return The marketplace fee percentage for the provided market ID. + */ + function _getMarketFeePct(uint256 _marketId) + internal + view + returns (uint16) + { + address _marketRegistryAddress = ITellerV2Storage(address(TELLER_V2)) + .marketRegistry(); + + return + IMarketRegistry(_marketRegistryAddress).getMarketplaceFee( + _marketId + ); + } + + /** + * @notice Fetches the protocol fee percentage from the Teller V2 protocol. + * @return The protocol fee percentage as defined in the Teller V2 protocol. + */ + function _getProtocolFeePct() internal view returns (uint16) { + return IProtocolFee(address(TELLER_V2)).protocolFee(); + } +} \ No newline at end of file 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/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol index c4ef59a7b..6aaff6327 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol @@ -33,14 +33,13 @@ import { ILoanRepaymentListener } from "../../../interfaces/ILoanRepaymentListen import { ILoanRepaymentCallbacks } from "../../../interfaces/ILoanRepaymentCallbacks.sol"; -import { IEscrowVault } from "../../../interfaces/IEscrowVault.sol"; import { ILenderCommitmentGroup } from "../../../interfaces/ILenderCommitmentGroup.sol"; import { Payment } from "../../../TellerV2Storage.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + /* @@ -71,14 +70,10 @@ contract LenderCommitmentGroup_Smart is uint256 public immutable STANDARD_EXPANSION_FACTOR = 1e18; - uint256 public immutable MIN_TWAP_INTERVAL = 3; - uint256 public immutable UNISWAP_EXPANSION_FACTOR = 2**96; uint256 public immutable EXCHANGE_RATE_EXPANSION_FACTOR = 1e36; - using SafeERC20 for IERC20; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address public immutable TELLER_V2; address public immutable SMART_COMMITMENT_FORWARDER; @@ -89,7 +84,6 @@ contract LenderCommitmentGroup_Smart is IERC20 public principalToken; IERC20 public collateralToken; - uint24 public uniswapPoolFee; uint256 marketId; @@ -113,13 +107,6 @@ contract LenderCommitmentGroup_Smart is uint16 public interestRateUpperBound; - - - mapping(address => uint256) public poolSharesPreparedToWithdrawForLender; - mapping(address => uint256) public poolSharesPreparedTimestamp; - uint256 immutable public DEFAULT_WITHDRAWL_DELAY_TIME_SECONDS = 300; - - //mapping(address => uint256) public principalTokensCommittedByLender; mapping(uint256 => bool) public activeBids; @@ -127,75 +114,9 @@ contract LenderCommitmentGroup_Smart is // maybe it is possible to get rid of this storage slot and calculate it from totalPrincipalTokensRepaid, totalPrincipalTokensLended int256 tokenDifferenceFromLiquidations; - bool public firstDepositMade; - uint256 public withdrawlDelayTimeSeconds; - - event PoolInitialized( - address indexed principalTokenAddress, - address indexed collateralTokenAddress, - uint256 marketId, - uint32 maxLoanDuration, - uint16 interestRateLowerBound, - uint16 interestRateUpperBound, - uint16 liquidityThresholdPercent, - uint16 loanToValuePercent, - uint24 uniswapPoolFee, - uint32 twapInterval, - address poolSharesToken - ); - - event LenderAddedPrincipal( - address indexed lender, - uint256 amount, - uint256 sharesAmount, - address indexed sharesRecipient - ); - - event BorrowerAcceptedFunds( - address indexed borrower, - uint256 indexed bidId, - uint256 principalAmount, - uint256 collateralAmount, - uint32 loanDuration, - uint16 interestRate - ); - - event EarningsWithdrawn( - address indexed lender, - uint256 amountPoolSharesTokens, - uint256 principalTokensWithdrawn, - address indexed recipient - ); - - - event DefaultedLoanLiquidated( - uint256 indexed bidId, - address indexed liquidator, - uint256 amountDue, - int256 tokenAmountDifference - ); - - - event LoanRepaid( - uint256 indexed bidId, - address indexed repayer, - uint256 principalAmount, - uint256 interestAmount, - uint256 totalPrincipalRepaid, - uint256 totalInterestCollected - ); - - event PoolSharesPrepared( - address lender, - uint256 sharesAmount, - uint256 preparedAt - - ); - - modifier onlySmartCommitmentForwarder() { require( msg.sender == address(SMART_COMMITMENT_FORWARDER), @@ -212,28 +133,12 @@ contract LenderCommitmentGroup_Smart is _; } - - modifier onlyProtocolOwner() { - require( - msg.sender == Ownable(address(TELLER_V2)).owner(), - "Can only be called by TellerV2" - ); - _; - } - modifier bidIsActiveForGroup(uint256 _bidId) { require(activeBids[_bidId] == true, "Bid is not active for group"); _; } - modifier whenForwarderNotPaused() { - require( PausableUpgradeable(address(SMART_COMMITMENT_FORWARDER)).paused() == false , "Protocol is paused"); - _; - } - - - /// @custom:oz-upgrades-unsafe-allow constructor constructor( address _tellerV2, @@ -243,7 +148,6 @@ contract LenderCommitmentGroup_Smart is TELLER_V2 = _tellerV2; SMART_COMMITMENT_FORWARDER = _smartCommitmentForwarder; UNISWAP_V3_FACTORY = _uniswapV3Factory; - } /* @@ -263,13 +167,13 @@ contract LenderCommitmentGroup_Smart is uint24 _uniswapPoolFee, uint32 _twapInterval ) external initializer returns (address poolSharesToken_) { - - __Ownable_init(); + // require(!_initialized,"already initialized"); + // _initialized = true; + __Pausable_init(); principalToken = IERC20(_principalTokenAddress); collateralToken = IERC20(_collateralTokenAddress); - uniswapPoolFee = _uniswapPoolFee; UNISWAP_V3_POOL = IUniswapV3Factory(UNISWAP_V3_FACTORY).getPool( _principalTokenAddress, @@ -277,13 +181,10 @@ contract LenderCommitmentGroup_Smart is _uniswapPoolFee ); - require(_twapInterval >= MIN_TWAP_INTERVAL, "Invalid TWAP Interval"); require(UNISWAP_V3_POOL != address(0), "Invalid uniswap pool address"); marketId = _marketId; - withdrawlDelayTimeSeconds = DEFAULT_WITHDRAWL_DELAY_TIME_SECONDS; - //in order for this to succeed, first, that SmartCommitmentForwarder needs to be THE trusted forwarder for the market @@ -309,29 +210,6 @@ contract LenderCommitmentGroup_Smart is poolSharesToken_ = _deployPoolSharesToken(); - - - emit PoolInitialized( - _principalTokenAddress, - _collateralTokenAddress, - _marketId, - _maxLoanDuration, - _interestRateLowerBound, - _interestRateUpperBound, - _liquidityThresholdPercent, - _collateralRatio, - _uniswapPoolFee, - _twapInterval, - poolSharesToken_ - ); - } - - - function setWithdrawlDelayTime(uint256 _seconds) - external - onlyProtocolOwner { - - withdrawlDelayTimeSeconds = _seconds; } function _deployPoolSharesToken() @@ -344,16 +222,37 @@ contract LenderCommitmentGroup_Smart is address(poolSharesToken) == address(0), "Pool shares already deployed" ); - + + + (string memory name, string memory symbol ) = _generateTokenNameAndSymbol( + address(principalToken), + address(collateralToken) + ); + poolSharesToken = new LenderCommitmentGroupShares( - "LenderGroupShares", - "SHR", + name, + symbol, 18 ); return address(poolSharesToken); - } + } + function _generateTokenNameAndSymbol(address principalToken, address collateralToken) + internal view + returns (string memory name, string memory symbol) { + // Read the symbol of the principal token + string memory principalSymbol = ERC20(principalToken).symbol(); + + // Read the symbol of the collateral token + string memory collateralSymbol = ERC20(collateralToken).symbol(); + + // Combine the symbols to create the name + name = string(abi.encodePacked("GroupShares-", principalSymbol, "-", collateralSymbol)); + + // Combine the symbols to create the symbol + symbol = string(abi.encodePacked("SHR-", principalSymbol, "-", collateralSymbol)); + } /** * @notice This determines the number of shares you get for depositing principal tokens and the number of principal tokens you receive for burning shares @@ -370,9 +269,9 @@ contract LenderCommitmentGroup_Smart is } rate_ = - MathUpgradeable.mulDiv(poolTotalEstimatedValue , - EXCHANGE_RATE_EXPANSION_FACTOR , - poolSharesToken.totalSupply() ); + (poolTotalEstimatedValue * + EXCHANGE_RATE_EXPANSION_FACTOR) / + poolSharesToken.totalSupply(); } function sharesExchangeRateInverse() @@ -407,59 +306,19 @@ contract LenderCommitmentGroup_Smart is */ function addPrincipalToCommitmentGroup( uint256 _amount, - address _sharesRecipient, - uint256 _minSharesAmountOut - ) external whenForwarderNotPaused returns (uint256 sharesAmount_) { + address _sharesRecipient + ) external returns (uint256 sharesAmount_) { //transfers the primary principal token from msg.sender into this contract escrow - - - - - uint256 principalTokenBalanceBefore = principalToken.balanceOf(address(this)); - - principalToken.safeTransferFrom(msg.sender, address(this), _amount); - - uint256 principalTokenBalanceAfter = principalToken.balanceOf(address(this)); - - require( principalTokenBalanceAfter == principalTokenBalanceBefore + _amount, "Token balance was not added properly" ); - - + principalToken.transferFrom(msg.sender, address(this), _amount); sharesAmount_ = _valueOfUnderlying(_amount, sharesExchangeRate()); - - totalPrincipalTokensCommitted += _amount; - + //principalTokensCommittedByLender[msg.sender] += _amount; //mint shares equal to _amount and give them to the shares recipient !!! poolSharesToken.mint(_sharesRecipient, sharesAmount_); - - - - // prepare current balance - uint256 sharesBalance = poolSharesToken.balanceOf(address(_sharesRecipient)); - _prepareSharesForWithdraw(_sharesRecipient,sharesBalance); - - - emit LenderAddedPrincipal( - - msg.sender, - _amount, - sharesAmount_, - _sharesRecipient - - ); - - require( sharesAmount_ >= _minSharesAmountOut, "Invalid: Min Shares AmountOut" ); - - if(!firstDepositMade){ - require(msg.sender == owner(), "Owner must initialize the pool with a deposit first."); - require( sharesAmount_>= 1e6, "Initial shares amount must be atleast 1e6" ); - - firstDepositMade = true; - } } function _valueOfUnderlying(uint256 amount, uint256 rate) @@ -471,7 +330,7 @@ contract LenderCommitmentGroup_Smart is return 0; } - value_ = MathUpgradeable.mulDiv(amount , EXCHANGE_RATE_EXPANSION_FACTOR , rate ) ; + value_ = (amount * EXCHANGE_RATE_EXPANSION_FACTOR) / rate; } function acceptFundsForAcceptBid( @@ -483,14 +342,14 @@ contract LenderCommitmentGroup_Smart is uint256 _collateralTokenId, uint32 _loanDuration, uint16 _interestRate - ) external onlySmartCommitmentForwarder whenForwarderNotPaused { + ) external onlySmartCommitmentForwarder whenNotPaused { require( _collateralTokenAddress == address(collateralToken), "Mismatching collateral token" ); //the interest rate must be at least as high has the commitment demands. The borrower can use a higher interest rate although that would not be beneficial to the borrower. - require(_interestRate >= getMinInterestRate(_principalAmount), "Invalid interest rate"); + require(_interestRate >= getMinInterestRate(), "Invalid interest rate"); //the loan duration must be less than the commitment max loan duration. The lender who made the commitment expects the money to be returned before this window. require(_loanDuration <= maxLoanDuration, "Invalid loan max duration"); @@ -499,13 +358,14 @@ contract LenderCommitmentGroup_Smart is "Invalid loan max principal" ); - + + //this is expanded by 10**18 uint256 requiredCollateral = getCollateralRequiredForPrincipalAmount( _principalAmount ); - require( - _collateralAmount >= + require( + (_collateralAmount * STANDARD_EXPANSION_FACTOR) >= requiredCollateral, "Insufficient Borrower Collateral" ); @@ -518,16 +378,7 @@ contract LenderCommitmentGroup_Smart is totalPrincipalTokensLended += _principalAmount; activeBids[_bidId] = true; //bool for now - - - emit BorrowerAcceptedFunds( - _borrower, - _bidId, - _principalAmount, - _collateralAmount, - _loanDuration, - _interestRate - ); + //emit event } function _acceptBidWithRepaymentListener(uint256 _bidId) internal { @@ -537,79 +388,28 @@ contract LenderCommitmentGroup_Smart is _bidId, address(this) ); - - } - function prepareSharesForWithdraw( - uint256 _amountPoolSharesTokens - ) external whenForwarderNotPaused returns (bool) { - return _prepareSharesForWithdraw(msg.sender,_amountPoolSharesTokens); - } - - function _prepareSharesForWithdraw( - address _recipient, - uint256 _amountPoolSharesTokens - ) internal returns (bool) { - - require( poolSharesToken.balanceOf(_recipient) >= _amountPoolSharesTokens ); - - poolSharesPreparedToWithdrawForLender[_recipient] = _amountPoolSharesTokens; - poolSharesPreparedTimestamp[_recipient] = block.timestamp; - - - - emit PoolSharesPrepared( - - _recipient, - _amountPoolSharesTokens, - block.timestamp - - ); - - - return true; - } - - /* */ function burnSharesToWithdrawEarnings( uint256 _amountPoolSharesTokens, - address _recipient, - uint256 _minAmountOut - ) external whenForwarderNotPaused returns (uint256) { + address _recipient + ) external returns (uint256) { - require(poolSharesPreparedToWithdrawForLender[msg.sender] >= _amountPoolSharesTokens,"Shares not prepared for withdraw"); - require(poolSharesPreparedTimestamp[msg.sender] <= block.timestamp - withdrawlDelayTimeSeconds,"Shares not prepared for withdraw"); + - - poolSharesPreparedToWithdrawForLender[msg.sender] = 0; - poolSharesPreparedTimestamp[msg.sender] = block.timestamp; - - - //this should compute BEFORE shares burn + poolSharesToken.burn(msg.sender, _amountPoolSharesTokens); + uint256 principalTokenValueToWithdraw = _valueOfUnderlying( _amountPoolSharesTokens, sharesExchangeRateInverse() ); - poolSharesToken.burn(msg.sender, _amountPoolSharesTokens); - totalPrincipalTokensWithdrawn += principalTokenValueToWithdraw; - principalToken.safeTransfer(_recipient, principalTokenValueToWithdraw); - - - emit EarningsWithdrawn( - msg.sender, - _amountPoolSharesTokens, - principalTokenValueToWithdraw, - _recipient - ); - - require( principalTokenValueToWithdraw >= _minAmountOut ,"Invalid: Min Amount Out"); + principalToken.transfer(_recipient, principalTokenValueToWithdraw); return principalTokenValueToWithdraw; } @@ -622,12 +422,8 @@ contract LenderCommitmentGroup_Smart is function liquidateDefaultedLoanWithIncentive( uint256 _bidId, int256 _tokenAmountDifference - ) public whenForwarderNotPaused bidIsActiveForGroup(_bidId) { - - //use original principal amount as amountDue - - uint256 amountDue = _getAmountOwedForBid(_bidId); - + ) public bidIsActiveForGroup(_bidId) { + uint256 amountDue = getAmountOwedForBid(_bidId, false); uint256 loanDefaultedTimeStamp = ITellerV2(TELLER_V2) .getLoanDefaultTimestamp(_bidId); @@ -642,12 +438,12 @@ contract LenderCommitmentGroup_Smart is "Insufficient tokenAmountDifference" ); - if (minAmountDifference > 0) { + if (_tokenAmountDifference > 0) { //this is used when the collateral value is higher than the principal (rare) //the loan will be completely made whole and our contract gets extra funds too - uint256 tokensToTakeFromSender = abs(minAmountDifference); + uint256 tokensToTakeFromSender = abs(_tokenAmountDifference); - IERC20(principalToken).safeTransferFrom( + IERC20(principalToken).transferFrom( msg.sender, address(this), amountDue + tokensToTakeFromSender @@ -655,12 +451,12 @@ contract LenderCommitmentGroup_Smart is tokenDifferenceFromLiquidations += int256(tokensToTakeFromSender); - + totalPrincipalTokensRepaid += amountDue; } else { - uint256 tokensToGiveToSender = abs(minAmountDifference); + uint256 tokensToGiveToSender = abs(_tokenAmountDifference); - IERC20(principalToken).safeTransferFrom( + IERC20(principalToken).transferFrom( msg.sender, address(this), amountDue - tokensToGiveToSender @@ -668,35 +464,26 @@ contract LenderCommitmentGroup_Smart is tokenDifferenceFromLiquidations -= int256(tokensToGiveToSender); - + totalPrincipalTokensRepaid += amountDue; } //this will give collateral to the caller ITellerV2(TELLER_V2).lenderCloseLoanWithRecipient(_bidId, msg.sender); - - - emit DefaultedLoanLiquidated( - _bidId, - msg.sender, - amountDue, - _tokenAmountDifference - ); } - - - function _getAmountOwedForBid(uint256 _bidId ) - internal + function getAmountOwedForBid(uint256 _bidId, bool _includeInterest) + public view virtual - returns (uint256 amountDue) + returns (uint256 amountOwed_) { - (,,,, amountDue, , , ) - = ITellerV2(TELLER_V2).getLoanSummary(_bidId); + Payment memory amountOwedPayment = ITellerV2(TELLER_V2) + .calculateAmountOwed(_bidId, block.timestamp); - + amountOwed_ = _includeInterest + ? amountOwedPayment.principal + amountOwedPayment.interest + : amountOwedPayment.principal; } - /* This function will calculate the incentive amount (using a uniswap bonus plus a timer) @@ -769,9 +556,8 @@ contract LenderCommitmentGroup_Smart is returns (uint256 price_) { - - - uint256 priceX96 = FullMath.mulDiv(uint256(_sqrtPriceX96), uint256(_sqrtPriceX96), (2**96) ); + uint256 priceX96 = (uint256(_sqrtPriceX96) * uint256(_sqrtPriceX96)) / + (2**96); // sqrtPrice is in X96 format so we scale it down to get the price // Also note that this price is a relative price between the two tokens in the pool @@ -792,22 +578,19 @@ contract LenderCommitmentGroup_Smart is .slot0(); } else { uint32[] memory secondsAgos = new uint32[](2); - secondsAgos[0] = twapInterval+1; // from (before) - secondsAgos[1] = 1; // to (now) + secondsAgos[0] = twapInterval; // from (before) + secondsAgos[1] = 0; // to (now) (int56[] memory tickCumulatives, ) = IUniswapV3Pool(UNISWAP_V3_POOL) .observe(secondsAgos); - - - int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0]; - int24 arithmeticMeanTick = int24(tickCumulativesDelta / int32(twapInterval)); - //// Always round to negative infinity - if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int32(twapInterval) != 0)) arithmeticMeanTick--; - - sqrtPriceX96 = TickMath.getSqrtRatioAtTick(arithmeticMeanTick); - - + // tick(imprecise as it's an integer) to price + sqrtPriceX96 = TickMath.getSqrtRatioAtTick( + int24( + (tickCumulatives[1] - tickCumulatives[0]) / + int32(twapInterval) + ) + ); } } @@ -848,15 +631,15 @@ contract LenderCommitmentGroup_Smart is Dev Note: pairPriceWithTwap and pairPriceImmediate are expanded by UNISWAP_EXPANSION_FACTOR */ - /*function _getCollateralTokensAmountEquivalentToPrincipalTokens( + function _getCollateralTokensAmountEquivalentToPrincipalTokens( uint256 principalTokenAmountValue, uint256 pairPriceWithTwap, uint256 pairPriceImmediate, bool principalTokenIsToken0 ) internal pure returns (uint256 collateralTokensAmountToMatchValue) { if (principalTokenIsToken0) { - - uint256 worstCasePairPrice = Math.max( + //token 1 to token 0 ? + uint256 worstCasePairPrice = Math.min( pairPriceWithTwap, pairPriceImmediate ); @@ -866,8 +649,8 @@ contract LenderCommitmentGroup_Smart is worstCasePairPrice //if this is lower, collateral tokens amt will be higher ); } else { - - uint256 worstCasePairPrice = Math.min( + //token 0 to token 1 ? + uint256 worstCasePairPrice = Math.max( pairPriceWithTwap, pairPriceImmediate ); @@ -877,27 +660,6 @@ contract LenderCommitmentGroup_Smart is worstCasePairPrice //if this is lower, collateral tokens amt will be higher ); } - }*/ - - function _getCollateralTokensAmountEquivalentToPrincipalTokens( - uint256 principalTokenAmountValue, - uint256 pairPrice, - bool principalTokenIsToken0 - ) internal pure returns (uint256 collateralTokensAmountToMatchValue) { - if (principalTokenIsToken0) { - - - collateralTokensAmountToMatchValue = token1ToToken0( - principalTokenAmountValue, - pairPrice //if this is lower, collateral tokens amt will be higher - ); - } else { - - collateralTokensAmountToMatchValue = token0ToToken1( - principalTokenAmountValue, - pairPrice //if this is lower, collateral tokens amt will be higher - ); - } } //note: the price is still expanded by UNISWAP_EXPANSION_FACTOR @@ -940,35 +702,12 @@ contract LenderCommitmentGroup_Smart is address repayer, uint256 principalAmount, uint256 interestAmount - ) external onlyTellerV2 whenForwarderNotPaused { + ) external onlyTellerV2 { //can use principal amt to increment amt paid back!! nice for math . totalPrincipalTokensRepaid += principalAmount; totalInterestCollected += interestAmount; - - emit LoanRepaid( - _bidId, - repayer, - principalAmount, - interestAmount, - totalPrincipalTokensRepaid, - totalInterestCollected - ); } - - /* - If principaltokens get stuck in the escrow vault for any reason, anyone may - call this function to move them from that vault in to this contract - */ - function withdrawFromEscrowVault ( uint256 _amount ) public whenForwarderNotPaused { - - - address _escrowVault = ITellerV2(TELLER_V2).getEscrowVault(); - - IEscrowVault(_escrowVault).withdraw(address(principalToken), _amount ); - - } - function getTotalPrincipalTokensOutstandingInActiveLoans() public @@ -978,9 +717,6 @@ contract LenderCommitmentGroup_Smart is return totalPrincipalTokensLended - totalPrincipalTokensRepaid; } - - - function getCollateralTokenAddress() external view returns (address) { return address(collateralToken); } @@ -1017,31 +753,23 @@ contract LenderCommitmentGroup_Smart is return maxLoanDuration; } - - function getPoolUtilizationRatio(uint256 activeLoansAmountDelta ) public view returns (uint16) { + //this is always between 0 and 10000 + function getPoolUtilizationRatio() public view returns (uint16) { if (getPoolTotalEstimatedValue() == 0) { return 0; } - return uint16( Math.min( - MathUpgradeable.mulDiv( - (getTotalPrincipalTokensOutstandingInActiveLoans() + activeLoansAmountDelta), - 10000 , - getPoolTotalEstimatedValue() ) , - 10000 )); + return uint16( Math.min( + getTotalPrincipalTokensOutstandingInActiveLoans() * 10000 / + getPoolTotalEstimatedValue() , 10000 )); + } + + function getMinInterestRate() public view returns (uint16) { + return interestRateLowerBound + uint16( uint256(interestRateUpperBound-interestRateLowerBound).percent(getPoolUtilizationRatio()) ); } - function getMinInterestRate(uint256 amountDelta) public view returns (uint16) { - return interestRateLowerBound + - uint16( uint256(interestRateUpperBound-interestRateLowerBound) - .percent(getPoolUtilizationRatio(amountDelta ) - - ) ); - } - - function getPrincipalTokenAddress() external view returns (address) { return address(principalToken); } @@ -1072,4 +800,4 @@ contract LenderCommitmentGroup_Smart is function unpauseBorrowing() public virtual onlyOwner whenPaused { _unpause(); } -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/MarketRegistry.sol b/packages/contracts/contracts/MarketRegistry.sol index 71f19122d..bb5993894 100644 --- a/packages/contracts/contracts/MarketRegistry.sol +++ b/packages/contracts/contracts/MarketRegistry.sol @@ -27,7 +27,7 @@ contract MarketRegistry is /** Constant Variables **/ uint256 public constant CURRENT_CODE_VERSION = 8; - uint256 public constant MAX_MARKET_FEE_PERCENT = 1000; + /* Storage Variables */ struct Marketplace { @@ -637,11 +637,7 @@ contract MarketRegistry is public ownsMarket(_marketId) { - require( - _newPercent >= 0 && _newPercent <= MAX_MARKET_FEE_PERCENT, - "invalid fee percent" - ); - + require(_newPercent >= 0 && _newPercent <= 10000, "invalid percent"); if (_newPercent != markets[_marketId].marketplaceFeePercent) { markets[_marketId].marketplaceFeePercent = _newPercent; emit SetMarketFee(_marketId, _newPercent); diff --git a/packages/contracts/contracts/TellerV2.sol b/packages/contracts/contracts/TellerV2.sol index d219a2396..736cf0dfa 100644 --- a/packages/contracts/contracts/TellerV2.sol +++ b/packages/contracts/contracts/TellerV2.sol @@ -164,24 +164,6 @@ contract TellerV2 is _; } - - modifier onlyPauser() { - - require( pauserRoleBearer[_msgSender()] || owner() == _msgSender(), "Requires role: Pauser"); - - - _; - } - - - modifier whenLiquidationsNotPaused() { - require(!liquidationsPaused, "Liquidations are paused"); - - _; - } - - - /** Constant Variables **/ uint8 public constant CURRENT_CODE_VERSION = 10; @@ -265,7 +247,29 @@ contract TellerV2 is escrowVault = IEscrowVault(_escrowVault); } - + /** + * @notice Gets the metadataURI for a bidId. + * @param _bidId The id of the bid to return the metadataURI for + * @return metadataURI_ The metadataURI for the bid, as a string. + */ + function getMetadataURI(uint256 _bidId) + public + view + returns (string memory metadataURI_) + { + // Check uri mapping first + metadataURI_ = uris[_bidId]; + // If the URI is not present in the mapping + if ( + keccak256(abi.encodePacked(metadataURI_)) == + 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 // hardcoded constant of keccak256('') + ) { + // Return deprecated bytes32 uri as a string + uint256 convertedURI = uint256(bids[_bidId]._metadataURI); + metadataURI_ = StringsUpgradeable.toHexString(convertedURI, 32); + } + } + /** * @notice Function for a borrower to create a bid for a loan without Collateral. * @param _lendingToken The lending token asset requested to be borrowed. @@ -704,57 +708,25 @@ contract TellerV2 is } /** - * @notice Lets a pauser of the protocol implement an emergency stop mechanism. + * @notice Lets the DAO/owner of the protocol implement an emergency stop mechanism. */ - function pauseProtocol() public virtual onlyPauser whenNotPaused { + function pauseProtocol() public virtual onlyOwner whenNotPaused { _pause(); } /** - * @notice Lets a pauser of the protocol undo a previously implemented emergency stop. + * @notice Lets the DAO/owner of the protocol undo a previously implemented emergency stop. */ - function unpauseProtocol() public virtual onlyPauser whenPaused { + function unpauseProtocol() public virtual onlyOwner whenPaused { _unpause(); } - - /** - * @notice Lets a pauser of the protocol implement an emergency stop mechanism. - */ - function pauseLiquidations() public virtual onlyPauser { - liquidationsPaused = true; - } - - /** - * @notice Lets a pauser of the protocol undo a previously implemented emergency stop. - */ - function unpauseLiquidations() public virtual onlyPauser { - liquidationsPaused = false; - } - - - - function addPauser(address _pauser) public virtual onlyOwner { - pauserRoleBearer[_pauser] = true; - } - - - function removePauser(address _pauser) public virtual onlyOwner { - pauserRoleBearer[_pauser] = false; - } - - - function isPauser(address _account) public view returns(bool){ - return pauserRoleBearer[_account] ; - } - - function lenderCloseLoan(uint256 _bidId) - external whenNotPaused whenLiquidationsNotPaused + external acceptedLoan(_bidId, "lenderClaimCollateral") { Bid storage bid = bids[_bidId]; - address _collateralRecipient = getLoanLender(_bidId); + address _collateralRecipient = bid.lender; _lenderCloseLoanWithRecipient(_bidId, _collateralRecipient); } @@ -766,7 +738,7 @@ contract TellerV2 is function lenderCloseLoanWithRecipient( uint256 _bidId, address _collateralRecipient - ) external whenNotPaused whenLiquidationsNotPaused { + ) external { _lenderCloseLoanWithRecipient(_bidId, _collateralRecipient); } @@ -780,10 +752,23 @@ contract TellerV2 is bid.state = BidState.CLOSED; address sender = _msgSenderForMarket(bid.marketplaceId); - require(sender == getLoanLender(_bidId), "only lender can close loan"); + require(sender == bid.lender, "only lender can close loan"); + + /* + - - collateralManager.lenderClaimCollateralWithRecipient(_bidId, _collateralRecipient); + address collateralManagerForBid = address(_getCollateralManagerForBid(_bidId)); + + if( collateralManagerForBid == address(collateralManagerV2) ){ + ICollateralManagerV2(collateralManagerForBid).lenderClaimCollateral(_bidId,_collateralRecipient); + }else{ + require( _collateralRecipient == address(bid.lender)); + ICollateralManager(collateralManagerForBid).lenderClaimCollateral(_bidId ); + } + + */ + + collateralManager.lenderClaimCollateral(_bidId); emit LoanClosed(_bidId); } @@ -793,7 +778,7 @@ contract TellerV2 is * @param _bidId The id of the loan to make the payment towards. */ function liquidateLoanFull(uint256 _bidId) - external whenNotPaused whenLiquidationsNotPaused + external acceptedLoan(_bidId, "liquidateLoan") { Bid storage bid = bids[_bidId]; @@ -805,7 +790,7 @@ contract TellerV2 is } function liquidateLoanFullWithRecipient(uint256 _bidId, address _recipient) - external whenNotPaused whenLiquidationsNotPaused + external acceptedLoan(_bidId, "liquidateLoan") { _liquidateLoanFull(_bidId, _recipient); @@ -889,8 +874,7 @@ contract TellerV2 is _borrowerBidsActive[bid.borrower].remove(_bidId); // If loan is is being liquidated and backed by collateral, withdraw and send to borrower - if (_shouldWithdrawCollateral) { - + if (_shouldWithdrawCollateral) { // _getCollateralManagerForBid(_bidId).withdraw(_bidId); collateralManager.withdraw(_bidId); } @@ -965,10 +949,9 @@ contract TellerV2 is address loanRepaymentListener = repaymentListenerForBid[_bidId]; if (loanRepaymentListener != address(0)) { - require(gasleft() >= 40000, "Insufficient gas"); //fixes the 63/64 remaining issue try ILoanRepaymentListener(loanRepaymentListener).repayLoanCallback{ - gas: 40000 + gas: 80000 }( //limit gas costs to prevent lender griefing repayments _bidId, _msgSenderForMarket(bid.marketplaceId), @@ -1121,10 +1104,6 @@ contract TellerV2 is dueDate + defaultDuration + _additionalDelay; } - function getEscrowVault() external view returns(address){ - return address(escrowVault); - } - function getBidState(uint256 _bidId) external view @@ -1265,25 +1244,19 @@ contract TellerV2 is return dueDate + defaultDuration; } - - function setRepaymentListenerForBid(uint256 _bidId, address _listener) external { - uint256 codeSize; - assembly { - codeSize := extcodesize(_listener) - } - require(codeSize > 0, "Listener must be a contract"); + + function setRepaymentListenerForBid(uint256 _bidId, address _listener) + external + { address sender = _msgSenderForMarket(bids[_bidId].marketplaceId); require( - sender == getLoanLender(_bidId), + sender == bids[_bidId].lender, "Only bid lender may set repayment listener" ); repaymentListenerForBid[_bidId] = _listener; - } - - - + } function getRepaymentListenerForBid(uint256 _bidId) external @@ -1360,4 +1333,4 @@ contract TellerV2 is { return ERC2771ContextUpgradeable._msgData(); } -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/TellerV2Context.sol b/packages/contracts/contracts/TellerV2Context.sol index 5ebb607d5..a9ba16421 100644 --- a/packages/contracts/contracts/TellerV2Context.sol +++ b/packages/contracts/contracts/TellerV2Context.sol @@ -161,4 +161,4 @@ abstract contract TellerV2Context is return _msgData(); } } -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/TellerV2MarketForwarder_G3.sol b/packages/contracts/contracts/TellerV2MarketForwarder_G3.sol index 2553b2f73..52d90e680 100644 --- a/packages/contracts/contracts/TellerV2MarketForwarder_G3.sol +++ b/packages/contracts/contracts/TellerV2MarketForwarder_G3.sol @@ -57,4 +57,4 @@ abstract contract TellerV2MarketForwarder_G3 is TellerV2MarketForwarder_G2 { //a gap is inherited from g2 so this is actually not necessary going forwards ---leaving it to maintain upgradeability uint256[50] private __gap; -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/TellerV2Storage.sol b/packages/contracts/contracts/TellerV2Storage.sol index bbbe88527..eb6aab3b7 100644 --- a/packages/contracts/contracts/TellerV2Storage.sol +++ b/packages/contracts/contracts/TellerV2Storage.sol @@ -162,9 +162,4 @@ abstract contract TellerV2Storage_G6 is TellerV2Storage_G5 { mapping(uint256 => address) public repaymentListenerForBid; } -abstract contract TellerV2Storage_G7 is TellerV2Storage_G6 { - mapping(address => bool) public pauserRoleBearer; - bool public liquidationsPaused; -} - -abstract contract TellerV2Storage is TellerV2Storage_G7 {} +abstract contract TellerV2Storage is TellerV2Storage_G6 {} \ No newline at end of file diff --git a/packages/contracts/contracts/escrow/CollateralEscrowV1.sol b/packages/contracts/contracts/escrow/CollateralEscrowV1.sol index 021fb2ee4..32df879de 100644 --- a/packages/contracts/contracts/escrow/CollateralEscrowV1.sol +++ b/packages/contracts/contracts/escrow/CollateralEscrowV1.sol @@ -10,12 +10,10 @@ import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../interfaces/escrow/ICollateralEscrowV1.sol"; contract CollateralEscrowV1 is OwnableUpgradeable, ICollateralEscrowV1 { - - uint256 public bidId; + uint256 public bidId; /* Mappings */ mapping(address => Collateral) public collateralBalances; // collateral address -> collateral @@ -104,13 +102,12 @@ contract CollateralEscrowV1 is OwnableUpgradeable, ICollateralEscrowV1 { emit CollateralWithdrawn(_collateralAddress, _amount, _recipient); } - function withdrawDustTokens( address tokenAddress, uint256 amount, address recipient ) external virtual onlyOwner { //the owner should be collateral manager - + require(tokenAddress != address(0), "Invalid token address"); Collateral storage collateral = collateralBalances[tokenAddress]; @@ -118,8 +115,8 @@ contract CollateralEscrowV1 is OwnableUpgradeable, ICollateralEscrowV1 { collateral._amount == 0, "Asset not allowed to be withdrawn as dust" ); - SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(tokenAddress),recipient, amount); - + + IERC20Upgradeable(tokenAddress).transfer(recipient, amount); } @@ -184,8 +181,7 @@ contract CollateralEscrowV1 is OwnableUpgradeable, ICollateralEscrowV1 { ) internal { // Withdraw ERC20 if (_collateral._collateralType == CollateralType.ERC20) { - - SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(_collateralAddress),_recipient, _amount); + IERC20Upgradeable(_collateralAddress).transfer(_recipient, _amount); } // Withdraw ERC721 else if (_collateral._collateralType == CollateralType.ERC721) { diff --git a/packages/contracts/contracts/interfaces/ICollateralManager.sol b/packages/contracts/contracts/interfaces/ICollateralManager.sol index fa047facd..7112e70d7 100644 --- a/packages/contracts/contracts/interfaces/ICollateralManager.sol +++ b/packages/contracts/contracts/interfaces/ICollateralManager.sol @@ -79,17 +79,6 @@ interface ICollateralManager { */ function lenderClaimCollateral(uint256 _bidId) external; - - - /** - * @notice Sends the deposited collateral to a lender of a bid. - * @notice Can only be called by the protocol. - * @param _bidId The id of the liquidated bid. - * @param _collateralRecipient the address that will receive the collateral - */ - function lenderClaimCollateralWithRecipient(uint256 _bidId, address _collateralRecipient) external; - - /** * @notice Sends the deposited collateral to a liquidator of a bid. * @notice Can only be called by the protocol. @@ -98,4 +87,4 @@ interface ICollateralManager { */ function liquidateCollateral(uint256 _bidId, address _liquidatorAddress) external; -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/interfaces/IFlashRolloverLoan.sol b/packages/contracts/contracts/interfaces/IFlashRolloverLoan.sol index 1ab552a03..73254cff0 100644 --- a/packages/contracts/contracts/interfaces/IFlashRolloverLoan.sol +++ b/packages/contracts/contracts/interfaces/IFlashRolloverLoan.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; interface IFlashRolloverLoan { - struct RolloverCallbackArgs { + struct RolloverCallbackArgs { uint256 loanId; address borrower; uint256 borrowerAmount; diff --git a/packages/contracts/contracts/interfaces/IFlashRolloverLoan_G4.sol b/packages/contracts/contracts/interfaces/IFlashRolloverLoan_G4.sol index 0a67bc4c5..781be5d06 100644 --- a/packages/contracts/contracts/interfaces/IFlashRolloverLoan_G4.sol +++ b/packages/contracts/contracts/interfaces/IFlashRolloverLoan_G4.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IFlashRolloverLoan_G4 { @@ -8,4 +9,4 @@ interface IFlashRolloverLoan_G4 { uint256 borrowerAmount; bytes acceptCommitmentArgs; } -} \ No newline at end of file +} diff --git a/packages/contracts/contracts/interfaces/IFlashRolloverLoan_G6.sol b/packages/contracts/contracts/interfaces/IFlashRolloverLoan_G6.sol new file mode 100644 index 000000000..c8863463d --- /dev/null +++ b/packages/contracts/contracts/interfaces/IFlashRolloverLoan_G6.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IFlashRolloverLoan_G6 { + struct RolloverCallbackArgs { + address lenderCommitmentForwarder; + uint256 loanId; + address borrower; + uint256 borrowerAmount; + address rewardRecipient; + uint256 rewardAmount; + bytes acceptCommitmentArgs; + } +} diff --git a/packages/contracts/contracts/interfaces/IHasProtocolPausingManager.sol b/packages/contracts/contracts/interfaces/IHasProtocolPausingManager.sol new file mode 100644 index 000000000..88d917511 --- /dev/null +++ b/packages/contracts/contracts/interfaces/IHasProtocolPausingManager.sol @@ -0,0 +1,19 @@ + + + + + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + +//records the unpause timestamp s +interface IHasProtocolPausingManager { + + + function getProtocolPausingManager() external view returns (address); + + // function isPauser(address _address) external view returns (bool); + + +} diff --git a/packages/contracts/contracts/interfaces/ILenderCommitmentForwarder.sol b/packages/contracts/contracts/interfaces/ILenderCommitmentForwarder.sol index 4b83c52c4..dd2aa4585 100644 --- a/packages/contracts/contracts/interfaces/ILenderCommitmentForwarder.sol +++ b/packages/contracts/contracts/interfaces/ILenderCommitmentForwarder.sol @@ -77,6 +77,7 @@ interface ILenderCommitmentForwarder { uint16 _interestRate, uint32 _loanDuration ) external returns (uint256 bidId_); + function acceptCommitmentWithRecipientAndProof( uint256 _commitmentId, diff --git a/packages/contracts/contracts/interfaces/ILenderCommitmentForwarder_U1.sol b/packages/contracts/contracts/interfaces/ILenderCommitmentForwarder_U1.sol index 5316c2a72..5aff0f346 100644 --- a/packages/contracts/contracts/interfaces/ILenderCommitmentForwarder_U1.sol +++ b/packages/contracts/contracts/interfaces/ILenderCommitmentForwarder_U1.sol @@ -99,4 +99,17 @@ interface ILenderCommitmentForwarder_U1 { uint32 _loanDuration, bytes32[] calldata _merkleProof ) external returns (uint256 bidId_); + + + function getCommitmentPrincipalTokenAddress(uint256 _commitmentId) + external + view + returns (address); + + function getCommitmentCollateralTokenAddress(uint256 _commitmentId) + external + view + returns (address); + + } diff --git a/packages/contracts/contracts/interfaces/ILenderCommitmentGroup.sol b/packages/contracts/contracts/interfaces/ILenderCommitmentGroup.sol index bc1e283a3..1c39edcb3 100644 --- a/packages/contracts/contracts/interfaces/ILenderCommitmentGroup.sol +++ b/packages/contracts/contracts/interfaces/ILenderCommitmentGroup.sol @@ -25,7 +25,6 @@ interface ILenderCommitmentGroup { function addPrincipalToCommitmentGroup( uint256 _amount, - address _sharesRecipient, - uint256 _minAmountOut + address _sharesRecipient ) external returns (uint256 sharesAmount_); -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/interfaces/IPausableTimestamp.sol b/packages/contracts/contracts/interfaces/IPausableTimestamp.sol new file mode 100644 index 000000000..e3b8f4d90 --- /dev/null +++ b/packages/contracts/contracts/interfaces/IPausableTimestamp.sol @@ -0,0 +1,22 @@ + + + + + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + +//records the unpause timestamp s +interface IPausableTimestamp { + + + function getLastUnpausedAt() + external view + returns (uint256) ; + + // function setLastUnpausedAt() internal; + + + +} diff --git a/packages/contracts/contracts/interfaces/IProtocolPausingManager.sol b/packages/contracts/contracts/interfaces/IProtocolPausingManager.sol new file mode 100644 index 000000000..187167b52 --- /dev/null +++ b/packages/contracts/contracts/interfaces/IProtocolPausingManager.sol @@ -0,0 +1,19 @@ + + + + + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + +//records the unpause timestamp s +interface IProtocolPausingManager { + + function isPauser(address _address) external view returns (bool); + function protocolPaused() external view returns (bool); + function liquidationsPaused() external view returns (bool); + + + +} diff --git a/packages/contracts/contracts/interfaces/ISmartCommitment.sol b/packages/contracts/contracts/interfaces/ISmartCommitment.sol index 73068dfdd..e13948984 100644 --- a/packages/contracts/contracts/interfaces/ISmartCommitment.sol +++ b/packages/contracts/contracts/interfaces/ISmartCommitment.sol @@ -26,7 +26,7 @@ interface ISmartCommitment { function getCollateralTokenId() external view returns (uint256); - function getMinInterestRate(uint256 _delta) external view returns (uint16); + function getMinInterestRate() external view returns (uint16); function getMaxLoanDuration() external view returns (uint32); @@ -51,4 +51,4 @@ interface ISmartCommitment { uint32 _loanDuration, uint16 _interestRate ) external; -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/interfaces/ISmartCommitmentForwarder.sol b/packages/contracts/contracts/interfaces/ISmartCommitmentForwarder.sol index 928a2867b..f95cb7aa1 100644 --- a/packages/contracts/contracts/interfaces/ISmartCommitmentForwarder.sol +++ b/packages/contracts/contracts/interfaces/ISmartCommitmentForwarder.sol @@ -15,4 +15,13 @@ interface ISmartCommitmentForwarder { uint32 _loanDuration ) external returns (uint256 bidId) ; + + function setLiquidationProtocolFeePercent(uint256 _percent) + external; + + function getLiquidationProtocolFeePercent() + external view returns (uint256) ; + + + } \ No newline at end of file diff --git a/packages/contracts/contracts/interfaces/ITellerV2.sol b/packages/contracts/contracts/interfaces/ITellerV2.sol index a930167a6..ecf09c040 100644 --- a/packages/contracts/contracts/interfaces/ITellerV2.sol +++ b/packages/contracts/contracts/interfaces/ITellerV2.sol @@ -168,10 +168,4 @@ interface ITellerV2 { external view returns (uint256); - - - function getEscrowVault() external view returns(address); - - - function isPauser(address _account) external view returns(bool); -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/interfaces/IUniswapPricingLibrary.sol b/packages/contracts/contracts/interfaces/IUniswapPricingLibrary.sol new file mode 100644 index 000000000..a9b953842 --- /dev/null +++ b/packages/contracts/contracts/interfaces/IUniswapPricingLibrary.sol @@ -0,0 +1,26 @@ +// SPDX-Licence-Identifier: MIT +pragma solidity >=0.8.0 <0.9.0; + +interface IUniswapPricingLibrary { + + + struct PoolRouteConfig { + address pool; + bool zeroForOne; + uint32 twapInterval; + uint256 token0Decimals; + uint256 token1Decimals; + } + + + + function getUniswapPriceRatioForPoolRoutes( + PoolRouteConfig[] memory poolRoutes + ) external view returns (uint256 priceRatio); + + + function getUniswapPriceRatioForPool( + PoolRouteConfig memory poolRoute + ) external view returns (uint256 priceRatio); + +} diff --git a/packages/contracts/contracts/interfaces/escrow/ICollateralEscrowV1.sol b/packages/contracts/contracts/interfaces/escrow/ICollateralEscrowV1.sol index 0969452a5..d8440f337 100644 --- a/packages/contracts/contracts/interfaces/escrow/ICollateralEscrowV1.sol +++ b/packages/contracts/contracts/interfaces/escrow/ICollateralEscrowV1.sol @@ -18,7 +18,7 @@ interface ICollateralEscrowV1 { /** * @notice Deposits a collateral asset into the escrow. * @param _collateralType The type of collateral asset to deposit (ERC721, ERC1155). - * @param _collateralAddress The address of the collateral token. + * @param _collateralAddress The address of the collateral token.i feel * @param _amount The amount to deposit. */ function depositAsset( @@ -46,7 +46,10 @@ interface ICollateralEscrowV1 { address _recipient ) external; + function getBid() external view returns (uint256); function initialize(uint256 _bidId) external; + + } diff --git a/packages/contracts/contracts/interfaces/oracleprotection/IHypernativeOracle.sol b/packages/contracts/contracts/interfaces/oracleprotection/IHypernativeOracle.sol new file mode 100644 index 000000000..120d14803 --- /dev/null +++ b/packages/contracts/contracts/interfaces/oracleprotection/IHypernativeOracle.sol @@ -0,0 +1,10 @@ +// SPDX-Licence-Identifier: MIT +pragma solidity >=0.8.0 <0.9.0; + +interface IHypernativeOracle { + function register(address account) external; + function registerStrict(address account) external; + function isBlacklistedAccount(address account) external view returns (bool); + function isBlacklistedContext(address sender, address origin) external view returns (bool); + function isTimeExceeded(address account) external view returns (bool); +} \ No newline at end of file diff --git a/packages/contracts/contracts/interfaces/oracleprotection/IOracleProtectionManager.sol b/packages/contracts/contracts/interfaces/oracleprotection/IOracleProtectionManager.sol new file mode 100644 index 000000000..4c9f0f7dc --- /dev/null +++ b/packages/contracts/contracts/interfaces/oracleprotection/IOracleProtectionManager.sol @@ -0,0 +1,9 @@ +// SPDX-Licence-Identifier: MIT +pragma solidity >=0.8.0 <0.9.0; + +interface IOracleProtectionManager { + function isOracleApproved(address _msgSender) external returns (bool) ; + + function isOracleApprovedAllowEOA(address _msgSender) external returns (bool); + +} \ No newline at end of file diff --git a/packages/contracts/contracts/libraries/UniswapPricingLibrary.sol b/packages/contracts/contracts/libraries/UniswapPricingLibrary.sol new file mode 100644 index 000000000..b996dfa54 --- /dev/null +++ b/packages/contracts/contracts/libraries/UniswapPricingLibrary.sol @@ -0,0 +1,134 @@ +pragma solidity >=0.8.0 <0.9.0; +// SPDX-License-Identifier: MIT + + + +import {IUniswapPricingLibrary} from "../interfaces/IUniswapPricingLibrary.sol"; + +import "@openzeppelin/contracts/utils/math/Math.sol"; + + +import "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol"; + +// Libraries +import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol"; + +import "../interfaces/uniswap/IUniswapV3Pool.sol"; + +import "../libraries/uniswap/TickMath.sol"; +import "../libraries/uniswap/FixedPoint96.sol"; +import "../libraries/uniswap/FullMath.sol"; + + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + + +/* + +Only do decimal expansion if it is an ERC20 not anything else !! + +*/ + +library UniswapPricingLibrary +{ + + uint256 constant STANDARD_EXPANSION_FACTOR = 1e18; + + function getUniswapPriceRatioForPoolRoutes( + IUniswapPricingLibrary.PoolRouteConfig[] memory poolRoutes + ) public view returns (uint256 priceRatio) { + require(poolRoutes.length <= 2, "invalid pool routes length"); + + if (poolRoutes.length == 2) { + uint256 pool0PriceRatio = getUniswapPriceRatioForPool( + poolRoutes[0] + ); + + uint256 pool1PriceRatio = getUniswapPriceRatioForPool( + poolRoutes[1] + ); + + return + FullMath.mulDiv( + pool0PriceRatio, + pool1PriceRatio, + STANDARD_EXPANSION_FACTOR + ); + } else if (poolRoutes.length == 1) { + return getUniswapPriceRatioForPool(poolRoutes[0]); + } + + //else return 0 + } + + /* + The resultant product is expanded by STANDARD_EXPANSION_FACTOR one time + */ + function getUniswapPriceRatioForPool( + IUniswapPricingLibrary.PoolRouteConfig memory _poolRouteConfig + ) public view returns (uint256 priceRatio) { + uint160 sqrtPriceX96 = getSqrtTwapX96( + _poolRouteConfig.pool, + _poolRouteConfig.twapInterval + ); + + //This is the token 1 per token 0 price + uint256 sqrtPrice = FullMath.mulDiv( + sqrtPriceX96, + STANDARD_EXPANSION_FACTOR, + 2**96 + ); + + uint256 sqrtPriceInverse = (STANDARD_EXPANSION_FACTOR * + STANDARD_EXPANSION_FACTOR) / sqrtPrice; + + uint256 price = _poolRouteConfig.zeroForOne + ? sqrtPrice * sqrtPrice + : sqrtPriceInverse * sqrtPriceInverse; + + return price / STANDARD_EXPANSION_FACTOR; + } + + + + function getSqrtTwapX96(address uniswapV3Pool, uint32 twapInterval) + internal + view + returns (uint160 sqrtPriceX96) + { + if (twapInterval == 0) { + // return the current price if twapInterval == 0 + (sqrtPriceX96, , , , , , ) = IUniswapV3Pool(uniswapV3Pool).slot0(); + } else { + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = twapInterval + 1; // from (before) + secondsAgos[1] = 1; // one block prior + + (int56[] memory tickCumulatives, ) = IUniswapV3Pool(uniswapV3Pool) + .observe(secondsAgos); + + // tick(imprecise as it's an integer) to price + sqrtPriceX96 = TickMath.getSqrtRatioAtTick( + int24( + (tickCumulatives[1] - tickCumulatives[0]) / + int32(twapInterval) + ) + ); + } + } + + function getPriceX96FromSqrtPriceX96(uint160 sqrtPriceX96) + internal + pure + returns (uint256 priceX96) + { + + + return FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, FixedPoint96.Q96); + } + +} \ No newline at end of file diff --git a/packages/contracts/contracts/libraries/V2Calculations.sol b/packages/contracts/contracts/libraries/V2Calculations.sol index f913616b4..8559d0373 100644 --- a/packages/contracts/contracts/libraries/V2Calculations.sol +++ b/packages/contracts/contracts/libraries/V2Calculations.sol @@ -48,7 +48,7 @@ library V2Calculations { PaymentCycleType _paymentCycleType, uint32 _paymentCycleDuration ) - internal + public view returns ( uint256 owedPrincipal_, @@ -74,7 +74,7 @@ library V2Calculations { PaymentCycleType _paymentCycleType, uint32 _paymentCycleDuration ) - internal + public view returns ( uint256 owedPrincipal_, @@ -93,7 +93,7 @@ library V2Calculations { ? 360 days : 365 days; - uint256 interestOwedInAYear = owedPrincipal_.percent(_bid.terms.APR); + uint256 interestOwedInAYear = owedPrincipal_.percent(_bid.terms.APR, 2); interest_ = (interestOwedInAYear * owedTime) / daysInYear; } @@ -154,7 +154,7 @@ library V2Calculations { uint32 _duration, uint32 _paymentCycle, uint16 _apr - ) internal returns (uint256) { + ) public view returns (uint256) { uint256 daysInYear = _cycleType == PaymentCycleType.Monthly ? 360 days : 365 days; diff --git a/packages/contracts/contracts/mock/TellerV2SolMock.sol b/packages/contracts/contracts/mock/TellerV2SolMock.sol index 341a5b71d..cdee01830 100644 --- a/packages/contracts/contracts/mock/TellerV2SolMock.sol +++ b/packages/contracts/contracts/mock/TellerV2SolMock.sol @@ -19,7 +19,6 @@ contract TellerV2SolMock is ITellerV2, IProtocolFee, TellerV2Storage , ILoanRepa uint256 public amountOwedMockPrincipal; uint256 public amountOwedMockInterest; address public approvedForwarder; - bool public isPausedMock; PaymentCycleType globalBidPaymentCycleType = PaymentCycleType.Seconds; @@ -41,20 +40,6 @@ contract TellerV2SolMock is ITellerV2, IProtocolFee, TellerV2Storage , ILoanRepa } - function getEscrowVault() external view returns(address){ - return address(0); - } - - - function paused() external view returns(bool){ - return isPausedMock; - } - - - function isPauser(address _account) public view returns(bool){ - return false; //for now - } - function approveMarketForwarder(uint256 _marketId, address _forwarder) external { @@ -387,4 +372,4 @@ contract TellerV2SolMock is ITellerV2, IProtocolFee, TellerV2Storage , ILoanRepa return globalBidPaymentCycleDuration; } -} +} \ No newline at end of file diff --git a/packages/contracts/contracts/mock/WethMock.sol b/packages/contracts/contracts/mock/WethMock.sol index c1042ba3e..57eb817ea 100644 --- a/packages/contracts/contracts/mock/WethMock.sol +++ b/packages/contracts/contracts/mock/WethMock.sol @@ -81,4 +81,7 @@ contract WethMock { return true; } + + + } diff --git a/packages/contracts/contracts/openzeppelin/ERC1967/ERC1967Proxy.sol b/packages/contracts/contracts/openzeppelin/ERC1967/ERC1967Proxy.sol new file mode 100644 index 000000000..dd9852645 --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/ERC1967/ERC1967Proxy.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Proxy.sol) +pragma solidity ^0.8.0; + + +import {Proxy} from "../Proxy.sol"; +import {ERC1967Utils} from "./ERC1967Utils.sol"; + +/** + * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an + * implementation address that can be changed. This address is stored in storage in the location specified by + * https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the + * implementation behind the proxy. + */ +contract ERC1967Proxy is Proxy { + /** + * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`. + * + * If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an + * encoded function call, and allows initializing the storage of the proxy like a Solidity constructor. + * + * Requirements: + * + * - If `data` is empty, `msg.value` must be zero. + */ + constructor(address implementation, bytes memory _data) payable { + ERC1967Utils.upgradeToAndCall(implementation, _data); + } + + /** + * @dev Returns the current implementation address. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using + * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` + */ + function _implementation() internal view virtual override returns (address) { + return ERC1967Utils.getImplementation(); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/ERC1967/ERC1967Utils.sol b/packages/contracts/contracts/openzeppelin/ERC1967/ERC1967Utils.sol new file mode 100644 index 000000000..4be21f584 --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/ERC1967/ERC1967Utils.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol) +pragma solidity ^0.8.0; + + +import {IBeacon} from "../beacon/IBeacon.sol"; +import {Address} from "../utils/Address.sol"; +import {StorageSlot} from "../utils/StorageSlot.sol"; + +/** + * @dev This abstract contract provides getters and event emitting update functions for + * https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots. + */ +library ERC1967Utils { + /** + * @dev Emitted when the implementation is upgraded. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Emitted when the admin account has changed. + */ + event AdminChanged(address previousAdmin, address newAdmin); + + /** + * @dev Emitted when the beacon is changed. + */ + event BeaconUpgraded(address indexed beacon); + + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + */ + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev The `implementation` of the proxy is invalid. + */ + error ERC1967InvalidImplementation(address implementation); + + /** + * @dev The `admin` of the proxy is invalid. + */ + error ERC1967InvalidAdmin(address admin); + + /** + * @dev The `beacon` of the proxy is invalid. + */ + error ERC1967InvalidBeacon(address beacon); + + /** + * @dev An upgrade function sees `msg.value > 0` that may be lost. + */ + error ERC1967NonPayable(); + + /** + * @dev Returns the current implementation address. + */ + function getImplementation() internal view returns (address) { + return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value; + } + + /** + * @dev Stores a new address in the ERC-1967 implementation slot. + */ + function _setImplementation(address newImplementation) private { + if (newImplementation.code.length == 0) { + revert ERC1967InvalidImplementation(newImplementation); + } + StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation; + } + + /** + * @dev Performs implementation upgrade with additional setup call if data is nonempty. + * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected + * to avoid stuck value in the contract. + * + * Emits an {IERC1967-Upgraded} event. + */ + function upgradeToAndCall(address newImplementation, bytes memory data) internal { + _setImplementation(newImplementation); + emit Upgraded(newImplementation); + + if (data.length > 0) { + Address.functionDelegateCall(newImplementation, data); + } else { + _checkNonPayable(); + } + } + + /** + * @dev Storage slot with the admin of the contract. + * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1. + */ + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /** + * @dev Returns the current admin. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using + * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` + */ + function getAdmin() internal view returns (address) { + return StorageSlot.getAddressSlot(ADMIN_SLOT).value; + } + + /** + * @dev Stores a new address in the ERC-1967 admin slot. + */ + function _setAdmin(address newAdmin) private { + if (newAdmin == address(0)) { + revert ERC1967InvalidAdmin(address(0)); + } + StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin; + } + + /** + * @dev Changes the admin of the proxy. + * + * Emits an {IERC1967-AdminChanged} event. + */ + function changeAdmin(address newAdmin) internal { + emit AdminChanged(getAdmin(), newAdmin); + _setAdmin(newAdmin); + } + + /** + * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. + * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1. + */ + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; + + /** + * @dev Returns the current beacon. + */ + function getBeacon() internal view returns (address) { + return StorageSlot.getAddressSlot(BEACON_SLOT).value; + } + + /** + * @dev Stores a new beacon in the ERC-1967 beacon slot. + */ + function _setBeacon(address newBeacon) private { + if (newBeacon.code.length == 0) { + revert ERC1967InvalidBeacon(newBeacon); + } + + StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon; + + address beaconImplementation = IBeacon(newBeacon).implementation(); + if (beaconImplementation.code.length == 0) { + revert ERC1967InvalidImplementation(beaconImplementation); + } + } + + /** + * @dev Change the beacon and trigger a setup call if data is nonempty. + * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected + * to avoid stuck value in the contract. + * + * Emits an {IERC1967-BeaconUpgraded} event. + * + * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since + * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for + * efficiency. + */ + function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal { + _setBeacon(newBeacon); + emit BeaconUpgraded(newBeacon); + + if (data.length > 0) { + Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); + } else { + _checkNonPayable(); + } + } + + /** + * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract + * if an upgrade doesn't perform an initialization call. + */ + function _checkNonPayable() private { + if (msg.value > 0) { + revert ERC1967NonPayable(); + } + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/ERC1967/IERC1967.sol b/packages/contracts/contracts/openzeppelin/ERC1967/IERC1967.sol new file mode 100644 index 000000000..2a996f83a --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/ERC1967/IERC1967.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1967.sol) +pragma solidity ^0.8.0; + + +/** + * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. + */ +interface IERC1967 { + /** + * @dev Emitted when the implementation is upgraded. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Emitted when the admin account has changed. + */ + event AdminChanged(address previousAdmin, address newAdmin); + + /** + * @dev Emitted when the beacon is changed. + */ + event BeaconUpgraded(address indexed beacon); +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/Ownable.sol b/packages/contracts/contracts/openzeppelin/Ownable.sol new file mode 100644 index 000000000..9e4fadbd2 --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/Ownable.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) +pragma solidity ^0.8.0; + + +import {Context} from "./utils/Context.sol"; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * The initial owner is set to the address provided by the deployer. This can + * later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + /** + * @dev The caller account is not authorized to perform an operation. + */ + error OwnableUnauthorizedAccount(address account); + + /** + * @dev The owner is not a valid owner account. (eg. `address(0)`) + */ + error OwnableInvalidOwner(address owner); + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the address provided by the deployer as the initial owner. + */ + constructor(address initialOwner) { + if (initialOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(initialOwner); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + if (owner() != _msgSender()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby disabling any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + if (newOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/Proxy.sol b/packages/contracts/contracts/openzeppelin/Proxy.sol new file mode 100644 index 000000000..fd3ba563e --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/Proxy.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol) +pragma solidity ^0.8.0; + + +/** + * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM + * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to + * be specified by overriding the virtual {_implementation} function. + * + * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a + * different contract through the {_delegate} function. + * + * The success and return data of the delegated call will be returned back to the caller of the proxy. + */ +abstract contract Proxy { + /** + * @dev Delegates the current call to `implementation`. + * + * This function does not return to its internal call site, it will return directly to the external caller. + */ + function _delegate(address implementation) internal virtual { + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } + + /** + * @dev This is a virtual function that should be overridden so it returns the address to which the fallback + * function and {_fallback} should delegate. + */ + function _implementation() internal view virtual returns (address); + + /** + * @dev Delegates the current call to the address returned by `_implementation()`. + * + * This function does not return to its internal call site, it will return directly to the external caller. + */ + function _fallback() internal virtual { + _delegate(_implementation()); + } + + /** + * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other + * function in the contract matches the call data. + */ + fallback() external payable virtual { + _fallback(); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/ProxyAdmin.sol b/packages/contracts/contracts/openzeppelin/ProxyAdmin.sol new file mode 100644 index 000000000..9d2ca2127 --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/ProxyAdmin.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/ProxyAdmin.sol) + +pragma solidity ^0.8.0; + +import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol"; +import {Ownable} from "./Ownable.sol"; + +/** + * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an + * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. + */ +contract ProxyAdmin is Ownable { + /** + * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address)` + * and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, + * while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string. + * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must + * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function + * during an upgrade. + */ + string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; + + /** + * @dev Sets the initial owner who can perform upgrades. + */ + constructor(address initialOwner) Ownable(initialOwner) {} + + /** + * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. + * See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}. + * + * Requirements: + * + * - This contract must be the admin of `proxy`. + * - If `data` is empty, `msg.value` must be zero. + */ + function upgradeAndCall( + ITransparentUpgradeableProxy proxy, + address implementation, + bytes memory data + ) public payable virtual onlyOwner { + proxy.upgradeToAndCall{value: msg.value}(implementation, data); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/TransparentUpgradeableProxy.sol b/packages/contracts/contracts/openzeppelin/TransparentUpgradeableProxy.sol new file mode 100644 index 000000000..db275cf00 --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/TransparentUpgradeableProxy.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol) + +pragma solidity ^0.8.0; + +import {ERC1967Utils} from "./ERC1967/ERC1967Utils.sol"; +import {ERC1967Proxy} from "./ERC1967/ERC1967Proxy.sol"; +import {IERC1967} from "./ERC1967/IERC1967.sol"; +import {ProxyAdmin} from "./ProxyAdmin.sol"; + +/** + * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy} + * does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch + * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not + * include them in the ABI so this interface must be used to interact with it. + */ +interface ITransparentUpgradeableProxy is IERC1967 { + function upgradeToAndCall(address, bytes calldata) external payable; +} + +/** + * @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance. + * + * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector + * clashing], which can potentially be used in an attack, this contract uses the + * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two + * things that go hand in hand: + * + * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if + * that call matches the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself. + * 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to + * the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating + * the proxy admin cannot fallback to the target implementation. + * + * These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a + * dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to + * call a function from the proxy implementation. For this reason, the proxy deploys an instance of {ProxyAdmin} and + * allows upgrades only if they come through it. You should think of the `ProxyAdmin` instance as the administrative + * interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership. + * + * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not + * inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch + * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to + * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the + * implementation. + * + * NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a + * meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract. + * + * IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an + * immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be + * overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an + * undesirable state where the admin slot is different from the actual admin. Relying on the value of the admin slot + * is generally fine if the implementation is trusted. + * + * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the + * compiler will not check that there are no selector conflicts, due to the note above. A selector clash between any new + * function and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This + * could render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency. + */ +contract TransparentUpgradeableProxy is ERC1967Proxy { + // An immutable address for the admin to avoid unnecessary SLOADs before each call + // at the expense of removing the ability to change the admin once it's set. + // This is acceptable if the admin is always a ProxyAdmin instance or similar contract + // with its own ability to transfer the permissions to another account. + address private immutable _admin; + + /** + * @dev The proxy caller is the current admin, and can't fallback to the proxy target. + */ + error ProxyDeniedAdminAccess(); + + /** + * @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`, + * backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in + * {ERC1967Proxy-constructor}. + */ + constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) { + _admin = address(new ProxyAdmin(initialOwner)); + // Set the storage value and emit an event for ERC-1967 compatibility + ERC1967Utils.changeAdmin(_proxyAdmin()); + } + + /** + * @dev Returns the admin of this proxy. + */ + function _proxyAdmin() internal view virtual returns (address) { + return _admin; + } + + /** + * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior. + */ + function _fallback() internal virtual override { + if (msg.sender == _proxyAdmin()) { + if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) { + revert ProxyDeniedAdminAccess(); + } else { + _dispatchUpgradeToAndCall(); + } + } else { + super._fallback(); + } + } + + /** + * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}. + * + * Requirements: + * + * - If `data` is empty, `msg.value` must be zero. + */ + function _dispatchUpgradeToAndCall() private { + (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); + ERC1967Utils.upgradeToAndCall(newImplementation, data); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/beacon/IBeacon.sol b/packages/contracts/contracts/openzeppelin/beacon/IBeacon.sol new file mode 100644 index 000000000..b55d492e3 --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/beacon/IBeacon.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol) +pragma solidity ^0.8.0; + + +/** + * @dev This is the interface that {BeaconProxy} expects of its beacon. + */ +interface IBeacon { + /** + * @dev Must return an address that can be used as a delegate call target. + * + * {UpgradeableBeacon} will check that this address is a contract. + */ + function implementation() external view returns (address); +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/utils/Address.sol b/packages/contracts/contracts/openzeppelin/utils/Address.sol new file mode 100644 index 000000000..2021cb640 --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/utils/Address.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol) +pragma solidity ^0.8.0; + + +import {Errors} from "./Errors.sol"; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev There's no code at `target` (it is not a contract). + */ + error AddressEmptyCode(address target); + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + if (address(this).balance < amount) { + revert Errors.InsufficientBalance(address(this).balance, amount); + } + + (bool success, ) = recipient.call{value: amount}(""); + if (!success) { + revert Errors.FailedCall(); + } + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason or custom error, it is bubbled + * up by this function (like regular Solidity function calls). However, if + * the call reverted with no returned reason, this function reverts with a + * {Errors.FailedCall} error. + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + if (address(this).balance < value) { + revert Errors.InsufficientBalance(address(this).balance, value); + } + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target + * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case + * of an unsuccessful call. + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata + ) internal view returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + // only check if target is a contract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + if (returndata.length == 0 && target.code.length == 0) { + revert AddressEmptyCode(target); + } + return returndata; + } + } + + /** + * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the + * revert reason or with a default {Errors.FailedCall} error. + */ + function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + return returndata; + } + } + + /** + * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}. + */ + function _revert(bytes memory returndata) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert Errors.FailedCall(); + } + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/utils/Context.sol b/packages/contracts/contracts/openzeppelin/utils/Context.sol new file mode 100644 index 000000000..dc19d7011 --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/utils/Context.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) +pragma solidity ^0.8.0; + + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + + function _contextSuffixLength() internal view virtual returns (uint256) { + return 0; + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/utils/Errors.sol b/packages/contracts/contracts/openzeppelin/utils/Errors.sol new file mode 100644 index 000000000..bcf487596 --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/utils/Errors.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + +/** + * @dev Collection of common custom errors used in multiple contracts + * + * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library. + * It is recommended to avoid relying on the error API for critical functionality. + */ +library Errors { + /** + * @dev The ETH balance of the account is not enough to perform the operation. + */ + error InsufficientBalance(uint256 balance, uint256 needed); + + /** + * @dev A call to an address target failed. The target may have reverted. + */ + error FailedCall(); + + /** + * @dev The deployment failed. + */ + error FailedDeployment(); +} \ No newline at end of file diff --git a/packages/contracts/contracts/openzeppelin/utils/StorageSlot.sol b/packages/contracts/contracts/openzeppelin/utils/StorageSlot.sol new file mode 100644 index 000000000..5e3111607 --- /dev/null +++ b/packages/contracts/contracts/openzeppelin/utils/StorageSlot.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol) +// This file was procedurally generated from scripts/generate/templates/StorageSlot.js. +pragma solidity ^0.8.0; + +/** + * @dev Library for reading and writing primitive types to specific storage slots. + * + * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. + * This library helps with reading and writing to such slots without the need for inline assembly. + * + * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. + * + * Example usage to set ERC-1967 implementation slot: + * ```solidity + * contract ERC1967 { + * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + * + * function _getImplementation() internal view returns (address) { + * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; + * } + * + * function _setImplementation(address newImplementation) internal { + * require(newImplementation.code.length > 0); + * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; + * } + * } + * ``` + */ +library StorageSlot { + struct AddressSlot { + address value; + } + + struct BooleanSlot { + bool value; + } + + struct Bytes32Slot { + bytes32 value; + } + + struct Uint256Slot { + uint256 value; + } + + struct StringSlot { + string value; + } + + struct BytesSlot { + bytes value; + } + + /** + * @dev Returns an `AddressSlot` with member `value` located at `slot`. + */ + function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `BooleanSlot` with member `value` located at `slot`. + */ + function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. + */ + function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `Uint256Slot` with member `value` located at `slot`. + */ + function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `StringSlot` with member `value` located at `slot`. + */ + function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `StringSlot` representation of the string storage pointer `store`. + */ + function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := store.slot + } + } + + /** + * @dev Returns an `BytesSlot` with member `value` located at `slot`. + */ + function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. + */ + function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := store.slot + } + } +} \ No newline at end of file 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 + ); + } + + + + + + +}