Skip to content

Commit

Permalink
test: back to 100% coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
kupermind committed May 8, 2024
1 parent be9a80b commit 67f227f
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 12 deletions.
84 changes: 72 additions & 12 deletions contracts/VoteWeighting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ error NomineeAlreadyExists(bytes32 account, uint256 chainId);
/// @param nextAllowedVotingTime Next allowed voting time.
error VoteTooOften(address voter, uint256 curTime, uint256 nextAllowedVotingTime);

/// @dev Nominee is not in the removed nominee map.
/// @param account Nominee account address.
/// @param chainId Nominee chain Id.
error NomineeNotRemoved(bytes32 account, uint256 chainId);

/// @dev Nominee is in the removed nominee map.
/// @param account Nominee account address.
/// @param chainId Nominee chain Id.
error NomineeRemoved(bytes32 account, uint256 chainId);

struct Point {
uint256 bias;
uint256 slope;
Expand Down Expand Up @@ -91,6 +101,8 @@ contract VoteWeighting is IErrors {
Nominee[] public setNominees;
// Mapping of hash(Nominee struct) => nominee Id
mapping(bytes32 => uint256) public mapNomineeIds;
// Mapping of hash(Nominee struct) => previously removed nominee flag
mapping(bytes32 => bool) public mapRemovedNominees;

// user -> hash(Nominee struct) -> VotedSlope
mapping(address => mapping(bytes32 => VotedSlope)) public voteUserSlopes;
Expand Down Expand Up @@ -171,9 +183,9 @@ contract VoteWeighting is IErrors {
// Construct the nominee struct
Nominee memory nominee = Nominee(account, chainId);

// Check that the nominee exists
// Check that the nominee exists or has been removed
bytes32 nomineeHash = keccak256(abi.encode(nominee));
if (mapNomineeIds[nomineeHash] == 0) {
if (!mapRemovedNominees[nomineeHash] && mapNomineeIds[nomineeHash] == 0) {
revert NomineeDoesNotExist(account, chainId);
}

Expand Down Expand Up @@ -211,6 +223,12 @@ contract VoteWeighting is IErrors {
if (mapNomineeIds[nomineeHash] > 0) {
revert NomineeAlreadyExists(nominee.account, nominee.chainId);
}

// Check for the previously removed nominee
if (mapRemovedNominees[nomineeHash]) {
revert NomineeRemoved(nominee.account, nominee.chainId);
}

uint256 id = setNominees.length;
mapNomineeIds[nomineeHash] = id;
// Push the nominee into the list
Expand Down Expand Up @@ -363,6 +381,11 @@ contract VoteWeighting is IErrors {
// Get the nominee hash
bytes32 nomineeHash = keccak256(abi.encode(Nominee(account, chainId)));

// Check for the previously removed nominee
if (mapRemovedNominees[nomineeHash]) {
revert NomineeRemoved(account, chainId);
}

uint256 slope = uint256(uint128(IVEOLAS(ve).getLastUserPoint(msg.sender).slope));
uint256 lockEnd = IVEOLAS(ve).lockedEnd(msg.sender);
uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK;
Expand Down Expand Up @@ -477,6 +500,21 @@ contract VoteWeighting is IErrors {
revert NomineeDoesNotExist(account, chainId);
}

// Set nominee weight to zero
uint256 oldWeight = _getWeight(account, chainId);
uint256 oldSum = _getSum();
uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK;
pointsWeight[nomineeHash][nextTime].bias = 0;
timeWeight[nomineeHash] = nextTime;

// Account for the the sum weight change
uint256 newSum = oldSum - oldWeight;
pointsSum[nextTime].bias = newSum;
timeSum = nextTime;

// Add to the removed nominee map
mapRemovedNominees[nomineeHash] = true;

// Remove nominee from the map
mapNomineeIds[nomineeHash] = 0;
// Shuffle the current last nominee id in the set to be placed to the removed one
Expand All @@ -487,19 +525,41 @@ contract VoteWeighting is IErrors {
// Pop the last element from the set
setNominees.pop();

// Set nominee weight to zero
uint256 nextTime = (block.timestamp + WEEK) / WEEK * WEEK;
pointsWeight[nomineeHash][nextTime].bias = 0;
timeWeight[nomineeHash] = nextTime;
emit RemoveNominee(account, chainId, newSum);
}

uint256 oldWeight = _getWeight(account, chainId);
uint256 oldSum = _getSum();
pointsSum[nextTime].bias = oldSum - oldWeight;
timeSum = nextTime;
/// @dev Retrieves user voting power from a removed nominee.
/// @param account Address of the nominee in bytes32 form.
/// @param chainId Chain Id.
function retrieveRemovedNomineeVotingPower(bytes32 account, uint256 chainId) external {
// Get the nominee struct and hash
Nominee memory nominee = Nominee(account, chainId);
bytes32 nomineeHash = keccak256(abi.encode(nominee));

emit RemoveNominee(account, chainId, newSum);
// Check that the nominee is removed
if (!mapRemovedNominees[nomineeHash]) {
revert NomineeNotRemoved(account, chainId);
}

// Get the user old slope
VotedSlope memory oldSlope = voteUserSlopes[msg.sender][nomineeHash];
if (oldSlope.power == 0) {
revert ZeroValue();
}

// Cancel old slope changes if they still didn't happen
if (oldSlope.end > block.timestamp) {
changesWeight[nomineeHash][oldSlope.end] -= oldSlope.slope;
changesSum[oldSlope.end] -= oldSlope.slope;
}

// Update the voting power
uint256 powerUsed = voteUserPower[msg.sender];
powerUsed = powerUsed - oldSlope.power;
voteUserPower[msg.sender] = powerUsed;
delete voteUserSlopes[msg.sender][nomineeHash];
}

/// @dev Get current nominee weight.
/// @param account Address of the nominee in bytes32 form.
/// @param chainId Chain Id.
Expand Down
144 changes: 144 additions & 0 deletions test/VoteWeighting.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,28 @@ describe("Voting Escrow OLAS", function () {
const veAddress = await vw.ve();
expect(ve.address).to.equal(veAddress);
});

it("Changing owner", async function () {
const account = signers[1];

// Trying to change owner from a non-owner account address
await expect(
vw.connect(account).changeOwner(account.address)
).to.be.revertedWithCustomError(vw, "OwnerOnly");

// Trying to change owner for the zero address
await expect(
vw.connect(deployer).changeOwner(AddressZero)
).to.be.revertedWithCustomError(vw, "ZeroAddress");

// Changing the owner
await vw.connect(deployer).changeOwner(account.address);

// Trying to change owner from the previous owner address
await expect(
vw.connect(deployer).changeOwner(deployer.address)
).to.be.revertedWithCustomError(vw, "OwnerOnly");
});
});

context("Adding nominees", async function () {
Expand Down Expand Up @@ -532,5 +554,127 @@ describe("Voting Escrow OLAS", function () {
// Restore to the state of the snapshot
await snapshot.restore();
});

it("Remove nominee and retrieve voting power", async function () {
// Take a snapshot of the current state of the blockchain
const snapshot = await helpers.takeSnapshot();

// Lock one OLAS into veOLAS
await olas.approve(ve.address, oneOLASBalance);
await ve.createLock(oneOLASBalance, oneYear);

const numNominees = 2;
// Add nominees and get their bytes32 addresses
let nominees = [signers[1].address, signers[2].address];
for (let i = 0; i < numNominees; i++) {
await vw.addNomineeEVM(nominees[i], chainId);
nominees[i] = convertAddressToBytes32(nominees[i]);
}

// Vote for the first nominee
await vw.voteForNomineeWeights(nominees[0], chainId, maxVoteWeight);

// Get the set of nominees
let setNominees = await vw.getAllNominees();
// Check that the length is 3 (including the zero one)
expect(setNominees.length).to.equal(3);

// Get the first nominee id
let id = await vw.getNomineeId(nominees[0], chainId);
// The id must be equal to 1
expect(id).to.equal(1);
// Get the second nominee id
id = await vw.getNomineeId(nominees[1], chainId);
// The id must be equal to 2
expect(id).to.equal(2);

// Try to remove the nominee not by the owner
await expect(
vw.connect(signers[1]).removeNominee(nominees[0], chainId)
).to.be.revertedWithCustomError(vw, "OwnerOnly");

// Remove the nominee
await vw.removeNominee(nominees[0], chainId);

// Get the removed nominee Id
id = await vw.getNomineeId(nominees[0], chainId);
expect(id).to.equal(0);

// Try to remove the nominee again
await expect(
vw.removeNominee(nominees[0], chainId)
).to.be.revertedWithCustomError(vw, "NomineeDoesNotExist");

// Get the id for the second nominee that was shifted from 2 to 1
id = await vw.getNomineeId(nominees[1], chainId);
expect(id).to.equal(1);

// Try to add a removed nominee
await expect(
vw.addNomineeEVM(convertBytes32ToAddress(nominees[0]), chainId)
).to.be.revertedWithCustomError(vw, "NomineeRemoved");

// Try to vote for a removed nominee
await expect(
vw.voteForNomineeWeights(nominees[0], chainId, maxVoteWeight)
).to.be.revertedWithCustomError(vw, "NomineeRemoved");

// Checkpoint the nominee weight, which is still possible
await vw.checkpointNominee(nominees[0], chainId);

// Wait for two weeks
await helpers.time.increase(oneWeek * 2);

// Try to vote for the second nominee - fails because the voting power is not retrieved
await expect(
vw.voteForNomineeWeights(nominees[1], chainId, maxVoteWeight)
).to.be.revertedWithCustomError(vw, "Overflow");

// Retrieve the nominee voting power
await vw.retrieveRemovedNomineeVotingPower(nominees[0], chainId);

// Try to retrieve voting power from the same nominee that was already retrieved from
await expect(
vw.retrieveRemovedNomineeVotingPower(nominees[0], chainId)
).to.be.revertedWithCustomError(vw, "ZeroValue");

// Try to retrieve voting power from the nominee that was not removed
await expect(
vw.retrieveRemovedNomineeVotingPower(nominees[1], chainId)
).to.be.revertedWithCustomError(vw, "NomineeNotRemoved");

// Now it's possible to case a vote for another nominee
await vw.voteForNomineeWeights(nominees[1], chainId, maxVoteWeight);

// Checkpoint the nominee
await vw.checkpointNominee(nominees[1], chainId);
// The removed nominee has still some weighting power
let weight = await vw.getNomineeWeight(nominees[1], chainId);
expect(weight).to.gt(0);

// Remove the second nominee
await vw.removeNominee(nominees[1], chainId);

// After removing, the weight must be zero
weight = await vw.getNomineeWeight(nominees[1], chainId);
expect(weight).to.equal(0);

// Wait for two weeks
await helpers.time.increase(oneWeek * 2);

// Checkpoint the removed nominee and check its weight again that must be zero
await vw.checkpointNominee(nominees[1], chainId);
weight = await vw.getNomineeWeight(nominees[1], chainId);
expect(weight).to.equal(0);

// Wait until the lock expires
await helpers.time.increase(oneYear);

// Retrieve the second nominee voting power
await vw.retrieveRemovedNomineeVotingPower(nominees[1], chainId);

// Restore to the state of the snapshot
await snapshot.restore();
});
});
});

0 comments on commit 67f227f

Please sign in to comment.