From 87b526ce029a67ec617d10cb331220bdac6732f2 Mon Sep 17 00:00:00 2001 From: AntonAndell Date: Mon, 7 Jul 2025 10:27:23 +0200 Subject: [PATCH] feat: add logic to close to balanced Dao --- .../score/core/governance/GovernanceImpl.java | 21 +++++++++ .../core/governance/GovernanceVotingTest.java | 20 +++++++++ .../balanced/score/core/loans/LoansImpl.java | 2 + .../score/core/loans/debt/DebtDB.java | 5 +++ .../score/core/loans/LoansTestInterest.java | 30 +++++++++++++ .../score/core/rewards/RewardsImpl.java | 6 +++ .../core/rewards/RewardsTestRewards.java | 45 +++++++++++++++++++ .../score/lib/interfaces/Governance.java | 4 ++ .../score/tokens/BoostedBalnImpl.java | 5 +++ .../score/tokens/StateMachineTest.java | 19 +++++++- .../score/util/trickler/TricklerImpl.java | 15 +++++++ 11 files changed, 170 insertions(+), 2 deletions(-) diff --git a/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/GovernanceImpl.java b/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/GovernanceImpl.java index 534541bb7..bf5c76514 100644 --- a/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/GovernanceImpl.java +++ b/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/GovernanceImpl.java @@ -54,6 +54,7 @@ public class GovernanceImpl implements Governance { public static final VarDB bnusdVoteDefinitionFee = Context.newVarDB(DEFINITION_FEE, BigInteger.class); public static final VarDB quorum = Context.newVarDB(QUORUM, BigInteger.class); private final VarDB currentVersion = Context.newVarDB(VERSION, String.class); + public static final VarDB isShutdown = Context.newVarDB("IS_SHUTDOWN", Boolean.class); public GovernanceImpl() { if (launched.getOrDefault(null) == null) { @@ -95,6 +96,23 @@ public void changeScoreOwner(Address score, Address newOwner) { Context.call(SYSTEM_SCORE_ADDRESS, "setScoreOwner", score, newOwner); } + @External + public void setIsShutdown(boolean _isShutdown) { + onlyOwnerOrContract(); + isShutdown.set(_isShutdown); + } + + @External(readonly = true) + public boolean getIsShutdown() { + return isShutdown.getOrDefault(false); + } + + protected void checkShutdownPermission() { + if (isShutdown.getOrDefault(false)) { + onlyOwnerOrContract(); + } + } + @External public void setVoteDurationLimits(BigInteger min, BigInteger max) { onlyOwnerOrContract(); @@ -167,12 +185,14 @@ public BigInteger getBalnVoteDefinitionCriterion() { @External public void defineVote(String name, String description, BigInteger vote_start, BigInteger duration, String forumLink, @Optional String transactions) { + checkShutdownPermission(); transactions = optionalDefault(transactions, "[]"); ProposalManager.defineVote(name, description, vote_start, duration, forumLink, transactions); } @External public void cancelVote(BigInteger vote_index) { + checkShutdownPermission(); ProposalManager.cancelVote(vote_index); } @@ -201,6 +221,7 @@ public void castVote(BigInteger vote_index, boolean vote) { @External public void evaluateVote(BigInteger vote_index) { + checkShutdownPermission(); ProposalManager.evaluateVote(vote_index); } diff --git a/core-contracts/Governance/src/test/java/network/balanced/score/core/governance/GovernanceVotingTest.java b/core-contracts/Governance/src/test/java/network/balanced/score/core/governance/GovernanceVotingTest.java index 1dcb47c43..c0b55feff 100644 --- a/core-contracts/Governance/src/test/java/network/balanced/score/core/governance/GovernanceVotingTest.java +++ b/core-contracts/Governance/src/test/java/network/balanced/score/core/governance/GovernanceVotingTest.java @@ -111,6 +111,26 @@ void defineVote() { assertEquals(ProposalStatus.STATUS[ProposalStatus.ACTIVE], vote.get("status")); } + @Test + void defineVote_isShutdown() { + // Arrange + Account account = sm.createAccount(); + BigInteger day = (BigInteger) governance.call("getDay"); + String name = "test"; + String forumLink = "https://gov.balanced.network/"; + String description = "test vote"; + BigInteger voteStart = day.add(BigInteger.TWO); + BigInteger voteDuration = BigInteger.TWO; + String actions = "[]"; + String expectedErrorMessage; + governance.invoke(owner, "setIsShutdown", true); + + Executable whenShutdown = () -> governance.invoke(account, "defineVote", name, description, voteStart, + voteDuration, forumLink, actions); + expectErrorMessage(whenShutdown, "SenderNotScoreOwnerOrContract"); + + } + @Test void cancelVote_Owner() { // Arrange 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 731d9e806..8ce3607dc 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 @@ -19,6 +19,8 @@ import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; + +import foundation.icon.xcall.NetworkAddress; import network.balanced.score.core.loans.collateral.CollateralDB; import network.balanced.score.core.loans.debt.DebtDB; import network.balanced.score.core.loans.positions.Position; diff --git a/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/debt/DebtDB.java b/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/debt/DebtDB.java index 079df5e86..220664b8b 100644 --- a/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/debt/DebtDB.java +++ b/core-contracts/Loans/src/main/java/network/balanced/score/core/loans/debt/DebtDB.java @@ -215,6 +215,11 @@ public static void claimInterest() { } accumulatedInterest.set(BigInteger.ZERO); + if (Context.call(Boolean.class, BalancedAddressManager.getGovernance(), "getIsShutdown")) { + TokenUtils.mintAssetTo(BalancedAddressManager.getFeehandler(), accumulatedAmount); + return; + } + BigInteger savingsRateAmount = savingsShare.get().multiply(accumulatedAmount).divide(POINTS); TokenUtils.mintAssetTo(BalancedAddressManager.getSavings(), savingsRateAmount); TokenUtils.mintAssetTo(BalancedAddressManager.getFeehandler(), accumulatedAmount.subtract(savingsRateAmount)); 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 c45ff2bb1..cc13f28e8 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 @@ -191,6 +191,36 @@ void claimInterest() { assertRoundedEquals(expectedDebt.add(interest), debt); } + @Test + void claimInterest_isShutdown() { + // Arrange + when(mockBalanced.governance.mock.getIsShutdown()).thenReturn(true); + + Account account = sm.createAccount(); + BigInteger collateral = BigInteger.valueOf(1000000).multiply(EXA); + BigInteger loan = BigInteger.valueOf(100000).multiply(EXA); + BigInteger expectedFee = calculateFee(loan); + BigInteger expectedDebt = loan.add(expectedFee); + BigInteger timePassed = BigInteger.valueOf(20000); + BigInteger savingsShare = BigInteger.valueOf(4000); + loans.invoke(mockBalanced.governance.account, "setSavingsRateShare", savingsShare); + + // Act + takeLoanICX(account, "bnUSD", collateral, loan); + sm.getBlock().increase(timePassed.longValue()/2); + loans.invoke(mockBalanced.governance.account, "applyInterest"); + BigInteger debt = getUserDebt(account, "sICX"); + loans.invoke(account, "claimInterest"); + + // Assert + BigInteger expectedInterest = expectedDebt.multiply(SICX_INTEREST).multiply(timePassed.multiply(MICRO_SECONDS_IN_A_SECOND)) + .divide(YEAR_IN_MICRO_SECONDS.multiply(POINTS)); + BigInteger interest = debt.subtract(expectedDebt); + assertTrue(expectedInterest.compareTo(EXA) > 0); + verify(mockBalanced.bnUSD.mock).mintTo(mockBalanced.feehandler.getAddress(), interest, new byte[0]); + assertRoundedEquals(expectedDebt.add(interest), debt); + } + @Test void permissions() { assertOnlyCallableBy(mockBalanced.governance.getAddress(), loans, "setSavingsRateShare", BigInteger.ONE); diff --git a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/RewardsImpl.java b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/RewardsImpl.java index 475da6a85..05565354c 100644 --- a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/RewardsImpl.java +++ b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/RewardsImpl.java @@ -355,6 +355,12 @@ public boolean distribute() { } private boolean mintAndAllocateBalnReward(BigInteger platformDay) { + if (Context.call(Boolean.class, BalancedAddressManager.getGovernance(), "getIsShutdown")) { + dailyVotableDistribution.set(platformDay, BigInteger.ZERO); + RewardsImpl.platformDay.set(platformDay.add(BigInteger.ONE)); + return false; + } + BigInteger distribution = dailyDistribution(platformDay); Context.call(getBaln(), "mint", distribution, new byte[0]); diff --git a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestRewards.java b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestRewards.java index a333bb356..0c9d0edc7 100644 --- a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestRewards.java +++ b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestRewards.java @@ -61,6 +61,26 @@ void distribute() { defaultPlatformDist.multiply(emission).divide(EXA), new byte[0]); } + @Test + void distribute_shutdown() { + // Act + when(mockBalanced.governance.mock.getIsShutdown()).thenReturn(true); + + sm.getBlock().increase(DAY); + syncDistributions(); + int day = ((BigInteger) rewardsScore.call("getDay")).intValue(); + + // Assert + BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); + + verify(baln.mock, times(0)).transfer(bwt.getAddress(), + BigInteger.ZERO, new byte[0]); + verify(baln.mock, times(0)).transfer(daoFund.getAddress(), + BigInteger.ZERO, new byte[0]); + verify(baln.mock, times(0)).transfer(reserve.getAddress(), + BigInteger.ZERO, new byte[0]); + } + @Test void claimRewards_updateRewardsData() { // Arrange @@ -101,6 +121,31 @@ void claimRewards_updateRewardsData() { verifyBalnReward(account.getAddress(), expectedRewards); } + + @Test + void claimRewards_updateRewardsData_isShutdown() { + // Arrange + Account account = sm.createAccount(); + BigInteger loansBalance = BigInteger.ONE.multiply(EXA); + BigInteger loansTotalSupply = BigInteger.TEN.multiply(EXA); + BigInteger swapBalance = BigInteger.TWO.multiply(EXA); + BigInteger swapTotalSupply = BigInteger.TEN.multiply(EXA); + when(mockBalanced.governance.mock.getIsShutdown()).thenReturn(true); + + // Act + mockBalanceAndSupply(loans, "Loans", account.getAddress(), loansBalance, loansTotalSupply); + mockBalanceAndSupply(dex, "sICX/ICX", account.getAddress(), swapBalance, swapTotalSupply); + rewardsScore.invoke(loans.account, "updateRewardsData", "Loans", loansTotalSupply.subtract(loansBalance), + account.getAddress(), BigInteger.ZERO); + rewardsScore.invoke(dex.account, "updateRewardsData", "sICX/ICX", swapTotalSupply.subtract(swapBalance), + account.getAddress(), BigInteger.ZERO); + + sm.getBlock().increase(DAY*2); + + // Assert + verify(baln.mock, times(0)).transfer(any(), any(), any()); + } + @Test void claimRewards_updateBalanceAndSupply() { // Arrange diff --git a/score-lib/src/main/java/network/balanced/score/lib/interfaces/Governance.java b/score-lib/src/main/java/network/balanced/score/lib/interfaces/Governance.java index 1ea28d013..1e5616149 100644 --- a/score-lib/src/main/java/network/balanced/score/lib/interfaces/Governance.java +++ b/score-lib/src/main/java/network/balanced/score/lib/interfaces/Governance.java @@ -40,9 +40,13 @@ public interface Governance extends Fallback, Version { + @External(readonly = true) BigInteger getDay(); + @External(readonly = true) + public boolean getIsShutdown(); + @External(readonly = true) Map getVotersCount(BigInteger vote_index); diff --git a/token-contracts/bBaln/src/main/java/network/balanced/score/tokens/BoostedBalnImpl.java b/token-contracts/bBaln/src/main/java/network/balanced/score/tokens/BoostedBalnImpl.java index 1ccc50597..386572f4d 100644 --- a/token-contracts/bBaln/src/main/java/network/balanced/score/tokens/BoostedBalnImpl.java +++ b/token-contracts/bBaln/src/main/java/network/balanced/score/tokens/BoostedBalnImpl.java @@ -243,6 +243,9 @@ public void withdrawEarly() { BigInteger maxPenalty = value.divide(BigInteger.TWO); BigInteger variablePenalty = balanceOf(sender, null); BigInteger penaltyAmount = variablePenalty.min(maxPenalty); + if (Context.call(Boolean.class, BalancedAddressManager.getGovernance(), "getIsShutdown")) { + penaltyAmount = BigInteger.ZERO; + } BigInteger returnAmount = value.subtract(penaltyAmount); LockedBalance oldLocked = locked.newLockedBalance(); @@ -255,8 +258,10 @@ public void withdrawEarly() { this.checkpoint(sender, oldLocked, locked); + if( penaltyAmount.compareTo(BigInteger.ZERO) > 0) { Context.call(getBaln(), "transfer", this.penaltyAddress.get(), penaltyAmount, "withdrawPenalty".getBytes()); + } Context.call(getBaln(), "transfer", sender, returnAmount, "withdrawEarly".getBytes()); users.remove(sender); diff --git a/token-contracts/bBaln/src/test/java/network/balanced/score/tokens/StateMachineTest.java b/token-contracts/bBaln/src/test/java/network/balanced/score/tokens/StateMachineTest.java index e6d394dec..bfdac942d 100644 --- a/token-contracts/bBaln/src/test/java/network/balanced/score/tokens/StateMachineTest.java +++ b/token-contracts/bBaln/src/test/java/network/balanced/score/tokens/StateMachineTest.java @@ -34,6 +34,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; @DisplayName("Statemachine Tests") public class StateMachineTest extends AbstractBoostedBalnTest { @@ -46,7 +47,7 @@ public class StateMachineTest extends AbstractBoostedBalnTest { private final ArrayList accounts = new ArrayList<>(); private final long MAXIMUM_LOCK_WEEKS = 208; private final long BLOCK_TIME = 2 * 1_000_000; - + protected MockBalanced mockBalanced; private Score bBalnScore; private BoostedBalnImpl scoreSpy; @@ -81,7 +82,7 @@ private void setupAccounts() { @BeforeEach public void setup() throws Exception { - MockBalanced mockBalanced = new MockBalanced(sm, owner); + mockBalanced = new MockBalanced(sm, owner); MockBalanced.addressManagerMock.when(() -> BalancedAddressManager.getBaln()).thenReturn(tokenScore.getAddress()); bBalnScore = sm.deploy(owner, BoostedBalnImpl.class, mockBalanced.governance.getAddress(), "bBALN"); @@ -434,6 +435,20 @@ void unlockEarlyBeforeExpiry_belowMaxPenalty() { tokenScore.invoke(penaltyAddress, "transfer", accounts.get(0).getAddress(), penalty, new byte[0]); } + @DisplayName("early before the expiry, when shutdown") + @Test + void unlockEarlyBeforeExpiry_isShutdown() { + when(mockBalanced.governance.mock.getIsShutdown()).thenReturn(true); + long increasedUnlockTime = addWeeksToCurrentTimestamp(180); + increaseUnlockTime(accounts.get(0), BigInteger.valueOf(increasedUnlockTime)); + bBalnScore.invoke(accounts.get(0), "withdrawEarly"); + + assertEquals(MINT_AMOUNT, tokenScore.call("balanceOf", + accounts.get(0).getAddress())); + votingBalances.put(accounts.get(0), new VotingBalance()); + + } + @DisplayName("early before the expiry, with max penalty") @Test void unlockEarlyBeforeExpiry_aboveMaxPenalty() { diff --git a/util-contracts/Trickler/src/main/java/network/balanced/score/util/trickler/TricklerImpl.java b/util-contracts/Trickler/src/main/java/network/balanced/score/util/trickler/TricklerImpl.java index 0448a93c4..1c6ed2917 100644 --- a/util-contracts/Trickler/src/main/java/network/balanced/score/util/trickler/TricklerImpl.java +++ b/util-contracts/Trickler/src/main/java/network/balanced/score/util/trickler/TricklerImpl.java @@ -114,6 +114,9 @@ public List
getAllowListTokens() { @External public void tokenFallback(Address _from, BigInteger _value, byte[] _data) { + if (Context.call(Boolean.class, BalancedAddressManager.getGovernance(), "getIsShutdown")) { + return; + } Address token = Context.getCaller(); Context.require(tokensAllowList.contains(token), Names.TRICKLER + ": Can't accept this token"); @@ -125,6 +128,9 @@ public void tokenFallback(Address _from, BigInteger _value, byte[] _data) { @External public void claimRewards(Address token) { + if (Context.call(Boolean.class, BalancedAddressManager.getGovernance(), "getIsShutdown")) { + return; + } Context.require(tokensAllowList.contains(token), Names.TRICKLER + ": Token is not listed"); BigInteger currentBlock = BigInteger.valueOf(Context.getBlockHeight()); BigInteger reward = getRewards(token); @@ -153,6 +159,9 @@ public void claimAllRewards() { @External(readonly = true) public BigInteger getRewards(Address token) { + if (Context.call(Boolean.class, BalancedAddressManager.getGovernance(), "getIsShutdown")) { + return BigInteger.ZERO; + } BigInteger currentBlock = BigInteger.valueOf(Context.getBlockHeight()); BigInteger currentRate = tokenBlockRewardRate.getOrDefault(token, BigInteger.ZERO); @@ -161,4 +170,10 @@ public BigInteger getRewards(Address token) { return reward; } + + @External + public void transfer(Address _tokenAddress, Address _targetAddress, BigInteger _amount) { + onlyOwner(); + Context.call(_tokenAddress, "transfer", _targetAddress, _amount, new byte[0]); + } } \ No newline at end of file