From 7fedc42bfd5ed95ff063febe9adfd67e1615461c Mon Sep 17 00:00:00 2001 From: Wayne Date: Tue, 24 Oct 2023 15:44:26 -1000 Subject: [PATCH] change to signing the bytes array --- contracts/QuestFactory.sol | 56 +++++++++++++++----------- contracts/interfaces/IQuestFactory.sol | 2 +- test/QuestFactory.t.sol | 50 ++++++++++++++++++++++- 3 files changed, 82 insertions(+), 26 deletions(-) diff --git a/contracts/QuestFactory.sol b/contracts/QuestFactory.sol index e9bb00f5..ddb9d5f1 100644 --- a/contracts/QuestFactory.sol +++ b/contracts/QuestFactory.sol @@ -16,6 +16,7 @@ import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol"; import {IERC1155} from "openzeppelin-contracts/token/ERC1155/IERC1155.sol"; import {IQuestOwnable} from "./interfaces/IQuestOwnable.sol"; import {IQuest1155Ownable} from "./interfaces/IQuest1155Ownable.sol"; +import "forge-std/console.sol"; /// @title QuestFactory /// @author RabbitHole.gg @@ -105,19 +106,20 @@ contract QuestFactory is Initializable, LegacyStorage, OwnableRoles, IQuestFacto modifier claimChecks(ClaimData memory claimData_) { Quest storage currentQuest = quests[claimData_.questId]; - bytes memory hashc = abi.encodePacked(msg.sender, claimData_.questId); + bytes memory hashc = abi.encodePacked(claimData_.claimer, claimData_.questId); + bytes32 encodedHash; if (claimData_.ref != address(0)) { hashc = abi.encodePacked(hashc, claimData_.ref); } if (bytes(claimData_.extraData).length > 0){ - hashc = abi.encodePacked(hashc, claimData_.extraData); + encodedHash = claimData_.hashBytes; + } else { + encodedHash = keccak256(hashc); } - bytes32 encodedHash = keccak256(hashc); - if (currentQuest.numberMinted + 1 > currentQuest.totalParticipants) revert OverMaxAllowedToMint(); - if (currentQuest.addressMinted[msg.sender]) revert AddressAlreadyMinted(); + if (currentQuest.addressMinted[claimData_.claimer]) revert AddressAlreadyMinted(); if (encodedHash != claimData_.hashBytes) revert InvalidHash(); if (recoverSigner(claimData_.hashBytes, claimData_.signature) != claimSignerAddress) revert AddressNotSigned(); _; @@ -308,13 +310,21 @@ contract QuestFactory is Initializable, LegacyStorage, OwnableRoles, IQuestFacto /// @param signature_ The signature of the data /// @param data_ The data to decode for the claim function claim(bytes calldata signature_, bytes calldata data_) external payable { - (address ref_, string memory questId_, string memory jsonData_) = abi.decode(data_, (address, string, string)); - bytes32 hash_ = keccak256(abi.encodePacked(msg.sender, questId_, ref_, jsonData_)); + ( + address claimer_, + address ref_, + string memory questId_, + string memory jsonData_ + ) = abi.decode( + data_, + (address, address, string, string) + ); + bytes32 hash_ = keccak256(data_); if (quests[questId_].questType.eq("erc1155")) { - claim1155RewardsRef(ClaimData(questId_, hash_, signature_, ref_, 0, jsonData_)); + claim1155RewardsRef(ClaimData(questId_, hash_, signature_, ref_, claimer_, jsonData_)); } else { // erc20, erc20Stream - claimRewardsRef(ClaimData(questId_, hash_, signature_, ref_, 0, jsonData_)); + claimRewardsRef(ClaimData(questId_, hash_, signature_, ref_, claimer_, jsonData_)); } } @@ -325,9 +335,9 @@ contract QuestFactory is Initializable, LegacyStorage, OwnableRoles, IQuestFacto /// @param ref_ The referral address function claim(string memory questId_, bytes32 hash_, bytes memory signature_, address ref_) external payable { if (quests[questId_].questType.eq("erc1155")) { - claim1155RewardsRef(ClaimData(questId_, hash_, signature_, ref_, 0, "")); + claim1155RewardsRef(ClaimData(questId_, hash_, signature_, ref_, msg.sender, "")); } else { // erc20, erc20Stream - claimRewardsRef(ClaimData(questId_, hash_, signature_, ref_, 0, "")); + claimRewardsRef(ClaimData(questId_, hash_, signature_, ref_, msg.sender, "")); } } @@ -336,7 +346,7 @@ contract QuestFactory is Initializable, LegacyStorage, OwnableRoles, IQuestFacto /// @param hash_ The hash of the message /// @param signature_ The signature of the hash function claim1155Rewards(string memory questId_, bytes32 hash_, bytes memory signature_) external payable { - claim1155RewardsRef(ClaimData(questId_, hash_, signature_, address(0), 0, "")); + claim1155RewardsRef(ClaimData(questId_, hash_, signature_, address(0), msg.sender, "")); } /// @dev claim rewards for a quest @@ -344,7 +354,7 @@ contract QuestFactory is Initializable, LegacyStorage, OwnableRoles, IQuestFacto /// @param hash_ The hash of the message /// @param signature_ The signature of the hash function claimRewards(string memory questId_, bytes32 hash_, bytes memory signature_) external payable { - claimRewardsRef(ClaimData(questId_, hash_, signature_, address(0), 0, "")); + claimRewardsRef(ClaimData(questId_, hash_, signature_, address(0), msg.sender, "")); } /*////////////////////////////////////////////////////////////// @@ -551,25 +561,25 @@ contract QuestFactory is Initializable, LegacyStorage, OwnableRoles, IQuestFacto if (block.timestamp < questContract_.startTime()) revert QuestNotStarted(); if (block.timestamp > questContract_.endTime()) revert QuestEnded(); - currentQuest.addressMinted[msg.sender] = true; + currentQuest.addressMinted[claimData_.claimer] = true; ++currentQuest.numberMinted; - questContract_.singleClaim(msg.sender); + questContract_.singleClaim(claimData_.claimer); if (mintFee > 0) processMintFee(claimData_.ref, currentQuest.questCreator, claimData_.questId); emit QuestClaimedData( - msg.sender, + claimData_.claimer, currentQuest.questAddress, claimData_.extraData ); emit Quest1155Claimed( - msg.sender, currentQuest.questAddress, claimData_.questId, questContract_.rewardToken(), questContract_.tokenId() + claimData_.claimer, currentQuest.questAddress, claimData_.questId, questContract_.rewardToken(), questContract_.tokenId() ); if (claimData_.ref != address(0)) { emit QuestClaimedReferred( - msg.sender, + claimData_.claimer, currentQuest.questAddress, claimData_.questId, questContract_.rewardToken(), @@ -594,20 +604,20 @@ contract QuestFactory is Initializable, LegacyStorage, OwnableRoles, IQuestFacto if (block.timestamp < questContract_.startTime()) revert QuestNotStarted(); if (block.timestamp > questContract_.endTime()) revert QuestEnded(); - currentQuest.addressMinted[msg.sender] = true; + currentQuest.addressMinted[claimData_.claimer] = true; ++currentQuest.numberMinted; - questContract_.singleClaim(msg.sender); + questContract_.singleClaim(claimData_.claimer); if (mintFee > 0) processMintFee(claimData_.ref, currentQuest.questCreator, claimData_.questId); emit QuestClaimedData( - msg.sender, + claimData_.claimer, currentQuest.questAddress, claimData_.extraData ); emit QuestClaimed( - msg.sender, + claimData_.claimer, currentQuest.questAddress, claimData_.questId, questContract_.rewardToken(), @@ -616,7 +626,7 @@ contract QuestFactory is Initializable, LegacyStorage, OwnableRoles, IQuestFacto if (claimData_.ref != address(0)) { emit QuestClaimedReferred( - msg.sender, + claimData_.claimer, currentQuest.questAddress, claimData_.questId, questContract_.rewardToken(), diff --git a/contracts/interfaces/IQuestFactory.sol b/contracts/interfaces/IQuestFactory.sol index e39525c4..00f75f32 100644 --- a/contracts/interfaces/IQuestFactory.sol +++ b/contracts/interfaces/IQuestFactory.sol @@ -63,7 +63,7 @@ interface IQuestFactory { bytes32 hashBytes; bytes signature; address ref; - uint256 amount; + address claimer; string extraData; } diff --git a/test/QuestFactory.t.sol b/test/QuestFactory.t.sol index 459134e8..25443e5b 100644 --- a/test/QuestFactory.t.sol +++ b/test/QuestFactory.t.sol @@ -370,9 +370,9 @@ contract TestQuestFactory is Test, Errors, Events, TestUtils { string memory json = string(abi.encodePacked('{"anyting": "we want", "foo": "bar"}')); - bytes32 msgHash = keccak256(abi.encodePacked(participant, "questId2", referrer, json)); + bytes memory data = abi.encode(participant, referrer, "questId2", json); + bytes32 msgHash = keccak256(data); bytes memory signature = signHash(msgHash, claimSignerPrivateKey); - bytes memory data = abi.encode(referrer, "questId2", json); vm.startPrank(participant); vm.recordLogs(); @@ -395,6 +395,52 @@ contract TestQuestFactory is Test, Errors, Events, TestUtils { assertEq(abi.decode(entries[3].data, (string)), json); } + function test_claim_with_bytes_no_referrer() public{ + vm.startPrank(owner); + questFactory.setRewardAllowlistAddress(address(sampleERC20), true); + + vm.startPrank(questCreator); + sampleERC20.approve(address(questFactory), calculateTotalRewardsPlusFee(TOTAL_PARTICIPANTS, REWARD_AMOUNT, QUEST_FEE)); + address questAddress = questFactory.createQuestAndQueue( + address(sampleERC20), + END_TIME, + START_TIME, + TOTAL_PARTICIPANTS, + REWARD_AMOUNT, + "questId2", + "actionSpec", + 0 + ); + + uint256 questCreatorBeforeBalance = questCreator.balance; + vm.warp(START_TIME + 1); + + string memory json = string(abi.encodePacked('{"anyting": "we want", "foo": "bar"}')); + + bytes memory data = abi.encode(participant, address(0), "questId2", json); + bytes32 msgHash = keccak256(data); + bytes memory signature = signHash(msgHash, claimSignerPrivateKey); + + vm.startPrank(participant); + vm.recordLogs(); + questFactory.claim{value: MINT_FEE}(signature, data); + + // erc20 reward + assertEq(sampleERC20.balanceOf(participant), REWARD_AMOUNT, "particpiant erc20 balance"); + + // claim fee rewards + assertEq(questCreator.balance - questCreatorBeforeBalance, MINT_FEE / 3, "questCreator mint fee"); + assertEq(protocolFeeRecipient.balance, (MINT_FEE / 3) * 2, "protocolFeeRecipient mint fee"); + + // assert QuestClaimedData event + Vm.Log[] memory entries = vm.getRecordedLogs(); + assertEq(entries.length, 5); + assertEq(entries[3].topics[0], keccak256("QuestClaimedData(address,address,string)")); + assertEq(entries[3].topics[1], bytes32(uint256(uint160(participant)))); + assertEq(entries[3].topics[2], bytes32(uint256(uint160(questAddress)))); + assertEq(abi.decode(entries[3].data, (string)), json); + } + function test_claim_with_claimRewards_without_referrer() public{ vm.startPrank(owner); questFactory.setRewardAllowlistAddress(address(sampleERC20), true);