Flow: https://miro.com/app/board/uXjVMXyfa-o=/?moveToWidget=3458764557414692165&cot=14
The QV Allocation Strategy is designed to handle allocations and distributions in a pool based on Quadratic Voting. Quadratic Voting allows users to allocate tokens while considering the square of the number of tokens they possess. The distribution of funds in the pool is determined based on the total votes received by each allocation.
The QV Allocation Strategy is implemented as a Solidity contract named QVAllocationStrategy
. It follows the IAllocationStrategy
interface and requires initialization before use. The strategy operates within the context of a pool and works alongside a distribution strategy.
The QV Allocation Strategy depends on the following:
- OpenZeppelin's Multicall2 for supporting multicall operations.
EnumerableMap
from OpenZeppelin for managing allocations.
The QVAllocationStrategy
contract requires initialization to set the necessary parameters. The initialization function initialize
takes encoded parameters as input and sets the following:
poolId
: The ID of the pool associated with the strategy.allo
: The address of the allo contract.applicationStart
: The timestamp when the application period starts.applicationEnd
: The timestamp when the application period ends.allocationStart
: The timestamp when the allocation period starts.allocationEnd
: The timestamp when the allocation period ends.
The initialization function should be called only once during contract deployment.
The QV Allocation Strategy supports the following workflow for applying to the pool:
- Users encode application data and call the
applyToPool
function, passing the encoded data and their address. - The encoded data is decoded to obtain the following:
identityId
: The identity ID associated with the application.applicationMetaPtr
: A pointer to the metadata of the application.recipientAddress
: The address to receive allocations if the application is accepted.
- Custom logic can be implemented in the
applyToPool
function to gate applications based on additional checks, such as an Enterprise Authentication Service (EAS), registry verification, or proof-of-humanity (POH) contract address. - The application status is set to either
Pending
orReapplied
based on the custom logic. - The application is added or updated in the
applications
mapping, with theidentityId
as the key.
The QV Allocation Strategy supports the following workflow for allocating funds:
- Users encode allocation data and call the
allocate
function, passing the encoded data and their address. - The encoded data is decoded to obtain the following:
identityId
: The identity ID associated with the allocation.amount
: The amount of tokens being allocated.token
: The token type being allocated.
- The application status is checked to ensure it is not
None
orRejected
. - The allocator's validity is checked by verifying their vote count (
voteCounter
). - The allocator's vote count is checked to ensure it is less than the required threshold (
votesPerAllocator
). - The votes received by the application in the
applications
mapping are updated based on the quadratic voting logic:Vote Weight = (Number of Tokens)^2
.
The QV Allocation Strategy provides a function called generatePayouts
to calculate the distribution of funds based on the quadratic voting logic.
- The
generatePayouts
function is called, and it returns a bytes array containing the payout information. - The function uses the votes received from the
applications
mapping as input to run the quadratic voting calculations and generate the payout percentages. - The allocation tracker (
allocationTracker
) is used as input to determine the allocation amounts for each address. - The function loops through the
allocationTracker
and generates the payouts based on the calculated percentages. - The specific quadratic voting math is implemented to determine the payouts.
The QV Allocation Strategy provides several functions to customize the strategy's behavior:
updateAllocationStart(uint64 _allocationStart)
: Allows updating the allocation period's start timestamp.updateAllocationEnd(uint64 _allocationEnd)
: Allows updating the allocation period's end timestamp.updateApplicationStart(uint64 _applicationStart)
: Allows updating the application period's start timestamp.updateApplicationEnd(uint64 _applicationEnd)
: Allows updating the application period's end timestamp.reviewApplications(bytes[] memory _data)
: Allows reviewing multiple applications by providing an array of encoded application data. The function decodes the data to obtain the identity ID and status and updates the respective applications.
The QV Allocation Strategy uses the following additional data structures:
-
Application
: A struct containing the following information about an application:identityId
: The identity ID associated with the application.recipientAddress
: The address to receive allocations if the application is accepted.status
: The status of the application, which can be one of the following:None
: Initial status before an application is submitted.Pending
: Status indicating the application is pending review.Accepted
: Status indicating the application has been accepted.Rejected
: Status indicating the application has been rejected.Reapplied
: Status indicating the application has been reapplied after rejection.
metaPtr
: A pointer to the metadata of the application.votesReceived
: The number of votes received by the application.
-
applications
: A mapping that associates an address (identity ID) with anApplication
struct. It stores the applications submitted by different identities. -
voteCounter
: A mapping that tracks the number of votes cast by each user (identity ID).
struct Application {
address identityId;
address recipientAddress;
ApplicationStatus status;
MetaPtr metaPtr;
uint32 votesReceived;
}
// create a mapping of applicationId to application status
mapping(address => Application) applications;
// some means to track votes casted by user
mapping(address => uint32) voteCounter;
// identityId => allocationAmount
EnumerableMap.AddressToUintMap private allocationTracker;
uint256 totalAllocations;
Functions around updating constructor arguments.
function updateAllocationStart(uint64 _allocationStart) external {}
function updateAllocationEnd(uint64 _allocationEnd) external {}
function updateApplicationStart(uint64 _applicationStart) external {}
function updateApplicationEnd(uint64 _applicationEnd) external {}
Functions around actual functionality
function reviewApplications(bytes[] memory _data) external {
// decode data to get identity id and status
// update application status
}
- How do we new add application status (like reapplied)
The allocation strategy can be customized for different usecase
- Application Gating
- update initialize() (if applicable)
- update applyToPool to invoke contracts to check gating
- Allocation Gating
- update initialize() (if applicable)
- update allocate to invoke contracts to check gating
- Not using registry. AKA no indentityId
- in these cases, an applicationID would have to be generated by the AllocationStrategy