Skip to content

Commit

Permalink
Merge pull request #408 from balancednetwork/feature/allow-for-multip…
Browse files Browse the repository at this point in the history
…le-reward-tokens

Multiple rewards tokens in Rewards
  • Loading branch information
AntonAndell authored Jul 29, 2024
2 parents de69bba + 40c9305 commit c7cc096
Show file tree
Hide file tree
Showing 9 changed files with 608 additions and 57 deletions.
2 changes: 2 additions & 0 deletions core-contracts/Rewards/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ dependencies {
compileOnly Dependencies.javaeeApi
implementation Dependencies.javaeeScorex
implementation project(':score-lib')
implementation Dependencies.minimalJson

testImplementation Dependencies.javaeeUnitTest
testImplementation Dependencies.junitJupiter
testRuntimeOnly Dependencies.junitJupiterEngine
testImplementation Dependencies.mockitoCore
testImplementation Dependencies.mockitoInline
testImplementation project(':test-lib')
testImplementation Dependencies.json

intTestAnnotationProcessor project(':score-client')
intTestImplementation project(':score-client')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -47,8 +48,21 @@ public class DataSourceImpl {
BigInteger.class);
private final BranchDB<String, VarDB<BigInteger>> totalWeight = Context.newBranchDB("running_total",
BigInteger.class);
private final BranchDB<String, VarDB<BigInteger>> totalSupply = Context.newBranchDB("total_supply",

// name -> token -> day -> amount
private final BranchDB<String, BranchDB<Address, DictDB<BigInteger, BigInteger>>> externalDist = Context
.newBranchDB("external_total_dist", BigInteger.class);
private final BranchDB<String, DictDB<String, BigInteger>> userBalance = Context.newBranchDB("user_balance",
BigInteger.class);
private final BranchDB<String, VarDB<BigInteger>> totalSupply = Context.newBranchDB("total_supplyV2",
BigInteger.class);
// name -> token -> user -> amount
private final BranchDB<String, BranchDB<Address, DictDB<String, BigInteger>>> externalUserWeight = Context
.newBranchDB("external_user_weight", BigInteger.class);
// name -> token -> weight
private final BranchDB<String, DictDB<Address, BigInteger>> externalTotalWeights = Context
.newBranchDB("external_running_total", BigInteger.class);
private final BranchDB<String, ArrayDB<Address>> rewardTokens = Context.newBranchDB("reward_tokens", Address.class);

private final String dbKey;

Expand Down Expand Up @@ -109,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);
Expand Down Expand Up @@ -146,6 +168,14 @@ private void setWorkingBalance(String user, BigInteger balance) {
this.userWorkingBalance.at(dbKey).set(user, balance);
}

public BigInteger getBalance(String user) {
return userBalance.at(dbKey).getOrDefault(user, BigInteger.ZERO);
}

private void setBalance(String user, BigInteger balance) {
this.userBalance.at(dbKey).set(user, balance);
}

public BigInteger getTotalDist(BigInteger day, boolean readonly) {
DictDB<BigInteger, BigInteger> distAt = totalDist.at(dbKey);
BigInteger dist = distAt.get(day);
Expand All @@ -169,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);
}
Expand All @@ -181,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);
}
Expand All @@ -189,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<Address> 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<Address> getRewardTokensDB() {
return rewardTokens.at(dbKey);
}

public void addRewardToken(Address token) {
rewardTokens.at(dbKey).add(token);
}

@SuppressWarnings("unchecked")
Expand All @@ -208,25 +270,28 @@ public Map<String, BigInteger> loadCurrentSupply(String owner) {
}
}

public BigInteger updateSingleUserData(BigInteger currentTime, BigInteger prevTotalSupply, String user,
BigInteger prevBalance, boolean readOnlyContext) {
public Map<Address, BigInteger> updateSingleUserData(BigInteger currentTime, BalanceData balances, String user, boolean readOnlyContext) {
BigInteger currentUserWeight = getUserWeight(user);
BigInteger lastUpdateTimestamp = getLastUpdateTimeUs();
Address[] externalRewards = getRewardTokens();
Address baln = BalancedAddressManager.getBaln();
Map<Address, BigInteger> totalWeight = updateTotalWeight(lastUpdateTimestamp, currentTime, balances, externalRewards, readOnlyContext);
Map<Address, BigInteger> 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;
Expand All @@ -238,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);

Expand Down Expand Up @@ -279,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<Address, BigInteger> updateTotalWeight(BigInteger lastUpdateTimestamp, BigInteger currentTime,
BalanceData balances, Address[] externalRewards, boolean readOnlyContext) {
Address baln = BalancedAddressManager.getBaln();
Map<Address, BigInteger> 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;
Expand All @@ -292,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
Expand All @@ -305,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() {
Expand All @@ -328,6 +414,13 @@ public Map<String, Object> getDataAt(BigInteger day) {
sourceData.put("contract_address", getContractAddress());
sourceData.put("workingSupply", getWorkingSupply());
sourceData.put("total_dist", getTotalDist(day, true));
Address[] externalRewards = getRewardTokens();
Map<String, Object> 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;
}
Expand All @@ -336,6 +429,19 @@ public Map<String, Object> getData() {
return getDataAt(RewardsImpl.getDay());
}

public Map<String, Object> getUserData(String user) {
Map<String, Object> 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) {
BigInteger deltaWeight = totalWeight.subtract(userWeight);
return deltaWeight.multiply(prevUserBalance).divide(EXA);
Expand Down
Loading

0 comments on commit c7cc096

Please sign in to comment.