diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 2d7f10280..d3ff79637 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -36,7 +36,7 @@ jobs: run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Start local Blockchain - run: docker logout public.ecr.aws && docker-compose up -d + run: docker logout public.ecr.aws && docker compose up -d # - name: Check if all contracts are deployable # run: ./gradlew deployContractsToLocal --max-workers=2 diff --git a/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/utils/GovernanceConstants.java b/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/utils/GovernanceConstants.java index fa334aef4..e1c06c2d8 100644 --- a/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/utils/GovernanceConstants.java +++ b/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/utils/GovernanceConstants.java @@ -62,7 +62,6 @@ public class GovernanceConstants extends Constants { ); public static Map ADMIN_ADDRESSES = Map.ofEntries( - entry("rewards", "governance"), entry("baln", "rewards"), entry("bwt", "governance") ); diff --git a/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/utils/SetupManager.java b/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/utils/SetupManager.java index 66aa2da0e..b92e3d3a3 100644 --- a/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/utils/SetupManager.java +++ b/core-contracts/Governance/src/main/java/network/balanced/score/core/governance/utils/SetupManager.java @@ -18,7 +18,6 @@ import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonObject; -import network.balanced.score.lib.structs.DistributionPercentage; import network.balanced.score.lib.utils.Names; import score.Address; import score.Context; @@ -28,6 +27,7 @@ import static network.balanced.score.core.governance.GovernanceImpl.*; import static network.balanced.score.core.governance.utils.GovernanceConstants.*; +import static network.balanced.score.lib.utils.Constants.EXA; import static network.balanced.score.lib.utils.Math.pow; public class SetupManager { @@ -75,12 +75,16 @@ public static void launchBalanced() { _setTimeOffset(timeDelta); for (Map source : DATA_SOURCES) { - call(ContractManager.getAddress(Names.REWARDS), "addNewDataSource", source.get("name"), - ContractManager.get(source.get("address"))); + call(ContractManager.getAddress(Names.REWARDS), "createDataSource", source.get("name"), + ContractManager.get(source.get("address")), 0); } - call(ContractManager.getAddress(Names.REWARDS), "updateBalTokenDistPercentage", (Object) RECIPIENTS); - } + call(ContractManager.getAddress(Names.REWARDS), "setPlatformDistPercentage", Names.DAOFUND, BigInteger.valueOf(5).multiply(pow(BigInteger.TEN, 16))); + call(ContractManager.getAddress(Names.REWARDS), "setPlatformDistPercentage", Names.RESERVE, BigInteger.valueOf(5).multiply(pow(BigInteger.TEN, 16))); + call(ContractManager.getAddress(Names.REWARDS), "setPlatformDistPercentage", Names.WORKERTOKEN, BigInteger.valueOf(20).multiply(pow(BigInteger.TEN, 16))); + call(ContractManager.getAddress(Names.REWARDS), "setFixedSourcePercentage", "Loans", BigInteger.valueOf(20).multiply(pow(BigInteger.TEN, 16))); + call(ContractManager.getAddress(Names.REWARDS), "setFixedSourcePercentage", "sICX/ICX", BigInteger.TEN.multiply(pow(BigInteger.TEN, 16))); + }; public static void createBnusdMarket() { BigInteger value = Context.getValue(); @@ -115,16 +119,9 @@ public static void createBnusdMarket() { _addLPDataSource(name, pid); - DistributionPercentage[] recipients = new DistributionPercentage[]{ - createDistributionPercentage("Loans", BigInteger.valueOf(25).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("sICX/ICX", BigInteger.TEN.multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("Worker Tokens", BigInteger.valueOf(20).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("Reserve Fund", BigInteger.valueOf(5).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("DAOfund", BigInteger.valueOf(225).multiply(pow(BigInteger.TEN, 15))), - createDistributionPercentage("sICX/bnUSD", BigInteger.valueOf(175).multiply(pow(BigInteger.TEN, 15))) - }; + call(ContractManager.getAddress(Names.REWARDS), "setFixedSourcePercentage", "sICX/bnUSD", BigInteger.valueOf(15).multiply(pow(BigInteger.TEN, 16))); + - call(ContractManager.getAddress(Names.REWARDS), "updateBalTokenDistPercentage", (Object) recipients); } public static void createBalnMarket(BigInteger _bnUSD_amount, BigInteger _baln_amount) { @@ -149,18 +146,7 @@ public static void createBalnMarket(BigInteger _bnUSD_amount, BigInteger _baln_a call(dexAddress, "setMarketName", pid, name); _addLPDataSource(name, pid); - - DistributionPercentage[] recipients = new DistributionPercentage[]{ - createDistributionPercentage("Loans", BigInteger.valueOf(25).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("sICX/ICX", BigInteger.TEN.multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("Worker Tokens", BigInteger.valueOf(20).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("Reserve Fund", BigInteger.valueOf(5).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("DAOfund", BigInteger.valueOf(5).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("sICX/bnUSD", BigInteger.valueOf(175).multiply(pow(BigInteger.TEN, 15))), - createDistributionPercentage("BALN/bnUSD", BigInteger.valueOf(175).multiply(pow(BigInteger.TEN, 15))) - }; - - call(rewardsAddress, "updateBalTokenDistPercentage", (Object) recipients); + call(ContractManager.getAddress(Names.REWARDS), "setFixedSourcePercentage", "BALN/bnUSD", BigInteger.valueOf(15).multiply(pow(BigInteger.TEN, 16))); } public static void createBalnSicxMarket(BigInteger _sicx_amount, BigInteger _baln_amount) { @@ -184,23 +170,13 @@ public static void createBalnSicxMarket(BigInteger _sicx_amount, BigInteger _bal _addLPDataSource(name, pid); - DistributionPercentage[] recipients = new DistributionPercentage[]{ - createDistributionPercentage("Loans", BigInteger.valueOf(20).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("sICX/ICX", BigInteger.TEN.multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("Worker Tokens", BigInteger.valueOf(20).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("Reserve Fund", BigInteger.valueOf(5).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("DAOfund", BigInteger.valueOf(5).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("sICX/bnUSD", BigInteger.valueOf(15).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("BALN/bnUSD", BigInteger.valueOf(15).multiply(pow(BigInteger.TEN, 16))), - createDistributionPercentage("BALN/sICX", BigInteger.valueOf(10).multiply(pow(BigInteger.TEN, 16))) - }; + call(ContractManager.getAddress(Names.REWARDS), "setFixedSourcePercentage", "BALN/sICX", BigInteger.valueOf(10).multiply(pow(BigInteger.TEN, 16))); - call(ContractManager.getAddress(Names.REWARDS), "updateBalTokenDistPercentage", (Object) recipients); } private static void _addNewDataSource(String _data_source_name, String _contract_address) { - Context.call(ContractManager.getAddress(Names.REWARDS), "addNewDataSource", _data_source_name, - Address.fromString(_contract_address)); + Context.call(ContractManager.getAddress(Names.REWARDS), "createDataSource", _data_source_name, + Address.fromString(_contract_address), 0); } private static void _addLPDataSource(String _name, BigInteger _poolId) { diff --git a/core-contracts/Governance/src/test/java/network/balanced/score/core/governance/GovernanceTest.java b/core-contracts/Governance/src/test/java/network/balanced/score/core/governance/GovernanceTest.java index eaaf84447..1457fd852 100644 --- a/core-contracts/Governance/src/test/java/network/balanced/score/core/governance/GovernanceTest.java +++ b/core-contracts/Governance/src/test/java/network/balanced/score/core/governance/GovernanceTest.java @@ -212,10 +212,6 @@ void launchBalanced() { verify(rewards.mock).setTimeOffset(any(BigInteger.class)); verify(dex.mock).setTimeOffset(any(BigInteger.class)); - verify(rewards.mock).addNewDataSource("Loans", loans.getAddress()); - verify(rewards.mock).addNewDataSource("sICX/ICX", dex.getAddress()); - - verify(rewards.mock).updateBalTokenDistPercentage(any(DistributionPercentage[].class)); } @Test @@ -254,9 +250,7 @@ void createBnusdMarket() { verify(dex.mock).add(sicx.getAddress(), bnUSD.getAddress(), sICXValue, bnUSDValue, false, BigInteger.ZERO); verify(dex.mock).setMarketName(sicxBnusdPid, "sICX/bnUSD"); - verify(rewards.mock).addNewDataSource("sICX/bnUSD", stakedLp.getAddress()); verify(stakedLp.mock).addDataSource(sicxBnusdPid, "sICX/bnUSD"); - verify(rewards.mock, times(2)).updateBalTokenDistPercentage(any(DistributionPercentage[].class)); } @Test @@ -286,9 +280,7 @@ void createBalnMarket() { verify(dex.mock).add(baln.getAddress(), bnUSD.getAddress(), balnValue, bnUSDValue, false, BigInteger.ZERO); verify(dex.mock).setMarketName(balnBnusdPid, "BALN/bnUSD"); - verify(rewards.mock).addNewDataSource("BALN/bnUSD", stakedLp.getAddress()); verify(stakedLp.mock).addDataSource(balnBnusdPid, "BALN/bnUSD"); - verify(rewards.mock, times(3)).updateBalTokenDistPercentage(any(DistributionPercentage[].class)); } @Test @@ -320,9 +312,7 @@ void createBalnSicxMarket() { verify(dex.mock).add(baln.getAddress(), sicx.getAddress(), balnValue, sicxValue, false, BigInteger.ZERO); verify(dex.mock).setMarketName(balnSicxPid, "BALN/sICX"); - verify(rewards.mock).addNewDataSource("BALN/sICX", stakedLp.getAddress()); verify(stakedLp.mock).addDataSource(balnSicxPid, "BALN/sICX"); - verify(rewards.mock, times(4)).updateBalTokenDistPercentage(any(DistributionPercentage[].class)); } @Test diff --git a/core-contracts/Rewards/build.gradle b/core-contracts/Rewards/build.gradle index d8de9d358..5baf901b8 100644 --- a/core-contracts/Rewards/build.gradle +++ b/core-contracts/Rewards/build.gradle @@ -28,6 +28,7 @@ dependencies { compileOnly Dependencies.javaeeApi implementation Dependencies.javaeeScorex implementation project(':score-lib') + implementation Dependencies.minimalJson testImplementation Dependencies.javaeeUnitTest testImplementation Dependencies.junitJupiter @@ -35,6 +36,7 @@ dependencies { testImplementation Dependencies.mockitoCore testImplementation Dependencies.mockitoInline testImplementation project(':test-lib') + testImplementation Dependencies.json intTestAnnotationProcessor project(':score-client') intTestImplementation project(':score-client') 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..38a4d7ef1 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 @@ -34,12 +34,10 @@ import static network.balanced.score.lib.utils.Constants.*; import static org.junit.jupiter.api.Assertions.*; - class RewardsIntegrationTest implements ScoreIntegrationTest { private static Balanced balanced; private static BalancedClient owner; private static BalancedClient reader; - private static Map initialDistributions; @BeforeAll static void setup() throws Exception { @@ -48,9 +46,6 @@ static void setup() throws Exception { owner = balanced.ownerClient; reader = balanced.newClient(BigInteger.ZERO); - BigInteger platformDay = hexObjectToBigInteger(reader.rewards.distStatus().get("platform_day")); - BigInteger distributedDay = platformDay.subtract(BigInteger.ONE); - initialDistributions = reader.rewards.recipientAt(distributedDay); } @Test @@ -193,7 +188,8 @@ void boostRewards() throws Exception { BigInteger lockDays = BigInteger.valueOf(7).multiply(BigInteger.valueOf(3)); BigInteger lpBalance = BigInteger.TEN.pow(22); - BigInteger initialSupply = reader.dex.getBalanceAndSupply(sourceName, reader.getAddress().toString()).get("_totalSupply"); + BigInteger initialSupply = reader.dex.getBalanceAndSupply(sourceName, reader.getAddress().toString()) + .get("_totalSupply"); icxSicxLp.dex._transfer(balanced.dex._address(), lpBalance, null); icxSicxLpBoosted.dex._transfer(balanced.dex._address(), lpBalance, null); @@ -212,13 +208,14 @@ void boostRewards() throws Exception { // Assert verifyRewards(icxSicxLpBoosted); - Map currentWorkingBalanceAndSupply = reader.rewards.getWorkingBalanceAndSupply(sourceName - , icxSicxLpBoosted.getAddress().toString()); + Map currentWorkingBalanceAndSupply = reader.rewards.getWorkingBalanceAndSupply(sourceName, + icxSicxLpBoosted.getAddress().toString()); assertTrue(currentWorkingBalanceAndSupply.get("workingSupply").compareTo(initialSupply) > 0); assertTrue(currentWorkingBalanceAndSupply.get("workingBalance").compareTo(lpBalance) > 0); - Map> boostData = reader.rewards.getBoostData(icxSicxLpBoosted.getAddress().toString(), - new String[]{"sICX/ICX", "Loans"}); + Map> boostData = reader.rewards.getBoostData( + icxSicxLpBoosted.getAddress().toString(), + new String[] { "sICX/ICX", "Loans" }); assertEquals(currentWorkingBalanceAndSupply.get("workingSupply"), boostData.get("sICX/ICX").get( "workingSupply")); assertEquals(currentWorkingBalanceAndSupply.get("workingBalance"), boostData.get("sICX/ICX").get( @@ -240,77 +237,59 @@ void boostRewards() throws Exception { @Test @Order(20) - void changeRewardsDistributions() { + void changeRewardsDistributions() throws Exception { // Arrange - balanced.increaseDay(1); - owner.rewards.distribute((txr) -> { - }); - - BigInteger platformDay = hexObjectToBigInteger(reader.rewards.distStatus().get("platform_day")); - BigInteger distributedDay = platformDay.subtract(BigInteger.ONE); - BigInteger emission = reader.rewards.getEmission(distributedDay); - Map distributions = reader.rewards.recipientAt(distributedDay); - BigInteger loansDist = distributions.get("Loans"); - BigInteger sicxDist = distributions.get("sICX/ICX"); + BigInteger emission = reader.rewards.getEmission(BigInteger.ZERO); + Map> distributions = reader.rewards.getDistributionPercentages(); + BigInteger loansDist = distributions.get("Fixed").get("Loans"); + BigInteger sicxDist = distributions.get("Fixed").get("sICX/ICX"); BigInteger expectedLoansMint = loansDist.multiply(emission).divide(EXA); BigInteger expectedSicxMint = sicxDist.multiply(emission).divide(EXA); // Assert - BigInteger loansMint = - hexObjectToBigInteger(reader.rewards.getDataSourcesAt(distributedDay).get("Loans").get("total_dist")); - BigInteger sicxMint = - hexObjectToBigInteger(reader.rewards.getDataSourcesAt(distributedDay).get("sICX/ICX").get("total_dist" - )); + BigInteger loansMint = hexObjectToBigInteger(reader.rewards.getSourceData("Loans").get("total_dist")); + BigInteger sicxMint = hexObjectToBigInteger(reader.rewards.getSourceData("sICX/ICX").get("total_dist")); assertEquals(expectedLoansMint, loansMint); assertEquals(expectedSicxMint, sicxMint); // Act - BigInteger halfLoansDist = distributions.get("Loans").divide(BigInteger.TWO); - JsonArray distributionPercentages = new JsonArray() - .add(createDistributionPercentage("Loans", halfLoansDist)) - .add(createDistributionPercentage("sICX/ICX", distributions.get("sICX/ICX").add(halfLoansDist))) - .add(createDistributionPercentage("Worker Tokens", distributions.get("Worker Tokens"))) - .add(createDistributionPercentage("Reserve Fund", distributions.get("Reserve Fund"))) - .add(createDistributionPercentage("DAOfund", distributions.get("DAOfund"))) - .add(createDistributionPercentage("sICX/bnUSD", distributions.get("sICX/bnUSD"))) - .add(createDistributionPercentage("BALN/bnUSD", distributions.get("BALN/bnUSD"))) - .add(createDistributionPercentage("BALN/sICX", distributions.get("BALN/sICX"))); - - JsonArray updateBalTokenDistPercentage = new JsonArray() - .add(createParameter("Struct[]", distributionPercentages)); - + BigInteger halfLoansDist = loansDist.divide(BigInteger.TWO); + JsonArray updateLoansPercentage = new JsonArray() + .add(createParameter("Loans")) + .add(createParameter(halfLoansDist)); + JsonArray updateSICXICXPercentage = new JsonArray() + .add(createParameter("sICX/ICX")) + .add(createParameter(sicxDist.add(halfLoansDist))); JsonArray actions = new JsonArray() - .add(createTransaction(balanced.rewards._address(), "updateBalTokenDistPercentage", - updateBalTokenDistPercentage)); + .add(createTransaction(balanced.rewards._address(), "setFixedSourcePercentage", + updateLoansPercentage)) + .add(createTransaction(balanced.rewards._address(), "setFixedSourcePercentage", + updateSICXICXPercentage)); owner.governance.execute(actions.toString()); balanced.increaseDay(1); - distributedDay = distributedDay.add(BigInteger.ONE); owner.rewards.distribute((txr) -> { }); // Assert - expectedLoansMint = loansDist.subtract(halfLoansDist).multiply(emission).divide(EXA); + expectedLoansMint = halfLoansDist.multiply(emission).divide(EXA); expectedSicxMint = sicxDist.add(halfLoansDist).multiply(emission).divide(EXA); - loansMint = hexObjectToBigInteger(reader.rewards.getDataSourcesAt(distributedDay).get("Loans").get( - "total_dist")); - sicxMint = hexObjectToBigInteger(reader.rewards.getDataSourcesAt(distributedDay).get("sICX/ICX").get( - "total_dist")); + loansMint = hexObjectToBigInteger(reader.rewards.getSourceData("Loans").get("total_dist")); + sicxMint = hexObjectToBigInteger(reader.rewards.getSourceData("sICX/ICX").get("total_dist")); assertEquals(expectedLoansMint, loansMint); assertEquals(expectedSicxMint, sicxMint); + } @Test @Order(21) void removeRewardsDistributions() throws Exception { // Arrange - BigInteger platformDay = hexObjectToBigInteger(reader.rewards.distStatus().get("platform_day")); - BigInteger distributedDay = platformDay.subtract(BigInteger.ONE); - Map distributions = reader.rewards.recipientAt(distributedDay); - BigInteger loansDist = distributions.get("Loans"); - BigInteger icxDist = distributions.get("sICX/ICX"); + Map> distributions = reader.rewards.getDistributionPercentages(); + BigInteger loansDist = distributions.get("Fixed").get("Loans"); + BigInteger sicxDist = distributions.get("Fixed").get("sICX/ICX"); BalancedClient loanTaker = balanced.newClient(); BalancedClient icxSicxLP = balanced.newClient(); @@ -320,22 +299,17 @@ void removeRewardsDistributions() throws Exception { icxSicxLP.dex._transfer(balanced.dex._address(), BigInteger.TEN.pow(22), null); // Act - JsonArray distributionPercentages = new JsonArray() - .add(createDistributionPercentage("Loans", BigInteger.ZERO)) - .add(createDistributionPercentage("sICX/ICX", distributions.get("sICX/ICX").add(loansDist))) - .add(createDistributionPercentage("Worker Tokens", distributions.get("Worker Tokens"))) - .add(createDistributionPercentage("Reserve Fund", distributions.get("Reserve Fund"))) - .add(createDistributionPercentage("DAOfund", distributions.get("DAOfund"))) - .add(createDistributionPercentage("sICX/bnUSD", distributions.get("sICX/bnUSD"))) - .add(createDistributionPercentage("BALN/bnUSD", distributions.get("BALN/bnUSD"))) - .add(createDistributionPercentage("BALN/sICX", distributions.get("BALN/sICX"))); - - JsonArray updateBalTokenDistPercentage = new JsonArray() - .add(createParameter("Struct[]", distributionPercentages)); - + JsonArray updateLoansPercentage = new JsonArray() + .add(createParameter("Loans")) + .add(createParameter(BigInteger.ZERO)); + JsonArray updateSICXICXPercentage = new JsonArray() + .add(createParameter("sICX/ICX")) + .add(createParameter(sicxDist.add(loansDist))); JsonArray actions = new JsonArray() - .add(createTransaction(balanced.rewards._address(), "updateBalTokenDistPercentage", - updateBalTokenDistPercentage)); + .add(createTransaction(balanced.rewards._address(), "setFixedSourcePercentage", + updateLoansPercentage)) + .add(createTransaction(balanced.rewards._address(), "setFixedSourcePercentage", + updateSICXICXPercentage)); owner.governance.execute(actions.toString()); @@ -354,37 +328,26 @@ void removeRewardsDistributions() throws Exception { owner.rewards.distribute((txr) -> { }); verifyNoRewards(loanTaker); - } - - @Test - @Order(22) - void resetRewardsDistributions() throws Exception { - JsonArray distributionPercentages = new JsonArray() - .add(createDistributionPercentage("Loans", initialDistributions.get("Loans"))) - .add(createDistributionPercentage("sICX/ICX", initialDistributions.get("sICX/ICX"))) - .add(createDistributionPercentage("Worker Tokens", initialDistributions.get("Worker Tokens"))) - .add(createDistributionPercentage("Reserve Fund", initialDistributions.get("Reserve Fund"))) - .add(createDistributionPercentage("DAOfund", initialDistributions.get("DAOfund"))) - .add(createDistributionPercentage("sICX/bnUSD", initialDistributions.get("sICX/bnUSD"))) - .add(createDistributionPercentage("BALN/bnUSD", initialDistributions.get("BALN/bnUSD"))) - .add(createDistributionPercentage("BALN/sICX", initialDistributions.get("BALN/sICX"))); - - JsonArray updateBalTokenDistPercentage = new JsonArray() - .add(createParameter("Struct[]", distributionPercentages)); - JsonArray actions = new JsonArray() - .add(createTransaction(balanced.rewards._address(), "updateBalTokenDistPercentage", - updateBalTokenDistPercentage)); + // reset + updateLoansPercentage = new JsonArray() + .add(createParameter("Loans")) + .add(createParameter(BigInteger.valueOf(10).multiply(BigInteger.TEN.pow(16)))); + updateSICXICXPercentage = new JsonArray() + .add(createParameter("sICX/ICX")) + .add(createParameter(BigInteger.TEN.pow(17))); + actions = new JsonArray() + .add(createTransaction(balanced.rewards._address(), "setFixedSourcePercentage", + updateSICXICXPercentage)) + .add(createTransaction(balanced.rewards._address(), "setFixedSourcePercentage", + updateLoansPercentage)); owner.governance.execute(actions.toString()); - balanced.increaseDay(1); - owner.rewards.distribute((txr) -> { - }); } @Test @Order(30) - void migrate() throws Exception { + void voting() throws Exception { // Arrange BalancedClient borrower = balanced.newClient(BigInteger.TEN.pow(25)); BalancedClient sicxBnusdLP = balanced.newClient(); @@ -394,61 +357,60 @@ void migrate() throws Exception { borrower.bnUSD.transfer(sicxBnusdLP.getAddress(), lpAmount, null); joinsICXBnusdLP(sicxBnusdLP, lpAmount, lpAmount); stakeICXBnusdLP(sicxBnusdLP); + balanced.increaseDay(1); // Act - BigInteger platformDay = hexObjectToBigInteger(reader.rewards.distStatus().get("platform_day")); - - JsonArray setMigrateToVotingDayParameters = new JsonArray() - .add(createParameter(platformDay.add(BigInteger.ONE))); - JsonArray actions = createSingleTransaction(balanced.rewards._address(), "setMigrateToVotingDay", - setMigrateToVotingDayParameters); - + JsonArray updateSICXBnUSDPercentage = new JsonArray() + .add(createParameter("sICX/bnUSD")) + .add(createParameter(BigInteger.ZERO)); + JsonArray updateSICXICXPercentage = new JsonArray() + .add(createParameter("sICX/ICX")) + .add(createParameter(BigInteger.ZERO)); + JsonArray updateBalnBnUSDPercentage = new JsonArray() + .add(createParameter("BALN/bnUSD")) + .add(createParameter(BigInteger.ZERO)); + JsonArray updateBalnSICXPercentage = new JsonArray() + .add(createParameter("BALN/sICX")) + .add(createParameter(BigInteger.ZERO)); + JsonArray actions = new JsonArray() + .add(createTransaction(balanced.rewards._address(), "setFixedSourcePercentage", + updateSICXBnUSDPercentage)) + .add(createTransaction(balanced.rewards._address(), "setFixedSourcePercentage", + updateSICXICXPercentage)) + .add(createTransaction(balanced.rewards._address(), "setFixedSourcePercentage", + updateBalnBnUSDPercentage)) + .add(createTransaction(balanced.rewards._address(), "setFixedSourcePercentage", + updateBalnSICXPercentage)); owner.governance.execute(actions.toString()); // Assert verifyRewards(borrower); verifyRewards(sicxBnusdLP); - balanced.increaseDay(2); - verifyRewards(borrower); - verifyRewards(sicxBnusdLP); - - // SICX/bnUSD will no longer have a percentage since votes take a week to come into effect balanced.increaseDay(1); verifyRewards(borrower); verifyNoRewards(sicxBnusdLP); - JsonArray params = new JsonArray() - .add(createParameter("sICX/bnUSD")) - .add(createParameter(EXA.divide(BigInteger.TEN))); - actions = createSingleTransaction(balanced.rewards._address(), "setFixedSourcePercentage", params); - owner.governance.execute(actions.toString()); - - - balanced.increaseDay(1); - - verifyRewards(borrower); - verifyRewards(sicxBnusdLP); - // Arrange BigInteger availableBalnBalance = reader.baln.balanceOf(sicxBnusdLP.getAddress()); - long unlockTime = - (System.currentTimeMillis() * 1000) + (MICRO_SECONDS_IN_A_DAY.multiply(BigInteger.valueOf(400))).longValue(); + long unlockTime = (System.currentTimeMillis() * 1000) + + (MICRO_SECONDS_IN_A_DAY.multiply(BigInteger.valueOf(400))).longValue(); String data = "{\"method\":\"createLock\",\"params\":{\"unlockTime\":" + unlockTime + "}}"; sicxBnusdLP.baln.transfer(owner.boostedBaln._address(), availableBalnBalance, data.getBytes()); availableBalnBalance = reader.baln.balanceOf(borrower.getAddress()); borrower.baln.transfer(owner.boostedBaln._address(), availableBalnBalance, data.getBytes()); // Act & Assert + // Simple vote test we can't test further due to time constraints sicxBnusdLP.rewards.voteForSource("BALN/bnUSD", BigInteger.valueOf(5000)); sicxBnusdLP.rewards.voteForSource("BALN/sICX", BigInteger.valueOf(5000)); borrower.rewards.voteForSource("BALN/sICX", BigInteger.valueOf(10000)); - assertThrows(UserRevertedException.class, () -> - sicxBnusdLP.rewards.voteForSource("BALN/bnUSD", BigInteger.valueOf(2000))); + assertThrows(UserRevertedException.class, + () -> sicxBnusdLP.rewards.voteForSource("BALN/bnUSD", BigInteger.valueOf(2000))); - assertThrows(UserRevertedException.class, () -> - borrower.rewards.voteForSource("BALN/bnUSD", BigInteger.valueOf(1))); + assertThrows(UserRevertedException.class, + () -> borrower.rewards.voteForSource("BALN/bnUSD", BigInteger.valueOf(1))); } private void joinsICXBnusdLP(BalancedClient client, BigInteger icxAmount, BigInteger bnusdAmount) { diff --git a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/DataSourceDB.java b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/DataSourceDB.java index 35209a254..70278f42a 100644 --- a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/DataSourceDB.java +++ b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/DataSourceDB.java @@ -48,32 +48,6 @@ public static void newSource(String name, Address address) { names.add(name); DataSourceImpl dataSource = get(name); dataSource.setName(name); - dataSource.setDay(RewardsImpl.getDay()); dataSource.setContractAddress(address); } - - public static void removeSource(String name) { - // TODO Shouldn't be removed (Also add test cases) - // Avoid removing data source, must be disabled instead - // TODO Double check, remove one, return boolean - if (!contains(names, name)) { - return; - } - DataSourceImpl dataSource = get(name); - dataSource.setName(null); - dataSource.setDay(null); - dataSource.setContractAddress(null); - - // TODO Use helper method to remove from array db - String topSourceName = names.pop(); - if (topSourceName.equals(name)) { - return; - } - - for (int i = 0; i < names.size(); i++) { - if (names.get(i).equals(name)) { - names.set(i, topSourceName); - } - } - } } \ No newline at end of file diff --git a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/DataSourceImpl.java b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/DataSourceImpl.java index 1c1451c6d..6d4c15ea1 100644 --- a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/DataSourceImpl.java +++ b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/DataSourceImpl.java @@ -18,6 +18,7 @@ import network.balanced.score.core.rewards.utils.BalanceData; import network.balanced.score.lib.interfaces.DataSourceScoreInterface; +import network.balanced.score.lib.utils.BalancedAddressManager; import network.balanced.score.lib.utils.BranchedAddressDictDB; import score.*; import scorex.util.HashMap; @@ -33,13 +34,8 @@ public class DataSourceImpl { private final BranchDB> contractAddress = Context.newBranchDB("contract_address", Address.class); private final BranchDB> name = Context.newBranchDB("name", String.class); - private final BranchDB> day = Context.newBranchDB("day", BigInteger.class); - private final BranchDB> precomp = Context.newBranchDB("precomp", Boolean.class); - private final BranchDB> offset = Context.newBranchDB("offset", Integer.class); private final BranchDB> workingSupply = Context.newBranchDB("working_supply", BigInteger.class); - private final BranchDB> totalValue = Context.newBranchDB("total_value", - BigInteger.class); private final BranchDB> totalDist = Context.newBranchDB("total_dist", BigInteger.class); private final BranchDB> distPercent = Context.newBranchDB("dist_percent", @@ -47,13 +43,26 @@ public class DataSourceImpl { private final BranchedAddressDictDB userWeight = new BranchedAddressDictDB<>("user_weight", BigInteger.class); private final BranchedAddressDictDB userWorkingBalance = new BranchedAddressDictDB<>( - "user_working_balance", BigInteger.class); + "user_working_balance", BigInteger.class); private final BranchDB> lastUpdateTimeUs = Context.newBranchDB("last_update_us", BigInteger.class); private final BranchDB> totalWeight = Context.newBranchDB("running_total", BigInteger.class); - private final BranchDB> totalSupply = Context.newBranchDB("total_supply", + + // name -> token -> day -> amount + private final BranchDB>> externalDist = Context + .newBranchDB("external_total_dist", BigInteger.class); + private final BranchDB> userBalance = Context.newBranchDB("user_balance", + BigInteger.class); + private final BranchDB> totalSupply = Context.newBranchDB("total_supplyV2", BigInteger.class); + // name -> token -> user -> amount + private final BranchDB>> externalUserWeight = Context + .newBranchDB("external_user_weight", BigInteger.class); + // name -> token -> weight + private final BranchDB> externalTotalWeights = Context + .newBranchDB("external_running_total", BigInteger.class); + private final BranchDB> rewardTokens = Context.newBranchDB("reward_tokens", Address.class); private final String dbKey; @@ -77,14 +86,6 @@ public void setName(String name) { this.name.at(dbKey).set(name); } - public BigInteger getDay() { - return day.at(dbKey).getOrDefault(BigInteger.ZERO); - } - - public void setDay(BigInteger day) { - this.day.at(dbKey).set(day); - } - // Used for migration if it happens on Claim public BigInteger getWorkingSupply(boolean readonly) { BigInteger workingSupply = this.workingSupply.at(dbKey).get(); @@ -122,6 +123,14 @@ private void setWorkingSupply(BigInteger supply) { this.workingSupply.at(dbKey).set(supply); } + public BigInteger getTotalSupply() { + return totalSupply.at(dbKey).getOrDefault(BigInteger.ZERO); + } + + private void setTotalSupply(BigInteger supply) { + this.totalSupply.at(dbKey).set(supply); + } + // Used for migration if it happens on Claim public BigInteger getWorkingBalance(String user, boolean readonly) { BigInteger workingBalance = userWorkingBalance.at(dbKey).get(user); @@ -159,16 +168,12 @@ private void setWorkingBalance(String user, BigInteger balance) { this.userWorkingBalance.at(dbKey).set(user, balance); } - public Boolean getPrecomp() { - return precomp.at(dbKey).getOrDefault(false); + public BigInteger getBalance(String user) { + return userBalance.at(dbKey).getOrDefault(user, BigInteger.ZERO); } - public Integer getOffset() { - return offset.at(dbKey).getOrDefault(0); - } - - public BigInteger getTotalValue(BigInteger day) { - return totalValue.at(dbKey).getOrDefault(day, BigInteger.ZERO); + private void setBalance(String user, BigInteger balance) { + this.userBalance.at(dbKey).set(user, balance); } public BigInteger getTotalDist(BigInteger day, boolean readonly) { @@ -194,6 +199,14 @@ public void setTotalDist(BigInteger day, BigInteger value) { totalDist.at(dbKey).set(day, value); } + public BigInteger getTotalExternalDist(BigInteger day, Address token) { + return externalDist.at(dbKey).at(token).getOrDefault(day, BigInteger.ZERO); + } + + public void setTotalExternalDist(BigInteger day, Address token, BigInteger value) { + externalDist.at(dbKey).at(token).set(day, value); + } + public BigInteger getDistPercent() { return distPercent.at(dbKey).getOrDefault(BigInteger.ZERO); } @@ -206,6 +219,10 @@ public BigInteger getUserWeight(String user) { return userWeight.at(dbKey).getOrDefault(user, BigInteger.ZERO); } + public BigInteger getExternalUserWeight(String user, Address token) { + return externalUserWeight.at(dbKey).at(token).getOrDefault(user, BigInteger.ZERO); + } + public BigInteger getLastUpdateTimeUs() { return lastUpdateTimeUs.at(dbKey).getOrDefault(BigInteger.ZERO); } @@ -214,8 +231,28 @@ public BigInteger getTotalWeight() { return totalWeight.at(dbKey).getOrDefault(BigInteger.ZERO); } - public BigInteger getTotalSupply() { - return totalSupply.at(dbKey).getOrDefault(BigInteger.ZERO); + + public BigInteger getTotalExternalWeight(Address token) { + return externalTotalWeights.at(dbKey).getOrDefault(token, BigInteger.ZERO); + } + + public Address[] getRewardTokens() { + ArrayDB
tokens = rewardTokens.at(dbKey); + int size = tokens.size(); + Address[] tokensArray = new Address[size]; + for (int i = 0; i < tokens.size(); i++) { + tokensArray[i] = tokens.get(i); + } + + return tokensArray; + } + + public ArrayDB
getRewardTokensDB() { + return rewardTokens.at(dbKey); + } + + public void addRewardToken(Address token) { + rewardTokens.at(dbKey).add(token); } @SuppressWarnings("unchecked") @@ -233,25 +270,28 @@ public Map loadCurrentSupply(String owner) { } } - public BigInteger updateSingleUserData(BigInteger currentTime, BigInteger prevTotalSupply, String user, - BigInteger prevBalance, boolean readOnlyContext) { + public Map updateSingleUserData(BigInteger currentTime, BalanceData balances, String user, boolean readOnlyContext) { BigInteger currentUserWeight = getUserWeight(user); BigInteger lastUpdateTimestamp = getLastUpdateTimeUs(); + Address[] externalRewards = getRewardTokens(); + Address baln = BalancedAddressManager.getBaln(); + Map totalWeight = updateTotalWeight(lastUpdateTimestamp, currentTime, balances, externalRewards, readOnlyContext); + Map accruedRewards = new HashMap<>(externalRewards.length+1); + accruedRewards.put(baln, BigInteger.ZERO); - BigInteger totalWeight = updateTotalWeight(lastUpdateTimestamp, currentTime, prevTotalSupply, readOnlyContext); - - if (currentUserWeight.equals(totalWeight)) { - return BigInteger.ZERO; - } - - BigInteger accruedRewards = BigInteger.ZERO; // If the user's current weight is less than the total, update their weight and issue rewards - if (prevBalance.compareTo(BigInteger.ZERO) > 0) { - accruedRewards = computeUserRewards(prevBalance, totalWeight, currentUserWeight); + if (balances.prevWorkingBalance.compareTo(BigInteger.ZERO) > 0) { + accruedRewards.put(baln, computeUserRewards(balances.prevWorkingBalance, totalWeight.get(baln), currentUserWeight)); + for (Address token : externalRewards) { + accruedRewards.put(token, computeUserRewards(balances.prevBalance, totalWeight.get(token), getExternalUserWeight(user, token))); + } } if (!readOnlyContext) { - userWeight.at(dbKey).set(user, totalWeight); + userWeight.at(dbKey).set(user, totalWeight.get(baln)); + for (Address token : externalRewards) { + externalUserWeight.at(dbKey).at(token).set(user, totalWeight.get(token)); + } } return accruedRewards; @@ -263,6 +303,9 @@ public void updateWorkingBalanceAndSupply(String user, BalanceData balances) { Context.require(balance.compareTo(BigInteger.ZERO) >= 0); Context.require(supply.compareTo(BigInteger.ZERO) >= 0); + setBalance(user, balance); + setTotalSupply(supply); + BigInteger weight = RewardsImpl.boostWeight.get(); BigInteger max = balance.multiply(EXA).divide(weight); @@ -304,10 +347,14 @@ private BigInteger computeTotalWeight(BigInteger previousTotalWeight, return previousTotalWeight.add(weightDelta); } - private BigInteger updateTotalWeight(BigInteger lastUpdateTimestamp, BigInteger currentTime, - BigInteger totalSupply, boolean readOnlyContext) { - - BigInteger runningTotal = getTotalWeight(); + private Map updateTotalWeight(BigInteger lastUpdateTimestamp, BigInteger currentTime, + BalanceData balances, Address[] externalRewards, boolean readOnlyContext) { + Address baln = BalancedAddressManager.getBaln(); + Map externalTotals = new HashMap<>(externalRewards.length+1); + externalTotals.put(baln, getTotalWeight()); + for (Address token : externalRewards) { + externalTotals.put(token, externalTotalWeights.at(dbKey).getOrDefault(token, BigInteger.ZERO)); + } if (lastUpdateTimestamp.equals(BigInteger.ZERO)) { lastUpdateTimestamp = currentTime; @@ -317,7 +364,7 @@ private BigInteger updateTotalWeight(BigInteger lastUpdateTimestamp, BigInteger } if (currentTime.equals(lastUpdateTimestamp)) { - return runningTotal; + return externalTotals; } // Emit rewards based on the time delta * reward rate @@ -330,17 +377,31 @@ private BigInteger updateTotalWeight(BigInteger lastUpdateTimestamp, BigInteger BigInteger endComputeTimestampUs = previousDayEndUs.min(currentTime); BigInteger emission = getTotalDist(previousRewardsDay, readOnlyContext); - runningTotal = computeTotalWeight(runningTotal, emission, totalSupply, lastUpdateTimestamp, - endComputeTimestampUs); + externalTotals.put(baln, computeTotalWeight(externalTotals.get(baln), emission, balances.prevWorkingSupply, lastUpdateTimestamp, + endComputeTimestampUs)); + for (Address token : externalRewards) { + BigInteger externalEmission = getTotalExternalDist(previousRewardsDay, token); + if (externalEmission.equals(BigInteger.ZERO)) { + continue; + } + + externalTotals.put(token, computeTotalWeight(externalTotals.get(token), externalEmission, + balances.prevSupply, lastUpdateTimestamp, endComputeTimestampUs)); + } + lastUpdateTimestamp = endComputeTimestampUs; } if (!readOnlyContext) { - totalWeight.at(dbKey).set(runningTotal); + totalWeight.at(dbKey).set(externalTotals.get(baln)); + for (Address token : externalRewards) { + externalTotalWeights.at(dbKey).set(token, externalTotals.get(token)); + } + lastUpdateTimeUs.at(dbKey).set(currentTime); } - return runningTotal; + return externalTotals; } public BigInteger getValue() { @@ -350,20 +411,35 @@ public BigInteger getValue() { public Map getDataAt(BigInteger day) { Map sourceData = new HashMap<>(); - sourceData.put("day", day); sourceData.put("contract_address", getContractAddress()); - // dist_percent is deprecated - sourceData.put("dist_percent", getDistPercent()); sourceData.put("workingSupply", getWorkingSupply()); - sourceData.put("total_value", getTotalValue(day)); sourceData.put("total_dist", getTotalDist(day, true)); + Address[] externalRewards = getRewardTokens(); + Map externalData = new HashMap<>(); + for (Address addr : externalRewards) { + externalData.put("external_dist", getTotalExternalDist(day, addr)); + externalData.put("total_weight", getTotalExternalWeight(addr)); + sourceData.put(addr.toString(), externalData); + } return sourceData; } public Map getData() { - BigInteger day = this.getDay(); - return getDataAt(day); + return getDataAt(RewardsImpl.getDay()); + } + + public Map getUserData(String user) { + Map data = new HashMap<>(); + Address[] externalRewards = getRewardTokens(); + data.put("user_weight", getUserWeight(user)); + data.put("balance", getBalance(user)); + data.put("working balance", getWorkingBalance(user, true)); + + for (Address addr : externalRewards) { + data.put(addr.toString() + "_weight", getExternalUserWeight(user,addr)); + } + return data; } private BigInteger computeUserRewards(BigInteger prevUserBalance, BigInteger totalWeight, BigInteger userWeight) { 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 dc445341e..7db2f9452 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 @@ -17,15 +17,13 @@ package network.balanced.score.core.rewards; import network.balanced.score.core.rewards.utils.BalanceData; -import network.balanced.score.core.rewards.utils.RewardsConstants; import network.balanced.score.core.rewards.weight.SourceWeightController; import network.balanced.score.lib.interfaces.Rewards; -import network.balanced.score.lib.structs.DistributionPercentage; import network.balanced.score.lib.structs.Point; import network.balanced.score.lib.structs.RewardsDataEntry; import network.balanced.score.lib.structs.RewardsDataEntryOld; import network.balanced.score.lib.structs.VotedSlope; -import network.balanced.score.lib.utils.BalancedAddressManager; +import network.balanced.score.lib.utils.ArrayDBUtils; import network.balanced.score.lib.utils.IterableDictDB; import network.balanced.score.lib.utils.Names; import network.balanced.score.lib.utils.SetDB; @@ -36,18 +34,25 @@ import score.annotation.Optional; import scorex.util.ArrayList; import scorex.util.HashMap; +import network.balanced.score.lib.utils.BalancedAddressManager; import java.math.BigInteger; -import java.util.Iterator; import java.util.List; import java.util.Map; +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonValue; + import static network.balanced.score.core.rewards.utils.RewardsConstants.*; import static network.balanced.score.lib.utils.Check.*; 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.DBHelpers.contains; +import static network.balanced.score.lib.utils.Constants.WEEK_IN_MICRO_SECONDS; +import static network.balanced.score.lib.utils.Math.convertToNumber; import static network.balanced.score.lib.utils.Math.pow; +import static network.balanced.score.lib.utils.BalancedAddressManager.getBaln; +import static network.balanced.score.lib.utils.BalancedAddressManager.getBoostedBaln; /*** @@ -56,17 +61,11 @@ */ public class RewardsImpl implements Rewards { public static final String TAG = "BalancedRewards"; - - private static final String GOVERNANCE = "governance"; - private static final String ADMIN = "admin"; - private static final String BALN_ADDRESS = "baln_address"; - private static final String BOOSTED_BALN_ADDRESS = "boosted_baln_address"; - private static final String BWT_ADDRESS = "bwt_address"; - private static final String RESERVE_FUND = "reserve_fund"; - private static final String DAO_FUND = "dao_fund"; - private static final String START_TIMESTAMP = "start_timestamp"; private static final String BALN_HOLDINGS = "baln_holdings"; + private static final String EXTERNAL_HOLDINGS = "external_holdings"; + private static final String EXTERNAL_REWARD_PROVIDERS = "external_reward_providers"; + private static final String EXTERNAL_REWARD_TOKENS = "external_reward_tokens"; private static final String PLATFORM_DAY = "platform_day"; private static final String DATA_PROVIDERS = "data_providers"; private static final String BOOST_WEIGHT = "boost_weight"; @@ -76,15 +75,11 @@ public class RewardsImpl implements Rewards { private static final String FIXED_DISTRIBUTION_PERCENTAGES = "fixed_distribution_percentages"; private static final String VERSION = "version"; - private static final VarDB
governance = Context.newVarDB(GOVERNANCE, Address.class); - private static final VarDB
admin = Context.newVarDB(ADMIN, Address.class); - private static final VarDB
balnAddress = Context.newVarDB(BALN_ADDRESS, Address.class); - public static final VarDB
boostedBaln = Context.newVarDB(BOOSTED_BALN_ADDRESS, Address.class); - private static final VarDB
bwtAddress = Context.newVarDB(BWT_ADDRESS, Address.class); - private static final VarDB
reserveFund = Context.newVarDB(RESERVE_FUND, Address.class); - private static final VarDB
daofund = Context.newVarDB(DAO_FUND, Address.class); private static final VarDB startTimestamp = Context.newVarDB(START_TIMESTAMP, BigInteger.class); static final DictDB balnHoldings = Context.newDictDB(BALN_HOLDINGS, BigInteger.class); + static final BranchDB> externalHoldings = Context.newBranchDB(EXTERNAL_HOLDINGS, BigInteger.class); + static final DictDB externalRewardProviders = Context.newDictDB(EXTERNAL_REWARD_PROVIDERS, Boolean.class); + static final IterableDictDB externalRewardTokens = new IterableDictDB<>(EXTERNAL_REWARD_TOKENS, Boolean.class, Address.class, false); private static final VarDB platformDay = Context.newVarDB(PLATFORM_DAY, BigInteger.class); private final static SetDB
dataProviders = new SetDB<>(DATA_PROVIDERS, Address.class, null); @@ -93,62 +88,37 @@ public class RewardsImpl implements Rewards { Context.newDictDB(DAILY_VOTABLE_DISTRIBUTIONS, BigInteger.class); public static final BranchDB> dailyFixedDistribution = Context.newBranchDB(DAILY_FIXED_DISTRIBUTIONS, BigInteger.class); - public static final VarDB weightControllerMigrationDay = Context.newVarDB( - "weightControllerMigrationDay", BigInteger.class); private static final IterableDictDB distributionPercentages = new IterableDictDB<>(DISTRIBUTION_PERCENTAGES, BigInteger.class, String.class, false); private static final IterableDictDB fixedDistributionPercentages = new IterableDictDB<>(FIXED_DISTRIBUTION_PERCENTAGES, BigInteger.class, String.class, false); - private static final Map> platformRecipients = Map.of(WORKER_TOKENS, bwtAddress, - RewardsConstants.RESERVE_FUND, reserveFund, - DAOFUND, daofund); - - // deprecated - private static final String RECIPIENT_SPLIT = "recipient_split"; - private static final String SNAPSHOT_RECIPIENT = "snapshot_recipient"; - private static final String COMPLETE_RECIPIENT = "complete_recipient"; - private static final String TOTAL_SNAPSHOTS = "total_snapshots"; - private static final String RECIPIENTS = "recipients"; - - private static final DictDB recipientSplit = Context.newDictDB(RECIPIENT_SPLIT, - BigInteger.class); - private static final DictDB totalSnapshots = Context.newDictDB(TOTAL_SNAPSHOTS, - BigInteger.class); - private static final BranchDB>> snapshotRecipient = - Context.newBranchDB(SNAPSHOT_RECIPIENT, BigInteger.class); - private static final ArrayDB completeRecipient = Context.newArrayDB(COMPLETE_RECIPIENT, String.class); - private static final ArrayDB recipients = Context.newArrayDB(RECIPIENTS, String.class); private final VarDB currentVersion = Context.newVarDB(VERSION, String.class); public RewardsImpl(@Optional Address _governance) { - if (governance.get() == null) { + SourceWeightController.rewards = this; + + if (BalancedAddressManager.getAddressByName(Names.GOVERNANCE) == null) { // On "install" code - isContract(_governance); - governance.set(_governance); - BalancedAddressManager.setGovernance(governance.get()); + BalancedAddressManager.setGovernance(_governance); platformDay.set(BigInteger.ONE); - distributionPercentages.set(WORKER_TOKENS, BigInteger.ZERO); - distributionPercentages.set(RewardsConstants.RESERVE_FUND, BigInteger.ZERO); - distributionPercentages.set(DAOFUND, BigInteger.ZERO); - - // deprecated setters - recipientSplit.set(WORKER_TOKENS, BigInteger.ZERO); - recipientSplit.set(RewardsConstants.RESERVE_FUND, BigInteger.ZERO); - recipientSplit.set(DAOFUND, BigInteger.ZERO); - - recipients.add(WORKER_TOKENS); - recipients.add(RewardsConstants.RESERVE_FUND); - recipients.add(DAOFUND); - completeRecipient.add(WORKER_TOKENS); - completeRecipient.add(RewardsConstants.RESERVE_FUND); - completeRecipient.add(DAOFUND); + distributionPercentages.set(Names.WORKERTOKEN, BigInteger.ZERO); + distributionPercentages.set(Names.RESERVE, BigInteger.ZERO); + distributionPercentages.set(Names.DAOFUND, BigInteger.ZERO); + + SourceWeightController.addType(coreTypeName, EXA); + SourceWeightController.addType(communityTypeName, EXA); boostWeight.set(WEIGHT); - } else { - SourceWeightController.reset(getAllSources()); + } else if (distributionPercentages.get(WORKER_TOKENS) != null) { + distributionPercentages.set(Names.WORKERTOKEN, distributionPercentages.get(WORKER_TOKENS)); + distributionPercentages.set(Names.RESERVE, distributionPercentages.get(RESERVE_FUND)); + distributionPercentages.set(Names.DAOFUND, distributionPercentages.get(DAOFUND)); + distributionPercentages.remove(WORKER_TOKENS); + distributionPercentages.remove(RESERVE_FUND); + distributionPercentages.remove(DAOFUND); } - SourceWeightController.rewards = this; + if (currentVersion.getOrDefault("").equals(Versions.REWARDS)) { Context.revert("Can't Update same version of code"); } @@ -165,6 +135,16 @@ public String version() { return currentVersion.getOrDefault(""); } + @External + public void updateAddress(String name) { + BalancedAddressManager.resetAddress(name); + } + + @External(readonly = true) + public Address getAddress(String name) { + return BalancedAddressManager.getAddressByName(name); + } + @External(readonly = true) public BigInteger getEmission(@Optional BigInteger _day) { if (_day == null || _day.equals(BigInteger.ZERO)) { @@ -189,21 +169,45 @@ public Map getBalnHoldings(String[] _holders) { return holdings; } + + @External(readonly = true) + public Map getHoldings(String _holder) { + List
tokens = externalRewardTokens.keys(); + DictDB holdingsDB = externalHoldings.at(_holder); + Map holdings = new HashMap<>(); + + for (Address token : tokens) { + holdings.put(token.toString(), holdingsDB.getOrDefault(token, BigInteger.ZERO)); + } + + holdings.put(getBaln().toString(), balnHoldings.getOrDefault(_holder, BigInteger.ZERO)); + + return holdings; + } + @External(readonly = true) public BigInteger getBalnHolding(String _holder) { - BigInteger accruedRewards = balnHoldings.getOrDefault(_holder, BigInteger.ZERO); + Address baln = getBaln(); + return getRewards(_holder).get(baln.toString()); + } + @External(readonly = true) + public Map getRewards(String _holder) { + Map accruedRewards = getHoldings(_holder); int dataSourcesCount = DataSourceDB.size(); - // TODO If we remove data source, user can't claim rewards for that data source for (int i = 0; i < dataSourcesCount; i++) { String name = DataSourceDB.names.get(i); DataSourceImpl dataSource = DataSourceDB.get(name); BigInteger currentTime = getTime(); - - BigInteger sourceRewards = dataSource.updateSingleUserData(currentTime, dataSource.getWorkingSupply(true) - , _holder, dataSource.getWorkingBalance(_holder, true), true); - - accruedRewards = accruedRewards.add(sourceRewards); + BalanceData balances = new BalanceData(); + balances.prevBalance = dataSource.getBalance(_holder); + balances.prevSupply = dataSource.getTotalSupply(); + balances.prevWorkingBalance = dataSource.getWorkingBalance(_holder, true); + balances.prevWorkingSupply = dataSource.getWorkingSupply(true); + Map sourceRewards = dataSource.updateSingleUserData(currentTime, balances, _holder, true); + for (Map.Entry entry : sourceRewards.entrySet()) { + accruedRewards.put(entry.getKey().toString(), accruedRewards.get(entry.getKey().toString()).add(entry.getValue())); + } } return accruedRewards; @@ -220,31 +224,9 @@ public List getDataSourceNames() { return names; } - // Split to allow creation of mainnet like scenario - // In the future use createDataSource - @External - public void addNewDataSource(String _name, Address _address) { - only(governance); - Context.require(!contains(recipients, _name), TAG + ": Recipient already exists"); - Context.require(_address.isContract(), TAG + " : Data source must be a contract."); - - recipients.add(_name); - completeRecipient.add(_name); - DataSourceDB.newSource(_name, _address); - } - - @External - public void addDataSource(String name, int sourceType, BigInteger weight) { - only(governance); - DataSourceImpl dataSource = DataSourceDB.get(name); - Context.require(name.equals(dataSource.getName()), "There is no data source with the name " + name); - SourceWeightController.addSource(name, sourceType, weight); - } - @External public void createDataSource(String _name, Address _address, int sourceType) { - only(governance); - Context.require(!contains(recipients, _name), TAG + ": Recipient already exists"); + onlyOwner(); Context.require(_address.isContract(), TAG + " : Data source must be a contract."); DataSourceDB.newSource(_name, _address); @@ -265,6 +247,19 @@ public Map> getDataSources() { return dataSources; } + @External(readonly = true) + public Map> getUserSourceData(String user) { + Map> dataSources = new HashMap<>(); + int dataSourcesCount = DataSourceDB.size(); + for (int i = 0; i < dataSourcesCount; i++) { + String name = DataSourceDB.names.get(i); + DataSourceImpl dataSource = DataSourceDB.get(name); + dataSources.put(name, dataSource.getUserData(user)); + } + + return dataSources; + } + @External(readonly = true) public Map> getDataSourcesAt(BigInteger _day) { Map> dataSources = new HashMap<>(); @@ -357,7 +352,7 @@ public boolean distribute() { private boolean mintAndAllocateBalnReward(BigInteger platformDay) { BigInteger distribution = dailyDistribution(platformDay); - Context.call(balnAddress.get(), "mint", distribution, new byte[0]); + Context.call(getBaln(), "mint", distribution, new byte[0]); BigInteger shares = HUNDRED_PERCENTAGE; BigInteger remaining = distribution; @@ -365,7 +360,7 @@ private boolean mintAndAllocateBalnReward(BigInteger platformDay) { for (String recipient : recipients) { BigInteger split = distributionPercentages.get(recipient); BigInteger share = split.multiply(remaining).divide(shares); - Context.call(balnAddress.get(), "transfer", platformRecipients.get(recipient).get(), share, new byte[0]); + Context.call(getBaln(), "transfer", BalancedAddressManager.getAddress(recipient) , share, new byte[0]); remaining = remaining.subtract(share); shares = shares.subtract(split); } @@ -407,45 +402,64 @@ public void claimRewards(@Optional String[] sources) { BigInteger boostedSupply = fetchBoostedSupply(); updateAllUserRewards(address, sources, boostedBalance, boostedSupply); + List
tokens = externalRewardTokens.keys(); + DictDB holdingsDB = externalHoldings.at(address); + + for (Address token : tokens) { + BigInteger amount = holdingsDB.getOrDefault(token, BigInteger.ZERO); + if (amount.compareTo(BigInteger.ZERO) > 0) { + Context.call(token, "transfer", caller, amount, new byte[0]); + holdingsDB.set(token, null); + RewardsClaimedV2(token, address, amount); + } + } + BigInteger userClaimableRewards = balnHoldings.getOrDefault(address, BigInteger.ZERO); if (userClaimableRewards.compareTo(BigInteger.ZERO) > 0) { balnHoldings.set(address, null); - Context.call(balnAddress.get(), "transfer", caller, userClaimableRewards, new byte[0]); + Address baln = getBaln(); + + Context.call(baln, "transfer", caller, userClaimableRewards, new byte[0]); RewardsClaimed(caller, userClaimableRewards); + RewardsClaimedV2(baln, address, userClaimableRewards); } } - @External(readonly = true) - public BigInteger getAPY(String _name) { - DataSourceImpl dexDataSource = DataSourceDB.get("sICX/ICX"); - DataSourceImpl dataSource = DataSourceDB.get(_name); - BigInteger emission = this.getEmission(BigInteger.valueOf(-1)); + @External + public void tokenFallback(Address _from, BigInteger _value, byte[] _data) { + checkStatus(); + Address token = Context.getCaller(); + if (token.equals(getBaln())) { + return; + } - BigInteger balnPrice = Context.call(BigInteger.class, dexDataSource.getContractAddress(), "getBalnPrice"); - BigInteger migrationDay = weightControllerMigrationDay.get(); - BigInteger percentage; + Context.require(externalRewardTokens.getOrDefault(token, false), "Only whitelisted tokens is allowed as rewards tokens"); + Context.require(externalRewardProviders.getOrDefault(_from, false), "Only whitelisted addresses is allowed to provider external rewards"); + String unpackedData = new String(_data); + Context.require(!unpackedData.isEmpty(), "Token Fallback: Data can't be empty"); - if (migrationDay == null || migrationDay.compareTo(getDay()) > 0) { - percentage = dataSource.getDistPercent(); - } else { - BigInteger relativePercentage = SourceWeightController.getRelativeWeight(_name, - BigInteger.valueOf(Context.getBlockTimestamp())); - percentage = relativePercentage.multiply(getVotableDist()).divide(HUNDRED_PERCENTAGE); - } + JsonArray amounts = Json.parse(unpackedData).asArray(); - BigInteger sourceValue = dataSource.getValue(); - BigInteger year = BigInteger.valueOf(365); + BigInteger rewardDay = getDay().add(BigInteger.ONE); + BigInteger total = BigInteger.ZERO; + for (JsonValue jsonValue : amounts) { + BigInteger amount = convertToNumber(jsonValue.asObject().get("amount")); + String source = jsonValue.asObject().get("source").asString(); - BigInteger sourceAmount = year.multiply(emission).multiply(percentage); + total = total.add(amount); - return sourceAmount.multiply(balnPrice).divide(sourceValue.multiply(HUNDRED_PERCENTAGE)); - } + DataSourceImpl dataSource = DataSourceDB.get(source); + if (dataSource.getTotalExternalWeight(token).equals(BigInteger.ZERO)) { + if (!ArrayDBUtils.arrayDbContains(dataSource.getRewardTokensDB(), token)) { + dataSource.addRewardToken(token); + } + } - @External - public void tokenFallback(Address _from, BigInteger _value, byte[] _data) { - checkStatus(); - Context.require(Context.getCaller().equals(balnAddress.get()), - TAG + ": The Rewards SCORE can only accept BALN tokens"); + BigInteger prevAmount = dataSource.getTotalDist(rewardDay); + dataSource.setTotalExternalDist(rewardDay, token, amount.add(prevAmount)); + } + + Context.require(total.equals(_value), "Total allocations do not match amount deposited"); } @External @@ -454,6 +468,18 @@ public void addDataProvider(Address _source) { dataProviders.add(_source); } + @External + public void configureExternalReward(Address token, boolean allow) { + onlyOwner(); + externalRewardTokens.set(token, allow); + } + + @External + public void configureExternalRewardProvider(Address provider, boolean allow) { + onlyOwner(); + externalRewardProviders.set(provider, allow); + } + @External(readonly = true) public List
getDataProviders() { int dataProvidersSize = dataProviders.size(); @@ -488,6 +514,8 @@ public void updateRewardsData(String _name, BigInteger _totalSupply, Address _us Map balanceAndSupply = dataSource.loadCurrentSupply(user); balances.balance = balanceAndSupply.get(BALANCE); balances.supply = balanceAndSupply.get(TOTAL_SUPPLY); + balances.prevBalance = dataSource.getBalance(user); + balances.prevSupply = dataSource.getTotalSupply(); balances.prevWorkingBalance = dataSource.getWorkingBalance(user, _balance, false); balances.prevWorkingSupply = dataSource.getWorkingSupply(_totalSupply, false); @@ -515,6 +543,8 @@ public void updateBatchRewardsData(String _name, BigInteger _totalSupply, Reward Map balanceAndSupply = dataSource.loadCurrentSupply(user); balances.balance = balanceAndSupply.get(BALANCE); balances.supply = balanceAndSupply.get(TOTAL_SUPPLY); + balances.prevBalance = dataSource.getBalance(user); + balances.prevSupply = dataSource.getTotalSupply(); balances.prevWorkingBalance = dataSource.getWorkingBalance(user, entry._balance, false); balances.prevWorkingSupply = dataSource.getWorkingSupply(_totalSupply, false); @@ -537,6 +567,8 @@ public void updateBalanceAndSupply(String _name, BigInteger _totalSupply, String balances.boostedSupply = fetchBoostedSupply(); balances.balance = _balance; balances.supply = _totalSupply; + balances.prevBalance = dataSource.getBalance(_user); + balances.prevSupply = dataSource.getTotalSupply(); balances.prevWorkingBalance = dataSource.getWorkingBalance(_user); balances.prevWorkingSupply = dataSource.getWorkingSupply(); @@ -561,6 +593,8 @@ public void updateBalanceAndSupplyBatch(String _name, BigInteger _totalSupply, R balances.boostedSupply = boostedSupply; balances.balance = entry._balance; balances.supply = _totalSupply; + balances.prevBalance = dataSource.getBalance(entry._user); + balances.prevSupply = dataSource.getTotalSupply(); balances.prevWorkingBalance = dataSource.getWorkingBalance(entry._user); balances.prevWorkingSupply = dataSource.getWorkingSupply(); @@ -571,7 +605,7 @@ public void updateBalanceAndSupplyBatch(String _name, BigInteger _totalSupply, R @External public void onKick(Address user) { checkStatus(); - only(boostedBaln); + only(getBoostedBaln()); BigInteger boostedSupply = fetchBoostedSupply(); updateAllUserRewards(user.toString(), getAllSources(), BigInteger.ZERO, boostedSupply); } @@ -587,14 +621,14 @@ public void kick(Address user, String[] sources) { @External public void onBalanceUpdate(Address user, BigInteger balance) { checkStatus(); - only(boostedBaln); + only(getBoostedBaln()); BigInteger boostedSupply = fetchBoostedSupply(); updateAllUserRewards(user.toString(), getAllSources(), balance, boostedSupply); } @External public void setBoostWeight(BigInteger weight) { - only(admin); + onlyOwner(); Context.require(weight.compareTo(HUNDRED_PERCENTAGE.divide(BigInteger.valueOf(100))) >= 0, "Boost weight has " + "to be above 1%"); Context.require(weight.compareTo(HUNDRED_PERCENTAGE) <= 0, "Boost weight has to be below 100%"); @@ -630,49 +664,32 @@ public String[] getUserSources(String user) { @External public void setVotable(String name, boolean votable) { - only(governance); + onlyOwner(); SourceWeightController.setVotable(name, votable); } @External public void addType(String name) { - only(governance); + onlyOwner(); SourceWeightController.addType(name, BigInteger.ZERO); } @External public void changeTypeWeight(int typeId, BigInteger weight) { - only(governance); - SourceWeightController.changeTypeWeight(typeId, weight); - } - - @External - public void setMigrateToVotingDay(BigInteger day) { onlyOwner(); - Context.require(day.compareTo(platformDay.get()) > 0, "day has to be greater than platformDay"); - if (weightControllerMigrationDay.get() == null) { - migrateWeightController(); - } - - weightControllerMigrationDay.set(day); - } - - @External(readonly = true) - public BigInteger getMigrateToVotingDay() { - return weightControllerMigrationDay.get(); + SourceWeightController.changeTypeWeight(typeId, weight); } @External public void setPlatformDistPercentage(String name, BigInteger percentage) { - only(governance); - Context.require(platformRecipients.containsKey(name), name + " is not a platform recipient"); + onlyOwner(); distributionPercentages.set(name, percentage); verifyPercentages(); } @External public void setFixedSourcePercentage(String name, BigInteger percentage) { - only(governance); + onlyOwner(); DataSourceImpl dataSource = DataSourceDB.get(name); Context.require(dataSource.getName().equals(name), "There is no data source with the name " + name); if (percentage.equals(BigInteger.ZERO)) { @@ -722,12 +739,6 @@ public Map> getDistributionPercentages() { return allPercentages; } - @External - public void revote(Address user) { - checkStatus(); - SourceWeightController.revote(user, getAllSources()); - } - @External public void checkpoint() { checkStatus(); @@ -899,6 +910,8 @@ private void updateAllUserRewards(String user, String[] sources, BigInteger boos Map balanceAndSupply = dataSource.loadCurrentSupply(user); balances.balance = balanceAndSupply.get(BALANCE); balances.supply = balanceAndSupply.get(TOTAL_SUPPLY); + balances.prevBalance = dataSource.getBalance(user); + balances.prevSupply = dataSource.getTotalSupply(); balances.prevWorkingBalance = workingBalance; balances.prevWorkingSupply = dataSource.getWorkingSupply(false); @@ -909,104 +922,29 @@ private void updateAllUserRewards(String user, String[] sources, BigInteger boos private void updateUserAccruedRewards(String _name, BigInteger currentTime, DataSourceImpl dataSource, String user, BalanceData balances) { - BigInteger accruedRewards = dataSource.updateSingleUserData(currentTime, balances.prevWorkingSupply, user, - balances.prevWorkingBalance, false); + Map accruedRewards = dataSource.updateSingleUserData(currentTime, balances, user, false); dataSource.updateWorkingBalanceAndSupply(user, balances); - if (accruedRewards.compareTo(BigInteger.ZERO) > 0) { + Address baln = getBaln(); + if (accruedRewards.get(baln).compareTo(BigInteger.ZERO) > 0) { BigInteger newHoldings = - balnHoldings.getOrDefault(user, BigInteger.ZERO).add(accruedRewards); + balnHoldings.getOrDefault(user, BigInteger.ZERO).add(accruedRewards.get(baln)); balnHoldings.set(user, newHoldings); - RewardsAccrued(user, _name, accruedRewards); - } - } - - @External - public void setGovernance(Address _address) { - onlyOwner(); - isContract(_address); - governance.set(_address); - } - - @External(readonly = true) - public Address getGovernance() { - return governance.get(); - } - - @External - public void setAdmin(Address _address) { - only(governance); - admin.set(_address); - } - - @External(readonly = true) - public Address getAdmin() { - return admin.get(); - } - - @External - public void setBaln(Address _address) { - only(admin); - isContract(_address); - balnAddress.set(_address); - } - - @External(readonly = true) - public Address getBaln() { - return balnAddress.get(); - } - - @External - public void setBoostedBaln(Address _address) { - only(admin); - isContract(_address); - boostedBaln.set(_address); - } + RewardsAccrued(user, _name, accruedRewards.get(baln)); + accruedRewards.remove(baln); - @External(readonly = true) - public Address getBoostedBaln() { - return boostedBaln.get(); - } - - @External - public void setBwt(Address _address) { - only(admin); - isContract(_address); - bwtAddress.set(_address); - } - - @External(readonly = true) - public Address getBwt() { - return bwtAddress.get(); - } - - @External - public void setReserve(Address _address) { - only(admin); - isContract(_address); - reserveFund.set(_address); - } - - @External(readonly = true) - public Address getReserve() { - return reserveFund.get(); - } - - @External - public void setDaofund(Address _address) { - only(admin); - isContract(_address); - daofund.set(_address); - } + } + DictDB externalHoldingsDB = externalHoldings.at(user); + for (Map.Entry entry : accruedRewards.entrySet()) { + BigInteger prevRewards = externalHoldingsDB.getOrDefault(entry.getKey(), BigInteger.ZERO); + externalHoldingsDB.set(entry.getKey(), prevRewards.add(entry.getValue())); + } - @External(readonly = true) - public Address getDaofund() { - return daofund.get(); } @External public void setTimeOffset(BigInteger _timestamp) { - only(admin); + onlyOwner(); startTimestamp.set(_timestamp); Context.require(getDay().compareTo(BigInteger.ZERO) > 0, TAG + ": Day should begin from 1. Please set earlier time offset"); @@ -1031,19 +969,6 @@ static Object call(Address targetAddress, String method, Object... params) { } public static BigInteger getTotalDist(String sourceName, BigInteger day, boolean readonly) { - BigInteger migrationDay = weightControllerMigrationDay.get(); - if (migrationDay == null || migrationDay.compareTo(day) > 0) { - BigInteger dist = dailyDistribution(day); - Map recipientDistribution = _recipientAt(day); - - BigInteger split = recipientDistribution.get(sourceName); - if (split == null) { - return BigInteger.ZERO; - } - - return split.multiply(dist).divide(HUNDRED_PERCENTAGE); - } - BigInteger dist = dailyVotableDistribution.getOrDefault(day, BigInteger.ZERO); BigInteger time = day.multiply(MICRO_SECONDS_IN_A_DAY).add(startTimestamp.get()); BigInteger weight; @@ -1102,49 +1027,9 @@ private BigInteger fetchBoostedBalance(String user) { return fetchBoostedBalance(Address.fromString(user)); } - private void migrateWeightController() { - String coreTypeName = "BALN Core"; - String communityTypeName = "Community"; - SourceWeightController.addType(coreTypeName, EXA); - SourceWeightController.addType(communityTypeName, EXA); - int coreType = SourceWeightController.getTypeId(coreTypeName); - int communityType = SourceWeightController.getTypeId(communityTypeName); - Map recipients = recipientAt(getDay()); - - // core - if (DataSourceDB.hasSource("Loans")) { - SourceWeightController.addSource("Loans", coreType, BigInteger.ZERO); - SourceWeightController.setVotable("Loans", false); - fixedDistributionPercentages.set("Loans", recipients.get("Loans")); - } - if (DataSourceDB.hasSource("sICX/bnUSD")) { - SourceWeightController.addSource("sICX/bnUSD", coreType, BigInteger.ZERO); - } - if (DataSourceDB.hasSource("BALN/bnUSD")) { - SourceWeightController.addSource("BALN/bnUSD", coreType, BigInteger.ZERO); - } - if (DataSourceDB.hasSource("BALN/sICX")) { - SourceWeightController.addSource("BALN/sICX", coreType, BigInteger.ZERO); - } - - // Community - if (DataSourceDB.hasSource("sICX/ICX")) { - SourceWeightController.addSource("sICX/ICX", communityType, BigInteger.ZERO); - } - if (DataSourceDB.hasSource("IUSDC/bnUSD")) { - SourceWeightController.addSource("IUSDC/bnUSD", communityType, BigInteger.ZERO); - } - if (DataSourceDB.hasSource("USDS/bnUSD")) { - SourceWeightController.addSource("USDS/bnUSD", communityType, BigInteger.ZERO); - } - if (DataSourceDB.hasSource("IUSDT/bnUSD")) { - SourceWeightController.addSource("IUSDT/bnUSD", communityType, BigInteger.ZERO); - } - } - private BigInteger fetchBoostedBalance(Address user) { try { - return (BigInteger) RewardsImpl.call(boostedBaln.get(), "balanceOf", user, BigInteger.ZERO); + return (BigInteger) RewardsImpl.call(getBoostedBaln(), "balanceOf", user, BigInteger.ZERO); } catch (Exception e) { return BigInteger.ZERO; } @@ -1152,7 +1037,7 @@ private BigInteger fetchBoostedBalance(Address user) { private BigInteger fetchBoostedSupply() { try { - return (BigInteger) RewardsImpl.call(boostedBaln.get(), "totalSupply", BigInteger.ZERO); + return (BigInteger) RewardsImpl.call(getBoostedBaln(), "totalSupply", BigInteger.ZERO); } catch (Exception e) { return BigInteger.ZERO; } @@ -1162,6 +1047,11 @@ private BigInteger fetchBoostedSupply() { public void RewardsClaimed(Address _address, BigInteger _amount) { } + @EventLog(indexed = 2) + public void RewardsClaimedV2(Address _token, String _address, BigInteger _amount) { + } + + @EventLog(indexed = 2) public void Report(BigInteger _day, String _name, BigInteger _dist, BigInteger _value) { } @@ -1191,176 +1081,4 @@ public void VoteForSource(String sourceName, Address user, BigInteger weight, Bi public void NewSource(String sourceName, int typeId, BigInteger weight) { } - // deprecated functions - @External(readonly = true) - public List getRecipients() { - List names = new ArrayList<>(); - int recipientsCount = recipients.size(); - for (int i = 0; i < recipientsCount; i++) { - names.add(recipients.get(i)); - } - return names; - } - - @External(readonly = true) - public Map getRecipientsSplit() { - return recipientAt(getDay()); - } - - @External(readonly = true) - public Map distStatus() { - Map sourceDays = new HashMap<>(); - int dataSourcesCount = DataSourceDB.size(); - for (int i = 0; i < dataSourcesCount; i++) { - String name = DataSourceDB.names.get(i); - DataSourceImpl dataSource = DataSourceDB.get(name); - - sourceDays.put(name, dataSource.getDay()); - } - - return Map.of( - "platform_day", platformDay.get(), - "source_days", sourceDays - ); - } - - - @External(readonly = true) - public Map recipientAt(BigInteger _day) { - return _recipientAt(_day); - } - - public static Map _recipientAt(BigInteger _day) { - Context.require(_day.compareTo(BigInteger.ZERO) >= 0, TAG + ": day:" + _day + " must be equal to or greater " + - "then Zero"); - - Map distributions = new HashMap<>(); - - int completeRecipientCount = completeRecipient.size(); - for (int i = 0; i < completeRecipientCount; i++) { - String recipient = completeRecipient.get(i); - BigInteger totalSnapshotsTaken = totalSnapshots.getOrDefault(recipient, BigInteger.ZERO); - BranchDB> snapshot = snapshotRecipient.at(recipient); - if (totalSnapshotsTaken.equals(BigInteger.ZERO)) { - distributions.put(recipient, recipientSplit.getOrDefault(recipient, BigInteger.ZERO)); - continue; - } else if (snapshot.at(totalSnapshotsTaken.subtract(BigInteger.ONE)).getOrDefault(IDS, BigInteger.ZERO).compareTo(_day) <= 0) { - distributions.put(recipient, - snapshot.at(totalSnapshotsTaken.subtract(BigInteger.ONE)).getOrDefault(AMOUNT, - BigInteger.ZERO)); - continue; - } else if (snapshot.at(BigInteger.ZERO).getOrDefault(IDS, BigInteger.ZERO).compareTo(_day) > 0) { - distributions.put(recipient, recipientSplit.getOrDefault(recipient, BigInteger.ZERO)); - continue; - } - - BigInteger low = BigInteger.ZERO; - BigInteger high = totalSnapshotsTaken.subtract(BigInteger.ONE); - while (high.compareTo(low) > 0) { - BigInteger mid = high.subtract(high.subtract(low).divide(BigInteger.TWO)); - DictDB midValue = snapshot.at(mid); - BigInteger index = midValue.getOrDefault(IDS, BigInteger.ZERO); - if (index.equals(_day)) { - low = mid; - break; - } else if (index.compareTo(_day) < 0) { - low = mid; - } else { - high = mid.subtract(BigInteger.ONE); - } - } - - distributions.put(recipient, snapshot.at(low).getOrDefault(AMOUNT, BigInteger.ZERO)); - } - - Iterator> it; - for (it = distributions.entrySet().iterator(); it.hasNext(); ) { - Map.Entry entry = it.next(); - if (entry.getValue().equals(BigInteger.ZERO)) { - it.remove(); - } - } - - return distributions; - } - - /** - * This method provides a means to adjust the allocation of rewards tokens. - * To maintain consistency a change to these percentages will only be - * accepted if they sum to 100%, with 100% represented by the value 10**18. - * This method must only be called when rewards are fully up-to-date. - * param _recipient_list: List of json containing the allocation spec. - * type _recipient_list: List[TypedDict] - **/ - @External - public void updateBalTokenDistPercentage(DistributionPercentage[] _recipient_list) { - only(governance); - Context.require(_recipient_list.length == recipients.size(), TAG + ": Recipient lists lengths mismatched!"); - BigInteger totalPercentage = BigInteger.ZERO; - BigInteger day = getDay(); - - for (DistributionPercentage recipient : _recipient_list) { - String name = recipient.recipient_name; - - Context.require(contains(recipients, name), TAG + ": Recipient " + name + " does not exist."); - - BigInteger percentage = recipient.dist_percent; - updateRecipientSnapshot(name, percentage); - DataSourceImpl dataSource = DataSourceDB.get(name); - BigInteger dataSourceDay = dataSource.getDay(); - if (dataSource.getTotalDist(dataSourceDay).equals(BigInteger.ZERO)) { - dataSource.setDay(day); - } - - if (distributionPercentages.get(name) != null) { - distributionPercentages.set(name, percentage); - } - - dataSource.setDistPercent(percentage); - totalPercentage = totalPercentage.add(percentage); - } - - Context.require(totalPercentage.equals(HUNDRED_PERCENTAGE), TAG + ": Total percentage does not sum up to 100."); - } - - @External - public void removeDataSource(String _name) { - only(governance); - if (!contains(recipients, _name)) { - return; - } - - BigInteger day = getDay(); - Map recipientDistribution = recipientAt(day); - Context.require(!recipientDistribution.containsKey(_name), TAG + ": Data source rewards percentage must be " + - "set to 0 before removing."); - // TODO this doesn't allow user to claim rewards from previous data source - String topRecipient = recipients.pop(); - if (!topRecipient.equals(_name)) { - for (int i = 0; i < recipients.size(); i++) { - if (recipients.get(i).equals(_name)) { - recipients.set(i, topRecipient); - } - } - } - - DataSourceDB.removeSource(_name); - } - - private void updateRecipientSnapshot(String recipient, BigInteger percentage) { - BigInteger currentDay = getDay(); - BigInteger totalSnapshotsTaken = totalSnapshots.getOrDefault(recipient, BigInteger.ZERO); - BranchDB> snapshot = snapshotRecipient.at(recipient); - - BigInteger recipientDay = snapshot.at(totalSnapshotsTaken.subtract(BigInteger.ONE)).getOrDefault(IDS, - BigInteger.ZERO); - - if (totalSnapshotsTaken.compareTo(BigInteger.ZERO) > 0 && recipientDay.equals(currentDay)) { - snapshot.at(totalSnapshotsTaken.subtract(BigInteger.ONE)).set(AMOUNT, percentage); - } else { - snapshot.at(totalSnapshotsTaken).set(IDS, currentDay); - snapshot.at(totalSnapshotsTaken).set(AMOUNT, percentage); - totalSnapshots.set(recipient, totalSnapshotsTaken.add(BigInteger.ONE)); - } - } } diff --git a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/utils/BalanceData.java b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/utils/BalanceData.java index f6d90b9d4..54b9315b9 100644 --- a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/utils/BalanceData.java +++ b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/utils/BalanceData.java @@ -5,6 +5,8 @@ public class BalanceData { public BigInteger prevWorkingBalance; public BigInteger prevWorkingSupply; + public BigInteger prevBalance; + public BigInteger prevSupply; public BigInteger boostedBalance; public BigInteger boostedSupply; public BigInteger balance; diff --git a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/utils/RewardsConstants.java b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/utils/RewardsConstants.java index d35d56b44..6caa0568c 100644 --- a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/utils/RewardsConstants.java +++ b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/utils/RewardsConstants.java @@ -30,6 +30,9 @@ public class RewardsConstants extends Constants { public static final String TOTAL_SUPPLY = "_totalSupply"; public static final String BALANCE = "_balance"; + public static final String coreTypeName = "BALN Core"; + public static final String communityTypeName = "Community"; + public static final BigInteger HUNDRED_PERCENTAGE = EXA; public static final BigInteger WEIGHT = BigInteger.valueOf(40) .multiply(HUNDRED_PERCENTAGE) diff --git a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/weight/SourceWeightController.java b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/weight/SourceWeightController.java index 159c6d3fb..fefb09d4e 100644 --- a/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/weight/SourceWeightController.java +++ b/core-contracts/Rewards/src/main/java/network/balanced/score/core/rewards/weight/SourceWeightController.java @@ -24,11 +24,11 @@ import java.math.BigInteger; -import static network.balanced.score.core.rewards.RewardsImpl.boostedBaln; import static network.balanced.score.lib.utils.ArrayDBUtils.arrayDbContains; import static network.balanced.score.lib.utils.ArrayDBUtils.removeFromArraydb; 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.BalancedAddressManager.*; public class SourceWeightController { public static RewardsImpl rewards; @@ -94,33 +94,11 @@ public class SourceWeightController { BigInteger.class); - private static final DictDB isRevoted = Context.newDictDB("isRevoted-v2", Boolean.class); - private static final VarDB isReset = Context.newVarDB("votesReset-v1", Boolean.class); - public SourceWeightController(Address bBalnAddress) { - boostedBaln.set(bBalnAddress); + public SourceWeightController() { timeTotal.set(getWeekTimestamp()); } - public static void reset(String[] sources) { - Context.require(!isReset.getOrDefault(false), "Votes have already been reset"); - BigInteger nextWeekTimestamp = getNextWeekTimestamp(); - // Move timesum of sources and type sums to next week then set to zero - for (String sourceName : sources) { - getWeight(sourceName); - pointsWeight.at(sourceName).set(nextWeekTimestamp, new Point()); - } - getTotal(); - // We can now safley set these to 0 and revote all users before next week - int nrSourceTypes = sourceTypeNames.length(); - for (int id = 0; id < nrSourceTypes; id++) { - pointsSum.at(id).set(nextWeekTimestamp, new Point()); - } - pointsTotal.set(nextWeekTimestamp, BigInteger.ZERO); - - isReset.set(true); - } - /** * Fill historic type weights week-over-week for missed checkins and return the type weight for the future week * @@ -312,45 +290,7 @@ public static void addSource(String name, int sourceType, BigInteger weight) { rewards.NewSource(name, sourceType, weight); } - public static void revote(Address user, String[] sources) { - Context.require(!isRevoted.getOrDefault(user, false), "This user already had its vote applied"); - BigInteger nextTime = getNextWeekTimestamp(); - for (String sourceName : sources) { - int sourceType = sourceTypes.get(sourceName) - 1; - Context.require(sourceType >= 0, "Source not added"); - // Prepare slopes and biases in memory - VotedSlope slope = voteUserSlopes.at(user).get(sourceName); - if (slope == null) { - continue; - } - if (slope.end.compareTo(nextTime) <= 0) { - continue; - } - - BigInteger newDt = slope.end.subtract(nextTime); - BigInteger newBias = slope.slope.multiply(newDt); - - Point weightPoint = pointsWeight.at(sourceName).getOrDefault(nextTime, new Point()); - weightPoint.bias = weightPoint.bias.add(newBias); - Point sumPoint = pointsSum.at(sourceType).getOrDefault(nextTime, new Point()); - sumPoint.bias = sumPoint.bias.add(newBias); - - weightPoint.slope = weightPoint.slope.add(slope.slope); - sumPoint.slope = sumPoint.slope.add(slope.slope); - - // Add slope changes for new slopes - BigInteger newChangeWeight = changesWeight.at(sourceName).getOrDefault(slope.end, BigInteger.ZERO); - changesWeight.at(sourceName).set(slope.end, newChangeWeight.add(slope.slope)); - BigInteger newChangeSum = changesSum.at(sourceType).getOrDefault(slope.end, BigInteger.ZERO); - changesSum.at(sourceType).set(slope.end, newChangeSum.add(slope.slope)); - - pointsWeight.at(sourceName).set(nextTime, weightPoint); - pointsSum.at(sourceType).set(nextTime, sumPoint); - } - - isRevoted.set(user, true); - } /** * Checkpoint to fill data common for all sources @@ -455,14 +395,10 @@ public static void addType(String name, BigInteger weight) { * @param userWeight Weight for a source in bps (units of 0.01%). Minimal is 0.01%. Ignored if 0 */ public static void voteForSourceWeights(String sourceName, BigInteger userWeight) { - if (!isRevoted.getOrDefault(Context.getCaller(), false)) { - revote(Context.getCaller(), RewardsImpl.getAllSources()); - } - Context.require(isVotable.getOrDefault(sourceName, true) || userWeight.equals(BigInteger.ZERO), sourceName + " is not a votable source, you can only remove weight"); - Address bBalnAddress = boostedBaln.get(); + Address bBalnAddress = getBoostedBaln(); Address user = Context.getCaller(); BigInteger slope = Context.call(BigInteger.class, bBalnAddress, "getLastUserSlope", user); BigInteger lockEnd = Context.call(BigInteger.class, bBalnAddress, "lockedEnd", user); @@ -475,7 +411,7 @@ public static void voteForSourceWeights(String sourceName, BigInteger userWeight "Weight has to be between 0 and 10000"); BigInteger nextUserVote = lastUserVote.at(user).getOrDefault(sourceName, BigInteger.ZERO).add(WEIGHT_VOTE_DELAY); - Context.require(timestamp.compareTo(nextUserVote) >= 0, "Cannot vote so often"); + Context.require(timestamp.compareTo(nextUserVote) >= 0, "Cannot vote so often"); int sourceType = sourceTypes.get(sourceName) - 1; Context.require(sourceType >= 0, "Source not added"); diff --git a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTest.java b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTest.java index 6a08ec543..bfee35230 100644 --- a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTest.java +++ b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTest.java @@ -17,17 +17,14 @@ package network.balanced.score.core.rewards; import com.iconloop.score.test.Account; -import network.balanced.score.lib.structs.DistributionPercentage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; import java.math.BigInteger; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; class RewardsTest extends RewardsTestBase { @@ -44,9 +41,10 @@ void getDataSources() { "getDataSources"); // Assert - assertEquals(2, dataSources.size()); + assertEquals(3, dataSources.size()); assertTrue(dataSources.containsKey("Loans")); assertTrue(dataSources.containsKey("sICX/ICX")); + assertTrue(dataSources.containsKey("sICX/bnUSD")); } @SuppressWarnings("unchecked") @@ -56,151 +54,10 @@ void getDataSourceNames() { List names = (List) rewardsScore.call("getDataSourceNames"); // Assert - assertEquals(2, names.size()); - assertTrue(names.contains("Loans")); - assertTrue(names.contains("sICX/ICX")); - } - - @SuppressWarnings("unchecked") - @Test - void getRecipients() { - // Act - List names = (List) rewardsScore.call("getRecipients"); - - // Assert - assertEquals(5, names.size()); - assertTrue(names.contains("Loans")); - assertTrue(names.contains("sICX/ICX")); - assertTrue(names.contains("Worker Tokens")); - assertTrue(names.contains("Reserve Fund")); - assertTrue(names.contains("DAOfund")); - } - - @SuppressWarnings("unchecked") - @Test - void removeDataSource() { - // Arrange - icxPoolDist.dist_percent = BigInteger.ZERO; //0% - loansDist.dist_percent = loansDist.dist_percent.multiply(BigInteger.TWO); //40% - - DistributionPercentage[] distributionPercentages = new DistributionPercentage[]{loansDist, icxPoolDist, - bwtDist, reserveDist, daoDist}; - rewardsScore.invoke(governance, "updateBalTokenDistPercentage", (Object) distributionPercentages); - rewardsScore.invoke(admin, "distribute"); - - // Act - rewardsScore.invoke(governance, "removeDataSource", "sICX/ICX"); - - // Assert - List names = (List) rewardsScore.call("getDataSourceNames"); - - assertEquals(1, names.size()); - assertTrue(names.contains("Loans")); - } - - @Test - void removeDataSource_nonEmptyDistribution() { - // Arrange - String expectedErrorMessage = RewardsImpl.TAG + ": Data source rewards percentage must be set to 0 before " + - "removing."; - - // Act - Executable removeNonEmpty = () -> rewardsScore.invoke(governance, "removeDataSource", "sICX/ICX"); - - // Assert - expectErrorMessage(removeNonEmpty, expectedErrorMessage); - } - - @SuppressWarnings("unchecked") - @Test - void removeDataSource_nonExisting() { - // Act - rewardsScore.invoke(governance, "removeDataSource", "test"); - - // Assert - List names = (List) rewardsScore.call("getDataSourceNames"); - - assertEquals(2, names.size()); + assertEquals(3, names.size()); assertTrue(names.contains("Loans")); assertTrue(names.contains("sICX/ICX")); - } - - @Test - void removeDataSource_notGovernance() { - // Arrange - String expectedErrorMessage = "Authorization Check: Authorization failed. Caller: " + admin.getAddress() + " " + - "Authorized Caller: " + governance.getAddress(); - - // Act - Executable removeAsAdmin = () -> rewardsScore.invoke(admin, "removeDataSource", "sICX/ICX"); - - // Assert - expectErrorMessage(removeAsAdmin, expectedErrorMessage); - } - - @Test - void updateBalTokenDistPercentage_nonExistingName() { - // Arrange - DistributionPercentage testDist = new DistributionPercentage(); - testDist.recipient_name = "test"; - testDist.dist_percent = BigInteger.TEN; - String expectedErrorMessage = RewardsImpl.TAG + ": Recipient test does not exist."; - - // Act - DistributionPercentage[] distributionPercentages = new DistributionPercentage[]{testDist, icxPoolDist, - bwtDist, reserveDist, daoDist}; - Executable updateWithWrongName = () -> rewardsScore.invoke(governance, "updateBalTokenDistPercentage", - (Object) distributionPercentages); - - // Assert - expectErrorMessage(updateWithWrongName, expectedErrorMessage); - } - - @Test - void updateBalTokenDistPercentage_wrongSum() { - // Arrange - icxPoolDist.dist_percent = BigInteger.ZERO; //0% - String expectedErrorMessage = RewardsImpl.TAG + ": Total percentage does not sum up to 100."; - - // Act - DistributionPercentage[] distributionPercentages = new DistributionPercentage[]{loansDist, icxPoolDist, - bwtDist, reserveDist, daoDist}; - Executable updateWithWrongSum = () -> rewardsScore.invoke(governance, "updateBalTokenDistPercentage", - (Object) distributionPercentages); - - // Assert - expectErrorMessage(updateWithWrongSum, expectedErrorMessage); - } - - @Test - void updateBalTokenDistPercentage_wrongLength() { - // Arrange - String expectedErrorMessage = RewardsImpl.TAG + ": Recipient lists lengths mismatched!"; - - // Act - DistributionPercentage[] distributionPercentages = new DistributionPercentage[]{loansDist, bwtDist, - reserveDist, daoDist}; - Executable updateWithWrongLength = () -> rewardsScore.invoke(governance, "updateBalTokenDistPercentage", - (Object) distributionPercentages); - - // Assert - expectErrorMessage(updateWithWrongLength, expectedErrorMessage); - } - - @Test - void updateBalTokenDistPercentage_notGovernance() { - // Arrange - String expectedErrorMessage = "Authorization Check: Authorization failed. Caller: " + admin.getAddress() + " " + - "Authorized Caller: " + governance.getAddress(); - - // Act - DistributionPercentage[] distributionPercentages = new DistributionPercentage[]{loansDist, icxPoolDist, - bwtDist, reserveDist, daoDist}; - Executable updateAsAdmin = () -> rewardsScore.invoke(admin, "updateBalTokenDistPercentage", - (Object) distributionPercentages); - - // Assert - expectErrorMessage(updateAsAdmin, expectedErrorMessage); + assertTrue(names.contains("sICX/bnUSD")); } @Test @@ -248,118 +105,7 @@ void tokenFallback_baln() { // Act & Assert rewardsScore.invoke(baln.account, "tokenFallback", account.getAddress(), BigInteger.TEN, new byte[0]); } - - @Test - void tokenFallback_notBaln() { - // Arrange - Account account = sm.createAccount(); - String expectedErrorMessage = RewardsImpl.TAG + ": The Rewards SCORE can only accept BALN tokens"; - - // Act & Assert - Executable tokenFallbackBwt = () -> rewardsScore.invoke(bwt.account, "tokenFallback", account.getAddress(), - BigInteger.TEN, new byte[0]); - expectErrorMessage(tokenFallbackBwt, expectedErrorMessage); - } - - @Test - void getApy() { - // Arrange - Account account = sm.createAccount(); - - BigInteger balnPrice = EXA; - BigInteger loansValue = BigInteger.valueOf(1000).multiply(EXA); - when(dex.mock.getBalnPrice()).thenReturn(balnPrice); - when(loans.mock.getBnusdValue("Loans")).thenReturn(loansValue); - - BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.ONE); - BigInteger year = BigInteger.valueOf(365); - - BigInteger loansYearlyEmission = year.multiply(emission).multiply(loansDist.dist_percent); - BigInteger expectedAPY = loansYearlyEmission.multiply(balnPrice).divide(EXA.multiply(loansValue)); - - //Act - BigInteger apy = (BigInteger) rewardsScore.call("getAPY", "Loans"); - - // Assert - assertEquals(expectedAPY, apy); - } - - @SuppressWarnings("unchecked") - @Test - void recipientAt_multipleSnapshots() { - // Arrange - BigInteger expectedLoansDist = loansDist.dist_percent.add(ICX.divide(BigInteger.TEN));//30% - BigInteger expectedSwapDist = icxPoolDist.dist_percent.divide(BigInteger.TWO);//10% - BigInteger originalLoansDist = loansDist.dist_percent; - BigInteger originalSwapDist = icxPoolDist.dist_percent; - //create a set of snapshot to be able to search for recipients - snapshotDistributionPercentage(); - snapshotDistributionPercentage(); - snapshotDistributionPercentage(); - snapshotDistributionPercentage(); - - loansDist.dist_percent = expectedLoansDist; - icxPoolDist.dist_percent = expectedSwapDist; - // capture day, where the specific change took place - BigInteger day = (BigInteger) rewardsScore.call("getDay"); - snapshotDistributionPercentage(); - - // reset distribution percentages - loansDist.dist_percent = originalLoansDist; - icxPoolDist.dist_percent = originalSwapDist; - - snapshotDistributionPercentage(); - snapshotDistributionPercentage(); - snapshotDistributionPercentage(); - - // Act - Map distributions = (Map) rewardsScore.call("recipientAt", day); - - // Assert - assertEquals(expectedLoansDist, distributions.get("Loans")); - assertEquals(expectedSwapDist, distributions.get("sICX/ICX")); - // TODO Check if the distribution percentages are back to original values - } - - @SuppressWarnings("unchecked") - @Test - void recipientAt_withNewDataSourceInTheFuture() { - // Arrange - sm.getBlock().increase(DAY); - sm.getBlock().increase(DAY); - DistributionPercentage testDist = new DistributionPercentage(); - testDist.recipient_name = "test"; - testDist.dist_percent = loansDist.dist_percent.divide(BigInteger.TWO); - loansDist.dist_percent = loansDist.dist_percent.divide(BigInteger.TWO); - - rewardsScore.invoke(governance, "addNewDataSource", testDist.recipient_name, loans.getAddress()); - - // Act - Object distributionPercentages = new DistributionPercentage[]{testDist, loansDist, icxPoolDist, bwtDist, - reserveDist, daoDist}; - BigInteger day = (BigInteger) rewardsScore.call("getDay"); - - rewardsScore.invoke(governance, "updateBalTokenDistPercentage", distributionPercentages); - Map distributionsYesterday = (Map) rewardsScore.call("recipientAt", - day.subtract(BigInteger.ONE)); - Map distributionsToday = (Map) rewardsScore.call("recipientAt", day); - - // Assert - assertFalse(distributionsYesterday.containsKey("test")); - assertEquals(testDist.dist_percent, distributionsToday.get("test")); - } - - @Test - void recipientAt_dayLessThanZero() { - // Arrange - String expectedErrorMessage = RewardsImpl.TAG + ": day:-1 must be equal to or greater then Zero"; - - // Act - Executable atDayLessThanZero = () -> rewardsScore.invoke(admin, "recipientAt", BigInteger.valueOf(-1)); - - // Assert - expectErrorMessage(atDayLessThanZero, expectedErrorMessage); - } } + diff --git a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestBase.java b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestBase.java index fa405407e..e8a7b3a7e 100644 --- a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestBase.java +++ b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestBase.java @@ -20,22 +20,23 @@ import com.iconloop.score.test.Account; import com.iconloop.score.test.Score; import com.iconloop.score.test.ServiceManager; -import network.balanced.score.lib.interfaces.BoostedBaln; -import network.balanced.score.lib.interfaces.BoostedBalnScoreInterface; -import network.balanced.score.lib.interfaces.DataSource; -import network.balanced.score.lib.interfaces.DataSourceScoreInterface; -import network.balanced.score.lib.interfaces.GovernanceScoreInterface; -import network.balanced.score.lib.interfaces.tokens.IRC2Mintable; -import network.balanced.score.lib.interfaces.tokens.IRC2MintableScoreInterface; -import network.balanced.score.lib.structs.DistributionPercentage; +import network.balanced.score.lib.interfaces.*; import network.balanced.score.lib.test.UnitTest; +import network.balanced.score.lib.utils.Names; import network.balanced.score.lib.test.mock.MockContract; +import network.balanced.score.lib.test.mock.MockBalanced; +import static network.balanced.score.lib.utils.Constants.MICRO_SECONDS_IN_A_DAY; + import score.Address; import java.math.BigInteger; import java.util.Map; +import static network.balanced.score.core.rewards.weight.SourceWeightController.VOTE_POINTS; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; class RewardsTestBase extends UnitTest { @@ -46,44 +47,50 @@ class RewardsTestBase extends UnitTest { static final ServiceManager sm = getServiceManager(); static final Account owner = sm.createAccount(); - static final Account admin = sm.createAccount(); - DistributionPercentage loansDist = new DistributionPercentage(); - DistributionPercentage icxPoolDist = new DistributionPercentage(); - DistributionPercentage bwtDist = new DistributionPercentage(); - DistributionPercentage reserveDist = new DistributionPercentage(); - DistributionPercentage daoDist = new DistributionPercentage(); + BigInteger defaultPlatformDist = ICX.divide(BigInteger.valueOf(5)); int scoreCount = 0; Account governance; - final Account daoFund = Account.newScoreAccount(scoreCount++); - final Account reserve = Account.newScoreAccount(scoreCount++); - - MockContract dex; - MockContract loans; - MockContract baln; - MockContract bwt; + Account daoFund; + Account reserve; + Account user; + MockBalanced mockBalanced; + + MockContract dex; + MockContract loans; + MockContract baln; + MockContract bwt; MockContract bBaln; Score rewardsScore; void setup() throws Exception { - governance = new MockContract<>(GovernanceScoreInterface.class, sm, admin).account; - dex = new MockContract<>(DataSourceScoreInterface.class, sm, admin); - loans = new MockContract<>(DataSourceScoreInterface.class, sm, admin); - baln = new MockContract<>(IRC2MintableScoreInterface.class, sm, admin); - bwt = new MockContract<>(IRC2MintableScoreInterface.class, sm, admin); - bBaln = new MockContract<>(BoostedBalnScoreInterface.class, sm, admin); + mockBalanced = new MockBalanced(sm, owner); + governance = mockBalanced.governance.account; + dex = mockBalanced.dex; + loans = mockBalanced.loans; + baln = mockBalanced.baln; + bwt = mockBalanced.bwt; + bBaln = mockBalanced.bBaln; + daoFund = mockBalanced.daofund.account; + reserve = mockBalanced.reserve.account; + + user = sm.createAccount(); BigInteger startTime = BigInteger.valueOf(sm.getBlock().getTimestamp()); - sm.getBlock().increase(DAY); - rewardsScore = sm.deploy(owner, RewardsImpl.class, governance.getAddress()); + // One vote period before being able to start voting + sm.getBlock().increase(DAY*10); - rewardsScore.invoke(governance, "setAdmin", admin.getAddress()); - rewardsScore.invoke(admin, "setTimeOffset", startTime); + rewardsScore = sm.deploy(owner, RewardsImpl.class, governance.getAddress()); + setupDistributions(); + rewardsScore.invoke(owner, "setTimeOffset", startTime); + rewardsScore.invoke(owner, "addDataProvider", loans.getAddress()); + rewardsScore.invoke(owner, "addDataProvider", dex.getAddress()); - rewardsScore.invoke(governance, "addNewDataSource", "sICX/ICX", dex.getAddress()); - rewardsScore.invoke(governance, "addNewDataSource", "Loans", loans.getAddress()); + rewardsScore.invoke(owner, "createDataSource", "sICX/ICX", dex.getAddress(), 0); + rewardsScore.invoke(owner, "createDataSource", "sICX/bnUSD", dex.getAddress(), 0); + rewardsScore.invoke(owner, "createDataSource", "Loans", loans.getAddress(), 0); Map emptyDataSource = Map.of( "_balance", BigInteger.ZERO, @@ -91,55 +98,45 @@ void setup() throws Exception { ); when(loans.mock.getBalanceAndSupply(any(String.class), any(String.class))).thenReturn(emptyDataSource); when(dex.mock.getBalanceAndSupply(any(String.class), any(String.class))).thenReturn(emptyDataSource); - when(loans.mock.precompute(any(BigInteger.class), any(BigInteger.class))).thenReturn(true); - when(dex.mock.precompute(any(BigInteger.class), any(BigInteger.class))).thenReturn(true); when(bBaln.mock.balanceOf(any(Address.class), any(BigInteger.class))).thenReturn(BigInteger.ZERO); when(bBaln.mock.totalSupply(any(BigInteger.class))).thenReturn(BigInteger.ZERO); - rewardsScore.invoke(admin, "setBaln", baln.getAddress()); - rewardsScore.invoke(admin, "setBwt", bwt.getAddress()); - rewardsScore.invoke(admin, "setDaofund", daoFund.getAddress()); - rewardsScore.invoke(admin, "setReserve", reserve.getAddress()); - rewardsScore.invoke(admin, "setBoostedBaln", bBaln.getAddress()); - - rewardsScore.invoke(owner, "addDataProvider", loans.getAddress()); - rewardsScore.invoke(owner, "addDataProvider", dex.getAddress()); - setupDistributions(); + BigInteger currentTime = BigInteger.valueOf(sm.getBlock().getTimestamp()); + BigInteger unlockTime = currentTime.add(MICRO_SECONDS_IN_A_DAY.multiply(BigInteger.valueOf(365))); + when(bBaln.mock.lockedEnd(any(Address.class))).thenReturn(unlockTime); sm.getBlock().increase(DAY); - syncDistributions(); - } - - protected void setupDistributions() { - loansDist.recipient_name = "Loans"; - loansDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% - icxPoolDist.recipient_name = "sICX/ICX"; - icxPoolDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% + mockUserWeight(user, EXA); - bwtDist.recipient_name = "Worker Tokens"; - bwtDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% - reserveDist.recipient_name = "Reserve Fund"; - reserveDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% + vote(user, "sICX/ICX", VOTE_POINTS.divide(BigInteger.TWO)); + vote(user, "sICX/bnUSD", VOTE_POINTS.divide(BigInteger.TWO)); + rewardsScore.invoke(owner, "setFixedSourcePercentage", "Loans", EXA.divide(BigInteger.TEN)); - daoDist.recipient_name = "DAOfund"; - daoDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% + // One vote period to apply votes + sm.getBlock().increase(DAY*10); - DistributionPercentage[] distributionPercentages = new DistributionPercentage[]{loansDist, icxPoolDist, - bwtDist, reserveDist, daoDist}; + rewardsScore.invoke(owner, "updateRelativeSourceWeight", "sICX/ICX", + BigInteger.valueOf(sm.getBlock().getTimestamp())); + rewardsScore.invoke(owner, "updateRelativeSourceWeight", "sICX/bnUSD", + BigInteger.valueOf(sm.getBlock().getTimestamp())); + } - rewardsScore.invoke(governance, "updateBalTokenDistPercentage", (Object) distributionPercentages); + protected void setupDistributions() { + rewardsScore.invoke(owner, "setPlatformDistPercentage", Names.DAOFUND, defaultPlatformDist); + rewardsScore.invoke(owner, "setPlatformDistPercentage", Names.WORKERTOKEN, defaultPlatformDist); + rewardsScore.invoke(owner, "setPlatformDistPercentage", Names.RESERVE, defaultPlatformDist); } void syncDistributions() { BigInteger currentDay = (BigInteger) rewardsScore.call("getDay"); for (long i = 0; i < currentDay.intValue(); i++) { - rewardsScore.invoke(admin, "distribute"); + rewardsScore.invoke(owner, "distribute"); } } - void mockBalanceAndSupply(MockContract dataSource, String name, Address address, + void mockBalanceAndSupply(MockContract dataSource, String name, Address address, BigInteger balance, BigInteger supply) { Map balanceAndSupply = Map.of( "_balance", balance, @@ -159,7 +156,7 @@ void verifyBalnReward(Address address, BigInteger expectedReward) { BigInteger getOneDayRewards(Address address) { BigInteger rewardsPre = (BigInteger) rewardsScore.call("getBalnHolding", address.toString()); sm.getBlock().increase(DAY); - rewardsScore.invoke(admin, "distribute"); + rewardsScore.invoke(owner, "distribute"); BigInteger rewardsPost = (BigInteger) rewardsScore.call("getBalnHolding", address.toString()); return rewardsPost.subtract(rewardsPre); @@ -169,14 +166,6 @@ Object getUserSources(Address address) { return rewardsScore.call("getUserSources", address.toString()); } - void snapshotDistributionPercentage() { - Object distributionPercentages = new DistributionPercentage[]{loansDist, icxPoolDist, bwtDist, reserveDist, - daoDist}; - - rewardsScore.invoke(governance, "updateBalTokenDistPercentage", distributionPercentages); - sm.getBlock().increase(DAY); - } - void vote(Account user, String name, BigInteger weight) { rewardsScore.invoke(user, "voteForSource", name, weight); } @@ -184,4 +173,10 @@ void vote(Account user, String name, BigInteger weight) { void mockUserWeight(Account user, BigInteger weight) { when(bBaln.mock.getLastUserSlope(user.getAddress())).thenReturn(weight); } + + @SuppressWarnings("unchecked") + BigInteger getVotePercentage(String name){ + Map> data = (Map>) rewardsScore.call("getDistributionPercentages"); + return data.get("Voting").get(name).add(data.get("Fixed").getOrDefault(name, BigInteger.ZERO)); + } } diff --git a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestExternalRewards.java b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestExternalRewards.java new file mode 100644 index 000000000..a6399822b --- /dev/null +++ b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestExternalRewards.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2022-2022 Balanced.network. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package network.balanced.score.core.rewards; + +import com.iconloop.score.test.Account; + +import score.Address; + +import org.json.JSONArray; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static network.balanced.score.lib.utils.Constants.MICRO_SECONDS_IN_A_DAY; +import static network.balanced.score.lib.utils.Constants.MICRO_SECONDS_IN_A_SECOND; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +class RewardsTestExternalRewards extends RewardsTestBase { + Account externalRewardsProvider = sm.createAccount(); + + @BeforeEach + void setup() throws Exception { + super.setup(); + + rewardsScore.invoke(owner, "configureExternalRewardProvider", externalRewardsProvider.getAddress(), true); + rewardsScore.invoke(owner, "configureExternalReward", mockBalanced.sicx.getAddress(), true); + } + + private void nextDay() { + BigInteger offset = (BigInteger) rewardsScore.call("getTimeOffset"); + BigInteger blockTime = BigInteger.valueOf(sm.getBlock().getTimestamp()).subtract(offset); + BigInteger roundedBlockTime = blockTime.divide(MICRO_SECONDS_IN_A_DAY).multiply(MICRO_SECONDS_IN_A_DAY); + BigInteger diff = blockTime.subtract(roundedBlockTime).divide(MICRO_SECONDS_IN_A_SECOND); + sm.getBlock().increase(DAY - diff.divide(BigInteger.TWO).longValue()); + } + + @Test + @SuppressWarnings("unchecked") + void externalRewards_base() { + // Arrange + Account account = sm.createAccount(); + Account supplyAccount = sm.createAccount(); + BigInteger balance = BigInteger.TWO.multiply(EXA); + BigInteger totalSupply = BigInteger.TEN.multiply(EXA); + BigInteger sICXRewards = BigInteger.valueOf(1000).multiply(EXA); + BigInteger expectedRewards = BigInteger.valueOf(200).multiply(EXA); + + // Act + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", totalSupply.subtract(balance), + supplyAccount.getAddress().toString(), totalSupply.subtract(balance)); + + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", totalSupply, + account.getAddress().toString(), balance); + BigInteger startTime = BigInteger.valueOf(sm.getBlock().getTimestamp()); + + mockBalanceAndSupply(dex, "sICX/ICX", account.getAddress(), balance, totalSupply); + + List _data = new ArrayList<>(1); + _data.add(Map.of("source", "sICX/ICX", "amount", sICXRewards)); + JSONArray data = new JSONArray(_data); + + rewardsScore.invoke(mockBalanced.sicx.account, "tokenFallback", externalRewardsProvider.getAddress(), + sICXRewards, data.toString().getBytes()); + BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); + BigInteger dist = getVotePercentage("sICX/ICX").multiply(emission).divide(EXA); + BigInteger userDist = dist.multiply(balance).divide(totalSupply); + + // Assert + // starts after 1 day, fully distributed after 2 + nextDay(); + Map rewards = (Map) rewardsScore.call("getRewards", + account.getAddress().toString()); + assertEquals(BigInteger.ZERO, rewards.get(mockBalanced.sicx.getAddress().toString())); + + nextDay(); + rewards = (Map) rewardsScore.call("getRewards", + account.getAddress().toString()); + rewardsScore.invoke(account, "claimRewards", getUserSources(account.getAddress())); + BigInteger timeInUS = BigInteger.valueOf(sm.getBlock().getTimestamp()); + BigInteger diff = timeInUS.subtract(startTime); + BigInteger expectedBalnRewards = userDist.multiply(diff).divide(MICRO_SECONDS_IN_A_DAY); + assertEquals(expectedRewards, rewards.get(mockBalanced.sicx.getAddress().toString())); + verify(mockBalanced.sicx.mock).transfer(account.getAddress(), expectedRewards, new byte[0]); + verifyBalnReward(account.getAddress(), expectedBalnRewards.subtract(BigInteger.ONE)); + + + // No more is distributed + nextDay(); + rewards = (Map) rewardsScore.call("getRewards", + account.getAddress().toString()); + assertEquals(BigInteger.ZERO, rewards.get(mockBalanced.sicx.getAddress().toString())); + } + + @Test + @SuppressWarnings("unchecked") + void externalRewards_continuousRewards() { + // Arrange + Account account = sm.createAccount(); + Account supplyAccount = sm.createAccount(); + BigInteger balance = BigInteger.TWO.multiply(EXA); + BigInteger totalSupply = BigInteger.TEN.multiply(EXA); + BigInteger sICXRewards = BigInteger.valueOf(1000).multiply(EXA); + BigInteger expectedRewards = BigInteger.valueOf(200).multiply(EXA); + + // Act + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", totalSupply.subtract(balance), + supplyAccount.getAddress().toString(), totalSupply.subtract(balance)); + + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", totalSupply, + account.getAddress().toString(), balance); + + mockBalanceAndSupply(dex, "sICX/ICX", account.getAddress(), balance, totalSupply); + + List _data = new ArrayList<>(1); + _data.add(Map.of("source", "sICX/ICX", "amount", sICXRewards)); + JSONArray data = new JSONArray(_data); + + rewardsScore.invoke(mockBalanced.sicx.account, "tokenFallback", externalRewardsProvider.getAddress(), + sICXRewards, data.toString().getBytes()); + + // Assert + // starts after 1 day + nextDay(); + sm.getBlock().increase(DAY / 4); + Map rewards = (Map) rewardsScore.call("getRewards", + account.getAddress().toString()); + assertEquals(expectedRewards.divide(BigInteger.valueOf(4)), rewards.get(mockBalanced.sicx.getAddress().toString())); + } + + @Test + @SuppressWarnings("unchecked") + void externalRewards_MultipleSources() { + // Arrange + Account account = sm.createAccount(); + Account supplyAccount = sm.createAccount(); + BigInteger balance1 = BigInteger.TWO.multiply(EXA); + BigInteger balance2 = BigInteger.valueOf(3).multiply(EXA); + BigInteger totalSupply1 = BigInteger.TEN.multiply(EXA); + BigInteger totalSupply2 = BigInteger.valueOf(30).multiply(EXA); + BigInteger sICXRewards1 = BigInteger.valueOf(1000).multiply(EXA); + BigInteger sICXRewards2 = BigInteger.valueOf(1500).multiply(EXA); + BigInteger expectedRewards1 = BigInteger.valueOf(200).multiply(EXA); // 20% of total + BigInteger expectedRewards2 = BigInteger.valueOf(150).multiply(EXA); // 10% of total + + // Act + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", totalSupply1.subtract(balance1), + supplyAccount.getAddress().toString(), totalSupply1.subtract(balance1)); + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/bnUSD", totalSupply2.subtract(balance2), + supplyAccount.getAddress().toString(), totalSupply2.subtract(balance2)); + + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", totalSupply1, + account.getAddress().toString(), balance1); + + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/bnUSD", totalSupply2, + account.getAddress().toString(), balance2); + + mockBalanceAndSupply(dex, "sICX/ICX", account.getAddress(), balance1, totalSupply1); + mockBalanceAndSupply(dex, "sICX/bnUSD", account.getAddress(), balance2, totalSupply2); + + List _data = new ArrayList<>(2); + _data.add(Map.of("source", "sICX/ICX", "amount", sICXRewards1)); + _data.add(Map.of("source", "sICX/bnUSD", "amount", sICXRewards2)); + JSONArray data = new JSONArray(_data); + + rewardsScore.invoke(mockBalanced.sicx.account, "tokenFallback", externalRewardsProvider.getAddress(), + sICXRewards1.add(sICXRewards2), data.toString().getBytes()); + // starts after 1 day, fully distributed after 2 + nextDay(); + nextDay(); + + // Assert + Map rewards = (Map) rewardsScore.call("getRewards", + account.getAddress().toString()); + rewardsScore.invoke(account, "claimRewards", getUserSources(account.getAddress())); + verify(mockBalanced.sicx.mock).transfer(account.getAddress(), expectedRewards1.add(expectedRewards2), + new byte[0]); + assertEquals(expectedRewards1.add(expectedRewards2), rewards.get(mockBalanced.sicx.getAddress().toString())); + } + + @Test + @SuppressWarnings("unchecked") + void externalRewards_MultipleUsers() { + // Arrange + Account account1 = sm.createAccount(); + Account account2 = sm.createAccount(); + Account supplyAccount = sm.createAccount(); + BigInteger balance1 = BigInteger.TWO.multiply(EXA); + BigInteger balance2 = BigInteger.valueOf(3).multiply(EXA); + BigInteger totalSupply = BigInteger.TEN.multiply(EXA); + BigInteger sICXRewards = BigInteger.valueOf(1000).multiply(EXA); + BigInteger expectedRewards1 = BigInteger.valueOf(200).multiply(EXA); // 20% of total + BigInteger expectedRewards2 = BigInteger.valueOf(300).multiply(EXA); // 30% of total + + // Act + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", + totalSupply.subtract(balance1.subtract(balance2)), + supplyAccount.getAddress().toString(), totalSupply.subtract(balance1).subtract(balance2)); + + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", totalSupply.subtract(balance2), + account1.getAddress().toString(), balance1); + + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", totalSupply, + account2.getAddress().toString(), balance2); + + mockBalanceAndSupply(dex, "sICX/ICX", account1.getAddress(), balance1, totalSupply); + mockBalanceAndSupply(dex, "sICX/ICX", account2.getAddress(), balance2, totalSupply); + + List _data = new ArrayList<>(2); + _data.add(Map.of("source", "sICX/ICX", "amount", sICXRewards)); + JSONArray data = new JSONArray(_data); + + rewardsScore.invoke(mockBalanced.sicx.account, "tokenFallback", externalRewardsProvider.getAddress(), + sICXRewards, data.toString().getBytes()); + // starts after 1 day, fully distributed after 2 + nextDay(); + nextDay(); + + // Assert + Map rewards = (Map) rewardsScore.call("getRewards", + account1.getAddress().toString()); + rewardsScore.invoke(account1, "claimRewards", getUserSources(account1.getAddress())); + verify(mockBalanced.sicx.mock).transfer(account1.getAddress(), expectedRewards1, new byte[0]); + assertEquals(expectedRewards1, rewards.get(mockBalanced.sicx.getAddress().toString())); + + rewards = (Map) rewardsScore.call("getRewards", + account2.getAddress().toString()); + rewardsScore.invoke(account2, "claimRewards", getUserSources(account2.getAddress())); + verify(mockBalanced.sicx.mock).transfer(account2.getAddress(), expectedRewards2, new byte[0]); + assertEquals(expectedRewards2, rewards.get(mockBalanced.sicx.getAddress().toString())); + } + + @Test + @SuppressWarnings("unchecked") + void externalRewards_MultipleTokens() { + // Arrange + Account account = sm.createAccount(); + Account supplyAccount = sm.createAccount(); + BigInteger balance = BigInteger.TWO.multiply(EXA); + BigInteger totalSupply = BigInteger.TEN.multiply(EXA); + BigInteger sICXRewards = BigInteger.valueOf(1000).multiply(EXA); + BigInteger bnUSDRewards = BigInteger.valueOf(200).multiply(EXA); + BigInteger expectedSICXRewards = BigInteger.valueOf(200).multiply(EXA); + BigInteger expectedBnUSDRewards = BigInteger.valueOf(40).multiply(EXA); + + rewardsScore.invoke(owner, "configureExternalReward", mockBalanced.bnUSD.getAddress(), true); + + // Act + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", totalSupply.subtract(balance), + supplyAccount.getAddress().toString(), totalSupply.subtract(balance)); + + rewardsScore.invoke(dex.account, "updateBalanceAndSupply", "sICX/ICX", totalSupply, + account.getAddress().toString(), balance); + + mockBalanceAndSupply(dex, "sICX/ICX", account.getAddress(), balance, totalSupply); + + List _data = new ArrayList<>(1); + _data.add(Map.of("source", "sICX/ICX", "amount", sICXRewards)); + JSONArray data = new JSONArray(_data); + rewardsScore.invoke(mockBalanced.sicx.account, "tokenFallback", externalRewardsProvider.getAddress(), + sICXRewards, data.toString().getBytes()); + _data = new ArrayList<>(1); + _data.add(Map.of("source", "sICX/ICX", "amount", bnUSDRewards)); + data = new JSONArray(_data); + rewardsScore.invoke(mockBalanced.bnUSD.account, "tokenFallback", externalRewardsProvider.getAddress(), + bnUSDRewards, data.toString().getBytes()); + + // Assert + // starts after 1 day, fully distributed after 2 + nextDay(); + nextDay(); + Map rewards = (Map) rewardsScore.call("getRewards", + account.getAddress().toString()); + rewardsScore.invoke(account, "claimRewards", getUserSources(account.getAddress())); + assertEquals(expectedSICXRewards, rewards.get(mockBalanced.sicx.getAddress().toString())); + assertEquals(expectedBnUSDRewards, rewards.get(mockBalanced.bnUSD.getAddress().toString())); + verify(mockBalanced.sicx.mock).transfer(account.getAddress(), expectedSICXRewards, new byte[0]); + verify(mockBalanced.bnUSD.mock).transfer(account.getAddress(), expectedBnUSDRewards, new byte[0]); + + // No more is distributed + nextDay(); + rewards = (Map) rewardsScore.call("getRewards", + account.getAddress().toString()); + assertEquals(BigInteger.ZERO, rewards.get(mockBalanced.sicx.getAddress().toString())); + } + + @Test + void externalRewards_verification() { + Executable whiteListedTokens = () -> rewardsScore.invoke(mockBalanced.bnUSD.account, "tokenFallback", + externalRewardsProvider.getAddress(), BigInteger.ONE, new byte[0]); + expectErrorMessage(whiteListedTokens, "Only whitelisted tokens is allowed as rewards tokens"); + + Executable whiteListedProviders = () -> rewardsScore.invoke(mockBalanced.sicx.account, "tokenFallback", + owner.getAddress(), BigInteger.ONE, new byte[0]); + expectErrorMessage(whiteListedProviders, "Only whitelisted addresses is allowed to provider external rewards"); + + List _data = new ArrayList<>(1); + _data.add(Map.of("source", "sICX/ICX", "amount", BigInteger.TEN)); + JSONArray data = new JSONArray(_data); + Executable invalidAmounts = () -> rewardsScore.invoke(mockBalanced.sicx.account, "tokenFallback", + externalRewardsProvider.getAddress(), + BigInteger.ONE, data.toString().getBytes()); + expectErrorMessage(invalidAmounts, "Total allocations do not match amount deposited"); + } +} \ No newline at end of file diff --git a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestMigration.java b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestMigration.java deleted file mode 100644 index be4751c8c..000000000 --- a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestMigration.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (c) 2022-2022 Balanced.network. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package network.balanced.score.core.rewards; - -import static network.balanced.score.core.rewards.weight.SourceWeightController.VOTE_POINTS; -import static network.balanced.score.lib.utils.Constants.MICRO_SECONDS_IN_A_DAY; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import java.math.BigInteger; -import java.util.Map; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; - -import com.iconloop.score.test.Account; - -import network.balanced.score.lib.structs.DistributionPercentage; -import score.Address; - -class RewardsTestMigration extends RewardsTestBase { - - BigInteger migrationDay; - DistributionPercentage sICXbnUSDDist = new DistributionPercentage(); - - @BeforeEach - void setup() throws Exception { - super.setup(); - sm.getBlock().increase(DAY *10); - syncDistributions(); - BigInteger day = (BigInteger) rewardsScore.call("getDay"); - migrationDay = day.add(BigInteger.valueOf(14)); - - rewardsScore.invoke(owner, "setMigrateToVotingDay", migrationDay); - - BigInteger currentTime = BigInteger.valueOf(sm.getBlock().getTimestamp()); - BigInteger unlockTime = currentTime.add(MICRO_SECONDS_IN_A_DAY.multiply(BigInteger.valueOf(365))); - when(bBaln.mock.lockedEnd(any(Address.class))).thenReturn(unlockTime); - } - - protected void setupDistributions() { - rewardsScore.invoke(governance, "addNewDataSource", "sICX/bnUSD", dex.getAddress()); - - loansDist.recipient_name = "Loans"; - loansDist.dist_percent = ICX.divide(BigInteger.valueOf(10)); //10% - - sICXbnUSDDist.recipient_name = "sICX/bnUSD"; - sICXbnUSDDist.dist_percent = ICX.divide(BigInteger.valueOf(10)); //10% - - icxPoolDist.recipient_name = "sICX/ICX"; - icxPoolDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% - - bwtDist.recipient_name = "Worker Tokens"; - bwtDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% - - reserveDist.recipient_name = "Reserve Fund"; - reserveDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% - - daoDist.recipient_name = "DAOfund"; - daoDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% - - DistributionPercentage[] distributionPercentages = new DistributionPercentage[]{loansDist, sICXbnUSDDist, icxPoolDist, - bwtDist, reserveDist, daoDist}; - - rewardsScore.invoke(governance, "updateBalTokenDistPercentage", (Object) distributionPercentages); - } - - @Test - void migration() { - // Arrange - Account user = sm.createAccount(); - mockUserWeight(user, EXA); - mockBalanceAndSupply(loans, "Loans", owner.getAddress(), BigInteger.ONE, BigInteger.ONE); - mockBalanceAndSupply(dex, "sICX/ICX", owner.getAddress(), BigInteger.ONE, BigInteger.ONE); - mockBalanceAndSupply(dex, "sICX/bnUSD", owner.getAddress(), BigInteger.ONE, BigInteger.ONE); - - BigInteger votingPercentage = BigInteger.valueOf(30).multiply(EXA).divide(BigInteger.valueOf(100)); - - rewardsScore.invoke(loans.account, "updateRewardsData", "Loans", BigInteger.ZERO, owner.getAddress(), BigInteger.ONE); - rewardsScore.invoke(dex.account, "updateRewardsData", "sICX/ICX", BigInteger.ZERO, owner.getAddress(), BigInteger.ONE); - rewardsScore.invoke(dex.account, "updateRewardsData", "sICX/bnUSD", BigInteger.ZERO, owner.getAddress(), BigInteger.ONE); - - // Act - vote(user, "sICX/ICX", VOTE_POINTS.divide(BigInteger.TWO)); - vote(user, "sICX/bnUSD", VOTE_POINTS.divide(BigInteger.TWO)); - - BigInteger day = (BigInteger) rewardsScore.call("getDay"); - while (day.compareTo(migrationDay) <= 0) { - rewardsScore.invoke(owner, "claimRewards", getUserSources(owner.getAddress())); - - Map> data = (Map>) rewardsScore.call("getDataSourcesAt", day.subtract(BigInteger.ONE)); - BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - - BigInteger expectedLoansDist = emission.multiply(loansDist.dist_percent).divide(EXA); - BigInteger expectedICXDist = emission.multiply(icxPoolDist.dist_percent).divide(EXA); - BigInteger expectedBnUSDDist = emission.multiply(sICXbnUSDDist.dist_percent).divide(EXA); - - BigInteger loansDist = (BigInteger) data.get("Loans").get("total_dist"); - BigInteger ICXDist = (BigInteger) data.get("sICX/ICX").get("total_dist"); - BigInteger bnUSDDist = (BigInteger) data.get("sICX/bnUSD").get("total_dist"); - - assertEquals(expectedLoansDist, loansDist); - assertEquals(expectedICXDist, ICXDist); - assertEquals(expectedBnUSDDist, bnUSDDist); - - sm.getBlock().increase(DAY); - day = (BigInteger) rewardsScore.call("getDay"); - } - - rewardsScore.invoke(owner, "claimRewards", getUserSources(owner.getAddress())); - Map> data = (Map>) rewardsScore.call("getDataSourcesAt", day.subtract(BigInteger.ONE)); - BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - - BigInteger expectedLoansDist = emission.multiply(loansDist.dist_percent).divide(EXA); - BigInteger expectedICXDist = emission.multiply(votingPercentage.divide(BigInteger.TWO)).divide(EXA); - BigInteger expectedBnUSDDist = emission.multiply(votingPercentage.divide(BigInteger.TWO)).divide(EXA); - - BigInteger loansDist = (BigInteger) data.get("Loans").get("total_dist"); - BigInteger ICXDist = (BigInteger) data.get("sICX/ICX").get("total_dist"); - BigInteger bnUSDDist = (BigInteger) data.get("sICX/bnUSD").get("total_dist"); - - - assertEquals(expectedLoansDist, loansDist); - assertEquals(expectedICXDist, ICXDist); - assertEquals(expectedBnUSDDist, bnUSDDist); - } - - @Test - void getDistPercentages() { - // Arrange - Account user = sm.createAccount(); - mockUserWeight(user, EXA); - BigInteger votingPercentage = BigInteger.valueOf(30).multiply(EXA).divide(BigInteger.valueOf(100)); - - // Act - vote(user, "sICX/ICX", VOTE_POINTS.divide(BigInteger.TWO)); - vote(user, "sICX/bnUSD", VOTE_POINTS.divide(BigInteger.TWO)); - - sm.getBlock().increase(WEEK_BLOCKS); - rewardsScore.invoke(owner, "updateRelativeSourceWeight", "sICX/ICX", BigInteger.valueOf(sm.getBlock().getTimestamp())); - rewardsScore.invoke(owner, "updateRelativeSourceWeight", "sICX/bnUSD", BigInteger.valueOf(sm.getBlock().getTimestamp())); - - // Assert - Map> distData = (Map>) rewardsScore.call("getDistributionPercentages"); - - assertEquals(bwtDist.dist_percent, distData.get("Base").get(bwtDist.recipient_name)); - assertEquals(daoDist.dist_percent, distData.get("Base").get(daoDist.recipient_name)); - assertEquals(reserveDist.dist_percent, distData.get("Base").get(reserveDist.recipient_name)); - - assertEquals(BigInteger.ZERO, distData.get("Voting").get(loansDist.recipient_name)); - assertEquals(votingPercentage.divide(BigInteger.TWO), distData.get("Voting").get(sICXbnUSDDist.recipient_name)); - assertEquals(votingPercentage.divide(BigInteger.TWO), distData.get("Voting").get(icxPoolDist.recipient_name)); - - assertEquals(loansDist.dist_percent, distData.get("Fixed").get(loansDist.recipient_name)); - } - - @Test - void setGetVotable() { - assertTrue((boolean)rewardsScore.call("isVotable", "sICX/ICX")); - assertOnlyCallableByGovernance(rewardsScore, "setVotable", "sICX/ICX", false); - rewardsScore.invoke(governance, "setVotable", "sICX/ICX", false); - assertTrue(!(boolean)rewardsScore.call("isVotable", "sICX/ICX")); - } - - @Test - void addDataSource() { - // Arrange - String newDataSource = "foo"; - int type = 1; - // Act & Assert - String expectedErrorMessage = "Reverted(0): There is no data source with the name foo"; - Executable nonExistingDataSource = () -> rewardsScore.invoke(governance, "addDataSource", newDataSource, 1, BigInteger.ZERO); - expectErrorMessage(nonExistingDataSource, expectedErrorMessage); - - // Arrange - rewardsScore.invoke(governance, "addNewDataSource", newDataSource, dex.getAddress()); - assertOnlyCallableByGovernance(rewardsScore, "addDataSource", newDataSource, type, BigInteger.ZERO); - - rewardsScore.invoke(governance, "addDataSource", newDataSource, 1, BigInteger.ZERO); - - // Assert - assertEquals(type, rewardsScore.call("getSourceType", newDataSource)); - } - - @Test - void addType() { - assertOnlyCallableByGovernance(rewardsScore, "addType", "newType"); - } - - @Test - void setGetTypeWeight() { - int type = (int)rewardsScore.call("getSourceType", "sICX/ICX"); - rewardsScore.invoke(admin, "checkpoint"); - assertEquals(EXA ,rewardsScore.call("getCurrentTypeWeight", type)); - assertOnlyCallableByGovernance(rewardsScore, "changeTypeWeight", type, EXA.multiply(BigInteger.TWO)); - rewardsScore.invoke(governance, "changeTypeWeight", type, EXA.multiply(BigInteger.TWO)); - assertEquals(EXA.multiply(BigInteger.TWO) ,rewardsScore.call("getCurrentTypeWeight", type)); - } -} \ No newline at end of file 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 b32e3522b..2e70ee826 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 @@ -19,46 +19,30 @@ import com.iconloop.score.test.Account; import network.balanced.score.lib.structs.RewardsDataEntry; import network.balanced.score.lib.structs.RewardsDataEntryOld; +import network.balanced.score.lib.utils.Names; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; import java.math.BigInteger; import java.util.Map; import static network.balanced.score.core.rewards.utils.RewardsConstants.WEIGHT; +import static network.balanced.score.core.rewards.weight.SourceWeightController.VOTE_POINTS; import static network.balanced.score.lib.utils.Constants.MICRO_SECONDS_IN_A_DAY; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; class RewardsTestRewards extends RewardsTestBase { - @BeforeEach void setup() throws Exception { super.setup(); } - @SuppressWarnings("unchecked") - @Test - void getDataSourcesAt() { - // Arrange - sm.getBlock().increase(DAY); - BigInteger currentDay = (BigInteger) rewardsScore.call("getDay"); - BigInteger previousDay = currentDay.subtract(BigInteger.ONE); - rewardsScore.invoke(admin, "distribute"); - - // Act - Map> data = (Map>) rewardsScore.call( - "getDataSourcesAt", previousDay); - - // Assert - BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - assertEquals(loansDist.dist_percent.multiply(emission).divide(EXA), data.get("Loans").get("total_dist")); - assertEquals(icxPoolDist.dist_percent.multiply(emission).divide(EXA), data.get("sICX/ICX").get("total_dist")); - } - @Test void distribute() { // Act @@ -69,12 +53,12 @@ void distribute() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - verify(baln.mock, times(day)).transfer(bwt.getAddress(), bwtDist.dist_percent.multiply(emission).divide(EXA), - new byte[0]); + verify(baln.mock, times(day)).transfer(bwt.getAddress(), + defaultPlatformDist.multiply(emission).divide(EXA), new byte[0]); verify(baln.mock, times(day)).transfer(daoFund.getAddress(), - daoDist.dist_percent.multiply(emission).divide(EXA), new byte[0]); + defaultPlatformDist.multiply(emission).divide(EXA), new byte[0]); verify(baln.mock, times(day)).transfer(reserve.getAddress(), - reserveDist.dist_percent.multiply(emission).divide(EXA), new byte[0]); + defaultPlatformDist.multiply(emission).divide(EXA), new byte[0]); } @Test @@ -99,10 +83,11 @@ void claimRewards_updateRewardsData() { sm.getBlock().increase(DAY*2); // Assert + System.out.println( getVotePercentage("Loans")); BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger userLoansDistribution = loansDistribution.multiply(loansBalance).divide(loansTotalSupply); - BigInteger swapDistribution = icxPoolDist.dist_percent.multiply(emission).divide(EXA); + BigInteger swapDistribution = getVotePercentage("sICX/ICX").multiply(emission).divide(EXA); BigInteger userSwapDistribution = swapDistribution.multiply(swapBalance).divide(swapTotalSupply); rewardsScore.invoke(account, "claimRewards", getUserSources(account.getAddress())); @@ -146,9 +131,9 @@ void claimRewards_updateBalanceAndSupply() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger userLoansDistribution = loansDistribution.multiply(loansBalance).divide(loansTotalSupply); - BigInteger swapDistribution = icxPoolDist.dist_percent.multiply(emission).divide(EXA); + BigInteger swapDistribution = getVotePercentage("sICX/ICX").multiply(emission).divide(EXA); BigInteger userSwapDistribution = swapDistribution.multiply(swapBalance).divide(swapTotalSupply); rewardsScore.invoke(account, "claimRewards", getUserSources(account.getAddress())); @@ -180,7 +165,7 @@ void boostedRewards() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger userLoansDistribution = loansDistribution.multiply(loansBalance).divide(currentSupply); BigInteger unBoostedRewards = getOneDayRewards(account.getAddress()); @@ -221,7 +206,7 @@ void boostedRewards_maxBoost() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger userLoansDistribution = loansDistribution.multiply(loansBalance).divide(currentSupply); BigInteger unBoostedRewards = getOneDayRewards(account.getAddress()); @@ -260,7 +245,7 @@ void boostedRewards_updateBoostedBalance_claim() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger userLoansDistribution = loansDistribution.multiply(loansBalance).divide(loansTotalSupply); BigInteger unBoostedRewards = getOneDayRewards(account.getAddress()); @@ -312,7 +297,7 @@ void boostedRewards_updateBoostedBalance_onBalanceUpdate() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger userLoansDistribution = loansDistribution.multiply(loansBalance).divide(loansTotalSupply); BigInteger unBoostedRewards = getOneDayRewards(account.getAddress()); @@ -363,7 +348,7 @@ void boostedRewards_updateRewardsData() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger userLoansDistribution = loansDistribution.multiply(loansBalance).divide(loansTotalSupply); BigInteger unBoostedRewards = getOneDayRewards(account.getAddress()); @@ -433,7 +418,7 @@ void claimRewards_updateBatchRewardsData() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger user1Distribution = loansDistribution.multiply(user1CurrentBalance).divide(currentTotalSupply); rewardsScore.invoke(account1, "claimRewards", getUserSources(account1.getAddress())); @@ -487,7 +472,7 @@ void claimRewards_updateBalanceAndSupplyBatch() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger user1Distribution = loansDistribution.multiply(user1CurrentBalance).divide(currentTotalSupply); rewardsScore.invoke(account1, "claimRewards", getUserSources(account1.getAddress())); @@ -527,12 +512,12 @@ void getBalnHolding() { assertEquals(BigInteger.ZERO, initialRewards); sm.getBlock().increase(DAY*3); - rewardsScore.invoke(admin, "distribute"); + rewardsScore.invoke(owner, "distribute"); BigInteger timeInUS = BigInteger.valueOf(sm.getBlock().getTimestamp()); // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger userDistribution = loansDistribution.multiply(currentBalance).divide(currentTotalSupply); BigInteger rewards = (BigInteger) rewardsScore.call("getBalnHolding", account.getAddress().toString()); @@ -568,7 +553,7 @@ void getBalnHoldings() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger distribution = loansDistribution.multiply(currentBalance).divide(currentSupply); BigInteger timeDiffInUS = endTimeInUS.subtract(startTimeInUS); @@ -623,7 +608,7 @@ void getBalnHoldings_batch() { // Assert BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - BigInteger loansDistribution = loansDist.dist_percent.multiply(emission).divide(EXA); + BigInteger loansDistribution = getVotePercentage("Loans").multiply(emission).divide(EXA); BigInteger user1Distribution = loansDistribution.multiply(user1CurrentBalance).divide(currentTotalSupply); BigInteger user2Distribution = loansDistribution.multiply(user2CurrentBalance).divide(currentTotalSupply); @@ -639,4 +624,120 @@ void getBalnHoldings_batch() { assertEquals(user1ExpectedRewards.divide(BigInteger.TEN), user1Rewards); assertEquals(user2ExpectedRewards.divide(BigInteger.TEN), user2Rewards); } + + @Test + @SuppressWarnings("unchecked") + void setPlatformDistPercentage() { + // Arrange + distribute(); + // assertOnlyCallableByGovernance(rewardsScore, "setPlatformDistPercentage", Names.DAOFUND, + // ICX.divide(BigInteger.TEN)); + + // Act + String expectedErrorMessage = "Reverted(0): Sum of distributions exceeds 100%"; + Executable overHundredPercent = () -> rewardsScore.invoke(owner, "setPlatformDistPercentage", Names.DAOFUND, + EXA); + expectErrorMessage(overHundredPercent, expectedErrorMessage); + // reduce by 10 % + rewardsScore.invoke(owner, "setPlatformDistPercentage", Names.DAOFUND, ICX.divide(BigInteger.TEN)); + BigInteger votingPercentage = BigInteger.valueOf(4).multiply(EXA).divide(BigInteger.TEN); + + // Assert + Map> distData = (Map>) rewardsScore.call( + "getDistributionPercentages"); + assertEquals(ICX.divide(BigInteger.TEN), distData.get("Base").get(Names.DAOFUND)); + assertEquals(votingPercentage.divide(BigInteger.TWO), getVotePercentage("sICX/bnUSD")); + assertEquals(votingPercentage.divide(BigInteger.TWO), getVotePercentage("sICX/ICX")); + + sm.getBlock().increase(DAY); + BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); + rewardsScore.invoke(owner, "distribute"); + + verify(baln.mock).transfer(daoFund.getAddress(), + ICX.divide(BigInteger.TEN).multiply(emission).divide(EXA), new byte[0]); + } + + @Test + @SuppressWarnings("unchecked") + void setFixedSourcePercentage() { + // Arrange + BigInteger ICXFixed = BigInteger.valueOf(3).multiply(EXA).divide(BigInteger.valueOf(100)); // 3% + BigInteger sICXBnusdFixed = BigInteger.valueOf(7).multiply(EXA).divide(BigInteger.valueOf(100)); // 7% + BigInteger votingPercentage = BigInteger.valueOf(2).multiply(EXA).divide(BigInteger.TEN); + // assertOnlyCallableBy(rewardsScore, "setFixedSourcePercentage", "sICX/bnUSD", + // sICXBnusdFixed); + + // Act + String expectedErrorMessage = "Reverted(0): Sum of distributions exceeds 100%"; + Executable overHundredPercent = () -> rewardsScore.invoke(owner, "setFixedSourcePercentage", + "sICX/bnUSD", EXA); + expectErrorMessage(overHundredPercent, expectedErrorMessage); + rewardsScore.invoke(owner, "setFixedSourcePercentage", "sICX/bnUSD", sICXBnusdFixed); + rewardsScore.invoke(owner, "setFixedSourcePercentage", "sICX/ICX", ICXFixed); + + // Assert + Map> distData = (Map>) rewardsScore.call( + "getDistributionPercentages"); + assertEquals(votingPercentage.divide(BigInteger.TWO), distData.get("Voting").get("sICX/bnUSD")); + assertEquals(votingPercentage.divide(BigInteger.TWO), distData.get("Voting").get("sICX/bnUSD")); + + assertEquals(sICXBnusdFixed, distData.get("Fixed").get("sICX/bnUSD")); + assertEquals(ICXFixed, distData.get("Fixed").get("sICX/ICX")); + } + + @Test + @SuppressWarnings("unchecked") + void getUserData() { + // Assert + Map> data = (Map>) rewardsScore.call( + "getUserVoteData", user.getAddress()); + assertTrue(data.containsKey("sICX/ICX")); + assertTrue(data.containsKey("sICX/bnUSD")); + assertEquals(VOTE_POINTS.divide(BigInteger.TWO), data.get("sICX/ICX").get("power")); + assertEquals(VOTE_POINTS.divide(BigInteger.TWO), data.get("sICX/bnUSD").get("power")); + + // Act + sm.getBlock().increase(DAY * 10); + vote(user, "sICX/ICX", BigInteger.ZERO); + vote(user, "sICX/bnUSD", VOTE_POINTS); + rewardsScore.invoke(owner, "updateRelativeSourceWeight", "sICX/bnUSD", + BigInteger.valueOf(sm.getBlock().getTimestamp())); + + // Assert + data = (Map>) rewardsScore.call("getUserVoteData", user.getAddress()); + assertTrue(data.containsKey("sICX/ICX")); + assertTrue(data.containsKey("sICX/bnUSD")); + assertEquals(BigInteger.ZERO, data.get("sICX/ICX").get("power")); + assertEquals(VOTE_POINTS, data.get("sICX/bnUSD").get("power")); + + sm.getBlock().increase(DAY * 10); + + data = (Map>) rewardsScore.call("getUserVoteData", user.getAddress()); + assertTrue(!data.containsKey("sICX/ICX")); + assertTrue(data.containsKey("sICX/bnUSD")); + assertEquals(VOTE_POINTS, data.get("sICX/bnUSD").get("power")); + } + + @Test + void setGetVotable() { + assertTrue((boolean)rewardsScore.call("isVotable", "sICX/ICX")); + // assertOnlyCallableByGovernance(rewardsScore, "setVotable", "sICX/ICX", false); + rewardsScore.invoke(owner, "setVotable", "sICX/ICX", false); + assertTrue(!(boolean)rewardsScore.call("isVotable", "sICX/ICX")); + } + + @Test + void addType() { + // assertOnlyCallableByGovernance(rewardsScore, "addType", "newType"); + } + + @Test + void setGetTypeWeight() { + int type = (int)rewardsScore.call("getSourceType", "sICX/ICX"); + rewardsScore.invoke(owner, "checkpoint"); + assertEquals(EXA ,rewardsScore.call("getCurrentTypeWeight", type)); + // assertOnlyCallableByGovernance(rewardsScore, "changeTypeWeight", type, EXA.multiply(BigInteger.TWO)); + rewardsScore.invoke(owner, "changeTypeWeight", type, EXA.multiply(BigInteger.TWO)); + assertEquals(EXA.multiply(BigInteger.TWO) ,rewardsScore.call("getCurrentTypeWeight", type)); + } } \ No newline at end of file diff --git a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestRewardsPostMigration.java b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestRewardsPostMigration.java deleted file mode 100644 index fa6a3854d..000000000 --- a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestRewardsPostMigration.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (c) 2022-2023 Balanced.network. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package network.balanced.score.core.rewards; - -import com.iconloop.score.test.Account; -import network.balanced.score.lib.structs.DistributionPercentage; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; -import score.Address; - -import java.math.BigInteger; -import java.util.Map; - -import static network.balanced.score.core.rewards.weight.SourceWeightController.VOTE_POINTS; -import static network.balanced.score.lib.utils.Constants.MICRO_SECONDS_IN_A_DAY; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -// mimics dist percentages with votes -// runs all the same tests as RewardsTestRewards but after migration -class RewardsTestRewardsPostMigration extends RewardsTestRewards { - - BigInteger migrationDay; - DistributionPercentage sICXbnUSDDist = new DistributionPercentage(); - Account user; - - @BeforeEach - @Order(1) - void setup() throws Exception { - super.setup(); - sm.getBlock().increase(DAY * 10); - syncDistributions(); - user = sm.createAccount(); - - BigInteger day = (BigInteger) rewardsScore.call("getDay"); - migrationDay = day.add(BigInteger.valueOf(14)); - - rewardsScore.invoke(owner, "setMigrateToVotingDay", migrationDay); - - BigInteger currentTime = BigInteger.valueOf(sm.getBlock().getTimestamp()); - BigInteger unlockTime = currentTime.add(MICRO_SECONDS_IN_A_DAY.multiply(BigInteger.valueOf(365))); - when(bBaln.mock.lockedEnd(any(Address.class))).thenReturn(unlockTime); - - mockUserWeight(user, EXA); - - // Act - vote(user, "sICX/ICX", VOTE_POINTS.divide(BigInteger.TWO)); - vote(user, "sICX/bnUSD", VOTE_POINTS.divide(BigInteger.TWO)); - - day = (BigInteger) rewardsScore.call("getDay"); - while (day.compareTo(migrationDay) <= 0) { - sm.getBlock().increase(DAY); - day = (BigInteger) rewardsScore.call("getDay"); - } - - rewardsScore.invoke(owner, "updateRelativeSourceWeight", "sICX/ICX", - BigInteger.valueOf(sm.getBlock().getTimestamp())); - rewardsScore.invoke(owner, "updateRelativeSourceWeight", "sICX/bnUSD", - BigInteger.valueOf(sm.getBlock().getTimestamp())); - } - - protected void setupDistributions() { - rewardsScore.invoke(governance, "addNewDataSource", "sICX/bnUSD", dex.getAddress()); - - loansDist.recipient_name = "Loans"; - loansDist.dist_percent = ICX.divide(BigInteger.valueOf(10)); //10% - - sICXbnUSDDist.recipient_name = "sICX/bnUSD"; - sICXbnUSDDist.dist_percent = BigInteger.valueOf(15).multiply(ICX).divide(BigInteger.valueOf(100)); //15% - - icxPoolDist.recipient_name = "sICX/ICX"; - icxPoolDist.dist_percent = BigInteger.valueOf(15).multiply(ICX).divide(BigInteger.valueOf(100)); //15% - - bwtDist.recipient_name = "Worker Tokens"; - bwtDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% - - reserveDist.recipient_name = "Reserve Fund"; - reserveDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% - - daoDist.recipient_name = "DAOfund"; - daoDist.dist_percent = ICX.divide(BigInteger.valueOf(5)); //20% - - DistributionPercentage[] distributionPercentages = new DistributionPercentage[]{loansDist, sICXbnUSDDist, - icxPoolDist, - bwtDist, reserveDist, daoDist}; - - rewardsScore.invoke(governance, "updateBalTokenDistPercentage", (Object) distributionPercentages); - } - - @Test - void setPlatformDistPercentage() { - // Arrange - distribute(); - assertOnlyCallableByGovernance(rewardsScore, "setPlatformDistPercentage", "DAOfund", - ICX.divide(BigInteger.TEN)); - - // Act - String expectedErrorMessage = "Reverted(0): Sum of distributions exceeds 100%"; - Executable overHundredPercent = () -> rewardsScore.invoke(governance, "setPlatformDistPercentage", "DAOfund", - EXA); - expectErrorMessage(overHundredPercent, expectedErrorMessage); - // reduce by 10 % - rewardsScore.invoke(governance, "setPlatformDistPercentage", "DAOfund", ICX.divide(BigInteger.TEN)); - BigInteger votingPercentage = BigInteger.valueOf(4).multiply(EXA).divide(BigInteger.TEN); - - // Assert - Map> distData = (Map>) rewardsScore.call( - "getDistributionPercentages"); - assertEquals(ICX.divide(BigInteger.TEN), distData.get("Base").get(daoDist.recipient_name)); - assertEquals(votingPercentage.divide(BigInteger.TWO), distData.get("Voting").get(sICXbnUSDDist.recipient_name)); - assertEquals(votingPercentage.divide(BigInteger.TWO), distData.get("Voting").get(icxPoolDist.recipient_name)); - - sm.getBlock().increase(DAY); - BigInteger emission = (BigInteger) rewardsScore.call("getEmission", BigInteger.valueOf(-1)); - rewardsScore.invoke(admin, "distribute"); - - verify(baln.mock).transfer(daoFund.getAddress(), - ICX.divide(BigInteger.TEN).multiply(emission).divide(EXA), new byte[0]); - } - - @Test - void setFixedSourcePercentage() { - // Arrange - BigInteger ICXFixed = BigInteger.valueOf(3).multiply(EXA).divide(BigInteger.valueOf(100)); // 3% - BigInteger sICXBnusdFixed = BigInteger.valueOf(7).multiply(EXA).divide(BigInteger.valueOf(100)); // 7% - BigInteger votingPercentage = BigInteger.valueOf(2).multiply(EXA).divide(BigInteger.TEN); - assertOnlyCallableByGovernance(rewardsScore, "setFixedSourcePercentage", sICXbnUSDDist.recipient_name, - sICXBnusdFixed); - - // Act - String expectedErrorMessage = "Reverted(0): Sum of distributions exceeds 100%"; - Executable overHundredPercent = () -> rewardsScore.invoke(governance, "setFixedSourcePercentage", - sICXbnUSDDist.recipient_name, EXA); - expectErrorMessage(overHundredPercent, expectedErrorMessage); - rewardsScore.invoke(governance, "setFixedSourcePercentage", sICXbnUSDDist.recipient_name, sICXBnusdFixed); - rewardsScore.invoke(governance, "setFixedSourcePercentage", icxPoolDist.recipient_name, ICXFixed); - - // Assert - Map> distData = (Map>) rewardsScore.call( - "getDistributionPercentages"); - assertEquals(votingPercentage.divide(BigInteger.TWO), distData.get("Voting").get(sICXbnUSDDist.recipient_name)); - assertEquals(votingPercentage.divide(BigInteger.TWO), distData.get("Voting").get(icxPoolDist.recipient_name)); - - assertEquals(sICXBnusdFixed, distData.get("Fixed").get(sICXbnUSDDist.recipient_name)); - assertEquals(ICXFixed, distData.get("Fixed").get(icxPoolDist.recipient_name)); - } - - @Test - void getUserData() { - // Assert - Map> data = (Map>) rewardsScore.call( - "getUserVoteData", user.getAddress()); - assertTrue(data.containsKey("sICX/ICX")); - assertTrue(data.containsKey("sICX/bnUSD")); - assertEquals(VOTE_POINTS.divide(BigInteger.TWO), data.get("sICX/ICX").get("power")); - assertEquals(VOTE_POINTS.divide(BigInteger.TWO), data.get("sICX/bnUSD").get("power")); - - // Act - sm.getBlock().increase(DAY * 10); - vote(user, "sICX/ICX", BigInteger.ZERO); - vote(user, "sICX/bnUSD", VOTE_POINTS); - rewardsScore.invoke(owner, "updateRelativeSourceWeight", "sICX/bnUSD", - BigInteger.valueOf(sm.getBlock().getTimestamp())); - - // Assert - data = (Map>) rewardsScore.call("getUserVoteData", user.getAddress()); - assertTrue(data.containsKey("sICX/ICX")); - assertTrue(data.containsKey("sICX/bnUSD")); - assertEquals(BigInteger.ZERO, data.get("sICX/ICX").get("power")); - assertEquals(VOTE_POINTS, data.get("sICX/bnUSD").get("power")); - - sm.getBlock().increase(DAY * 10); - - data = (Map>) rewardsScore.call("getUserVoteData", user.getAddress()); - assertTrue(!data.containsKey("sICX/ICX")); - assertTrue(data.containsKey("sICX/bnUSD")); - assertEquals(VOTE_POINTS, data.get("sICX/bnUSD").get("power")); - } -} \ No newline at end of file diff --git a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestSetup.java b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestSetup.java index 13edd2332..012ce2fe3 100644 --- a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestSetup.java +++ b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/RewardsTestSetup.java @@ -34,11 +34,9 @@ class RewardsTestSetup extends RewardsTestBase { - private final Account testAccount = Account.newScoreAccount(scoreCount++); - @BeforeEach void setup() throws Exception { - governance = new MockContract<>(GovernanceScoreInterface.class, sm, admin).account; + governance = new MockContract<>(GovernanceScoreInterface.class, sm, owner).account; rewardsScore = sm.deploy(owner, RewardsImpl.class, governance.getAddress()); } @@ -47,57 +45,15 @@ void name() { assertEquals("Balanced Rewards", rewardsScore.call("name")); } - @Test - void setAndGetAdmin() { - testAdmin(rewardsScore, governance, admin); - } - - @Test - void setAndGetGovernance() { - testGovernance(rewardsScore, governance, owner); - } - - @Test - void setAndGetBwt() { - testContractSettersAndGetters(rewardsScore, governance, admin, "setBwt", - testAccount.getAddress(), "getBwt"); - } - - @Test - void setAndGetBaln() { - testContractSettersAndGetters(rewardsScore, governance, admin, "setBaln", - testAccount.getAddress(), "getBaln"); - } - - @Test - void setAndGetReserve() { - testContractSettersAndGetters(rewardsScore, governance, admin, "setReserve", - testAccount.getAddress(), "getReserve"); - } - - @Test - void setAndGetDaofund() { - testContractSettersAndGetters(rewardsScore, governance, admin, "setDaofund", - testAccount.getAddress(), "getDaofund"); - } - @Test void setAndGetTimeOffset() { BigInteger timeOffset = BigInteger.ONE.multiply(MICRO_SECONDS_IN_A_DAY).negate(); - String expectedErrorMessage = "Authorization Check: Address not set"; - - Executable setWithoutAdmin = () -> rewardsScore.invoke(admin, "setTimeOffset", timeOffset); - expectErrorMessage(setWithoutAdmin, expectedErrorMessage); - - rewardsScore.invoke(governance, "setAdmin", admin.getAddress()); - - Account nonAdmin = sm.createAccount(); - expectedErrorMessage = "Authorization Check: Authorization failed. Caller: " + nonAdmin.getAddress() + - " Authorized Caller: " + admin.getAddress(); - Executable setNotFromAdmin = () -> rewardsScore.invoke(nonAdmin, "setTimeOffset", timeOffset); - expectErrorMessage(setNotFromAdmin, expectedErrorMessage); + Account nonOwner = sm.createAccount(); + String expectedErrorMessage = "SenderNotScoreOwner"; + Executable setNotFromOwner = () -> rewardsScore.invoke(nonOwner, "setTimeOffset",timeOffset); + expectErrorMessage(setNotFromOwner, expectedErrorMessage); - rewardsScore.invoke(admin, "setTimeOffset", timeOffset); + rewardsScore.invoke(owner, "setTimeOffset", timeOffset); assertEquals(timeOffset, rewardsScore.call("getTimeOffset")); } @@ -135,28 +91,20 @@ void setBoostWeight() { BigInteger weight = BigInteger.valueOf(50).multiply(HUNDRED_PERCENTAGE).divide(BigInteger.valueOf(100)); BigInteger weightAbove = HUNDRED_PERCENTAGE.add(BigInteger.ONE); BigInteger weightBelow = HUNDRED_PERCENTAGE.divide(BigInteger.valueOf(100)).subtract(BigInteger.ONE); - String expectedErrorMessage = "Authorization Check: Address not set"; - - Executable setWithoutAdmin = () -> rewardsScore.invoke(admin, "setBoostWeight", weight); - expectErrorMessage(setWithoutAdmin, expectedErrorMessage); - - rewardsScore.invoke(governance, "setAdmin", admin.getAddress()); - - Account nonAdmin = sm.createAccount(); - expectedErrorMessage = "Authorization Check: Authorization failed. Caller: " + nonAdmin.getAddress() + - " Authorized Caller: " + admin.getAddress(); - Executable setNotFromAdmin = () -> rewardsScore.invoke(nonAdmin, "setBoostWeight", weight); - expectErrorMessage(setNotFromAdmin, expectedErrorMessage); + Account nonOwner = sm.createAccount(); + String expectedErrorMessage = "SenderNotScoreOwner"; + Executable setNotFromOwner = () -> rewardsScore.invoke(nonOwner, "setBoostWeight",weight); + expectErrorMessage(setNotFromOwner, expectedErrorMessage); expectedErrorMessage = "Reverted(0): Boost weight has to be above 1%"; - Executable setWeightAbove = () -> rewardsScore.invoke(admin, "setBoostWeight", weightBelow); + Executable setWeightAbove = () -> rewardsScore.invoke(owner, "setBoostWeight", weightBelow); expectErrorMessage(setWeightAbove, expectedErrorMessage); expectedErrorMessage = "Reverted(0): Boost weight has to be below 100%"; - Executable setWeightBelow = () -> rewardsScore.invoke(admin, "setBoostWeight", weightAbove); + Executable setWeightBelow = () -> rewardsScore.invoke(owner, "setBoostWeight", weightAbove); expectErrorMessage(setWeightBelow, expectedErrorMessage); - rewardsScore.invoke(admin, "setBoostWeight", weight); + rewardsScore.invoke(owner, "setBoostWeight", weight); assertEquals(weight, rewardsScore.call("getBoostWeight")); } } diff --git a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/SourceWeightControllerOld.java b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/SourceWeightControllerOld.java deleted file mode 100644 index f0d8a2249..000000000 --- a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/SourceWeightControllerOld.java +++ /dev/null @@ -1,578 +0,0 @@ -/* - * Copyright (c) 2022-2023 Balanced.network. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package network.balanced.score.core.rewards.weight; - -import network.balanced.score.core.rewards.RewardsImpl; -import network.balanced.score.lib.structs.Point; -import network.balanced.score.lib.structs.VotedSlope; -import network.balanced.score.lib.utils.EnumerableSetDB; -import score.*; - -import java.math.BigInteger; - -import static network.balanced.score.core.rewards.RewardsImpl.boostedBaln; -import static network.balanced.score.lib.utils.ArrayDBUtils.arrayDbContains; -import static network.balanced.score.lib.utils.ArrayDBUtils.removeFromArraydb; -import static network.balanced.score.lib.utils.Constants.EXA; -import static network.balanced.score.lib.utils.Constants.MICRO_SECONDS_IN_A_DAY; - -public class SourceWeightControllerOld { - public static RewardsImpl rewards; - // 7 * 86400 seconds - all future times are rounded by week - public static final BigInteger WEEK = MICRO_SECONDS_IN_A_DAY.multiply(BigInteger.valueOf(7)); - // Cannot change weight votes more often than once in 10 days - public static final BigInteger WEIGHT_VOTE_DELAY = MICRO_SECONDS_IN_A_DAY.multiply(BigInteger.TEN); - public static final BigInteger VOTE_POINTS = BigInteger.valueOf(10000); - - // sourceTypeNames: public(HashMap.get(int128, String[64])) - private static final EnumerableSetDB sourceTypeNames = new EnumerableSetDB<>("sourceTypeNames", - String.class); - - // we increment values by 1 prior to storing them here, so we can rely on a value - // of zero as meaning the source has not been set - private static final DictDB sourceTypes = Context.newDictDB("sourceTypes", Integer.class); - private static final DictDB isVotable = Context.newDictDB("votable", Boolean.class); - - private static final BranchDB> voteUserSlopes = Context.newBranchDB( - "voteUserSlopes", VotedSlope.class); - - private static final DictDB voteUserPower = Context.newDictDB("voteUserPower", - BigInteger.class); - - private static final BranchDB> lastUserVote = Context.newBranchDB( - "lastUserVote", BigInteger.class); - private static final BranchDB> activeUserWeights = Context.newBranchDB( - "activeUserWeights", String.class); - - // Past and scheduled points for source weight, sum of weights per type, total weight - // Point is for bias+slope - // changes* are for changes in slope - // time* are for the last change timestamp - // timestamps are rounded to whole weeks - - private static final BranchDB> pointsWeight = Context.newBranchDB("pointsWeight" - , Point.class); - - private static final BranchDB> changesWeight = Context.newBranchDB( - "changesWeight", BigInteger.class); - - private static final DictDB timeWeight = Context.newDictDB("timeWeight", BigInteger.class); - - private static final BranchDB> pointsSum = Context.newBranchDB("pointsSum", - Point.class); - - private static final BranchDB> changesSum = Context.newBranchDB( - "changesSum", BigInteger.class); - - private static final DictDB timeSum = Context.newDictDB("timeSum", BigInteger.class); - - - // time -> total weight - private static final DictDB pointsTotal = Context.newDictDB("pointsTotal", - BigInteger.class); - - // last scheduled time - private static final VarDB timeTotal = Context.newVarDB("timeTotal", BigInteger.class); - - private static final BranchDB> pointsTypeWeight = Context.newBranchDB( - "pointsTypeWeight", BigInteger.class); - private static final DictDB timeTypeWeight = Context.newDictDB("timeTypeWeight", - BigInteger.class); - - public SourceWeightControllerOld(Address bBalnAddress) { - boostedBaln.set(bBalnAddress); - timeTotal.set(getWeekTimestamp()); - } - - /** - * Fill historic type weights week-over-week for missed checkins and return the type weight for the future week - * - * @param sourceType Source type id - * @return Type weight - */ - private static BigInteger getTypeWeight(int sourceType) { - BigInteger time = timeTypeWeight.getOrDefault(sourceType, BigInteger.ZERO); - if (time.compareTo(BigInteger.ZERO) <= 0) { - return BigInteger.ZERO; - } - - BigInteger timestamp = BigInteger.valueOf(Context.getBlockTimestamp()); - BigInteger weight = pointsTypeWeight.at(sourceType).get(time); - for (int i = 0; i < 500; i++) { - if (time.compareTo(timestamp) > 0) { - break; - } - - time = time.add(WEEK); - pointsTypeWeight.at(sourceType).set(time, weight); - if (time.compareTo(timestamp) > 0) { - timeTypeWeight.set(sourceType, time); - } - } - - return weight; - } - - /** - * Fill sum of source weights for the same type week-over-week for missed checkins and return the sum for the - * future week - * - * @param sourceType Source type id - * @return Sum of weights - */ - private static BigInteger getSum(int sourceType) { - BigInteger time = timeSum.getOrDefault(sourceType, BigInteger.ZERO); - BigInteger timeStamp = BigInteger.valueOf(Context.getBlockTimestamp()); - if (time.compareTo(BigInteger.ZERO) <= 0) { - return BigInteger.ZERO; - } - - Point pt = pointsSum.at(sourceType).getOrDefault(time, new Point()); - for (int i = 0; i < 500; i++) { - if (time.compareTo(timeStamp) > 0) { - break; - } - - time = time.add(WEEK); - BigInteger dBias = pt.slope.multiply(WEEK); - if (pt.bias.compareTo(dBias) > 0) { - pt.bias = pt.bias.subtract(dBias); - BigInteger dSlope = changesSum.at(sourceType).getOrDefault(time, BigInteger.ZERO); - pt.slope = pt.slope.subtract(dSlope); - } else { - pt.bias = BigInteger.ZERO; - pt.slope = BigInteger.ZERO; - } - - pointsSum.at(sourceType).set(time, pt); - if (time.compareTo(timeStamp) > 0) { - timeSum.set(sourceType, time); - } - } - - return pt.bias; - } - - /** - * Fill historic total weights week-over-week for missed checkins and return the total for the future week - * - * @return Total weight - */ - private static BigInteger getTotal() { - BigInteger time = timeTotal.getOrDefault(BigInteger.ZERO); - BigInteger timestamp = BigInteger.valueOf(Context.getBlockTimestamp()); - if (time.compareTo(timestamp) > 0) { - // If we have already check pointed - still need to change the value - time = time.subtract(WEEK); - } - BigInteger pt = pointsTotal.getOrDefault(time, BigInteger.ZERO); - - int nrSourceTypes = sourceTypeNames.length(); - for (int id = 0; id < nrSourceTypes; id++) { - getSum(id); - getTypeWeight(id); - } - - for (int i = 0; i < 500; i++) { - if (time.compareTo(timestamp) > 0) { - break; - } - - time = time.add(WEEK); - pt = BigInteger.ZERO; - for (int id = 0; id < nrSourceTypes; id++) { - BigInteger typeSum = pointsSum.at(id).getOrDefault(time, new Point()).bias; - BigInteger typeWeight = pointsTypeWeight.at(id).getOrDefault(time, BigInteger.ZERO); - - pt = pt.add(typeSum.multiply(typeWeight)); - } - - pointsTotal.set(time, pt); - - if (time.compareTo(timestamp) > 0) { - timeTotal.set(time); - } - } - - return pt; - } - - /** - * Fill historic source weights week-over-week for missed checkins and return the total for the future week - * - * @param source Name of the source - * @return Source weight - */ - private static BigInteger getWeight(String source) { - BigInteger time = timeWeight.getOrDefault(source, BigInteger.ZERO); - BigInteger timestamp = BigInteger.valueOf(Context.getBlockTimestamp()); - if (time.compareTo(BigInteger.ZERO) <= 0) { - return BigInteger.ZERO; - } - - Point pt = pointsWeight.at(source).getOrDefault(time, new Point()); - for (int i = 0; i < 500; i++) { - if (time.compareTo(timestamp) > 0) { - break; - } - time = time.add(WEEK); - BigInteger dBias = pt.slope.multiply(WEEK); - if (pt.bias.compareTo(dBias) > 0) { - pt.bias = pt.bias.subtract(dBias); - BigInteger dSlope = changesWeight.at(source).getOrDefault(time, BigInteger.ZERO); - pt.slope = pt.slope.subtract(dSlope); - } else { - pt.bias = BigInteger.ZERO; - pt.slope = BigInteger.ZERO; - } - - pointsWeight.at(source).set(time, pt); - if (time.compareTo(timestamp) > 0) { - timeWeight.set(source, time); - } - } - - return pt.bias; - } - - /** - * Add source `name` of type `sourceType` with weight `weight` - * - * @param name Source name - * @param sourceType Source type - * @param weight Source weight - */ - public static void addSource(String name, int sourceType, BigInteger weight) { - Context.require(sourceTypeNames.at(sourceType) != null, "Not a valid sourceType"); - Context.require(sourceTypes.get(name) == null, "Source with name " + name + " already exists"); - - sourceTypes.set(name, sourceType + 1); - BigInteger nextTime = getNextWeekTimestamp(); - - if (weight.compareTo(BigInteger.ZERO) > 0) { - BigInteger typeWeight = getTypeWeight(sourceType); - BigInteger oldSum = getSum(sourceType); - BigInteger oldTotal = getTotal(); - - pointsSum.at(sourceType).getOrDefault(nextTime, new Point()).bias = weight.add(oldSum); - timeSum.set(sourceType, nextTime); - pointsTotal.set(nextTime, oldTotal.add(typeWeight.multiply(weight))); - timeTotal.set(nextTime); - - Point weightPoint = pointsWeight.at(name).getOrDefault(nextTime, new Point()); - weightPoint.bias = weight; - pointsWeight.at(name).set(nextTime, weightPoint); - } - - if (timeSum.get(sourceType) == null) { - timeSum.set(sourceType, nextTime); - - } - - timeWeight.set(name, nextTime); - rewards.NewSource(name, sourceType, weight); - } - - /** - * Checkpoint to fill data common for all sources - */ - public static void checkpoint() { - getTotal(); - } - - /** - * Checkpoint to fill data for both a specific source and common for all sources - * - * @param name Source name - */ - public static void checkpointSource(String name) { - getWeight(name); - getTotal(); - } - - /** - * Get Source relative weight (not more than 1.0) normalized to 1e18 (e.g. 1.0 == 1e18). Inflation which will be - * received by it is inflationRate * relativeWeight / 1e18 - * - * @param name Source name - * @param time Relative weight at the specified timestamp in the past or present - * @return Value of relative weight normalized to 1e18 - */ - public static BigInteger getRelativeWeight(String name, BigInteger time) { - if (time.equals(BigInteger.ZERO)) { - time = getWeekTimestamp(); - } else { - time = getWeekTimestamp(time); - } - - BigInteger totalWeight = pointsTotal.getOrDefault(time, BigInteger.ZERO); - if (totalWeight.compareTo(BigInteger.ZERO) <= 0) { - return BigInteger.ZERO; - } - - int sourceType = sourceTypes.get(name) - 1; - BigInteger typeWeight = pointsTypeWeight.at(sourceType).get(time); - BigInteger sourceWeight = pointsWeight.at(name).getOrDefault(time, new Point()).bias; - return EXA.multiply(typeWeight).multiply(sourceWeight).divide(totalWeight); - } - - /** - * Get source weight normalized to 1e18 and also fill all the unfilled values for type and source records - * - * @param name Source name - * @param time Relative weight at the specified timestamp in the past or present - * @return Value of relative weight normalized to 1e18 - * @dev Any address can call, however nothing is recorded if the values are filled already - */ - public static BigInteger updateRelativeWeight(String name, BigInteger time) { - getWeight(name); - getTotal(); // Also calculates getSum - return getRelativeWeight(name, time); - } - - /** - * Change type weight - * - * @param typeId Type id - * @param weight New type weight - */ - public static void changeTypeWeight(int typeId, BigInteger weight) { - BigInteger oldWeight = getTypeWeight(typeId); - BigInteger oldSum = getSum(typeId); - BigInteger totalWeight = getTotal(); - BigInteger timestamp = BigInteger.valueOf(Context.getBlockTimestamp()); - BigInteger nextTime = (timestamp.add(WEEK)).divide(WEEK).multiply(WEEK); - - totalWeight = totalWeight.add(oldSum.multiply(weight)).subtract(oldSum.multiply(oldWeight)); - pointsTotal.set(nextTime, totalWeight); - pointsTypeWeight.at(typeId).set(nextTime, weight); - timeTotal.set(nextTime); - timeTypeWeight.set(typeId, nextTime); - - rewards.NewTypeWeight(typeId, nextTime, oldWeight, totalWeight); - } - - /** - * Add source type with name `Name` and weight `weight` - * - * @param name Name of source type - * @param weight Weight of source type = 0 - */ - public static void addType(String name, BigInteger weight) { - int typeId = sourceTypeNames.length(); - sourceTypeNames.add(name); - Context.require(sourceTypeNames.at(typeId).equals(name)); - if (!weight.equals(BigInteger.ZERO)) { - changeTypeWeight(typeId, weight); - } - - rewards.AddType(name, typeId); - } - - /** - * Allocate voting power for changing pool weights - * - * @param sourceName Source which `user` votes for - * @param userWeight Weight for a source in bps (units of 0.01%). Minimal is 0.01%. Ignored if 0 - */ - public static void voteForSourceWeights(String sourceName, BigInteger userWeight) { - Context.require(isVotable.getOrDefault(sourceName, true) || userWeight.equals(BigInteger.ZERO), sourceName + - " is not a votable source, you can only remove weight"); - - Address bBalnAddress = boostedBaln.get(); - Address user = Context.getCaller(); - BigInteger slope = Context.call(BigInteger.class, bBalnAddress, "getLastUserSlope", user); - BigInteger lockEnd = Context.call(BigInteger.class, bBalnAddress, "lockedEnd", user); - BigInteger timestamp = BigInteger.valueOf(Context.getBlockTimestamp()); - BigInteger nextTime = getNextWeekTimestamp(); - - Context.require(lockEnd.compareTo(nextTime) > 0, "Your token lock expires too soon"); - Context.require((userWeight.compareTo(BigInteger.ZERO) >= 0) && (userWeight.compareTo(VOTE_POINTS) <= 0), - "Weight has to be between 0 and 10000"); - BigInteger nextUserVote = - lastUserVote.at(user).getOrDefault(sourceName, BigInteger.ZERO).add(WEIGHT_VOTE_DELAY); - Context.require(timestamp.compareTo(nextUserVote) >= 0, "Cannot vote so often"); - - int sourceType = sourceTypes.get(sourceName) - 1; - Context.require(sourceType >= 0, "Source not added"); - // Prepare slopes and biases in memory - VotedSlope oldSlope = voteUserSlopes.at(user).getOrDefault(sourceName, new VotedSlope()); - BigInteger oldDt = BigInteger.ZERO; - if (oldSlope.end.compareTo(nextTime) > 0) { - oldDt = oldSlope.end.subtract(nextTime); - } - - - BigInteger oldBias = oldSlope.slope.multiply(oldDt); - - VotedSlope newSlope = new VotedSlope( - slope.multiply(userWeight).divide(VOTE_POINTS), - userWeight, - lockEnd - ); - BigInteger newDt = lockEnd.subtract(nextTime); // dev: raises when expired - BigInteger newBias = newSlope.slope.multiply(newDt); - - // Check and update powers (weights) used - BigInteger powerUsed = voteUserPower.getOrDefault(user, BigInteger.ZERO); - powerUsed = powerUsed.add(newSlope.power).subtract(oldSlope.power); - Context.require((powerUsed.compareTo(BigInteger.ZERO) >= 0) && (powerUsed.compareTo(VOTE_POINTS) <= 0), "Used" + - " too much power"); - voteUserPower.set(user, powerUsed); - - // Remove old and schedule new slope changes - // Remove slope changes for old slopes - // Schedule recording of initial slope for nextTime - BigInteger oldWeightBias = getWeight(sourceName); - BigInteger oldWeightSlope = pointsWeight.at(sourceName).getOrDefault(nextTime, new Point()).slope; - BigInteger oldSumBias = getSum(sourceType); - BigInteger oldSumSlope = pointsSum.at(sourceType).getOrDefault(nextTime, new Point()).slope; - - Point weightPoint = pointsWeight.at(sourceName).getOrDefault(nextTime, new Point()); - weightPoint.bias = (oldWeightBias.add(newBias)).max(oldBias).subtract(oldBias); - Point sumPoint = pointsSum.at(sourceType).getOrDefault(nextTime, new Point()); - sumPoint.bias = (oldSumBias.add(newBias)).max(oldBias).subtract(oldBias); - - if (oldSlope.end.compareTo(nextTime) > 0) { - weightPoint.slope = (oldWeightSlope.add(newSlope.slope)).max(oldSlope.slope).subtract(oldSlope.slope); - sumPoint.slope = (oldSumSlope.add(newSlope.slope)).max(oldSlope.slope).subtract(oldSlope.slope); - } else { - weightPoint.slope = weightPoint.slope.add(newSlope.slope); - sumPoint.slope = sumPoint.slope.add(newSlope.slope); - } - - if (oldSlope.end.compareTo(timestamp) > 0) { - // Cancel old slope changes if they still didn't happen - BigInteger oldWeight = changesWeight.at(sourceName).get(oldSlope.end); - changesWeight.at(sourceName).set(oldSlope.end, oldWeight.subtract(oldSlope.slope)); - BigInteger oldSum = changesSum.at(sourceType).get(oldSlope.end); - changesSum.at(sourceType).set(oldSlope.end, oldSum.subtract(oldSlope.slope)); - } - - // Add slope changes for new slopes - BigInteger newWeight = changesWeight.at(sourceName).getOrDefault(newSlope.end, BigInteger.ZERO); - changesWeight.at(sourceName).set(newSlope.end, newWeight.subtract(newSlope.slope)); - BigInteger newSum = changesSum.at(sourceType).getOrDefault(newSlope.end, BigInteger.ZERO); - changesSum.at(sourceType).set(newSlope.end, newSum.subtract(newSlope.slope)); - - pointsWeight.at(sourceName).set(nextTime, weightPoint); - pointsSum.at(sourceType).set(nextTime, sumPoint); - - getTotal(); - - voteUserSlopes.at(user).set(sourceName, newSlope); - - // Record last action time - if (userWeight.equals(BigInteger.ZERO)) { - removeFromArraydb(sourceName, activeUserWeights.at(user)); - } else { - if (!arrayDbContains(activeUserWeights.at(user), sourceName)) { - activeUserWeights.at(user).add(sourceName); - } - } - - lastUserVote.at(user).set(sourceName, timestamp); - rewards.VoteForSource(sourceName, user, newWeight, nextTime); - } - - public static void setVotable(String name, boolean votable) { - Context.require(sourceTypes.get(name) != null, "Source with name " + name + " does not exists"); - isVotable.set(name, votable); - } - - public static boolean isVotable(String name) { - Context.require(sourceTypes.get(name) != null, "Source with name " + name + " does not exists"); - return isVotable.getOrDefault(name, true); - } - - /** - * Get current source weight - * - * @param sourceName Source name - * @return Source weight - */ - public static Point getSourcePointsWeight(String sourceName) { - return getSourcePointsWeightAt(sourceName, timeWeight.get(sourceName)); - } - - public static Point getSourcePointsWeightAt(String sourceName, BigInteger time) { - return pointsWeight.at(sourceName).getOrDefault(time, new Point()); - } - - /** - * Get current type weight - * - * @param typeId Type id - * @return Type weight - */ - public static BigInteger getCurrentTypeWeight(int typeId) { - return pointsTypeWeight.at(typeId).get(timeTypeWeight.get(typeId)); - } - - /** - * Get current total (type-weighted) weight - * - * @return Total weight - */ - public static BigInteger getTotalWeight() { - return pointsTotal.get(timeTotal.get()); - } - - /** - * Get sum of source weights per type - * - * @param typeId Type id - * @return Sum of source weights - */ - public static Point getPointsSumPerType(int typeId) { - return pointsSum.at(typeId).get(timeSum.get(typeId)); - } - - public static VotedSlope getUserSlope(Address user, String source) { - return voteUserSlopes.at(user).getOrDefault(source, new VotedSlope()); - } - - public static BigInteger getLastUserVote(Address user, String source) { - return lastUserVote.at(user).getOrDefault(source, BigInteger.ZERO); - } - - - public static int getSourceType(String sourceName) { - return sourceTypes.get(sourceName) - 1; - } - - public static int getTypeId(String name) { - return sourceTypeNames.indexOf(name); - } - - public static String getTypeName(int id) { - return sourceTypeNames.at(id); - } - - private static BigInteger getWeekTimestamp() { - return BigInteger.valueOf(Context.getBlockTimestamp()).divide(WEEK).multiply(WEEK); - } - - private static BigInteger getWeekTimestamp(BigInteger time) { - return time.divide(WEEK).multiply(WEEK); - } - - private static BigInteger getNextWeekTimestamp() { - return (BigInteger.valueOf(Context.getBlockTimestamp()).add(WEEK)).divide(WEEK).multiply(WEEK); - } - -} diff --git a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/SourceWeightControllerRevoteTest.java b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/SourceWeightControllerRevoteTest.java deleted file mode 100644 index 08000a38d..000000000 --- a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/SourceWeightControllerRevoteTest.java +++ /dev/null @@ -1,238 +0,0 @@ - -/* - * Copyright (c) 2022-2023 Balanced.network. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package network.balanced.score.core.rewards; - -import com.iconloop.score.test.Account; -import com.iconloop.score.test.Score; -import com.iconloop.score.test.ServiceManager; -import network.balanced.score.core.rewards.weight.SourceWeightController; -import network.balanced.score.core.rewards.weight.SourceWeightControllerOld; -import network.balanced.score.lib.interfaces.BoostedBaln; -import network.balanced.score.lib.interfaces.BoostedBalnScoreInterface; -import network.balanced.score.lib.structs.Point; -import network.balanced.score.lib.test.UnitTest; -import network.balanced.score.lib.test.mock.MockContract; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; -import org.opentest4j.AssertionFailedError; - -import score.Address; -import score.RevertedException; - -import java.math.BigInteger; -import java.util.Map; - -import static network.balanced.score.core.rewards.weight.SourceWeightController.VOTE_POINTS; -import static network.balanced.score.lib.utils.Constants.EXA; -import static network.balanced.score.lib.utils.Constants.MICRO_SECONDS_IN_A_DAY; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.when; - -public class SourceWeightControllerRevoteTest extends UnitTest { - static final Long DAY_BLOCKS = 43200L; - static final Long WEEK_BLOCKS = 7 * 43200L; - - static final ServiceManager sm = getServiceManager(); - static final Account owner = sm.createAccount(); - - static MockContract bBaln; - - static Score weightController; - static Score weightControllerRef; - - static String stakedLPType = "stakedLPRewards"; - - static int stakedLPId; - static int externalSourcesId; - - static BigInteger unlockTime; - - static Account user1; - static Account user2; - static Account user3; - - @BeforeAll - static void setup() throws Exception { - user1 = sm.createAccount(); - user2 = sm.createAccount(); - user3 = sm.createAccount(); - - bBaln = new MockContract<>(BoostedBalnScoreInterface.class, sm, owner); - sm.getBlock().increase(WEEK_BLOCKS * 10); - - weightController = sm.deploy(owner, SourceWeightControllerOld.class, bBaln.getAddress()); - weightControllerRef = sm.deploy(owner, SourceWeightController.class, bBaln.getAddress()); - SourceWeightControllerOld.rewards = - (RewardsImpl) sm.deploy(owner, RewardsImpl.class, bBaln.getAddress()).getInstance(); - SourceWeightController.rewards = - (RewardsImpl) sm.deploy(owner, RewardsImpl.class, bBaln.getAddress()).getInstance(); - - weightController.invoke(owner, "addType", stakedLPType, BigInteger.ZERO); - weightControllerRef.invoke(owner, "addType", stakedLPType, BigInteger.ZERO); - - stakedLPId = (int) weightController.call("getTypeId", stakedLPType); - - weightController.invoke(owner, "changeTypeWeight", stakedLPId, EXA); - weightControllerRef.invoke(owner, "changeTypeWeight", stakedLPId, EXA); - - weightController.invoke(owner, "addSource", "sICX/ICX", stakedLPId, BigInteger.ZERO); - weightController.invoke(owner, "addSource", "sICX/bnUSD", stakedLPId, BigInteger.ZERO); - weightControllerRef.invoke(owner, "addSource", "sICX/ICX", stakedLPId, BigInteger.ZERO); - weightControllerRef.invoke(owner, "addSource", "sICX/bnUSD", stakedLPId, BigInteger.ZERO); - } - - @Test - void testFix() throws Exception { - // Arrange - mockUserLock(user1, EXA, 365); - mockUserLock(user2, EXA, 21); - mockUserLock(user3, EXA, 35); - - // Act - vote(user1, "sICX/ICX", BigInteger.valueOf(3000)); - vote(user1, "sICX/bnUSD", BigInteger.valueOf(7000)); - vote(user2, "sICX/ICX", BigInteger.valueOf(4000)); - vote(user2, "sICX/bnUSD", BigInteger.valueOf(5000)); - vote(user3, "sICX/ICX", BigInteger.valueOf(2000)); - vote(user3, "sICX/bnUSD", BigInteger.valueOf(5000)); - - compareToRef(); - - // Until a vote expires everything should be as expected - sm.getBlock().increase(WEEK_BLOCKS); - updateWeights(); - compareToRef(); - - sm.getBlock().increase(WEEK_BLOCKS); - updateWeights(); - compareToRef(); - - // Vote has expired and they should start to differ - sm.getBlock().increase(WEEK_BLOCKS); - updateWeights(); - assertThrows(AssertionFailedError.class, ()-> compareToRef()); - - Score upgrade = sm.deploy(owner, SourceWeightController.class, bBaln.getAddress()); - weightController.setInstance(upgrade.getInstance()); - - BigInteger relativeWeightPre1 = (BigInteger) weightController.call("getRelativeWeight", "sICX/ICX", - BigInteger.valueOf(sm.getBlock().getTimestamp())); - BigInteger relativeWeightPre2 = (BigInteger) weightController.call("getRelativeWeight", "sICX/bnUSD", - BigInteger.valueOf(sm.getBlock().getTimestamp())); - - weightController.invoke(owner, "reset", (Object)new String[]{"sICX/ICX", "sICX/bnUSD"}); - weightController.invoke(owner, "revote", user1.getAddress(), (Object)new String[]{"sICX/ICX", "sICX/bnUSD"}); - weightController.invoke(owner, "revote", user2.getAddress(), (Object)new String[]{"sICX/ICX", "sICX/bnUSD"}); - weightController.invoke(owner, "revote", user3.getAddress(), (Object)new String[]{"sICX/ICX", "sICX/bnUSD"}); - // cannot re vote twice - assertThrows(AssertionError.class, () -> weightController.invoke(owner, "revote", user3.getAddress(), (Object)new String[]{"sICX/ICX", "sICX/bnUSD"})); - - // Asset that the reset does not affect the current weeks vote - BigInteger relativeWeightPost1 = (BigInteger) weightController.call("getRelativeWeight", "sICX/ICX", - BigInteger.valueOf(sm.getBlock().getTimestamp())); - BigInteger relativeWeightPost2 = (BigInteger) weightController.call("getRelativeWeight", "sICX/bnUSD", - BigInteger.valueOf(sm.getBlock().getTimestamp())); - assertEquals(relativeWeightPre1, relativeWeightPost1); - assertEquals(relativeWeightPre2, relativeWeightPost2); - - // This weeks values should still be wrong - assertThrows(AssertionFailedError.class, () -> compareToRef()); - - sm.getBlock().increase(WEEK_BLOCKS); - updateWeights(); - compareToRef(); - - // Another vote expires and should not give any issues - sm.getBlock().increase(WEEK_BLOCKS); - sm.getBlock().increase(WEEK_BLOCKS); - updateWeights(); - compareToRef(); - - // change a vote - vote(user1, "sICX/bnUSD", BigInteger.valueOf(1000)); - vote(user1, "sICX/ICX", BigInteger.valueOf(4000)); - - // relock and revote - mockUserLock(user2, EXA, 21); - vote(user2, "sICX/bnUSD", BigInteger.valueOf(3000)); - vote(user2, "sICX/ICX", BigInteger.valueOf(2000)); - - updateWeights(); - compareToRef(); - - sm.getBlock().increase(WEEK_BLOCKS); - updateWeights(); - compareToRef(); - } - - - private void compareToRef() { - BigInteger currentTime = BigInteger.valueOf(sm.getBlock().getTimestamp()); - BigInteger WEEK = MICRO_SECONDS_IN_A_DAY.multiply(BigInteger.valueOf(7)); - currentTime = currentTime.divide(WEEK).multiply(WEEK); - - Point point1 = (Point)weightController.call("getSourcePointsWeightAt", "sICX/ICX", currentTime); - Point point1Ref = (Point)weightControllerRef.call("getSourcePointsWeightAt", "sICX/ICX", currentTime); - Point point2 = (Point)weightController.call("getSourcePointsWeightAt", "sICX/bnUSD", currentTime); - Point point2Ref = (Point)weightControllerRef.call("getSourcePointsWeightAt", "sICX/bnUSD", currentTime); - Point sum = (Point)weightControllerRef.call("getPointsSumPerType", stakedLPId); - Point sumRef = (Point)weightControllerRef.call("getPointsSumPerType", stakedLPId); - - - BigInteger total = (BigInteger)weightControllerRef.call("getTotalWeight"); - BigInteger totalRef = (BigInteger)weightControllerRef.call("getTotalWeight"); - - assertEquals(point1.slope, point1Ref.slope); - assertEquals(point1.bias, point1Ref.bias); - assertEquals(point2.slope, point2Ref.slope); - assertEquals(point2.bias, point2Ref.bias); - - assertEquals(sum.bias, sumRef.bias); - assertEquals(sum.slope, sumRef.slope); - assertEquals(total, totalRef); - } - - private void vote(Account user, String name, BigInteger weight) { - weightController.invoke(user, "voteForSourceWeights", name, weight); - weightControllerRef.invoke(user, "voteForSourceWeights", name, weight); - } - - private void updateWeights() { - BigInteger currentTime = BigInteger.valueOf(sm.getBlock().getTimestamp()); - - weightController.invoke(owner, "updateRelativeWeight", "sICX/ICX", currentTime); - weightController.invoke(owner, "updateRelativeWeight", "sICX/bnUSD", currentTime); - - weightControllerRef.invoke(owner, "updateRelativeWeight", "sICX/ICX", currentTime); - weightControllerRef.invoke(owner, "updateRelativeWeight", "sICX/bnUSD", currentTime); - } - - private void mockUserLock(Account user, BigInteger slope, int days) { - BigInteger currentTime = BigInteger.valueOf(sm.getBlock().getTimestamp()); - BigInteger WEEK = MICRO_SECONDS_IN_A_DAY.multiply(BigInteger.valueOf(7)); - currentTime = currentTime.divide(WEEK).multiply(WEEK); - when(bBaln.mock.lockedEnd(user.getAddress())).thenReturn(currentTime.add(MICRO_SECONDS_IN_A_DAY.multiply(BigInteger.valueOf(days)))); - when(bBaln.mock.getLastUserSlope(user.getAddress())).thenReturn(slope); - } -} diff --git a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/SourceWeightControllerTest.java b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/SourceWeightControllerTest.java index 9c94268b8..9a4ea66d2 100644 --- a/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/SourceWeightControllerTest.java +++ b/core-contracts/Rewards/src/test/java/network/balanced/score/core/rewards/SourceWeightControllerTest.java @@ -22,9 +22,9 @@ import com.iconloop.score.test.ServiceManager; import network.balanced.score.core.rewards.weight.SourceWeightController; import network.balanced.score.lib.interfaces.BoostedBaln; -import network.balanced.score.lib.interfaces.BoostedBalnScoreInterface; import network.balanced.score.lib.test.UnitTest; import network.balanced.score.lib.test.mock.MockContract; +import network.balanced.score.lib.test.mock.MockBalanced; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; @@ -37,7 +37,7 @@ import static network.balanced.score.lib.utils.Constants.MICRO_SECONDS_IN_A_DAY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.any; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; public class SourceWeightControllerTest extends UnitTest { @@ -47,6 +47,7 @@ public class SourceWeightControllerTest extends UnitTest { static final ServiceManager sm = getServiceManager(); static final Account owner = sm.createAccount(); + MockBalanced mockBalanced; MockContract bBaln; Score weightController; @@ -61,12 +62,13 @@ public class SourceWeightControllerTest extends UnitTest { @BeforeEach void setup() throws Exception { - bBaln = new MockContract<>(BoostedBalnScoreInterface.class, sm, owner); + mockBalanced = new MockBalanced(sm, owner); + bBaln = mockBalanced.bBaln; sm.getBlock().increase(WEEK_BLOCKS * 10); - weightController = sm.deploy(owner, SourceWeightController.class, bBaln.getAddress()); + weightController = sm.deploy(owner, SourceWeightController.class); SourceWeightController.rewards = - (RewardsImpl) sm.deploy(owner, RewardsImpl.class, bBaln.getAddress()).getInstance(); + (RewardsImpl) sm.deploy(owner, RewardsImpl.class, mockBalanced.governance.getAddress()).getInstance(); weightController.invoke(owner, "addType", stakedLPType, BigInteger.ZERO); weightController.invoke(owner, "addType", externalSources, BigInteger.ZERO); diff --git a/core-contracts/StakedLP/src/intTest/java/network/balanced/score/core/stakedlp/StakedlpIntegrationTest.java b/core-contracts/StakedLP/src/intTest/java/network/balanced/score/core/stakedlp/StakedlpIntegrationTest.java index 59cc0760f..f6e6903d8 100644 --- a/core-contracts/StakedLP/src/intTest/java/network/balanced/score/core/stakedlp/StakedlpIntegrationTest.java +++ b/core-contracts/StakedLP/src/intTest/java/network/balanced/score/core/stakedlp/StakedlpIntegrationTest.java @@ -123,14 +123,6 @@ void testStakeAndUnstake() { BigInteger poolId = dex.getPoolId(tokenAClient._address(), tokenBClient._address()); BigInteger balance = dex.balanceOf(userAddress, poolId); - // init rewards weight controller - BigInteger day = balanced.ownerClient.governance.getDay(); - JsonArray setMigrateToVotingDayParameters = new JsonArray() - .add(createParameter(day.add(BigInteger.TEN))); - JsonArray actions = createSingleTransaction(balanced.rewards._address(), "setMigrateToVotingDay", - setMigrateToVotingDayParameters); - balanced.ownerClient.governance.execute(actions.toString()); - //set name addNewDataSource("test", poolId, BigInteger.ONE); diff --git a/score-lib/src/main/java/network/balanced/score/lib/interfaces/DataSource.java b/score-lib/src/main/java/network/balanced/score/lib/interfaces/DataSource.java index 3e5e1cfb0..4b1f1bae1 100644 --- a/score-lib/src/main/java/network/balanced/score/lib/interfaces/DataSource.java +++ b/score-lib/src/main/java/network/balanced/score/lib/interfaces/DataSource.java @@ -25,21 +25,10 @@ @ScoreInterface public interface DataSource { - @External - Object precompute(BigInteger _snapshot_id, BigInteger batch_size); - @External(readonly = true) - BigInteger getTotalValue(String _name, BigInteger _snapshot_id); + Map getBalanceAndSupply(String _name, String _owner); - @External + @External(readonly = true) BigInteger getBnusdValue(String _name); - @External - Map getDataBatch(String _name, int _snapshot_id, int _limit, int _offset); - - @External - BigInteger getBalnPrice(); - - @External - Map getBalanceAndSupply(String _name, String _owner); } \ No newline at end of file diff --git a/score-lib/src/main/java/network/balanced/score/lib/interfaces/Dex.java b/score-lib/src/main/java/network/balanced/score/lib/interfaces/Dex.java index a48465a59..bed093ed8 100644 --- a/score-lib/src/main/java/network/balanced/score/lib/interfaces/Dex.java +++ b/score-lib/src/main/java/network/balanced/score/lib/interfaces/Dex.java @@ -32,7 +32,7 @@ @ScoreClient @ScoreInterface public interface Dex extends Name, AddressManager, Fallback, TokenFallback, - IRC31Base, Version, FloorLimitedInterface { + IRC31Base, Version, FloorLimitedInterface, DataSource { @External void setPoolLpFee(BigInteger _value); @@ -151,9 +151,6 @@ public interface Dex extends Name, AddressManager, Fallback, TokenFallback, @External(readonly = true) BigInteger totalDexAddresses(BigInteger _id); - @External(readonly = true) - Map getBalanceAndSupply(String _name, String _owner); - @External(readonly = true) BigInteger balanceOfAt(Address _account, BigInteger _id, BigInteger _snapshot_id, @Optional boolean _twa); 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..1ae558b25 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 @@ -34,7 +34,7 @@ @ScoreClient @ScoreInterface -public interface Loans extends Name, AddressManager, Version, XTokenReceiver, FloorLimitedInterface { +public interface Loans extends Name, AddressManager, Version, XTokenReceiver, FloorLimitedInterface, DataSource { @External(readonly = true) BigInteger getDay(); @@ -71,9 +71,6 @@ public interface Loans extends Name, AddressManager, Version, XTokenReceiver, Fl @External void addAsset(Address _token_address, boolean _active, boolean _collateral); - @External(readonly = true) - Map getBalanceAndSupply(String _name, String _owner); - @External(readonly = true) BigInteger getBnusdValue(String _name); diff --git a/score-lib/src/main/java/network/balanced/score/lib/interfaces/Rewards.java b/score-lib/src/main/java/network/balanced/score/lib/interfaces/Rewards.java index 44b166c2d..279e1405c 100644 --- a/score-lib/src/main/java/network/balanced/score/lib/interfaces/Rewards.java +++ b/score-lib/src/main/java/network/balanced/score/lib/interfaces/Rewards.java @@ -23,7 +23,6 @@ import network.balanced.score.lib.interfaces.base.RewardsVoting; import network.balanced.score.lib.interfaces.base.TokenFallback; import network.balanced.score.lib.interfaces.base.Version; -import network.balanced.score.lib.structs.DistributionPercentage; import network.balanced.score.lib.structs.RewardsDataEntry; import network.balanced.score.lib.structs.RewardsDataEntryOld; import score.Address; @@ -39,15 +38,9 @@ public interface Rewards extends Name, TokenFallback, - GovernanceAddress, - AdminAddress, - BalnAddress, - BwtAddress, - DaoFundAddress, - ReserveAddress, - BoostedBalnAddress, RewardsVoting, - Version { + Version, + AddressManager { @External(readonly = true) BigInteger getEmission(BigInteger _day); @@ -56,32 +49,17 @@ public interface Rewards extends Map getBalnHoldings(String[] _holders); @External(readonly = true) - BigInteger getBalnHolding(String _holder); + Map getRewards(String _holder); @External(readonly = true) - Map distStatus(); - - @External - void updateBalTokenDistPercentage(DistributionPercentage[] _recipient_list); + BigInteger getBalnHolding(String _holder); @External(readonly = true) List getDataSourceNames(); - @External(readonly = true) - List getRecipients(); - - @External(readonly = true) - Map getRecipientsSplit(); - - @External - void addNewDataSource(String _name, Address _address); - @External void createDataSource(String _name, Address _address, int sourceType); - @External - void removeDataSource(String _name); - @External(readonly = true) Map> getDataSources(); @@ -100,18 +78,12 @@ public interface Rewards extends @External boolean distribute(); - @External(readonly = true) - Map recipientAt(BigInteger _day); - @External void boost(String[] sources); @External void claimRewards(@Optional String[] sources); - @External(readonly = true) - BigInteger getAPY(String _name); - @External void updateRewardsData(String _name, BigInteger _totalSupply, Address _user, BigInteger _balance); diff --git a/score-lib/src/main/java/network/balanced/score/lib/interfaces/base/RewardsVoting.java b/score-lib/src/main/java/network/balanced/score/lib/interfaces/base/RewardsVoting.java index 7da3ece62..a670dc05d 100644 --- a/score-lib/src/main/java/network/balanced/score/lib/interfaces/base/RewardsVoting.java +++ b/score-lib/src/main/java/network/balanced/score/lib/interfaces/base/RewardsVoting.java @@ -30,9 +30,6 @@ public interface RewardsVoting { @External void setVotable(String name, boolean votable); - @External - void addDataSource(String name, int sourceType, BigInteger weight); - @External void addType(String name); diff --git a/score-lib/src/main/java/network/balanced/score/lib/utils/BalancedAddressManager.java b/score-lib/src/main/java/network/balanced/score/lib/utils/BalancedAddressManager.java index d2eaced58..349881937 100644 --- a/score-lib/src/main/java/network/balanced/score/lib/utils/BalancedAddressManager.java +++ b/score-lib/src/main/java/network/balanced/score/lib/utils/BalancedAddressManager.java @@ -20,7 +20,6 @@ import score.Context; import score.DictDB; import score.VarDB; -import foundation.icon.xcall.NetworkAddress; import static network.balanced.score.lib.utils.Check.readonly; public class BalancedAddressManager { @@ -48,7 +47,7 @@ public static Address fetchAddress(String name) { "getAddress", name); } - private static Address getAddress(String name) { + public static Address getAddress(String name) { Address address = contractAddresses.get(name); if (address == null) { address = fetchAddress(name); 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..94f43336e 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 @@ -26,7 +26,7 @@ public class Versions { public final static String WORKERTOKEN = "v1.0.0"; public final static String BNUSD = "v1.1.0"; public final static String FEEHANDLER = "v1.0.1"; - public final static String REWARDS = "v1.1.1"; + public final static String REWARDS = "v1.2.0"; public final static String STABILITY = "v1.1.1"; public final static String BALANCEDORACLE = "v1.1.0"; public final static String DAOFUND = "v1.1.1";