diff --git a/packages/protocol/test-sol/governance/network/GovernanceSlasher.t.sol b/packages/protocol/test-sol/governance/network/GovernanceSlasher.t.sol new file mode 100644 index 00000000000..a52c3fa75e2 --- /dev/null +++ b/packages/protocol/test-sol/governance/network/GovernanceSlasher.t.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.5.13; + +import "celo-foundry/Test.sol"; + +import "@celo-contracts/common/Accounts.sol"; +import "@celo-contracts/common/FixidityLib.sol"; +import "@celo-contracts/common/interfaces/IRegistry.sol"; +import "@celo-contracts/governance/Proposals.sol"; +import "@celo-contracts/governance/test/MockLockedGold.sol"; + +// Contract to test +import "@celo-contracts/governance/GovernanceSlasher.sol"; + +contract GovernanceSlasherTest is Test { + event SlashingApproved(address indexed account, uint256 amount); + event GovernanceSlashPerformed(address indexed account, uint256 amount); + + IRegistry registry; + Accounts accounts; + MockLockedGold mockLockedGold; + + GovernanceSlasher public governanceSlasher; + address owner; + address nonOwner; + address validator; + address slashedAddress; + address registryAddress = 0x000000000000000000000000000000000000ce10; + + function setUp() public { + owner = address(this); + nonOwner = actor("nonOwner"); + validator = actor("validator"); + slashedAddress = actor("slashedAddress"); + + accounts = new Accounts(true); + mockLockedGold = new MockLockedGold(); + governanceSlasher = new GovernanceSlasher(true); + + deployCodeTo("Registry.sol", abi.encode(false), registryAddress); + registry = IRegistry(registryAddress); + registry.setAddressFor("Accounts", address(accounts)); + registry.setAddressFor("LockedGold", address(mockLockedGold)); + + governanceSlasher.initialize(registryAddress); + mockLockedGold.setAccountTotalLockedGold(validator, 5000); + } +} + +contract GovernanceSlasherInitializeTest is GovernanceSlasherTest { + function test_shouldHaveSetOwner() public { + assertEq(governanceSlasher.owner(), owner); + } + + function test_CanOnlyBeCalledOnce() public { + vm.expectRevert("contract already initialized"); + governanceSlasher.initialize(registryAddress); + } +} + +contract GovernanceSlasherApproveSlashingTest is GovernanceSlasherTest { + function test_ShouldSetSlashableAmount() public { + governanceSlasher.approveSlashing(slashedAddress, 1000); + assertEq(governanceSlasher.getApprovedSlashing(slashedAddress), 1000); + } + + function test_ShouldIncrementSlashableAmountWhenApprovedTwice() public { + governanceSlasher.approveSlashing(slashedAddress, 1000); + governanceSlasher.approveSlashing(slashedAddress, 1000); + assertEq(governanceSlasher.getApprovedSlashing(slashedAddress), 2000); + } + + function test_CanOnlyBeCalledByOnwer() public { + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(nonOwner); + governanceSlasher.approveSlashing(slashedAddress, 1000); + } + + function test_EmitsSlashingApprovedEvent() public { + vm.expectEmit(true, true, true, true); + emit SlashingApproved(slashedAddress, 1000); + governanceSlasher.approveSlashing(slashedAddress, 1000); + } +} + +contract GovernanceSlasherSlashTest is GovernanceSlasherTest { + address[] lessers = new address[](0); + address[] greaters = new address[](0); + uint256[] indices = new uint256[](0); + + function test_ShouldFailIfThereIsNothingToSlash() public { + vm.expectRevert("No penalty given by governance"); + governanceSlasher.slash(validator, lessers, greaters, indices); + } + + function test_ShouldDecrementCelo() public { + governanceSlasher.approveSlashing(validator, 1000); + governanceSlasher.slash(validator, lessers, greaters, indices); + assertEq(mockLockedGold.accountTotalLockedGold(validator), 4000); + } + + function test_ShouldHaveSetTheApprovedSlashingToZero() public { + governanceSlasher.approveSlashing(validator, 1000); + governanceSlasher.slash(validator, lessers, greaters, indices); + assertEq(governanceSlasher.getApprovedSlashing(validator), 0); + } + + function test_EmitsGovernanceSlashPerformedEvent() public { + governanceSlasher.approveSlashing(validator, 1000); + vm.expectEmit(true, true, true, true); + emit GovernanceSlashPerformed(validator, 1000); + governanceSlasher.slash(validator, lessers, greaters, indices); + } +} diff --git a/packages/protocol/test/governance/network/governance_slasher.ts b/packages/protocol/test/governance/network/governance_slasher.ts deleted file mode 100644 index bb694cf19ce..00000000000 --- a/packages/protocol/test/governance/network/governance_slasher.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { CeloContractName } from '@celo/protocol/lib/registry-utils' -import { - assertContainSubset, - assertTransactionRevertWithReason, -} from '@celo/protocol/lib/test-utils' -import BigNumber from 'bignumber.js' -import { - AccountsContract, - AccountsInstance, - GovernanceSlasherContract, - GovernanceSlasherInstance, - MockLockedGoldContract, - MockLockedGoldInstance, - MockValidatorsContract, - MockValidatorsInstance, - RegistryContract, - RegistryInstance, -} from 'types' - -const Accounts: AccountsContract = artifacts.require('Accounts') -const MockValidators: MockValidatorsContract = artifacts.require('MockValidators') -const GovernanceSlasher: GovernanceSlasherContract = artifacts.require('GovernanceSlasher') -const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold') -const Registry: RegistryContract = artifacts.require('Registry') - -// TODO(mcortesi): Use BN -// @ts-ignore -GovernanceSlasher.numberFormat = 'BigNumber' - -contract('GovernanceSlasher', (accounts: string[]) => { - let accountsInstance: AccountsInstance - let validators: MockValidatorsInstance - let registry: RegistryInstance - let mockLockedGold: MockLockedGoldInstance - let slasher: GovernanceSlasherInstance - const nonOwner = accounts[1] - const validator = accounts[1] - - beforeEach(async () => { - accountsInstance = await Accounts.new(true) - await Promise.all(accounts.map((account) => accountsInstance.createAccount({ from: account }))) - mockLockedGold = await MockLockedGold.new() - registry = await Registry.new(true) - validators = await MockValidators.new() - slasher = await GovernanceSlasher.new(true) - await accountsInstance.initialize(registry.address) - await registry.setAddressFor(CeloContractName.Accounts, accountsInstance.address) - await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address) - await registry.setAddressFor(CeloContractName.Validators, validators.address) - await slasher.initialize(registry.address) - await mockLockedGold.setAccountTotalLockedGold(validator, 5000) - }) - - describe('#initialize()', () => { - it('should have set the owner', async () => { - const owner: string = await slasher.owner() - assert.equal(owner, accounts[0]) - }) - it('can only be called once', async () => { - await assertTransactionRevertWithReason( - slasher.initialize(registry.address), - 'contract already initialized' - ) - }) - }) - - describe('#approveSlashing()', () => { - it('should set slashable amount', async () => { - await slasher.approveSlashing(accounts[2], 1000) - const amount = await slasher.getApprovedSlashing(accounts[2]) - assert.equal(amount.toNumber(), 1000) - }) - it('should increment slashable amount when approved twice', async () => { - await slasher.approveSlashing(accounts[2], 1000) - await slasher.approveSlashing(accounts[2], 1000) - const amount = await slasher.getApprovedSlashing(accounts[2]) - assert.equal(amount.toNumber(), 2000) - }) - it('can only be called by owner', async () => { - await assertTransactionRevertWithReason( - slasher.approveSlashing(accounts[2], 1000, { from: nonOwner }), - 'Ownable: caller is not the owner' - ) - }) - }) - - describe('#slash()', () => { - it('fails if there is nothing to slash', async () => { - await assertTransactionRevertWithReason( - slasher.slash(validator, [], [], []), - 'No penalty given by governance' - ) - }) - it('decrements gold', async () => { - await slasher.approveSlashing(validator, 1000) - await slasher.slash(validator, [], [], []) - const amount = await mockLockedGold.accountTotalLockedGold(validator) - assert.equal(amount.toNumber(), 4000) - }) - it('has set the approved slashing to zero', async () => { - await slasher.approveSlashing(validator, 1000) - await slasher.slash(validator, [], [], []) - const amount = await slasher.getApprovedSlashing(validator) - assert.equal(amount.toNumber(), 0) - }) - it('should emit the corresponding event', async () => { - const amount = 1000 - await slasher.approveSlashing(validator, amount) - const resp = await slasher.slash(validator, [], [], []) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'GovernanceSlashPerformed', - args: { - account: validator, - amount: new BigNumber(amount), - }, - }) - }) - }) -})