Skip to content

Commit

Permalink
Merge pull request #26 from subspace/feat/add-staking-functions-and-t…
Browse files Browse the repository at this point in the history
…ests

Add staking functions and tests
  • Loading branch information
marc-aurele-besner authored Jun 20, 2024
2 parents fa02528 + cb28707 commit 2a66934
Show file tree
Hide file tree
Showing 19 changed files with 806 additions and 112 deletions.
34 changes: 4 additions & 30 deletions packages/auto-consensus/__test__/balances.test.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,11 @@
import type { NetworkInput } from '@autonomys/auto-utils'
import {
ActivateWalletInput,
activate,
activateWallet,
disconnect,
networks,
} from '@autonomys/auto-utils'
import { ActivateWalletInput, activateWallet } from '@autonomys/auto-utils'
import { address } from '../src/address'
import { balance, totalIssuance } from '../src/balances'
import { setup } from './helpers'

describe('Verify balances functions', () => {
const isLocalhost = process.env.LOCALHOST === 'true'

// Define the test network and its details
const TEST_NETWORK: NetworkInput = !isLocalhost
? { networkId: networks[0].id }
: { networkId: 'autonomys-localhost' }
const TEST_INVALID_NETWORK = { networkId: 'invalid-network' }

const TEST_MNEMONIC = 'test test test test test test test test test test test junk'
const TEST_ADDRESS = '5GmS1wtCfR4tK5SSgnZbVT4kYw5W8NmxmijcsxCQE6oLW6A8'
const ALICE_URI = '//Alice'
const ALICE_ADDRESS = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'
const BOB_URI = '//Bob'
const BOB_ADDRESS = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'

beforeAll(async () => {
await activate(TEST_NETWORK)
})

afterAll(async () => {
await disconnect()
})
const { isLocalhost, TEST_NETWORK, TEST_MNEMONIC, TEST_ADDRESS, ALICE_URI, ALICE_ADDRESS } =
setup()

describe('Test totalIssuance()', () => {
test('Check totalIssuance return a number greater than zero', async () => {
Expand Down
117 changes: 117 additions & 0 deletions packages/auto-consensus/__test__/helpers/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
export type ActionEvents = string | string[]
export type Events = ActionEvents | ActionEvents[]

// Enum for Event Types
const enum Type {
system = 'system',
balances = 'balances',
transactionPayment = 'transactionPayment',
domains = 'domains',
sudo = 'sudo',
}

// Utility Function for Event Names
const eventName = (type: Type, event: string) => `${type}.${event}`

// System Events
const system: {
[key: string]: string
} = {
failure: eventName(Type.system, 'ExtrinsicFailed'),
newAccount: eventName(Type.system, 'NewAccount'),
success: eventName(Type.system, 'ExtrinsicSuccess'),
}

// Balances Events
const balances: {
[key: string]: string
} = {
deposit: eventName(Type.balances, 'Deposit'),
endowed: eventName(Type.balances, 'Endowed'),
transfer: eventName(Type.balances, 'Transfer'),
withdraw: eventName(Type.balances, 'Withdraw'),
}

// Transaction Payment Events
const transactionPayment: {
[key: string]: string
} = {
feePaid: eventName(Type.transactionPayment, 'TransactionFeePaid'),
}

// Domains Events
const domains: {
[key: string]: string
} = {
forceDomainEpochTransition: eventName(Type.domains, 'ForceDomainEpochTransition'),
fundsUnlocked: eventName(Type.domains, 'FundsUnlocked'),
operatorDeregistered: eventName(Type.domains, 'OperatorDeregistered'),
operatorNominated: eventName(Type.domains, 'OperatorNominated'),
operatorRegistered: eventName(Type.domains, 'OperatorRegistered'),
operatorUnlocked: eventName(Type.domains, 'OperatorUnlocked'),
storageFeeDeposited: eventName(Type.domains, 'StorageFeeDeposited'),
withdrawStake: eventName(Type.domains, 'WithdrewStake'),
}

// Sudo Events
const sudo: {
[key: string]: string
} = {
sudid: eventName(Type.sudo, 'Sudid'),
}

// Define specific extrinsic keys for events
type EventKeys =
| 'transfer'
| 'operatorRegistered'
| 'operatorNominated'
| 'operatorDeRegistered'
| 'withdrawStake'
| 'unlockFunds'
| 'forceDomainEpochTransition'

// Events Mappings
export const events: { [key in EventKeys]: ActionEvents } = {
transfer: [balances.withdraw, balances.transfer, transactionPayment.feePaid, system.success],
operatorRegistered: [
balances.withdraw,
domains.storageFeeDeposited,
domains.operatorRegistered,
transactionPayment.feePaid,
system.success,
],
operatorNominated: [
balances.withdraw,
balances.transfer,
domains.storageFeeDeposited,
domains.operatorNominated,
transactionPayment.feePaid,
system.success,
],
operatorDeRegistered: [
balances.withdraw,
domains.operatorDeregistered,
transactionPayment.feePaid,
system.success,
],
withdrawStake: [
balances.withdraw,
domains.withdrawStake,
transactionPayment.feePaid,
system.success,
],
unlockFunds: [
balances.withdraw,
domains.fundsUnlocked,
transactionPayment.feePaid,
system.success,
],
forceDomainEpochTransition: [
balances.withdraw,
domains.forceDomainEpochTransition,
sudo.sudid,
balances.deposit,
transactionPayment.feePaid,
system.success,
],
}
5 changes: 5 additions & 0 deletions packages/auto-consensus/__test__/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './events'
export * from './setup'
export * from './staking'
export * from './sudo'
export * from './tx'
39 changes: 39 additions & 0 deletions packages/auto-consensus/__test__/helpers/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { NetworkInput } from '@autonomys/auto-utils'
import { activate, disconnect, networks } from '@autonomys/auto-utils'

export const setup = () => {
const isLocalhost = process.env.LOCALHOST === 'true'

// Define the test network and its details
const TEST_NETWORK: NetworkInput = !isLocalhost
? { networkId: networks[0].id }
: { networkId: 'autonomys-localhost' }
const TEST_INVALID_NETWORK = { networkId: 'invalid-network' }

const TEST_MNEMONIC = 'test test test test test test test test test test test junk'
const TEST_ADDRESS = '5GmS1wtCfR4tK5SSgnZbVT4kYw5W8NmxmijcsxCQE6oLW6A8'
const ALICE_URI = '//Alice'
const ALICE_ADDRESS = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'
const BOB_URI = '//Bob'
const BOB_ADDRESS = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'

beforeAll(async () => {
await activate(TEST_NETWORK)
})

afterAll(async () => {
await disconnect()
})

return {
isLocalhost,
TEST_NETWORK,
TEST_INVALID_NETWORK,
TEST_MNEMONIC,
TEST_ADDRESS,
ALICE_URI,
ALICE_ADDRESS,
BOB_URI,
BOB_ADDRESS,
}
}
79 changes: 79 additions & 0 deletions packages/auto-consensus/__test__/helpers/staking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { ApiPromise } from '@polkadot/api'
import { u8aToHex } from '@polkadot/util'
import { operator, operators, RegisterOperatorInput } from '../../src/staking'

const STORAGE_FEE_DEPOSIT_PERCENTAGE = 20 // 20%

export const parseBigInt = (operatorId: string | number | bigint): bigint =>
typeof operatorId === 'bigint' ? operatorId : BigInt(operatorId)

export const calculateStake = (input: RegisterOperatorInput) => {
const { amountToStake, nominationTax } = input

return (parseBigInt(amountToStake) * BigInt(100 - STORAGE_FEE_DEPOSIT_PERCENTAGE)) / BigInt(100)
// To-Do: Add the nomination tax
}

export const calculateStorageFee = (input: RegisterOperatorInput) => {
const { amountToStake } = input

return (parseBigInt(amountToStake) * BigInt(STORAGE_FEE_DEPOSIT_PERCENTAGE)) / BigInt(100)
}

export const verifyOperatorRegistration = async (input: RegisterOperatorInput) => {
const { api, Operator, domainId, minimumNominatorStake, nominationTax } = input

const operatorsList = await operators(api)
const findOperator = operatorsList.find(
(o) => o.operatorDetails.signingKey === u8aToHex(Operator.publicKey),
)
expect(findOperator).toBeDefined()
if (findOperator) {
expect(findOperator.operatorDetails.currentDomainId).toEqual(BigInt(domainId))
expect(findOperator.operatorDetails.currentTotalStake).toEqual(BigInt(0))
expect(findOperator.operatorDetails.minimumNominatorStake).toEqual(
BigInt(minimumNominatorStake),
)
expect(findOperator.operatorDetails.nominationTax).toEqual(Number(nominationTax))
expect(findOperator.operatorDetails.status).toEqual({ registered: null })
const thisOperator = await operator(api, findOperator.operatorId)
expect(thisOperator.currentDomainId).toEqual(BigInt(domainId))
expect(thisOperator.currentTotalStake).toEqual(BigInt(0))
expect(thisOperator.minimumNominatorStake).toEqual(BigInt(minimumNominatorStake))
expect(thisOperator.nominationTax).toEqual(Number(nominationTax))
expect(thisOperator.status).toEqual({ registered: null })
}

return findOperator
}

export const verifyOperatorRegistrationFinal = async (input: RegisterOperatorInput) => {
const { api, Operator, domainId, amountToStake, minimumNominatorStake, nominationTax } = input

const operatorsList = await operators(api)
const findOperator = operatorsList.find(
(o) => o.operatorDetails.signingKey === u8aToHex(Operator.publicKey),
)
expect(findOperator).toBeDefined()
if (findOperator) {
expect(findOperator.operatorDetails.currentDomainId).toEqual(BigInt(domainId))
expect(findOperator.operatorDetails.currentTotalStake).toEqual(
(BigInt(amountToStake) / BigInt(100)) * BigInt(80),
)
expect(findOperator.operatorDetails.minimumNominatorStake).toEqual(
BigInt(minimumNominatorStake),
)
expect(findOperator.operatorDetails.nominationTax).toEqual(Number(nominationTax))
expect(findOperator.operatorDetails.totalStorageFeeDeposit).toEqual(
(BigInt(amountToStake) / BigInt(100)) * BigInt(20),
)
const thisOperator = await operator(api, findOperator.operatorId)
expect(thisOperator.currentDomainId).toEqual(BigInt(domainId))
expect(thisOperator.currentTotalStake).toEqual(calculateStake(input))
expect(thisOperator.minimumNominatorStake).toEqual(BigInt(minimumNominatorStake))
expect(thisOperator.nominationTax).toEqual(Number(nominationTax))
expect(thisOperator.totalStorageFeeDeposit).toEqual(calculateStorageFee(input))
}

return findOperator
}
13 changes: 13 additions & 0 deletions packages/auto-consensus/__test__/helpers/sudo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ApiPromise } from '@polkadot/api'
import type { AddressOrPair, SubmittableExtrinsic } from '@polkadot/api/types'
import type { ISubmittableResult } from '@polkadot/types/types'
import type { Events } from './events'
import { signAndSendTx } from './tx'

export const sudo = async (
api: ApiPromise,
sender: AddressOrPair,
tx: SubmittableExtrinsic<'promise', ISubmittableResult>,
eventsExpected: Events = [],
log: boolean = true,
) => await signAndSendTx(sender, api.tx.sudo.sudo(tx), eventsExpected, log)
68 changes: 68 additions & 0 deletions packages/auto-consensus/__test__/helpers/tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { AddressOrPair, SubmittableExtrinsic } from '@polkadot/api/types'
import type { EventRecord } from '@polkadot/types/interfaces'
import type { ISubmittableResult } from '@polkadot/types/types'
import type { Events } from './events'

const validateEvents = (
events: EventRecord[],
eventsExpected: Events,
tx: string,
block: string,
log: boolean = true,
) => {
const _eventsExpected =
typeof eventsExpected === 'string'
? [eventsExpected]
: eventsExpected.map((e: string | string[]) => (typeof e === 'string' ? [e] : e)).flat()

events.forEach(({ event: { data, method, section } }) => {
// if (log) console.log(`${section}.${method}`, data.toString()) // Uncomment this line to log every events with their data
const index = _eventsExpected.indexOf(`${section}.${method}`)
if (index > -1) _eventsExpected.splice(index, 1)
else if (log)
console.log('Event not expected', `${section}.${method}`, 'tx', tx, 'block', block)
})
if (_eventsExpected.length > 0)
console.log('Events not found', _eventsExpected, 'tx', tx, 'block', block)

expect(_eventsExpected).toHaveLength(0)

return _eventsExpected
}

export const signAndSendTx = async (
sender: AddressOrPair,
tx: SubmittableExtrinsic<'promise', ISubmittableResult>,
eventsExpected: Events = [],
log: boolean = true,
) => {
let txHashHex: string | undefined = undefined
let blockHash: string | undefined = undefined
await new Promise<void>((resolve, reject) => {
tx.signAndSend(sender, ({ events, status, txHash }) => {
if (status.isInBlock) {
txHashHex = txHash.toHex()
blockHash = status.asInBlock.toHex()
if (log) console.log('Successful tx', txHashHex, 'in block', blockHash)

if (eventsExpected.length > 0) {
eventsExpected = validateEvents(events, eventsExpected, txHashHex, blockHash, log)
if (eventsExpected.length === 0) resolve()
else reject(new Error('Events not found'))
} else resolve()
} else if (
status.isRetracted ||
status.isFinalityTimeout ||
status.isDropped ||
status.isInvalid
) {
if (log) console.error('Transaction failed')
reject(new Error('Transaction failed'))
}
})
})
expect(txHashHex).toBeDefined()
expect(blockHash).toBeDefined()

return { txHash: txHashHex, blockHash }
}
10 changes: 2 additions & 8 deletions packages/auto-consensus/__test__/info.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { activate, disconnect } from '@autonomys/auto-utils'
import { networkTimestamp } from '../src/info'
import { setup } from './helpers'

describe('Verify info functions', () => {
beforeAll(async () => {
await activate()
})

afterAll(async () => {
await disconnect()
})
setup()

test('Check network timestamp return a number greater than zero', async () => {
// totalIssuance is an async function that returns a hex number as a string
Expand Down
Loading

0 comments on commit 2a66934

Please sign in to comment.