The function proposeMajorityMetavestAmendment cannot identify expired proposals
Description
If a majority proposal is not pending or has expired, the authority can propose a new one to override it.
The function proposeMajorityMetavestAmendment checks if the proposal is pending and the time is less than block.timestamp. If so, the transaction is reverted.
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();
// [...]
}The functionToSetMajorityProposal[_msgSig][setName].time is the proposal creation time. So, the statement block.timestamp > functionToSetMajorityProposal[_msgSig][setName].time will always return true, unless in the same block.
Impact
Because a proposal remains pending after its creation to update multiple grants, if the function cannot correctly identify an expired proposal, it cannot create a new proposal with the same msg.sig and the same set.
The following proof-of-concept script demonstrates that a transaction will be reverted even if the proposal has expired:
function testAuditProposeMajorityMetavestAmendmentExpire() public {
address mockAllocation = createDummyVestingAllocation();
bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)"));
bytes memory callData = abi.encodeWithSelector(msgSig, mockAllocation, true);
vm.prank(authority);
controller.addMetaVestToSet("testSet", mockAllocation);
vm.prank(authority);
controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData);
// proposal expired
uint256 AMENDMENT_TIME_LIMIT = 604800;
vm.warp(block.timestamp + AMENDMENT_TIME_LIMIT + 1);
vm.prank(authority);
vm.expectRevert(
metavestController
.MetaVesTController_AmendmentAlreadyPending
.selector
);
controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData);
}Recommendations
Consider using the statement block.timestamp < functionToSetMajorityProposal[_msgSig][setName].time + AMENDMENT_TIME_LIMIT to ensure the proposal has not expired.
Remediation
This issue has been acknowledged by MetaLeX Labs, Inc, and a fix was implemented in commit c62b8c25↗.