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(protocol): allow one-tx claim and delegation for bridged ERC20 tokens #15727

Merged
merged 16 commits into from
Feb 11, 2024
15 changes: 14 additions & 1 deletion packages/protocol/contracts/team/airdrop/ERC20Airdrop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
pragma solidity 0.8.24;

import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "lib/openzeppelin-contracts-upgradeable/contracts/governance/utils/IVotesUpgradeable.sol";
import "./MerkleClaimable.sol";

/// @title ERC20Airdrop
Expand All @@ -41,8 +42,20 @@ contract ERC20Airdrop is MerkleClaimable {
vault = _vault;
}

function _claimWithData(bytes calldata data) internal override {
function _claimWithData(bytes calldata data, bytes memory extraData) internal override {
(address user, uint256 amount) = abi.decode(data, (address, uint256));

// Transfer the token first
IERC20(token).transferFrom(vault, user, amount);

if (extraData.length > 0) {
// Delegate the voting power to delegatee.
// Note that the signature (v,r,s) may not correspond to the user address,
// but since the data is provided by Taiko backend, it's not an issue even if
// client can change the data to call delegateBySig for another user.
(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) =
abi.decode(extraData, (address, uint256, uint256, uint8, bytes32, bytes32));
IVotesUpgradeable(token).delegateBySig(delegatee, nonce, expiry, v, r, s);
}
}
}
4 changes: 3 additions & 1 deletion packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ contract ERC20Airdrop2 is MerkleClaimable {
event Withdrawn(address user, uint256 amount);

error WITHDRAWALS_NOT_ONGOING();
error INVALID_DATA();

modifier ongoingWithdrawals() {
if (claimEnd > block.timestamp || claimEnd + withdrawalWindow < block.timestamp) {
Expand Down Expand Up @@ -103,7 +104,8 @@ contract ERC20Airdrop2 is MerkleClaimable {
withdrawableAmount = timeBasedAllowance - withdrawnAmount[user];
}

function _claimWithData(bytes calldata data) internal override {
function _claimWithData(bytes calldata data, bytes memory extraData) internal override {
if (extraData.length != 0) revert INVALID_DATA();
(address user, uint256 amount) = abi.decode(data, (address, uint256));
claimedAmount[user] += amount;
}
Expand Down
5 changes: 4 additions & 1 deletion packages/protocol/contracts/team/airdrop/ERC721Airdrop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ contract ERC721Airdrop is MerkleClaimable {
address public vault;
uint256[48] private __gap;

error INVALID_DATA();

function init(
uint64 _claimStarts,
uint64 _claimEnds,
Expand All @@ -40,7 +42,8 @@ contract ERC721Airdrop is MerkleClaimable {
vault = _vault;
}

function _claimWithData(bytes calldata data) internal override {
function _claimWithData(bytes calldata data, bytes memory extraData) internal override {
if (extraData.length != 0) revert INVALID_DATA();
(address user, uint256[] memory tokenIds) = abi.decode(data, (address, uint256[]));

for (uint256 i; i < tokenIds.length; ++i) {
Expand Down
26 changes: 24 additions & 2 deletions packages/protocol/contracts/team/airdrop/MerkleClaimable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,33 @@ abstract contract MerkleClaimable is EssentialContract {

function claim(
bytes calldata data,
bytes calldata extraData,
bytes32[] calldata proof
)
external
nonReentrant
ongoingClaim
{
_claim(data, extraData, proof);
}

function claim(
bytes calldata data,
bytes32[] calldata proof
)
external
nonReentrant
ongoingClaim
{
_claim(data, bytes(""), proof);
}

function _claim(
bytes calldata data,
bytes memory extraData,
bytes32[] calldata proof
)
private
{
bytes32 hash = keccak256(abi.encode("CLAIM_TAIKO_AIRDROP", data));

Expand All @@ -59,7 +81,7 @@ abstract contract MerkleClaimable is EssentialContract {
}

isClaimed[hash] = true;
_claimWithData(data);
_claimWithData(data, extraData);
emit Claimed(hash);
}

Expand All @@ -85,5 +107,5 @@ abstract contract MerkleClaimable is EssentialContract {
}

/// @dev Must revert in case of errors.
function _claimWithData(bytes calldata data) internal virtual;
function _claimWithData(bytes calldata data, bytes memory extraData) internal virtual;
}
Loading