Skip to content

Commit

Permalink
Merge pull request #213 from ommfinance/audit/issue-32-fix
Browse files Browse the repository at this point in the history
IISS4 Reward System
  • Loading branch information
Suyog007 authored Mar 6, 2024
2 parents ba6d29d + 5bfa8c2 commit 0eaa872
Show file tree
Hide file tree
Showing 13 changed files with 590 additions and 64 deletions.
2 changes: 1 addition & 1 deletion core-contracts/FeeDistribution/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version '0.0.1'
version '0.1.1'

optimizedJar {
mainClassName = 'finance.omm.score.fee.distribution.FeeDistributionImpl'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.math.BigInteger;
import java.util.Map;

import static finance.omm.utils.constants.AddressConstant.ZERO_SCORE_ADDRESS;
import static finance.omm.utils.math.MathUtils.ICX;

public abstract class AbstractFeeDistribution extends AddressProvider implements FeeDistribution {
Expand All @@ -25,28 +26,32 @@ public abstract class AbstractFeeDistribution extends AddressProvider implements
public static final String ACCUMULATED_FEE = "accumulated_fee";
public static final String FEE_DISTRIBUTION_WEIGHT = "fee_distribution_weight";
public static final String FEE_TO_DISTRIBUTE = "fee_to_distribute";
public static final String JAILED_VALIDATOR_SHARE = "jailed_validator_share";
protected final EnumerableDictDB<Address, BigInteger> collectedFee =
new EnumerableDictDB<>(COLLECTED_FEE, Address.class, BigInteger.class);
protected final VarDB<BigInteger> validatorRewards = Context.newVarDB(VALIDATOR_FEE_COLLECTED, BigInteger.class);
protected final DictDB<Address, BigInteger> accumulatedFee = Context.newDictDB(ACCUMULATED_FEE, BigInteger.class);
protected final EnumerableDictDB<Address, BigInteger> feeDistributionWeight = new

EnumerableDictDB<>(FEE_DISTRIBUTION_WEIGHT, Address.class, BigInteger.class);
protected final VarDB<BigInteger> feeToDistribute = Context.newVarDB(FEE_TO_DISTRIBUTE, BigInteger.class);
protected final VarDB<BigInteger> feeToDistribute = Context.newVarDB(FEE_TO_DISTRIBUTE,BigInteger.class);
protected final VarDB<BigInteger> validatorShare = Context.newVarDB(JAILED_VALIDATOR_SHARE,BigInteger.class);



public AbstractFeeDistribution(Address addressProvider) {
super(addressProvider, false);
}


protected void distributeFee(BigInteger amount) {
Address lendingPoolCoreAddr = getAddress(Contracts.LENDING_POOL_CORE.getKey());
Address daoFundAddr = getAddress(Contracts.DAO_FUND.getKey());


BigInteger remaining = amount;
BigInteger denominator = ICX;
BigInteger amountToDistribute;

Address lendingPoolCoreAddr = getAddress(Contracts.LENDING_POOL_CORE.getKey());
Address daoFundAddr = getAddress(Contracts.DAO_FUND.getKey());

for (Address receiver : feeDistributionWeight.keySet()) {
BigInteger percentageToDistribute = feeDistributionWeight.get(receiver);
if (percentageToDistribute.signum() > 0) {
Expand All @@ -57,14 +62,26 @@ protected void distributeFee(BigInteger amount) {

if (amountToDistribute.signum() > 0) {
if (receiver.equals(lendingPoolCoreAddr)) {
distributeFeeToValidator(lendingPoolCoreAddr, amountToDistribute);
validatorRewards.set(getValidatorCollectedFee().add(amountToDistribute));

BigInteger validatorFee = distributeFeeToValidator(lendingPoolCoreAddr,amountToDistribute);

BigInteger currentValidatorRewards = amountToDistribute.subtract(validatorFee);
validatorRewards.set(getValidatorCollectedFee().add(currentValidatorRewards));

if (validatorFee.compareTo(BigInteger.ZERO) > 0){
this.validatorShare.set(getValidatorShare().add(validatorFee));
}

} else if (receiver.equals(daoFundAddr)) {

BigInteger feeCollected = collectedFee.getOrDefault(receiver, BigInteger.ZERO);
collectedFee.put(receiver, feeCollected.add(amountToDistribute));
call(Contracts.sICX, "transfer", receiver, amountToDistribute);
BigInteger totalDistributionFee = amountToDistribute.add(getValidatorShare());
collectedFee.put(receiver, feeCollected.add(totalDistributionFee));
this.validatorShare.set(BigInteger.ZERO);
call(Contracts.sICX, "transfer", receiver, totalDistributionFee);

} else {

BigInteger feeAccumulatedAfterClaim = accumulatedFee.getOrDefault(receiver, BigInteger.ZERO);
accumulatedFee.set(receiver, feeAccumulatedAfterClaim.add(amountToDistribute));
}
Expand All @@ -73,33 +90,62 @@ protected void distributeFee(BigInteger amount) {
denominator = denominator.subtract(percentageToDistribute);
}

if (remaining.signum() > 0){
BigInteger feeCollected = collectedFee.getOrDefault(daoFundAddr, BigInteger.ZERO);
collectedFee.put(daoFundAddr, feeCollected.add(remaining));

call(Contracts.sICX, "transfer", daoFundAddr, remaining);
}


this.feeToDistribute.set(BigInteger.ZERO);
FeeDisbursed(amount);


}


private void distributeFeeToValidator(Address lendingPoolCoreAddr, BigInteger amount) {

private BigInteger distributeFeeToValidator(Address lendingPoolCoreAddr,BigInteger amount) {
Map<String, BigInteger> ommValidators = call(Map.class, Contracts.STAKING,
"getActualUserDelegationPercentage", lendingPoolCoreAddr);
BigInteger amountToDistribute = amount;
BigInteger denominator = BigInteger.valueOf(100).multiply(ICX);

BigInteger remaining = BigInteger.ZERO;

for (String validator : ommValidators.keySet()) {
if (denominator.signum() == 0){
break;
}
Address validatorAddr = Address.fromString(validator);

BigInteger currentPercentage = ommValidators.get(validator);
BigInteger feePortion = (currentPercentage.multiply(amountToDistribute)).divide(denominator);

amountToDistribute = amountToDistribute.subtract(feePortion);
denominator = denominator.subtract(currentPercentage);

BigInteger feeAccumulatedAfterClaim = accumulatedFee.getOrDefault(validatorAddr, BigInteger.ZERO);
accumulatedFee.set(validatorAddr, feeAccumulatedAfterClaim.add(feePortion));
if (checkPrepStatus(validatorAddr)){
BigInteger feeAccumulatedAfterClaim = accumulatedFee.getOrDefault(validatorAddr, BigInteger.ZERO);
accumulatedFee.set(validatorAddr, feeAccumulatedAfterClaim.add(feePortion));
}else {
remaining = remaining.add(feePortion);
}

}

return remaining;
}

private boolean checkPrepStatus(Address prepAddr){
Map<String, Object> prepDict = call(Map.class,ZERO_SCORE_ADDRESS, "getPRep", prepAddr);
BigInteger jailFlags = (BigInteger) prepDict.get("jailFlags");

return jailFlags == null || jailFlags.signum() == 0;
}

private BigInteger getValidatorShare(){
return this.validatorShare.getOrDefault(BigInteger.ZERO);
}

@EventLog(indexed = 3)
Expand All @@ -122,4 +168,8 @@ protected void onlyOwner() {
}
}

public <K> K call(Class<K> kClass, Address address, String method, Object... params) {
return Context.call(kClass, address, method, params);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ public BigInteger getClaimableFee(Address user) {
}

@External
public void setFeeDistribution(Address[] addresses, BigInteger[] weights) {
public void setFeeDistribution(Address[] addresses, BigInteger[] weights){
// Note: when setting addresses validator address should precede daoFund address for complete distribution

onlyOwner();
int addressSize = addresses.length;
if (!(addressSize == weights.length)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class AbstractFeeDistributionTest extends TestBase {
protected static final Account testScore3 =Account.newScoreAccount(3);
protected static final Account validator1 = sm.createAccount();
protected static final Account validator2 = sm.createAccount();
protected static final Account validator3 = sm.createAccount();
protected static final BigInteger HUNDRED = BigInteger.valueOf(100);

protected static MockedStatic<Context> contextMock;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.math.BigInteger;
import java.util.Map;

import static finance.omm.utils.constants.AddressConstant.ZERO_SCORE_ADDRESS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
Expand Down Expand Up @@ -91,23 +92,22 @@ public void tokenFallback(){
BigInteger weight2 = (BigInteger.valueOf(225).multiply(ICX).divide(BigInteger.TEN)).divide(HUNDRED);
BigInteger weight3 = (BigInteger.valueOf(675).multiply(ICX).divide(BigInteger.TEN)).divide(HUNDRED);

Address[] receiverAddr = new Address[]{testScore.getAddress(),daoFund,validator};
BigInteger[] weight = new BigInteger[]{weight1,weight2,weight3};
Address[] receiverAddr = new Address[]{testScore.getAddress(),validator,daoFund};
BigInteger[] weight = new BigInteger[]{weight1,weight3,weight2};

doNothing().when(spyScore).call(any(Contracts.class),eq("transfer"),any(),any());

doReturn(Map.of(
validator1.getAddress().toString(),BigInteger.valueOf(10).multiply(ICX),
validator2.getAddress().toString(),BigInteger.valueOf(90).multiply(ICX)
)).when(spyScore).call(eq(Map.class),any(),eq("getActualUserDelegationPercentage"),any());
)).when(spyScore).call(eq(Map.class),any(Contracts.class),eq("getActualUserDelegationPercentage"),any());



score.invoke(owner,"setFeeDistribution",receiverAddr,weight);

Address sicx = MOCK_CONTRACT_ADDRESS.get(Contracts.sICX).getAddress();
contextMock.when(mockCaller()).thenReturn(sicx);
System.out.println(sicx);
score.invoke(owner,"tokenFallback",owner.getAddress(),feeAmount,"b".getBytes());

verify(spyScore).FeeDistributed(BigInteger.valueOf(100).multiply(ICX));
Expand All @@ -121,6 +121,8 @@ public void claimRewards(){
// validator 2 = 90% of 67.5 = 60.75 ICX
tokenFallback();

doReturn(Map.of()).when(spyScore).call(Map.class,ZERO_SCORE_ADDRESS, "getPRep", validator1.getAddress());
doReturn(Map.of("jailFlags",BigInteger.ZERO)).when(spyScore).call(Map.class,ZERO_SCORE_ADDRESS, "getPRep", validator2.getAddress());

BigInteger calimAmountValidator1 = BigInteger.valueOf(675).multiply(ICX).divide(BigInteger.valueOf(100));

Expand Down Expand Up @@ -172,6 +174,55 @@ void claimRewards_without_fee_disburse(){
}

@Test

void distributeFeeToValidator_withJailedPreps(){
tokenFallback();
// validator = 67.5 ICX
// validator1 = 30% of 67.5 = 20.25 ICX
// validator 2 = 50% of 67.5 = 33.75 ICX
// validator 3 = 20% of 67.5 = 13.5 ICX


doReturn(Map.of()).when(spyScore).call(Map.class,ZERO_SCORE_ADDRESS, "getPRep", validator1.getAddress());
doReturn(Map.of("jailFlags",BigInteger.ZERO)).when(spyScore).call(Map.class,ZERO_SCORE_ADDRESS, "getPRep", validator2.getAddress());
doReturn(Map.of("jailFlags",BigInteger.TEN)).when(spyScore).call(Map.class,ZERO_SCORE_ADDRESS, "getPRep", validator3.getAddress());


// only 2 validators of omm are in valid Preps of staking
doReturn(Map.of(
validator1.getAddress().toString(),BigInteger.valueOf(30).multiply(ICX),
validator2.getAddress().toString(),BigInteger.valueOf(50).multiply(ICX),
validator3.getAddress().toString(),BigInteger.valueOf(20).multiply(ICX)
)).when(spyScore).call(eq(Map.class),any(Contracts.class),eq("getActualUserDelegationPercentage"),any());

contextMock.when(mockCaller()).thenReturn(validator1.getAddress());

// fee will be disbursed here
score.invoke(validator1,"claimRewards",validator1.getAddress());

assertEquals(BigInteger.valueOf(3375).multiply(ICX).divide(HUNDRED),
score.call("getAccumulatedFee",validator2.getAddress()));
assertEquals(BigInteger.ZERO,
score.call("getAccumulatedFee",validator3.getAddress()));


Address daoFund = MOCK_CONTRACT_ADDRESS.get(Contracts.DAO_FUND).getAddress();
BigInteger val = BigInteger.valueOf(36).multiply(ICX);
assertEquals(val,score.call("getCollectedFee",daoFund));


assertEquals(BigInteger.valueOf(2025).multiply(ICX).divide(HUNDRED),
score.call("getCollectedFee",validator1.getAddress()));

verify(spyScore).FeeDisbursed(BigInteger.valueOf(100).multiply(ICX));
verify(spyScore).FeeClaimed(validator1.getAddress(),validator1.getAddress(),
BigInteger.valueOf(2025).multiply(ICX).divide(HUNDRED));



}


void distributeFee_withClaimRewards(){
tokenFallback();

Expand All @@ -190,6 +241,7 @@ void distributeFee_withClaimRewards(){

verify(spyScore).FeeDisbursed(BigInteger.valueOf(100).multiply(ICX));
verify(spyScore,never()).FeeClaimed(validator2.getAddress(),validator2.getAddress(),BigInteger.ZERO);

}

@Test
Expand Down Expand Up @@ -226,7 +278,7 @@ void check_claimableFee(){
doReturn(Map.of(
validator1.getAddress().toString(),BigInteger.valueOf(10).multiply(ICX),
validator2.getAddress().toString(),BigInteger.valueOf(90).multiply(ICX)
)).when(spyScore).call(eq(Map.class),any(),eq("getActualUserDelegationPercentage"),any());
)).when(spyScore).call(eq(Map.class),any(Contracts.class),eq("getActualUserDelegationPercentage"),any());



Expand Down
2 changes: 1 addition & 1 deletion core-contracts/Staking/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version '0.0.1'
version '1.0.3'

optimizedJar {
mainClassName = 'finance.omm.score.staking.StakingImpl'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ public static void setup() throws Exception {

ownerClient.staking.setOmmLendingPoolCore(lendingPoolCore);
ownerClient.sICX.setMinter(addressMap.get(Contracts.STAKING.getKey()));
ownerClient.staking.setPrepProductivity(BigInteger.ZERO);
ownerClient.staking.setFeePercentage(BigInteger.valueOf(10).multiply(ONE_EXA));


}

Expand Down
Loading

0 comments on commit 0eaa872

Please sign in to comment.