From f826adb9a657c145811cf05cb0b9d399e4d54290 Mon Sep 17 00:00:00 2001 From: Aditya Anand M C Date: Tue, 22 Oct 2024 13:29:12 +0530 Subject: [PATCH 1/5] add hackathon bounty --- .../examples/bounties/ProfileBounties.sol | 143 ++++++++++++++++++ .../bounties/ProfileBountiesStorage.sol | 22 +++ 2 files changed, 165 insertions(+) create mode 100644 contracts/strategies/examples/bounties/ProfileBounties.sol create mode 100644 contracts/strategies/examples/bounties/ProfileBountiesStorage.sol diff --git a/contracts/strategies/examples/bounties/ProfileBounties.sol b/contracts/strategies/examples/bounties/ProfileBounties.sol new file mode 100644 index 000000000..9224306e4 --- /dev/null +++ b/contracts/strategies/examples/bounties/ProfileBounties.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; + +// Internal Imports +// Interfaces +import {IAllo} from "contracts/core/interfaces/IAllo.sol"; +// Core Contracts +import {BaseStrategy} from "strategies/BaseStrategy.sol"; +// Internal Libraries +import {Transfer} from "contracts/core/libraries/Transfer.sol"; +import {Metadata} from "contracts/core/libraries/Metadata.sol"; +import {ProfileBountiesStorage} from "./ProfileBountiesStorage.sol"; + +// NOTE: Singleton contracts will require different extensions +// NOTE: Why do the extensions have constructors ? + +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⢿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⡟⠘⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣾⠻⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⡿⠀⠀⠸⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⢀⣠⣴⣴⣶⣶⣶⣦⣦⣀⡀⠀⠀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⡿⠃⠀⠙⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⠁⠀⠀⠀⢻⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠘⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⠃⠀⠀⠀⠀⠈⢿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⣰⣿⣿⣿⡿⠋⠁⠀⠀⠈⠘⠹⣿⣿⣿⣿⣆⠀⠀⠀ +// ⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠈⢿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⢰⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⡀⠀⠀ +// ⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣟⠀⡀⢀⠀⡀⢀⠀⡀⢈⢿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⡇⠀⠀ +// ⠀⠀⣠⣿⣿⣿⣿⣿⣿⡿⠋⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⡿⢿⠿⠿⠿⠿⠿⠿⠿⠿⠿⢿⣿⣿⣿⣷⡀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠸⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⠂⠀⠀ +// ⠀⠀⠙⠛⠿⠻⠻⠛⠉⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣿⣿⣿⣧⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⢻⣿⣿⣿⣷⣀⢀⠀⠀⠀⡀⣰⣾⣿⣿⣿⠏⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣧⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠹⢿⣿⣿⣿⣿⣾⣾⣷⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠙⠋⠛⠙⠋⠛⠙⠋⠛⠙⠋⠃⠀⠀⠀⠀⠀⠀⠀⠀⠠⠿⠻⠟⠿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⠿⠟⠿⠆⠀⠸⠿⠿⠟⠯⠀⠀⠀⠸⠿⠿⠿⠏⠀⠀⠀⠀⠀⠈⠉⠻⠻⡿⣿⢿⡿⡿⠿⠛⠁⠀⠀⠀⠀⠀⠀ +// allo.gitcoin.co + +/// @title ProfileBounties Strategy +/// @notice Strategy that allows allo profiles to create and manage bounties under one instance +// Every profile on the registry would deploy their own instance of this strategy +// and then manage all the bounties for that profile. +contract ProfileBounties is BaseStrategy, ProfileBountiesStorage { + using Transfer for address; + + /// =============================== + /// ========== Events ============ + /// =============================== + + event BountyCreated(uint256 indexed bountyId, Bounty bounty); + + /// =============================== + /// ======== Constructor ========== + /// =============================== + constructor(address _allo, string memory _strategyName) BaseStrategy(_allo, _strategyName) {} + + /// =============================== + /// ========= Initialize ========== + /// =============================== + function initialize(uint256 _poolId, bytes memory _data) external override { + __BaseStrategy_init(_poolId); + // NOTE: can this entire function be moved to the BaseStrategy ? + // and have another internal function which strategies can override + emit Initialized(_poolId, _data); + } + + /// ==================================== + /// ============ Internal ============== + /// ==================================== + + function _createBounty( + address _token, + uint256 _amount, + Metadata memory _metadata + ) internal { + Bounty _bounty = new Bounty({ + token: _token, + amount: _amount, + metadata: _metadata + }); + + bountyIdCounter++; + bounties[bountyIdCounter] = _bounty; + + emit BountyCreated(bountyIdCounter, _bounty); + } + + + function _processRecipient(address _recipientId, bool _isUsingRegistryAnchor, Metadata memory _metadata, bytes memory _extraData) internal override { + + uint256 bountyId = abi.decode(_extraData, (uint256)); + // TODO: implement + + } + + /// @inheritdoc BaseStrategy + function _allocate(address[] memory, uint256[] memory, bytes memory, address) internal virtual override { + revert NOT_IMPLEMENTED(); + } + + function _distribute(address[] memory _recipientIds, bytes memory _data, address _sender) + internal + virtual + override + { + uint256[] _bountyIds = abi.decode(_extraData, (uint256)); + + uint256 _bountiesLength = _bountyIds.length; + + if (_recipientIds.length != _bountiesLength) { + revert INVALID_DATA(); + } + + for (uint256 i = 0; i < _bountiesLength; i++) { + Bounty storage _bounty = bounties[bountyIds[i]]; + _bounty.status = BountyStatus.Closed; + _bounty.token.transfer(_recipientIds[i], _bounty.amount); + } + + // emit Distribute(_recipientIds, _data, _sender); + + } + + + + + /// ==================================== + /// ============ External ============== + /// ==================================== + + function createBounties( + address[] memory _tokens, + uint256[] memory _amounts, + Metadata[] memory _metadata + ) external { + uint256 _tokensLength = _tokens.length; + if (_tokensLength != _amounts.length || _tokensLength != _metadata.length) { + revert INVALID_DATA(); + } + + for (uint256 i = 0; i < _tokensLength; i++) { + _createBounty(_tokens[i], _amounts[i], _metadata[i]); + } + } + + z + + + +} diff --git a/contracts/strategies/examples/bounties/ProfileBountiesStorage.sol b/contracts/strategies/examples/bounties/ProfileBountiesStorage.sol new file mode 100644 index 000000000..d7dc77ff9 --- /dev/null +++ b/contracts/strategies/examples/bounties/ProfileBountiesStorage.sol @@ -0,0 +1,22 @@ +import {Metadata} from "contracts/core/libraries/Metadata.sol"; + +contract ProfileBountiesStorage { + + struct Bounty { + address token; + uint256 amount; + Metadata metadata; + // status + } + + /// =============================== + /// ========== Storage ============ + /// =============================== + + /// @notice Counter for the number of bounties created + uint256 public bountyIdCounter; + + /// @notice Mapping of bounty id to bounty + mapping(uint256 => Bounty) public bounties; + +} \ No newline at end of file From 15cba9875f2b8e9ff39ad6df9d50cc21f0ea8701 Mon Sep 17 00:00:00 2001 From: 0xKurt Date: Tue, 22 Oct 2024 11:11:19 +0200 Subject: [PATCH 2/5] updates --- .../examples/bounties/ProfileBounties.sol | 86 ++++++++++--------- .../bounties/ProfileBountiesStorage.sol | 16 +++- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/contracts/strategies/examples/bounties/ProfileBounties.sol b/contracts/strategies/examples/bounties/ProfileBounties.sol index 9224306e4..5e1cdd1ce 100644 --- a/contracts/strategies/examples/bounties/ProfileBounties.sol +++ b/contracts/strategies/examples/bounties/ProfileBounties.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.19; -// Internal Imports -// Interfaces -import {IAllo} from "contracts/core/interfaces/IAllo.sol"; // Core Contracts import {BaseStrategy} from "strategies/BaseStrategy.sol"; // Internal Libraries @@ -29,7 +26,7 @@ import {ProfileBountiesStorage} from "./ProfileBountiesStorage.sol"; // ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠙⠋⠛⠙⠋⠛⠙⠋⠛⠙⠋⠃⠀⠀⠀⠀⠀⠀⠀⠀⠠⠿⠻⠟⠿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⠿⠟⠿⠆⠀⠸⠿⠿⠟⠯⠀⠀⠀⠸⠿⠿⠿⠏⠀⠀⠀⠀⠀⠈⠉⠻⠻⡿⣿⢿⡿⡿⠿⠛⠁⠀⠀⠀⠀⠀⠀ // allo.gitcoin.co -/// @title ProfileBounties Strategy +/// @title ProfileBounties Strategy /// @notice Strategy that allows allo profiles to create and manage bounties under one instance // Every profile on the registry would deploy their own instance of this strategy // and then manage all the bounties for that profile. @@ -42,6 +39,10 @@ contract ProfileBounties is BaseStrategy, ProfileBountiesStorage { event BountyCreated(uint256 indexed bountyId, Bounty bounty); + error ProfileBounties_InvalidData(); + error ProfileBounties_NotImplemented(); + error ProfileBounties_AlreadyDistributed(); + /// =============================== /// ======== Constructor ========== /// =============================== @@ -52,7 +53,7 @@ contract ProfileBounties is BaseStrategy, ProfileBountiesStorage { /// =============================== function initialize(uint256 _poolId, bytes memory _data) external override { __BaseStrategy_init(_poolId); - // NOTE: can this entire function be moved to the BaseStrategy ? + // NOTE: can this entire function be moved to the BaseStrategy ? // and have another internal function which strategies can override emit Initialized(_poolId, _data); } @@ -61,16 +62,8 @@ contract ProfileBounties is BaseStrategy, ProfileBountiesStorage { /// ============ Internal ============== /// ==================================== - function _createBounty( - address _token, - uint256 _amount, - Metadata memory _metadata - ) internal { - Bounty _bounty = new Bounty({ - token: _token, - amount: _amount, - metadata: _metadata - }); + function _createBounty(address _token, uint256 _amount, Metadata memory _metadata) internal { + Bounty memory _bounty = Bounty({token: _token, acceptedRecipient: address(0), amount: _amount, metadata: _metadata}); bountyIdCounter++; bounties[bountyIdCounter] = _bounty; @@ -78,17 +71,33 @@ contract ProfileBounties is BaseStrategy, ProfileBountiesStorage { emit BountyCreated(bountyIdCounter, _bounty); } + function _revertInvalidBounty(uint256 _bountyId) internal { + Bounty memory bounty = bounties[_bountyId]; + if (bounty.token == address(0) || bounty.acceptedRecipient != address(0)) { + revert ProfileBounties_InvalidData(); + } + } - function _processRecipient(address _recipientId, bool _isUsingRegistryAnchor, Metadata memory _metadata, bytes memory _extraData) internal override { - + function _processRecipient( + address _recipientId, + bool _isUsingRegistryAnchor, + Metadata memory _metadata, + bytes memory _extraData + ) internal override { uint256 bountyId = abi.decode(_extraData, (uint256)); - // TODO: implement + _revertInvalidBounty(bountyId); + + BountyApplication memory _bountyApplication = + BountyApplication({bountyId: bountyId, recipientId: _recipientId, metadata: _metadata}); + // add additional fields here + bountyApplications[bountyId][_recipientId] = _bountyApplication; + // should we emit an event here or can we rely on the Registered Event? } /// @inheritdoc BaseStrategy function _allocate(address[] memory, uint256[] memory, bytes memory, address) internal virtual override { - revert NOT_IMPLEMENTED(); + revert ProfileBounties_NotImplemented(); } function _distribute(address[] memory _recipientIds, bytes memory _data, address _sender) @@ -96,48 +105,45 @@ contract ProfileBounties is BaseStrategy, ProfileBountiesStorage { virtual override { - uint256[] _bountyIds = abi.decode(_extraData, (uint256)); + uint256[] memory _bountyIds = abi.decode(_data, (uint256[])); uint256 _bountiesLength = _bountyIds.length; if (_recipientIds.length != _bountiesLength) { - revert INVALID_DATA(); + revert ProfileBounties_InvalidData(); } for (uint256 i = 0; i < _bountiesLength; i++) { - Bounty storage _bounty = bounties[bountyIds[i]]; - _bounty.status = BountyStatus.Closed; - _bounty.token.transfer(_recipientIds[i], _bounty.amount); - } - - // emit Distribute(_recipientIds, _data, _sender); + uint256 bountyId = _bountyIds[i]; + address recipientId = _recipientIds[i]; + _revertInvalidBounty(bountyId); - } + if(bountyApplications[bountyId][recipientId].bountyId != bountyId) { + revert ProfileBounties_InvalidData(); + } - + Bounty storage _bounty = bounties[bountyId]; + _bounty.acceptedRecipient = recipientId; + // todo: _bounty.token.transfer(recipientId, _bounty.amount); + } + // emit Distribute(_recipientIds, _data, _sender); + } /// ==================================== /// ============ External ============== /// ==================================== - function createBounties( - address[] memory _tokens, - uint256[] memory _amounts, - Metadata[] memory _metadata - ) external { + function createBounties(address[] memory _tokens, uint256[] memory _amounts, Metadata[] memory _metadata) + external + { uint256 _tokensLength = _tokens.length; if (_tokensLength != _amounts.length || _tokensLength != _metadata.length) { - revert INVALID_DATA(); + revert ProfileBounties_InvalidData(); } for (uint256 i = 0; i < _tokensLength; i++) { _createBounty(_tokens[i], _amounts[i], _metadata[i]); } } - - z - - - } diff --git a/contracts/strategies/examples/bounties/ProfileBountiesStorage.sol b/contracts/strategies/examples/bounties/ProfileBountiesStorage.sol index d7dc77ff9..7d7f8cfda 100644 --- a/contracts/strategies/examples/bounties/ProfileBountiesStorage.sol +++ b/contracts/strategies/examples/bounties/ProfileBountiesStorage.sol @@ -1,12 +1,20 @@ import {Metadata} from "contracts/core/libraries/Metadata.sol"; contract ProfileBountiesStorage { - struct Bounty { address token; + address acceptedRecipient; // instead of status uint256 amount; Metadata metadata; - // status + } + + // if we don't add any fields to application, + // we can remove this struct and save only the metadata + struct BountyApplication { + uint256 bountyId; + address recipientId; + Metadata metadata; + // maybe recipientAddress? } /// =============================== @@ -19,4 +27,6 @@ contract ProfileBountiesStorage { /// @notice Mapping of bounty id to bounty mapping(uint256 => Bounty) public bounties; -} \ No newline at end of file + /// @notice Mapping of bounty id to recipientAddress to bounty application + mapping(uint256 => mapping(address => BountyApplication)) public bountyApplications; +} From 29768b81d6ec90a9b8f647f525062ef9693a7241 Mon Sep 17 00:00:00 2001 From: 0xKurt Date: Tue, 22 Oct 2024 12:56:03 +0200 Subject: [PATCH 3/5] bounty extension --- .../examples/bounties/ProfileBounties.sol | 108 +----------- .../bounties/ProfileBountiesRanked.sol | 49 ++++++ .../extensions/bounties/BountyExtension.sol | 158 ++++++++++++++++++ .../register/RecipientsExtension.sol | 10 -- 4 files changed, 211 insertions(+), 114 deletions(-) create mode 100644 contracts/strategies/examples/bounties/ProfileBountiesRanked.sol create mode 100644 contracts/strategies/extensions/bounties/BountyExtension.sol diff --git a/contracts/strategies/examples/bounties/ProfileBounties.sol b/contracts/strategies/examples/bounties/ProfileBounties.sol index 5e1cdd1ce..45abbcd65 100644 --- a/contracts/strategies/examples/bounties/ProfileBounties.sol +++ b/contracts/strategies/examples/bounties/ProfileBounties.sol @@ -3,10 +3,7 @@ pragma solidity ^0.8.19; // Core Contracts import {BaseStrategy} from "strategies/BaseStrategy.sol"; -// Internal Libraries -import {Transfer} from "contracts/core/libraries/Transfer.sol"; -import {Metadata} from "contracts/core/libraries/Metadata.sol"; -import {ProfileBountiesStorage} from "./ProfileBountiesStorage.sol"; +import {BountyExtension} from "strategies/extensions/bounties/BountyExtension.sol"; // NOTE: Singleton contracts will require different extensions // NOTE: Why do the extensions have constructors ? @@ -30,19 +27,7 @@ import {ProfileBountiesStorage} from "./ProfileBountiesStorage.sol"; /// @notice Strategy that allows allo profiles to create and manage bounties under one instance // Every profile on the registry would deploy their own instance of this strategy // and then manage all the bounties for that profile. -contract ProfileBounties is BaseStrategy, ProfileBountiesStorage { - using Transfer for address; - - /// =============================== - /// ========== Events ============ - /// =============================== - - event BountyCreated(uint256 indexed bountyId, Bounty bounty); - - error ProfileBounties_InvalidData(); - error ProfileBounties_NotImplemented(); - error ProfileBounties_AlreadyDistributed(); - +contract ProfileBounties is BaseStrategy, BountyExtension { /// =============================== /// ======== Constructor ========== /// =============================== @@ -58,92 +43,7 @@ contract ProfileBounties is BaseStrategy, ProfileBountiesStorage { emit Initialized(_poolId, _data); } - /// ==================================== - /// ============ Internal ============== - /// ==================================== - - function _createBounty(address _token, uint256 _amount, Metadata memory _metadata) internal { - Bounty memory _bounty = Bounty({token: _token, acceptedRecipient: address(0), amount: _amount, metadata: _metadata}); - - bountyIdCounter++; - bounties[bountyIdCounter] = _bounty; - - emit BountyCreated(bountyIdCounter, _bounty); - } - - function _revertInvalidBounty(uint256 _bountyId) internal { - Bounty memory bounty = bounties[_bountyId]; - if (bounty.token == address(0) || bounty.acceptedRecipient != address(0)) { - revert ProfileBounties_InvalidData(); - } - } - - function _processRecipient( - address _recipientId, - bool _isUsingRegistryAnchor, - Metadata memory _metadata, - bytes memory _extraData - ) internal override { - uint256 bountyId = abi.decode(_extraData, (uint256)); - _revertInvalidBounty(bountyId); - - BountyApplication memory _bountyApplication = - BountyApplication({bountyId: bountyId, recipientId: _recipientId, metadata: _metadata}); - // add additional fields here - - bountyApplications[bountyId][_recipientId] = _bountyApplication; - // should we emit an event here or can we rely on the Registered Event? - } - - /// @inheritdoc BaseStrategy - function _allocate(address[] memory, uint256[] memory, bytes memory, address) internal virtual override { - revert ProfileBounties_NotImplemented(); - } - - function _distribute(address[] memory _recipientIds, bytes memory _data, address _sender) - internal - virtual - override - { - uint256[] memory _bountyIds = abi.decode(_data, (uint256[])); - - uint256 _bountiesLength = _bountyIds.length; - - if (_recipientIds.length != _bountiesLength) { - revert ProfileBounties_InvalidData(); - } - - for (uint256 i = 0; i < _bountiesLength; i++) { - uint256 bountyId = _bountyIds[i]; - address recipientId = _recipientIds[i]; - _revertInvalidBounty(bountyId); - - if(bountyApplications[bountyId][recipientId].bountyId != bountyId) { - revert ProfileBounties_InvalidData(); - } - - Bounty storage _bounty = bounties[bountyId]; - _bounty.acceptedRecipient = recipientId; - // todo: _bounty.token.transfer(recipientId, _bounty.amount); - } - - // emit Distribute(_recipientIds, _data, _sender); - } - - /// ==================================== - /// ============ External ============== - /// ==================================== - - function createBounties(address[] memory _tokens, uint256[] memory _amounts, Metadata[] memory _metadata) - external - { - uint256 _tokensLength = _tokens.length; - if (_tokensLength != _amounts.length || _tokensLength != _metadata.length) { - revert ProfileBounties_InvalidData(); - } - - for (uint256 i = 0; i < _tokensLength; i++) { - _createBounty(_tokens[i], _amounts[i], _metadata[i]); - } + function _getBountyIdFromExtraData(bytes memory _data) internal view override returns (uint256) { + return abi.decode(_data, (uint256)); } } diff --git a/contracts/strategies/examples/bounties/ProfileBountiesRanked.sol b/contracts/strategies/examples/bounties/ProfileBountiesRanked.sol new file mode 100644 index 000000000..88aaa8644 --- /dev/null +++ b/contracts/strategies/examples/bounties/ProfileBountiesRanked.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; + +// Core Contracts +import {BaseStrategy} from "strategies/BaseStrategy.sol"; +import {BountyExtension} from "strategies/extensions/bounties/BountyExtension.sol"; + +// NOTE: Singleton contracts will require different extensions +// NOTE: Why do the extensions have constructors ? + +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⢿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⡟⠘⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣾⠻⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⡿⠀⠀⠸⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⢀⣠⣴⣴⣶⣶⣶⣦⣦⣀⡀⠀⠀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⡿⠃⠀⠙⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⠁⠀⠀⠀⢻⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡀⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠘⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⠃⠀⠀⠀⠀⠈⢿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⣰⣿⣿⣿⡿⠋⠁⠀⠀⠈⠘⠹⣿⣿⣿⣿⣆⠀⠀⠀ +// ⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠈⢿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⢰⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⡀⠀⠀ +// ⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣟⠀⡀⢀⠀⡀⢀⠀⡀⢈⢿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⡇⠀⠀ +// ⠀⠀⣠⣿⣿⣿⣿⣿⣿⡿⠋⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⡿⢿⠿⠿⠿⠿⠿⠿⠿⠿⠿⢿⣿⣿⣿⣷⡀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠸⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⠂⠀⠀ +// ⠀⠀⠙⠛⠿⠻⠻⠛⠉⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣿⣿⣿⣧⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⢻⣿⣿⣿⣷⣀⢀⠀⠀⠀⡀⣰⣾⣿⣿⣿⠏⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣧⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠹⢿⣿⣿⣿⣿⣾⣾⣷⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀ +// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠙⠋⠛⠙⠋⠛⠙⠋⠛⠙⠋⠃⠀⠀⠀⠀⠀⠀⠀⠀⠠⠿⠻⠟⠿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⠿⠟⠿⠆⠀⠸⠿⠿⠟⠯⠀⠀⠀⠸⠿⠿⠿⠏⠀⠀⠀⠀⠀⠈⠉⠻⠻⡿⣿⢿⡿⡿⠿⠛⠁⠀⠀⠀⠀⠀⠀ +// allo.gitcoin.co + +/// @title ProfileBounties Strategy +/// @notice Strategy that allows allo profiles to create and manage bounties under one instance +// Every profile on the registry would deploy their own instance of this strategy +// and then manage all the bounties for that profile. +contract ProfileBountiesRanked is BaseStrategy, BountyExtension { + /// =============================== + /// ======== Constructor ========== + /// =============================== + constructor(address _allo, string memory _strategyName) BaseStrategy(_allo, _strategyName) {} + + /// =============================== + /// ========= Initialize ========== + /// =============================== + function initialize(uint256 _poolId, bytes memory _data) external override { + __BaseStrategy_init(_poolId); + // NOTE: can this entire function be moved to the BaseStrategy ? + // and have another internal function which strategies can override + emit Initialized(_poolId, _data); + } + + function _getBountyIdFromExtraData(bytes memory _data) internal view virtual returns (uint256) { + return abi.decode(_data, (uint256)); + } +} diff --git a/contracts/strategies/extensions/bounties/BountyExtension.sol b/contracts/strategies/extensions/bounties/BountyExtension.sol new file mode 100644 index 000000000..9723cfbb0 --- /dev/null +++ b/contracts/strategies/extensions/bounties/BountyExtension.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; + +// Internal Imports +// Core Contracts +import {BaseStrategy} from "strategies/BaseStrategy.sol"; +import {Transfer} from "contracts/core/libraries/Transfer.sol"; +import {Metadata} from "contracts/core/libraries/Metadata.sol"; + +/// @title NFT Gating Extension +/// @notice This contract is providing nft gating options for a strategy's calls +/// @dev This contract is inheriting BaseStrategy +abstract contract BountyExtension is BaseStrategy { + using Transfer for address; + + struct Bounty { + address token; + address acceptedRecipient; // instead of status + uint256 amount; + Metadata metadata; + } + + // if we don't add any fields to application, + // we can remove this struct and save only the metadata + struct BountyApplication { + uint256 bountyId; + address recipientId; + Metadata metadata; + } + // maybe recipientAddress? + + /// =============================== + /// ========== Storage ============ + /// =============================== + + /// @notice Counter for the number of bounties created + uint256 public bountyIdCounter; + + /// @notice Mapping of bounty id to bounty + mapping(uint256 => Bounty) public bounties; + + /// @notice Mapping of bounty id to recipientAddress to bounty application + mapping(uint256 => mapping(address => BountyApplication)) public bountyApplications; + + /// ================================ + /// ========== Events ============== + /// ================================ + event BountyCreated(uint256 indexed bountyId, Bounty bounty); + + /// ================================ + /// ========== Errors ============== + /// ================================ + + error ProfileBounties_InvalidData(); + error ProfileBounties_NotImplemented(); + error ProfileBounties_AlreadyDistributed(); + + /// ============================== + /// ========= Modifiers ========== + /// ============================== + + /// =============================== + /// ======= Internal Functions ==== + /// =============================== + + function __BountyExtension_init() internal { + // todo: no code? + } + + function _getBountyIdFromExtraData(bytes memory _data) internal virtual view returns (uint256); + + function _processRecipient( + address _recipientId, + bool _isUsingRegistryAnchor, + Metadata memory _metadata, + bytes memory _extraData + ) internal { + uint256 bountyId = _getBountyIdFromExtraData(_extraData); + _revertInvalidBounty(bountyId); + + BountyApplication memory _bountyApplication = + BountyApplication({bountyId: bountyId, recipientId: _recipientId, metadata: _metadata}); + // add additional fields here + + bountyApplications[bountyId][_recipientId] = _bountyApplication; + // should we emit an event here or can we rely on the Registered Event? + } + + /// @inheritdoc BaseStrategy + function _allocate(address[] memory, uint256[] memory, bytes memory, address) internal virtual override { + revert ProfileBounties_NotImplemented(); + } + + function _createBounty(address _token, uint256 _amount, Metadata memory _metadata) internal onlyPoolManager(msg.sender) { + Bounty memory _bounty = + Bounty({token: _token, acceptedRecipient: address(0), amount: _amount, metadata: _metadata}); + + bountyIdCounter++; + bounties[bountyIdCounter] = _bounty; + + emit BountyCreated(bountyIdCounter, _bounty); + } + + function _revertInvalidBounty(uint256 _bountyId) internal { + Bounty memory bounty = bounties[_bountyId]; + if (bounty.token == address(0) || bounty.acceptedRecipient != address(0)) { + revert ProfileBounties_InvalidData(); + } + } + + function _distribute(address[] memory _recipientIds, bytes memory _data, address _sender) + internal + virtual + override + onlyPoolManager(msg.sender) + { + uint256[] memory _bountyIds = abi.decode(_data, (uint256[])); + + uint256 _bountiesLength = _bountyIds.length; + + if (_recipientIds.length != _bountiesLength) { + revert ProfileBounties_InvalidData(); + } + + for (uint256 i = 0; i < _bountiesLength; i++) { + uint256 bountyId = _bountyIds[i]; + address recipientId = _recipientIds[i]; + _revertInvalidBounty(bountyId); + + if (bountyApplications[bountyId][recipientId].bountyId != bountyId) { + revert ProfileBounties_InvalidData(); + } + + Bounty storage _bounty = bounties[bountyId]; + _bounty.acceptedRecipient = recipientId; + // todo: _bounty.token.transfer(recipientId, _bounty.amount); + } + + // emit Distribute(_recipientIds, _data, _sender); + } + + /// ==================================== + /// ============ External ============== + /// ==================================== + + function createBounties(address[] memory _tokens, uint256[] memory _amounts, Metadata[] memory _metadata) + external + { + uint256 _tokensLength = _tokens.length; + if (_tokensLength != _amounts.length || _tokensLength != _metadata.length) { + revert ProfileBounties_InvalidData(); + } + + for (uint256 i = 0; i < _tokensLength; i++) { + _createBounty(_tokens[i], _amounts[i], _metadata[i]); + } + } +} diff --git a/contracts/strategies/extensions/register/RecipientsExtension.sol b/contracts/strategies/extensions/register/RecipientsExtension.sol index 721535551..7847f4f74 100644 --- a/contracts/strategies/extensions/register/RecipientsExtension.sol +++ b/contracts/strategies/extensions/register/RecipientsExtension.sol @@ -53,16 +53,6 @@ abstract contract RecipientsExtension is BaseStrategy, IRecipientsExtension, Err /// ========== Constructor ============= /// ==================================== - /// @notice Constructor to set the Allo contract - /// @param _allo Address of the Allo contract. - /// @param _strategyName Name of the strategy. - /// @param _reviewEachStatus true if custom review logic was added. - constructor(address _allo, string memory _strategyName, bool _reviewEachStatus) - BaseStrategy(_allo, _strategyName) - { - REVIEW_EACH_STATUS = _reviewEachStatus; - } - /// @notice Modifier to check if the registration is active /// @dev This will revert if the registration has not started or if the registration has ended. modifier onlyActiveRegistration() { From 3b379fdc1fc73f04f4b03af1dad11acc00fc191e Mon Sep 17 00:00:00 2001 From: 0xKurt Date: Tue, 22 Oct 2024 15:09:02 +0200 Subject: [PATCH 4/5] extension with flavor --- ...ed.sol => MultipleBountyDistributions.sol} | 41 ++++++++- .../bounties/ProfileBountiesStorage.sol | 32 ------- .../extensions/bounties/BountyExtension.sol | 85 +++++++++++++------ 3 files changed, 95 insertions(+), 63 deletions(-) rename contracts/strategies/examples/bounties/{ProfileBountiesRanked.sol => MultipleBountyDistributions.sol} (76%) delete mode 100644 contracts/strategies/examples/bounties/ProfileBountiesStorage.sol diff --git a/contracts/strategies/examples/bounties/ProfileBountiesRanked.sol b/contracts/strategies/examples/bounties/MultipleBountyDistributions.sol similarity index 76% rename from contracts/strategies/examples/bounties/ProfileBountiesRanked.sol rename to contracts/strategies/examples/bounties/MultipleBountyDistributions.sol index 88aaa8644..89d23fee0 100644 --- a/contracts/strategies/examples/bounties/ProfileBountiesRanked.sol +++ b/contracts/strategies/examples/bounties/MultipleBountyDistributions.sol @@ -27,15 +27,17 @@ import {BountyExtension} from "strategies/extensions/bounties/BountyExtension.so /// @notice Strategy that allows allo profiles to create and manage bounties under one instance // Every profile on the registry would deploy their own instance of this strategy // and then manage all the bounties for that profile. -contract ProfileBountiesRanked is BaseStrategy, BountyExtension { +contract MultipleBountyDistributions is BaseStrategy, BountyExtension { /// =============================== /// ======== Constructor ========== /// =============================== constructor(address _allo, string memory _strategyName) BaseStrategy(_allo, _strategyName) {} + mapping(uint256 => uint256) public distributedAmounts; /// =============================== /// ========= Initialize ========== /// =============================== + function initialize(uint256 _poolId, bytes memory _data) external override { __BaseStrategy_init(_poolId); // NOTE: can this entire function be moved to the BaseStrategy ? @@ -43,7 +45,40 @@ contract ProfileBountiesRanked is BaseStrategy, BountyExtension { emit Initialized(_poolId, _data); } - function _getBountyIdFromExtraData(bytes memory _data) internal view virtual returns (uint256) { - return abi.decode(_data, (uint256)); + function _getBountyIdFromExtraData(bytes memory _extraData) internal view virtual returns (uint256) { + return abi.decode(_extraData, (uint256)); + } + + function _handleDistributedBountyState(address _recipientId, uint256 _bountyId, bytes memory _data) + internal + override + { + // nothing to do + } + + function _transferDistribution(address _recipientId, uint256 _bountyId, bytes memory _data) internal override { + uint256 distributedAmount = distributedAmounts[_bountyId]; + uint256 amount = _getAmountFromBountyData(bounties[_bountyId].data); + + uint256 amountToDistribute = abi.decode(_data, (uint256)); + uint256 newDistributedAmount = distributedAmount + amountToDistribute; + if (newDistributedAmount <= amount) { + distributedAmounts[_bountyId] = newDistributedAmount; + // todo: _bounty.token.transfer(_recipientId, amountToDistribute); + } + if (newDistributedAmount == amount) { + Bounty storage _bounty = bounties[bountyId]; + _bounty.status = Status.Paid; + } + } + + // returns totalAmount and distribution amounts + function _decodeData(bytes memory _data) internal view virtual returns (uint256, uint256) { + return abi.decode(_data, (uint256, uint256)); + } + + function _getAmountFromBountyData(bytes memory _data) internal view virtual returns (uint256) { + (uint256 amount, _) = _decodeData(_data); + return amount; } } diff --git a/contracts/strategies/examples/bounties/ProfileBountiesStorage.sol b/contracts/strategies/examples/bounties/ProfileBountiesStorage.sol deleted file mode 100644 index 7d7f8cfda..000000000 --- a/contracts/strategies/examples/bounties/ProfileBountiesStorage.sol +++ /dev/null @@ -1,32 +0,0 @@ -import {Metadata} from "contracts/core/libraries/Metadata.sol"; - -contract ProfileBountiesStorage { - struct Bounty { - address token; - address acceptedRecipient; // instead of status - uint256 amount; - Metadata metadata; - } - - // if we don't add any fields to application, - // we can remove this struct and save only the metadata - struct BountyApplication { - uint256 bountyId; - address recipientId; - Metadata metadata; - // maybe recipientAddress? - } - - /// =============================== - /// ========== Storage ============ - /// =============================== - - /// @notice Counter for the number of bounties created - uint256 public bountyIdCounter; - - /// @notice Mapping of bounty id to bounty - mapping(uint256 => Bounty) public bounties; - - /// @notice Mapping of bounty id to recipientAddress to bounty application - mapping(uint256 => mapping(address => BountyApplication)) public bountyApplications; -} diff --git a/contracts/strategies/extensions/bounties/BountyExtension.sol b/contracts/strategies/extensions/bounties/BountyExtension.sol index 9723cfbb0..4b45b4f3b 100644 --- a/contracts/strategies/extensions/bounties/BountyExtension.sol +++ b/contracts/strategies/extensions/bounties/BountyExtension.sol @@ -13,11 +13,17 @@ import {Metadata} from "contracts/core/libraries/Metadata.sol"; abstract contract BountyExtension is BaseStrategy { using Transfer for address; + enum Status { + None, + Pending, + Paid + } + struct Bounty { address token; - address acceptedRecipient; // instead of status - uint256 amount; + Status status; Metadata metadata; + bytes data; } // if we don't add any fields to application, @@ -26,6 +32,7 @@ abstract contract BountyExtension is BaseStrategy { uint256 bountyId; address recipientId; Metadata metadata; + bytes data; } // maybe recipientAddress? @@ -67,7 +74,7 @@ abstract contract BountyExtension is BaseStrategy { // todo: no code? } - function _getBountyIdFromExtraData(bytes memory _data) internal virtual view returns (uint256); + function _getBountyIdFromExtraData(bytes memory _data) internal view virtual returns (uint256); function _processRecipient( address _recipientId, @@ -91,9 +98,11 @@ abstract contract BountyExtension is BaseStrategy { revert ProfileBounties_NotImplemented(); } - function _createBounty(address _token, uint256 _amount, Metadata memory _metadata) internal onlyPoolManager(msg.sender) { - Bounty memory _bounty = - Bounty({token: _token, acceptedRecipient: address(0), amount: _amount, metadata: _metadata}); + function _createBounty(address _token, Metadata memory _metadata, bytes _data) + internal + onlyPoolManager(msg.sender) + { + Bounty memory _bounty = Bounty({token: _token, status: Status.Pending, metadata: _metadata, data: _data}); bountyIdCounter++; bounties[bountyIdCounter] = _bounty; @@ -101,58 +110,78 @@ abstract contract BountyExtension is BaseStrategy { emit BountyCreated(bountyIdCounter, _bounty); } - function _revertInvalidBounty(uint256 _bountyId) internal { - Bounty memory bounty = bounties[_bountyId]; - if (bounty.token == address(0) || bounty.acceptedRecipient != address(0)) { - revert ProfileBounties_InvalidData(); - } - } - function _distribute(address[] memory _recipientIds, bytes memory _data, address _sender) internal virtual override onlyPoolManager(msg.sender) { - uint256[] memory _bountyIds = abi.decode(_data, (uint256[])); + uint256[] memory _bountyIds = _getBountyIdsFromDistributeData(_data); + bytes[] memory _datas = abi.decode(_data, (bytes[])); uint256 _bountiesLength = _bountyIds.length; + uint256 _datasLength = _datas.length; - if (_recipientIds.length != _bountiesLength) { + if (_recipientIds.length != _bountiesLength || _bountiesLength != _datasLength) { revert ProfileBounties_InvalidData(); } for (uint256 i = 0; i < _bountiesLength; i++) { uint256 bountyId = _bountyIds[i]; address recipientId = _recipientIds[i]; - _revertInvalidBounty(bountyId); - - if (bountyApplications[bountyId][recipientId].bountyId != bountyId) { - revert ProfileBounties_InvalidData(); - } - Bounty storage _bounty = bounties[bountyId]; - _bounty.acceptedRecipient = recipientId; - // todo: _bounty.token.transfer(recipientId, _bounty.amount); + _revertInvalidBounty(bountyId, _datas[i]); + _checkRecipientValidity(recipientId, bountyId, _datas[i]); + _handleDistributedBountyState(recipientId, bountyId, _datas[i]); + _transferDistribution(recipientId, bountyId, _datas[i]); } // emit Distribute(_recipientIds, _data, _sender); } + function _getBountyIdsFromDistributeData(bytes memory _data) internal view virtual returns (uint256[] memory) { + uint256[] memory _bountyIds = abi.decode(_data, (uint256[])); + return _bountyIds; + } + + function _revertInvalidBounty(uint256 _bountyId, bytes memory _data) internal virtual { + Bounty memory bounty = bounties[_bountyId]; + if (bounty.token == address(0) || bounty.status != Status.Pending) { + revert ProfileBounties_InvalidData(); + } + } + + function _checkRecipientValidity(address _recipientId, uint256 _bountyId, bytes memory _data) internal virtual { + if (bountyApplications[_bountyId][_recipientId].bountyId != _bountyId) { + revert ProfileBounties_InvalidData(); + } + } + + function _handleDistributedBountyState(address _recipientId, uint256 _bountyId, bytes memory _data) internal virtual { + Bounty storage _bounty = bounties[bountyId]; + _bounty.status = Status.Paid; + } + + function _getAmountFromBountyData(bytes memory _data) internal view virtual returns (uint256) { + return abi.decode(_data, (uint256)); + } + + function _transferDistribution(address _recipientId, uint256 _bountyId, bytes memory _data) internal virtual { + // todo: _bounty.token.transfer(_recipientId, _getAmountFromBountyData(bounties[_bountyId].data)); + } + /// ==================================== /// ============ External ============== /// ==================================== - function createBounties(address[] memory _tokens, uint256[] memory _amounts, Metadata[] memory _metadata) - external - { + function createBounties(address[] memory _tokens, bytes[] memory _data, Metadata[] memory _metadata) external { uint256 _tokensLength = _tokens.length; - if (_tokensLength != _amounts.length || _tokensLength != _metadata.length) { + if (_tokensLength != _data.length || _tokensLength != _metadata.length) { revert ProfileBounties_InvalidData(); } for (uint256 i = 0; i < _tokensLength; i++) { - _createBounty(_tokens[i], _amounts[i], _metadata[i]); + _createBounty(_tokens[i], _data[i], _metadata[i]); } } } From 5aa260c02466ddde8ab4a4e439c64e3bf2fd5881 Mon Sep 17 00:00:00 2001 From: 0xKurt Date: Tue, 22 Oct 2024 15:22:26 +0200 Subject: [PATCH 5/5] add back recipient extension --- .../examples/bounties/MultipleBountyDistributions.sol | 5 +++-- contracts/strategies/examples/bounties/ProfileBounties.sol | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/strategies/examples/bounties/MultipleBountyDistributions.sol b/contracts/strategies/examples/bounties/MultipleBountyDistributions.sol index 89d23fee0..1856d536f 100644 --- a/contracts/strategies/examples/bounties/MultipleBountyDistributions.sol +++ b/contracts/strategies/examples/bounties/MultipleBountyDistributions.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.19; // Core Contracts import {BaseStrategy} from "strategies/BaseStrategy.sol"; import {BountyExtension} from "strategies/extensions/bounties/BountyExtension.sol"; +import {RecipientExtension} from "strategies/extensions/recipients/RecipientsExtension.sol"; // NOTE: Singleton contracts will require different extensions // NOTE: Why do the extensions have constructors ? @@ -27,7 +28,7 @@ import {BountyExtension} from "strategies/extensions/bounties/BountyExtension.so /// @notice Strategy that allows allo profiles to create and manage bounties under one instance // Every profile on the registry would deploy their own instance of this strategy // and then manage all the bounties for that profile. -contract MultipleBountyDistributions is BaseStrategy, BountyExtension { +contract MultipleBountyDistributions is BaseStrategy, RecipientExtension, BountyExtension { /// =============================== /// ======== Constructor ========== /// =============================== @@ -53,7 +54,7 @@ contract MultipleBountyDistributions is BaseStrategy, BountyExtension { internal override { - // nothing to do + // nothing to do } function _transferDistribution(address _recipientId, uint256 _bountyId, bytes memory _data) internal override { diff --git a/contracts/strategies/examples/bounties/ProfileBounties.sol b/contracts/strategies/examples/bounties/ProfileBounties.sol index 45abbcd65..bfced9db1 100644 --- a/contracts/strategies/examples/bounties/ProfileBounties.sol +++ b/contracts/strategies/examples/bounties/ProfileBounties.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; // Core Contracts import {BaseStrategy} from "strategies/BaseStrategy.sol"; import {BountyExtension} from "strategies/extensions/bounties/BountyExtension.sol"; - +import {RecipientExtension} from "strategies/extensions/recipients/RecipientsExtension.sol"; // NOTE: Singleton contracts will require different extensions // NOTE: Why do the extensions have constructors ? @@ -27,7 +27,7 @@ import {BountyExtension} from "strategies/extensions/bounties/BountyExtension.so /// @notice Strategy that allows allo profiles to create and manage bounties under one instance // Every profile on the registry would deploy their own instance of this strategy // and then manage all the bounties for that profile. -contract ProfileBounties is BaseStrategy, BountyExtension { +contract ProfileBounties is BaseStrategy, RecipientExtension, BountyExtension { /// =============================== /// ======== Constructor ========== /// ===============================