Skip to content

Commit

Permalink
crosschain lp functionalities and unitest
Browse files Browse the repository at this point in the history
  • Loading branch information
sagars committed Nov 26, 2024
1 parent db53648 commit 53056e1
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 20 deletions.
1 change: 1 addition & 0 deletions core-contracts/Savings/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
implementation Dependencies.javaeeScorex
implementation Dependencies.minimalJson
implementation project(':score-lib')
implementation 'xyz.venture23:xcall-lib:2.1.0'

testImplementation Dependencies.javaeeUnitTest
testImplementation Dependencies.javaeeTokens
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import network.balanced.score.lib.utils.BalancedFloorLimits;
import network.balanced.score.lib.utils.EnumerableSetDB;
import network.balanced.score.lib.utils.TokenTransfer;
import score.Address;
import score.BranchDB;
import score.Context;
Expand Down Expand Up @@ -74,7 +75,6 @@ public static void addWeight(Address token, BigInteger amount) {
}

public static void changeLock(String user, BigInteger change) {
checkAddressIsICON(user);
BigInteger prevAmount = workingBalance.getOrDefault(user, BigInteger.ZERO);
BigInteger prevTotal = totalWorkingBalance.getOrDefault(BigInteger.ZERO);
updateAllUserRewards(user, prevAmount);
Expand All @@ -85,18 +85,18 @@ public static void changeLock(String user, BigInteger change) {
totalWorkingBalance.set(prevTotal.add(change));
}

public static void claimRewards(Address user) {
updateAllUserRewards(user.toString());
public static void claimRewards(String user) {
updateAllUserRewards(user);
int numberOfTokens = allowedTokens.length();
DictDB<Address, BigInteger> rewards = userRewards.at(user.toString());
DictDB<Address, BigInteger> rewards = userRewards.at(user);

for (int i = 0; i < numberOfTokens; i++) {
Address token = allowedTokens.at(i);
BigInteger amount = rewards.getOrDefault(token, BigInteger.ZERO);
rewards.set(token, null);
BalancedFloorLimits.verifyWithdraw(token, amount);
if (!amount.equals(BigInteger.ZERO)) {
Context.call(token, "transfer", user, amount, new byte[0]);
TokenTransfer.transfer(token, user, amount, new byte[0]);
}
}
}
Expand Down Expand Up @@ -130,10 +130,4 @@ public static void removeToken(Address token) {
allowedTokens.remove(token);
}

// For now only allow ICON addresses to lock
// But keep as string to allow it in the future easily
private static void checkAddressIsICON(String str) {
Context.require(str.length() == Address.LENGTH * 2, "Only ICON addresses are allowed to lock into the saving account at this time");
Context.require(str.startsWith("hx") || str.startsWith("cx"), "Only ICON addresses are allowed to lock into the saving account at this time");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,25 @@
import static network.balanced.score.lib.utils.BalancedAddressManager.getLoans;
import static network.balanced.score.lib.utils.BalancedAddressManager.getTrickler;
import static network.balanced.score.lib.utils.BalancedAddressManager.resetAddress;
import static network.balanced.score.lib.utils.Check.checkStatus;
import static network.balanced.score.lib.utils.Check.onlyGovernance;
import static network.balanced.score.lib.utils.Check.*;

import java.math.BigInteger;
import java.util.Map;

import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;

import network.balanced.score.lib.utils.BalancedAddressManager;
import network.balanced.score.lib.utils.Names;
import network.balanced.score.lib.utils.Versions;
import network.balanced.score.lib.interfaces.RewardsXCall;
import network.balanced.score.lib.interfaces.SavingsXCall;
import network.balanced.score.lib.utils.*;
import network.balanced.score.lib.interfaces.Savings;
import network.balanced.score.lib.utils.FloorLimited;
import network.balanced.score.lib.utils.BalancedFloorLimits;

import score.Address;
import score.Context;
import score.VarDB;
import score.DictDB;
import score.annotation.External;
import score.annotation.Optional;

public class SavingsImpl extends FloorLimited implements Savings {
public static final String VERSION = "version";
Expand Down Expand Up @@ -104,10 +102,23 @@ public void unlock(BigInteger amount) {
Context.call(bnUSD, "transfer", Context.getCaller(), amount, new byte[0]);
}

@External
public void handleCallMessage(String _from, byte[] _data, @Optional String[] _protocols) {
checkStatus();
only(BalancedAddressManager.getXCall());
XCallUtils.verifyXCallProtocols(_from, _protocols);
SavingsXCall.process(this, _from, _data);
}

public void xClaimRewards(String from) {
gatherRewards();
RewardsManager.claimRewards(from);
}

@External
public void claimRewards() {
gatherRewards();
RewardsManager.claimRewards(Context.getCaller());
RewardsManager.claimRewards(Context.getCaller().toString());
}

@External(readonly = true)
Expand Down Expand Up @@ -142,6 +153,11 @@ public void tokenFallback(Address _from, BigInteger _value, byte[] _data) {
handleTokenDeposit(_from.toString(), _value, _data);
}

@External
public void xTokenFallback(String _from, BigInteger _value, byte[] _data) {
handleTokenDeposit(_from, _value, _data);
}

private void handleTokenDeposit(String _from, BigInteger _value, byte[] _data) {
checkStatus();
Context.require(_value.compareTo(BigInteger.ZERO) > 0, "Zero transfers not allowed");
Expand All @@ -156,8 +172,11 @@ private void handleTokenDeposit(String _from, BigInteger _value, byte[] _data) {
String unpackedData = new String(_data);
Context.require(!unpackedData.equals(""), "Token Fallback: Data can't be empty");
JsonObject json = Json.parse(unpackedData).asObject();

String method = json.get("method").asString();
JsonObject params = json.get("params").asObject();
if(params.get("to")!=null){
_from = params.get("to").asString();
}
switch (method) {
case "_lock": {
Context.require(token.equals(getBnusd()), "Only BnUSD can be locked");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@
import com.iconloop.score.test.Account;
import com.iconloop.score.test.Score;
import com.iconloop.score.test.ServiceManager;
import foundation.icon.xcall.NetworkAddress;
import network.balanced.score.lib.test.UnitTest;
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;
import score.Address;
import score.ByteArrayObjectWriter;
import score.Context;

import java.math.BigInteger;
import java.util.Map;
Expand All @@ -41,15 +45,18 @@ class SavingsTest extends UnitTest {
private MockBalanced mockBalanced;

private Score savings;
private final String NATIVE_NID = "0x1.ICON";


@BeforeEach
void setup() throws Exception {
mockBalanced = new MockBalanced(sm, owner);
when(mockBalanced.xCall.mock.getNetworkId()).thenReturn(NATIVE_NID);
savings = sm.deploy(owner, SavingsImpl.class, mockBalanced.governance.getAddress());
savings.invoke(mockBalanced.governance.account, "addAcceptedToken", mockBalanced.sicx.getAddress());
savings.invoke(mockBalanced.governance.account, "addAcceptedToken", mockBalanced.baln.getAddress());
savings.invoke(mockBalanced.governance.account, "addAcceptedToken", mockBalanced.bnUSD.getAddress());

}

@Test
Expand Down Expand Up @@ -208,4 +215,119 @@ void permissions() {
assertOnlyCallableBy(mockBalanced.governance.getAddress(), savings, "addAcceptedToken", mockBalanced.sicx.getAddress());
assertOnlyCallableBy(mockBalanced.governance.getAddress(), savings, "removeAcceptedToken", mockBalanced.sicx.getAddress());
}

@Test
void lockCrosschainBnUSDWithOutTo() {
//Arrange
String user = new NetworkAddress("0x1.ETH", "0x120").toString();
BigInteger lockAmount = BigInteger.valueOf(100).multiply(EXA);

// Act
byte[] data = tokenData("_lock", Map.of());
savings.invoke(mockBalanced.bnUSD.account, "xTokenFallback", user, lockAmount, data);

// Assert
BigInteger amountLocked = (BigInteger) savings.call("getLockedAmount", user);
assertEquals(lockAmount, amountLocked);
}

@Test
void lockCrosschainBnUSDWithTo() {
//Arrange
String user = new NetworkAddress("0x1.ETH", "0x120").toString();
String toUser = new NetworkAddress("0x1.ETH", "0x121").toString();
BigInteger lockAmount = BigInteger.valueOf(100).multiply(EXA);

// Act
byte[] data = tokenData("_lock", Map.of("to", toUser ));
savings.invoke(mockBalanced.bnUSD.account, "xTokenFallback", user, lockAmount, data);

// Assert
BigInteger amountLocked = (BigInteger) savings.call("getLockedAmount", toUser);
assertEquals(lockAmount, amountLocked);
}

@Test
@SuppressWarnings("unchecked")
void crossChainClaimRewards() {
//Arrange
String user1 = new NetworkAddress("0x1.ETH", "0x120").toString();
String user2 = new NetworkAddress("0x1.ETH", "0x121").toString();
String user3 = new NetworkAddress("0x1.ETH", "0x122").toString();
BigInteger lockAmount1 = BigInteger.valueOf(100).multiply(EXA);
BigInteger lockAmount2 = BigInteger.valueOf(200).multiply(EXA);
BigInteger lockAmount3 = BigInteger.valueOf(200).multiply(EXA);

byte[] data = tokenData("_lock", Map.of());
savings.invoke(mockBalanced.bnUSD.account, "xTokenFallback", user1, lockAmount1, data);
savings.invoke(mockBalanced.bnUSD.account, "xTokenFallback", user2, lockAmount2, data);

// Act
BigInteger balnRewards = BigInteger.valueOf(100).multiply(EXA);
BigInteger sICXRewards = BigInteger.valueOf(100).multiply(EXA);
BigInteger bnUSDRewards = BigInteger.valueOf(2000).multiply(EXA);
savings.invoke(mockBalanced.baln.account, "tokenFallback", mockBalanced.daofund.getAddress(), balnRewards, new byte[0]);
savings.invoke(mockBalanced.sicx.account, "tokenFallback", mockBalanced.daofund.getAddress(), sICXRewards, new byte[0]);
savings.invoke(mockBalanced.bnUSD.account, "tokenFallback", mockBalanced.daofund.getAddress(), bnUSDRewards, new byte[0]);
savings.invoke(mockBalanced.bnUSD.account, "xTokenFallback", user3, lockAmount3, data);

// Assert
Map<String, BigInteger> rewards1 = (Map<String, BigInteger>) savings.call("getUnclaimedRewards", user1);
Map<String, BigInteger> rewards2 = (Map<String, BigInteger>) savings.call("getUnclaimedRewards", user2);
Map<String, BigInteger> rewards3 = (Map<String, BigInteger>) savings.call("getUnclaimedRewards", user3);

BigInteger total = lockAmount1.add(lockAmount2);
BigInteger sICXWeight = sICXRewards.multiply(EXA).divide(total);
BigInteger balnWeight = balnRewards.multiply(EXA).divide(total);
BigInteger bnUSDWeight = bnUSDRewards.multiply(EXA).divide(total);
assertEquals(sICXWeight.multiply(lockAmount1).divide(EXA), rewards1.get(mockBalanced.sicx.getAddress().toString()));
assertEquals(balnWeight.multiply(lockAmount1).divide(EXA), rewards1.get(mockBalanced.baln.getAddress().toString()));
assertEquals(bnUSDWeight.multiply(lockAmount1).divide(EXA), rewards1.get(mockBalanced.bnUSD.getAddress().toString()));
assertEquals(sICXWeight.multiply(lockAmount2).divide(EXA), rewards2.get(mockBalanced.sicx.getAddress().toString()));
assertEquals(balnWeight.multiply(lockAmount2).divide(EXA), rewards2.get(mockBalanced.baln.getAddress().toString()));
assertEquals(bnUSDWeight.multiply(lockAmount2).divide(EXA), rewards2.get(mockBalanced.bnUSD.getAddress().toString()));
assertEquals(BigInteger.ZERO, rewards3.get(mockBalanced.sicx.getAddress().toString()));
assertEquals(BigInteger.ZERO, rewards3.get(mockBalanced.baln.getAddress().toString()));
assertEquals(BigInteger.ZERO, rewards3.get(mockBalanced.bnUSD.getAddress().toString()));


// Act
savings.invoke(mockBalanced.baln.account, "tokenFallback", mockBalanced.daofund.getAddress(), balnRewards, new byte[0]);
savings.invoke(mockBalanced.sicx.account, "tokenFallback", mockBalanced.daofund.getAddress(), sICXRewards, new byte[0]);

// Assert
rewards1 = (Map<String, BigInteger>) savings.call("getUnclaimedRewards", user1);
rewards2 = (Map<String, BigInteger>) savings.call("getUnclaimedRewards", user2);
rewards3 = (Map<String, BigInteger>) savings.call("getUnclaimedRewards", user3);

BigInteger newTotal = lockAmount1.add(lockAmount2).add(lockAmount3);
BigInteger newSICXWeight = sICXWeight.add(sICXRewards.multiply(EXA).divide(newTotal));
BigInteger newBalnWeight = balnWeight.add(balnRewards.multiply(EXA).divide(newTotal));
assertEquals(newSICXWeight.multiply(lockAmount1).divide(EXA), rewards1.get(mockBalanced.sicx.getAddress().toString()));
assertEquals(newBalnWeight.multiply(lockAmount1).divide(EXA), rewards1.get(mockBalanced.baln.getAddress().toString()));
assertEquals(newSICXWeight.multiply(lockAmount2).divide(EXA), rewards2.get(mockBalanced.sicx.getAddress().toString()));
assertEquals(newBalnWeight.multiply(lockAmount2).divide(EXA), rewards2.get(mockBalanced.baln.getAddress().toString()));
assertEquals(newSICXWeight.subtract(sICXWeight).multiply(lockAmount3).divide(EXA), rewards3.get(mockBalanced.sicx.getAddress().toString()));
assertEquals(newBalnWeight.subtract(balnWeight).multiply(lockAmount3).divide(EXA), rewards3.get(mockBalanced.baln.getAddress().toString()));

assertEquals(sICXRewards.multiply(BigInteger.TWO), savings.call("getTotalPayout", mockBalanced.sicx.getAddress()));
assertEquals(balnRewards.multiply(BigInteger.TWO), savings.call("getTotalPayout", mockBalanced.baln.getAddress()));
assertEquals(bnUSDRewards, savings.call("getTotalPayout", mockBalanced.bnUSD.getAddress()));

// Act
when(mockBalanced.daofund.mock.getXCallFeePermission(any(Address.class), any(String.class))).thenReturn(true);
savings.invoke(mockBalanced.xCall.account, "handleCallMessage", user1, getClaimRewardsData(), new String[0]);

// Assert
verify(mockBalanced.sicx.mock).crossTransfer(user1, rewards1.get(mockBalanced.sicx.getAddress().toString()), new byte[0]);
verify(mockBalanced.baln.mock).crossTransfer(user1, rewards1.get(mockBalanced.baln.getAddress().toString()), new byte[0]);
}

static byte[] getClaimRewardsData() {
ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn");
writer.beginList(1);
writer.write("xclaimrewards");
writer.end();
return writer.toByteArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@

import foundation.icon.score.client.ScoreClient;
import foundation.icon.score.client.ScoreInterface;
import network.balanced.score.lib.annotations.XCall;
import network.balanced.score.lib.interfaces.addresses.AddressManager;
import network.balanced.score.lib.interfaces.base.Name;
import network.balanced.score.lib.interfaces.base.Version;
import score.annotation.External;
import score.Address;
import score.annotation.Optional;

import java.math.BigInteger;
import java.util.Map;
Expand All @@ -39,6 +41,8 @@ public interface Savings extends Name, Version, AddressManager, FloorLimitedInte
@External(readonly = true)
BigInteger getTotalPayout(Address token);

@XCall
void xClaimRewards(String from);
@External
void claimRewards();

Expand Down

0 comments on commit 53056e1

Please sign in to comment.