From 0af9787d8adb9854ec82a74e5bf4a3827108acf0 Mon Sep 17 00:00:00 2001 From: andy Date: Wed, 24 Apr 2024 12:32:33 -0400 Subject: [PATCH 1/6] ryans updates --- .../LenderCommitmentGroup_Smart.sol | 196 +++++------------- .../contracts/interfaces/ISmartCommitment.sol | 4 +- 2 files changed, 57 insertions(+), 143 deletions(-) diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol index 514be166a..135b568c3 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol @@ -41,86 +41,19 @@ import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; /* - - - -////---- - - -1. Use 50% forced max utilization ratio as initial game theory - have a global utilization limit and a user-signalled utilization limit (based on shares signalling) - -2. When pool shares are burned, give the lender : [ their pct shares * ( currentPrincipalTokens in contract, totalCollateralShares, totalInterestCollected) ] and later, they can burn the collateral shares for any collateral tokens that are in the contract. -3. use noahs TToken contract as reference for ratios -> amt of tokens to get when committing -4. Need price oracle bc we dont want to use maxPrincipalPerCollateral ratio as a static ideally -5. have an LTV ratio - -Every time a lender deposits tokens, we can mint an equal amt of RepresentationToken - - -// -- LIMITATIONS -1. neither the principal nor collateral token shall not have more than 18 decimals due to the way expansion is configured - - -// -- EXITING - -When exiting, a lender is burning X shares - - - We calculate the total equity value (Z) of the pool multiplies by their pct of shares (S%) (naive is just total committed princ tokens and interest , could maybe do better ) - - We are going to give the lender (Z * S%) value. The way we are going to give it to them is in a split of principal (P) and collateral tokens (C) which are in the pool right now. Similar to exiting a uni pool . C tokens will only be in the pool if bad defaults happened. - - NOTE: We will know the price of C in terms of P due to the ratio of total P used for loans and total C used for loans - - NOTE: if there are not enough P and C tokens in the pool to give the lender to equal a value of (Z * S%) then we revert . - -// --------- - - -// ISSUES - -1. for 'overall pool value' calculations (for shares math) an active loans value should be treated as "principal+interest" - aka the amount that will be paid to the pool optimistically. DONE -2. Redemption with ' the split' of principal and collateral is not ideal . What would be more ideal is a "conversion auction' or a 'swap auction'. - In this paradigm, any party can offer to give X principal tokens for the Y collateral tokens that are in the pool. the auction lasts (1 hour?) and this way it is always only principal tha is being withdrawn - far less risk of MEV attacker taking more C -- DONE -3. it is annoying that a bad default can cause a pool to have to totally exit and close ..this is a minor issue. maybe some form of Insurance can help resurrect a pool in this case, mayeb anyone can restore the health of the pool w a fn call. - a. fix this by changing the shares logic so you do get more shares in this event (i dont think its possible) - b. have a function that lets anyone donate principal tokens to make the pool whole again . (refill underwater pools w insurance fund??) - c. lets pools expire and get unwound and withdrawn completely , make a new pool - -4. build a function to do lender close loan - - - -TODO: -A. Make a mental map of these subsystems, attack vectors, mitigaions - -B. - - -// ----- - - - -// TODO - - - 2. consider adding PATHS to this for the oracle.. so the pair can be USDC to PNDC but use weth as intermediate - 4. tests -// ---- + Each LenderCommitmentGroup SmartContract acts as its own Loan Commitment (for the SmartCommitmentForwarder) and acts as its own Lender in the Teller Protocol. + Lender Users can deposit principal tokens in this contract and this will give them Share Tokens (LP tokens) representing their ownership in the liquidity pool of this contract. + Borrower Users can borrow principal token funds from this contract (via the SCF contract) by providing collateral tokens in the proper amount as specified by the rules of this smart contract. + These collateral tokens are then owned by this smart contract and are returned to the borrower via the Teller Protocol rules to the borrower if and only if the borrower repays principal and interest of the loan they took. - - -If a lender puts up 50,000 originally, im able to withdraw all my deposits. Everyone else is in the hole until a borrower repays a loan -If there isnt enough liquidity, you just cannot burn those shares. + If the borrower defaults on a loan, for 24 hours a liquidation auction is automatically conducted by this smart contract in order to incentivize a liquidator to take the collateral tokens in exchange for principal tokens. - -Consider implementing eip-4626 - */ @@ -139,16 +72,14 @@ contract LenderCommitmentGroup_Smart is uint256 public immutable UNISWAP_EXPANSION_FACTOR = 2**96; - uint256 public immutable EXCHANGE_RATE_EXPANSION_FACTOR = 1e36; //consider making this dynamic + uint256 public immutable EXCHANGE_RATE_EXPANSION_FACTOR = 1e36; /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address public immutable TELLER_V2; address public immutable SMART_COMMITMENT_FORWARDER; address public immutable UNISWAP_V3_FACTORY; address public UNISWAP_V3_POOL; - - // bool private _initialized; - + LenderCommitmentGroupShares public poolSharesToken; IERC20 public principalToken; @@ -157,17 +88,17 @@ contract LenderCommitmentGroup_Smart is uint256 marketId; //this should be the net total principal tokens , lenderAdded - lenderWithdrawn - uint256 public totalPrincipalTokensCommitted; + uint256 public totalPrincipalTokensCommitted; uint256 public totalPrincipalTokensLended; uint256 public totalPrincipalTokensRepaid; //subtract this and the above to find total principal tokens outstanding for loans - uint256 public totalCollateralTokensEscrowedForLoans; // we use this in conjunction with totalPrincipalTokensLended for a psuedo TWAP price oracle of C tokens, used for exiting . Nice bc it is averaged over all of our relevant loans, not the current price. - + uint256 public totalPrincipalTokensWithdrawn; + uint256 public totalInterestCollected; uint16 public liquidityThresholdPercent; //5000 is 50 pct // enforce max of 10000 - uint16 public loanToValuePercent; //the overcollateralization ratio, typically 80 pct + uint16 public collateralRatio; //the overcollateralization ratio, typically 80 pct uint32 public twapInterval; uint32 public maxLoanDuration; @@ -231,7 +162,7 @@ contract LenderCommitmentGroup_Smart is uint16 _interestRateLowerBound, uint16 _interestRateUpperBound, uint16 _liquidityThresholdPercent, // When 100% , the entire pool can be drawn for lending. When 80%, only 80% of the pool can be drawn for lending. - uint16 _loanToValuePercent, //the required overcollateralization ratio. 10000 is 1:1 baseline , typically this is above 10000 + uint16 _collateralRatio, //the required overcollateralization ratio. 10000 is 1:1 baseline , typically this is above 10000 uint24 _uniswapPoolFee, uint32 _twapInterval ) external initializer returns (address poolSharesToken_) { @@ -255,7 +186,7 @@ contract LenderCommitmentGroup_Smart is //in order for this to succeed, first, that SmartCommitmentForwarder needs to be THE trusted forwarder for the market - //approve this market as a forwarder + ITellerV2Context(TELLER_V2).approveMarketForwarder( _marketId, SMART_COMMITMENT_FORWARDER @@ -267,13 +198,13 @@ contract LenderCommitmentGroup_Smart is - require(interestRateUpperBound <= 10000, "invalid _interestRateUpperBound"); + require(interestRateLowerBound <= interestRateUpperBound, "invalid _interestRateLowerBound"); require(_liquidityThresholdPercent <= 10000, "invalid _liquidityThresholdPercent"); liquidityThresholdPercent = _liquidityThresholdPercent; - loanToValuePercent = _loanToValuePercent; + collateralRatio = _collateralRatio; twapInterval = _twapInterval; @@ -285,8 +216,7 @@ contract LenderCommitmentGroup_Smart is onlyInitializing returns (address poolSharesToken_) { - // uint256 principalTokenDecimals = principalToken.decimals(); - + require( address(poolSharesToken) == address(0), "Pool shares already deployed" @@ -295,7 +225,7 @@ contract LenderCommitmentGroup_Smart is poolSharesToken = new LenderCommitmentGroupShares( "PoolShares", "PSH", - 18 //may want this to equal the decimals of principal token !? + 18 ); return address(poolSharesToken); @@ -312,17 +242,17 @@ contract LenderCommitmentGroup_Smart is */ uint256 poolTotalEstimatedValue = getPoolTotalEstimatedValue(); - uint256 poolTotalEstimatedValuePlusInterest = poolTotalEstimatedValue + - totalInterestCollected; + // uint256 poolTotalEstimatedValuePlusInterest = poolTotalEstimatedValue + + // totalInterestCollected; if (poolTotalEstimatedValue == 0) { return EXCHANGE_RATE_EXPANSION_FACTOR; // 1 to 1 for first swap } rate_ = - (poolTotalEstimatedValuePlusInterest * + (poolTotalEstimatedValue * EXCHANGE_RATE_EXPANSION_FACTOR) / - poolTotalEstimatedValue; + poolSharesToken.totalSupply(); } function sharesExchangeRateInverse() @@ -341,9 +271,10 @@ contract LenderCommitmentGroup_Smart is view returns (uint256 poolTotalEstimatedValue_) { - int256 poolTotalEstimatedValueSigned = int256( - totalPrincipalTokensCommitted - ) + tokenDifferenceFromLiquidations; + + int256 poolTotalEstimatedValueSigned = int256(totalPrincipalTokensCommitted) + + int256(totalInterestCollected) + int256(tokenDifferenceFromLiquidations) + - int256(totalPrincipalTokensWithdrawn); //if the poolTotalEstimatedValue_ is less than 0, we treat it as 0. poolTotalEstimatedValue_ = poolTotalEstimatedValueSigned > int256(0) @@ -359,7 +290,7 @@ contract LenderCommitmentGroup_Smart is address _sharesRecipient ) external returns (uint256 sharesAmount_) { //transfers the primary principal token from msg.sender into this contract escrow - //gives + principalToken.transferFrom(msg.sender, address(this), _amount); sharesAmount_ = _valueOfUnderlying(_amount, sharesExchangeRate()); @@ -389,11 +320,11 @@ contract LenderCommitmentGroup_Smart is uint256 _principalAmount, uint256 _collateralAmount, address _collateralTokenAddress, - uint256 _collateralTokenId, //not used + uint256 _collateralTokenId, uint32 _loanDuration, uint16 _interestRate ) external onlySmartCommitmentForwarder whenNotPaused { - //consider putting these into less readonly fn calls + require( _collateralTokenAddress == address(collateralToken), "Mismatching collateral token" @@ -407,8 +338,7 @@ contract LenderCommitmentGroup_Smart is getPrincipalAmountAvailableToBorrow() >= _principalAmount, "Invalid loan max principal" ); - - require(isAllowedToBorrow(_borrower), "unauthorized borrow"); + //this is expanded by 10**18 uint256 requiredCollateral = getCollateralRequiredForPrincipalAmount( @@ -420,8 +350,7 @@ contract LenderCommitmentGroup_Smart is requiredCollateral, "Insufficient Borrower Collateral" ); - - //consider changing how this works + principalToken.approve(address(TELLER_V2), _principalAmount); //do not have to spoof/forward as this contract is the lender ! @@ -451,7 +380,7 @@ contract LenderCommitmentGroup_Smart is ) external returns (uint256) { - //this reduces total supply + poolSharesToken.burn(msg.sender, _amountPoolSharesTokens); uint256 principalTokenValueToWithdraw = _valueOfUnderlying( @@ -459,7 +388,7 @@ contract LenderCommitmentGroup_Smart is sharesExchangeRateInverse() ); - totalPrincipalTokensCommitted -= principalTokenValueToWithdraw; + totalPrincipalTokensWithdrawn += principalTokenValueToWithdraw; principalToken.transfer(_recipient, principalTokenValueToWithdraw); @@ -505,8 +434,7 @@ contract LenderCommitmentGroup_Smart is totalPrincipalTokensRepaid += amountDue; } else { - //the loan will be not be made whole and will be short - + uint256 tokensToGiveToSender = abs(_tokenAmountDifference); IERC20(principalToken).transferFrom( @@ -517,7 +445,7 @@ contract LenderCommitmentGroup_Smart is tokenDifferenceFromLiquidations -= int256(tokensToGiveToSender); - totalPrincipalTokensRepaid += (amountDue - tokensToGiveToSender); + totalPrincipalTokensRepaid += amountDue; } //this will give collateral to the caller @@ -541,8 +469,7 @@ contract LenderCommitmentGroup_Smart is /* This function will calculate the incentive amount (using a uniswap bonus plus a timer) of principal tokens that will be given to incentivize liquidating a loan - - Starts at 5000 and ticks down to -5000 + */ function getMinimumAmountDifferenceToCloseDefaultedLoan( uint256 _amountOwed, @@ -559,9 +486,8 @@ contract LenderCommitmentGroup_Smart is uint256 secondsSinceDefaulted = block.timestamp - _loanDefaultedTimestamp; - - //make this 10000 be a param in the constructor - int256 incentiveMultiplier = int256(10000) - + + int256 incentiveMultiplier = int256(86400) - int256(secondsSinceDefaulted); if (incentiveMultiplier < -10000) { @@ -576,8 +502,7 @@ contract LenderCommitmentGroup_Smart is function abs(int x) private pure returns (uint) { return x >= 0 ? uint(x) : uint(-x); } - - //this is expanded by 1e18 + function getCollateralRequiredForPrincipalAmount(uint256 _principalAmount) public view @@ -588,10 +513,10 @@ contract LenderCommitmentGroup_Smart is ); //this is an amount of collateral - return baseAmount.percent(loanToValuePercent); + return baseAmount.percent(collateralRatio); } - //remember that this result is expanded by UNISWAP_EXPANSION_FACTOR + //this result is expanded by UNISWAP_EXPANSION_FACTOR function _getUniswapV3TokenPairPrice(uint32 _twapInterval) internal view @@ -605,14 +530,13 @@ contract LenderCommitmentGroup_Smart is return _getPriceFromSqrtX96(sqrtPriceX96); } - //remember that this result is expanded by UNISWAP_EXPANSION_FACTOR + //this result is expanded by UNISWAP_EXPANSION_FACTOR function _getPriceFromSqrtX96(uint160 _sqrtPriceX96) internal pure returns (uint256 price_) { - // uint160 sqrtPrice = _sqrtPriceX96 ; - + uint256 priceX96 = (uint256(_sqrtPriceX96) * uint256(_sqrtPriceX96)) / (2**96); @@ -765,19 +689,7 @@ contract LenderCommitmentGroup_Smart is totalInterestCollected += interestAmount; } - function getAverageWeightedPriceForCollateralTokensPerPrincipalTokens() - public - view - returns (uint256) - { - if (totalPrincipalTokensLended <= 0) { - return 0; - } - - return - totalCollateralTokensEscrowedForLoans / totalPrincipalTokensLended; - } - + function getTotalPrincipalTokensOutstandingInActiveLoans() public view @@ -829,7 +741,8 @@ contract LenderCommitmentGroup_Smart is return 0; } - return uint16( Math.min( (totalPrincipalTokensLended - totalPrincipalTokensRepaid) * 10000 / totalPrincipalTokensCommitted , 10000 )); + return uint16( Math.min( getTotalPrincipalTokensOutstandingInActiveLoans() * 10000 / + getPoolTotalEstimatedValue() , 10000 )); } @@ -841,20 +754,23 @@ contract LenderCommitmentGroup_Smart is return address(principalToken); } - function isAllowedToBorrow(address borrower) public view returns (bool) { - return true; - } + function getPrincipalAmountAvailableToBorrow() public view returns (uint256) - { - int256 amountAvailable = int256(totalPrincipalTokensCommitted) - - int256(getTotalPrincipalTokensOutstandingInActiveLoans()) + - tokenDifferenceFromLiquidations; + { + + int256 poolTotalEstimatedValueSigned = int256(totalPrincipalTokensCommitted) + + int256(totalInterestCollected) + int256(tokenDifferenceFromLiquidations) + - int256(totalPrincipalTokensWithdrawn); + + int256 amountAvailable = int256( ( uint256( poolTotalEstimatedValueSigned )).percent(liquidityThresholdPercent) - + getTotalPrincipalTokensOutstandingInActiveLoans() ) ; - return uint256(amountAvailable).percent(liquidityThresholdPercent); + return uint256(amountAvailable); + } /** diff --git a/packages/contracts/contracts/interfaces/ISmartCommitment.sol b/packages/contracts/contracts/interfaces/ISmartCommitment.sol index afca940df..b86dcbee1 100644 --- a/packages/contracts/contracts/interfaces/ISmartCommitment.sol +++ b/packages/contracts/contracts/interfaces/ISmartCommitment.sol @@ -39,9 +39,7 @@ interface ISmartCommitment { external view returns (uint256); - - function isAllowedToBorrow(address borrower) external view returns (bool); - + function acceptFundsForAcceptBid( address _borrower, uint256 _bidId, From e8206549cd2e29e6c03ae99302edf643e9e769a6 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Wed, 24 Apr 2024 12:50:41 -0400 Subject: [PATCH 2/6] tests pass --- .../LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol | 4 +--- packages/contracts/lib/forge-std | 2 +- .../SmartCommitments/LenderCommitmentGroup_Smart_Test.sol | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol index 135b568c3..55766a488 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol @@ -242,10 +242,8 @@ contract LenderCommitmentGroup_Smart is */ uint256 poolTotalEstimatedValue = getPoolTotalEstimatedValue(); - // uint256 poolTotalEstimatedValuePlusInterest = poolTotalEstimatedValue + - // totalInterestCollected; - if (poolTotalEstimatedValue == 0) { + if (poolSharesToken.totalSupply() == 0) { return EXCHANGE_RATE_EXPANSION_FACTOR; // 1 to 1 for first swap } diff --git a/packages/contracts/lib/forge-std b/packages/contracts/lib/forge-std index 2f6762e4f..73a504d2c 160000 --- a/packages/contracts/lib/forge-std +++ b/packages/contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit 2f6762e4f73f3d835457c220b5f62dfeeb6f6341 +Subproject commit 73a504d2cf6f37b7ce285b479f4c681f76e95f1b diff --git a/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Test.sol b/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Test.sol index 0f131c002..05da7f952 100644 --- a/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Test.sol +++ b/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Test.sol @@ -213,7 +213,7 @@ contract LenderCommitmentGroup_Smart_Test is Testable { uint256 sharesAmount_ = lenderCommitmentGroupSmart .addPrincipalToCommitmentGroup(1000000, address(borrower)); - uint256 expectedSharesAmount = 500000; + uint256 expectedSharesAmount = 1000000; //use ttoken logic to make this better assertEq( @@ -518,7 +518,7 @@ contract LenderCommitmentGroup_Smart_Test is Testable { loanDefaultTimestamp ); - int256 expectedMinAmount = 400; //based on loanDefaultTimestamp gap + int256 expectedMinAmount = 4220; //based on loanDefaultTimestamp gap assertEq(min_amount,expectedMinAmount,"min_amount unexpected"); @@ -566,7 +566,7 @@ contract LenderCommitmentGroup_Smart_Test is Testable { loanDefaultTimestamp ); - int256 expectedMinAmount = -500; //based on loanDefaultTimestamp gap + int256 expectedMinAmount = 3220; //based on loanDefaultTimestamp gap assertEq(min_amount,expectedMinAmount,"min_amount unexpected"); From 38e10a5309affecbc34f18cbbfaf7b21713fca62 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Wed, 24 Apr 2024 13:06:49 -0400 Subject: [PATCH 3/6] dynamic pool share sym --- .../LenderCommitmentGroup_Smart.sol | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol index 55766a488..ba82e5ed0 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol @@ -87,13 +87,14 @@ contract LenderCommitmentGroup_Smart is uint256 marketId; - //this should be the net total principal tokens , lenderAdded - lenderWithdrawn + uint256 public totalPrincipalTokensCommitted; + uint256 public totalPrincipalTokensWithdrawn; uint256 public totalPrincipalTokensLended; uint256 public totalPrincipalTokensRepaid; //subtract this and the above to find total principal tokens outstanding for loans - uint256 public totalPrincipalTokensWithdrawn; + uint256 public totalInterestCollected; @@ -211,7 +212,7 @@ contract LenderCommitmentGroup_Smart is poolSharesToken_ = _deployPoolSharesToken(); } - function _deployPoolSharesToken() + function _deployPoolSharesToken( ) internal onlyInitializing returns (address poolSharesToken_) @@ -222,15 +223,37 @@ contract LenderCommitmentGroup_Smart is "Pool shares already deployed" ); + + (string memory name, string memory symbol ) = _generateTokenNameAndSymbol( + address(principalToken), + address(collateralToken) + ); + poolSharesToken = new LenderCommitmentGroupShares( - "PoolShares", - "PSH", + 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 * @return rate_ The current exchange rate, scaled by the EXCHANGE_RATE_FACTOR. @@ -735,11 +758,12 @@ contract LenderCommitmentGroup_Smart is //this is always between 0 and 10000 function getPoolUtilizationRatio() public view returns (uint16) { - if (totalPrincipalTokensCommitted == 0) { + if (getPoolTotalEstimatedValue() == 0) { return 0; } - return uint16( Math.min( getTotalPrincipalTokensOutstandingInActiveLoans() * 10000 / + return uint16( Math.min( + getTotalPrincipalTokensOutstandingInActiveLoans() * 10000 / getPoolTotalEstimatedValue() , 10000 )); } From 24eb2a2b51743ee93bbb8fa8db32c919250d1caa Mon Sep 17 00:00:00 2001 From: Admazzola Date: Wed, 24 Apr 2024 13:30:47 -0400 Subject: [PATCH 4/6] tests pass --- .../LenderCommitmentGroup_Smart.sol | 10 +- .../LenderCommitmentGroup_Smart_Override.sol | 12 +- .../LenderCommitmentGroup_Smart_Test.sol | 135 +++++++++++++++--- 3 files changed, 129 insertions(+), 28 deletions(-) diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol index ba82e5ed0..243d5c88e 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol @@ -107,7 +107,7 @@ contract LenderCommitmentGroup_Smart is uint16 public interestRateUpperBound; - mapping(address => uint256) public principalTokensCommittedByLender; + //mapping(address => uint256) public principalTokensCommittedByLender; mapping(uint256 => bool) public activeBids; //this excludes interest @@ -260,9 +260,7 @@ contract LenderCommitmentGroup_Smart is */ function sharesExchangeRate() public view virtual returns (uint256 rate_) { - /* - Should get slightly less shares than principal tokens put in !! diluted by ratio of pools actual equity - */ + uint256 poolTotalEstimatedValue = getPoolTotalEstimatedValue(); @@ -288,7 +286,7 @@ contract LenderCommitmentGroup_Smart is } function getPoolTotalEstimatedValue() - internal + public view returns (uint256 poolTotalEstimatedValue_) { @@ -317,7 +315,7 @@ contract LenderCommitmentGroup_Smart is sharesAmount_ = _valueOfUnderlying(_amount, sharesExchangeRate()); totalPrincipalTokensCommitted += _amount; - principalTokensCommittedByLender[msg.sender] += _amount; + //principalTokensCommittedByLender[msg.sender] += _amount; //mint shares equal to _amount and give them to the shares recipient !!! poolSharesToken.mint(_sharesRecipient, sharesAmount_); diff --git a/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Override.sol b/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Override.sol index 696e7898d..57894db32 100644 --- a/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Override.sol +++ b/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Override.sol @@ -75,16 +75,18 @@ contract LenderCommitmentGroup_Smart_Override is LenderCommitmentGroup_Smart { totalPrincipalTokensCommitted = _mockAmt; } + function set_totalPrincipalTokensWithdrawn(uint256 _mockAmt) public { + totalPrincipalTokensWithdrawn = _mockAmt; + } + function set_totalInterestCollected(uint256 _mockAmt) public { totalInterestCollected = _mockAmt; } - function set_principalTokensCommittedByLender( - address lender, - uint256 _mockAmt - ) public { - principalTokensCommittedByLender[lender] = _mockAmt; + function set_tokenDifferenceFromLiquidations(int256 _mockAmt) public { + tokenDifferenceFromLiquidations = _mockAmt; } + function mock_mintShares(address _sharesRecipient, uint256 _mockAmt) public diff --git a/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Test.sol b/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Test.sol index 05da7f952..8d4681755 100644 --- a/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Test.sol +++ b/packages/contracts/tests/LenderCommitmentForwarder/extensions/SmartCommitments/LenderCommitmentGroup_Smart_Test.sol @@ -16,7 +16,7 @@ import "lib/forge-std/src/console.sol"; //contract LenderCommitmentGroup_Smart_Mock is ExtensionsContextUpgradeable {} /* -TODO + Write tests for a borrower . borrowing money from the group @@ -357,51 +357,152 @@ contract LenderCommitmentGroup_Smart_Test is Testable { } //test this thoroughly -- using spreadsheet data - function test_get_shares_exchange_rate() public { + function test_get_shares_exchange_rate_scenario_A() public { initialize_group_contract(); + lenderCommitmentGroupSmart.set_totalInterestCollected(0); + + lenderCommitmentGroupSmart.set_totalPrincipalTokensCommitted( + + 5000000 + ); + + uint256 rate = lenderCommitmentGroupSmart.super_sharesExchangeRate(); + + assertEq(rate , 1e36, "unexpected sharesExchangeRate"); + + } + + function test_get_shares_exchange_rate_scenario_B() public { + initialize_group_contract(); + + lenderCommitmentGroupSmart.set_totalInterestCollected(1000000); + + lenderCommitmentGroupSmart.set_totalPrincipalTokensCommitted( + + 1000000 + ); + + lenderCommitmentGroupSmart.set_totalPrincipalTokensWithdrawn( + + 1000000 + ); + + uint256 rate = lenderCommitmentGroupSmart.super_sharesExchangeRate(); + + assertEq(rate , 1e36, "unexpected sharesExchangeRate"); + + } + + function test_get_shares_exchange_rate_scenario_C() public { + initialize_group_contract(); + + + lenderCommitmentGroupSmart.set_totalPrincipalTokensCommitted( + + 1000000 + ); + lenderCommitmentGroupSmart.set_totalInterestCollected(1000000); - lenderCommitmentGroupSmart.set_principalTokensCommittedByLender( + uint256 sharesAmount = 500000; + + + lenderCommitmentGroupSmart.mock_mintShares( address(lender), - 5000000 + sharesAmount ); + uint256 poolTotalEstimatedValue = lenderCommitmentGroupSmart.getPoolTotalEstimatedValue(); + assertEq(poolTotalEstimatedValue , 2 * 1000000, "unexpected poolTotalEstimatedValue"); + uint256 rate = lenderCommitmentGroupSmart.super_sharesExchangeRate(); + + assertEq(rate , 4 * 1e36, "unexpected sharesExchangeRate"); + + /* + Rate should be 2 at this point so a depositor will only get half as many shares for their principal + */ } - function test_get_shares_exchange_rate_inverse() public { - lenderCommitmentGroupSmart.set_mockSharesExchangeRate(1000000); + + function test_get_shares_exchange_rate_after_default_liquidation_A() public { + initialize_group_contract(); + + + lenderCommitmentGroupSmart.set_totalPrincipalTokensCommitted( + 1000000 + ); + + lenderCommitmentGroupSmart.set_totalInterestCollected(1000000); + + lenderCommitmentGroupSmart.set_tokenDifferenceFromLiquidations(-1000000); + + uint256 sharesAmount = 1000000; + + lenderCommitmentGroupSmart.mock_mintShares( + address(lender), + sharesAmount + ); + + uint256 poolTotalEstimatedValue = lenderCommitmentGroupSmart.getPoolTotalEstimatedValue(); + assertEq(poolTotalEstimatedValue , 1 * 1000000, "unexpected poolTotalEstimatedValue"); + + uint256 rate = lenderCommitmentGroupSmart.super_sharesExchangeRate(); + + assertEq(rate , 1 * 1e36, "unexpected sharesExchangeRate"); - uint256 rate = lenderCommitmentGroupSmart.super_sharesExchangeRateInverse(); } - function test_shares_exchange_rate_after_interest_payments() public { + + function test_get_shares_exchange_rate_after_default_liquidation_B() public { + initialize_group_contract(); - initialize_group_contract(); - principalToken.transfer(address(lenderCommitmentGroupSmart), 1e18); - - lenderCommitmentGroupSmart.set_mockSharesExchangeRate( 1e36 ); //the default for now + lenderCommitmentGroupSmart.set_totalPrincipalTokensCommitted( + 1000000 + ); - vm.prank(address(lender)); - principalToken.approve(address(lenderCommitmentGroupSmart), 1000000); + + lenderCommitmentGroupSmart.set_tokenDifferenceFromLiquidations(-500000); - uint256 sharesAmount = 500000; + uint256 sharesAmount = 1000000; lenderCommitmentGroupSmart.mock_mintShares( address(lender), sharesAmount ); - //todo - + uint256 poolTotalEstimatedValue = lenderCommitmentGroupSmart.getPoolTotalEstimatedValue(); + assertEq(poolTotalEstimatedValue , 1 * 500000, "unexpected poolTotalEstimatedValue"); + + uint256 rate = lenderCommitmentGroupSmart.super_sharesExchangeRate(); + + assertEq(rate , 1e36 / 2, "unexpected sharesExchangeRate"); + + } + + + + + + function test_get_shares_exchange_rate_inverse() public { + lenderCommitmentGroupSmart.set_mockSharesExchangeRate(1000000); + + + uint256 rate = lenderCommitmentGroupSmart.super_sharesExchangeRateInverse(); } + + + + + + /* make sure both pos and neg branches get run, and tellerV2 is called at the end */ From 566ebda97cc130f16d3f8eb883209d4ab3bf2bbe Mon Sep 17 00:00:00 2001 From: "gmryan.eth" <45776981+rbcp18@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:47:12 +0100 Subject: [PATCH 5/6] shorten code on getPrincipalAmountAvailableToBorrow() --- .../LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol index 243d5c88e..1cc084a40 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol @@ -782,11 +782,7 @@ contract LenderCommitmentGroup_Smart is returns (uint256) { - int256 poolTotalEstimatedValueSigned = int256(totalPrincipalTokensCommitted) - + int256(totalInterestCollected) + int256(tokenDifferenceFromLiquidations) - - int256(totalPrincipalTokensWithdrawn); - - int256 amountAvailable = int256( ( uint256( poolTotalEstimatedValueSigned )).percent(liquidityThresholdPercent) - + int256 amountAvailable = int256( ( uint256( getPoolTotalEstimatedValue() )).percent(liquidityThresholdPercent) - getTotalPrincipalTokensOutstandingInActiveLoans() ) ; return uint256(amountAvailable); From d3f307c83bd55c25e18d968f45e0bd1dc6706a76 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Wed, 24 Apr 2024 13:51:14 -0400 Subject: [PATCH 6/6] shorten fn --- .../LenderCommitmentGroup_Smart.sol | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol index 243d5c88e..b25a70ca9 100644 --- a/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol +++ b/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol @@ -780,19 +780,14 @@ contract LenderCommitmentGroup_Smart is public view returns (uint256) - { + { - int256 poolTotalEstimatedValueSigned = int256(totalPrincipalTokensCommitted) - + int256(totalInterestCollected) + int256(tokenDifferenceFromLiquidations) - - int256(totalPrincipalTokensWithdrawn); - - int256 amountAvailable = int256( ( uint256( poolTotalEstimatedValueSigned )).percent(liquidityThresholdPercent) - - getTotalPrincipalTokensOutstandingInActiveLoans() ) ; - - return uint256(amountAvailable); + return ( uint256( getPoolTotalEstimatedValue() )).percent(liquidityThresholdPercent) - + getTotalPrincipalTokensOutstandingInActiveLoans() ; } + /** * @notice Lets the DAO/owner of the protocol implement an emergency stop mechanism. */