diff --git a/.gitmodules b/.gitmodules index 9296efd..b1a15eb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/allo-v2.1"] + path = lib/allo-v2.1 + url = https://github.com/allo-protocol/allo-v2.1.git diff --git a/compiler_config.json b/compiler_config.json new file mode 100644 index 0000000..1e4845d --- /dev/null +++ b/compiler_config.json @@ -0,0 +1,36 @@ +{ + "language": "Solidity", + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.legacyAssembly", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "evm.gasEstimates", + "evm.assembly" + ] + } + }, + "remappings": [ + "ds-test/=lib/forge-std/lib/ds-test/src/", + "forge-std/=lib/forge-std/src/", + "strategies/=lib/allo-v2.1/contracts/strategies/", + "libraries/=lib/allo-v2.1/contracts/core/libraries/", + "solady/=lib/allo-v2.1/lib/solady/src/" + ] + } +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 1c7d9b8..e16791d 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,6 +8,9 @@ optimizer_runs = 200 # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options remappings = [ - "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", - "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/" + "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", + "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", + "strategies/=lib/allo-v2.1/contracts/strategies/", + "libraries/=lib/allo-v2.1/contracts/core/libraries/", + "solady/=lib/allo-v2.1/lib/solady/src/", ] diff --git a/lib/allo-v2.1 b/lib/allo-v2.1 new file mode 160000 index 0000000..258f3c6 --- /dev/null +++ b/lib/allo-v2.1 @@ -0,0 +1 @@ +Subproject commit 258f3c6aa2823577e3e1f95b1dbed8b6d9482a1d diff --git a/src/HyperStrategy.sol b/src/HyperStrategy.sol new file mode 100644 index 0000000..4bf85d9 --- /dev/null +++ b/src/HyperStrategy.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {AccessControl} from "lib/allo-v2.1/lib/openzeppelin-contracts/contracts/access/AccessControl.sol"; +import {BaseStrategy} from "strategies/BaseStrategy.sol"; +import {IAllo} from "lib/allo-v2.1/contracts/core/interfaces/IAllo.sol"; +import {IHypercertToken} from "./interfaces/IHypercertToken.sol"; +import {IHyperfund} from "./interfaces/IHyperfund.sol"; + +contract HyperStrategy is AccessControl, BaseStrategy { + // Roles + bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); + + // Interfaces + IHyperfund public hyperfund; + IHypercertToken public hypercertMinter; + + // Events + event Donated(address indexed donor, address token, uint256 amount, uint256 indexed hypercertUnits); + + // Errors + error NOOP(); + + function initialize(uint256 _poolId, bytes memory _data) external virtual override { + (address _manager, address _hyperfund) = abi.decode(_data, (address, address)); + + hyperfund = IHyperfund(_hyperfund); + hypercertMinter = IHypercertToken(hyperfund.hypercertMinter()); + + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + _grantRole(MANAGER_ROLE, _manager); + __BaseStrategy_init(_poolId); + emit Initialized(_poolId, _data); + } + + /// =============================== + /// ========= Constructor ========= + /// =============================== + constructor(address _allo, string memory _name) BaseStrategy(_allo, _name) {} + + /// @notice Allocate funds hyperfund and hyperstaker pools + /// @param _data The data to decode + /// @param _sender The sender + function _allocate(bytes memory _data, address _sender) internal virtual override onlyRole(MANAGER_ROLE) { + (address[] memory _recipients, uint256[] memory _amounts) = abi.decode(_data, (address[], uint256[])); + + // Assert recipient and amounts length are equal + if (_recipients.length != _amounts.length) { + revert ARRAY_MISMATCH(); + } + + IAllo.Pool memory pool = allo.getPool(poolId); + for (uint256 i; i < _recipients.length; ++i) { + uint256 _amount = _amounts[i]; + address _recipientAddress = _recipients[i]; + + _transferAmount(pool.token, _recipientAddress, _amount); + + emit Allocated(_recipientAddress, _amount, pool.token, _sender); + } + } + + function _afterIncreasePoolAmount(uint256 _amount) internal virtual override { + IAllo.Pool memory pool = allo.getPool(poolId); + uint256 hypercertFraction = hyperfund.hypercertId(); + int256 multiplier = hyperfund.tokenMultipliers(pool.token); + uint256 units; + if (multiplier > 0) { + units = _amount * uint256(multiplier); + } else { + units = _amount / uint256(-multiplier); + } + + uint256 availableSupply = hypercertMinter.unitsOf(hypercertFraction); + require(availableSupply >= units); + _mintFraction(tx.origin, units, hypercertFraction); + emit Donated(tx.origin, pool.token, _amount, units); + } + + function _distribute(address[] memory _recipientIds, bytes memory _recipientAmounts, address _sender) + internal + virtual + override + { + revert NOOP(); + } + + function _getRecipientStatus(address) internal view virtual override returns (Status) { + revert NOOP(); + } + + function _isValidAllocator(address _allocator) internal view virtual override returns (bool) {} + + function _registerRecipient(bytes memory _data, address _sender) internal virtual override returns (address) {} + + function _getPayout(address _recipientId, bytes memory _data) + internal + view + virtual + override + returns (PayoutSummary memory) + {} + + function _mintFraction(address account, uint256 units, uint256 hypercertId) internal { + uint256[] memory newallocations = new uint256[](2); + newallocations[0] = hypercertMinter.unitsOf(hypercertId) - units; + newallocations[1] = units; + hypercertMinter.splitFraction(account, hypercertId, newallocations); + } + + receive() external payable {} +} diff --git a/src/HyperstrategyFactory.sol b/src/HyperstrategyFactory.sol new file mode 100644 index 0000000..15df89d --- /dev/null +++ b/src/HyperstrategyFactory.sol @@ -0,0 +1,29 @@ +// Creating a seperate factory for compatibility issues +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "./HyperStrategy.sol"; // Import Hyperstrategy contract + +contract HyperStrategyFactory { + address hypercertMinter; + + // Event to emit when a new HyperStrategy is created + event HyperstrategyCreated(address indexed hyperstrategyAddress, address allo); + + constructor(address _hypercertMinter) { + require(_hypercertMinter != address(0)); + hypercertMinter = _hypercertMinter; + } + + // Function to create a new Hyperstrategy + function createHyperstrategy(address _allo, string memory _name) external returns (address) { + require(_allo != address(0)); + + address newHyperStrategy = address(new HyperStrategy(_allo, _name)); + require(newHyperStrategy != address(0)); + + IHypercertToken(hypercertMinter).setApprovalForAll(newHyperStrategy, true); + emit HyperstrategyCreated(newHyperStrategy, _allo); + return newHyperStrategy; + } +} diff --git a/src/interfaces/IHypercertToken.sol b/src/interfaces/IHypercertToken.sol index 4a7cf2d..6ad3f5c 100644 --- a/src/interfaces/IHypercertToken.sol +++ b/src/interfaces/IHypercertToken.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.28; +pragma solidity ^0.8.18; interface IHypercertToken { /** diff --git a/src/interfaces/IHyperfund.sol b/src/interfaces/IHyperfund.sol new file mode 100644 index 0000000..1d2e48d --- /dev/null +++ b/src/interfaces/IHyperfund.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +interface IHyperfund { + function hypercertId() external view returns (uint256); + function tokenMultipliers(address token) external view returns (int256); + function hypercertMinter() external view returns (address); +}