Skip to content

Commit 3afe52c

Browse files
committed
test: token controller unit tests
1 parent 81d6040 commit 3afe52c

24 files changed

+1380
-28
lines changed

contracts/mocks/common/NXMTokenMock.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ contract NXMTokenMock is INXMToken, ERC20 {
6969
return true;
7070
}
7171

72-
function lockForMemberVote(address /*_of*/, uint /*_days*/) external {
73-
// noop
72+
function lockForMemberVote(address _member, uint lockTime) external {
73+
isLockedForMV[_member] = block.timestamp + lockTime;
7474
}
7575
}

contracts/mocks/modules/TokenController/TCMockAssessment.sol

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
3+
pragma solidity ^0.8.18;
4+
5+
import "../../generic/PoolGeneric.sol";
6+
7+
contract TCMockPool is PoolGeneric {
8+
function getTokenPrice() public override pure returns (uint) {
9+
return 1 ether;
10+
}
11+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
3+
pragma solidity ^0.8.18;
4+
5+
import "../../generic/RegistryGeneric.sol";
6+
7+
contract TCMockRegistry is RegistryGeneric {
8+
// contracts
9+
mapping(uint index => Contract) internal contracts;
10+
mapping(address contractAddress => uint index) internal contractIndexes;
11+
12+
SystemPause internal systemPause; // 3 slots
13+
14+
function addContract(uint index, address contractAddress, bool isProxy) external override {
15+
contracts[index] = Contract({addr: contractAddress, isProxy: isProxy});
16+
contractIndexes[contractAddress] = index;
17+
}
18+
19+
function getContractIndexByAddress(address contractAddress) external override view returns (uint) {
20+
return contractIndexes[contractAddress];
21+
}
22+
23+
function getContractAddressByIndex(uint index) external override view returns (address payable) {
24+
return payable(contracts[index].addr);
25+
}
26+
27+
function getPauseConfig() public override view returns (uint config) {
28+
return systemPause.config;
29+
}
30+
31+
function setPauseConfig(uint config) external {
32+
systemPause.config = uint48(config);
33+
}
34+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
const { expect } = require('chai');
2+
const { ethers } = require('hardhat');
3+
const { loadFixture, time } = require('@nomicfoundation/hardhat-network-helpers');
4+
const setup = require('./setup');
5+
6+
const poolId = 2n ** 95n; // overflows at uint96
7+
const maxDeadline = 2n ** 31n;
8+
9+
describe('acceptStakingPoolOwnershipOffer', function () {
10+
it('should revert if the caller is not the proposed manager', async function () {
11+
const fixture = await loadFixture(setup);
12+
const { tokenController } = fixture.contracts;
13+
14+
await expect(tokenController.acceptStakingPoolOwnershipOffer(poolId)).to.be.revertedWithCustomError(
15+
tokenController,
16+
'OnlyProposedManager',
17+
);
18+
});
19+
20+
it('should fail to accept a canceled offer', async function () {
21+
const fixture = await loadFixture(setup);
22+
const { tokenController } = fixture.contracts;
23+
const {
24+
members: [oldManager, newManager],
25+
stakingProducts: [stakingProducts],
26+
} = fixture.accounts;
27+
28+
// Set old manager
29+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, oldManager.address);
30+
31+
// Create offer
32+
await tokenController.connect(oldManager).createStakingPoolOwnershipOffer(poolId, newManager.address, maxDeadline);
33+
34+
// Cancel offer
35+
await tokenController.connect(oldManager).cancelStakingPoolOwnershipOffer(poolId);
36+
37+
await expect(
38+
tokenController.connect(newManager).acceptStakingPoolOwnershipOffer(poolId),
39+
).to.be.revertedWithCustomError(tokenController, 'OnlyProposedManager');
40+
});
41+
42+
it('should revert if the ownership offer has expired', async function () {
43+
const fixture = await loadFixture(setup);
44+
const { tokenController } = fixture.contracts;
45+
const {
46+
members: [oldManager, newManager],
47+
stakingProducts: [stakingProducts],
48+
} = fixture.accounts;
49+
50+
// Set old manager
51+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, oldManager.address);
52+
53+
const { timestamp: deadline } = await ethers.provider.getBlock('latest');
54+
// Create offer
55+
await tokenController.connect(oldManager).createStakingPoolOwnershipOffer(poolId, newManager.address, deadline + 2);
56+
57+
await time.increaseTo(deadline + 3);
58+
59+
await expect(
60+
tokenController.connect(newManager).acceptStakingPoolOwnershipOffer(poolId),
61+
).to.be.revertedWithCustomError(tokenController, 'OwnershipOfferHasExpired');
62+
});
63+
64+
it('should successfully remove pools from last manager and add them to new managers list', async function () {
65+
const fixture = await loadFixture(setup);
66+
const { tokenController } = fixture.contracts;
67+
const {
68+
members: [oldManager, newManager],
69+
stakingProducts: [stakingProducts],
70+
} = fixture.accounts;
71+
72+
// Set old manager
73+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, oldManager.address);
74+
75+
// Create offer
76+
await tokenController.connect(oldManager).createStakingPoolOwnershipOffer(poolId, newManager.address, maxDeadline);
77+
78+
// Accept offer
79+
await tokenController.connect(newManager).acceptStakingPoolOwnershipOffer(poolId);
80+
81+
// Check that new manager is assigned
82+
const stakingPools = await tokenController.getManagerStakingPools(newManager.address);
83+
expect(await tokenController.getStakingPoolManager(poolId)).to.be.equal(newManager.address);
84+
expect(stakingPools).to.be.deep.equal([poolId]);
85+
expect(stakingPools.length).to.be.equal(1);
86+
expect(await tokenController.isStakingPoolManager(newManager.address)).to.be.eq(true);
87+
88+
// Check that old manager is unassigned
89+
expect(await tokenController.getManagerStakingPools(oldManager.address)).to.be.deep.equal([]);
90+
expect(!(await tokenController.isStakingPoolManager(oldManager.address))).to.be.eq(true);
91+
92+
// Make sure the offer is removed
93+
expect(await tokenController.getStakingPoolOwnershipOffer(poolId)).to.be.deep.equal([ethers.ZeroAddress, 0]);
94+
});
95+
96+
it('should revert if current manager is locked for voting', async function () {
97+
const fixture = await loadFixture(setup);
98+
const { tokenController, nxm } = fixture.contracts;
99+
const {
100+
members: [oldManager, newManager],
101+
stakingProducts: [stakingProducts],
102+
} = fixture.accounts;
103+
104+
// Set old manager
105+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, oldManager.address);
106+
107+
// Create offer
108+
await tokenController.connect(oldManager).createStakingPoolOwnershipOffer(poolId, newManager.address, maxDeadline);
109+
110+
// Lock old manager
111+
await nxm.setLock(oldManager.address, 2n ** 30n);
112+
113+
await expect(
114+
tokenController.connect(newManager).acceptStakingPoolOwnershipOffer(poolId),
115+
).to.be.revertedWithCustomError(tokenController, 'ManagerIsLockedForVoting');
116+
});
117+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const { ethers } = require('hardhat');
2+
const { expect } = require('chai');
3+
const { loadFixture, impersonateAccount, setBalance } = require('@nomicfoundation/hardhat-network-helpers');
4+
const setup = require('./setup');
5+
6+
const { parseEther } = ethers;
7+
8+
describe('addToWhitelist', function () {
9+
it('reverts if caller is not an registry', async function () {
10+
const fixture = await loadFixture(setup);
11+
const { tokenController } = fixture.contracts;
12+
const [member] = fixture.accounts.members;
13+
14+
await expect(tokenController.addToWhitelist(member.address)).to.be.revertedWithCustomError(
15+
tokenController,
16+
'Unauthorized',
17+
);
18+
});
19+
20+
it('add member to white list', async function () {
21+
const fixture = await loadFixture(setup);
22+
const { tokenController, registry, nxm } = fixture.contracts;
23+
const [nonMember] = fixture.accounts.nonMembers;
24+
25+
await impersonateAccount(registry.target);
26+
const registrySigner = await ethers.provider.getSigner(registry.target);
27+
await setBalance(registry.target, parseEther('1'));
28+
29+
const whiteListedBefore = await nxm.whiteListed(nonMember.address);
30+
31+
await tokenController.connect(registrySigner).addToWhitelist(nonMember.address);
32+
33+
const whiteListedAfter = await nxm.whiteListed(nonMember.address);
34+
35+
expect(whiteListedBefore).to.be.equal(false);
36+
expect(whiteListedAfter).to.be.equal(true);
37+
});
38+
});
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
const { expect } = require('chai');
2+
const { ethers } = require('hardhat');
3+
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
4+
const setup = require('./setup');
5+
6+
const poolId = 2n ** 95n; // overflows at uint96
7+
8+
describe('assignStakingPoolManager', function () {
9+
it('should revert if not called from internal address', async function () {
10+
const fixture = await loadFixture(setup);
11+
const { tokenController } = fixture.contracts;
12+
13+
await expect(tokenController.assignStakingPoolManager(poolId, ethers.ZeroAddress)).to.be.revertedWithCustomError(
14+
tokenController,
15+
'Unauthorized',
16+
);
17+
});
18+
19+
it('should transfer a staking pool when there is a previous manager', async function () {
20+
const fixture = await loadFixture(setup);
21+
const { tokenController } = fixture.contracts;
22+
const {
23+
members: [oldManager, newManager],
24+
stakingProducts: [stakingProducts],
25+
} = fixture.accounts;
26+
27+
// Set old manager
28+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, oldManager.address);
29+
30+
// Set new manager
31+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, newManager.address);
32+
33+
const pools = await tokenController.getManagerStakingPools(newManager.address);
34+
expect(pools).to.be.deep.equal([poolId]);
35+
expect(pools.length).to.be.equal(1);
36+
37+
expect(await tokenController.getStakingPoolManager(poolId)).to.equal(newManager.address);
38+
expect(await tokenController.isStakingPoolManager(newManager.address)).to.be.equal(true);
39+
40+
expect(await tokenController.getManagerStakingPools(oldManager.address)).to.be.deep.equal([]);
41+
expect(await tokenController.isStakingPoolManager(oldManager.address)).to.be.equal(false);
42+
});
43+
44+
it('should transfer a staking pool when there is no previous manager', async function () {
45+
const fixture = await loadFixture(setup);
46+
const { tokenController } = fixture.contracts;
47+
const {
48+
members: [newManager],
49+
stakingProducts: [stakingProducts],
50+
} = fixture.accounts;
51+
52+
// Set new manager
53+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, newManager.address);
54+
55+
expect(await tokenController.getStakingPoolManager(poolId)).to.equal(newManager.address);
56+
expect(await tokenController.isStakingPoolManager(newManager.address)).to.be.equal(true);
57+
});
58+
59+
it('should transfer staking pools when the new owner is already a manager of another pool', async function () {
60+
const fixture = await loadFixture(setup);
61+
const { tokenController } = fixture.contracts;
62+
const {
63+
members: [oldManager, newManager],
64+
stakingProducts: [stakingProducts],
65+
} = fixture.accounts;
66+
67+
// Set old manager
68+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, oldManager.address);
69+
70+
// Set new manager
71+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId + 1n, newManager.address);
72+
73+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, newManager.address);
74+
75+
const pools = await tokenController.getManagerStakingPools(newManager.address);
76+
expect(pools).to.be.deep.equal([poolId + 1n, poolId]);
77+
expect(pools.length).to.be.equal(2);
78+
79+
expect(await tokenController.getStakingPoolManager(poolId + 1n)).to.equal(newManager.address);
80+
expect(await tokenController.getStakingPoolManager(poolId)).to.equal(newManager.address);
81+
expect(await tokenController.isStakingPoolManager(newManager.address)).to.be.equal(true);
82+
83+
expect(await tokenController.getManagerStakingPools(oldManager.address)).to.be.deep.equal([]);
84+
expect(await tokenController.isStakingPoolManager(oldManager.address)).to.be.equal(false);
85+
});
86+
87+
it('should transfer several staking pools to a new manager', async function () {
88+
const fixture = await loadFixture(setup);
89+
const { tokenController } = fixture.contracts;
90+
const {
91+
members: [oldManager, newManager],
92+
stakingProducts: [stakingProducts],
93+
} = fixture.accounts;
94+
95+
// Set old manager
96+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, oldManager.address);
97+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId + 1n, oldManager.address);
98+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId + 2n, oldManager.address);
99+
100+
// Set new manager
101+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId, newManager.address);
102+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId + 1n, newManager.address);
103+
await tokenController.connect(stakingProducts).assignStakingPoolManager(poolId + 2n, newManager.address);
104+
105+
const pools = await tokenController.getManagerStakingPools(newManager.address);
106+
expect(pools).to.be.deep.equal([poolId, poolId + 1n, poolId + 2n]);
107+
expect(pools.length).to.be.equal(3);
108+
109+
// New manager is the owner of all the pools
110+
expect(await tokenController.getStakingPoolManager(poolId)).to.equal(newManager.address);
111+
expect(await tokenController.getStakingPoolManager(poolId + 1n)).to.equal(newManager.address);
112+
expect(await tokenController.getStakingPoolManager(poolId + 2n)).to.equal(newManager.address);
113+
expect(await tokenController.isStakingPoolManager(newManager.address)).to.be.equal(true);
114+
115+
// Old manager is no longer staking pool manager
116+
expect(await tokenController.getManagerStakingPools(oldManager.address)).to.be.deep.equal([]);
117+
expect(await tokenController.isStakingPoolManager(oldManager.address)).to.be.equal(false);
118+
});
119+
});

test/unit/TokenController/burnFrom.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const { ethers } = require('hardhat');
2+
const { expect } = require('chai');
3+
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
4+
const setup = require('./setup');
5+
6+
const { parseEther } = ethers;
7+
8+
describe('burnFrom', function () {
9+
it('reverts if caller is not an internal contract', async function () {
10+
const fixture = await loadFixture(setup);
11+
const { tokenController } = fixture.contracts;
12+
const [member1] = fixture.accounts.members;
13+
14+
const amount = parseEther('10');
15+
await expect(tokenController.connect(member1).burnFrom(member1.address, amount)).to.be.revertedWithCustomError(
16+
tokenController,
17+
'Unauthorized',
18+
);
19+
});
20+
21+
it('burns nxm from member', async function () {
22+
const fixture = await loadFixture(setup);
23+
const { tokenController, nxm } = fixture.contracts;
24+
const [cover] = fixture.accounts.cover;
25+
const [member1] = fixture.accounts.members;
26+
27+
const amount = parseEther('10');
28+
await nxm.mint(member1.address, amount);
29+
await nxm.connect(member1).approve(tokenController, amount);
30+
const initialBalanceMember1 = await nxm.balanceOf(member1.address);
31+
32+
await tokenController.connect(cover).burnFrom(member1.address, amount);
33+
34+
const balanceMember1 = await nxm.balanceOf(member1.address);
35+
36+
expect(balanceMember1).to.equal(initialBalanceMember1 - amount);
37+
});
38+
});

0 commit comments

Comments
 (0)