Skip to content

Commit

Permalink
fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Octavian authored and Dan Octavian committed Mar 29, 2023
1 parent 67b5811 commit 6457d7a
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 4 deletions.
2 changes: 2 additions & 0 deletions contracts/interfaces/ITokenController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,6 @@ interface ITokenController {
bytes32[] memory lockReasons,
uint withdrawableAmount
);

function getPendingRewards(address member) external view returns (uint);
}
12 changes: 12 additions & 0 deletions contracts/mocks/MemberRoles/MRMockAssessment.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.18;
import "../../interfaces/IAssessment.sol";

contract MRMockAssessment {
mapping(address => IAssessment.Stake) public stakeOf;

function setStakeOf(address staker, uint96 stakeAmount) external {
stakeOf[staker] = IAssessment.Stake(stakeAmount, 0 /* rewardWithdrawableFromIndex */ , 0 /* fraudCount */);
}
}
21 changes: 21 additions & 0 deletions contracts/mocks/MemberRoles/MRMockPooledStaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.18;

import "../../abstract/MasterAwareV2.sol";
import "../../interfaces/IPooledStaking.sol";

contract MRMockPooledStaking {

mapping(address => uint) public stakerReward;
mapping(address => uint) public stakerDeposit;

// Manually set the staker reward
function setStakerReward(address staker, uint reward) external {
stakerReward[staker] = reward;
}

// Manually set the staker deposit
function setStakerDeposit(address staker, uint deposit) external {
stakerDeposit[staker] = deposit;
}
}
36 changes: 34 additions & 2 deletions contracts/mocks/TokenControllerMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ contract TokenControllerMock is MasterAwareV2 {

mapping(address => bool) public isStakingPoolManager;

mapping(address => mapping (bytes32 => uint)) public _tokensLocked;

mapping(address => uint) public _withdrawableCoverNotes;

mapping(address => uint) public _pendingRewards;

function mint(address _member, uint256 _amount) public onlyInternal {
token().mint(_member, _amount);
}
Expand Down Expand Up @@ -119,6 +125,34 @@ contract TokenControllerMock is MasterAwareV2 {
isStakingPoolManager[member] = isManager;
}

function setTokensLocked(address member, bytes32 reason, uint amount) external {
_tokensLocked[member][reason] = amount;
}

function setWithdrawableCoverNotes(address member, uint amount) external {
_withdrawableCoverNotes[member] = amount;
}

function setPendingRewards(address member, uint amount) external {
_pendingRewards[member] = amount;
}

function tokensLocked(address member, bytes32 reason) external view returns (uint) {
return _tokensLocked[member][reason];
}

function getWithdrawableCoverNotes(address member) external view returns (
uint[] memory /* coverIds */,
bytes32[] memory /* lockReasons */,
uint amount
) {
amount = _withdrawableCoverNotes[member];
}

function getPendingRewards(address member) external view returns (uint) {
return _pendingRewards[member];
}

/* unused functions */

modifier unused {
Expand All @@ -128,7 +162,5 @@ contract TokenControllerMock is MasterAwareV2 {

function burnLockedTokens(address, bytes32, uint256) unused external {}

function tokensLocked(address, bytes32) unused external pure returns (uint256) { return 0; }

function releaseLockedTokens(address _of, bytes32 _reason, uint256 _amount) unused external {}
}
19 changes: 17 additions & 2 deletions contracts/modules/governance/MemberRoles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import "../../interfaces/INXMToken.sol";
import "../../interfaces/IStakingPool.sol";
import "../../interfaces/IPooledStaking.sol";
import "../../interfaces/ITokenController.sol";
import "../../interfaces/IAssessment.sol";
import "./external/Governed.sol";

contract MemberRoles is IMemberRoles, Governed, MasterAwareV2 {
Expand Down Expand Up @@ -114,6 +115,10 @@ contract MemberRoles is IMemberRoles, Governed, MasterAwareV2 {
return IPooledStaking(internalContracts[uint(ID.PS)]);
}

function assessment() internal view returns (IAssessment) {
return IAssessment(internalContracts[uint(ID.AS)]);
}

/// Updates contracts dependencies.
///
/// @dev Iupgradable Interface to update dependent contract address
Expand All @@ -129,6 +134,7 @@ contract MemberRoles is IMemberRoles, Governed, MasterAwareV2 {
internalContracts[uint(ID.P1)] = master.getLatestAddress("P1");
internalContracts[uint(ID.CO)] = master.getLatestAddress("CO");
internalContracts[uint(ID.PS)] = master.getLatestAddress("PS");
internalContracts[uint(ID.AS)] = master.getLatestAddress("AS");
}

/// Adds a new member role.
Expand Down Expand Up @@ -229,7 +235,7 @@ contract MemberRoles is IMemberRoles, Governed, MasterAwareV2 {
tokenController().isStakingPoolManager(msg.sender) == false,
"MemberRoles: Member is a staking pool manager"
);

IPooledStaking _legacyPooledStaking = legacyPooledStaking();

// check that there are no NXM tokens left to withdraw in Nexus V1
Expand All @@ -238,6 +244,10 @@ contract MemberRoles is IMemberRoles, Governed, MasterAwareV2 {
require(_tokenController.tokensLocked(msg.sender, "CLA") == 0, "V1 CLA tokensLocked != 0");
(, , uint coverNotesAmount) = _tokenController.getWithdrawableCoverNotes(msg.sender);
require(coverNotesAmount == 0, "V1 coverNotesAmount != 0");
// tc.getPendingRewards includes both assessment and governance rewards
require(_tokenController.getPendingRewards(msg.sender) == 0, "TC pendingRewards != 0");
(uint96 stakeAmount, ,) = assessment().stakeOf(msg.sender);
require(stakeAmount == 0, "Assessment stake != 0");

_tokenController.burnFrom(msg.sender, token.balanceOf(msg.sender));
_updateRole(msg.sender, uint(Role.Member), false);
Expand Down Expand Up @@ -327,8 +337,13 @@ contract MemberRoles is IMemberRoles, Governed, MasterAwareV2 {
require(_tokenController.tokensLocked(currentAddress, "CLA") == 0, "V1 CLA tokensLocked != 0");
(, , uint coverNotesAmount) = _tokenController.getWithdrawableCoverNotes(currentAddress);
require(coverNotesAmount == 0, "V1 coverNotesAmount != 0");
// tc.getPendingRewards includes both assessment and governance rewards
require(_tokenController.getPendingRewards(currentAddress) == 0, "TC pendingRewards != 0");
(uint96 stakeAmount, ,) = assessment().stakeOf(currentAddress);
require(stakeAmount == 0, "Assessment stake != 0");


_tokenController.addToWhitelist(newAddress);
_tokenController.addToWhitelist(newAddress);
_updateRole(currentAddress, uint(Role.Member), false);
_updateRole(newAddress, uint(Role.Member), true);

Expand Down
12 changes: 12 additions & 0 deletions test/unit/MemberRoles/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ async function setup() {
const stakingNFT = await StakingNFT.deploy('', '');
await stakingNFT.deployed();

const PooledStaking = await ethers.getContractFactory('MRMockPooledStaking');
const pooledStaking = await PooledStaking.deploy();

const Cover = await ethers.getContractFactory('MRMockCover');
const cover = await Cover.deploy(coverNFT.address, memberRoles.address, stakingNFT.address);

const Governance = await ethers.getContractFactory('MRMockGovernance');
const governance = await Governance.deploy();

const Assessment = await ethers.getContractFactory('MRMockAssessment');
const assessment = await Assessment.deploy();

// quotation data is currently hardcoded in the MemberRoles contract
// using setCode to deploy the QD mock at that specific address
const quotationDataAddress = '0x1776651F58a17a50098d31ba3C3cD259C1903f7A';
Expand All @@ -51,11 +57,15 @@ async function setup() {
await master.setLatestAddress(hex('P1'), pool.address);
await master.setLatestAddress(hex('MR'), memberRoles.address);
await master.setLatestAddress(hex('GV'), governance.address);
await master.setLatestAddress(hex('PS'), pooledStaking.address);
await master.setLatestAddress(hex('AS'), assessment.address);
await master.enrollInternal(tokenController.address);
await master.enrollInternal(pool.address);
await master.enrollInternal(nxm.address);
await master.enrollInternal(cover.address);
await master.enrollInternal(memberRoles.address);
await master.enrollInternal(pooledStaking.address);
await master.enrollInternal(assessment.address);

const accounts = await getAccounts();
await master.enrollGovernance(accounts.governanceContracts[0].address);
Expand Down Expand Up @@ -109,6 +119,8 @@ async function setup() {
stakingNFT,
tokenController,
quotationData,
pooledStaking,
assessment
};
}

Expand Down
79 changes: 79 additions & 0 deletions test/unit/MemberRoles/switchMembership.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { ethers } = require('hardhat');
const { Role } = require('../utils').constants;
const { expect } = require('chai');
const { formatBytes32String } = ethers.utils;

describe('switchMembership', function () {
it('grants the member role to the new address', async function () {
Expand Down Expand Up @@ -103,6 +104,84 @@ describe('switchMembership', function () {
);
});

it('reverts when member has LegacyPooledStaking deposit tokens', async function () {
const { memberRoles, pooledStaking } = this.contracts;
const {
members: [member],
nonMembers: [nonMember],
} = this.accounts;

await pooledStaking.setStakerDeposit(member.address, 100);
await expect(memberRoles.connect(member).switchMembership(nonMember.address)).to.be.revertedWith(
'V1 stakerDeposit != 0',
);
});

it('reverts when member has LegacyPooledStaking reward tokens', async function () {
const { memberRoles, pooledStaking } = this.contracts;
const {
members: [member],
nonMembers: [nonMember],
} = this.accounts;

await pooledStaking.setStakerReward(member.address, 100);
await expect(memberRoles.connect(member).switchMembership(nonMember.address)).to.be.revertedWith(
'V1 stakerReward != 0',
);
});

it('reverts when member has tokens locked for claim assessment', async function () {
const { memberRoles, tokenController } = this.contracts;
const {
members: [member],
nonMembers: [nonMember],
} = this.accounts;

await tokenController.setTokensLocked(member.address, formatBytes32String('CLA'), 100);
await expect(memberRoles.connect(member).switchMembership(nonMember.address)).to.be.revertedWith(
'V1 CLA tokensLocked != 0',
);
});

it('reverts when member has withdrawable cover notes', async function () {
const { memberRoles, tokenController } = this.contracts;
const {
members: [member],
nonMembers: [nonMember],
} = this.accounts;

await tokenController.setWithdrawableCoverNotes(member.address, 100);
await expect(memberRoles.connect(member).switchMembership(nonMember.address)).to.be.revertedWith(
'V1 coverNotesAmount != 0',
);
});

it('reverts when member has tokens staked for assessment', async function () {
const { memberRoles, assessment } = this.contracts;
const {
members: [member],
nonMembers: [nonMember],
} = this.accounts;

await assessment.setStakeOf(member.address, 100);
await expect(memberRoles.connect(member).switchMembership(nonMember.address)).to.be.revertedWith(
'Assessment stake != 0',
);
});

it('reverts when member has pending rewards in TokenController', async function () {
const { memberRoles, tokenController } = this.contracts;
const {
members: [member],
nonMembers: [nonMember],
} = this.accounts;

await tokenController.setPendingRewards(member.address, 100);
await expect(memberRoles.connect(member).switchMembership(nonMember.address)).to.be.revertedWith(
'TC pendingRewards != 0',
);
});

it('reverts when member system is paused', async function () {
const { memberRoles, master } = this.contracts;
const {
Expand Down
66 changes: 66 additions & 0 deletions test/unit/MemberRoles/withdrawMembership.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const { Role } = require('../utils').constants;
const { expect } = require('chai');
const { ethers } = require('hardhat');
const { formatBytes32String } = ethers.utils;

describe('withdrawMembership', function () {
it('reverts when withdrawing membership for non-member', async function () {
Expand All @@ -20,6 +22,70 @@ describe('withdrawMembership', function () {
await expect(memberRoles.connect(member).withdrawMembership()).to.be.reverted;
});

it('reverts when member has LegacyPooledStaking deposit tokens', async function () {
const { memberRoles, pooledStaking } = this.contracts;
const {
members: [member],
} = this.accounts;

await pooledStaking.setStakerDeposit(member.address, 100);
await expect(memberRoles.connect(member).withdrawMembership()).to.be.revertedWith('V1 stakerDeposit != 0');
});

it('reverts when member has LegacyPooledStaking reward tokens', async function () {
const { memberRoles, pooledStaking } = this.contracts;
const {
members: [member],
} = this.accounts;

await pooledStaking.setStakerReward(member.address, 100);
await expect(memberRoles.connect(member).withdrawMembership()).to.be.revertedWith('V1 stakerReward != 0');
});

it('reverts when member has tokens locked for claim assessment', async function () {
const { memberRoles, tokenController } = this.contracts;
const {
members: [member],
} = this.accounts;

await tokenController.setTokensLocked(member.address, formatBytes32String('CLA'), 100);
await expect(memberRoles.connect(member).withdrawMembership()).to.be.revertedWith('V1 CLA tokensLocked != 0');
});

it('reverts when member has withdrawable cover notes', async function () {
const { memberRoles, tokenController } = this.contracts;
const {
members: [member],
} = this.accounts;

await tokenController.setWithdrawableCoverNotes(member.address, 100);
await expect(memberRoles.connect(member).withdrawMembership()).to.be.revertedWith('V1 coverNotesAmount != 0');
});

it('reverts when member has tokens staked for assessment', async function () {
const { memberRoles, assessment } = this.contracts;
const {
members: [member],
} = this.accounts;

await assessment.setStakeOf(member.address, 100);
await expect(memberRoles.connect(member).withdrawMembership()).to.be.revertedWith(
'Assessment stake != 0',
);
});

it('reverts when member has pending rewards in TokenController', async function () {
const { memberRoles, tokenController } = this.contracts;
const {
members: [member],
} = this.accounts;

await tokenController.setPendingRewards(member.address, 100);
await expect(memberRoles.connect(member).withdrawMembership()).to.be.revertedWith(
'TC pendingRewards != 0',
);
});

it("removes member's the address from the whitelist", async function () {
const { memberRoles, tokenController } = this.contracts;
const {
Expand Down

0 comments on commit 6457d7a

Please sign in to comment.