Skip to content
Merged

docs #61

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 74 additions & 42 deletions src/Hyperfund.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ contract Hyperfund is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgrade
uint256 public hypercertUnits;

// erc20 token allowlist, 0 means the token is not allowed
// negative multiplier means the total amount of hypercert units is smaller than the amount of tokens it represents and rounding is applied
// negative multiplier means the total amount of Hypercert units is smaller than the amount of tokens it represents and rounding is applied
mapping(address token => int256 multiplier) public tokenMultipliers;

// allowlist for non-financial contributions, 0 means the contributor is not allowed
Expand Down Expand Up @@ -55,10 +55,10 @@ contract Hyperfund is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgrade
}

/// @notice Initialize the contract, to be called by proxy
/// @notice NOTE: after deployment of proxy, the hypercert owner must approve the proxy contract to split and burn fractions
/// @notice NOTE: after deployment of proxy, the Hypercert owner must approve the proxy contract to split and burn fractions
/// by calling hypercertMinter.setApprovalForAll(address(proxy), true)
/// @param _hypercertMinter The address of the hypercert minter contract
/// @param _hypercertTypeId The id of the hypercert type
/// @param _hypercertMinter The address of the Hypercert minter contract
/// @param _hypercertTypeId The id of the Hypercert type
/// @param _admin The address that will have the DEFAULT_ADMIN_ROLE
/// @param _manager The address that will have the MANAGER_ROLE
/// @param _pauser The address that will have the PAUSER_ROLE
Expand Down Expand Up @@ -88,15 +88,21 @@ contract Hyperfund is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgrade

function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {}

/// @notice set the multiplier for an allowlisted token, 0 means the token is not allowed
// ADMIN FUNCTIONS

/// @notice Set the multiplier for an allowlisted token, 0 means the token is not allowed, only callable by a manager
/// @param _token address of the token
/// @param _multiplier multiplier for the token, negative means the total amount of hypercert units is smaller
/// than the amount of tokens it represents and rounding is applied
/// @param _multiplier multiplier for the token, negative multiplier means the total amount of Hypercert units is
/// smaller than the amount of tokens it represents and rounding is applied
function allowlistToken(address _token, int256 _multiplier) external onlyRole(MANAGER_ROLE) {
tokenMultipliers[_token] = _multiplier;
emit TokenAllowlisted(_token, _multiplier);
}

/// @notice Withdraw funds from the hyperfund, only callable by a manager
/// @param _token address of the token to withdraw, address(0) for native token
/// @param _amount amount of the token to withdraw
/// @param _to address to send the funds to
function withdrawFunds(address _token, uint256 _amount, address _to) external onlyRole(MANAGER_ROLE) {
if (_token == address(0)) {
payable(_to).transfer(_amount);
Expand All @@ -106,15 +112,48 @@ contract Hyperfund is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgrade
emit FundsWithdrawn(_token, _amount, _to);
}

/// @notice Issue a Hypercert fraction for a non-financial contribution, only callable by a manager
/// @param _contributor address of the contributor to receive the Hypercert fraction
/// @param _units amount of units to register as a non-financial contribution
function nonfinancialContribution(address _contributor, uint256 _units) external whenNotPaused onlyRole(MANAGER_ROLE) {
uint256 availableSupply = hypercertMinter.unitsOf(hypercertId);
require(availableSupply >= _units, AmountExceedsAvailableSupply(availableSupply));
_nonfinancialContribution(_contributor, _units);
}

/// @notice Issue Hypercert fractions for non-financial contributions, only callable by a manager
/// @param _contributors array of addresses of the contributors
/// @param _units array of amounts of units to register as non-financial contributions
function nonFinancialContributions(address[] calldata _contributors, uint256[] calldata _units)
external
onlyRole(MANAGER_ROLE)
{
require(_contributors.length == _units.length, ArrayLengthsMismatch());
uint256 totalUnits = 0;
for (uint256 i = 0; i < _units.length; i++) {
totalUnits += _units[i];
}
uint256 availableSupply = hypercertMinter.unitsOf(hypercertId);
require(availableSupply >= totalUnits, AmountExceedsAvailableSupply(availableSupply));

for (uint256 i = 0; i < _contributors.length; i++) {
_nonfinancialContribution(_contributors[i], _units[i]);
}
}

/// @notice pause the hyperfund, only callable by a pauser
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}

/// @notice unpause the hyperfund, only callable by a pauser
function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}

/// @notice send funds to the hyperfund and receive a hypercert fraction
// USER FUNCTIONS

/// @notice Send funds to the hyperfund and receive a Hypercert fraction
/// @param _token address of the token to send, must be allowlisted. address(0) for native token
/// @param _amount amount of the token to send
function fund(address _token, uint256 _amount) external payable whenNotPaused {
Expand All @@ -132,42 +171,17 @@ contract Hyperfund is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgrade
emit Funded(_token, _amount);
}

function nonfinancialContribution(address _contributor, uint256 _units)
external
whenNotPaused
onlyRole(MANAGER_ROLE)
{
uint256 availableSupply = hypercertMinter.unitsOf(hypercertId);
require(availableSupply >= _units, AmountExceedsAvailableSupply(availableSupply));
_nonfinancialContribution(_contributor, _units);
}

function nonFinancialContributions(address[] calldata _contributors, uint256[] calldata _units)
external
onlyRole(MANAGER_ROLE)
{
require(_contributors.length == _units.length, ArrayLengthsMismatch());
uint256 totalUnits = 0;
for (uint256 i = 0; i < _units.length; i++) {
totalUnits += _units[i];
}
uint256 availableSupply = hypercertMinter.unitsOf(hypercertId);
require(availableSupply >= totalUnits, AmountExceedsAvailableSupply(availableSupply));

for (uint256 i = 0; i < _contributors.length; i++) {
_nonfinancialContribution(_contributors[i], _units[i]);
}
}

/// @notice redeem a hypercert fraction for the corresponding amount of tokens
/// NOTE: sender must first approve the hyperfund to burn the hypercert fraction, by calling hypercertMinter.setApprovalForAll(address(this), true)
/// @param _fractionId id of the hypercert fraction
/// @notice Redeem a Hypercert fraction for the corresponding amount of tokens. User must have previously contributed
/// enough non-financial units
/// NOTE: sender must first approve the hyperfund to burn the Hypercert fraction, by calling hypercertMinter.setApprovalForAll(address(this), true)
/// @param _fractionId id of the Hypercert fraction
/// @param _token address of the token to redeem, must be allowlisted. address(0) for native token
function redeem(uint256 _fractionId, address _token) external whenNotPaused {
require(nonfinancialContributions[msg.sender] != 0, NotAllowlisted());
require(hypercertMinter.ownerOf(_fractionId) == msg.sender, Unauthorized());
require(_isFraction(_fractionId), NotFractionOfThisHypercert(hypercertTypeId));
uint256 units = hypercertMinter.unitsOf(_fractionId);
require(units != 0, InvalidAmount());
uint256 tokenAmount = _unitsToTokenAmount(_token, units);
if (_token == address(0)) {
(bool success,) = payable(msg.sender).call{value: tokenAmount}("");
Expand All @@ -180,6 +194,11 @@ contract Hyperfund is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgrade
emit FractionRedeemed(_fractionId, _token, tokenAmount);
}

// INTERNAL FUNCTIONS

/// @notice Mint a Hypercert fraction for a non-financial contributor and register the amount of non-financial units contributed
/// @param _contributor address of the contributor to receive the Hypercert fraction
/// @param _units amount of units to register as a non-financial contribution
function _nonfinancialContribution(address _contributor, uint256 _units) internal {
require(_contributor != address(0), InvalidAddress());
require(_units != 0, InvalidAmount());
Expand All @@ -188,13 +207,20 @@ contract Hyperfund is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgrade
emit NonfinancialContribution(_contributor, _units);
}

function _mintFraction(address account, uint256 units) internal {
/// @notice Split a fraction of the Hypercert for a contributor
/// @param _contributor address of the contributor to receive the Hypercert fraction
/// @param _units amount of units to mint
function _mintFraction(address _contributor, uint256 _units) internal {
uint256[] memory newallocations = new uint256[](2);
newallocations[0] = hypercertMinter.unitsOf(hypercertId) - units;
newallocations[1] = units;
hypercertMinter.splitFraction(account, hypercertId, newallocations);
newallocations[0] = hypercertMinter.unitsOf(hypercertId) - _units;
newallocations[1] = _units;
hypercertMinter.splitFraction(_contributor, hypercertId, newallocations);
}

/// @notice Convert token amount to Hypercert units using the token's multiplier
/// @param _token address of the token
/// @param _amount amount of the token
/// @return units amount of Hypercert units
function _tokenAmountToUnits(address _token, uint256 _amount) internal view returns (uint256 units) {
int256 multiplier = tokenMultipliers[_token];
if (multiplier > 0) {
Expand All @@ -204,6 +230,10 @@ contract Hyperfund is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgrade
}
}

/// @notice Convert Hypercert units to token amount using the token's multiplier
/// @param _token address of the token
/// @param _units amount of Hypercert units
/// @return amount amount of the token
function _unitsToTokenAmount(address _token, uint256 _units) internal view returns (uint256 amount) {
int256 multiplier = tokenMultipliers[_token];
if (multiplier > 0) {
Expand All @@ -213,6 +243,8 @@ contract Hyperfund is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgrade
}
}

/// @notice Check if a Hypercert belongs to the correct Hypercert type
/// @param _fractionId id of the Hypercert fraction
function _isFraction(uint256 _fractionId) internal view returns (bool) {
return _fractionId & TYPE_MASK == hypercertTypeId;
}
Expand Down
27 changes: 22 additions & 5 deletions src/HyperfundFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ contract HyperfundFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable
error AlreadyDeployed();
error NotOwnerOfHypercert();

// Event to emit when a new Hyperfund is created
event HyperfundCreated(
address indexed hyperfundAddress,
uint256 indexed hypercertId,
Expand All @@ -31,7 +30,6 @@ contract HyperfundFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable
address upgrader
);

// Event to emit when a new Hyperstaker is created
event HyperstakerCreated(
address indexed hyperstakerAddress,
uint256 indexed hypercertId,
Expand All @@ -41,7 +39,6 @@ contract HyperfundFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable
address upgrader
);

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
Expand All @@ -55,7 +52,13 @@ contract HyperfundFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

// Function to create a new Hyperfund
/// @notice Create a new Hyperfund
/// @param hypercertTypeId id of the Hypercert type to create the Hyperfund for
/// @param admin address of the admin of the Hyperfund
/// @param manager address of the manager of the Hyperfund
/// @param pauser address of the pauser of the Hyperfund
/// @param upgrader address of the upgrader of the Hyperfund
/// @return address of the new Hyperfund
function createHyperfund(uint256 hypercertTypeId, address admin, address manager, address pauser, address upgrader)
external
returns (address)
Expand All @@ -79,7 +82,13 @@ contract HyperfundFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable
return newHyperfund;
}

// Function to create a new Hyperstaker
/// @notice Create a new Hyperstaker
/// @param hypercertTypeId id of the Hypercert type to create the Hyperstaker for
/// @param admin address of the admin of the Hyperstaker
/// @param manager address of the manager of the Hyperstaker
/// @param pauser address of the pauser of the Hyperstaker
/// @param upgrader address of the upgrader of the Hyperstaker
/// @return address of the new Hyperstaker
function createHyperstaker(
uint256 hypercertTypeId,
address admin,
Expand All @@ -105,6 +114,14 @@ contract HyperfundFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable
return newHyperstaker;
}

/// @notice Create a new Hyperfund and Hyperstaker
/// @param hypercertTypeId id of the Hypercert type to create the Hyperfund and Hyperstaker for
/// @param admin address of the admin of the Hyperfund and Hyperstaker
/// @param manager address of the manager of the Hyperfund and Hyperstaker
/// @param pauser address of the pauser of the Hyperfund and Hyperstaker
/// @param upgrader address of the upgrader of the Hyperfund and Hyperstaker
/// @return hyperfund address of the new Hyperfund
/// @return hyperstaker address of the new Hyperstaker
function createProject(uint256 hypercertTypeId, address admin, address manager, address pauser, address upgrader)
external
returns (address hyperfund, address hyperstaker)
Expand Down
Loading
Loading