import chai, { expect, util } from 'chai'; import { BigNumber, Signer, Wallet } from 'ethers'; import { solidity } from 'ethereum-waffle'; import { ethers } from 'hardhat'; import { MockToken, MockToken__factory, MockStake, MockStake__factory, MockRewarder, MockRewarder__factory, MockElection, MockElection__factory, MockMajorCandidates, MockMajorCandidates__factory, MockSlasher, MockSlasher__factory } from '../types'; import { oneToken, increaseTime, AddressZero } from './utils' import { hashMessage } from 'ethers/lib/utils'; chai.use(solidity); describe("Slasher tests", () => { let signers: Signer[]; let token: MockToken; let stake: MockStake; let rewarder: MockRewarder; let election: MockElection; let slasher: MockSlasher; let majorCandidates: MockMajorCandidates; const rewardPerSecond = oneToken.mul(40) const slashStake = oneToken.mul(5000) const stakeAmount = oneToken.mul(10000) let mockWallets: Wallet[] = [] beforeEach(async () => { signers = await ethers.getSigners(); token = await new MockToken__factory(signers[0]).deploy(); stake = await new MockStake__factory(signers[0]).deploy(); rewarder = await new MockRewarder__factory(signers[0]).deploy(); election = await new MockElection__factory(signers[0]).deploy(); majorCandidates = await new MockMajorCandidates__factory(signers[0]).deploy(); slasher = await new MockSlasher__factory(signers[0]).deploy(); const signer0 = await signers[0].getAddress() const signer1 = await signers[1].getAddress() await token.transfer(rewarder.address, oneToken.mul(1e7)) await rewarder.init(token.address, [stake.address, election.address]) await stake.init(token.address, rewarder.address, election.address, majorCandidates.address, slasher.address, 60 * 10, 100, rewardPerSecond) await majorCandidates.init(stake.address, election.address) await election.init(token.address, rewarder.address, stake.address, majorCandidates.address) await slasher.init(signer0, signer1, stake.address, majorCandidates.address, slashStake, 60 * 10, 1e6, 30e4, 40e4, 10e4) let last = '' for (let i = 0; i < 9; i++) { const mnemonic = 'test test test test test test test test test test test junk' const wallet = Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`).connect(ethers.provider) await signers[0].sendTransaction({ to: wallet.address, value: oneToken }) await token.transfer(wallet.address, oneToken.mul(1e7)) const manifest = `candidate manifest ${i}` await token.connect(wallet).approve(stake.address, stakeAmount) await stake.connect(wallet).registerAndStake(stakeAmount, manifest) const votes = oneToken.mul((10 - i) * 10000) await token.connect(wallet).approve(election.address, votes) await election.connect(wallet).vote(wallet.address, wallet.address, votes, !!last ? last : AddressZero, 10) last = wallet.address mockWallets.push(wallet) } }); it('slasher draft', async () => { const nonce = await slasher.nonce() const slashBlock = 1 const manifest = 'candidate manifest 0' const accuracy = 100 const raw = ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64', 'string', 'uint64'], [nonce, slashBlock, manifest, accuracy]) const hash = hashMessage(Buffer.from(raw.substring(2), 'hex')) let signatures:string[] = [] const l = await majorCandidates.MAX_MAJOR_CANDIDATES() for(let i = 0; i < l.toNumber(); i ++) { const sig = await mockWallets[i].signMessage(Buffer.from(raw.substring(2), 'hex')) signatures.push(sig) } const majors = await majorCandidates.majorCandidateList() signatures = signatures.sort((a, b) => { const aaddr = ethers.utils.recoverAddress(hash, a) const baddr = ethers.utils.recoverAddress(hash, b) return BigNumber.from(aaddr).gt(BigNumber.from(baddr)) ? 1 : -1 }) await slasher.connect(signers[1]).draft(slashBlock, manifest, accuracy, signatures) const slash = await slasher.getSlashAt(nonce) const candidate = await stake.manifestMap(manifest) expect(slash.candidate == candidate, 'invalid slash') }); }); describe("Slasher execute or reject", () => { let signers: Signer[]; let token: MockToken; let stake: MockStake; let rewarder: MockRewarder; let election: MockElection; let slasher: MockSlasher; let majorCandidates: MockMajorCandidates; const rewardPerSecond = oneToken.mul(40) const slashStake = oneToken.mul(5000) const stakeAmount = oneToken.mul(10000) const publicNoticePeriod = 60 * 10 let mockWallets: Wallet[] = [] const manifest = 'candidate manifest 0' beforeEach(async () => { signers = await ethers.getSigners(); token = await new MockToken__factory(signers[0]).deploy(); stake = await new MockStake__factory(signers[0]).deploy(); rewarder = await new MockRewarder__factory(signers[0]).deploy(); election = await new MockElection__factory(signers[0]).deploy(); majorCandidates = await new MockMajorCandidates__factory(signers[0]).deploy(); slasher = await new MockSlasher__factory(signers[0]).deploy(); const signer0 = await signers[0].getAddress() const signer1 = await signers[1].getAddress() await token.transfer(rewarder.address, oneToken.mul(1e7)) await rewarder.init(token.address, [stake.address, election.address]) await stake.init(token.address, rewarder.address, election.address, majorCandidates.address, slasher.address, 60 * 10, 100, rewardPerSecond) await majorCandidates.init(stake.address, election.address) await election.init(token.address, rewarder.address, stake.address, majorCandidates.address) await slasher.init(signer0, signer1, stake.address, majorCandidates.address, slashStake, publicNoticePeriod, 1e6, 30e4, 40e4, 10e4) let last = '' for (let i = 0; i < 10; i++) { const mnemonic = 'test test test test test test test test test test test junk' const wallet = Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`).connect(ethers.provider) await signers[0].sendTransaction({ to: wallet.address, value: oneToken }) await token.transfer(wallet.address, oneToken.mul(1e7)) const manifest = `candidate manifest ${i}` await token.connect(wallet).approve(stake.address, stakeAmount) await stake.connect(wallet).registerAndStake(stakeAmount, manifest) const votes = oneToken.mul((10 - i) * 10000) await token.connect(wallet).approve(election.address, votes) await election.connect(wallet).vote(wallet.address, wallet.address, votes, !!last ? last : AddressZero, 10) last = wallet.address mockWallets.push(wallet) } const nonce = await slasher.nonce() const slashBlock = 1 const accuracy = 100 const raw = ethers.utils.defaultAbiCoder.encode(['uint256', 'uint64', 'string', 'uint64'], [nonce, slashBlock, manifest, accuracy]) const hash = hashMessage(Buffer.from(raw.substring(2), 'hex')) let signatures: string[] = [] const l = await majorCandidates.MAX_MAJOR_CANDIDATES() for (let i = 0; i < l.toNumber(); i++) { const sig = await mockWallets[i].signMessage(Buffer.from(raw.substring(2), 'hex')) signatures.push(sig) } signatures = signatures.sort((a, b) => { const aaddr = ethers.utils.recoverAddress(hash, a) const baddr = ethers.utils.recoverAddress(hash, b) return BigNumber.from(aaddr).gt(BigNumber.from(baddr)) ? 1 : -1 }) await slasher.connect(signers[1]).draft(slashBlock, manifest, accuracy, signatures) }); it('slasher execute', async () => { await increaseTime(publicNoticePeriod+10) const nonceOf = await slasher.nonceOf(mockWallets[0].address) await slasher.connect(signers[1]).execute(manifest) const slash = await slasher.getSlashAt(nonceOf) expect(slash.status == 2, 'invalid slash status') }); it('slasher reject', async () => { const nonceOf = await slasher.nonceOf(mockWallets[0].address) await slasher.connect(signers[1]).reject(manifest) const slash = await slasher.getSlashAt(nonceOf) expect(slash.status == 1, 'invalid slash status') }); });