███╗ ███╗███████╗████████╗ █████╗ ██╗ ██╗███████╗ ███████╗████████╗
████╗ ████║██╔════╝╚══██╔══╝ ██╔══██╗██║ ██║██╔════╝ ██╔════╝╚══██╔══╝
██╔████╔██║█████╗ ██║ ███████║██║ ██║█████╗ ███████╗ ██║
██║╚██╔╝██║██╔══╝ ██║ ██╔══██║ ██╗ ██╔╝██╔══╝ ╚════██║ ██║
██║ ╚═╝ ██║███████╗ ██║ ██║ ██║ ╚██╔═╝ ███████╗ ███████║ ██║
╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝
///BORG-Compatible Token Vesting/Lockup Protocol.
╚══╝ ╚════════╝ ╚═══╝ ╚════════════╝ ╚══════╝
MetaVesT is a BORG-compatible token vesting/lockup protocol for ERC20 tokens, supporting:
- Vesting allocations
- Token Options
- Restricted Token Awards
with both vesting and unlock schedules, rates, and cliffs, as well as any number of milestones (each with any number of conditions and tokens to be awarded), internal transfer abilities, and configurable governing power for MetaVesTed tokens.
Each MetaVest framework supports any number of grantees and different ERC20 tokens.
Each MetaVesT framework is designed to be on a per-BORG or more general per-authority basis.
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
This call deploys a 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).
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:
_type: enum 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 atvestingStartTimeunlockingCliffCredit: uint128 lump sum of tokens which become unlocked atunlockStartTimevestingRate: uint160 tokens per second that become vested; if RestrictedToken type, this amount corresponds to 'lapse rate' for tokens that become non-repurchasablevestingStartTime: uint48 linear vesting start time; if RestrictedToken type, this amount corresponds to 'lapse start time'unlockRate: uint160 tokens per second that become unlockedunlockStartTime: uint48 linear unlocking start timetokenContract: contract address of the ERC20 token included in the MetaVesT
_milestones: calldata array of Milestone structs for this grantee, comprised of:
milestoneAward: uint256 per-milestone indexed lump sums of tokens vested upon corresponding milestone completionunlockOnCompletion: boolean whether themilestoneAwardis to be unlocked upon completioncomplete: bool whether the Milestone is satisfied and themilestoneAwardis to be releasedconditionContracts: 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.
_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
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 theunlockStartTime(Allocation.unlockingCliffCredit). Altogether this amount is thetokenStreamTotal - Tokens to be vested and unlocked as a
milestoneAward, according to any applicableconditionContractsassigned, within themilestonesarray of Milestone structs
Tokens become withdrawable by the applicable grantee when both vested and unlocked (or in the case of a token option, vested and exercised, and unlocked), and when a milestone is confirmed complete according to its conditions.
In MetaVesTController.sol, authority is able to:
-
Create a new MetaVesT for a grantee that does not have an active MetaVesT and transfer the corresponding total amount of tokens
-
Terminate the vesting of an active MetaVesT
-
Add a milestone to an active MetaVesT and transfer the corresponding total amount of additional tokens
-
Repurchase tokens from an Restricted Token Allocation
-
Withdraw controller’s withdrawable tokens to the controller, and from controller to itself
-
Replace its own address
-
Propose any of the following amendments to a grantee’s MetaVesT, executing IFF it passes the
consentCheck()(either consent by the affected grantee, or > 50% consent by a majority-in-tokenGoverningPowerof grantees with the same token):- Terminate a MetaVesT entirely
- Amend a MetaVesT’s:
- transferability
- token option exercise price
- repurchase price
- stop time and short stop time
- unlock rate,
- vesting rate, and
- remove a milestone
Such amendment proposals have a one-week expiry. They are initiated by authority calling proposeMetavestAmendment(), and then consented either by the affected grantee calling consentToMetavestAmendment() or by grantees with the same MetaVesTed token voting in voteOnMetavestAmendment() and > 50% of such grantees weighted by tokenGoverningPower voting in favor.
To alter a MetaVesT’s type, grantee, or token, the current MetaVesT must be terminated entirely and a new one created. Any address can refresh any active MetaVesT and query whether any milestone in any active MetaVesT is completed (and if so, state updates and if completed, the milestoneAward is unlocked for the grantee).
In MetaVesTController.sol, dao is able to replace its own address, and impose conditions on authority's ability to call certain functions (including if consented) by calling updateFunctionCondition(). This requires a conditionContract be satisfied in order for the supplied msg.sig (function selector) to execute.
Each MetaVesT contract type inherits the Base Allocation. In each type of MetaVesT, a grantee is able to:
-
view the details of its MetaVesT via
getMetavestDetails() -
Query a milestone for completion by calling
confirmMilestone()with the applicable milestone index -
Query its
tokenGoverningPowercorresponding to nonwithdrawable, vested, or unlocked tokens (as configured by authority) -
If its MetaVesT is
transferable, transfer its MetaVesT to another address -
Withdraw any amount of its withdrawable tokens (calculated in
getAmountWithdrawable()) by callingwithdraw()
Vesting Allocation inherits the Base Allocation and contains the vesting and unlocking rate calculations, providing a grantee's amount of withdrawable tokens in getAmountWithdrawable().
Token Option Allocation inherits the Base Allocation and contains the vesting and unlocking rate calculations, as well as exercisable (getAmountExercisable()) and forfeited tokens pursuant to the token option terms. Grantee exercises the option by calling exerciseTokenOption() with its applicable _tokensToExercise and necessary payment amount in its balance which will be transferred to authority during the call, and authority recovers non-exercised tokens following the short stop time by calling recoverForfeitTokens().
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().
- Each
conditionContract(used in milestones as well, either alone or in combination, as well as anyconditionCheck()imposed bydaoon MetaVesTController functions) is intended to follow the MetaLeX condition contract specs and must return a boolean.
Each grantee address must be capable of calling functions (for example, to withdraw tokens)
MetaVesT does not support native gas tokens (ERC20 wrappers will be necessary) nor fee-on-transfer nor rebasing tokens.
Before you begin, ensure you have the following installed:
To set up the project locally, follow these steps:
-
Clone the repository
git clone https://github.com/MetaLex-Tech/MetaVesT cd MetaVesT -
Install dependencies
foundryup # Update Foundry tools forge install # Install project dependencies
-
Compile Contracts
forge build --optimize --optimizer-runs 200 --use solc:0.8.20