From 430f272e75e0085093594cca215bcc4c58bb0b0c Mon Sep 17 00:00:00 2001 From: Dan Rusnac Date: Tue, 10 Sep 2024 18:36:07 +0200 Subject: [PATCH] feat: use latest upgraded interface --- .../interfaces/IX2EarnRewardsPool.sol | 54 ++++++- .../contracts/mock/X2EarnRewardsPoolMock.sol | 148 +++++++++++++++++- 2 files changed, 198 insertions(+), 4 deletions(-) diff --git a/apps/contracts/contracts/interfaces/IX2EarnRewardsPool.sol b/apps/contracts/contracts/interfaces/IX2EarnRewardsPool.sol index 2246645..49d365e 100644 --- a/apps/contracts/contracts/interfaces/IX2EarnRewardsPool.sol +++ b/apps/contracts/contracts/interfaces/IX2EarnRewardsPool.sol @@ -77,7 +77,59 @@ interface IX2EarnRewardsPool { * @param appId the app id that is emitting the reward * @param amount the amount of B3TR token the user is rewarded with * @param receiver the address of the user that performed the sustainable action and is rewarded - * @param proof a JSON file uploaded on IPFS by the app that adds information on the type of action that was performed + * @param proof deprecated argument, pass an empty string instead */ function distributeReward(bytes32 appId, uint256 amount, address receiver, string memory proof) external; + + /** + * @dev Function used by x2earn apps to reward users that performed sustainable actions. + * @notice This function is depracted in favor of distributeRewardWithProof. + * + * @param appId the app id that is emitting the reward + * @param amount the amount of B3TR token the user is rewarded with + * @param receiver the address of the user that performed the sustainable action and is rewarded + * @param proof the JSON string that contains the proof and impact of the sustainable action + */ + function distributeRewardDeprecated(bytes32 appId, uint256 amount, address receiver, string memory proof) external; + + /** + * @dev Function used by x2earn apps to reward users that performed sustainable actions. + * + * @param appId the app id that is emitting the reward + * @param amount the amount of B3TR token the user is rewarded with + * @param receiver the address of the user that performed the sustainable action and is rewarded + * @param proofTypes the types of the proof of the sustainable action + * @param proofValues the values of the proof of the sustainable action + * @param impactCodes the codes of the impacts of the sustainable action + * @param impactValues the values of the impacts of the sustainable action + * @param description the description of the sustainable action + */ + function distributeRewardWithProof( + bytes32 appId, + uint256 amount, + address receiver, + string[] memory proofTypes, // link, photo, video, text, etc. + string[] memory proofValues, // "https://...", "Qm...", etc., + string[] memory impactCodes, // carbon, water, etc. + uint256[] memory impactValues, // 100, 200, etc., + string memory description + ) external; + + /** + * @dev Builds the JSON proof string that will be stored + * on chain regarding the proofs, impacts and description of the sustainable action. + * + * @param proofTypes the types of the proof of the sustainable action + * @param proofValues the values of the proof of the sustainable action + * @param impactCodes the codes of the impacts of the sustainable action + * @param impactValues the values of the impacts of the sustainable action + * @param description the description of the sustainable action + */ + function buildProof( + string[] memory proofTypes, // link, photo, video, text, etc. + string[] memory proofValues, // "https://...", "Qm...", etc., + string[] memory impactCodes, // carbon, water, etc. + uint256[] memory impactValues, // 100, 200, etc., + string memory description + ) external returns (string memory); } diff --git a/apps/contracts/contracts/mock/X2EarnRewardsPoolMock.sol b/apps/contracts/contracts/mock/X2EarnRewardsPoolMock.sol index 6e498d6..6018385 100644 --- a/apps/contracts/contracts/mock/X2EarnRewardsPoolMock.sol +++ b/apps/contracts/contracts/mock/X2EarnRewardsPoolMock.sol @@ -9,6 +9,7 @@ import {IX2EarnAppsMock} from './interfaces/IX2EarnAppsMock.sol'; import {IX2EarnRewardsPool} from '../interfaces/IX2EarnRewardsPool.sol'; import {IERC1155Receiver} from '@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol'; import {IERC721Receiver} from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol'; +import '@openzeppelin/contracts/utils/Strings.sol'; /** * Mock contract forked from vebetterdao-contracts. @@ -77,7 +78,7 @@ contract X2EarnRewardsPoolMock is IX2EarnRewardsPool, AccessControl, ReentrancyG /** * @dev See {IX2EarnRewardsPool-withdraw} */ - function withdraw(uint256 amount, bytes32 appId, string memory reason) external nonReentrant { + function withdraw(uint256 amount, bytes32 appId, string memory reason) external { X2EarnRewardsPoolStorage storage $ = _getX2EarnRewardsPoolStorage(); require($.x2EarnApps.appExists(appId), 'X2EarnRewardsPool: app does not exist'); @@ -103,10 +104,42 @@ contract X2EarnRewardsPoolMock is IX2EarnRewardsPool, AccessControl, ReentrancyG emit TeamWithdrawal(amount, appId, teamWalletAddress, msg.sender, reason); } + /** + * @dev See {IX2EarnRewardsPool-distributeRewardDeprecated} + */ + function distributeRewardDeprecated(bytes32 appId, uint256 amount, address receiver, string memory) external { + _distributeReward(appId, amount, receiver); + + // emit event with empty proof + emit RewardDistributed(amount, appId, receiver, '', msg.sender); + } + /** * @dev See {IX2EarnRewardsPool-distributeReward} */ - function distributeReward(bytes32 appId, uint256 amount, address receiver, string memory proof) external nonReentrant { + function distributeReward(bytes32 appId, uint256 amount, address receiver, string memory) external { + _distributeReward(appId, amount, receiver); + _emitProof(appId, amount, receiver, new string[](0), new string[](0), new string[](0), new uint256[](0), ''); + } + + /** + * @dev See {IX2EarnRewardsPool-distributeRewardWithProof} + */ + function distributeRewardWithProof( + bytes32 appId, + uint256 amount, + address receiver, + string[] memory proofTypes, // link, photo, video, text, etc. + string[] memory proofValues, // "https://...", "Qm...", etc., + string[] memory impactCodes, // carbon, water, etc. + uint256[] memory impactValues, // 100, 200, etc., + string memory description + ) public { + _distributeReward(appId, amount, receiver); + _emitProof(appId, amount, receiver, proofTypes, proofValues, impactCodes, impactValues, description); + } + + function _distributeReward(bytes32 appId, uint256 amount, address receiver) internal nonReentrant { X2EarnRewardsPoolStorage storage $ = _getX2EarnRewardsPoolStorage(); require($.x2EarnApps.appExists(appId), 'X2EarnRewardsPool: app does not exist'); @@ -122,9 +155,118 @@ contract X2EarnRewardsPoolMock is IX2EarnRewardsPool, AccessControl, ReentrancyG // transfer the rewards to the receiver $.availableFunds[appId] -= amount; require($.b3tr.transfer(receiver, amount), 'X2EarnRewardsPool: Allocation transfer to app failed'); + } + + /** + * @dev Emits the RewardDistributed event with the provided proofs and impacts. + */ + function _emitProof( + bytes32 appId, + uint256 amount, + address receiver, + string[] memory proofTypes, + string[] memory proofValues, + string[] memory impactCodes, + uint256[] memory impactValues, + string memory description + ) internal { + // Build the JSON proof string from the proof and impact data + string memory jsonProof = buildProof(proofTypes, proofValues, impactCodes, impactValues, description); // emit event - emit RewardDistributed(amount, appId, receiver, proof, msg.sender); + emit RewardDistributed(amount, appId, receiver, jsonProof, msg.sender); + } + + /** + * @dev see {IX2EarnRewardsPool-buildProof} + */ + function buildProof( + string[] memory proofTypes, + string[] memory proofValues, + string[] memory impactCodes, + uint256[] memory impactValues, + string memory description + ) public view virtual returns (string memory) { + bool hasProof = proofTypes.length > 0 && proofValues.length > 0; + bool hasImpact = impactCodes.length > 0 && impactValues.length > 0; + bool hasDescription = bytes(description).length > 0; + + // If neither proof nor impact is provided, return an empty string + if (!hasProof && !hasImpact) { + return ''; + } + + // Initialize an empty JSON bytes array with version + bytes memory json = abi.encodePacked('{"version": 2'); + + // Add description if available + if (hasDescription) { + json = abi.encodePacked(json, ',"description": "', description, '"'); + } + + // Add proof if available + if (hasProof) { + bytes memory jsonProof = _buildProofJson(proofTypes, proofValues); + + json = abi.encodePacked(json, ',"proof": ', jsonProof); + } + + // Add impact if available + if (hasImpact) { + bytes memory jsonImpact = _buildImpactJson(impactCodes, impactValues); + + json = abi.encodePacked(json, ',"impact": ', jsonImpact); + } + + // Close the JSON object + json = abi.encodePacked(json, '}'); + + return string(json); + } + + /** + * @dev Builds the proof JSON string from the proof data. + * @param proofTypes the proof types + * @param proofValues the proof values + */ + function _buildProofJson(string[] memory proofTypes, string[] memory proofValues) internal pure returns (bytes memory) { + require(proofTypes.length == proofValues.length, 'X2EarnRewardsPool: Mismatched input lengths for Proof'); + + bytes memory json = abi.encodePacked('{'); + + for (uint256 i; i < proofTypes.length; i++) { + json = abi.encodePacked(json, '"', proofTypes[i], '":', '"', proofValues[i], '"'); + if (i < proofTypes.length - 1) { + json = abi.encodePacked(json, ','); + } + } + + json = abi.encodePacked(json, '}'); + + return json; + } + + /** + * @dev Builds the impact JSON string from the impact data. + * + * @param impactCodes the impact codes + * @param impactValues the impact values + */ + function _buildImpactJson(string[] memory impactCodes, uint256[] memory impactValues) internal pure returns (bytes memory) { + require(impactCodes.length == impactValues.length, 'X2EarnRewardsPool: Mismatched input lengths for Impact'); + + bytes memory json = abi.encodePacked('{'); + + for (uint256 i; i < impactValues.length; i++) { + json = abi.encodePacked(json, '"', impactCodes[i], '":', Strings.toString(impactValues[i])); + if (i < impactValues.length - 1) { + json = abi.encodePacked(json, ','); + } + } + + json = abi.encodePacked(json, '}'); + + return json; } /**