From 30e76739eb42c207634c985467d4ae6ff0f06fc5 Mon Sep 17 00:00:00 2001 From: Erich Dylus <51535743+ErichDylus@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:50:00 -0400 Subject: [PATCH 1/8] add NatSpec comments and harmonize formatting --- src/MetaVesTController.sol | 668 ++++++++++++++++++++++++++----------- 1 file changed, 474 insertions(+), 194 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index ffd8ff9..da3a845 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -20,6 +20,8 @@ import "./lib/EnumberableSet.sol"; /** * @title MetaVesT Controller * + * @author MetaLeX Labs, Inc. + * * @notice Contract for a MetaVesT's authority to configure parameters, confirm milestones, and * other permissioned functions, with some powers checked by the applicable 'dao' or subject to consent * by an applicable affected grantee or a majority-in-governing power of similar token grantees @@ -59,16 +61,22 @@ contract metavestController is SafeTransferLib { mapping(address => uint256) voterPower; } - enum metavestType { Vesting, TokenOption, RestrictedTokenAward } + enum metavestType { + Vesting, + TokenOption, + RestrictedTokenAward + } /// @notice maps a function's signature to a Condition contract address mapping(bytes4 => address[]) public functionToConditions; /// @notice maps a metavest-parameter-updating function's signature to token contract to whether a majority amendment is pending - mapping(bytes4 => mapping(bytes32 => MajorityAmendmentProposal)) public functionToSetMajorityProposal; + mapping(bytes4 => mapping(bytes32 => MajorityAmendmentProposal)) + public functionToSetMajorityProposal; /// @notice maps a metavest-parameter-updating function's signature to affected grantee address to whether an amendment is pending - mapping(bytes4 => mapping(address => AmendmentProposal)) public functionToGranteeToAmendmentPending; + mapping(bytes4 => mapping(address => AmendmentProposal)) + public functionToGranteeToAmendmentPending; /// @notice tracks if an address has voted for an amendment by mapping a hash of the pertinent details to time they last voted for these details (voter, function and affected grantee) mapping(bytes32 => uint256) internal _lastVoted; @@ -79,17 +87,46 @@ contract metavestController is SafeTransferLib { /// EVENTS /// - event MetaVesTController_AmendmentConsentUpdated(bytes4 indexed msgSig, address indexed grantee, bool inFavor); - event MetaVesTController_AmendmentProposed(address indexed grant, bytes4 msgSig); + event MetaVesTController_AmendmentConsentUpdated( + bytes4 indexed msgSig, + address indexed grantee, + bool inFavor + ); + event MetaVesTController_AmendmentProposed( + address indexed grant, + bytes4 msgSig + ); event MetaVesTController_AuthorityUpdated(address indexed newAuthority); - event MetaVesTController_ConditionUpdated(address indexed condition, bytes4 functionSig); + event MetaVesTController_ConditionUpdated( + address indexed condition, + bytes4 functionSig + ); event MetaVesTController_DaoUpdated(address newDao); - event MetaVesTController_MajorityAmendmentProposed(string indexed set, bytes4 msgSig, bytes callData, uint256 totalVotingPower); - event MetaVesTController_MajorityAmendmentVoted(string indexed set, bytes4 msgSig, address grantee, bool inFavor, uint256 votingPower, uint256 currentVotingPower, uint256 totalVotingPower); + event MetaVesTController_MajorityAmendmentProposed( + string indexed set, + bytes4 msgSig, + bytes callData, + uint256 totalVotingPower + ); + event MetaVesTController_MajorityAmendmentVoted( + string indexed set, + bytes4 msgSig, + address grantee, + bool inFavor, + uint256 votingPower, + uint256 currentVotingPower, + uint256 totalVotingPower + ); event MetaVesTController_SetCreated(string indexed set); event MetaVesTController_SetRemoved(string indexed set); - event MetaVesTController_AddressAddedToSet(string set, address indexed grantee); - event MetaVesTController_AddressRemovedFromSet(string set, address indexed grantee); + event MetaVesTController_AddressAddedToSet( + string set, + address indexed grantee + ); + event MetaVesTController_AddressRemovedFromSet( + string set, + address indexed grantee + ); /// /// ERRORS @@ -110,7 +147,10 @@ contract metavestController is SafeTransferLib { error MetaVesTController_LengthMismatch(); error MetaVesTController_MetaVesTAlreadyExists(); error MetaVesTController_MilestoneIndexCompletedOrDoesNotExist(); - error MetaVesTController_NoPendingAmendment(bytes4 msgSig, address affectedGrantee); + error MetaVesTController_NoPendingAmendment( + bytes4 msgSig, + address affectedGrantee + ); error MetaVesTController_OnlyAuthority(); error MetaVesTController_OnlyDAO(); error MetaVesTController_OnlyPendingAuthority(); @@ -132,7 +172,13 @@ contract metavestController is SafeTransferLib { modifier conditionCheck() { address[] memory conditions = functionToConditions[msg.sig]; for (uint256 i; i < conditions.length; ++i) { - if (!IConditionM(conditions[i]).checkCondition(address(this), msg.sig, "")) { + if ( + !IConditionM(conditions[i]).checkCondition( + address(this), + msg.sig, + "" + ) + ) { revert MetaVesTController_ConditionNotSatisfied(conditions[i]); } } @@ -142,17 +188,26 @@ contract metavestController is SafeTransferLib { modifier consentCheck(address _grant, bytes calldata _data) { if (isMetavestInSet(_grant)) { bytes32 set = getSetOfMetavest(_grant); - MajorityAmendmentProposal storage proposal = functionToSetMajorityProposal[msg.sig][set]; - if(proposal.changeApplied[_grant]) revert MetaVesTController_AmendmentCanOnlyBeAppliedOnce(); - if (_data.length>32 && _data.length<69) - { - if (!proposal.isPending || proposal.totalVotingPower>proposal.currentVotingPower*2 || keccak256(_data[_data.length - 32:]) != proposal.dataHash ) { + MajorityAmendmentProposal + storage proposal = functionToSetMajorityProposal[msg.sig][set]; + if (proposal.changeApplied[_grant]) + revert MetaVesTController_AmendmentCanOnlyBeAppliedOnce(); + if (_data.length > 32 && _data.length < 69) { + if ( + !proposal.isPending || + proposal.totalVotingPower > + proposal.currentVotingPower * 2 || + keccak256(_data[_data.length - 32:]) != proposal.dataHash + ) { revert MetaVesTController_AmendmentNeitherMutualNorMajorityConsented(); } - } - else revert MetaVesTController_AmendmentNeitherMutualNorMajorityConsented(); + } else + revert MetaVesTController_AmendmentNeitherMutualNorMajorityConsented(); } else { - AmendmentProposal storage proposal = functionToGranteeToAmendmentPending[msg.sig][_grant]; + AmendmentProposal + storage proposal = functionToGranteeToAmendmentPending[msg.sig][ + _grant + ]; if (!proposal.inFavor || proposal.dataHash != keccak256(_data)) { revert MetaVesTController_AmendmentNeitherMutualNorMajorityConsented(); } @@ -170,10 +225,18 @@ contract metavestController is SafeTransferLib { _; } - /// @param _authority address of the authority who can call the functions in this contract and update each MetaVesT in '_metavest', such as a BORG - /// @param _dao DAO governance contract address which exercises control over ability of 'authority' to call certain functions via imposing - /// conditions through 'updateFunctionCondition'. - constructor(address _authority, address _dao, address _vestingFactory, address _tokenOptionFactory, address _restrictedTokenFactory) { + /// @param _authority address of the authority who can call the functions in this contract and update each MetaVesT, such as a BORG + /// @param _dao DAO governance contract address which exercises control over ability of 'authority' to call certain functions via imposing conditions through 'updateFunctionCondition'. + /// @param _vestingFactory vesting allocation factory (VestingAllocationFactory.sol) contract address + /// @param _tokenOptionFactory token option factory (TokenOptionFactory.sol) contract address + /// @param _restrictedTokenFactory restricted token award factory (RestrictedTokenFactory.sol) contract address + constructor( + address _authority, + address _dao, + address _vestingFactory, + address _tokenOptionFactory, + address _restrictedTokenFactory + ) { if (_authority == address(0)) revert MetaVesTController_ZeroAddress(); authority = _authority; vestingFactory = _vestingFactory; @@ -183,34 +246,54 @@ contract metavestController is SafeTransferLib { } /// @notice for a grantee to consent to an update to one of their metavestDetails by 'authority' corresponding to the applicable function in this controller + /// @param _grant address of grantee providing (or revoking) consent /// @param _msgSig function signature of the function in this controller which (if successfully executed) will execute the grantee's metavest detail update /// @param _inFavor whether msg.sender consents to the applicable amending function call (rather than assuming true, this param allows a grantee to later revoke decision should 'authority' delay or breach agreement elsewhere) - function consentToMetavestAmendment(address _grant, bytes4 _msgSig, bool _inFavor) external { - if (!functionToGranteeToAmendmentPending[_msgSig][_grant].isPending) + function consentToMetavestAmendment( + address _grant, + bytes4 _msgSig, + bool _inFavor + ) external { + if (!functionToGranteeToAmendmentPending[_msgSig][_grant].isPending) revert MetaVesTController_NoPendingAmendment(_msgSig, _grant); - address grantee =BaseAllocation(_grant).grantee(); - if(msg.sender!= grantee) revert MetaVesTController_OnlyGranteeMayCall(); + address grantee = BaseAllocation(_grant).grantee(); + if (msg.sender != grantee) + revert MetaVesTController_OnlyGranteeMayCall(); functionToGranteeToAmendmentPending[_msgSig][_grant].inFavor = _inFavor; - emit MetaVesTController_AmendmentConsentUpdated(_msgSig, msg.sender, _inFavor); + emit MetaVesTController_AmendmentConsentUpdated( + _msgSig, + msg.sender, + _inFavor + ); } /// @notice enables the DAO to toggle whether a function requires Condition contract calls (enabling time delays, signature conditions, etc.) /// @dev see https://github.com/MetaLex-Tech/BORG-CORE/tree/main/src/libs/conditions for condition options; note this mechanic requires all conditions satisfied, but logic within such conditions is flexible /// @param _condition address of the applicable Condition contract-- pass address(0) to remove the requirement for '_functionSig' /// @param _functionSig signature of the function which is having its condition requirement updated - function updateFunctionCondition(address _condition, bytes4 _functionSig) external onlyDao { + function updateFunctionCondition( + address _condition, + bytes4 _functionSig + ) external onlyDao { //call check condition to ensure the condition is valid IConditionM(_condition).checkCondition(address(this), msg.sig, ""); //check to ensure the condition is unique for (uint256 i; i < functionToConditions[_functionSig].length; ++i) { - if (functionToConditions[_functionSig][i] == _condition) revert MetaVestController_DuplicateCondition(); + if (functionToConditions[_functionSig][i] == _condition) + revert MetaVestController_DuplicateCondition(); } functionToConditions[_functionSig].push(_condition); emit MetaVesTController_ConditionUpdated(_condition, _functionSig); } - function removeFunctionCondition(address _condition, bytes4 _functionSig) external onlyDao { + /// @notice enables the DAO to remove a function condition + /// @param _condition address of the applicable Condition contract + /// @param _functionSig signature of the function which is having its condition requirement removed + function removeFunctionCondition( + address _condition, + bytes4 _functionSig + ) external onlyDao { address[] storage conditions = functionToConditions[_functionSig]; for (uint256 i; i < conditions.length; ++i) { if (conditions[i] == _condition) { @@ -222,76 +305,120 @@ contract metavestController is SafeTransferLib { emit MetaVesTController_ConditionUpdated(_condition, _functionSig); } - function createMetavest(metavestType _type, address _grantee, BaseAllocation.Allocation calldata _allocation, BaseAllocation.Milestone[] calldata _milestones, uint256 _exercisePrice, address _paymentToken, uint256 _shortStopDuration, uint256 _longStopDate) external onlyAuthority conditionCheck returns (address) - { + /// @notice allows `authority` to create a new MetaVesT for `_grantee` via the applicable factory contract + /// @param _type metavestType enum corresponding to a vesting, token option, or restricted token award metavest that is being created + /// @param _grantee address of the grantee receiving the MetaVesT + /// @param _allocation BaseAllocation.Allocation struct details of the allocation applicable to this grantee + /// @param _milestones array of BaseAllocation.Milestone structs, setting out the milestones (if any) for this grantee + /// @param _exercisePrice if _type == TokenOption, the token option exercise price; If _type == RestrictedToken, this corresponds to the _repurchasePrice + /// @param _paymentToken contract address for the token used to pay for token option exercises (for grantee, if a token option) or restricted token repurchases (for authority, if a restricted token award) + /// @param _shortStopDuration if _type == TokenOption, length of period before vesting stop time and exercise deadline; if _type == RestrictedToken, length of period before lapse stop time and repurchase deadline + function createMetavest( + metavestType _type, + address _grantee, + BaseAllocation.Allocation calldata _allocation, + BaseAllocation.Milestone[] calldata _milestones, + uint256 _exercisePrice, + address _paymentToken, + uint256 _shortStopDuration + ) external onlyAuthority conditionCheck returns (address) { address newMetavest; - if(_type == metavestType.Vesting) - { - newMetavest = createVestingAllocation(_grantee, _allocation, _milestones); - } - else if(_type == metavestType.TokenOption) - { - newMetavest = createTokenOptionAllocation(_grantee, _exercisePrice, _paymentToken, _shortStopDuration, _allocation, _milestones); - } - else if(_type == metavestType.RestrictedTokenAward) - { - newMetavest = createRestrictedTokenAward(_grantee, _exercisePrice, _paymentToken, _shortStopDuration, _allocation, _milestones); - } - else - { + if (_type == metavestType.Vesting) { + newMetavest = createVestingAllocation( + _grantee, + _allocation, + _milestones + ); + } else if (_type == metavestType.TokenOption) { + newMetavest = createTokenOptionAllocation( + _grantee, + _exercisePrice, + _paymentToken, + _shortStopDuration, + _allocation, + _milestones + ); + } else if (_type == metavestType.RestrictedTokenAward) { + newMetavest = createRestrictedTokenAward( + _grantee, + _exercisePrice, + _paymentToken, + _shortStopDuration, + _allocation, + _milestones + ); + } else { revert MetaVesTController_IncorrectMetaVesTType(); } return newMetavest; } - + /// @notice validates certain metavest parameters as being nonzero function validateInputParameters( address _grantee, address _paymentToken, uint256 _exercisePrice, VestingAllocation.Allocation calldata _allocation ) internal pure { - if (_grantee == address(0) || _allocation.tokenContract == address(0) || _paymentToken == address(0) || _exercisePrice == 0) - revert MetaVesTController_ZeroAddress(); + if ( + _grantee == address(0) || + _allocation.tokenContract == address(0) || + _paymentToken == address(0) || + _exercisePrice == 0 + ) revert MetaVesTController_ZeroAddress(); } - function validateAllocation(VestingAllocation.Allocation calldata _allocation) internal pure { + /// @notice validates allocation cliff details + function validateAllocation( + VestingAllocation.Allocation calldata _allocation + ) internal pure { if ( _allocation.vestingCliffCredit > _allocation.tokenStreamTotal || _allocation.unlockingCliffCredit > _allocation.tokenStreamTotal ) revert MetaVesTController_CliffGreaterThanTotal(); } + /// @notice validates milestones as being proper in length and nonzero in amount function validateAndCalculateMilestones( VestingAllocation.Milestone[] calldata _milestones ) internal pure returns (uint256 _milestoneTotal) { if (_milestones.length != 0) { - if (_milestones.length > ARRAY_LENGTH_LIMIT) revert MetaVesTController_LengthMismatch(); + if (_milestones.length > ARRAY_LENGTH_LIMIT) + revert MetaVesTController_LengthMismatch(); for (uint256 i; i < _milestones.length; ++i) { - if (_milestones[i].conditionContracts.length > ARRAY_LENGTH_LIMIT) - revert MetaVesTController_LengthMismatch(); - if (_milestones[i].milestoneAward == 0) revert MetaVesTController_ZeroAmount(); + if ( + _milestones[i].conditionContracts.length > + ARRAY_LENGTH_LIMIT + ) revert MetaVesTController_LengthMismatch(); + if (_milestones[i].milestoneAward == 0) + revert MetaVesTController_ZeroAmount(); _milestoneTotal += _milestones[i].milestoneAward; } } } - function validateTokenApprovalAndBalance(address tokenContract, uint256 total) internal view { + /// @notice validates token approvals and balances of authority + function validateTokenApprovalAndBalance( + address tokenContract, + uint256 total + ) internal view { if ( - IERC20M(tokenContract).allowance(authority, address(this)) < total || + IERC20M(tokenContract).allowance(authority, address(this)) < + total || IERC20M(tokenContract).balanceOf(authority) < total ) revert MetaVesTController_AmountNotApprovedForTransferFrom(); } - function createAndInitializeTokenOptionAllocation( - address _grantee, - address _paymentToken, - uint256 _exercisePrice, - uint256 _shortStopDuration, - VestingAllocation.Allocation calldata _allocation, - VestingAllocation.Milestone[] calldata _milestones - ) internal returns (address) { - return IAllocationFactory(tokenOptionFactory).createAllocation( + function createAndInitializeTokenOptionAllocation( + address _grantee, + address _paymentToken, + uint256 _exercisePrice, + uint256 _shortStopDuration, + VestingAllocation.Allocation calldata _allocation, + VestingAllocation.Milestone[] calldata _milestones + ) internal returns (address) { + return + IAllocationFactory(tokenOptionFactory).createAllocation( IAllocationFactory.AllocationType.TokenOption, _grantee, address(this), @@ -301,17 +428,18 @@ contract metavestController is SafeTransferLib { _exercisePrice, _shortStopDuration ); - } + } - function createAndInitializeRestrictedTokenAward( - address _grantee, - address _paymentToken, - uint256 _repurchasePrice, - uint256 _shortStopDuration, - VestingAllocation.Allocation calldata _allocation, - VestingAllocation.Milestone[] calldata _milestones - ) internal returns (address) { - return IAllocationFactory(restrictedTokenFactory).createAllocation( + function createAndInitializeRestrictedTokenAward( + address _grantee, + address _paymentToken, + uint256 _repurchasePrice, + uint256 _shortStopDuration, + VestingAllocation.Allocation calldata _allocation, + VestingAllocation.Milestone[] calldata _milestones + ) internal returns (address) { + return + IAllocationFactory(restrictedTokenFactory).createAllocation( IAllocationFactory.AllocationType.RestrictedToken, _grantee, address(this), @@ -321,10 +449,14 @@ contract metavestController is SafeTransferLib { _repurchasePrice, _shortStopDuration ); - } - + } - function createVestingAllocation(address _grantee, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal returns (address){ + /// @notice creates a new vesting allocation MetaVesT for `_grantee` + function createVestingAllocation( + address _grantee, + VestingAllocation.Allocation calldata _allocation, + VestingAllocation.Milestone[] calldata _milestones + ) internal returns (address) { //hard code values not to trigger the failure for the 2 parameters that don't matter for this type of allocation validateInputParameters(_grantee, address(this), 1, _allocation); validateAllocation(_allocation); @@ -334,31 +466,49 @@ contract metavestController is SafeTransferLib { if (_total == 0) revert MetaVesTController_ZeroAmount(); validateTokenApprovalAndBalance(_allocation.tokenContract, _total); - address vestingAllocation = IAllocationFactory(vestingFactory).createAllocation( - IAllocationFactory.AllocationType.Vesting, - _grantee, - address(this), - _allocation, - _milestones, - address(0), - 0, - 0 + address vestingAllocation = IAllocationFactory(vestingFactory) + .createAllocation( + IAllocationFactory.AllocationType.Vesting, + _grantee, + address(this), + _allocation, + _milestones, + address(0), + 0, + 0 + ); + safeTransferFrom( + _allocation.tokenContract, + authority, + vestingAllocation, + _total ); - safeTransferFrom(_allocation.tokenContract, authority, vestingAllocation, _total); return vestingAllocation; } - function createTokenOptionAllocation(address _grantee, uint256 _exercisePrice, address _paymentToken, uint256 _shortStopDuration, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal conditionCheck returns (address) { - - validateInputParameters(_grantee, _paymentToken, _exercisePrice, _allocation); + /// @notice creates a new token option MetaVesT for `_grantee` + function createTokenOptionAllocation( + address _grantee, + uint256 _exercisePrice, + address _paymentToken, + uint256 _shortStopDuration, + VestingAllocation.Allocation calldata _allocation, + VestingAllocation.Milestone[] calldata _milestones + ) internal conditionCheck returns (address) { + validateInputParameters( + _grantee, + _paymentToken, + _exercisePrice, + _allocation + ); validateAllocation(_allocation); uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; if (_total == 0) revert MetaVesTController_ZeroAmount(); validateTokenApprovalAndBalance(_allocation.tokenContract, _total); - + address tokenOptionAllocation = createAndInitializeTokenOptionAllocation( _grantee, _paymentToken, @@ -368,39 +518,66 @@ contract metavestController is SafeTransferLib { _milestones ); - safeTransferFrom(_allocation.tokenContract, authority, tokenOptionAllocation, _total); - return tokenOptionAllocation; - } + safeTransferFrom( + _allocation.tokenContract, + authority, + tokenOptionAllocation, + _total + ); + return tokenOptionAllocation; + } - function createRestrictedTokenAward(address _grantee, uint256 _repurchasePrice, address _paymentToken, uint256 _shortStopDuration, VestingAllocation.Allocation calldata _allocation, VestingAllocation.Milestone[] calldata _milestones) internal conditionCheck returns (address){ - validateInputParameters(_grantee, _paymentToken, _repurchasePrice, _allocation); - validateAllocation(_allocation); - uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); + /// @notice creates a new restricted token award MetaVesT for `_grantee` + function createRestrictedTokenAward( + address _grantee, + uint256 _repurchasePrice, + address _paymentToken, + uint256 _shortStopDuration, + VestingAllocation.Allocation calldata _allocation, + VestingAllocation.Milestone[] calldata _milestones + ) internal conditionCheck returns (address) { + validateInputParameters( + _grantee, + _paymentToken, + _repurchasePrice, + _allocation + ); + validateAllocation(_allocation); + uint256 _milestoneTotal = validateAndCalculateMilestones(_milestones); - uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; - if (_total == 0) revert MetaVesTController_ZeroAmount(); - validateTokenApprovalAndBalance(_allocation.tokenContract, _total); + uint256 _total = _allocation.tokenStreamTotal + _milestoneTotal; + if (_total == 0) revert MetaVesTController_ZeroAmount(); + validateTokenApprovalAndBalance(_allocation.tokenContract, _total); - address restrictedTokenAward = createAndInitializeRestrictedTokenAward( - _grantee, - _paymentToken, - _repurchasePrice, - _shortStopDuration, - _allocation, - _milestones - ); + address restrictedTokenAward = createAndInitializeRestrictedTokenAward( + _grantee, + _paymentToken, + _repurchasePrice, + _shortStopDuration, + _allocation, + _milestones + ); - safeTransferFrom(_allocation.tokenContract, authority, restrictedTokenAward, _total); - return restrictedTokenAward; - } - + safeTransferFrom( + _allocation.tokenContract, + authority, + restrictedTokenAward, + _total + ); + return restrictedTokenAward; + } + + /// @notice getter function to return a grantee's vesting type + /// @param _grant grantee's address function getMetaVestType(address _grant) public view returns (uint256) { return BaseAllocation(_grant).getVestingType(); } /// @notice for 'authority' to withdraw tokens from this controller (i.e. which it has withdrawn from 'metavest', typically 'paymentToken') /// @param _tokenContract contract address of the token which is being withdrawn - function withdrawFromController(address _tokenContract) external onlyAuthority { + function withdrawFromController( + address _tokenContract + ) external onlyAuthority { uint256 _balance = IERC20M(_tokenContract).balanceOf(address(this)); if (_balance == 0) revert MetaVesTController_ZeroAmount(); @@ -427,7 +604,8 @@ contract metavestController is SafeTransferLib { ) external onlyAuthority conditionCheck consentCheck(_grant, msg.data) { if (_newPrice == 0) revert MetaVesTController_ZeroPrice(); IPriceAllocation grant = IPriceAllocation(_grant); - if(grant.getVestingType()!=2 && grant.getVestingType()!=3) revert MetaVesTController_IncorrectMetaVesTType(); + if (grant.getVestingType() != 2 && grant.getVestingType() != 3) + revert MetaVesTController_IncorrectMetaVesTType(); _resetAmendmentParams(_grant, msg.sig); grant.updatePrice(_newPrice); } @@ -440,27 +618,43 @@ contract metavestController is SafeTransferLib { uint256 _milestoneIndex ) external onlyAuthority conditionCheck consentCheck(_grant, msg.data) { _resetAmendmentParams(_grant, msg.sig); - (uint256 milestoneAward, , bool completed) = BaseAllocation(_grant).milestones(_milestoneIndex); - if(completed || milestoneAward == 0) revert MetaVesTController_MilestoneIndexCompletedOrDoesNotExist(); + (uint256 milestoneAward, , bool completed) = BaseAllocation(_grant) + .milestones(_milestoneIndex); + if (completed || milestoneAward == 0) + revert MetaVesTController_MilestoneIndexCompletedOrDoesNotExist(); BaseAllocation(_grant).removeMilestone(_milestoneIndex); } /// @notice add a milestone for a '_grantee' (and any transferees) and transfer the milestoneAward amount of tokens /// @param _grant address of grantee whose MetaVesT is being updated /// @param _milestone new Milestone struct added for '_grant', to be added to their 'milestones' array - function addMetavestMilestone(address _grant, VestingAllocation.Milestone calldata _milestone) external onlyAuthority { - - address _tokenContract = BaseAllocation(_grant).getMetavestDetails().tokenContract; - if (_milestone.milestoneAward == 0) revert MetaVesTController_ZeroAmount(); - if (_milestone.conditionContracts.length > ARRAY_LENGTH_LIMIT) revert MetaVesTController_LengthMismatch(); - if (_milestone.complete == true) revert MetaVesTController_MilestoneIndexCompletedOrDoesNotExist(); + function addMetavestMilestone( + address _grant, + VestingAllocation.Milestone calldata _milestone + ) external onlyAuthority { + address _tokenContract = BaseAllocation(_grant) + .getMetavestDetails() + .tokenContract; + if (_milestone.milestoneAward == 0) + revert MetaVesTController_ZeroAmount(); + if (_milestone.conditionContracts.length > ARRAY_LENGTH_LIMIT) + revert MetaVesTController_LengthMismatch(); + if (_milestone.complete == true) + revert MetaVesTController_MilestoneIndexCompletedOrDoesNotExist(); if ( - IERC20M(_tokenContract).allowance(msg.sender, address(this)) < _milestone.milestoneAward || - IERC20M(_tokenContract).balanceOf(msg.sender) < _milestone.milestoneAward + IERC20M(_tokenContract).allowance(msg.sender, address(this)) < + _milestone.milestoneAward || + IERC20M(_tokenContract).balanceOf(msg.sender) < + _milestone.milestoneAward ) revert MetaVesT_AmountNotApprovedForTransferFrom(); // send the new milestoneAward to 'metavest' - safeTransferFrom(_tokenContract, msg.sender, _grant, _milestone.milestoneAward); + safeTransferFrom( + _tokenContract, + msg.sender, + _grant, + _milestone.milestoneAward + ); BaseAllocation(_grant).addMilestone(_milestone); } @@ -484,8 +678,10 @@ contract metavestController is SafeTransferLib { uint160 _unlockRate ) external onlyAuthority conditionCheck { //get unlock rate from the _grant - (,,,,,uint160 unlockRate,,) = BaseAllocation(_grant).allocation(); - if(BaseAllocation(_grant).terminated() == false || unlockRate != 0) revert MetaVesTController_EmergencyUnlockNotSatisfied(); + (, , , , , uint160 unlockRate, , ) = BaseAllocation(_grant) + .allocation(); + if (BaseAllocation(_grant).terminated() == false || unlockRate != 0) + revert MetaVesTController_EmergencyUnlockNotSatisfied(); BaseAllocation(_grant).updateUnlockRate(_unlockRate); } @@ -516,12 +712,20 @@ contract metavestController is SafeTransferLib { /// @notice for 'authority' to irrevocably terminate (stop) this '_grantee''s vesting (including transferees), but preserving the unlocking schedule for any already-vested tokens, so their MetaVesT is not deleted /// @dev returns unvested remainder to 'authority' but preserves MetaVesT for all vested tokens up until call. To temporarily/revocably stop vesting, use 'updateVestingRate' /// @param _grant: address of grantee whose MetaVesT's vesting is being stopped - function terminateMetavestVesting(address _grant) external onlyAuthority conditionCheck { + function terminateMetavestVesting( + address _grant + ) external onlyAuthority conditionCheck { _resetAmendmentParams(_grant, msg.sig); BaseAllocation(_grant).terminate(); } - function setMetaVestGovVariables(address _grant, BaseAllocation.GovType _govType) external onlyAuthority consentCheck(_grant, msg.data){ + /// @notice for `authority` to set the `GovType` for a grantee's MetaVesT + /// @param _grant the applicable grantee's address + /// @param _govType BaseAllocation.GovType struct corresponding to the governance for this grantee's MetaVesT + function setMetaVestGovVariables( + address _grant, + BaseAllocation.GovType _govType + ) external onlyAuthority consentCheck(_grant, msg.data) { _resetAmendmentParams(_grant, msg.sig); BaseAllocation(_grant).setGovVariables(_govType); } @@ -529,15 +733,19 @@ contract metavestController is SafeTransferLib { /// @notice allows the 'authority' to propose a replacement to their address. First step in two-step address change, as '_newAuthority' will subsequently need to call 'acceptAuthorityRole()' /// @dev use care in updating 'authority' as it must have the ability to call 'acceptAuthorityRole()', or once it needs to be replaced, 'updateAuthority()' /// @param _newAuthority new address for pending 'authority', who must accept the role by calling 'acceptAuthorityRole' - function initiateAuthorityUpdate(address _newAuthority) external onlyAuthority { - if (_newAuthority == address(0)) revert MetaVesTController_ZeroAddress(); + function initiateAuthorityUpdate( + address _newAuthority + ) external onlyAuthority { + if (_newAuthority == address(0)) + revert MetaVesTController_ZeroAddress(); _pendingAuthority = _newAuthority; } /// @notice allows the pending new authority to accept the role transfer /// @dev access restricted to the address stored as '_pendingauthority' to accept the two-step change. Transfers 'authority' role to the caller (reflected in 'metavest') and deletes '_pendingauthority' to reset. function acceptAuthorityRole() external { - if (msg.sender != _pendingAuthority) revert MetaVesTController_OnlyPendingAuthority(); + if (msg.sender != _pendingAuthority) + revert MetaVesTController_OnlyPendingAuthority(); delete _pendingAuthority; authority = msg.sender; emit MetaVesTController_AuthorityUpdated(msg.sender); @@ -555,7 +763,8 @@ contract metavestController is SafeTransferLib { /// @dev access restricted to the address stored as '_pendingDao' to accept the two-step change. Transfers 'dao' role to the caller (reflected in 'metavest') and deletes '_pendingDao' to reset. /// no 'conditionCheck' necessary as it more properly contained in 'initiateAuthorityUpdate' function acceptDaoRole() external { - if (msg.sender != _pendingDao) revert MetaVesTController_OnlyPendingDao(); + if (msg.sender != _pendingDao) + revert MetaVesTController_OnlyPendingDao(); delete _pendingDao; dao = msg.sender; emit MetaVesTController_DaoUpdated(msg.sender); @@ -564,46 +773,51 @@ contract metavestController is SafeTransferLib { /// @notice for 'authority' to propose a metavest detail amendment /// @param _grant address of the grantee whose metavest is being updated /// @param _msgSig function signature of the function in this controller which (if successfully executed) will execute the metavest detail update + /// @param _callData data corresponding to the proposed amendment function proposeMetavestAmendment( address _grant, bytes4 _msgSig, bytes memory _callData ) external onlyAuthority { - //override existing amendment if it exists - functionToGranteeToAmendmentPending[_msgSig][_grant] = AmendmentProposal( - true, - keccak256(_callData), - false - ); + //override existing amendment if it exists + functionToGranteeToAmendmentPending[_msgSig][ + _grant + ] = AmendmentProposal(true, keccak256(_callData), false); emit MetaVesTController_AmendmentProposed(_grant, _msgSig); } - /// @notice for 'authority' to propose a metavest detail amendment + /// @notice for 'authority' to propose a metavest detail amendment /// @param setName name of the set for majority set amendment proposal /// @param _msgSig function signature of the function in this controller which (if successfully executed) will execute the metavest detail update - /// @param _callData data for the amendement + /// @param _callData data for the amendment function proposeMajorityMetavestAmendment( string memory setName, bytes4 _msgSig, bytes calldata _callData ) external onlyAuthority { - if(!doesSetExist(setName)) revert MetaVesTController_SetDoesNotExist(); - if(_callData.length!=68) revert MetaVesTController_LengthMismatch(); + if (!doesSetExist(setName)) revert MetaVesTController_SetDoesNotExist(); + if (_callData.length != 68) revert MetaVesTController_LengthMismatch(); bytes32 nameHash = keccak256(bytes(setName)); //if the majority proposal is already pending and not expired, revert - if ((functionToSetMajorityProposal[_msgSig][nameHash].isPending && block.timestamp < functionToSetMajorityProposal[_msgSig][nameHash].time + AMENDMENT_TIME_LIMIT) || setMajorityVoteActive[nameHash]) - revert MetaVesTController_AmendmentAlreadyPending(); - - MajorityAmendmentProposal storage proposal = functionToSetMajorityProposal[_msgSig][nameHash]; + if ( + (functionToSetMajorityProposal[_msgSig][nameHash].isPending && + block.timestamp < + functionToSetMajorityProposal[_msgSig][nameHash].time + + AMENDMENT_TIME_LIMIT) || setMajorityVoteActive[nameHash] + ) revert MetaVesTController_AmendmentAlreadyPending(); + + MajorityAmendmentProposal + storage proposal = functionToSetMajorityProposal[_msgSig][nameHash]; proposal.isPending = true; proposal.dataHash = keccak256(_callData[_callData.length - 32:]); proposal.time = block.timestamp; proposal.voters = new address[](0); proposal.currentVotingPower = 0; - + uint256 totalVotingPower; for (uint256 i; i < sets[nameHash].length(); ++i) { - uint256 _votingPower = BaseAllocation(sets[nameHash].at(i)).getMajorityVotingPower(); + uint256 _votingPower = BaseAllocation(sets[nameHash].at(i)) + .getMajorityVotingPower(); totalVotingPower += _votingPower; proposal.voterPower[sets[nameHash].at(i)] = _votingPower; proposal.changeApplied[sets[nameHash].at(i)] = false; @@ -611,54 +825,85 @@ contract metavestController is SafeTransferLib { proposal.totalVotingPower = totalVotingPower; setMajorityVoteActive[nameHash] = true; - emit MetaVesTController_MajorityAmendmentProposed(setName, _msgSig, _callData, totalVotingPower); + emit MetaVesTController_MajorityAmendmentProposed( + setName, + _msgSig, + _callData, + totalVotingPower + ); } /// @notice for 'authority' to cancel a metavest majority amendment /// @param _setName name of the set for majority set amendment proposal /// @param _msgSig function signature of the function in this controller which (if successfully executed) will execute the metavest detail update - function cancelExpiredMajorityMetavestAmendment(string memory _setName, bytes4 _msgSig) external onlyAuthority { - if(!doesSetExist(_setName)) revert MetaVesTController_SetDoesNotExist(); + function cancelExpiredMajorityMetavestAmendment( + string memory _setName, + bytes4 _msgSig + ) external onlyAuthority { + if (!doesSetExist(_setName)) + revert MetaVesTController_SetDoesNotExist(); bytes32 nameHash = keccak256(bytes(_setName)); - if (!setMajorityVoteActive[nameHash] || block.timestamp < functionToSetMajorityProposal[_msgSig][nameHash].time + AMENDMENT_TIME_LIMIT) revert MetaVesTController_AmendmentCannotBeCanceled(); + if ( + !setMajorityVoteActive[nameHash] || + block.timestamp < + functionToSetMajorityProposal[_msgSig][nameHash].time + + AMENDMENT_TIME_LIMIT + ) revert MetaVesTController_AmendmentCannotBeCanceled(); setMajorityVoteActive[nameHash] = false; } /// @notice for a grantees to vote upon a metavest update for which they share a common amount of 'tokenGoverningPower' + /// @param _grant address of the grantee + /// @param _setName name of the set for majority set amendment proposal /// @param _msgSig function signature of the function in this controller which (if successfully executed) will execute the metavest detail update /// @param _inFavor whether msg.sender is in favor of the applicable amendment - function voteOnMetavestAmendment(address _grant, string memory _setName, bytes4 _msgSig, bool _inFavor) external { - + function voteOnMetavestAmendment( + address _grant, + string memory _setName, + bytes4 _msgSig, + bool _inFavor + ) external { bytes32 nameHash = keccak256(bytes(_setName)); - if(BaseAllocation(_grant).grantee() != msg.sender) revert MetaVesTController_OnlyGranteeMayCall(); - if (!isMetavestInSet(_grant, _setName)) revert MetaVesTController_SetDoesNotExist(); - if (!functionToSetMajorityProposal[_msgSig][nameHash].isPending) revert MetaVesTController_NoPendingAmendment(_msgSig, _grant); + if (BaseAllocation(_grant).grantee() != msg.sender) + revert MetaVesTController_OnlyGranteeMayCall(); + if (!isMetavestInSet(_grant, _setName)) + revert MetaVesTController_SetDoesNotExist(); + if (!functionToSetMajorityProposal[_msgSig][nameHash].isPending) + revert MetaVesTController_NoPendingAmendment(_msgSig, _grant); if (!_checkFunctionToTokenToAmendmentTime(_msgSig, _setName)) revert MetaVesTController_ProposedAmendmentExpired(); - - - metavestController.MajorityAmendmentProposal storage proposal = functionToSetMajorityProposal[_msgSig][nameHash]; + metavestController.MajorityAmendmentProposal + storage proposal = functionToSetMajorityProposal[_msgSig][nameHash]; uint256 _callerPower = proposal.voterPower[_grant]; - + //check if the grant has already voted. for (uint256 i; i < proposal.voters.length; ++i) { - if (proposal.voters[i] == _grant) revert MetaVesTController_AlreadyVoted(); + if (proposal.voters[i] == _grant) + revert MetaVesTController_AlreadyVoted(); } //add the msg.sender's vote if (_inFavor) { proposal.voters.push(_grant); proposal.currentVotingPower += _callerPower; - } - emit MetaVesTController_MajorityAmendmentVoted(_setName, _msgSig, _grant, _inFavor, _callerPower, proposal.currentVotingPower, proposal.totalVotingPower); + } + emit MetaVesTController_MajorityAmendmentVoted( + _setName, + _msgSig, + _grant, + _inFavor, + _callerPower, + proposal.currentVotingPower, + proposal.totalVotingPower + ); } /// @notice resets applicable amendment variables because either the applicable amending function has been successfully called or a pending amendment is being overridden with a new one function _resetAmendmentParams(address _grant, bytes4 _msgSig) internal { - if(isMetavestInSet(_grant)) - { + if (isMetavestInSet(_grant)) { bytes32 set = getSetOfMetavest(_grant); - MajorityAmendmentProposal storage proposal = functionToSetMajorityProposal[_msgSig][set]; + MajorityAmendmentProposal + storage proposal = functionToSetMajorityProposal[_msgSig][set]; proposal.changeApplied[_grant] = true; setMajorityVoteActive[set] = false; } @@ -666,27 +911,40 @@ contract metavestController is SafeTransferLib { } /// @notice check whether the applicable proposed amendment has expired - function _checkFunctionToTokenToAmendmentTime(bytes4 _msgSig, string memory _setName) internal view returns (bool) { + function _checkFunctionToTokenToAmendmentTime( + bytes4 _msgSig, + string memory _setName + ) internal view returns (bool) { //check the majority proposal time bytes32 nameHash = keccak256(bytes(_setName)); - return (block.timestamp < functionToSetMajorityProposal[_msgSig][nameHash].time + AMENDMENT_TIME_LIMIT); + return (block.timestamp < + functionToSetMajorityProposal[_msgSig][nameHash].time + + AMENDMENT_TIME_LIMIT); } + /// @notice for `authority` to create a setName + /// @param _name setName being created function createSet(string memory _name) external onlyAuthority { bytes32 nameHash = keccak256(bytes(_name)); - if(bytes(_name).length == 0) revert MetaVesTController_ZeroAddress(); - if (setNames.contains(nameHash)) revert MetaVesTController_SetAlreadyExists(); - if (bytes(_name).length > 512) revert MetaVesTController_StringTooLong(); - + if (bytes(_name).length == 0) revert MetaVesTController_ZeroAddress(); + if (setNames.contains(nameHash)) + revert MetaVesTController_SetAlreadyExists(); + if (bytes(_name).length > 512) + revert MetaVesTController_StringTooLong(); + setNames.add(nameHash); emit MetaVesTController_SetCreated(_name); } + /// @notice for `authority` to remove a setName + /// @param _name setName being removed function removeSet(string memory _name) external onlyAuthority { bytes32 nameHash = keccak256(bytes(_name)); - if (setMajorityVoteActive[nameHash]) revert MetaVesTController_AmendmentAlreadyPending(); - if (!setNames.contains(nameHash)) revert MetaVesTController_SetDoesNotExist(); - + if (setMajorityVoteActive[nameHash]) + revert MetaVesTController_AmendmentAlreadyPending(); + if (!setNames.contains(nameHash)) + revert MetaVesTController_SetDoesNotExist(); + // Remove all addresses from the set starting from the last element for (uint256 i = sets[nameHash].length(); i > 0; i--) { address _grant = sets[nameHash].at(i - 1); @@ -712,12 +970,17 @@ contract metavestController is SafeTransferLib { return false; } - function isMetavestInSet(address _metavest, string memory _setName) internal view returns (bool) { + function isMetavestInSet( + address _metavest, + string memory _setName + ) internal view returns (bool) { bytes32 nameHash = keccak256(bytes(_setName)); return sets[nameHash].contains(_metavest); } - function getSetOfMetavest(address _metavest) internal view returns (bytes32) { + function getSetOfMetavest( + address _metavest + ) internal view returns (bytes32) { uint256 length = setNames.length(); for (uint256 i = 0; i < length; i++) { bytes32 nameHash = setNames.at(i); @@ -728,24 +991,41 @@ contract metavestController is SafeTransferLib { return ""; } - function addMetaVestToSet(string memory _name, address _metaVest) external onlyAuthority { + /// @notice for `authority` to add a MetaVesT to a set + /// @param _name setName to which the MetaVesT is being added + /// @param _metaVest address of the MetaVesT being added + function addMetaVestToSet( + string memory _name, + address _metaVest + ) external onlyAuthority { bytes32 nameHash = keccak256(bytes(_name)); - if (!setNames.contains(nameHash)) revert MetaVesTController_SetDoesNotExist(); - if (isMetavestInSet(_metaVest)) revert MetaVesTController_MetaVesTAlreadyExists(); - if (setMajorityVoteActive[nameHash]) revert MetaVesTController_AmendmentAlreadyPending(); - + if (!setNames.contains(nameHash)) + revert MetaVesTController_SetDoesNotExist(); + if (isMetavestInSet(_metaVest)) + revert MetaVesTController_MetaVesTAlreadyExists(); + if (setMajorityVoteActive[nameHash]) + revert MetaVesTController_AmendmentAlreadyPending(); + sets[nameHash].add(_metaVest); emit MetaVesTController_AddressAddedToSet(_name, _metaVest); } - function removeMetaVestFromSet(string memory _name, address _metaVest) external onlyAuthority { + /// @notice for `authority` to remove a MetaVesT from a set + /// @param _name setName from which the MetaVesT is being removed + /// @param _metaVest address of the MetaVesT being removed + function removeMetaVestFromSet( + string memory _name, + address _metaVest + ) external onlyAuthority { bytes32 nameHash = keccak256(bytes(_name)); - if (!setNames.contains(nameHash)) revert MetaVesTController_SetDoesNotExist(); - if (setMajorityVoteActive[nameHash]) revert MetaVesTController_AmendmentAlreadyPending(); - if (!sets[nameHash].contains(_metaVest)) revert MetaVestController_MetaVestNotInSet(); - + if (!setNames.contains(nameHash)) + revert MetaVesTController_SetDoesNotExist(); + if (setMajorityVoteActive[nameHash]) + revert MetaVesTController_AmendmentAlreadyPending(); + if (!sets[nameHash].contains(_metaVest)) + revert MetaVestController_MetaVestNotInSet(); + sets[nameHash].remove(_metaVest); emit MetaVesTController_AddressRemovedFromSet(_name, _metaVest); } - } From 74686ec8ca86cb4bdc085255f44e83ea49524183 Mon Sep 17 00:00:00 2001 From: Erich Dylus <51535743+ErichDylus@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:45:26 -0400 Subject: [PATCH 2/8] Remove unused import and comments --- src/MetaVesTController.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/MetaVesTController.sol b/src/MetaVesTController.sol index da3a845..ce48619 100644 --- a/src/MetaVesTController.sol +++ b/src/MetaVesTController.sol @@ -8,15 +8,11 @@ pragma solidity 0.8.20; -//import "./MetaVesT.sol"; import "./interfaces/IAllocationFactory.sol"; import "./BaseAllocation.sol"; -import "./RestrictedTokenAllocation.sol"; import "./interfaces/IPriceAllocation.sol"; import "./lib/EnumberableSet.sol"; -//interface deleted - /** * @title MetaVesT Controller * From 02e8134ff433eef60b4bd4c20c93acbe9e40ebf4 Mon Sep 17 00:00:00 2001 From: Erich Dylus <51535743+ErichDylus@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:52:51 -0400 Subject: [PATCH 3/8] Update NatSpec and harmonize formatting --- src/MetaVesTFactory.sol | 52 ++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src/MetaVesTFactory.sol b/src/MetaVesTFactory.sol index 0c200b6..8789282 100644 --- a/src/MetaVesTFactory.sol +++ b/src/MetaVesTFactory.sol @@ -16,12 +16,13 @@ interface IMetaVesTController { /** * @title MetaVesT Factory * - * @notice Deploy a new instance of MetaVesTController, which in turn deploys a new MetaVesT it controls + * @author MetaLeX Labs, Inc. + * + * @notice Factory contract to deploy new instances of MetaVesTController, which may initiate and affect individual MetaVesTs * **/ contract MetaVesTFactory { event MetaVesT_Deployment( - address newMetaVesT, address authority, address controller, address dao, @@ -32,19 +33,42 @@ contract MetaVesTFactory { error MetaVesTFactory_ZeroAddress(); - constructor() { } + constructor() {} - /// @notice constructs a MetaVesT framework specifying authority address, DAO staking/voting contract address - /// each individual grantee's MetaVesT will be initiated in the newly deployed MetaVesT contract, and deployed MetaVesTs are amendable by 'authority' via the controller contract - /// @dev conditionals are contained in the deployed MetaVesT, which is deployed in the MetaVesTController's constructor(); the MetaVesT within the MetaVesTController is immutable, but the 'authority' which has access control within the controller may replace itself - /// @param _authority: address which initiates and may update each MetaVesT, such as a BORG or DAO - /// @param _dao: contract address which token may be staked and used for voting, typically a DAO pool, governor, staking address. Submit address(0) for no such functionality. - function deployMetavestAndController(address _authority, address _dao, address _vestingAllocationFactory, address _tokenOptionFactory, address _restrictedTokenFactory ) external returns(address) { - if(_vestingAllocationFactory == address(0) || _tokenOptionFactory == address(0) || _restrictedTokenFactory == address(0)) - revert MetaVesTFactory_ZeroAddress(); - metavestController _controller = new metavestController(_authority, _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory); - emit MetaVesT_Deployment(address(0), _authority, address(_controller), _dao, _vestingAllocationFactory, _tokenOptionFactory, _restrictedTokenFactory); + /// @notice constructs a MetaVesT Controller and overall framework specifying authority address, DAO contract address, and each MetaVesT type factory address + /// @dev conditionals are contained in the deployed MetaVesT Controller; the `authority` which has access control within the `_controller` may replace itself + /// @param _authority address which initiates and may update each MetaVesT + /// @param _dao DAO governance contract address which exercises control over ability of 'authority' to call certain functions via imposing conditions in the controller. Submit address(0) for no such functionality. + /// @param _vestingFactory vesting allocation factory (VestingAllocationFactory.sol) contract address + /// @param _tokenOptionFactory token option factory (TokenOptionFactory.sol) contract address + /// @param _restrictedTokenFactory restricted token award factory (RestrictedTokenFactory.sol) contract address + function deployMetavestAndController( + address _authority, + address _dao, + address _vestingAllocationFactory, + address _tokenOptionFactory, + address _restrictedTokenFactory + ) external returns (address) { + if ( + _vestingAllocationFactory == address(0) || + _tokenOptionFactory == address(0) || + _restrictedTokenFactory == address(0) + ) revert MetaVesTFactory_ZeroAddress(); + metavestController _controller = new metavestController( + _authority, + _dao, + _vestingAllocationFactory, + _tokenOptionFactory, + _restrictedTokenFactory + ); + emit MetaVesT_Deployment( + _authority, + address(_controller), + _dao, + _vestingAllocationFactory, + _tokenOptionFactory, + _restrictedTokenFactory + ); return address(_controller); } - } From d786f3adb3a73c098d874f7a8efcbd5b9f9c2b19 Mon Sep 17 00:00:00 2001 From: Erich Dylus <51535743+ErichDylus@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:24:57 -0400 Subject: [PATCH 4/8] Update NatSpec and harmonize formatting Re-order types/variables per Solidity style guide --- src/BaseAllocation.sol | 539 +++++++++++++++++++++++------------------ 1 file changed, 304 insertions(+), 235 deletions(-) diff --git a/src/BaseAllocation.sol b/src/BaseAllocation.sol index 161ca4a..0013cb7 100644 --- a/src/BaseAllocation.sol +++ b/src/BaseAllocation.sol @@ -4,20 +4,26 @@ pragma solidity 0.8.20; /// @notice interface to a MetaLeX condition contract /// @dev see https://github.com/MetaLex-Tech/BORG-CORE/tree/main/src/libs/conditions interface IConditionM { - function checkCondition(address _contract, bytes4 _functionSignature, bytes memory data) external view returns (bool); + function checkCondition( + address _contract, + bytes4 _functionSignature, + bytes memory data + ) external view returns (bool); } interface IERC20M { - function allowance(address owner, address spender) external view returns (uint256); + function allowance( + address owner, + address spender + ) external view returns (uint256); function decimals() external view returns (uint8); function balanceOf(address account) external view returns (uint256); } -interface IController { +interface IController { function authority() external view returns (address); } -/// @notice Solady's SafeTransferLib 'SafeTransfer()' and 'SafeTransferFrom()'; (https://github.com/Vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) abstract contract SafeTransferLib { error TransferFailed(); error TransferFromFailed(); @@ -45,7 +51,12 @@ abstract contract SafeTransferLib { /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. Reverts upon failure. /// The `from` account must have at least `amount` approved for the current contract to manage. - function safeTransferFrom(address token, address from, address to, uint256 amount) internal { + function safeTransferFrom( + address token, + address from, + address to, + uint256 amount + ) internal { assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x60, amount) // Store the `amount` argument. @@ -69,7 +80,7 @@ abstract contract SafeTransferLib { } } -/// @notice gas-optimized reentrancy protection by Solady (https://github.com/Vectorized/solady/blob/main/src/utils/ReentrancyGuard.sol) +/// @notice gas-optimized reentrancy protection abstract contract ReentrancyGuard { /// @dev Equivalent to: `uint72(bytes9(keccak256("_REENTRANCY_GUARD_SLOT")))`. 9 bytes is large enough to avoid collisions with lower slots, /// but not too large to result in excessive bytecode bloat. @@ -92,259 +103,317 @@ abstract contract ReentrancyGuard { } } +/** + * @title Base Allocation + * + * @author MetaLeX Labs, Inc. + * + * @notice Basic Token Allocation which is inherited by the other MetaVesT types + **/ +abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib { + // enum to determine which tokens in the contract will be counted towards governing power + enum GovType { + all, + vested, + unlocked + } -abstract contract BaseAllocation is ReentrancyGuard, SafeTransferLib{ + struct Allocation { + uint256 tokenStreamTotal; // total number of tokens subject to linear vesting/restriction removal (includes cliff credits but not each 'milestoneAward') + uint128 vestingCliffCredit; // lump sum of tokens which become vested at 'startTime' and will be added to '_linearVested' + uint128 unlockingCliffCredit; // lump sum of tokens which become unlocked at 'startTime' and will be added to '_linearUnlocked' + uint160 vestingRate; // tokens per second that become vested; if RESTRICTED this amount corresponds to 'lapse rate' for tokens that become non-repurchasable + uint48 vestingStartTime; // if RESTRICTED this amount corresponds to 'lapse start time' + uint160 unlockRate; // tokens per second that become unlocked; + uint48 unlockStartTime; // start of the linear unlock + address tokenContract; // contract address of the ERC20 token included in the MetaVesT + } - /// @notice MetaVesTController contract address, immutably tied to this MetaVesT - address public immutable controller; - uint256 constant public MAX_MILESTONES = 20; - /// @notice authority address, may replace itself in 'controller' - address public authority; // REVIEW: probably just have `getAuthority` which calls thru to `controller`? Saves having to worry about updating if it changes? - struct Milestone { - uint256 milestoneAward; // per-milestone indexed lump sums of tokens vested upon corresponding milestone completion - bool unlockOnCompletion; // whether the 'milestoneAward' is to be unlocked upon completion - bool complete; // whether the Milestone is satisfied and the 'milestoneAward' is to be released - address[] conditionContracts; // array of contract addresses corresponding to condition(s) that must satisfied for this Milestone to be 'complete' - } - error MetaVesT_OnlyController(); - error MetaVesT_OnlyGrantee(); - error MetaVesT_OnlyAuthority(); - error MetaVesT_ZeroAddress(); - error MetaVesT_RateTooHigh(); - error MetaVesT_ZeroAmount(); - error MetaVesT_MilestoneIndexOutOfRange(); - error MetaVesT_NotTerminated(); - error MetaVesT_MilestoneIndexCompletedOrDoesNotExist(); - error MetaVesT_ConditionNotSatisfied(); - error MetaVesT_AlreadyTerminated(); - error MetaVesT_MoreThanAvailable(); - error MetaVesT_VestNotTransferable(); - error MetaVesT_ShortStopTimeNotReached(); - error MetaVest_ShortStopDatePassed(); - error MetaVesT_MaxMilestonesReached(); - error MetaVesT_TooSmallAmount(); - - event MetaVesT_MilestoneCompleted(address indexed grantee, uint256 indexed milestoneIndex); - event MetaVesT_MilestoneAdded(address indexed grantee, Milestone milestone); - event MetaVesT_MilestoneRemoved(address indexed grantee, uint256 indexed milestoneIndex); - event MetaVesT_StopTimesUpdated( - address indexed grant, - uint48 shortStopTime - ); - event MetaVesT_TransferabilityUpdated(address indexed grantee, bool isTransferable); - event MetaVest_TransferRightsPending(address indexed grantee, address indexed pendingGrantee); - event MetaVesT_TransferredRights(address indexed grantee, address transferee); - event MetaVesT_UnlockRateUpdated(address indexed grantee, uint208 unlockRate); - event MetaVesT_VestingRateUpdated(address indexed grantee, uint208 vestingRate); - event MetaVesT_Withdrawn(address indexed grantee, address indexed tokenAddress, uint256 amount); - event MetaVesT_PriceUpdated(address indexed grantee, uint256 exercisePrice); - event MetaVesT_RepurchaseAndWithdrawal(address indexed grantee, address indexed tokenAddress, uint256 withdrawalAmount, uint256 repurchaseAmount); - event MetaVesT_Terminated(address indexed grantee, uint256 tokensRecovered); - event MetaVest_GovVariablesUpdated(GovType _govType); - - struct Allocation { - uint256 tokenStreamTotal; // total number of tokens subject to linear vesting/restriction removal (includes cliff credits but not each 'milestoneAward') - uint128 vestingCliffCredit; // lump sum of tokens which become vested at 'startTime' and will be added to '_linearVested' - uint128 unlockingCliffCredit; // lump sum of tokens which become unlocked at 'startTime' and will be added to '_linearUnlocked' - uint160 vestingRate; // tokens per second that become vested; if RESTRICTED this amount corresponds to 'lapse rate' for tokens that become non-repurchasable - uint48 vestingStartTime; // if RESTRICTED this amount corresponds to 'lapse start time' - uint160 unlockRate; // tokens per second that become unlocked; - uint48 unlockStartTime; // start of the linear unlock - address tokenContract; // contract address of the ERC20 token included in the MetaVesT - } + struct Milestone { + uint256 milestoneAward; // per-milestone indexed lump sums of tokens vested upon corresponding milestone completion + bool unlockOnCompletion; // whether the 'milestoneAward' is to be unlocked upon completion + bool complete; // whether the Milestone is satisfied and the 'milestoneAward' is to be released + address[] conditionContracts; // array of contract addresses corresponding to condition(s) that must satisfied for this Milestone to be 'complete' + } - // enum to determine which tokens in the vesting contract will be counted towards governing power - enum GovType {all, vested, unlocked} - - address public grantee; // grantee of the tokens - address public pendingGrantee; // address of the pending grantee - bool transferable; // whether grantee can transfer their MetaVesT in whole - Milestone[] public milestones; // array of Milestone structs - Allocation public allocation; // struct containing vesting and unlocking details - uint256 public milestoneAwardTotal; // total number of tokens awarded in milestones - uint256 public milestoneUnlockedTotal; // total number of tokens unlocked in milestones - uint256 public tokensWithdrawn; // total number of tokens withdrawn - GovType public govType; - bool public terminated; - uint256 public terminationTime; - - /// @notice BaseAllocation constructor - /// @param _grantee: address of the grantee, cannot be a zero address - /// @param _controller: address of the MetaVesTController contract - constructor(address _grantee, address _controller) { - // Controller can be 0 for an immuatable version, but grantee cannot - if (_grantee == address(0)) revert MetaVesT_ZeroAddress(); - grantee = _grantee; - controller = _controller; - govType = GovType.vested; - } + /// @notice MetaVesTController contract address, immutably tied to this MetaVesT + address public immutable controller; + uint256 public constant MAX_MILESTONES = 20; + + address public grantee; // grantee of the tokens + address public pendingGrantee; // address of the pending grantee + bool public terminated; + bool transferable; // whether grantee can transfer their MetaVesT in whole + Milestone[] public milestones; // array of Milestone structs + Allocation public allocation; // struct containing vesting and unlocking details + GovType public govType; + uint256 public milestoneAwardTotal; // total number of tokens awarded in milestones + uint256 public milestoneUnlockedTotal; // total number of tokens unlocked in milestones + uint256 public tokensWithdrawn; // total number of tokens withdrawn + uint256 public terminationTime; + + error MetaVesT_OnlyController(); + error MetaVesT_OnlyGrantee(); + error MetaVesT_OnlyAuthority(); + error MetaVesT_ZeroAddress(); + error MetaVesT_RateTooHigh(); + error MetaVesT_ZeroAmount(); + error MetaVesT_MilestoneIndexOutOfRange(); + error MetaVesT_NotTerminated(); + error MetaVesT_MilestoneIndexCompletedOrDoesNotExist(); + error MetaVesT_ConditionNotSatisfied(); + error MetaVesT_AlreadyTerminated(); + error MetaVesT_MoreThanAvailable(); + error MetaVesT_VestNotTransferable(); + error MetaVesT_ShortStopTimeNotReached(); + error MetaVest_ShortStopDatePassed(); + error MetaVesT_MaxMilestonesReached(); + error MetaVesT_TooSmallAmount(); + + event MetaVesT_MilestoneCompleted( + address indexed grantee, + uint256 indexed milestoneIndex + ); + event MetaVesT_MilestoneAdded(address indexed grantee, Milestone milestone); + event MetaVesT_MilestoneRemoved( + address indexed grantee, + uint256 indexed milestoneIndex + ); + event MetaVesT_StopTimesUpdated( + address indexed grant, + uint48 shortStopTime + ); + event MetaVesT_TransferabilityUpdated( + address indexed grantee, + bool isTransferable + ); + event MetaVest_TransferRightsPending( + address indexed grantee, + address indexed pendingGrantee + ); + event MetaVesT_TransferredRights( + address indexed grantee, + address transferee + ); + event MetaVesT_UnlockRateUpdated( + address indexed grantee, + uint208 unlockRate + ); + event MetaVesT_VestingRateUpdated( + address indexed grantee, + uint208 vestingRate + ); + event MetaVesT_Withdrawn( + address indexed grantee, + address indexed tokenAddress, + uint256 amount + ); + event MetaVesT_PriceUpdated(address indexed grantee, uint256 exercisePrice); + event MetaVesT_RepurchaseAndWithdrawal( + address indexed grantee, + address indexed tokenAddress, + uint256 withdrawalAmount, + uint256 repurchaseAmount + ); + event MetaVesT_Terminated(address indexed grantee, uint256 tokensRecovered); + event MetaVest_GovVariablesUpdated(GovType _govType); + + /// @notice BaseAllocation constructor + /// @param _grantee address of the grantee, cannot be a zero address + /// @param _controller address of the MetaVesTController contract + constructor(address _grantee, address _controller) { + // Controller can be 0 for an immuatable version, but grantee cannot + if (_grantee == address(0)) revert MetaVesT_ZeroAddress(); + grantee = _grantee; + controller = _controller; + govType = GovType.vested; + } - function getVestingType() external view virtual returns (uint256); - function getGoverningPower() external virtual returns (uint256); - function updateStopTimes(uint48 _shortStopTime) external virtual;// onlyController; - function terminate() external virtual;// onlyController; - function getAmountWithdrawable() public view virtual returns (uint256); - - /// @notice returns the amount of voting power that may be affected by amendment proposals - /// @return majorityVotingPower - the amount of tokens that are vested, locked, and unexercised - function getMajorityVotingPower() external view returns (uint256 majorityVotingPower) { - //add up the total tokens that are unvested or locked - if(terminated) return 0; - uint256 totalMilestoneAward = 0; - for(uint256 i; i < milestones.length; ++i) - { - totalMilestoneAward += milestones[i].milestoneAward; - } - uint256 tokensNotAffected = tokensWithdrawn + getAmountWithdrawable(); - majorityVotingPower = allocation.tokenStreamTotal + totalMilestoneAward - tokensNotAffected; + function getVestingType() external view virtual returns (uint256); + function getGoverningPower() external virtual returns (uint256); + function updateStopTimes(uint48 _shortStopTime) external virtual; // onlyController; + function terminate() external virtual; // onlyController; + function getAmountWithdrawable() public view virtual returns (uint256); + + /// @notice returns the amount of voting power that may be affected by amendment proposals + /// @return majorityVotingPower - the amount of tokens that are vested, locked, and unexercised + function getMajorityVotingPower() + external + view + returns (uint256 majorityVotingPower) + { + //add up the total tokens that are unvested or locked + if (terminated) return 0; + uint256 totalMilestoneAward = 0; + for (uint256 i; i < milestones.length; ++i) { + totalMilestoneAward += milestones[i].milestoneAward; } + uint256 tokensNotAffected = tokensWithdrawn + getAmountWithdrawable(); + majorityVotingPower = + allocation.tokenStreamTotal + + totalMilestoneAward - + tokensNotAffected; + } - /// @notice updates the transferability of the vesting contract - /// @dev onlyController -- must be called from the metavest controller - /// @param _transferable - bool to set the transferability of the vesting contract - function updateTransferability(bool _transferable) external onlyController { - transferable = _transferable; - emit MetaVesT_TransferabilityUpdated(grantee, _transferable); - } + /// @notice updates the transferability of the vesting contract + /// @dev onlyController -- must be called from the metavest controller + /// @param _transferable - bool to set the transferability of the vesting contract + function updateTransferability(bool _transferable) external onlyController { + transferable = _transferable; + emit MetaVesT_TransferabilityUpdated(grantee, _transferable); + } - /// @notice updates the vesting rate of the VestingAllocation - /// @dev onlyController -- must be called from the metavest controller - /// @param _newVestingRate - the updated vesting rate in tokens per second in the vesting token decimal - function updateVestingRate(uint160 _newVestingRate) external onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - allocation.vestingRate = _newVestingRate; - emit MetaVesT_VestingRateUpdated(grantee, _newVestingRate); - } + /// @notice updates the vesting rate of the VestingAllocation + /// @dev onlyController -- must be called from the metavest controller + /// @param _newVestingRate - the updated vesting rate in tokens per second in the vesting token decimal + function updateVestingRate( + uint160 _newVestingRate + ) external onlyController { + if (terminated) revert MetaVesT_AlreadyTerminated(); + allocation.vestingRate = _newVestingRate; + emit MetaVesT_VestingRateUpdated(grantee, _newVestingRate); + } - /// @notice updates the unlock rate of the VestingAllocation - /// @dev onlyController -- must be called from the metavest controller - /// @param _newUnlockRate - the updated unlock rate in tokens per second in the vesting token decimal - function updateUnlockRate(uint160 _newUnlockRate) external onlyController { - allocation.unlockRate = _newUnlockRate; - emit MetaVesT_UnlockRateUpdated(grantee, _newUnlockRate); - } + /// @notice updates the unlock rate of the VestingAllocation + /// @dev onlyController -- must be called from the metavest controller + /// @param _newUnlockRate - the updated unlock rate in tokens per second in the vesting token decimal + function updateUnlockRate(uint160 _newUnlockRate) external onlyController { + allocation.unlockRate = _newUnlockRate; + emit MetaVesT_UnlockRateUpdated(grantee, _newUnlockRate); + } - /// @notice Sets the governing power type for the MetaVesT - /// @param _govType: the type of governing power to be used - function setGovVariables(GovType _govType) external onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - govType = _govType; - emit MetaVest_GovVariablesUpdated(govType); + /// @notice Sets the governing power type for the MetaVesT + /// @param _govType: the type of governing power to be used + function setGovVariables(GovType _govType) external onlyController { + if (terminated) revert MetaVesT_AlreadyTerminated(); + govType = _govType; + emit MetaVest_GovVariablesUpdated(govType); + } + + /// @notice allows a milestone to be 'unlocked'. callable by anyone but the conditions for the milestone must be met + /// @param _milestoneIndex - the index of the milestone to confirm + function confirmMilestone(uint256 _milestoneIndex) external nonReentrant { + if (terminated) revert MetaVesT_AlreadyTerminated(); + if (_milestoneIndex >= milestones.length) + revert MetaVesT_MilestoneIndexOutOfRange(); + Milestone storage milestone = milestones[_milestoneIndex]; + if (_milestoneIndex >= milestones.length || milestone.complete) + revert MetaVesT_MilestoneIndexCompletedOrDoesNotExist(); + + //encode the milestone index to bytes for signature verification + bytes memory _data = abi.encodePacked(_milestoneIndex); + // perform any applicable condition checks, including whether 'authority' has a signatureCondition + for (uint256 i; i < milestone.conditionContracts.length; ++i) { + if ( + !IConditionM(milestone.conditionContracts[i]).checkCondition( + address(this), + msg.sig, + _data + ) + ) revert MetaVesT_ConditionNotSatisfied(); } - /// @notice allows a milestone to be 'unlocked'. callable by anyone but the conditions for the milestone must be met - /// @param _milestoneIndex - the index of the milestone to confirm - function confirmMilestone(uint256 _milestoneIndex) external nonReentrant { - if(terminated) revert MetaVesT_AlreadyTerminated(); - if(_milestoneIndex >= milestones.length) revert MetaVesT_MilestoneIndexOutOfRange(); - Milestone storage milestone = milestones[_milestoneIndex]; - if (_milestoneIndex >= milestones.length || milestone.complete) - revert MetaVesT_MilestoneIndexCompletedOrDoesNotExist(); - - //encode the milestone index to bytes for signature verification - bytes memory _data = abi.encodePacked(_milestoneIndex); - // perform any applicable condition checks, including whether 'authority' has a signatureCondition - for (uint256 i; i < milestone.conditionContracts.length; ++i) { - if (!IConditionM(milestone.conditionContracts[i]).checkCondition(address(this), msg.sig, _data)) - revert MetaVesT_ConditionNotSatisfied(); - } + milestone.complete = true; + milestoneAwardTotal += milestone.milestoneAward; + if (milestone.unlockOnCompletion) + milestoneUnlockedTotal += milestone.milestoneAward; - milestone.complete = true; - milestoneAwardTotal += milestone.milestoneAward; - if(milestone.unlockOnCompletion) - milestoneUnlockedTotal += milestone.milestoneAward; - - emit MetaVesT_MilestoneCompleted(grantee, _milestoneIndex); - } + emit MetaVesT_MilestoneCompleted(grantee, _milestoneIndex); + } - /// @notice removes a milestone from the VestingAllocation - /// @dev onlyController -- must be called from the metavest controller - /// @param _milestoneIndex - the index of the milestone to remove - function removeMilestone(uint256 _milestoneIndex) external onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - if (_milestoneIndex >= milestones.length) revert MetaVesT_MilestoneIndexOutOfRange(); - uint256 _milestoneAward = milestones[_milestoneIndex].milestoneAward; - //transfer the milestone award back to the authority, we check in the controller to ensure only uncompleted milestones can be removed - safeTransfer(allocation.tokenContract, getAuthority(), _milestoneAward); - delete milestones[_milestoneIndex]; - milestones[_milestoneIndex] = milestones[milestones.length - 1]; - milestones.pop(); - emit MetaVesT_MilestoneRemoved(grantee, _milestoneIndex); - } + /// @notice removes a milestone from the VestingAllocation + /// @dev onlyController -- must be called from the metavest controller + /// @param _milestoneIndex - the index of the milestone to remove + function removeMilestone(uint256 _milestoneIndex) external onlyController { + if (terminated) revert MetaVesT_AlreadyTerminated(); + if (_milestoneIndex >= milestones.length) + revert MetaVesT_MilestoneIndexOutOfRange(); + uint256 _milestoneAward = milestones[_milestoneIndex].milestoneAward; + //transfer the milestone award back to the authority, we check in the controller to ensure only uncompleted milestones can be removed + safeTransfer(allocation.tokenContract, getAuthority(), _milestoneAward); + delete milestones[_milestoneIndex]; + milestones[_milestoneIndex] = milestones[milestones.length - 1]; + milestones.pop(); + emit MetaVesT_MilestoneRemoved(grantee, _milestoneIndex); + } - /// @notice adds a milestone to the VestingAllocation - /// @dev onlyController -- must be called from the metavest controller - /// @param _milestone - the milestone to add - function addMilestone(Milestone calldata _milestone) external onlyController { - if(terminated) revert MetaVesT_AlreadyTerminated(); - if(milestones.length >= MAX_MILESTONES) revert MetaVesT_MaxMilestonesReached(); - milestones.push(_milestone); - emit MetaVesT_MilestoneAdded(grantee, _milestone); - } + /// @notice adds a milestone to the VestingAllocation + /// @dev onlyController -- must be called from the metavest controller + /// @param _milestone - the milestone to add + function addMilestone( + Milestone calldata _milestone + ) external onlyController { + if (terminated) revert MetaVesT_AlreadyTerminated(); + if (milestones.length >= MAX_MILESTONES) + revert MetaVesT_MaxMilestonesReached(); + milestones.push(_milestone); + emit MetaVesT_MilestoneAdded(grantee, _milestone); + } - /// @notice transfers the rights of the VestingAllocation to a new owner - /// @dev onlyGrantee -- must be called by the grantee - /// @param _newOwner - the address of the new owner - function transferRights(address _newOwner) external onlyGrantee { - if(_newOwner == address(0)) revert MetaVesT_ZeroAddress(); - if(!transferable) revert MetaVesT_VestNotTransferable(); - emit MetaVest_TransferRightsPending(grantee, _newOwner); - pendingGrantee = _newOwner; - } + /// @notice transfers the rights of the VestingAllocation to a new owner + /// @dev onlyGrantee -- must be called by the grantee + /// @param _newOwner - the address of the new owner + function transferRights(address _newOwner) external onlyGrantee { + if (_newOwner == address(0)) revert MetaVesT_ZeroAddress(); + if (!transferable) revert MetaVesT_VestNotTransferable(); + emit MetaVest_TransferRightsPending(grantee, _newOwner); + pendingGrantee = _newOwner; + } - /// @notice confirms the transfer of the rights of the VestingAllocation to a new owner - function confirmTransfer() external { - if(msg.sender != pendingGrantee) revert MetaVesT_OnlyGrantee(); - grantee = pendingGrantee; - emit MetaVesT_TransferredRights(grantee, pendingGrantee); - pendingGrantee = address(0); - } + /// @notice confirms the transfer of the rights of the VestingAllocation to a new owner + function confirmTransfer() external { + if (msg.sender != pendingGrantee) revert MetaVesT_OnlyGrantee(); + grantee = pendingGrantee; + emit MetaVesT_TransferredRights(grantee, pendingGrantee); + pendingGrantee = address(0); + } - /// @notice withdraws tokens from the VestingAllocation - /// @dev onlyGrantee -- must be called by the grantee - /// @param _amount - the amount of tokens to withdraw - function withdraw(uint256 _amount) external nonReentrant onlyGrantee { - if (_amount == 0) revert MetaVesT_ZeroAmount(); - if (_amount > getAmountWithdrawable() || _amount > IERC20M(allocation.tokenContract).balanceOf(address(this))) revert MetaVesT_MoreThanAvailable(); - tokensWithdrawn += _amount; - safeTransfer(allocation.tokenContract, msg.sender, _amount); - emit MetaVesT_Withdrawn(msg.sender, allocation.tokenContract, _amount); - } + /// @notice withdraws tokens from the VestingAllocation + /// @dev onlyGrantee -- must be called by the grantee + /// @param _amount - the amount of tokens to withdraw + function withdraw(uint256 _amount) external nonReentrant onlyGrantee { + if (_amount == 0) revert MetaVesT_ZeroAmount(); + if ( + _amount > getAmountWithdrawable() || + _amount > IERC20M(allocation.tokenContract).balanceOf(address(this)) + ) revert MetaVesT_MoreThanAvailable(); + tokensWithdrawn += _amount; + safeTransfer(allocation.tokenContract, msg.sender, _amount); + emit MetaVesT_Withdrawn(msg.sender, allocation.tokenContract, _amount); + } - /// @notice gets the details of the vest - /// @return Allocation - the allocation details - function getMetavestDetails() external view returns (Allocation memory) { - return allocation; - } + /// @notice gets the details of the vest + /// @return Allocation - the allocation details + function getMetavestDetails() external view returns (Allocation memory) { + return allocation; + } - /// @notice returns the authority address - /// @return address of the authority - function getAuthority() public view returns (address){ - return IController(controller).authority(); - } - - modifier onlyController() { - if (msg.sender != controller) revert MetaVesT_OnlyController(); - _; - } + /// @notice returns the authority address + /// @return address of the authority + function getAuthority() public view returns (address) { + return IController(controller).authority(); + } - modifier onlyGrantee() { - if (msg.sender != grantee) revert MetaVesT_OnlyGrantee(); - _; - } + modifier onlyController() { + if (msg.sender != controller) revert MetaVesT_OnlyController(); + _; + } - modifier onlyAuthority() { - if (msg.sender != getAuthority()) revert MetaVesT_OnlyAuthority(); - _; - } + modifier onlyGrantee() { + if (msg.sender != grantee) revert MetaVesT_OnlyGrantee(); + _; + } - /// @dev returns the minimum of `x` and `y`. See https://github.com/Vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol - function _min(uint256 x, uint256 y) internal pure returns (uint256 z) { + modifier onlyAuthority() { + if (msg.sender != getAuthority()) revert MetaVesT_OnlyAuthority(); + _; + } + + /// @dev returns the minimum of `x` and `y`. See https://github.com/Vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol + function _min(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { z := xor(x, mul(xor(x, y), lt(y, x))) - } } - - + } } From d2f8eb9f0ea88138c3502db06c3ccb4592ad484e Mon Sep 17 00:00:00 2001 From: Erich Dylus <51535743+ErichDylus@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:42:47 -0400 Subject: [PATCH 5/8] Update RestrictedTokenFactory.sol --- src/RestrictedTokenFactory.sol | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/RestrictedTokenFactory.sol b/src/RestrictedTokenFactory.sol index c9e43a3..c353907 100644 --- a/src/RestrictedTokenFactory.sol +++ b/src/RestrictedTokenFactory.sol @@ -4,8 +4,23 @@ pragma solidity 0.8.20; import "./RestrictedTokenAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; +/** + * @title Restricted Token Factory + * + * @author MetaLeX Labs, Inc. + * + * @notice Factory Contract for deploying Restricted Token Allocation MetaVesTs + **/ contract RestrictedTokenFactory is IAllocationFactory { - + /// @notice creates a Restricted Token Allocation + /// @param _allocationType AllocationType struct, which should be == AllocationType.RestrictedToken + /// @param _grantee address of the grantee receiving the Restricted Token Allocation + /// @param _controller contract address of the applicable MetaVesT Controller + /// @param _allocation RestrictedTokenAward.Allocation struct details + /// @param _milestones array of RestrictedTokenAward.Milestone[] structs for this allocation + /// @param _paymentToken contract address of the payment token for repurchases + /// @param _exercisePrice price at which the restricted tokens can be repurchased in vesting token decimals but only up to payment decimal precision + /// @param _shortStopDuration duration after termination during which restricted tokens can be repurchased function createAllocation( AllocationType _allocationType, address _grantee, @@ -16,8 +31,19 @@ contract RestrictedTokenFactory is IAllocationFactory { uint256 _exercisePrice, uint256 _shortStopDuration ) external returns (address) { - if (_allocationType == AllocationType.RestrictedToken) { - return address(new RestrictedTokenAward(_grantee, _controller, _paymentToken, _exercisePrice, _shortStopDuration, _allocation, _milestones)); + if (_allocationType == AllocationType.RestrictedToken) { + return + address( + new RestrictedTokenAward( + _grantee, + _controller, + _paymentToken, + _exercisePrice, + _shortStopDuration, + _allocation, + _milestones + ) + ); } else { revert("AllocationFactory: invalid allocation type"); } From d5a35992fb695ea66ca8c00c9d656969bddd69e3 Mon Sep 17 00:00:00 2001 From: Erich Dylus <51535743+ErichDylus@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:45:40 -0400 Subject: [PATCH 6/8] Update TokenOptionFactory.sol --- src/TokenOptionFactory.sol | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/TokenOptionFactory.sol b/src/TokenOptionFactory.sol index ed90780..3324875 100644 --- a/src/TokenOptionFactory.sol +++ b/src/TokenOptionFactory.sol @@ -4,8 +4,23 @@ pragma solidity 0.8.20; import "./TokenOptionAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; +/** + * @title Token Option Factory + * + * @author MetaLeX Labs, Inc. + * + * @notice Factory Contract for deploying Token Option Allocation MetaVesTs + **/ contract TokenOptionFactory is IAllocationFactory { - + /// @notice creates a Token Option Allocation + /// @param _allocationType AllocationType struct, which should be == AllocationType.TokenOption + /// @param _grantee address of the grantee receiving the Token Option Allocation + /// @param _controller contract address of the applicable MetaVesT Controller + /// @param _allocation TokenOptionAllocation.Allocation struct details + /// @param _milestones array of TokenOptionAllocation.Milestone[] structs for this allocation + /// @param _paymentToken contract address of the payment token for repurchases + /// @param _exercisePrice price of the token option exercise in vesting token decimals but only up to payment decimal precision + /// @param _shortStopDuration duration of the short stop function createAllocation( AllocationType _allocationType, address _grantee, From 27ff203842d91550ef60091d0e6826dea6a0d19b Mon Sep 17 00:00:00 2001 From: Erich Dylus <51535743+ErichDylus@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:49:48 -0400 Subject: [PATCH 7/8] Update VestingAllocationFactory.sol --- src/VestingAllocationFactory.sol | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/VestingAllocationFactory.sol b/src/VestingAllocationFactory.sol index a27e220..d9d7916 100644 --- a/src/VestingAllocationFactory.sol +++ b/src/VestingAllocationFactory.sol @@ -4,8 +4,23 @@ pragma solidity 0.8.20; import "./VestingAllocation.sol"; import "./interfaces/IAllocationFactory.sol"; +/** + * @title Vesting Allocation Factory + * + * @author MetaLeX Labs, Inc. + * + * @notice Factory Contract for deploying Vesting Allocation MetaVesTs + **/ contract VestingAllocationFactory is IAllocationFactory { - + /// @notice creates a Vesting Allocation + /// @param _allocationType AllocationType struct, which should be == AllocationType.Vesting + /// @param _grantee address of the grantee receiving the Vesting Allocation + /// @param _controller contract address of the applicable MetaVesT Controller + /// @param _allocation VestingAllocation.Allocation struct details + /// @param _milestones array of VestingAllocation.Milestone[] structs for this allocation + /// @param _paymentToken ignored param for this allocation type + /// @param _exercisePrice ignored param for this allocation type + /// @param _shortStopDuration ignored param for this allocation type function createAllocation( AllocationType _allocationType, address _grantee, @@ -17,7 +32,15 @@ contract VestingAllocationFactory is IAllocationFactory { uint256 _shortStopDuration ) external returns (address) { if (_allocationType == AllocationType.Vesting) { - return address(new VestingAllocation(_grantee, _controller, _allocation, _milestones)); + return + address( + new VestingAllocation( + _grantee, + _controller, + _allocation, + _milestones + ) + ); } else { revert("AllocationFactory: invalid allocation type"); } From c0d9a763bea9cb6902d24651e5e0cd7db59e0208 Mon Sep 17 00:00:00 2001 From: Erich Dylus <51535743+ErichDylus@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:33:28 -0400 Subject: [PATCH 8/8] Update README.md --- README.md | 85 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index b773038..d8df1ea 100644 --- a/README.md +++ b/README.md @@ -27,15 +27,13 @@ Each MetaVesT framework is designed to be on a per-BORG or more general per-auth A MetaVesT framework is initiated by calling `deployMetavestAndController()` in `MetaVesTFactory`, supplying: -`_authority`: address of the `authority` who will have the ability to call the functions in the MetaVesTController (including creating and updating MetaVesTs within the framework) such as a BORG. - -`_dao`: DAO governance contract address which exercises control over ability of 'authority' to call certain functions via imposing conditions through 'updateFunctionCondition' - -`_vestingAllocationFactory`: factory contract address which will be used to create each vesting allocation in this MetaVesT framework - -`_tokenOptionFactory`: factory contract address which will be used to create each token option in this MetaVesT framework - -`_restrictedTokenFactory`: factory contract address which will be used to create each restricted token award in this MetaVesT framework +| Name | Type | Description | +| :---: |:----:| :---: | +|`_authority`| address | the `authority` who will have the ability to call the functions in the MetaVesTController (including creating and updating MetaVesTs within the framework) such as a BORG.| +|`_dao`| address | DAO governance contract address which exercises control over ability of `authority` to call certain functions in the MetaVesTController via imposing conditions through `updateFunctionCondition()`| +|`_vestingAllocationFactory`| address | factory contract address which will be used to create each vesting allocation in this MetaVesT framework | +| `_tokenOptionFactory`| address | factory contract address which will be used to create each token option in this MetaVesT framework | +|`_restrictedTokenFactory`| address | factory contract address which will be used to create each restricted token award in this MetaVesT framework| This call deploys a [MetaVesTController.sol](https://github.com/MetaLex-Tech/MetaVesT/blob/main/src/MetaVesTController.sol), the authority-facing contract which it uses to create individual MetaVesTs, update MetaVesT details, add/remove/confirm milestones, terminate MetaVesTs, toggle transferability, etc. subject to grantee or majority-in-tokenGoverningPower consent where applicable (see below). @@ -43,37 +41,39 @@ This call deploys a [MetaVesTController.sol](https://github.com/MetaLex-Tech/Met Each MetaVesT initiated via the MetaVesT Controller by the `authority` is designed to be on a per-grantee basis. -Each separate MetaVesT under the framework can have a variety of different attributes, including different ERC20s, different MetaVesT types (vesting allocation, option, RTA), amounts, transferability, milestone amounts and conditions, vesting and unlock schedules, etc. The `authority` for a given MetaVesT framework creates a new MetaVesT for a given recipient by calling `createMetavest()` in the `MetaVesTController`, supplying: +Each separate MetaVesT under the framework can have a variety of different attributes, including different ERC20s, different MetaVesT types (vesting allocation, token option, restricted token award), amounts, transferability, milestone amounts and conditions, vesting and unlock schedules, etc. The `authority` for a given MetaVesT framework creates a new MetaVesT for a given recipient by calling `createMetavest()` in the `MetaVesTController`, supplying: -`_type`: enum of the MetaVesT type for this `grantee`, either `Vesting` (simple vesting allocation), `RestrictedToken` (restricted token award), or `TokenOption` (token option) +`_type`: enum AllocationType of the MetaVesT type for this `grantee`, either `Vesting` (simple vesting allocation), `RestrictedToken` (restricted token award), or `TokenOption` (token option) `_grantee`: address of the `grantee` for the new MetaVesT `_allocation`: calldata of the `BaseAllocation.Allocation` struct for this grantee, comprised of: - -- `tokenStreamTotal`: uint256 total number of tokens subject to linear vesting/restriction removal (includes cliff credits but not each 'milestoneAward') -- `vestingCliffCredit`: uint128 lump sum of tokens which become vested at `vestingStartTime` -- `unlockingCliffCredit`: uint128 lump sum of tokens which become unlocked at `unlockStartTime` -- `vestingRate`: uint160 tokens per second that become vested; if RestrictedToken type, this amount corresponds to 'lapse rate' for tokens that become non-repurchasable -- `vestingStartTime`: uint48 linear vesting start time; if RestrictedToken type, this amount corresponds to 'lapse start time' -- `unlockRate`: uint160 tokens per second that become unlocked -- `unlockStartTime`: uint48 linear unlocking start time -- `tokenContract`: contract address of the ERC20 token included in the MetaVesT +| Name | Type | Description | +| :---: |:----:| :---: | +|`tokenStreamTotal`| uint256 | total number of tokens subject to linear vesting/restriction removal (includes cliff credits but not each `milestoneAward`) | +| `vestingCliffCredit` | uint128 | lump sum of tokens which become vested at `vestingStartTime`| +| `unlockingCliffCredit`| uint128 | lump sum of tokens which become unlocked at `unlockStartTime`| +| `vestingRate` | uint160 | tokens per second that become vested; if RestrictedToken type, this amount corresponds to 'lapse rate' for tokens that become non-repurchasable | +| `vestingStartTime`| uint48 | linear vesting start time; if RestrictedToken type, this amount corresponds to 'lapse start time'| +| `unlockRate` | uint160 | tokens per second that become unlocked | +| `unlockStartTime` | uint48 | linear unlocking start time | +| `tokenContract` | address | contract address of the ERC20 token included in the MetaVesT | `_milestones`: calldata array of `Milestone` structs for this grantee, comprised of: +| Name | Type | Description | +| :---: |:----:| :---: | +| `milestoneAward` | uint256 | per-milestone indexed lump sums of tokens vested upon corresponding milestone completion | +| `unlockOnCompletion` | bool | whether the `milestoneAward` is to be unlocked upon completion | +| `complete` | bool | whether the Milestone is satisfied and the `milestoneAward` is to be released | +| `conditionContracts` | address[] | contract addresses corresponding to condition(s) that must satisfied for this Milestone to be `complete`| -- `milestoneAward`: uint256 per-milestone indexed lump sums of tokens vested upon corresponding milestone completion -- `unlockOnCompletion`: boolean whether the `milestoneAward` is to be unlocked upon completion -- `complete`: bool whether the Milestone is satisfied and the `milestoneAward` is to be released -- `conditionContracts`: array of contract addresses corresponding to condition(s) that must satisfied for this Milestone to be 'complete' - -`_exercisePrice`: if `_type` == `TokenOption`, the uint256 price in at which a token option may be exercised in vesting token decimals but only up to payment decimal precision. If `_type` == `RestrictedToken`, this corresponds to the `_repurchasePrice`: the uint256 price at which the restricted tokens can be repurchased in vesting token decimals but only up to payment decimal precision. +`_exercisePrice`: if `_type` == `TokenOption`, the uint256 price in at which a token option may be exercised in vesting token decimals but only up to payment decimal precision. If `_type` == `RestrictedTokenAward`, this corresponds to the `_repurchasePrice`: the uint256 price at which the restricted tokens can be repurchased in vesting token decimals but only up to payment decimal precision. `_paymentToken`: contract address for the token used to pay for option exercises (for a grantee) or restricted token repurchases (for authority); immutable for this MetaVesT. -`_shortStopDuration`: uint256 if `_type` == `TokenOption`, length of period before vesting stop time and exercise deadline; if `_type` == `RestrictedToken`, length of period before lapse stop time and repurchase deadline +`_shortStopDuration`: uint256 if `_type` == `TokenOption`, length of period before vesting stop time and exercise deadline; if `_type` == `RestrictedTokenAward`, length of period before lapse stop time and repurchase deadline -When a grantee’s MetaVesT is created by authority, the full amount of corresponding tokens will be transferred from `authority` to the applicable newly deployed contract (either `VestingAllocation.sol`, `RestrictedTokenAllocation.sol`, or `TokenOptionAllocation.sol`) in the same transaction. This consists of any combination of: +When a grantee’s MetaVesT is created by `authority`, the full amount of corresponding tokens will be transferred from `authority` to the applicable newly deployed contract (either `VestingAllocation.sol`, `RestrictedTokenAllocation.sol`, or `TokenOptionAllocation.sol`) in the same transaction. This consists of any combination of: - Tokens to be linearly vested and unlocked, with any vested lump sum (cliff) credit at the `vestingStartTime` (`Allocation.vestingCliffCredit`) or unlocked lump sum (cliff) credit at the `unlockStartTime` (`Allocation.unlockingCliffCredit`). Altogether this amount is the `tokenStreamTotal` - Tokens to be vested and unlocked as a `milestoneAward`, according to any applicable `conditionContracts` assigned, within the `milestones` array of Milestone structs @@ -140,11 +140,36 @@ Token Option Allocation inherits the Base Allocation and contains the vesting an ### RestrictedTokenAllocation.sol -Restricted Token Allocation inherits the Base Allocation and contains the vesting and unlocking rate calculations, as well as repurchasable (`getAmountRepurchasable()`) tokens pursuant to the restricted token award terms. `Authority` repurchases available tokens by calling `repurchaseTokens()` with its applicable `_amount` and necessary payment amount in its balance which will be transferred to the contract during the call, and `grantee` claims the payment amount for any repurchased tokens by calling `claimRepurchasedTokens()`. +Restricted Token Allocation inherits the Base Allocation and contains the vesting and unlocking rate calculations, as well as repurchasable (`getAmountRepurchasable()`) tokens pursuant to the restricted token award terms. `authority` repurchases available tokens by calling `repurchaseTokens()` with its applicable `_amount` and necessary payment amount in its balance which will be transferred to the contract during the call, and `grantee` claims the payment amount for any repurchased tokens by calling `claimRepurchasedTokens()`. + +### Factories + +An overall MetaVesT framework is initiated by calling `deployMetavestAndController()` in `MetaVesTFactory`, which deploys a `MetaVesTController`, passing the contract addresses of the factories for each MetaVesT type. + +Each new MetaVesT in a framework (i.e. for a given `MetaVesTController`) is deployed via a correponding factory contract upon the `authority`'s call to `createMetavest()` in the `MetaVesTController`. The factory contracts for each MetaVesT type are `VestingAllocationFactory`, `TokenOptionFactory`, and `RestrictedTokenFactory`; each contains a `createAllocation()` function with the following params: +| Name | Type | Description | +| :---: |:----:| :---: | +| _allocationType | AllocationType enum | `Vesting`, `TokenOption`, or `RestrictedToken`| +| _grantee | address | address of the grantee receiving the applicable MetaVesTed allocation | +| _controller | address | contract address of the applicable MetaVesT Controller | +| _allocation | Allocation struct | Allocation struct details (see above in "Creating and Using MetaVesTs") | +| _milestones | Milestone[] | array of Milestone structs for this allocation (see above in "Creating and Using MetaVesTs") | +| _paymentToken | address | contract address for the payment token, used for option exercises by grantee for token options, or for repurchases by authority for restricted token awards | +| _exercisePrice | uint256 | price of the token option exercise in vesting token decimals for token options, or price at which the restricted tokens can be repurchased in vesting token decimals for restricted token awards | +| _shortStopDuration | uint256 | duration of the short stop for token options, or duration after termination during which restricted tokens can be repurchased for restricted token awards | ### Milestone Conditions -- Each `conditionContract` (used in milestones as well, either alone or in combination, as well as any `conditionCheck()` imposed by `dao` on MetaVesTController functions) is intended to follow the [MetaLeX condition contract specs](https://github.com/MetaLex-Tech/BORG-CORE/tree/main/src/libs/conditions) and must return a boolean. +Each `conditionContract` (used in milestones as well, either alone or in combination, as well as any `conditionCheck()` imposed by `dao` on MetaVesTController functions) is intended to follow the [MetaLeX condition contract specs](https://github.com/MetaLex-Tech/BORG-CORE/tree/main/src/libs/conditions). All condition contracts MUST have a `checkCondition` function which conforms to the following interface: + + interface ICondition { + function checkCondition( + address _contract, + bytes4 _functionSignature, + bytes memory data + ) external view returns (bool); + } + ## Restrictions and Considerations