From 91c79daeb13a6bcebf3f23cbcf715e8a027e159f Mon Sep 17 00:00:00 2001 From: AntonAndell Date: Wed, 12 Jun 2024 10:16:33 +0200 Subject: [PATCH 1/2] feat: Allow users to repay any loan and take a loan with a destination --- .../governance/GovernanceIntegrationTest.java | 4 +- .../core/loans/LoansIntegrationTest.java | 32 ++++----- .../balanced/score/core/loans/LoansImpl.java | 60 ++++++++++------ .../score/core/loans/utils/TokenUtils.java | 12 ++-- .../balanced/score/core/loans/LoansTest.java | 71 +++++++++++++------ .../score/core/loans/LoansTestCrosschain.java | 64 ++++++++++++++++- .../score/core/loans/LoansTestInterest.java | 2 +- .../score/core/loans/LoansTestRewards.java | 6 +- .../core/rewards/RewardsIntegrationTest.java | 6 +- .../balanced/score/lib/interfaces/Loans.java | 6 +- .../lib/test/integration/BalancedClient.java | 2 +- .../score/lib/annotations/XCallProcessor.java | 40 +++++++---- 12 files changed, 211 insertions(+), 94 deletions(-) diff --git a/core-contracts/Governance/src/intTest/java/network/balanced/score/core/governance/GovernanceIntegrationTest.java b/core-contracts/Governance/src/intTest/java/network/balanced/score/core/governance/GovernanceIntegrationTest.java index a9c717a7c..627970b48 100644 --- a/core-contracts/Governance/src/intTest/java/network/balanced/score/core/governance/GovernanceIntegrationTest.java +++ b/core-contracts/Governance/src/intTest/java/network/balanced/score/core/governance/GovernanceIntegrationTest.java @@ -435,7 +435,7 @@ void blacklist() throws Throwable { null); Executable nonAllowedTransfer2 = () -> blacklistedUser2.bnUSD.transfer(owner.getAddress(), BigInteger.ONE, null); - Executable nonAllowedBurn = () -> blacklistedUser1.loans.returnAsset("bnUSD", BigInteger.TEN.pow(18), "sICX"); + Executable nonAllowedBurn = () -> blacklistedUser1.loans.returnAsset("bnUSD", BigInteger.TEN.pow(18), "sICX", ""); user3.bnUSD.transfer(owner.getAddress(), BigInteger.ONE, null); assertThrows(Exception.class, nonAllowedTransfer1); assertThrows(Exception.class, nonAllowedTransfer2); @@ -472,7 +472,7 @@ void emergency_disable_enable() throws Throwable { BigInteger.valueOf(200).multiply(BigInteger.TEN.pow(18)), null); Executable dividendsStatusTest = () -> user.dividends.distribute((tx) -> { }); - Executable loansStatusTest = () -> user.loans.returnAsset("bnUSD", BigInteger.ONE, "sICX"); + Executable loansStatusTest = () -> user.loans.returnAsset("bnUSD", BigInteger.ONE, "sICX", ""); Executable rewardsStatusTest = () -> user.rewards.distribute((tx) -> { }); Executable stakingStatusTest = () -> user.staking.stakeICX(collateral.multiply(BigInteger.TWO), null, null); diff --git a/core-contracts/Loans/src/intTest/java/network/balanced/score/core/loans/LoansIntegrationTest.java b/core-contracts/Loans/src/intTest/java/network/balanced/score/core/loans/LoansIntegrationTest.java index 353a25f09..ab8a5ec97 100644 --- a/core-contracts/Loans/src/intTest/java/network/balanced/score/core/loans/LoansIntegrationTest.java +++ b/core-contracts/Loans/src/intTest/java/network/balanced/score/core/loans/LoansIntegrationTest.java @@ -177,7 +177,7 @@ void crossChainDepositAndBorrow() throws Exception { // Deposit first then borrow byte[] depositBSC = AssetManagerMessages.deposit(balanced.BSC_TOKEN_ADDRESS, bscUser.account().toString(), loansNetAddress, collateral, "{}".getBytes()); owner.xcall.sendCall(balanced.assetManager._address(), new NetworkAddress(balanced.BSC_NID, balanced.BSC_ASSET_MANAGER).toString(), depositBSC); - byte[] borrowBSC = LoansMessages.xBorrow(balanced.BSC_TOKEN_SYMBOL, loanAmount); + byte[] borrowBSC = LoansMessages.xBorrow(balanced.BSC_TOKEN_SYMBOL, loanAmount, "", new byte[0]); owner.xcall.sendCall(balanced.loans._address(), bscUser.toString(), borrowBSC); // Bridge collateral to hub wallet first then borrow @@ -256,7 +256,7 @@ void crossChainRepayAndWithdraw() throws Exception { owner.xcall.sendCall(balanced.bnusd._address(), bscHubUser.toString(), repayTransfer); // Repay and withdraw with bnUSD on the ICON wallet. - nativeLoanTaker.loans.returnAsset("bnUSD", amountToRepay, balanced.BSC_TOKEN_SYMBOL); + nativeLoanTaker.loans.returnAsset("bnUSD", amountToRepay, balanced.BSC_TOKEN_SYMBOL, ""); nativeLoanTaker.loans.withdrawCollateral(amountToWithdraw, balanced.BSC_TOKEN_SYMBOL); // Assert @@ -316,17 +316,17 @@ void repayDebt() throws Exception { loanTakerIETHFullRepay.depositAndBorrow(ethAddress, collateralETH, loanAmount); loanTakerMultiPartialRepay.depositAndBorrow(ethAddress, collateralETH, loanAmount); - loanTakerPartialRepay.loans.returnAsset("bnUSD", debt.divide(BigInteger.TWO), "sICX"); + loanTakerPartialRepay.loans.returnAsset("bnUSD", debt.divide(BigInteger.TWO), "sICX", ""); loanTakerPartialRepay.bnUSD.transfer(loanTakerFullRepay.getAddress(), fee, null); loanTakerPartialRepay.bnUSD.transfer(loanTakerIETHFullRepay.getAddress(), fee, null); - loanTakerFullRepay.loans.returnAsset("bnUSD", debt, "sICX"); + loanTakerFullRepay.loans.returnAsset("bnUSD", debt, "sICX", ""); assertThrows(UserRevertedException.class, () -> - loanTakerIETHFullRepay.loans.returnAsset("bnUSD", debt, "sICX")); + loanTakerIETHFullRepay.loans.returnAsset("bnUSD", debt, "sICX", "")); - loanTakerIETHFullRepay.loans.returnAsset("bnUSD", debt, "iETH"); - loanTakerMultiPartialRepay.loans.returnAsset("bnUSD", debt.divide(BigInteger.TWO), "sICX"); - loanTakerMultiPartialRepay.loans.returnAsset("bnUSD", debt.divide(BigInteger.TWO), "iETH"); + loanTakerIETHFullRepay.loans.returnAsset("bnUSD", debt, "iETH", ""); + loanTakerMultiPartialRepay.loans.returnAsset("bnUSD", debt.divide(BigInteger.TWO), "sICX", ""); + loanTakerMultiPartialRepay.loans.returnAsset("bnUSD", debt.divide(BigInteger.TWO), "iETH", ""); BigInteger outstandingNewDebt = BigInteger.valueOf(3).multiply(debt.divide(BigInteger.TWO)); @@ -536,10 +536,10 @@ void withdrawCollateral() throws Exception { loanTakerPartialWithdraw.bnUSD.transfer(loanTakerFullWithdraw.getAddress(), fee, null); loanTakerPartialWithdraw.bnUSD.transfer(loanTakerETHFullWithdraw.getAddress(), fee, null); - loanTakerFullWithdraw.loans.returnAsset("bnUSD", debt, "sICX"); + loanTakerFullWithdraw.loans.returnAsset("bnUSD", debt, "sICX", ""); loanTakerFullWithdraw.loans.withdrawCollateral(collateral, "sICX"); - loanTakerETHFullWithdraw.loans.returnAsset("bnUSD", debt, "iETH"); + loanTakerETHFullWithdraw.loans.returnAsset("bnUSD", debt, "iETH", ""); loanTakerETHFullWithdraw.loans.withdrawCollateral(collateralETH, "iETH"); BigInteger amountToWithdraw = BigInteger.TEN.pow(20); @@ -607,14 +607,14 @@ void reOpenPosition() throws Exception { loanTakerPartialRepay.bnUSD.transfer(loanTakerFullClose.getAddress(), fee, null); loanTakerPartialRepay.bnUSD.transfer(loanTakerETHFullClose.getAddress(), fee, null); - loanTakerCloseLoanOnly.loans.returnAsset("bnUSD", debt, "sICX"); - loanTakerFullClose.loans.returnAsset("bnUSD", debt, "sICX"); - loanTakerETHFullClose.loans.returnAsset("bnUSD", debt, "iETH"); + loanTakerCloseLoanOnly.loans.returnAsset("bnUSD", debt, "sICX", ""); + loanTakerFullClose.loans.returnAsset("bnUSD", debt, "sICX", ""); + loanTakerETHFullClose.loans.returnAsset("bnUSD", debt, "iETH", ""); BigInteger amountRepaid = BigInteger.TEN.pow(21); BigInteger amountETHRepaid = BigInteger.TEN.pow(18); - loanTakerPartialRepay.loans.returnAsset("bnUSD", amountRepaid, "sICX"); - loanTakerETHPartialRepay.loans.returnAsset("bnUSD", amountETHRepaid, "iETH"); + loanTakerPartialRepay.loans.returnAsset("bnUSD", amountRepaid, "sICX", ""); + loanTakerETHPartialRepay.loans.returnAsset("bnUSD", amountETHRepaid, "iETH", ""); loanTakerFullClose.loans.withdrawCollateral(loanTakerFullClose.getLoansCollateralPosition("sICX"), null); loanTakerETHFullClose.loans.withdrawCollateral(loanTakerETHFullClose.getLoansCollateralPosition("iETH"), @@ -688,7 +688,7 @@ void debtCeilings() throws Exception { loanTaker1.stakeDepositAndBorrow(sICXCollateral, loanAmount1); assertThrows(RevertedException.class, () -> loanTaker2.stakeDepositAndBorrow(sICXCollateral, loanAmount2)); - loanTaker1.loans.returnAsset("bnUSD", debt2, "sICX"); + loanTaker1.loans.returnAsset("bnUSD", debt2, "sICX", ""); loanTaker2.stakeDepositAndBorrow(sICXCollateral, loanAmount2); assertThrows(UserRevertedException.class, () -> loanTaker1.borrowFrom("sICX", debt2)); diff --git a/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/LoansImpl.java b/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/LoansImpl.java index dc8b951be..3f44a7ddd 100644 --- a/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/LoansImpl.java +++ b/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/LoansImpl.java @@ -23,6 +23,7 @@ import network.balanced.score.core.loans.debt.DebtDB; import network.balanced.score.core.loans.positions.Position; import network.balanced.score.core.loans.positions.PositionsDB; +import network.balanced.score.core.loans.utils.LoansConstants.Standings; import network.balanced.score.core.loans.utils.PositionBatch; import network.balanced.score.core.loans.utils.TokenUtils; import network.balanced.score.lib.interfaces.Loans; @@ -47,13 +48,15 @@ import java.util.List; import java.util.Map; -import static network.balanced.score.core.loans.LoansVariables.loansOn; import static network.balanced.score.core.loans.LoansVariables.*; import static network.balanced.score.core.loans.utils.Checks.loansOn; import static network.balanced.score.core.loans.utils.LoansConstants.*; import static network.balanced.score.lib.utils.BalancedAddressManager.*; import static network.balanced.score.lib.utils.Check.*; import static network.balanced.score.lib.utils.Constants.EOA_ZERO; +import static network.balanced.score.lib.utils.Constants.EXA; +import static network.balanced.score.lib.utils.Constants.MICRO_SECONDS_IN_A_DAY; +import static network.balanced.score.lib.utils.Constants.POINTS; import static network.balanced.score.lib.utils.Math.convertToNumber; import static network.balanced.score.lib.utils.Math.pow; @@ -267,10 +270,15 @@ public void xTokenFallback(String _from, BigInteger _value, byte[] _data) { if (token.equals(getBnusd())) { String collateralSymbol = json.get("_collateral").asString(); JsonValue withdrawAmount = json.get("_withdrawAmount"); + String to = json.getString("_to", _from); + if (to.equals("")) { + to = _from; + } + BigInteger collateralToWithdraw = convertToNumber(withdrawAmount, BigInteger.ZERO); TokenUtils.burnAsset(_value); - _returnAsset(_from, _value, collateralSymbol); - if (BigInteger.ZERO.compareTo(collateralToWithdraw) < 0) { + _returnAsset(to, _value, collateralSymbol); + if (BigInteger.ZERO.compareTo(collateralToWithdraw) < 0 && to.equals(_from)) { xWithdraw(_from, collateralToWithdraw, collateralSymbol); } } else { @@ -280,7 +288,7 @@ public void xTokenFallback(String _from, BigInteger _value, byte[] _data) { depositCollateral(collateralSymbol, _value, _from); if (BigInteger.ZERO.compareTo(requestedAmount) < 0) { - originateLoan(collateralSymbol, requestedAmount, _from); + originateLoan(collateralSymbol, requestedAmount, _from, null, new byte[0]); } } } @@ -317,7 +325,7 @@ public void tokenFallback(Address _from, BigInteger _value, byte[] _data) { depositCollateral(collateralSymbol, _value, _from.toString()); if (BigInteger.ZERO.compareTo(requestedAmount) < 0) { - originateLoan(collateralSymbol, requestedAmount, _from.toString()); + originateLoan(collateralSymbol, requestedAmount, _from.toString(), null, new byte[0]); } } @@ -329,8 +337,8 @@ public void handleCallMessage(String _from, byte[] _data, @Optional String[] _pr LoansXCall.process(this, _from, _data); } - public void xBorrow(String from, String _collateralToBorrowAgainst, BigInteger _amountToBorrow) { - originateLoan(_collateralToBorrowAgainst, _amountToBorrow, from); + public void xBorrow(String from, String _collateralToBorrowAgainst, BigInteger _amountToBorrow, String _to, byte[] _data) { + originateLoan(_collateralToBorrowAgainst, _amountToBorrow, from, _to, _data); } public void xWithdraw(String from, BigInteger _value, String _collateralSymbol) { @@ -351,13 +359,13 @@ private boolean canWithdraw(String net) { } @External - public void borrow(String _collateralToBorrowAgainst, String _assetToBorrow, BigInteger _amountToBorrow) { + public void borrow(String _collateralToBorrowAgainst, String _assetToBorrow, BigInteger _amountToBorrow, @Optional String _to, @Optional byte[] _data) { checkStatus(); loansOn(); Context.require(_amountToBorrow.compareTo(BigInteger.ZERO) > 0, TAG + ": _amountToBorrow needs to be larger " + "than 0"); Context.require(_assetToBorrow.equals(BNUSD_SYMBOL)); - originateLoan(_collateralToBorrowAgainst, _amountToBorrow, Context.getCaller().toString()); + originateLoan(_collateralToBorrowAgainst, _amountToBorrow, Context.getCaller().toString(), _to, _data); } @External @@ -381,7 +389,7 @@ public void depositAndBorrow(@Optional String _asset, @Optional BigInteger _amou return; } - originateLoan(SICX_SYMBOL, _amount, depositor); + originateLoan(SICX_SYMBOL, _amount, depositor, null, new byte[0]); } @External @@ -444,14 +452,18 @@ public void retireBadDebtForCollateral(String _symbol, BigInteger _value, String } @External - public void returnAsset(String _symbol, BigInteger _value, @Optional String _collateralSymbol) { + public void returnAsset(String _symbol, BigInteger _value, @Optional String _collateralSymbol, @Optional String to) { Address from = Context.getCaller(); + if (to == null || to.equals("") ){ + to = from.toString(); + } + Context.require(_symbol.equals(BNUSD_SYMBOL)); String collateralSymbol = optionalDefault(_collateralSymbol, SICX_SYMBOL); Context.require(TokenUtils.balanceOf(getBnusd(), from).compareTo(_value) >= 0, TAG + ": Insufficient balance."); TokenUtils.burnAssetFrom(from, _value); - _returnAsset(from.toString(), _value, collateralSymbol); + _returnAsset(to, _value, collateralSymbol); } @@ -755,7 +767,7 @@ private void removeCollateral(String from, BigInteger value, String collateralSy } - private void originateLoan(String collateralSymbol, BigInteger amount, String from) { + private void originateLoan(String collateralSymbol, BigInteger amount, String from, String to, byte[] data) { DebtDB.applyInterest(collateralSymbol); Position position = PositionsDB.getPosition(from); @@ -788,22 +800,30 @@ private void originateLoan(String collateralSymbol, BigInteger amount, String fr position.setDebt(collateralSymbol, holdings.add(newDebt)); Context.call(getRewards(), "updateBalanceAndSupply", "Loans", DebtDB.getTotalDebt(), from.toString(), position.getTotalDebt()); - originateBnUSD(from, amount, fee); + originateBnUSD(from, amount, fee, to, data); } - private void originateBnUSD(String from, BigInteger amount, BigInteger fee) { + private void originateBnUSD(String from, BigInteger amount, BigInteger fee, String to, byte[] data) { TokenUtils.mintAsset(amount); - if (from.contains("/")) { - String net = NetworkAddress.valueOf(from).net(); + if (to == null || to.equals("")) { + to = from; + } + + if (data == null) { + data = new byte[0]; + } + + if (to.contains("/")) { + String net = NetworkAddress.valueOf(to).net(); boolean canWithdraw = Context.call(Boolean.class, getDaofund(), "getXCallFeePermission", Context.getAddress(), net); if (canWithdraw) { BigInteger xCallFee = Context.call(BigInteger.class, getDaofund(), "claimXCallFee", net, true); - TokenUtils.crossTransfer(xCallFee, from, amount); + TokenUtils.crossTransfer(xCallFee, to, amount, data); } else { - TokenUtils.hubTransfer(from, amount); + TokenUtils.hubTransfer(to, amount, data); } } else { - TokenUtils.transfer(Address.fromString(from), amount); + TokenUtils.transfer(Address.fromString(to), amount, data); } String logMessage = "Loan of " + amount + " " + BNUSD_SYMBOL + " from Balanced."; OriginateLoan(from, BNUSD_SYMBOL, amount, logMessage); diff --git a/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/utils/TokenUtils.java b/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/utils/TokenUtils.java index d489e5c19..98dfa366e 100644 --- a/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/utils/TokenUtils.java +++ b/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/utils/TokenUtils.java @@ -57,15 +57,15 @@ public static void burnAssetFrom(Address from, BigInteger amount) { Context.call(getBnusd(), "burnFrom", from, amount); } - public static void crossTransfer(BigInteger fee, String to, BigInteger amount) { - Context.call(fee, getBnusd(), "crossTransfer", to, amount, new byte[0]); + public static void crossTransfer(BigInteger fee, String to, BigInteger amount, byte[] data) { + Context.call(fee, getBnusd(), "crossTransfer", to, amount, data); } - public static void hubTransfer(String to, BigInteger amount) { - Context.call(getBnusd(), "hubTransfer", to, amount, new byte[0]); + public static void hubTransfer(String to, BigInteger amount, byte[] data) { + Context.call(getBnusd(), "hubTransfer", to, amount, data); } - public static void transfer(Address to, BigInteger amount) { - Context.call(getBnusd(), "transfer", to, amount, new byte[0]); + public static void transfer(Address to, BigInteger amount, byte[] data) { + Context.call(getBnusd(), "transfer", to, amount, data); } } diff --git a/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTest.java b/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTest.java index 531f7bf97..5f69a9d9d 100644 --- a/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTest.java +++ b/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTest.java @@ -412,7 +412,7 @@ void borrow_sICX() { takeLoanSICX(account, collateral, BigInteger.ZERO); // Act - loans.invoke(account, "borrow", "sICX", "bnUSD", loan); + loans.invoke(account, "borrow", "sICX", "bnUSD", loan, "", new byte[0]); // Assert Map position = (Map) loans.call("getAccountPositions", account.getAddress().toString()); Map> assetHoldings = (Map>) position.get( @@ -455,13 +455,13 @@ void borrow_differentLockingRatios() { loans.invoke(governance.account, "setLockingRatio", "iETH", iETHLockingRatio); // Assert - Executable borrowSICX = () -> loans.invoke(account, "borrow", "sICX", "bnUSD", sICXMaxLoan.add(BigInteger.ONE)); - Executable borrowIETH = () -> loans.invoke(account, "borrow", "iETH", "bnUSD", iETHMaxLoan.add(BigInteger.ONE)); + Executable borrowSICX = () -> loans.invoke(account, "borrow", "sICX", "bnUSD", sICXMaxLoan.add(BigInteger.ONE), "", new byte[0]); + Executable borrowIETH = () -> loans.invoke(account, "borrow", "iETH", "bnUSD", iETHMaxLoan.add(BigInteger.ONE), "", new byte[0]); expectErrorMessage(borrowSICX, "collateral is insufficient to originate a loan of"); expectErrorMessage(borrowIETH, "collateral is insufficient to originate a loan of"); - loans.invoke(account, "borrow", "sICX", "bnUSD", sICXMaxLoan); - loans.invoke(account, "borrow", "iETH", "bnUSD", iETHMaxLoan); + loans.invoke(account, "borrow", "sICX", "bnUSD", sICXMaxLoan, "", new byte[0]); + loans.invoke(account, "borrow", "iETH", "bnUSD", iETHMaxLoan, "", new byte[0]); verifyTotalDebt(iETHMaxDebt.add(sICXMaxDebt).divide(EXA)); } @@ -478,7 +478,7 @@ void borrow_iETH() { takeLoaniETH(account, collateral, BigInteger.ZERO); // Act - loans.invoke(account, "borrow", "iETH", "bnUSD", loan); + loans.invoke(account, "borrow", "iETH", "bnUSD", loan, "", new byte[0]); // Assert Map position = (Map) loans.call("getAccountPositions", account.getAddress().toString()); @@ -507,11 +507,11 @@ void borrow_withDebtCeilings_toLow() { // Act & Assert String expectedErrorMessage = "BalancedLoansPositions: Cannot mint more bnUSD on collateral iETH"; - Executable overDebtCeilingiETH = () -> loans.invoke(account, "borrow", "iETH", "bnUSD", iETHLoan); + Executable overDebtCeilingiETH = () -> loans.invoke(account, "borrow", "iETH", "bnUSD", iETHLoan, "", new byte[0]); expectErrorMessage(overDebtCeilingiETH, expectedErrorMessage); expectedErrorMessage = "BalancedLoansPositions: Cannot mint more bnUSD on collateral sICX"; - Executable overDebtCeilingsICX = () -> loans.invoke(account, "borrow", "sICX", "bnUSD", sICXLoan); + Executable overDebtCeilingsICX = () -> loans.invoke(account, "borrow", "sICX", "bnUSD", sICXLoan, "", new byte[0]); expectErrorMessage(overDebtCeilingsICX, expectedErrorMessage); } @@ -533,8 +533,8 @@ void borrow_withDebtCeilings() { loans.invoke(governance.account, "setDebtCeiling", "sICX", sICXLoan.add(sICXExpectedFee)); // Act - loans.invoke(account, "borrow", "iETH", "bnUSD", iETHLoan); - loans.invoke(account, "borrow", "sICX", "bnUSD", sICXLoan); + loans.invoke(account, "borrow", "iETH", "bnUSD", iETHLoan, "", new byte[0]); + loans.invoke(account, "borrow", "sICX", "bnUSD", sICXLoan, "", new byte[0]); // Assert Map position = (Map) loans.call("getAccountPositions", account.getAddress().toString()); @@ -626,8 +626,8 @@ void borrow_fromWrongCollateral() { loan + " bnUSD when max_debt_value = 0," + " new_debt_value = " + loan.add(expectedFee) + ", which includes a fee of " + expectedFee + " bnUSD, given an existing loan value of 0."; - Executable returnToMuch = () -> loans.invoke(account, "borrow", "iETH", "bnUSD", loan); - expectErrorMessage(returnToMuch, expectedErrorMessage); + Executable borrowWrong = () -> loans.invoke(account, "borrow", "iETH", "bnUSD", loan, "", new byte[0]); + expectErrorMessage(borrowWrong, expectedErrorMessage); // Assert @@ -659,7 +659,7 @@ void borrow_collateralWithoutLockingRation() throws Exception { // Act & Assert String expectedErrorMessage = "Reverted(0): Locking ratio for iBTC is not set"; - Executable loanWithoutLockingRatio = () -> loans.invoke(account, "borrow", "iBTC", "bnUSD", loan); + Executable loanWithoutLockingRatio = () -> loans.invoke(account, "borrow", "iBTC", "bnUSD", loan, "", new byte[0]); expectErrorMessage(loanWithoutLockingRatio, expectedErrorMessage); } @@ -674,7 +674,7 @@ void borrow_negativeAmount() { // Act & Assert String expectedErrorMessage = "_amountToBorrow needs to be larger than 0"; - Executable negativeLoan = () -> loans.invoke(account, "borrow", "sICX", "bnUSD", sICXLoan); + Executable negativeLoan = () -> loans.invoke(account, "borrow", "sICX", "bnUSD", sICXLoan, "", new byte[0]); expectErrorMessage(negativeLoan, expectedErrorMessage); } @@ -727,8 +727,8 @@ void returnAsset() { when(bnusd.mock.balanceOf(account.getAddress())).thenReturn(iETHLoan.add(loan)); // Act - loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, "sICX"); - loans.invoke(account, "returnAsset", "bnUSD", iETHloanToRepay, "iETH"); + loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, "sICX", ""); + loans.invoke(account, "returnAsset", "bnUSD", iETHloanToRepay, "iETH", ""); // Assert verify(bnusd.mock).burnFrom(account.getAddress(), loanToRepay); @@ -741,6 +741,31 @@ void returnAsset() { verifyTotalDebt(expectedTotal); } + @Test + void returnAsset_to() { + // Arrange + Account account = sm.createAccount(); + Account repayer = sm.createAccount(); + BigInteger collateral = BigInteger.valueOf(1000).multiply(EXA); + BigInteger loan = BigInteger.valueOf(200).multiply(EXA); + BigInteger expectedFee = calculateFee(loan); + + + BigInteger loanToRepay = BigInteger.valueOf(100).multiply(EXA); + + takeLoanICX(account, "bnUSD", collateral, loan); + when(bnusd.mock.balanceOf(repayer.getAddress())).thenReturn(loanToRepay); + + // Act + loans.invoke(repayer, "returnAsset", "bnUSD", loanToRepay, "sICX", account.getAddress().toString()); + + // Assert + verify(bnusd.mock).burnFrom(repayer.getAddress(), loanToRepay); + verifyPosition(account.getAddress(), collateral, loan.subtract(loanToRepay).add(expectedFee), "sICX"); + BigInteger expectedTotal = loan.subtract(loanToRepay).add(expectedFee); + verifyTotalDebt(expectedTotal); + } + @Test void returnAssetAndReopenPosition() { // Arrange @@ -751,7 +776,7 @@ void returnAssetAndReopenPosition() { takeLoanICX(account, "bnUSD", collateral, loan); when(bnusd.mock.balanceOf(account.getAddress())).thenReturn(expectedFee.add(loan)); - loans.invoke(account, "returnAsset", "bnUSD", loan.add(expectedFee), "sICX"); + loans.invoke(account, "returnAsset", "bnUSD", loan.add(expectedFee), "sICX", ""); // Assert assertFalse((boolean) loans.call("hasDebt", account.getAddress().toString())); @@ -782,7 +807,7 @@ void returnAsset_MoreThanHoldings() { takeLoanICX(account, "bnUSD", collateral, loan); // Assert & Act - Executable returnToMuch = () -> loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, "sICX"); + Executable returnToMuch = () -> loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, "sICX", ""); expectErrorMessage(returnToMuch, expectedErrorMessage); } @@ -800,7 +825,7 @@ void returnAsset_WrongCollateral() { when(bnusd.mock.balanceOf(account.getAddress())).thenReturn(loan); // Assert & Act - Executable returnForiETH = () -> loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, "iETH"); + Executable returnForiETH = () -> loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, "iETH", ""); expectErrorMessage(returnForiETH, expectedErrorMessage); } @@ -819,7 +844,7 @@ void returnAsset_InsufficientBalance() { // Assert & Act Executable returnWithInsufficientBalance = () -> loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, - "sICX"); + "sICX", ""); expectErrorMessage(returnWithInsufficientBalance, expectedErrorMessage); } @@ -834,7 +859,7 @@ void returnAsset_NoPosition() { "does not have a position in Balanced"; // Assert & Act - Executable returnWithNoPosition = () -> loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, "sICX"); + Executable returnWithNoPosition = () -> loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, "sICX", ""); expectErrorMessage(returnWithNoPosition, expectedErrorMessage); } @@ -858,7 +883,7 @@ void returnAsset_AlreadyAboveCeiling() { verifyTotalDebt(expectedDebt.multiply(BigInteger.TWO)); // Act - loans.invoke(loanRepayer, "returnAsset", "bnUSD", loanToRepay, "sICX"); + loans.invoke(loanRepayer, "returnAsset", "bnUSD", loanToRepay, "sICX", ""); // Assert verify(bnusd.mock).burnFrom(loanRepayer.getAddress(), loanToRepay); @@ -1210,7 +1235,7 @@ void liquidate_liquidationRatioNotSet() throws Exception { mockOraclePrice("iBTC", EXA); loans.invoke(iBTC.account, "tokenFallback", account.getAddress(), collateral, data.toString().getBytes()); - loans.invoke(account, "borrow", "iBTC", "bnUSD", loan); + loans.invoke(account, "borrow", "iBTC", "bnUSD", loan, "", new byte[0]); // Act & Assert String expectedErrorMessage = "Reverted(0): Liquidation ratio for iBTC is not set"; diff --git a/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestCrosschain.java b/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestCrosschain.java index e72a0efb0..57b09ad29 100644 --- a/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestCrosschain.java +++ b/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestCrosschain.java @@ -22,6 +22,9 @@ import network.balanced.score.lib.interfaces.tokens.SpokeToken; import network.balanced.score.lib.interfaces.tokens.SpokeTokenScoreInterface; import network.balanced.score.lib.test.mock.MockContract; +import score.ByteArrayObjectWriter; +import score.Context; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -38,6 +41,7 @@ @DisplayName("Loans Tests") class LoansTestCrosschain extends LoansTestBase { String BSC_NID = "0x3.BSC"; + String ETH_NID = "0x1.ETH"; MockContract bnb; String BNB_SYMBOL = "BNB"; @@ -69,6 +73,15 @@ protected void repayLoanBNB(NetworkAddress account, BigInteger loan, BigInteger loans.invoke(bnusd.account, "xTokenFallback", account.toString(), loan, params); } + protected void repayLoanToBNB(NetworkAddress account, BigInteger loan, BigInteger collateralToWithdraw, String to) { + JsonObject data = new JsonObject() + .add("_collateral", BNB_SYMBOL) + .add("_withdrawAmount", collateralToWithdraw.toString()) + .add("_to", to.toString()); + byte[] params = data.toString().getBytes(); + loans.invoke(bnusd.account, "xTokenFallback", account.toString(), loan, params); + } + @Test void xDepositCollateral() { // Arrange @@ -137,6 +150,25 @@ void xRepayDebt() { verifyPosition(user.toString(), collateral, expectedFee, BNB_SYMBOL); } + @Test + void xRepayDebt_to() { + // Arrange + NetworkAddress user = new NetworkAddress(BSC_NID, "0x1"); + NetworkAddress user2 = new NetworkAddress(ETH_NID, "0x1"); + BigInteger collateral = BigInteger.valueOf(1000).multiply(EXA); + BigInteger loan = BigInteger.valueOf(100).multiply(EXA); + BigInteger expectedFee = calculateFee(loan); + takeLoanBNB(user, collateral, loan); + + // Act + repayLoanToBNB(user2, loan, BigInteger.ZERO, user.toString()); + + // Assert + verify(bnusd.mock).burn(loan); + verify(bnb.mock, times(0)).hubTransfer(any(), any(), any()); + verifyPosition(user.toString(), collateral, expectedFee, BNB_SYMBOL); + } + @Test void xRepayDebtAndWithdraw() { // Arrange @@ -167,8 +199,15 @@ void xBorrow() { when(mockBalanced.daofund.mock.getXCallFeePermission(loans.getAddress(), BSC_NID)).thenReturn(true); when(mockBalanced.daofund.mock.claimXCallFee(BSC_NID, true)).thenReturn(BigInteger.TEN); + ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn"); + writer.beginList(3); + writer.write("xBorrow"); + writer.write(BNB_SYMBOL); + writer.write(loan); + writer.end(); + byte[] msg = writer.toByteArray(); + // Act - byte[] msg = LoansMessages.xBorrow(BNB_SYMBOL, loan); loans.invoke(mockBalanced.xCall.account, "handleCallMessage", user.toString(), msg, new String[0]); // Assert @@ -188,13 +227,34 @@ void xBorrow_withoutWithdraw() { when(mockBalanced.daofund.mock.getXCallFeePermission(loans.getAddress(), BSC_NID)).thenReturn(false); // Act - byte[] msg = LoansMessages.xBorrow(BNB_SYMBOL, loan); + byte[] msg = LoansMessages.xBorrow(BNB_SYMBOL, loan, "", new byte[0]); loans.invoke(mockBalanced.xCall.account, "handleCallMessage", user.toString(), msg, new String[0]); // Assert verify(bnusd.mock).hubTransfer(user.toString(), loan, new byte[0]); verifyPosition(user.toString(), collateral, loan.add(expectedFee), BNB_SYMBOL); } + @Test + void xBorrow_to() { + // Arrange + NetworkAddress user = new NetworkAddress(BSC_NID, "0x1"); + NetworkAddress to = new NetworkAddress(ETH_NID, "0x1"); + BigInteger collateral = BigInteger.valueOf(1000).multiply(EXA); + BigInteger loan = BigInteger.valueOf(100).multiply(EXA); + BigInteger expectedFee = calculateFee(loan); + takeLoanBNB(user, collateral, BigInteger.ZERO); + + when(mockBalanced.daofund.mock.getXCallFeePermission(loans.getAddress(), ETH_NID)).thenReturn(true); + when(mockBalanced.daofund.mock.claimXCallFee(ETH_NID, true)).thenReturn(BigInteger.TEN); + + // Act + byte[] msg = LoansMessages.xBorrow(BNB_SYMBOL, loan, to.toString(), "test".getBytes()); + loans.invoke(mockBalanced.xCall.account, "handleCallMessage", user.toString(), msg, new String[0]); + + // Assert + verify(bnusd.mock).crossTransfer(to.toString(), loan, "test".getBytes()); + verifyPosition(user.toString(), collateral, loan.add(expectedFee), BNB_SYMBOL); + } @Test void xWithdraw() { diff --git a/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestInterest.java b/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestInterest.java index 89cbb4d62..3d8f5da97 100644 --- a/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestInterest.java +++ b/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestInterest.java @@ -150,7 +150,7 @@ void returnAsset() { BigInteger debt = getUserDebt(account, "sICX"); when(bnusd.mock.balanceOf(account.getAddress())).thenReturn(debt); - loans.invoke(account, "returnAsset", "bnUSD", debt, "sICX"); + loans.invoke(account, "returnAsset", "bnUSD", debt, "sICX", ""); // Assert BigInteger interest = expectedDebt.multiply(SICX_INTEREST).multiply(timePassed.multiply(MICRO_SECONDS_IN_A_SECOND)) diff --git a/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestRewards.java b/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestRewards.java index 9fd3ae683..f2dc1ef83 100644 --- a/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestRewards.java +++ b/core-contracts/Loans/src/test/java/network/balanced/score/core/loans/LoansTestRewards.java @@ -121,7 +121,7 @@ void returnAsset() { when(bnusd.mock.balanceOf(account.getAddress())).thenReturn(loan.subtract(expectedFee)); // Act - loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, "sICX"); + loans.invoke(account, "returnAsset", "bnUSD", loanToRepay, "sICX", ""); // Assert verify(bnusd.mock).burnFrom(account.getAddress(), loanToRepay); @@ -156,8 +156,8 @@ void returnAsset_MultiCollateral() { when(bnusd.mock.balanceOf(account.getAddress())).thenReturn(sICXloan.add(iETHloan)); // Act - loans.invoke(account, "returnAsset", "bnUSD", sICXLoanToRepay, "sICX"); - loans.invoke(account, "returnAsset", "bnUSD", iETHLoanToRepay, "iETH"); + loans.invoke(account, "returnAsset", "bnUSD", sICXLoanToRepay, "sICX", ""); + loans.invoke(account, "returnAsset", "bnUSD", iETHLoanToRepay, "iETH", ""); // Assert BigInteger expectedTotalDebt = iETHDebt.add(sICXDebt).subtract(sICXLoanToRepay).subtract(iETHLoanToRepay); diff --git a/core-contracts/Rewards/src/intTest/java/network/balanced/score/core/rewards/RewardsIntegrationTest.java b/core-contracts/Rewards/src/intTest/java/network/balanced/score/core/rewards/RewardsIntegrationTest.java index e4a0ece53..fa69bf9d0 100644 --- a/core-contracts/Rewards/src/intTest/java/network/balanced/score/core/rewards/RewardsIntegrationTest.java +++ b/core-contracts/Rewards/src/intTest/java/network/balanced/score/core/rewards/RewardsIntegrationTest.java @@ -79,8 +79,8 @@ void verifyRewards_Loans() throws Exception { // Act loanTaker1.bnUSD.transfer(loanTaker2.getAddress(), fee, null); - loanTaker2.loans.returnAsset("bnUSD", loanAmount.add(fee), "sICX"); - loanTaker3.loans.returnAsset("bnUSD", loanAmount.divide(BigInteger.TWO), "sICX"); + loanTaker2.loans.returnAsset("bnUSD", loanAmount.add(fee), "sICX", ""); + loanTaker3.loans.returnAsset("bnUSD", loanAmount.divide(BigInteger.TWO), "sICX", ""); loanTaker2.rewards.claimRewards(reader.rewards.getUserSources(loanTaker2.getAddress().toString())); loanTaker3.rewards.claimRewards(reader.rewards.getUserSources(loanTaker3.getAddress().toString())); @@ -90,7 +90,7 @@ void verifyRewards_Loans() throws Exception { verifyRewards(loanTaker3); // Act - loanTaker2.loans.borrow("sICX", "bnUSD", loanAmount); + loanTaker2.loans.borrow("sICX", "bnUSD", loanAmount, "", null); // Assert verifyRewards(loanTaker1); diff --git a/score-lib/src/main/java/network/balanced/score/lib/interfaces/Loans.java b/score-lib/src/main/java/network/balanced/score/lib/interfaces/Loans.java index ec583adb7..61467c592 100644 --- a/score-lib/src/main/java/network/balanced/score/lib/interfaces/Loans.java +++ b/score-lib/src/main/java/network/balanced/score/lib/interfaces/Loans.java @@ -78,7 +78,7 @@ public interface Loans extends Name, AddressManager, Version, XTokenReceiver, Fl BigInteger getBnusdValue(String _name); @External - void borrow(String _collateralToBorrowAgainst, String _assetToBorrow, BigInteger _amountToBorrow); + void borrow(String _collateralToBorrowAgainst, String _assetToBorrow, BigInteger _amountToBorrow, @Optional String _to, @Optional byte[] _data); @External @Payable @@ -92,7 +92,7 @@ void depositAndBorrow(@Optional String _asset, @Optional BigInteger _amount, @Op void retireBadDebtForCollateral(String _symbol, BigInteger _value, String _collateralSymbol); @External - void returnAsset(String _symbol, BigInteger _value, @Optional String _collateralSymbol); + void returnAsset(String _symbol, BigInteger _value, @Optional String _collateralSymbol, @Optional String to); @External void withdrawAndUnstake(BigInteger _value); @@ -191,7 +191,7 @@ void depositAndBorrow(@Optional String _asset, @Optional BigInteger _amount, @Op Map getParameters(); @XCall - void xBorrow(String from, String _collateralToBorrowAgainst, BigInteger _amountToBorrow); + void xBorrow(String from, String _collateralToBorrowAgainst, BigInteger _amountToBorrow, @Optional String _to, @Optional byte[] _data); @XCall void xWithdraw(String from, BigInteger _value, String _collateralSymbol); diff --git a/test-lib/src/main/java/network/balanced/score/lib/test/integration/BalancedClient.java b/test-lib/src/main/java/network/balanced/score/lib/test/integration/BalancedClient.java index 42c889bfa..a2f2a0599 100644 --- a/test-lib/src/main/java/network/balanced/score/lib/test/integration/BalancedClient.java +++ b/test-lib/src/main/java/network/balanced/score/lib/test/integration/BalancedClient.java @@ -133,7 +133,7 @@ public void depositAndBorrow(Address collateralAddress, BigInteger collateral, B } public void borrowFrom(String collateral, BigInteger amount) { - loans.borrow(collateral, "bnUSD", amount); + loans.borrow(collateral, "bnUSD", amount, null, null); } @SuppressWarnings("unchecked") diff --git a/xcall-annotations/src/main/java/network/balanced/score/lib/annotations/XCallProcessor.java b/xcall-annotations/src/main/java/network/balanced/score/lib/annotations/XCallProcessor.java index 971393193..0f6ea1e35 100644 --- a/xcall-annotations/src/main/java/network/balanced/score/lib/annotations/XCallProcessor.java +++ b/xcall-annotations/src/main/java/network/balanced/score/lib/annotations/XCallProcessor.java @@ -166,28 +166,37 @@ private TypeSpec processorTypeSpec(ClassName elementClassName, ClassName classNa throw new RuntimeException("First parameter in a XCall must be the from parameter, (String from)"); } - handleMethod.addCode("case $S: \n", methodName.toString().toLowerCase()); - handleMethod.addCode("$> score." + methodName + "(from"); + handleMethod.addCode("case $S:{ \n", methodName.toString().toLowerCase()); + handleMethod.addCode("$>"); + for (int i = 1; i < parameters.size(); i++) { + TypeMirror type = parameters.get(i).asType(); + String name = parameters.get(i).getSimpleName().toString(); + handleMethod.addStatement("$T $L", type, name); + } for (int i = 1; i < parameters.size(); i++) { - String nullable = ""; - if (parameters.get(i).getAnnotation(Optional.class) != null) { - nullable = "Nullable"; - } TypeMirror type = parameters.get(i).asType(); - if (type.getKind() == TypeKind.ARRAY) { - if (((ArrayType)type).getComponentType().toString().equals("java.lang.String")) { - handleMethod.addCode(", $T.readStringArray(reader)", RLPUtils.class); + String name = parameters.get(i).getSimpleName().toString(); + if (isStringArray(type)) { + handleMethod.addStatement("$L = $T.readStringArray(reader)", name, RLPUtils.class); + } else { + if (parameters.get(i).getAnnotation(Optional.class) != null) { + handleMethod.addStatement("$L = reader.hasNext() ? reader.readNullable($T.class): null", name, type); } else { - handleMethod.addCode(", reader.read$L($T.class)", nullable, type); + handleMethod.addStatement("$L = reader.read($T.class)", name, type); } - } else { - handleMethod.addCode(", reader.read$L($T.class)", nullable, type); } } - handleMethod.addCode(");\n$<"); - handleMethod.addStatement("$>break$<"); + handleMethod.addCode("score." + methodName + "(from"); + for (int i = 1; i < parameters.size(); i++) { + handleMethod.addCode(", " + parameters.get(i).getSimpleName().toString()); + } + + handleMethod.addCode(");\n"); + handleMethod.addCode("break; $<\n"); + handleMethod.addCode("} "); + } @@ -199,6 +208,9 @@ private TypeSpec processorTypeSpec(ClassName elementClassName, ClassName classNa return builder.build(); } + private boolean isStringArray(TypeMirror type) { + return type.getKind() == TypeKind.ARRAY && ((ArrayType)type).getComponentType().toString().equals("java.lang.String"); + } private TypeSpec messagesTypeSpec(ClassName className, List elements) { TypeSpec.Builder builder = TypeSpec .classBuilder(className) From 36cd1e0e7897dbc4525883b62fe1cde5ed4caf8e Mon Sep 17 00:00:00 2001 From: AntonAndell Date: Wed, 12 Jun 2024 10:21:20 +0200 Subject: [PATCH 2/2] bump loans version --- .../main/java/network/balanced/score/lib/utils/Versions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/score-lib/src/main/java/network/balanced/score/lib/utils/Versions.java b/score-lib/src/main/java/network/balanced/score/lib/utils/Versions.java index 243dfdfb8..6e1ece716 100644 --- a/score-lib/src/main/java/network/balanced/score/lib/utils/Versions.java +++ b/score-lib/src/main/java/network/balanced/score/lib/utils/Versions.java @@ -19,7 +19,7 @@ public class Versions { public final static String BALN = "v1.0.2"; public final static String DIVIDENDS = "v1.0.0"; - public final static String LOANS = "v1.1.1"; + public final static String LOANS = "v1.1.2"; public final static String RESERVE = "v1.0.0"; public final static String SICX = "v1.0.1"; public final static String STAKING = "v1.0.1";