-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #26 from subspace/feat/add-staking-functions-and-t…
…ests Add staking functions and tests
- Loading branch information
Showing
19 changed files
with
806 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.