Skip to content

Commit 9a4b142

Browse files
authored
Merge pull request #14 from icon-project/13-icon-proof-of-liquidity-bucket
feat: Add proof of liquidity bucket contract
2 parents adc1c0c + 683868e commit 9a4b142

File tree

7 files changed

+613
-0
lines changed

7 files changed

+613
-0
lines changed

proof-of-liquidity/README.md

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Introduction
2+
The savings rate contract stakes ICX and relays it to the balanced handler for the savings rate given to bnUSD stakers.
3+
## Overview
4+
5+
### Requirements
6+
* The contract should be owned by governance.
7+
* The contract should swap incoming inflation to sICX and distribute to balanced target contract.
8+
* The contract should be able to manually stake in case of failure during fallback.
9+
10+
# Design
11+
12+
## Storage and Structs
13+
```java
14+
15+
LiquidityDistribution {
16+
BigInteger share;
17+
// Source name in balanced rewards contract
18+
String source;
19+
}
20+
21+
// All below dbs should be configurable by governance only.
22+
ArrayDB<LiquidityDistribution> distribution;
23+
VarDB<Address> staking;
24+
VarDB<Address> sICX;
25+
VarDB<Address> balancedRewards;
26+
27+
28+
BigInteger TOTAL_SHARE = EXA;
29+
30+
```
31+
32+
33+
## Methods
34+
35+
```java
36+
/**
37+
* Tries to stake the whole ICX balance to the balanced receiver
38+
*
39+
*/
40+
@Payable
41+
public void fallback() {
42+
try {
43+
distribute();
44+
} catch (Exception e) {
45+
}
46+
}
47+
```
48+
49+
```java
50+
/**
51+
*
52+
* Returns the current distribution config
53+
*/
54+
@External(readonly = true)
55+
public LiquidityDistribution[] getDistributions() {
56+
return distribution
57+
}
58+
59+
```
60+
61+
```java
62+
/**
63+
* Allocated the rewards to the balanced rewards contract for the configured distributions
64+
*
65+
* @param amount The amount to stake and send
66+
*/
67+
@External
68+
public void distribute() {
69+
BigInteger balance = Context.getBalance(Context.getAddress());
70+
BigInteger sICXAmount = staking.stakeICX(amount, balancedReceiver)
71+
JsonArray data = new JsonArray();
72+
for (dist: distribution) {
73+
BigInteger share = dist.share.multiply(sICXAmount).divide(TOTAL_SHARE);
74+
data.add(
75+
'{
76+
"source": dist.source
77+
"amount": share
78+
}'
79+
);
80+
}
81+
82+
sICX.transfer(balancedRewards, sICXAmount, jsonData)
83+
}
84+
85+
86+
```java
87+
/**
88+
*
89+
* Configures the rewards
90+
*
91+
* @param _distribution The source names and their share, which adds up to 10**18
92+
*/
93+
@External
94+
public void configureDistributions(LiquidityDistribution[] _distribution) {
95+
onlyOwner();
96+
require(sum(_distribution.share) == TOTAL_SHARE)
97+
}
98+
99+
```
100+

proof-of-liquidity/build.gradle

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
version = '0.1.0'
2+
3+
dependencies {
4+
compileOnly("foundation.icon:javaee-api:$javaeeVersion")
5+
implementation("foundation.icon:javaee-scorex:$scorexVersion")
6+
implementation project(':score-lib')
7+
implementation "com.github.sink772:minimal-json:$jsonVersion";
8+
9+
testImplementation project(':test-lib')
10+
testImplementation("foundation.icon:javaee-unittest:$javaeeUnittestVersion")
11+
testImplementation("org.junit.jupiter:junit-jupiter-api:$jupiterApiVersion")
12+
testImplementation "com.fasterxml.jackson.core:jackson-databind:2.13.3"
13+
testImplementation("org.mockito:mockito-core:$mockitoCoreVersion")
14+
}
15+
16+
test {
17+
useJUnitPlatform()
18+
finalizedBy jacocoTestReport
19+
}
20+
21+
jacocoTestReport {
22+
dependsOn test
23+
reports {
24+
xml.required = true
25+
csv.required = false
26+
html.outputLocation = layout.buildDirectory.dir('jacocoHtml')
27+
}
28+
}
29+
30+
optimizedJar {
31+
dependsOn project(':score-lib').jar
32+
mainClassName = 'icon.inflation.score.pol.ProofOfLiquidity'
33+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
34+
from {
35+
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
36+
}
37+
}
38+
39+
deployJar {
40+
endpoints {
41+
sejong {
42+
uri = 'https://sejong.net.solidwallet.io/api/v3'
43+
nid = 0x53
44+
}
45+
berlin {
46+
uri = 'https://berlin.net.solidwallet.io/api/v3'
47+
nid = 0x7
48+
}
49+
lisbon {
50+
uri = 'https://lisbon.net.solidwallet.io/api/v3'
51+
nid = 0x2
52+
}
53+
local {
54+
uri = 'http://localhost:9082/api/v3'
55+
nid = 0x3
56+
}
57+
mainnet {
58+
uri = 'https://ctz.solidwallet.io/api/v3'
59+
nid = 0x1
60+
}
61+
}
62+
keystore = rootProject.hasProperty('keystoreName') ? "$keystoreName" : ''
63+
password = rootProject.hasProperty('keystorePass') ? "$keystorePass" : ''
64+
}
65+
66+
task integrationTest(type: Test) {
67+
useJUnitPlatform()
68+
69+
rootProject.allprojects {
70+
if (it.getTasks().findByName('optimizedJar')) {
71+
dependsOn(it.getTasks().getByName('optimizedJar'))
72+
}
73+
}
74+
75+
options {
76+
testLogging.showStandardStreams = true
77+
description = 'Runs integration tests.'
78+
group = 'verification'
79+
80+
testClassesDirs = sourceSets.intTest.output.classesDirs
81+
classpath = sourceSets.intTest.runtimeClasspath
82+
83+
systemProperty "java", optimizedJar.outputJarName
84+
}
85+
86+
}
87+
88+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package icon.inflation.score.pol;
2+
3+
4+
public class Errors {
5+
public static final String NEGATIVE_PERCENTAGE = "The share cannot be negative";
6+
public static final String INVALID_SUM = "The total shares of all distributions must add up to 100%";
7+
public static final String NOT_CONFIGURED = "Distributions has not yet been configured";
8+
public static final String EMPTY_BALANCE = "Current ICX Balance is empty";
9+
public static final String NO_REENTRY = "No reentry allowed";
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package icon.inflation.score.pol;
2+
3+
import static icon.inflation.score.util.Checks.onlyOwner;
4+
import static icon.inflation.score.util.Constants.EXA;
5+
6+
import java.math.BigInteger;
7+
8+
import com.eclipsesource.json.JsonArray;
9+
import com.eclipsesource.json.JsonObject;
10+
import com.eclipsesource.json.JsonValue;
11+
12+
import icon.inflation.score.structs.LiquidityDistribution;
13+
import icon.inflation.score.util.DBUtils;
14+
import score.Address;
15+
import score.ArrayDB;
16+
import score.Context;
17+
import score.VarDB;
18+
import score.annotation.External;
19+
import score.annotation.Payable;
20+
21+
public class ProofOfLiquidity {
22+
23+
public static final ArrayDB<LiquidityDistribution> distribution = Context.newArrayDB("DISTRIBUTIONS", LiquidityDistribution.class);
24+
25+
public static final VarDB<Address> staking = Context.newVarDB("STAKING_ADDRESS", Address.class);
26+
public static final VarDB<Address> sICX = Context.newVarDB("STAKED_ICX", Address.class);
27+
public static final VarDB<Address> balancedRewards = Context.newVarDB("BALANCED_REWARDS", Address.class);
28+
29+
private static BigInteger TOTAL_SHARE = EXA;
30+
private static String NAME = "ICON Proof of liquidity Manager";
31+
32+
private static boolean distributing = false;
33+
34+
public ProofOfLiquidity(Address staking, Address sICX, Address balancedRewards) {
35+
ProofOfLiquidity.staking.set(staking);
36+
ProofOfLiquidity.sICX.set(sICX);
37+
ProofOfLiquidity.balancedRewards.set(balancedRewards);
38+
}
39+
40+
@External(readonly = true)
41+
public String name() {
42+
return NAME;
43+
}
44+
45+
46+
@External
47+
public void setStaking(Address _staking) {
48+
onlyOwner();
49+
staking.set(_staking);
50+
}
51+
52+
@External(readonly = true)
53+
public Address getStaking() {
54+
return staking.get();
55+
}
56+
57+
58+
@External
59+
public void setBalancedRewards(Address _balancedRewards) {
60+
onlyOwner();
61+
balancedRewards.set(_balancedRewards);
62+
}
63+
64+
@External(readonly = true)
65+
public Address getBalancedRewards() {
66+
return balancedRewards.get();
67+
}
68+
69+
70+
@External
71+
public void setSICX(Address _sICX) {
72+
onlyOwner();
73+
sICX.set(_sICX);
74+
}
75+
76+
@External(readonly = true)
77+
public Address getSICX() {
78+
return sICX.get();
79+
}
80+
81+
@External(readonly = true)
82+
public LiquidityDistribution[] getDistributions() {
83+
int size = distribution.size();
84+
LiquidityDistribution[] _distribution = new LiquidityDistribution[size];
85+
for (int i = 0; i < size; i++) {
86+
_distribution[i] = distribution.get(i);
87+
}
88+
89+
return _distribution;
90+
}
91+
92+
@External
93+
public void configureDistributions(LiquidityDistribution[] _distribution) {
94+
onlyOwner();
95+
BigInteger sum = BigInteger.ZERO;
96+
DBUtils.clear(distribution);
97+
98+
for (LiquidityDistribution dist : _distribution) {
99+
Context.require(dist.share.compareTo(BigInteger.ZERO) > 0, Errors.NEGATIVE_PERCENTAGE);
100+
sum = sum.add(dist.share);
101+
distribution.add(dist);
102+
}
103+
104+
Context.require(sum.equals(TOTAL_SHARE), Errors.INVALID_SUM);
105+
}
106+
107+
@External
108+
public void distribute() {
109+
Context.require(!distributing, Errors.NO_REENTRY);
110+
111+
BigInteger balance = Context.getBalance(Context.getAddress());
112+
Context.require(balance.compareTo(BigInteger.ZERO) > 0, Errors.EMPTY_BALANCE);
113+
114+
int size = distribution.size();
115+
Context.require(size > 0, Errors.NOT_CONFIGURED);
116+
117+
distributing = true;
118+
119+
Context.call(balance, staking.get(), "stakeICX", null, null);
120+
BigInteger amount = Context.call(BigInteger.class, sICX.get(), "balanceOf", Context.getAddress());
121+
BigInteger sum = BigInteger.ZERO;
122+
123+
JsonArray data = new JsonArray();
124+
for (int i = 0; i < size; i++) {
125+
LiquidityDistribution dist = distribution.get(i);
126+
// Dev note: This way of calculating will create remaining dust. But since we
127+
// are dealing with large amounts this should not matter and a good trade of for
128+
// simplicity
129+
BigInteger share = dist.share.multiply(amount).divide(TOTAL_SHARE);
130+
sum = sum.add(share);
131+
JsonValue jsonData = new JsonObject()
132+
.add("source", dist.source)
133+
.add("amount", share.toString());
134+
data.add(jsonData);
135+
}
136+
137+
Context.call(sICX.get(), "transfer", balancedRewards.get(), sum, data.toString().getBytes());
138+
distributing = false;
139+
}
140+
141+
142+
143+
@External
144+
public void tokenFallback(Address _from, BigInteger _value, byte[] _data) {
145+
}
146+
147+
@Payable
148+
public void fallback() {
149+
try {
150+
distribute();
151+
} catch (Exception e) {
152+
}
153+
}
154+
155+
}

0 commit comments

Comments
 (0)