Assessment reports>Metavest>Medium findings>Incorrect calculation about voting power
Category: Business Logic

Incorrect calculation about voting power

Medium Severity
Medium Impact
Medium Likelihood

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:

Zellic © 2024Back to top ↑