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): enable EIP712 signature for TimelockTokenPool #16335

Merged
merged 15 commits into from
Mar 6, 2024
Merged
2 changes: 1 addition & 1 deletion packages/protocol/contracts/common/EssentialContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ abstract contract EssentialContract is UUPSUpgradeable, Ownable2StepUpgradeable,
}

/// @notice Returns true if the contract is paused, and false otherwise.
/// @return True if paused, false otherwise.
/// @return true if paused, false otherwise.
function paused() public view returns (bool) {
return __paused == _TRUE;
}
Expand Down
47 changes: 37 additions & 10 deletions packages/protocol/contracts/team/TimelockTokenPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity 0.8.24;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol";
import "../common/EssentialContract.sol";

/// @title TimelockTokenPool
Expand All @@ -22,7 +23,8 @@ import "../common/EssentialContract.sol";
/// - team members, advisors, etc.
/// - grant program grantees
/// @custom:security-contact security@taiko.xyz
contract TimelockTokenPool is EssentialContract {
contract TimelockTokenPool is EssentialContract, EIP712Upgradeable {
using ECDSA for bytes32;
using SafeERC20 for IERC20;

struct Grant {
Expand Down Expand Up @@ -55,6 +57,11 @@ contract TimelockTokenPool is EssentialContract {
Grant grant;
}

struct Withdrawal {
address recipient;
address to;
}

/// @notice The Taiko token address.
address public taikoToken;

Expand Down Expand Up @@ -101,6 +108,7 @@ contract TimelockTokenPool is EssentialContract {
error ALREADY_GRANTED();
error INVALID_GRANT();
error INVALID_PARAM();
error INVALID_SIGNATURE();
error NOTHING_TO_VOID();

/// @notice Initializes the contract.
Expand All @@ -118,6 +126,8 @@ contract TimelockTokenPool is EssentialContract {
initializer
{
__Essential_init(_owner);
__EIP712_init("Taiko TimelockTokenPool", "1");
dantaik marked this conversation as resolved.
Show resolved Hide resolved

if (_taikoToken == address(0)) revert INVALID_PARAM();
taikoToken = _taikoToken;

Expand All @@ -132,7 +142,7 @@ contract TimelockTokenPool is EssentialContract {
/// This transaction should happen on a regular basis, e.g., quarterly.
/// @param _recipient The grant recipient address.
/// @param _grant The grant struct.
function grant(address _recipient, Grant memory _grant) external onlyOwner {
function grant(address _recipient, Grant memory _grant) external onlyOwner nonReentrant {
if (_recipient == address(0)) revert INVALID_PARAM();
if (recipients[_recipient].grant.amount != 0) revert ALREADY_GRANTED();

Expand All @@ -147,7 +157,7 @@ contract TimelockTokenPool is EssentialContract {
/// granted to the recipient will NOT be voided but are subject to the
/// original unlock schedule.
/// @param _recipient The grant recipient address.
function void(address _recipient) external onlyOwner {
function void(address _recipient) external onlyOwner nonReentrant {
Recipient storage r = recipients[_recipient];
uint128 amountVoided = _voidGrant(r.grant);

Expand All @@ -158,18 +168,18 @@ contract TimelockTokenPool is EssentialContract {
}

/// @notice Withdraws all withdrawable tokens.
function withdraw() external {
function withdraw() external nonReentrant {
_withdraw(msg.sender, msg.sender);
}

/// @notice Withdraws all withdrawable tokens.
/// @notice Withdraws all withdrawable tokens to a designated address.
/// @param _to The address where the granted and unlocked tokens shall be sent to.
/// @param _sig Signature provided by the grant recipient.
function withdraw(address _to, bytes memory _sig) external {
/// @param _sig Signature provided by the recipient.
function withdraw(address _recipient, address _to, bytes memory _sig) external nonReentrant {
if (_to == address(0)) revert INVALID_PARAM();
bytes32 hash = keccak256(abi.encodePacked("Withdraw unlocked Taiko token to: ", _to));
address recipient = ECDSA.recover(hash, _sig);
_withdraw(recipient, _to);
if (!verifySignature(Withdrawal(_recipient, _to), _sig)) revert INVALID_SIGNATURE();

_withdraw(_recipient, _to);
}

/// @notice Returns the summary of the grant for a given recipient.
Expand Down Expand Up @@ -206,6 +216,23 @@ contract TimelockTokenPool is EssentialContract {
return recipients[_recipient].grant;
}

/// @notice Verifies if a withdrawal signature is valid
/// @param _withdrawal The withdrawal request.
/// @return true if the signature is valid, false otherwise.
function verifySignature(
Withdrawal memory _withdrawal,
bytes memory _sig
)
public
view
returns (bool)
{
bytes32 typed = keccak256("Withdrawal(address recipient,address to)");
bytes32 hash =
_hashTypedDataV4(keccak256(abi.encode(typed, _withdrawal.recipient, _withdrawal.to)));
return hash.recover(_sig) == _withdrawal.recipient;
}

function _withdraw(address _recipient, address _to) private {
Recipient storage r = recipients[_recipient];

Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@

/// @notice External withdraw function
/// @param user User address
function withdraw(address user) external ongoingWithdrawals {
function withdraw(address user) external ongoingWithdrawals nonReentrant {
(, uint256 amount) = getBalance(user);
withdrawnAmount[user] += amount;
IERC20(token).safeTransferFrom(vault, user, amount);

emit Withdrawn(user, amount);
}

Check failure

Code scanning / Slither

Arbitrary `from` in transferFrom High


/// @notice Getter for the balance and withdrawal amount per given user
/// The 2nd airdrop is subject to an unlock period. User has to claim his
Expand Down
Loading