Skip to content

Commit 5e7f1fe

Browse files
committed
feat: Add Network owned liquidity contract
1 parent 25b3887 commit 5e7f1fe

File tree

20 files changed

+1823
-1
lines changed

20 files changed

+1823
-1
lines changed

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ build
9191
.vscode
9292
.keystores
9393
.deployment
94-
gradle.properties
9594

9695
#eclipse
9796
.project

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,6 @@ public void distribute() {
7070

7171
## Insurance
7272
[Insurance](insurance/README.md)
73+
74+
## Network Owned liquidity
75+
[Network Owned liquidity](network-owned-liquidity/README.md)

gradle.properties

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
javaeeVersion=0.9.3
2+
scorexVersion=0.5.3
3+
javaeeUnittestVersion=0.12.1
4+
scoreClientVersion=0.10.1
5+
jacksonVersion=2.14.2
6+
iconsdkVersion=2.3.0
7+
jupiterApiVersion=5.9.2
8+
jupiterParamsVersion=5.9.2
9+
jupiterEngineVersion=5.9.2
10+
javaeePluginVersion=0.8.2
11+
mockitoCoreVersion=4.5.1
12+
jsonVersion=0.9.7
13+
14+
keystoreName=test-lib/conf/godWallet.json
15+
keystorePass=gochain
16+

network-owned-liquidity/README.md

+301
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
# Introduction
2+
The Network Owned Liquidity contract is the contract that will hold and buy liquidity on the Balanced DEX.
3+
In this contract validators can assign different amount of LP tokens to be bought continuously.
4+
## Overview
5+
6+
### Requirements
7+
* The contract should be owned by governance.
8+
* The contract should swap incoming inflation to bnUSD continuously.
9+
* The contract should expose a method to do smaller swaps in case of slippage issues.
10+
* The contract should allow governance to setup buy orders with limits. Ex buy $100K dollars of sICX/bnUSD liquidity each month.
11+
* The contract should allow anyone to fill liquidity orders.
12+
* The contract should be able to disburse any token via governance.
13+
* The contract should be able to withdraw liquidity via governance.
14+
15+
# Design
16+
17+
## Storage and Structs
18+
```java
19+
public DictDB<BigInteger, LiquidityOrder> orders;
20+
21+
// All below parameters should be configurable by governance only.
22+
public VarDB<BigInteger> orderPeriod; // Microseconds
23+
24+
public VarDB<Address> balancedDex;
25+
public VarDB<Address> bnUSD;
26+
public VarDB<Address> sICX;
27+
public VarDB<Address> balancedOracle;
28+
public VarDB<Address> balancedRouter;
29+
30+
public VarDB<BigInteger> maxSwapSlippage; // Points (0-10000)
31+
public VarDB<BigInteger> swapReward; // Points (0-10000)
32+
public VarDB<BigInteger> lPSlippage; // Points (0-10000)
33+
34+
LiquidityOrder {
35+
BigInteger limit
36+
BigInteger period
37+
BigInteger payoutThisPeriod
38+
}
39+
```
40+
41+
## Methods
42+
43+
```java
44+
/**
45+
* Swaps ICX amount to bnUSD
46+
*
47+
* @param amount The amount of ICX to be swapped
48+
*/
49+
@External
50+
public void swap(BigInteger amount) {
51+
icxPriceInUSD = balancedOracle.getPriceInUSD("ICX");
52+
usdAmount = amount*icxPriceInUSD / EXA;
53+
minReceive = (POINTS-maxSwapSlippage)*usdAmount / POINTS;
54+
balancedRouter.route(amount, [sICX, bnUSD], minReceive);
55+
}
56+
```
57+
58+
```java
59+
/**
60+
* Configures a new liquidity order
61+
*
62+
* @param pid The poolId on the balanced dex
63+
* @param limit The max USD limit to purchase for each Order Period
64+
*/
65+
@External
66+
public void configureOrder(BigInteger pid, BigInteger limit) {
67+
OnlyICONGovernance()
68+
timestamp = Context.getTimestamp()
69+
period = orderPeriod.get()
70+
71+
order = LiquidityOrder {
72+
limit = limit,
73+
period = (timestamp/period)*period,
74+
payoutThisPeriod = 0
75+
}
76+
77+
orders.set(pid, order);
78+
}
79+
```
80+
81+
```java
82+
/**
83+
* Removes a liquidity order
84+
*
85+
* @param pid The poolId on the balanced dex
86+
*/
87+
@External
88+
public void removeOrder(BigInteger pid) {
89+
OnlyICONGovernance()
90+
orders.set(pid, null);
91+
}
92+
```
93+
```java
94+
/**
95+
* Sends a token to given recipient
96+
*
97+
* @param token TThe token address
98+
* @param recipient The address to receive the token
99+
* @param amount The amount to send
100+
* @param data Optional data to use for contract interaction.
101+
*/
102+
@External
103+
public void disburse(Address token, Address recipient, BigInteger amount, @Optional byte[] data) {
104+
OnlyICONGovernance();
105+
token.transfer(recipient, amount, data);
106+
}
107+
```
108+
109+
```java
110+
/**
111+
* Sends ICX to given recipient
112+
*
113+
* @param recipient The address to receive ICX
114+
* @param amount The amount to send
115+
*/
116+
@External
117+
public void disburseICX(Address recipient, BigInteger amount) {
118+
OnlyICONGovernance();
119+
Context.transfer(recipient, amount);
120+
}
121+
```
122+
123+
<!-- ```java
124+
/**
125+
* Manually supplies liquidity through available tokens via governance
126+
*
127+
* @param baseAddress The address of the base token
128+
* @param baseAmount The amount of of the base token to use
129+
* @param quoteAddress The address of the quote token
130+
* @param quoteAmount The amount of of the quote token to use
131+
* @param slippage The biggest allowed difference between decided price and the actual price when supplying.
132+
*/
133+
public void supplyLiquidity(Address baseAddress, BigInteger baseAmount, Address quoteAddress,
134+
BigInteger quoteAmount, BigInteger slippage) {
135+
136+
OnlyICONGovernance();
137+
pid = balancedDex.getPoolId(baseAddress, quoteAddress);
138+
139+
supplyPrice = quoteAmount.multiply(EXA).divide(baseAmount);
140+
dexPrice = balancedDex.getPrice(pid);
141+
allowedDiff = supplyPrice. * slippage / POINTS;
142+
assert supplyPrice - allowedDiff < dexprice;
143+
assert supplyPrice + allowedDiff > dexprice;
144+
baseAddress.transfer(dex, baseAmount, <tokenDepositData>)
145+
quoteAddress.transfer(dex, quoteAmount, <tokenDepositData>)
146+
balancedDex.add(baseAddress, quoteAddress, baseAmount, quoteAmount, true)
147+
}
148+
``` -->
149+
150+
```java
151+
/**
152+
* Removes liquidity
153+
*
154+
* @param pid The poolId on the balanced dex
155+
* @param amount The amount of LP tokens to withdraw
156+
*/
157+
@External
158+
public void withdrawLiquidity(BigInteger pid, BigInteger amount) {
159+
OnlyICONGovernance();
160+
balancedDex.remove(pid, amount);
161+
}
162+
```
163+
164+
165+
```java
166+
/**
167+
* Calculates the bnUSD rewards gained by swapping 'amount' of LP tokens with a specific pid
168+
*
169+
* @param pid The poolId on the balanced dex
170+
* @param amount The amount of LP tokens to swap
171+
*/
172+
@External(readonly = true)
173+
public BigInteger calculateBnUSDReward(BigInteger pid, BigInteger amount) {
174+
stats = balancedDex.getPoolStats(pid);
175+
176+
baseAmount = stats["base"]*amount / stats["total_supply"];
177+
quoteAmount = stats["quote"]*amount / stats["total_supply"];
178+
179+
baseSymbol = stats["baseToken"].symbol()
180+
baseUSDPrice = balancedOracle.getPriceInUSD(baseSymbol);
181+
182+
183+
quoteSymbol = stats["quoteToken"].symbol()
184+
quoteUSDPrice = balancedOracle.getPriceInUSD(quoteSymbol);
185+
186+
// USD value of supply in 18 decimals
187+
baseAmountInUSD = baseAmount*baseUSDPrice / stats["baseDecimals"];
188+
quoteAmountInUSD = quoteAmount*quoteUSDPrice / stats["quoteDecimals"];
189+
190+
// If the pool is perfectly priced, we should have the two USD amount be exactly equal
191+
absDiff = abs(baseAmountInUSD-quoteAmountInUSD) * POINTS;
192+
avg = (baseAmountInUSD+quoteAmountInUSD) / 2
193+
slippage = absDiff / avg
194+
assert slippage < lPSlippage
195+
196+
reward = (baseAmountInUSD+quoteAmountInUSD)*(POINTS+swapReward) / POINTS
197+
198+
return reward
199+
}
200+
```
201+
202+
203+
```java
204+
/**
205+
* Receives LP tokens from the balanced dex, If the method is swap then calculate payout to user.
206+
*
207+
*/
208+
@External
209+
public void onIRC31Received(Address _operator, Address _from, BigInteger _id, BigInteger _value, byte[] _data) {
210+
OnlyBalancedDex();
211+
data = json(_data);
212+
assert unpackedData != empty
213+
switch data["method"]:
214+
case: "deposit"
215+
return
216+
case: "swap":
217+
swapLPTokens(_from, _id, _value);
218+
return
219+
}
220+
```
221+
222+
223+
```java
224+
/**
225+
* Swaps ICX amount to bnUSD
226+
*
227+
* @param amount The amount of ICX to be swapped
228+
*/
229+
@External
230+
public void fallback() {
231+
try {
232+
swap(Context.getValue());
233+
} catch error {
234+
SwapFailed(error)
235+
}
236+
}
237+
```
238+
239+
```java
240+
/**
241+
* Receives tokens
242+
*
243+
*/
244+
@External
245+
public void tokenFallback(Address _from, BigInteger _value, byte[] _data) {
246+
}
247+
```
248+
249+
250+
251+
```java
252+
/**
253+
* Swaps LP tokens for bnUSD
254+
*
255+
* @param from The user who supplied the LP tokens
256+
* @param id The poolId on the balanced dex
257+
* @param value The amount of lp tokens to be swapped
258+
*/
259+
private void swapLPTokens(Address from, BigInteger id, BigInteger value) {
260+
order = orders.get(id);
261+
reward = calculateBnUSDReward(id, value);
262+
order = validateOrder(order, id);
263+
orders.set(id, order)
264+
bnUSD.transfer(from, reward)
265+
LiquidityPurchased(id, value, reward)
266+
}
267+
```
268+
269+
```java
270+
/**
271+
* Validates a order so that purchases do not exceed the defined limit.
272+
*
273+
*/
274+
private LiquidityOrder validateOrder(LiquidityOrder order, BigInteger payoutAmount) {
275+
orderPeriod = orderPeriod.get();
276+
currentTime = Context.getTimestamp();
277+
if (order.period + orderPeriod >= currentTime) {
278+
// with integer division this return the current period
279+
order.period = (currentTime / orderPeriod) * orderPeriod;
280+
order.payoutThisPeriod = 0;
281+
}
282+
283+
order.payoutThisPeriod += payoutAmount;
284+
assert order.payoutThisPeriod <= order.limit
285+
286+
return order;
287+
288+
}
289+
```
290+
291+
## Eventlogs
292+
293+
```java
294+
@EventLog(indexed = 1)
295+
public void LiquidityPurchased(BigInteger pid, BigInteger lpTokenAmount, BigInteger bnUSDPayout)
296+
```
297+
298+
```java
299+
@EventLog(indexed = 1)
300+
public void SwapFailed(String reason)
301+
```

0 commit comments

Comments
 (0)