Skip to content

Commit 8623785

Browse files
authored
Merge pull request #431 from keep-network/apy-calc
Connect new Keep website staking information
2 parents 408e34a + f243f62 commit 8623785

16 files changed

Lines changed: 19506 additions & 9640 deletions

jest.config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* For a detailed explanation regarding each configuration property, visit:
3+
* https://jestjs.io/docs/en/configuration.html
4+
*/
5+
6+
module.exports = {
7+
testEnvironment: "node",
8+
testRegex: "(src/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$",
9+
}

package-lock.json

Lines changed: 16046 additions & 9636 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
"dependencies": {
77
"aos": "^2.3.4",
88
"axios": "^0.21.1",
9+
"bignumber.js": "^9.0.1",
910
"bootstrap-css-only": "^4.4.1",
1011
"bulma": "^0.8.0",
12+
"ethers": "^5.1.4",
1113
"gatsby": "^2.13.31",
1214
"gatsby-image": "^2.0.23",
1315
"gatsby-plugin-favicon": "^3.1.6",
@@ -23,6 +25,7 @@
2325
"gatsby-source-filesystem": "^2.0.26",
2426
"gatsby-transformer-remark": "^2.6.9",
2527
"gatsby-transformer-sharp": "^2.1.9",
28+
"jest": "^26.6.3",
2629
"lodash": "^4.17.19",
2730
"lodash-webpack-plugin": "^0.11.4",
2831
"netlify-cms-app": "^2.9.6",
@@ -55,7 +58,7 @@
5558
"build:prefixed": "npm run clean && gatsby build --prefix-paths",
5659
"develop": "npm run clean && gatsby develop",
5760
"format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"{gatsby-*.js,src/**/*.js}\"",
58-
"test": "echo \"Error: no test specified\" && exit 1",
61+
"test": "jest",
5962
"lint": "eslint src/ gatsby-*.js",
6063
"lint:fix": "eslint --fix src/ gatsby-*.js"
6164
},
@@ -65,7 +68,10 @@
6568
"not op_mini all"
6669
],
6770
"devDependencies": {
71+
"@babel/core": "^7.14.3",
72+
"@babel/preset-env": "^7.14.2",
6873
"babel-eslint": "^10.1.0",
74+
"babel-jest": "^26.6.3",
6975
"eslint": "^6.8.0",
7076
"eslint-config-keep": "git+https://github.com/keep-network/eslint-config-keep.git#0.3.0",
7177
"eslint-plugin-react": "^7.19.0",
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useEffect, useState } from "react"
2+
3+
import LiquidityRewardsFactory, {
4+
SUPPORTED_LIQUIDITY_POOLS,
5+
} from "../lib/liquidity-rewards"
6+
7+
const liquidityRewardsKEEPETH = LiquidityRewardsFactory.create(
8+
SUPPORTED_LIQUIDITY_POOLS.KEEP_ETH
9+
)
10+
const liquidityRewardsTBTCSaddle = LiquidityRewardsFactory.create(
11+
SUPPORTED_LIQUIDITY_POOLS.TBTC_SADDLE
12+
)
13+
14+
const liquidityRewardsTBTCETH = LiquidityRewardsFactory.create(
15+
SUPPORTED_LIQUIDITY_POOLS.TBTC_ETH
16+
)
17+
const liquidityRewardsKEEP = LiquidityRewardsFactory.create(
18+
SUPPORTED_LIQUIDITY_POOLS.KEEP
19+
)
20+
21+
const useLiquidityRewardsAPY = () => {
22+
const [isFetching, setIsFetching] = useState(true)
23+
const [liquidityRewardsAPYs, setLiquidityRewardsAPYs] = useState([])
24+
const [error, setError] = useState(null)
25+
26+
useEffect(() => {
27+
let shouldSetState = true
28+
29+
setIsFetching(true)
30+
Promise.all([
31+
liquidityRewardsKEEPETH.calculateAPY(),
32+
liquidityRewardsTBTCSaddle.calculateAPY(),
33+
liquidityRewardsTBTCETH.calculateAPY(),
34+
liquidityRewardsKEEP.calculateAPY(),
35+
])
36+
.then(([apyKEEPETH, apyTBTCSaddle, apyTBTCETH, apyKEEP]) => {
37+
if (shouldSetState) {
38+
setLiquidityRewardsAPYs(
39+
[
40+
{ value: apyKEEPETH, pool: "KEEP/ETH" },
41+
{ value: apyTBTCSaddle, pool: "TBTC/SADDLE" },
42+
{ value: apyTBTCETH, pool: "TBTC/ETH" },
43+
{ value: apyKEEP, pool: "KEEP" },
44+
].sort((a, b) => b.value - a.value)
45+
)
46+
setIsFetching(false)
47+
}
48+
})
49+
.catch((error) => {
50+
if (shouldSetState) {
51+
setIsFetching(false)
52+
setError(error)
53+
}
54+
console.log(
55+
"Unexpected error while fetching liquidity rewards APY:",
56+
error
57+
)
58+
})
59+
60+
return () => {
61+
shouldSetState = false
62+
}
63+
}, [])
64+
65+
return [liquidityRewardsAPYs, isFetching, error]
66+
}
67+
68+
export default useLiquidityRewardsAPY
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import axios from "axios"
2+
import BigNumber from "bignumber.js"
3+
4+
import {
5+
BaseExchangeService,
6+
UniswapExchangeService,
7+
} from "../exchange-service"
8+
9+
jest.mock("axios")
10+
11+
describe("Test exchange service", () => {
12+
describe("Test BaseExchangeService", () => {
13+
const exchange = new BaseExchangeService()
14+
exchange._getUniswapPairData = jest.fn()
15+
exchange._getKeepTokenPriceInUSD = jest.fn()
16+
exchange._getBTCPriceInUSD = jest.fn()
17+
const mockedPairId = 1
18+
19+
test("should call function that implements fetching uniswap pair data", () => {
20+
exchange.getUniswapPairData(mockedPairId)
21+
22+
expect(exchange._getUniswapPairData).toHaveBeenCalledWith(mockedPairId)
23+
})
24+
25+
test("should call function that implements fetching KEEP token price", () => {
26+
exchange.getKeepTokenPriceInUSD()
27+
28+
expect(exchange._getKeepTokenPriceInUSD).toHaveBeenCalled()
29+
})
30+
31+
test("should call function that implements fetching BTC price", () => {
32+
exchange.getBTCPriceInUSD()
33+
34+
expect(exchange._getBTCPriceInUSD).toHaveBeenCalled()
35+
})
36+
})
37+
38+
describe("Test UniswapExchangeService", () => {
39+
const exchange = new UniswapExchangeService()
40+
const mockedPairId = 1
41+
42+
const mockedResponse = {
43+
data: {
44+
data: {
45+
pair: {
46+
reserveUSD: 30000,
47+
reserveETH: 1000,
48+
token0: {
49+
derivedETH: 0.2,
50+
},
51+
},
52+
},
53+
},
54+
}
55+
56+
test("should fetch uniswap pair data correctly", async () => {
57+
axios.post.mockResolvedValue(mockedResponse)
58+
59+
const result = await exchange.getUniswapPairData(mockedPairId)
60+
61+
const mockCalls = axios.post.mock.calls
62+
expect(axios.post).toHaveBeenCalled()
63+
expect(mockCalls[0][0]).toEqual(exchange.UNISWAP_API_URL)
64+
expect(
65+
mockCalls[0][1].query.toString().includes(mockedPairId)
66+
).toBeTruthy()
67+
68+
expect(result).toStrictEqual(mockedResponse.data.data.pair)
69+
})
70+
71+
test("should fetch keep token price correctly", async () => {
72+
const spy = jest.spyOn(exchange, "_getTokenPriceInUSD")
73+
const getUniswapPairDataSpy = jest.spyOn(exchange, "_getUniswapPairData")
74+
axios.post.mockResolvedValue(mockedResponse)
75+
const pairData = mockedResponse.data.data.pair
76+
const expectedPrice = new BigNumber(pairData.reserveUSD)
77+
.div(pairData.reserveETH)
78+
.multipliedBy(pairData.token0.derivedETH)
79+
80+
const result = await exchange.getKeepTokenPriceInUSD()
81+
82+
expect(spy).toHaveBeenCalledWith(
83+
"0xe6f19dab7d43317344282f803f8e8d240708174a"
84+
)
85+
expect(getUniswapPairDataSpy).toHaveBeenCalledWith(
86+
"0xe6f19dab7d43317344282f803f8e8d240708174a"
87+
)
88+
expect(result).toEqual(expectedPrice)
89+
})
90+
91+
test("should fetch BTC price correctly", () => {
92+
const spy = jest
93+
.spyOn(exchange, "_getTokenPriceInUSD")
94+
.mockResolvedValue("300")
95+
96+
exchange.getBTCPriceInUSD()
97+
98+
expect(spy).toHaveBeenCalledWith(
99+
"0xe6f19dab7d43317344282f803f8e8d240708174a"
100+
)
101+
})
102+
})
103+
})
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { Contract, providers } from "ethers"
2+
import BigNumber from "bignumber.js"
3+
4+
import {
5+
LiquidityRewards,
6+
LiquidityRewardsFactory,
7+
LiquidityRewardsUniswap,
8+
} from "../liquidity-rewards"
9+
import { Token } from "../utils"
10+
import LPRewardsKEEPETH from "../abis/LPRewardsKEEPETH.json"
11+
import { UniswapExchangeService } from "../exchange-service"
12+
13+
jest.mock("ethers")
14+
15+
jest.mock("../abis/LPRewardsKEEPETH.json", () => ({
16+
abi: [],
17+
networks: {
18+
1: {
19+
address: "0x0",
20+
},
21+
},
22+
}))
23+
24+
describe("Test LiquidityRewards", () => {
25+
describe("Test base LiquidityRewards", () => {
26+
const liquidityRewards = new LiquidityRewards(
27+
{},
28+
{ getKeepTokenPriceInUSD: () => {} }
29+
)
30+
const mockedTotalSupply = Token.fromTokenUnit(100000)
31+
const mockedRewarPoolPerWeek = "30000"
32+
const mockedTotalSupplyInUSD = "50000"
33+
const mockedKeepTokenPriceInUSD = new BigNumber("0.55")
34+
liquidityRewards._totalSupply = jest
35+
.fn()
36+
.mockResolvedValue(mockedTotalSupply)
37+
liquidityRewards._rewardPoolPerWeek = jest
38+
.fn()
39+
.mockResolvedValue(mockedRewarPoolPerWeek)
40+
liquidityRewards._totalSupplyInUSD = jest
41+
.fn()
42+
.mockResolvedValue(mockedTotalSupplyInUSD)
43+
44+
const exchangeSpy = jest
45+
.spyOn(liquidityRewards.exchangeSercvice, "getKeepTokenPriceInUSD")
46+
.mockResolvedValue(mockedKeepTokenPriceInUSD)
47+
const calculateRSpy = jest.spyOn(liquidityRewards, "_calculateR")
48+
const calculateAPYSpy = jest.spyOn(liquidityRewards, "_calculateAPY")
49+
50+
test("should correctly calculate APY", async () => {
51+
const result = await liquidityRewards.calculateAPY()
52+
53+
expect(liquidityRewards._totalSupply).toHaveBeenCalled()
54+
expect(liquidityRewards._rewardPoolPerWeek).toHaveBeenCalled()
55+
expect(liquidityRewards._totalSupplyInUSD).toHaveBeenCalledWith(
56+
mockedTotalSupply.toString()
57+
)
58+
expect(exchangeSpy).toHaveBeenCalled()
59+
expect(calculateRSpy).toHaveBeenCalledWith(
60+
mockedKeepTokenPriceInUSD,
61+
mockedRewarPoolPerWeek,
62+
mockedTotalSupplyInUSD
63+
)
64+
65+
const expectedR = new BigNumber(mockedKeepTokenPriceInUSD)
66+
.multipliedBy(mockedRewarPoolPerWeek)
67+
.div(mockedTotalSupplyInUSD)
68+
69+
expect(calculateAPYSpy).toHaveBeenCalledWith(expectedR, 52)
70+
71+
const expectedAPY = expectedR
72+
.plus(1)
73+
.pow(52)
74+
.minus(1)
75+
.multipliedBy(100)
76+
.decimalPlaces(2, BigNumber.ROUND_DOWN)
77+
.toString()
78+
79+
expect(result).toEqual(expectedAPY)
80+
})
81+
})
82+
83+
describe("Test LiquidityRewards factory", () => {
84+
test("should create the liquidity rewards pool wrapper correctly", () => {
85+
const liquidityRewards = LiquidityRewardsFactory.create("KEEP_ETH")
86+
const mockContractCalls = Contract.mock.calls
87+
expect(Contract).toHaveBeenCalled()
88+
expect(mockContractCalls[0][0]).toEqual(
89+
LPRewardsKEEPETH.networks[1].address
90+
)
91+
expect(mockContractCalls[0][1]).toEqual(LPRewardsKEEPETH.abi)
92+
93+
expect(liquidityRewards).toBeInstanceOf(LiquidityRewardsUniswap)
94+
expect(liquidityRewards.exchangeSercvice).toBeInstanceOf(
95+
UniswapExchangeService
96+
)
97+
expect(liquidityRewards.contract).toBeInstanceOf(Contract)
98+
})
99+
})
100+
})

0 commit comments

Comments
 (0)