Incorrect calculation about voting power
Description
In the function proposeMajorityMetavestAmendment
, the totalVotingPower
is accumulated from the governing power of the sets
. If the allocation contract's govType
is all
, the calculation uses the total token stream and the total milestone award, which is not changed during the proposal.
For other govType
values, the calculation uses the vested token amount, which can change during the proposal period.
function proposeMajorityMetavestAmendment(
string memory setName,
bytes4 _msgSig,
bytes calldata _callData
) external onlyAuthority {
//if the majority proposal is already pending and not expired, revert
if (functionToSetMajorityProposal[_msgSig][setName].isPending && block.timestamp > functionToSetMajorityProposal[_msgSig][setName].time)
revert MetaVesTController_AmendmentAlreadyPending();
uint256 totalVotingPower;
for (uint256 i; i < sets[setName].length; ++i) {
totalVotingPower += BaseAllocation(sets[setName][i]).getGoverningPower();
}
functionToSetMajorityProposal[_msgSig][setName] = MajorityAmendmentProposal(
totalVotingPower,
0,
block.timestamp,
true,
keccak256(_callData[_callData.length - 32:]),
new address[](0)
);
emit MetaVesTController_MajorityAmendmentProposed(setName, _msgSig, _callData);
}
Additionally, there is no check to verify if a _grant
is already in sets
or not. This allows any grant, whether in sets
or added after the proposal, to vote without its voting power being included in totalVotingPower
.
function voteOnMetavestAmendment(address _grant, string memory _setName, bytes4 _msgSig, bool _inFavor) external {
if(BaseAllocation(_grant).grantee() != msg.sender) revert MetaVesTController_OnlyGrantee();
if (!functionToSetMajorityProposal[_msgSig][_setName].isPending) revert MetaVesTController_NoPendingAmendment(_msgSig, _grant);
if (!_checkFunctionToTokenToAmendmentTime(_msgSig, _setName))
revert MetaVesTController_ProposedAmendmentExpired();
uint256 _callerPower = BaseAllocation(_grant).getGoverningPower();
metavestController.MajorityAmendmentProposal storage proposal = functionToSetMajorityProposal[_msgSig][_setName];
//check if the grant has already voted.
for (uint256 i; i < proposal.voters.length; ++i) {
if (proposal.voters[i] == _grant) revert MetaVesTController_AlreadyVoted();
}
//add the msg.sender's vote
if (_inFavor) {
proposal.voters.push(_grant);
proposal.currentVotingPower += _callerPower;
}
}
Impact
The voting power may change during the proposal, which can lead to an incorrect decision about the proposal.
Recommendations
Consider using a snapshot of the voting power to prevent the voting power from changing during the proposal.
Remediation
This issue has been acknowledged by MetaLeX Labs, Inc, and fixes were implemented in the following commits: