Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/liquidity incentive #841

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5,000 changes: 0 additions & 5,000 deletions e2e/common/generated_users.txt

This file was deleted.

22 changes: 4 additions & 18 deletions e2e/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { SignerOptions, SubmittableExtrinsic } from "@polkadot/api/types";
import { KeyringPair } from "@polkadot/keyring/types";
import { AnyJson } from "@polkadot/types/types";
import { BigNumber } from "ethers";
import fs, { writeFileSync } from "fs";
import { writeFileSync } from "fs";
import { CliPrettify } from "markdown-table-prettify";
import path, { join } from "path";
import { join } from "path";
import web3 from "web3";

export * from "./node";
Expand Down Expand Up @@ -734,21 +734,7 @@ export const executeForPreviousEvent = async (
}
};

export const loadTestUsers = (userAmount?: number): KeyringPair[] => {
const content = fs.readFileSync(path.resolve(__dirname, "./generated_users.txt"), "utf-8");
const mnemonics = content
.replace(/\n{2,}/g, "\n")
.toString()
.split("\n");
export const generateTestUsers = (userAmount: number): KeyringPair[] => {
const keyring = new Keyring({ type: "ethereum" });
const keypairs: KeyringPair[] = [];

for (let i = 0; i < mnemonics.length; i++) {
const mnemonic = mnemonics[i];
if (mnemonic !== "" && (userAmount === undefined || i < userAmount)) {
keypairs.push(keyring.addFromMnemonic(mnemonic, {}));
}
}
console.log(`loaded ${keypairs.length} users`);
return keypairs;
return Array.from({ length: userAmount }, () => keyring.addFromUri(`${Math.random().toString(36).substring(2)}`));
};
180 changes: 180 additions & 0 deletions e2e/test/LiquidityPools.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { ApiPromise, Keyring, WsProvider } from "@polkadot/api";
import { KeyringPair } from "@polkadot/keyring/types";
import { hexToU8a } from "@polkadot/util";
import { expect } from "chai";

import {
ALITH_PRIVATE_KEY,
GAS_TOKEN_ID,
NodeProcess,
finalizeTx,
generateTestUsers,
getNextAssetId,
rpcs,
sleep,
startNode,
typedefs,
} from "../common";

describe("Reward", () => {
let TOKEN_ID: number;
let node: NodeProcess;
let api: ApiPromise;
let alith: KeyringPair;
let testUsers: KeyringPair[];

before(async () => {
node = await startNode();

const wsProvider = new WsProvider(`ws://localhost:${node.wsPort}`);
// const wsProvider = new WsProvider(`wss://archive.morel.micklelab.xyz/ws`);
api = await ApiPromise.create({
provider: wsProvider,
types: typedefs,
rpc: rpcs,
});

const keyring = new Keyring({ type: "ethereum" });
alith = keyring.addFromSeed(hexToU8a(ALITH_PRIVATE_KEY));

TOKEN_ID = await getNextAssetId(api);

// load test users
testUsers = generateTestUsers(10);

const txs = [
api.tx.assetsExt.createAsset("testliquiditypools", "TESTLIQUIDITYPOOLS", 6, 1, alith.address), // create asset
];

await finalizeTx(alith, api.tx.utility.batch(txs));
});

after(async () => node.stop());

it("load test for rollover", async () => {
const batchSize = Number(api.consts.liquidityPools.rolloverBatchSize.toHuman() as number) + 1;

const amount = 1000;
const joinPoolAmount = 100;

// fund test users
let txs = [];
for (let i = 0; i < testUsers.length; i++) {
const user = testUsers[i];
// transfer ROOT && GAS assets to test users
txs.push(api.tx.assets.mint(TOKEN_ID, user.address, amount));
txs.push(api.tx.assets.mint(GAS_TOKEN_ID, user.address, 100_000_000));
txs.push(api.tx.balances.transfer(user.address, amount));

if (txs.length >= batchSize || i === testUsers.length - 1) {
console.log(`funding ${txs.length} test users`);
await finalizeTx(alith, api.tx.utility.batch(txs));
txs = [];
}
}
console.log(`${testUsers.length} test users funded`);

const liquidityPoolsVaultAccount = "0x6d6f646c6c7164706f6f6c730000000000000000";
// fund vault account
await finalizeTx(
alith,
api.tx.utility.batch([api.tx.balances.transfer(liquidityPoolsVaultAccount, 1_000_000_000 * 2)]),
);
console.log(`vault account ${liquidityPoolsVaultAccount} funded`);

// pool 1
const pool1 = await api.query.liquidityPools.nextPoolId();
const interestRate = 1_000_000;
const maxTokens = 1000_000;
const blockDuration = 4000;
const intervalBlock = 5;
let startBlock = intervalBlock + Number((await api.rpc.chain.getHeader()).number);
const rewardPeriod = Math.ceil(testUsers.length / batchSize) + intervalBlock;
let endBlock = startBlock + rewardPeriod;

// create pool
await finalizeTx(
alith,
api.tx.sudo.sudo(api.tx.liquidityPools.createPool(TOKEN_ID, interestRate, maxTokens, startBlock, endBlock)),
);
console.log(`pool ${pool1} created`);

// join pool
for (let i = 0; i < testUsers.length; i++) {
const user = testUsers[i];
api.tx.liquidityPools.enterPool(pool1, joinPoolAmount).signAndSend(user);

if (i % batchSize === 0 || i === testUsers.length - 1) {
await sleep(blockDuration);
}
}
console.log(`${testUsers.length} test users joined pool ${pool1}`);

// verify pool info's lock amount
const pool1Info = ((await api.query.liquidityPools.pools(pool1)) as any).unwrap();
expect(pool1Info.lockedAmount.toNumber()).to.equal(testUsers.length * joinPoolAmount);
console.log(`pool ${pool1} locked amount verified`);

// get next pool id
const pool2 = await api.query.liquidityPools.nextPoolId();

startBlock = endBlock + 1;
endBlock = startBlock + rewardPeriod;
// create pool
await finalizeTx(
alith,
api.tx.sudo.sudo(api.tx.liquidityPools.createPool(TOKEN_ID, interestRate, maxTokens, startBlock, endBlock)),
);
console.log(`pool ${pool2} created`);

// set successor pool
await finalizeTx(alith, api.tx.sudo.sudo(api.tx.liquidityPools.setPoolSuccession(pool1, pool2)));
console.log(`pool ${pool2} set as successor of pool ${pool1}`);

await sleep(intervalBlock * 2 * blockDuration);
console.log(`waited for start block ${startBlock}`);

// wait for reward period
await sleep(rewardPeriod * blockDuration);
console.log(`waited for reward period ${rewardPeriod} blocks`);

// verify successor pool's lock amount
const pool2Info = ((await api.query.liquidityPools.pools(pool2)) as any).unwrap();
expect(pool2Info.lockedAmount.toNumber()).to.equal(testUsers.length * joinPoolAmount);
console.log(`pool ${pool2} locked amount verified`);

// record user balances before claiming reward
const userBalancesBefore: { [key: string]: number } = {};
for (const user of testUsers) {
const userBalance: any = (await api.query.system.account(user.address)).toJSON();
userBalancesBefore[user.address] = userBalance?.data?.free;
}

// claim reward for pool 1 & 2
for (let i = 0; i < testUsers.length; i++) {
const user = testUsers[i];
api.tx.liquidityPools.claimReward(pool1).signAndSend(user);

if (i % batchSize === 0 || i === testUsers.length - 1) {
await sleep(blockDuration);
}
}
console.log(`${testUsers.length} test users claimed reward for pool ${pool1}`);
for (let i = 0; i < testUsers.length; i++) {
const user = testUsers[i];
api.tx.liquidityPools.claimReward(pool2).signAndSend(user);

if (i % batchSize === 0 || i === testUsers.length - 1) {
await sleep(blockDuration);
}
}
console.log(`${testUsers.length} test users claimed reward for pool ${pool2}`);

for (const user of testUsers) {
// verify reward
const userBalance: any = (await api.query.system.account(user.address)).toJSON();
expect(userBalance?.data?.free).to.equal(joinPoolAmount * 2 + userBalancesBefore[user.address]);
}
console.log(`test users' reward verified`);
});
});
4 changes: 2 additions & 2 deletions e2e/test/Vortex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
NATIVE_TOKEN_ID,
NodeProcess,
finalizeTx,
generateTestUsers,
getNextAssetId,
loadTestUsers,
sleep,
startNode,
typedefs,
Expand Down Expand Up @@ -114,7 +114,7 @@ describe("Vortex Distribution", () => {
);

// load test users
const users = loadTestUsers(20);
const users = generateTestUsers(20);

// transfer native token to users to create accounts
txs = [];
Expand Down
48 changes: 48 additions & 0 deletions pallet/liquidity-pools/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
[package]
name = "pallet-liquidity-pools"
version = "1.0.0"
authors = ["The Root Network Team"]
edition = "2021"
license = "GPL-3.0"
repository = "https://github.com/futureversecom/seed"
description = "Root Network Liquidity Pools pallet"

[dependencies]
scale-info = { version = "2.3.0", default-features = false, features = ["derive"] }

codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }

sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.30" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.30" }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.30" }
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.30" }
sp-arithmetic = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.30" }
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.30" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.30" }
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30", optional = true, default-features = false }

pallet-assets-ext = { path = "../assets-ext", default-features = false }
seed-primitives = { path = "../../primitives", default-features = false }
seed-pallet-common = { path = "../common", default-features = false }

[dev-dependencies]
hex={ version = "0.4.3" }
hex-literal = { version = "0.3.4" }
sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" }
pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.30" }

[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"seed-primitives/std",
"seed-pallet-common/std",
"sp-core/std",
"sp-std/std",
"frame-support/std",
"frame-system/std",
]
runtime-benchmarks = ["frame-benchmarking"]
try-runtime = ["frame-support/try-runtime"]
Loading
Loading