Skip to content

Commit

Permalink
Ethereum: Add tests for fetching data
Browse files Browse the repository at this point in the history
  • Loading branch information
welldan97 committed Aug 1, 2024
1 parent 53d0702 commit 2db1821
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 6 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ dist
.turbo

packages/ethereum/cache
packages/ethereum/hardhat.log
packages/ethereum/hardhat.log
packages/ethereum/hardhat.pid
9 changes: 5 additions & 4 deletions packages/ethereum/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"description": "All-in-one toolkit for building staking dApps on Ethereum network",
"scripts": {
"build": "rm -fr dist/* && tsc -p tsconfig.mjs.json --outDir dist/mjs && tsc -p tsconfig.cjs.json --outDir dist/cjs && bash ../../scripts/fix-package-json",
"hardhat:start": "hardhat node",
"hardhat:stop": "kill $(lsof -t -i:8545) > /dev/null 2>&1",
"test:integration": "mocha --timeout 20000 '**/*.spec.ts'",
"test": "npm run hardhat:start > hardhat.log & (sleep 5; npm run test:integration; status=$?; npm run hardhat:stop; exit $status)"
"hardhat:start": "hardhat node & echo $! > hardhat.pid;",
"hardhat:stop": "kill $(cat hardhat.pid) && rm hardhat.pid",
"test:integration": "mocha --timeout 30000 '**/*.spec.ts'",
"test": "npm run hardhat:start > hardhat.log & sleep 5; npm run test:integration; status=$?; npm run hardhat:stop; exit $status"

},
"main": "dist/cjs/index.js",
"module": "dist/mjs/index.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/ethereum/test/buildStakeTx.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EthereumStaker } from '@chorus-one/ethereum'
import { Hex, PublicClient, WalletClient, parseEther } from 'viem'
import { assert, assert } from 'chai'
import { assert } from 'chai'
import { prepareTests, stake } from './lib/utils'

const amountToStake = parseEther('2')
Expand Down
59 changes: 59 additions & 0 deletions packages/ethereum/test/getRewardsHistory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { EthereumStaker } from '@chorus-one/ethereum'
import { assert } from 'chai'
import { Hex } from 'viem'
import { prepareTests } from './lib/utils'

describe('EthereumStaker.getRewards', () => {
let validatorAddress: Hex
let delegatorAddress: Hex
let staker: EthereumStaker

beforeEach(async () => {
const setup = await prepareTests()
validatorAddress = setup.validatorAddress
delegatorAddress = '0x2dF83a340D5067751e8045cCe90764B19D9e7A4D'
staker = setup.staker
})

it('returns correct rewards history for given period of time for Chorus mainnet stakers', async () => {
const rewards = await staker.getRewardsHistory({
startTime: new Date('2024-02-01').getTime(),
endTime: new Date('2024-03-01').getTime(),
validatorAddress,
delegatorAddress
})

assert.deepEqual(rewards, [
{ timestamp: 1706745600000, amount: '0.000024786824502065' },
{ timestamp: 1706832000000, amount: '0.000044119038630881' },
{ timestamp: 1706918400000, amount: '0.000062876329371494' },
{ timestamp: 1707004800000, amount: '0.00008190429845996' },
{ timestamp: 1707091200000, amount: '0.0001097109140602' },
{ timestamp: 1707177600000, amount: '0.000153434503075382' },
{ timestamp: 1707264000000, amount: '0.000146997696335134' },
{ timestamp: 1707350400000, amount: '0.000146832073871066' },
{ timestamp: 1707436800000, amount: '0.000146482684232609' },
{ timestamp: 1707523200000, amount: '0.000146408328074781' },
{ timestamp: 1707609600000, amount: '0.000146334140031532' },
{ timestamp: 1707696000000, amount: '0.000146312956091355' },
{ timestamp: 1707782400000, amount: '0.00014640161118171' },
{ timestamp: 1707868800000, amount: '0.000146491769380892' },
{ timestamp: 1707955200000, amount: '0.000146580473685824' },
{ timestamp: 1708041600000, amount: '0.000146669140376886' },
{ timestamp: 1708128000000, amount: '0.000146755869058696' },
{ timestamp: 1708214400000, amount: '0.000146842138530308' },
{ timestamp: 1708300800000, amount: '0.000146883811424052' },
{ timestamp: 1708387200000, amount: '0.000146968064066342' },
{ timestamp: 1708473600000, amount: '0.000147054516896928' },
{ timestamp: 1708560000000, amount: '0.000147133777127185' },
{ timestamp: 1708646400000, amount: '0.000147208399117727' },
{ timestamp: 1708732800000, amount: '0.000147289422275367' },
{ timestamp: 1708819200000, amount: '0.000147352436930246' },
{ timestamp: 1708905600000, amount: '0.000147420173096827' },
{ timestamp: 1708992000000, amount: '0.000147500180062545' },
{ timestamp: 1709078400000, amount: '0.000147585418073838' },
{ timestamp: 1709164800000, amount: '0.000147671364763405' },
{ timestamp: 1709251200000, amount: '0.000148768196229641' }
])
})
})
35 changes: 35 additions & 0 deletions packages/ethereum/test/getTxHistory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { EthereumStaker } from '@chorus-one/ethereum'
import { assert } from 'chai'
import { Hex } from 'viem'
import { prepareTests } from './lib/utils'

describe('EthereumStaker.getTxHistory', () => {
let validatorAddress: Hex
let delegatorAddress: Hex
let staker: EthereumStaker

beforeEach(async () => {
const setup = await prepareTests()
validatorAddress = setup.validatorAddress
delegatorAddress = '0x2dF83a340D5067751e8045cCe90764B19D9e7A4D'
staker = setup.staker
})

it('returns correct interaction history for given period of time for Chorus mainnet stakers', async () => {
const txHistory = await staker.getTxHistory({
validatorAddress,
delegatorAddress
})

const expectedTx = {
timestamp: 1705042416000,
type: 'Deposited',
amount: '0.01',
txHash: '0xd2d3c10b5e4dde53afe9cede8d10a961c357f574324599e9d78467f6c811afcf'
}

const tx = txHistory.find((tx) => tx.txHash === expectedTx.txHash)

assert.deepEqual(tx, expectedTx)
})
})
177 changes: 177 additions & 0 deletions packages/ethereum/test/getUnstakeQueue.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { Hex, PublicClient, WalletClient, decodeEventLog, formatEther, parseEther } from 'viem'
import { assert } from 'chai'
import { EthereumStaker } from '../dist/mjs'
import { prepareTests, stake } from './lib/utils'
import { VaultABI } from '../src/lib/contracts/vaultAbi'
const amountToStake = parseEther('5')
const amountToUnstake = parseEther('1')

const originalFetch = global.fetch

// https://github.com/tc39/proposal-promise-with-resolvers/blob/main/polyfills.js
const withResolvers = <V = unknown, Err = unknown>() => {
const out: {
resolve: (value: V) => void
reject: (reason: Err) => void
promise: Promise<V>
} = {
resolve: () => {},
reject: () => {},
promise: Promise.resolve() as Promise<V>
}

out.promise = new Promise<V>((resolve, reject) => {
out.resolve = resolve
out.reject = reject
})

return out
}

type VaultEvent = ReturnType<typeof decodeEventLog<typeof VaultABI, 'ExitQueueEntered'>>

describe('EthereumStaker.getUnstakeQueue', () => {
let delegatorAddress: Hex
let validatorAddress: Hex
let walletClient: WalletClient
let publicClient: PublicClient
let staker: EthereumStaker
let unwatch: () => void = () => {}

const unstake = async (amount: string) => {
const { tx } = await staker.buildUnstakeTx({
delegatorAddress,
validatorAddress,
amount
})

const request = await walletClient.prepareTransactionRequest({
...tx,
chain: undefined
})
const hash = await walletClient.sendTransaction({
...request,
account: delegatorAddress
})

const receipt = await publicClient.waitForTransactionReceipt({ hash })
assert.equal(receipt.status, 'success')
}

beforeEach(async () => {
const setup = await prepareTests()

delegatorAddress = setup.walletClient.account.address
validatorAddress = setup.validatorAddress
publicClient = setup.publicClient
walletClient = setup.walletClient
staker = setup.staker

await stake({
delegatorAddress,
validatorAddress,
amountToStake,
publicClient,
walletClient,
staker
})
})

afterEach(() => {
unwatch()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
global.fetch = originalFetch
})

it('should return the unstake queue', async () => {
// Subscribe to the ExitQueueEntered events
const { resolve: eventsResolve, promise: eventsPromise } = withResolvers<VaultEvent[]>()
const passedEvents: VaultEvent[] = []

unwatch = publicClient.watchEvent({
onLogs: (logs) => {
const nextEvents = logs
.map((l) =>
decodeEventLog({
abi: VaultABI,
data: l.data,
topics: l.topics
})
)
.filter((e): e is VaultEvent => e.eventName === 'ExitQueueEntered')
passedEvents.push(...nextEvents)
if (passedEvents.length === 2) {
eventsResolve(passedEvents.sort((a, b) => Number(a.args.shares) - Number(b.args.shares)))
}
}
})

// Unstake

await unstake(formatEther(amountToUnstake))
await unstake('2')

// Wait for the events to be processed

const events = await eventsPromise

assert.strictEqual(events.length, 2)
// The shares are not exactly the same as the amount to unstake
assert.closeTo(Number(events[0].args.shares), Number(parseEther('1')), Number(parseEther('0.1')))
assert.isTrue(typeof events[0].args.positionTicket === 'bigint')

// mock the request to Stakewise with positionTicket and totalShares from the events

const day = 24 * 60 * 60
const mockExitRequests = [
{
positionTicket: events[0].args.positionTicket.toString(),
totalShares: events[0].args.shares.toString(),
// earlier
timestamp: Math.round((new Date().getTime() - 60000) / 1000 - day * 2).toString()
},
{
positionTicket: events[1].args.positionTicket.toString(),
totalShares: events[1].args.shares.toString(),
// later
timestamp: Math.round(new Date().getTime() / 1000 - day * 2).toString()
}
]

const mockFetch = (input, init) => {
if (input === 'https://holesky-graph.stakewise.io/subgraphs/name/stakewise/stakewise?opName=exitQueue') {
return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
data: {
exitRequests: mockExitRequests
}
})
})
} else {
return originalFetch(input, init) // Fallback to the original fetch for other URLs
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
global.fetch = mockFetch

const unstakeQueue = await staker.getUnstakeQueue({
validatorAddress,
delegatorAddress
})

assert.strictEqual(unstakeQueue.length, 2)
// The queue is sorted by the timestamp from latest to earliest
const earlierItem = unstakeQueue[1]
const earlierMock = mockExitRequests[0]

assert.equal(earlierItem.timestamp, new Date(Number(earlierMock.timestamp) * 1000).getTime())
// Take into account 1 wei assets conversion issues on the contract
assert.closeTo(Number(parseEther(earlierItem.totalAmount)), Number(amountToUnstake), 1)

assert.isFalse(earlierItem.isWithdrawable)
})
})
26 changes: 26 additions & 0 deletions packages/ethereum/test/getVault.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { EthereumStaker } from '@chorus-one/ethereum'
import { assert } from 'chai'
import { Hex } from 'viem'
import { prepareTests } from './lib/utils'

describe('EthereumStaker.getVault', () => {
let validatorAddress: Hex
let staker: EthereumStaker

beforeEach(async () => {
const setup = await prepareTests()
validatorAddress = setup.validatorAddress
staker = setup.staker
})

it('returns correct details and stake for Chorus mainnet wallet by default', async () => {
const { vault } = await staker.getVault({
validatorAddress
})

assert.equal(vault.name, 'Chorus One Test Wallet')
assert.isTrue(Number(vault.tvl) > 1000 * 10 ** 18)
assert.isTrue(vault.description === 'Test wallet for Chorus')
assert.isTrue(/^https?:\/\/.*.png$/.test(vault.logoUrl))
})
})

0 comments on commit 2db1821

Please sign in to comment.