Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add claimCompressedRefBatch function #290

Closed
wants to merge 2 commits into from
Closed
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
23 changes: 23 additions & 0 deletions contracts/QuestFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,29 @@ contract QuestFactory is Initializable, LegacyStorage, OwnableRoles, IQuestFacto
_claimCompressed(compressedData_, claimer);
}

/// @dev Claim rewards for multiple quests on behalf of multiple claimers
function claimCompressedRefBatch(BatchClaimData[] calldata batchClaimDataArray) external payable {
uint256 totalFees = 0;
for (uint256 i = 0; i < batchClaimDataArray.length; i++) {
totalFees += batchClaimDataArray[i].fee;
}
Comment on lines +394 to +396
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can calculate totalFees by doing batchClaimDataArray.length * mintFee

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this would also allow us to get rid of fee from the BatchClaimData struct

require(msg.value >= totalFees, "Insufficient ETH sent");

for (uint256 i = 0; i < batchClaimDataArray.length; i++) {
try this.claimCompressedRef{value: batchClaimDataArray[i].fee}(
batchClaimDataArray[i].compressedData,
batchClaimDataArray[i].claimer
) {} catch (bytes memory reason) {
emit BatchClaimFailed(batchClaimDataArray[i].claimer, batchClaimDataArray[i].compressedData, reason);
}
}

uint256 excess = msg.value - totalFees;
if (excess > 0) {
payable(msg.sender).transfer(excess);
}
}

/// @dev Claim rewards for a quest
/// @param compressedData_ The claim data in abi encoded bytes, compressed with cdCompress from solady LibZip
/// @param claimer The address of the claimer - where rewards are sent
Expand Down
10 changes: 10 additions & 0 deletions contracts/interfaces/IQuestFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ interface IQuestFactory {
string extraData;
}

/// @dev struct to allow for an array of claims in batch claim functions
struct BatchClaimData {
bytes compressedData;
address claimer;
uint256 fee;
}

struct ERC20QuestData {
uint32 txHashChainId;
address rewardTokenAddress;
Expand Down Expand Up @@ -179,6 +186,9 @@ interface IQuestFactory {
);
event ReferralFeeSet(uint16 percent);

/// @dev event to track failed claims in batch claim functions
event BatchClaimFailed(address indexed claimer, bytes compressedData, bytes reason);

// Read Functions
function getAddressMinted(string memory questId_, address address_) external view returns (bool);
function getNumberMinted(string memory questId_) external view returns (uint256);
Expand Down
67 changes: 67 additions & 0 deletions test/QuestFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,73 @@ contract TestQuestFactory is Test, Errors, Events, TestUtils {
assertEq(Quest(payable(questAddress)).getReferralAmount(referrer), Quest(payable(questAddress)).referralRewardAmount());
}

function test_claimCompressedRefBatch_erc20_mocked_data() public {
// Setup for two participants
address participant1 = address(0x1);
address participant2 = address(0x2);
address referrer1 = address(0x3);
address referrer2 = address(0x4);

// Generate signature data for both participants
bytes memory signData1 = abi.encode(participant1, referrer1, QUEST.QUEST_ID_STRING, QUEST.JSON_MSG);
bytes memory signData2 = abi.encode(participant2, referrer2, QUEST.QUEST_ID_STRING, QUEST.JSON_MSG);

bytes32 msgHash1 = keccak256(signData1);
bytes32 msgHash2 = keccak256(signData2);
bytes32 digest1 = ECDSA.toEthSignedMessageHash(msgHash1);
bytes32 digest2 = ECDSA.toEthSignedMessageHash(msgHash2);
(, bytes32 r1, bytes32 vs1) = TestUtils.getSplitSignature(claimSignerPrivateKey, digest1);
(, bytes32 r2, bytes32 vs2) = TestUtils.getSplitSignature(claimSignerPrivateKey, digest2);

vm.deal(participant1, 1000000);
vm.deal(participant2, 1000000);

vm.startPrank(questCreator);
sampleERC20.approve(address(questFactory), calculateTotalRewardsPlusFee(QUEST.TOTAL_PARTICIPANTS, QUEST.REWARD_AMOUNT, QUEST_FEE, REFERRAL_FEE));
address questAddress = questFactory.createERC20Boost(
QUEST.CHAIN_ID,
address(sampleERC20),
QUEST.END_TIME,
QUEST.START_TIME,
QUEST.TOTAL_PARTICIPANTS,
QUEST.REWARD_AMOUNT,
QUEST.QUEST_ID_STRING,
QUEST.ACTION_TYPE,
QUEST.QUEST_NAME,
QUEST.PROJECT_NAME
);

vm.warp(QUEST.START_TIME + 1);

bytes memory data1 = abi.encode(QUEST.TX_HASH, r1, vs1, referrer1, QUEST.QUEST_ID, QUEST.CHAIN_ID);
bytes memory data2 = abi.encode(QUEST.TX_HASH, r2, vs2, referrer2, QUEST.QUEST_ID, QUEST.CHAIN_ID);
bytes memory dataCompressed1 = LibZip.cdCompress(data1);
bytes memory dataCompressed2 = LibZip.cdCompress(data2);

IQuestFactory.BatchClaimData[] memory claimDataArray = new IQuestFactory.BatchClaimData[](2);
claimDataArray[0] = IQuestFactory.BatchClaimData({
compressedData: dataCompressed1,
claimer: participant1,
fee: MINT_FEE
});
claimDataArray[1] = IQuestFactory.BatchClaimData({
compressedData: dataCompressed2,
claimer: participant2,
fee: MINT_FEE
});

vm.startPrank(anyone, anyone);
questFactory.claimCompressedRefBatch{value: MINT_FEE * 2}(claimDataArray);

// Check ERC20 rewards
assertEq(sampleERC20.balanceOf(participant1), QUEST.REWARD_AMOUNT, "participant1 erc20 balance");
assertEq(sampleERC20.balanceOf(participant2), QUEST.REWARD_AMOUNT, "participant2 erc20 balance");

// Check referrer claimable amounts
assertEq(Quest(payable(questAddress)).getReferralAmount(referrer1), Quest(payable(questAddress)).referralRewardAmount(), "referrer1 claimable amount");
assertEq(Quest(payable(questAddress)).getReferralAmount(referrer2), Quest(payable(questAddress)).referralRewardAmount(), "referrer2 claimable amount");
}

function test_claimCompressed_erc20_with_ref() public{
vm.startPrank(questCreator);
sampleERC20.approve(address(questFactory), calculateTotalRewardsPlusFee(QUEST.TOTAL_PARTICIPANTS, QUEST.REWARD_AMOUNT, QUEST_FEE, REFERRAL_FEE));
Expand Down
Loading