diff --git a/contracts/connectors/loantoken/LoanTokenLogicStandard.sol b/contracts/connectors/loantoken/LoanTokenLogicStandard.sol index 52d7def12..815c57a45 100644 --- a/contracts/connectors/loantoken/LoanTokenLogicStandard.sol +++ b/contracts/connectors/loantoken/LoanTokenLogicStandard.sol @@ -124,7 +124,17 @@ contract LoanTokenLogicStandard is AdvancedToken { return returnData; } */ - + + /** + * borrows funds from the pool. The underlying loan token may not be used as collateral. + * @param loanId the ID of the loan, 0 for a new loan + * @param withdrawAmount the amount to be withdrawn (actually borrowed) + * @param initialLoanDuration the duration of the loan in seconds. if the loan is not paid back until then, it'll need to be rolled over + * @param collateralTokenSent the amount of collateral token sent (150% of the withdrawn amount worth in collateral tokenns) + * @param collateralTokenAddress the address of the tokenn to be used as collateral. cannot be the loan token address + * @param borrower the one paying for the collateral + * @param receiver the one receiving the withdrawn amount + * */ function borrow( bytes32 loanId, // 0 if new loan uint256 withdrawAmount, diff --git a/tests/test_LoanToken.py b/tests/test_LoanToken.py index 3fbc8e230..735603cae 100644 --- a/tests/test_LoanToken.py +++ b/tests/test_LoanToken.py @@ -44,7 +44,7 @@ def test_loanAddress(loanToken, SUSD): assert loanTokenAddress == SUSD.address @pytest.fixture(scope="module", autouse=True) -def margin_pool_setup(accounts, RBTC, loanTokenSettings, loanToken, sovryn, SUSD): +def loan_pool_setup(accounts, RBTC, loanTokenSettings, loanToken, sovryn, SUSD): constants = shared.Constants() params = []; setup1 = [ @@ -62,6 +62,10 @@ def margin_pool_setup(accounts, RBTC, loanTokenSettings, loanToken, sovryn, SUSD tx = loanToken.updateSettings(loanTokenSettings.address, calldata) assert('LoanParamsSetup' in tx.events) assert('LoanParamsIdSetup' in tx.events) + calldata = loanTokenSettings.setupTorqueLoanParams.encode_input(params) + tx = loanToken.updateSettings(loanTokenSettings.address, calldata) + assert('LoanParamsSetup' in tx.events) + assert('LoanParamsIdSetup' in tx.events) print(tx.info()) sovryn.setLoanPool( [loanToken.address], @@ -682,7 +686,9 @@ def test_liquidate(accounts, loanToken, SUSD, set_demand_curve, RBTC, sovryn, pr assert(liquidate_event['currentMargin'] == current_margin) + def test_rollover(accounts, chain, loanToken, set_demand_curve, sovryn, priceFeeds, SUSD, RBTC, BZRX): + """ Tests paid interests to lender Test that loan attributes are updated @@ -764,6 +770,199 @@ def test_rollover(accounts, chain, loanToken, set_demand_curve, sovryn, priceFee assert(loan_swap_event['destAmount'] == interest_unpaid) +def test_borrow(accounts,loanToken,sovryn,set_demand_curve,lend_to_pool, SUSD, RBTC): + ''' + borrows some funds, checks the event is correct (including principal and collateral -> interest check) + and that the receiver received the correct amount of tokens + ''' + + # prepare the test + set_demand_curve() + lend_to_pool() + + # determine borrowing parameter + withdrawAmount = 10e18 #i want to borrow 10 USD + # compute the required collateral. params: address loanToken, address collateralToken, uint256 newPrincipal,uint256 marginAmount, bool isTorqueLoan + collateralTokenSent = sovryn.getRequiredCollateral(SUSD.address,RBTC.address,withdrawAmount,50e18, True) + print("collateral needed", collateralTokenSent) + durationInSeconds = 60*60*24*10 #10 days + + # compute expected values for asserts + interestRate = loanToken.nextBorrowInterestRate(withdrawAmount) + #principal = withdrawAmount/(1 - interestRate/1e20 * durationInSeconds / 31536000) + principal = fixedint(withdrawAmount).mul(1e18).div(fixedint(1e18).sub(fixedint(interestRate).mul(durationInSeconds).mul(10e18).div(31536000).div(10e20))) + borrowingFee = fixedint(sovryn.borrowingFeePercent()).mul(collateralTokenSent).div(1e20) + expectedBalance = fixedint(SUSD.balanceOf(accounts[1])).add(withdrawAmount) + + #approve the transfer of the collateral + RBTC.approve(loanToken.address, collateralTokenSent) + + # borrow some funds + tx = loanToken.borrow( + "0", # bytes32 loanId + withdrawAmount, # uint256 withdrawAmount + durationInSeconds, # uint256 initialLoanDuration + collateralTokenSent, # uint256 collateralTokenSent + RBTC.address, # address collateralTokenAddress + accounts[0], # address borrower + accounts[1], # address receiver + b'' # bytes memory loanDataBytes + ) + + #assert the trade was processed as expected + print(tx.info()) + borrow_event = tx.events['Borrow'] + assert(borrow_event['user'] == accounts[0]) + assert(borrow_event['lender'] == loanToken.address) + assert(borrow_event['loanToken'] == SUSD.address) + assert(borrow_event['collateralToken'] == RBTC.address) + assert(borrow_event['newPrincipal'] == principal) + assert(borrow_event['newCollateral'] == fixedint(collateralTokenSent).sub(borrowingFee)) + assert(borrow_event['interestRate'] == interestRate) + assert(borrow_event['interestDuration'] >= durationInSeconds-1 and borrow_event['interestDuration'] <= durationInSeconds) + assert(borrow_event['currentMargin'] >= 49e18) + + #assert the user received the borrowed amount + assert(SUSD.balanceOf(accounts[1]) == expectedBalance) + +def test_borrow_0_collateral_should_fail(accounts,loanToken,sovryn,set_demand_curve,lend_to_pool, SUSD, RBTC): + # prepare the test + lend_to_pool() + set_demand_curve() + + with reverts("8"): + loanToken.borrow( + "0", # bytes32 loanId + 10 , # uint256 withdrawAmount + 24*60*60, # uint256 initialLoanDuration + 0, # uint256 collateralTokenSent + RBTC.address, # address collateralTokenAddress + accounts[0], # address borrower + accounts[1], # address receiver + b'' # bytes memory loanDataBytes + ) + +def test_borrow_0_withdraw_should_fail(accounts,loanToken,sovryn,set_demand_curve,lend_to_pool, SUSD, RBTC): + # prepare the test + lend_to_pool() + set_demand_curve() + + with reverts("6"): + loanToken.borrow( + "0", # bytes32 loanId + 0 , # uint256 withdrawAmount + 24*60*60, # uint256 initialLoanDuration + 10, # uint256 collateralTokenSent + RBTC.address, # address collateralTokenAddress + accounts[0], # address borrower + accounts[1], # address receiver + b'' # bytes memory loanDataBytes + ) + +def test_borrow_sending_value_with_tokens_should_fail(accounts,loanToken,sovryn,set_demand_curve,lend_to_pool, SUSD, RBTC): + # prepare the test + lend_to_pool() + set_demand_curve() + + with reverts("7"): + loanToken.borrow( + "0", # bytes32 loanId + 10 , # uint256 withdrawAmount + 24*60*60, # uint256 initialLoanDuration + 10, # uint256 collateralTokenSent + RBTC.address, # address collateralTokenAddress + accounts[0], # address borrower + accounts[1], # address receiver + b'' , # bytes memory loanDataBytes + {'value': 100} + ) + +def test_borrow_invalid_collateral_should_fail(accounts,loanToken,sovryn,set_demand_curve,lend_to_pool, SUSD, RBTC): + # prepare the test + lend_to_pool() + set_demand_curve() + constants = shared.Constants() + + with reverts("9"): + loanToken.borrow( + "0", # bytes32 loanId + 10 , # uint256 withdrawAmount + 24*60*60, # uint256 initialLoanDuration + 10, # uint256 collateralTokenSent + constants.ZERO_ADDRESS, # address collateralTokenAddress + accounts[0], # address borrower + accounts[1], # address receiver + b'' # bytes memory loanDataBytes + ) + + with reverts("10"): + loanToken.borrow( + "0", # bytes32 loanId + 10 , # uint256 withdrawAmount + 24*60*60, # uint256 initialLoanDuration + 10, # uint256 collateralTokenSent + SUSD.address, # address collateralTokenAddress + accounts[0], # address borrower + accounts[1], # address receiver + b'' # bytes memory loanDataBytes + ) + + +def test_borrow_no_interest_should_fail(accounts,loanToken,sovryn,set_demand_curve,lend_to_pool, SUSD, RBTC): + #no demand curve settings -> no interest set + # prepare the test + lend_to_pool() + + # determine borrowing parameter + withdrawAmount = 10e18 #i want to borrow 10 USD + # compute the required collateral. params: address loanToken, address collateralToken, uint256 newPrincipal,uint256 marginAmount, bool isTorqueLoan + collateralTokenSent = sovryn.getRequiredCollateral(SUSD.address,RBTC.address,withdrawAmount,50e18, True) + + #approve the transfer of the collateral + RBTC.approve(loanToken.address, collateralTokenSent) + + with reverts("invalid interest"): + loanToken.borrow( + "0", # bytes32 loanId + withdrawAmount , # uint256 withdrawAmount + 24*60*60, # uint256 initialLoanDuration + collateralTokenSent, # uint256 collateralTokenSent + RBTC.address, # address collateralTokenAddress + accounts[0], # address borrower + accounts[1], # address receiver + b'' # bytes memory loanDataBytes + ) + + +def test_borrow_insufficient_collateral_should_fail(accounts,loanToken,sovryn,set_demand_curve,lend_to_pool, SUSD, RBTC): + # prepare the test + lend_to_pool() + set_demand_curve() + + # determine borrowing parameter + withdrawAmount = 10e18 #i want to borrow 10 USD + # compute the required collateral. params: address loanToken, address collateralToken, uint256 newPrincipal,uint256 marginAmount, bool isTorqueLoan + collateralTokenSent = sovryn.getRequiredCollateral(SUSD.address,RBTC.address,withdrawAmount,50e18, True) + collateralTokenSent /= 2 + print("sending collateral",collateralTokenSent) + + #approve the transfer of the collateral + RBTC.approve(loanToken.address, collateralTokenSent) + + with reverts("collateral insufficient"): + loanToken.borrow( + "0", # bytes32 loanId + withdrawAmount , # uint256 withdrawAmount + 24*60*60, # uint256 initialLoanDuration + collateralTokenSent, # uint256 collateralTokenSent + RBTC.address, # address collateralTokenAddress + accounts[0], # address borrower + accounts[1], # address receiver + b'' # bytes memory loanDataBytes + ) + + + def test_deposit_collateral(sovryn,set_demand_curve,lend_to_pool,open_margin_trade_position, RBTC): #prepare the test set_demand_curve() @@ -795,4 +994,5 @@ def test_deposit_collateral_0_value(sovryn,set_demand_curve,lend_to_pool,open_ma with reverts("depositAmount is 0"): sovryn.depositCollateral(loan_id, 0) -#note: deposit collateral tests for WETH still missing. \ No newline at end of file +#note: deposit collateral tests for WETH still missing. +