From 4d4733ad368b5f7372459bf63fa1d2a34b1bc125 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Tue, 8 Apr 2025 17:48:00 +0200 Subject: [PATCH 1/2] feat: add possibility to handle transactions by signature --- contracts/Distributor.sol | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/contracts/Distributor.sol b/contracts/Distributor.sol index 60c8d50..206c89a 100644 --- a/contracts/Distributor.sol +++ b/contracts/Distributor.sol @@ -97,6 +97,15 @@ contract Distributor is UUPSHelper { /// @dev If the mapping is empty, by default rewards will accrue on the user address mapping(address => mapping(address => address)) public claimRecipient; + bytes32 public constant SET_CLAIM_TYPEHASH = + keccak256("SetClaimRecipient(address user,address recipient,address token,uint256 deadline)"); + + bytes32 public constant TOGGLE_OPERATOR_TYPEHASH = + keccak256("ToggleOperator(address user,address operator,uint256 deadline)"); + + bytes32 internal constant EIP712_DOMAIN_TYPEHASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + uint256[36] private __gap; /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -244,6 +253,67 @@ contract Distributor is UUPSHelper { emit ClaimRecipientUpdated(msg.sender, recipient, token); } + function getDomainSeparator() public view returns (bytes32 domainSeparator) { + domainSeparator = keccak256( + abi.encode( + EIP712_DOMAIN_TYPEHASH, + keccak256(bytes("MerklDistributor")), + keccak256(bytes("1")), + block.chainid, + address(this) + ) + ); + } + + // New function with signature verification (added for upgrade) + function toggleOperatorWithSig( + address user, + address operator, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + // Create the hash to match the signature + bytes32 structHash = keccak256(abi.encode(TOGGLE_OPERATOR_TYPEHASH, user, operator, deadline)); + + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", getDomainSeparator(), structHash)); + + // Recover the signer from the signature + address recovered = ecrecover(digest, v, r, s); + if (recovered == address(0) || recovered != user || block.timestamp > deadline) + revert Errors.InvalidSignature(); + + // Now toggle the operator as per original logic + uint256 oldValue = operators[user][operator]; + operators[user][operator] = 1 - oldValue; + emit OperatorToggled(user, operator, oldValue == 0); + } + + function setClaimRecipientWithSig( + address user, + address recipient, + address token, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + // Create the hash to match the signature + bytes32 structHash = keccak256(abi.encode(SET_CLAIM_TYPEHASH, user, recipient, token, deadline)); + + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", getDomainSeparator(), structHash)); + + // Recover the signer from the signature + address recovered = ecrecover(digest, v, r, s); + if (recovered == address(0) || recovered != user || block.timestamp > deadline) + revert Errors.InvalidSignature(); + + // Set the claim recipient + claimRecipient[user][token] = recipient; + emit ClaimRecipientUpdated(user, recipient, token); + } + /// @notice Freezes the Merkle tree update until the dispute is resolved /// @dev Requires a deposit of `disputeToken` that'll be slashed if the dispute is not accepted /// @dev It is only possible to create a dispute within `disputePeriod` after each tree update From 187ad82882823b8b8251ea1b6f3364d27dad7325 Mon Sep 17 00:00:00 2001 From: Pablo Veyrat Date: Tue, 29 Apr 2025 19:08:24 +0200 Subject: [PATCH 2/2] feat: distributor --- contracts/Distributor.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/Distributor.sol b/contracts/Distributor.sol index 206c89a..6128d07 100644 --- a/contracts/Distributor.sol +++ b/contracts/Distributor.sol @@ -445,8 +445,12 @@ contract Distributor is UUPSHelper { bytes memory data = datas[i]; // Only approved operator can claim for `user` - if (msg.sender != user && tx.origin != user && operators[user][msg.sender] == 0) - revert Errors.NotWhitelisted(); + if ( + msg.sender != user && + tx.origin != user && + operators[user][msg.sender] == 0 && + operators[user][tx.origin] == 0 + ) revert Errors.NotWhitelisted(); // Verifying proof bytes32 leaf = keccak256(abi.encode(user, token, amount));