Skip to content
Merged
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
4 changes: 2 additions & 2 deletions tests/network-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"license": "GPL-3.0-only",
"scripts": {
"build": "tsc --build tsconfig.json",
"test": "tap --files src/nicaea/tests/proposals/*Test.ts --files src/nicaea/tests/workingGroup/*Test.ts -T",
"test": "tap --files src/iznik/tests/proposals/*Test.ts --files src/iznik/tests/workingGroup/*Test.ts -T",
"test-migration-constantinople": "tap --files src/rome/tests/romeRuntimeUpgradeTest.ts --files src/constantinople/tests/electingCouncilTest.ts -T",
"test-migration-nicaea": "tap --files src/constantinople/tests/proposals/updateRuntimeTest.ts --files src/nicaea/tests/electingCouncilTest.ts -T",
"debug": "tap --files src/nicaea/tests/workingGroup/*Test.ts -T",
"debug": "tap --files src/iznik/tests/proposals/manageLeaderRoleTest.ts -T",
"lint": "eslint . --quiet --ext .ts",
"checks": "yarn lint && tsc --noEmit --pretty && prettier ./ --check",
"format": "prettier ./ --write "
Expand Down
Empty file.
65 changes: 65 additions & 0 deletions tests/network-tests/src/iznik/tests/council/electingCouncilTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { KeyringPair } from '@polkadot/keyring/types'
import { initConfig } from '../../utils/config'
import { Keyring, WsProvider } from '@polkadot/api'
import { setTestTimeout } from '../../utils/setTestTimeout'
import BN from 'bn.js'
import tap from 'tap'
import { registerJoystreamTypes } from '@nicaea/types'
import { ApiWrapper } from '../../utils/apiWrapper'
import { closeApi } from '../../utils/closeApi'
import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
import { Utils } from '../../utils/utils'

tap.mocha.describe('Electing council scenario', async () => {
initConfig()
registerJoystreamTypes()

const nodeUrl: string = process.env.NODE_URL!
const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
const keyring = new Keyring({ type: 'sr25519' })
const provider = new WsProvider(nodeUrl)
const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
const sudo: KeyringPair = keyring.addFromUri(sudoUri)

const N: number = +process.env.MEMBERSHIP_CREATION_N!
const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
const K: number = +process.env.COUNCIL_ELECTION_K!
const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)

const durationInBlocks = 25

setTestTimeout(apiWrapper, durationInBlocks)

const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
apiWrapper,
sudo,
m1KeyPairs,
paidTerms
)
tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))

const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
apiWrapper,
sudo,
m2KeyPairs,
paidTerms
)
tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))

const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
apiWrapper,
m1KeyPairs,
m2KeyPairs,
K,
sudo,
greaterStake,
lesserStake
)
tap.test('Elect council', async () => electCouncilFixture.runner(false))

closeApi(apiWrapper)
})
140 changes: 140 additions & 0 deletions tests/network-tests/src/iznik/tests/fixtures/councilElectionModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { ApiWrapper } from '../../utils/apiWrapper'
import { KeyringPair } from '@polkadot/keyring/types'
import BN from 'bn.js'
import { assert } from 'chai'
import { Seat } from '@nicaea/types/council'
import { v4 as uuid } from 'uuid'
import { Utils } from '../../utils/utils'
import { Fixture } from './interfaces/fixture'

export class ElectCouncilFixture implements Fixture {
private apiWrapper: ApiWrapper
private m1KeyPairs: KeyringPair[]
private m2KeyPairs: KeyringPair[]
private k: number
private sudo: KeyringPair
private greaterStake: BN
private lesserStake: BN

public constructor(
apiWrapper: ApiWrapper,
m1KeyPairs: KeyringPair[],
m2KeyPairs: KeyringPair[],
k: number,
sudo: KeyringPair,
greaterStake: BN,
lesserStake: BN
) {
this.apiWrapper = apiWrapper
this.m1KeyPairs = m1KeyPairs
this.m2KeyPairs = m2KeyPairs
this.k = k
this.sudo = sudo
this.greaterStake = greaterStake
this.lesserStake = lesserStake
}

public async runner(expectFailure: boolean): Promise<void> {
let now = await this.apiWrapper.getBestBlock()
const applyForCouncilFee: BN = this.apiWrapper.estimateApplyForCouncilFee(this.greaterStake)
const voteForCouncilFee: BN = this.apiWrapper.estimateVoteForCouncilFee(
this.sudo.address,
this.sudo.address,
this.greaterStake
)
const salt: string[] = []
this.m1KeyPairs.forEach(() => {
salt.push(''.concat(uuid().replace(/-/g, '')))
})
const revealVoteFee: BN = this.apiWrapper.estimateRevealVoteFee(this.sudo.address, salt[0])

// Topping the balances
await this.apiWrapper.transferBalanceToAccounts(
this.sudo,
this.m2KeyPairs,
applyForCouncilFee.add(this.greaterStake)
)
await this.apiWrapper.transferBalanceToAccounts(
this.sudo,
this.m1KeyPairs,
voteForCouncilFee.add(revealVoteFee).add(this.greaterStake)
)

// First K members stake more
await this.apiWrapper.sudoStartAnnouncingPerion(this.sudo, now.addn(100))
await this.apiWrapper.batchApplyForCouncilElection(this.m2KeyPairs.slice(0, this.k), this.greaterStake)
this.m2KeyPairs.slice(0, this.k).forEach((keyPair) =>
this.apiWrapper.getCouncilElectionStake(keyPair.address).then((stake) => {
assert(
stake.eq(this.greaterStake),
`${keyPair.address} not applied correctly for council election with stake ${stake} versus expected ${this.greaterStake}`
)
})
)

// Last members stake less
await this.apiWrapper.batchApplyForCouncilElection(this.m2KeyPairs.slice(this.k), this.lesserStake)
this.m2KeyPairs.slice(this.k).forEach((keyPair) =>
this.apiWrapper.getCouncilElectionStake(keyPair.address).then((stake) => {
assert(
stake.eq(this.lesserStake),
`${keyPair.address} not applied correctrly for council election with stake ${stake} versus expected ${this.lesserStake}`
)
})
)

// Voting
await this.apiWrapper.sudoStartVotingPerion(this.sudo, now.addn(100))
await this.apiWrapper.batchVoteForCouncilMember(
this.m1KeyPairs.slice(0, this.k),
this.m2KeyPairs.slice(0, this.k),
salt.slice(0, this.k),
this.lesserStake
)
await this.apiWrapper.batchVoteForCouncilMember(
this.m1KeyPairs.slice(this.k),
this.m2KeyPairs.slice(this.k),
salt.slice(this.k),
this.greaterStake
)

// Revealing
await this.apiWrapper.sudoStartRevealingPerion(this.sudo, now.addn(100))
await this.apiWrapper.batchRevealVote(
this.m1KeyPairs.slice(0, this.k),
this.m2KeyPairs.slice(0, this.k),
salt.slice(0, this.k)
)
await this.apiWrapper.batchRevealVote(
this.m1KeyPairs.slice(this.k),
this.m2KeyPairs.slice(this.k),
salt.slice(this.k)
)
now = await this.apiWrapper.getBestBlock()

// Resolving election
// 3 is to ensure the revealing block is in future
await this.apiWrapper.sudoStartRevealingPerion(this.sudo, now.addn(3))
await Utils.wait(this.apiWrapper.getBlockDuration().muln(2.5).toNumber())
const seats: Seat[] = await this.apiWrapper.getCouncil()

// Preparing collections to increase assertion readability
const m2addresses: string[] = this.m2KeyPairs.map((keyPair) => keyPair.address)
const m1addresses: string[] = this.m1KeyPairs.map((keyPair) => keyPair.address)
const members: string[] = seats.map((seat) => seat.member.toString())
const bakers: string[] = seats.map((seat) => seat.backers.map((baker) => baker.member.toString())).flat()

// Assertions
m2addresses.forEach((address) => assert(members.includes(address), `Account ${address} is not in the council`))
m1addresses.forEach((address) => assert(bakers.includes(address), `Account ${address} is not in the voters`))
seats.forEach((seat) =>
assert(
Utils.getTotalStake(seat).eq(this.greaterStake.add(this.lesserStake)),
`Member ${seat.member} has unexpected stake ${Utils.getTotalStake(seat)}`
)
)
if (expectFailure) {
throw new Error('Successful fixture run while expecting failure')
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface Fixture {
runner(expectFailure: boolean): Promise<void>
}
106 changes: 106 additions & 0 deletions tests/network-tests/src/iznik/tests/fixtures/membershipModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { ApiWrapper } from '../../utils/apiWrapper'
import { KeyringPair } from '@polkadot/keyring/types'
import BN from 'bn.js'
import { assert } from 'chai'
import { Fixture } from './interfaces/fixture'

export class BuyMembershipHappyCaseFixture implements Fixture {
private apiWrapper: ApiWrapper
private sudo: KeyringPair
private keyPairs: KeyringPair[]
private paidTerms: number

public constructor(apiWrapper: ApiWrapper, sudo: KeyringPair, keyPairs: KeyringPair[], paidTerms: number) {
this.apiWrapper = apiWrapper
this.sudo = sudo
this.keyPairs = keyPairs
this.paidTerms = paidTerms
}

public async runner(expectFailure: boolean): Promise<void> {
// Fee estimation and transfer
const membershipFee: BN = await this.apiWrapper.getMembershipFee(this.paidTerms)
const membershipTransactionFee: BN = this.apiWrapper.estimateBuyMembershipFee(
this.sudo,
this.paidTerms,
'member_name_which_is_longer_than_expected'
)
await this.apiWrapper.transferBalanceToAccounts(
this.sudo,
this.keyPairs,
membershipTransactionFee.add(new BN(membershipFee))
)

// Buying membership
await Promise.all(
this.keyPairs.map(async (keyPair, index) => {
await this.apiWrapper.buyMembership(
keyPair,
this.paidTerms,
`new_member_${index}${keyPair.address.substring(0, 8)}`
)
})
)

// Assertions
this.keyPairs.forEach((keyPair) =>
this.apiWrapper
.getMemberIds(keyPair.address)
.then((membership) => assert(membership.length > 0, `Account ${keyPair.address} is not a member`))
)
if (expectFailure) {
throw new Error('Successful fixture run while expecting failure')
}
}
}

export class BuyMembershipWithInsufficienFundsFixture implements Fixture {
private apiWrapper: ApiWrapper
private sudo: KeyringPair
private aKeyPair: KeyringPair
private paidTerms: number

public constructor(apiWrapper: ApiWrapper, sudo: KeyringPair, aKeyPair: KeyringPair, paidTerms: number) {
this.apiWrapper = apiWrapper
this.sudo = sudo
this.aKeyPair = aKeyPair
this.paidTerms = paidTerms
}

public async runner(expectFailure: boolean) {
// Fee estimation and transfer
const membershipFee: BN = await this.apiWrapper.getMembershipFee(this.paidTerms)
const membershipTransactionFee: BN = this.apiWrapper.estimateBuyMembershipFee(
this.sudo,
this.paidTerms,
'member_name_which_is_longer_than_expected'
)
await this.apiWrapper.transferBalance(this.sudo, this.aKeyPair.address, membershipTransactionFee)

// Balance assertion
await this.apiWrapper
.getBalance(this.aKeyPair.address)
.then((balance) =>
assert(
balance.toBn() < membershipFee.add(membershipTransactionFee),
'Account A already have sufficient balance to purchase membership'
)
)

// Buying memebership
await this.apiWrapper.buyMembership(
this.aKeyPair,
this.paidTerms,
`late_member_${this.aKeyPair.address.substring(0, 8)}`,
true
)

// Assertions
this.apiWrapper
.getMemberIds(this.aKeyPair.address)
.then((membership) => assert(membership.length === 0, 'Account A is a member'))
if (expectFailure) {
throw new Error('Successful fixture run while expecting failure')
}
}
}
Loading