Skip to content

Commit

Permalink
Merge branch 'develop' into update-packages
Browse files Browse the repository at this point in the history
  • Loading branch information
jellegerbrandy committed Mar 21, 2024
2 parents b7bdc58 + 0778e53 commit dfb2e64
Show file tree
Hide file tree
Showing 24 changed files with 3,396 additions and 4 deletions.
18 changes: 17 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,26 @@ module.exports = {
],
"rewardToken": "HATToken"
}],
"hatArbitratorConf": {
"expertCommittee": "0x0000000000000000000000000000000000000001",
"token": "0x0000000000000000000000000000000000000002",
"bondsNeededToStartDispute": "1000000000000000000000",
"minBondAmount": "100000000000000000000",
"resolutionChallengePeriod": 60 * 60 * 24 * 7,
"submitClaimRequestReviewPeriod": 60 * 60 * 24 * 90,
},
"hatKlerosConnectorConf": {
"klerosArbitrator": "0x0000000000000000000000000000000000000003",
"arbitratorExtraData": "0x85",
"metaEvidence": "ipfs/X",
"winnerMultiplier": 3000,
"loserMultiplier": 7000
},
"hatVaultsRegistryConf": {
"bountyGovernanceHAT": "1000",
"bountyHackerHATVested": "500",
"swapToken": "HATToken"
"swapToken": "HATToken",
"useKleros": false
},
"hatVaultsNFTConf": {
"merkleTreeIPFSRef": "",
Expand Down
118 changes: 118 additions & 0 deletions contracts/tge/HATAirdrop.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol";
import "../tokenlock/ITokenLockFactory.sol";
import "./interfaces/IHATAirdrop.sol";

/*
An airdrop contract that transfers tokens based on a merkle tree.
*/
contract HATAirdrop is IHATAirdrop, Initializable {
error CannotRedeemBeforeStartTime();
error CannotRedeemAfterDeadline();
error LeafAlreadyRedeemed();
error InvalidMerkleProof();
error CannotRecoverBeforeDeadline();
error RedeemerMustBeBeneficiary();

using SafeERC20Upgradeable for IERC20Upgradeable;

bytes32 public root;
uint256 public startTime;
uint256 public deadline;
uint256 public lockEndTime;
uint256 public periods;
IERC20Upgradeable public token;
ITokenLockFactory public tokenLockFactory;
address public factory;

mapping (bytes32 => bool) public leafRedeemed;

event MerkleTreeSet(string _merkleTreeIPFSRef, bytes32 _root, uint256 _startTime, uint256 _deadline);
event TokensRedeemed(address indexed _account, address indexed _tokenLock, uint256 _amount);

constructor () {
_disableInitializers();
}

/**
* @notice Initialize a HATAirdrop instance
* @param _merkleTreeIPFSRef new merkle tree ipfs reference.
* @param _root new merkle tree root to use for verifying airdrop data.
* @param _startTime start of the redeem period and of the token lock (if exists)
* @param _deadline end time to redeem from the contract
* @param _lockEndTime end time for the token lock contract. If this date is in the past, the tokens will be transferred directly to the user and no token lock will be created
* @param _periods number of periods of the token lock contract (if exists)
* @param _token the token to be airdropped
* @param _tokenLockFactory the token lock factory to use to deploy the token locks
*/
function initialize(
string memory _merkleTreeIPFSRef,
bytes32 _root,
uint256 _startTime,
uint256 _deadline,
uint256 _lockEndTime,
uint256 _periods,
IERC20Upgradeable _token,
ITokenLockFactory _tokenLockFactory
) external initializer {
root = _root;
startTime = _startTime;
deadline = _deadline;
lockEndTime = _lockEndTime;
periods = _periods;
token = _token;
tokenLockFactory = _tokenLockFactory;
factory = msg.sender;
emit MerkleTreeSet(_merkleTreeIPFSRef, _root, _startTime, _deadline);
}

function redeem(address _account, uint256 _amount, bytes32[] calldata _proof) external {
if (msg.sender != _account && msg.sender != factory) {
revert RedeemerMustBeBeneficiary();
}
// solhint-disable-next-line not-rely-on-time
if (block.timestamp < startTime) revert CannotRedeemBeforeStartTime();
// solhint-disable-next-line not-rely-on-time
if (block.timestamp > deadline) revert CannotRedeemAfterDeadline();
bytes32 leaf = _leaf(_account, _amount);
if (leafRedeemed[leaf]) revert LeafAlreadyRedeemed();
if(!_verify(_proof, leaf)) revert InvalidMerkleProof();
leafRedeemed[leaf] = true;

address _tokenLock = address(0);
// solhint-disable-next-line not-rely-on-time
if (lockEndTime > block.timestamp) {
_tokenLock = tokenLockFactory.createTokenLock(
address(token),
0x0000000000000000000000000000000000000000,
_account,
_amount,
startTime,
lockEndTime,
periods,
0,
0,
false,
true
);
token.safeTransferFrom(factory, _tokenLock, _amount);
} else {
token.safeTransferFrom(factory, _account, _amount);
}

emit TokensRedeemed(_account, _tokenLock, _amount);
}

function _verify(bytes32[] calldata proof, bytes32 leaf) internal view returns (bool) {
return MerkleProofUpgradeable.verifyCalldata(proof, root, leaf);
}

function _leaf(address _account, uint256 _amount) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(_account, _amount));
}
}
77 changes: 77 additions & 0 deletions contracts/tge/HATAirdropFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
// Disclaimer https://github.com/hats-finance/hats-contracts/blob/main/DISCLAIMER.md

pragma solidity 0.8.16;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IHATAirdrop.sol";

contract HATAirdropFactory is Ownable {
error RedeemDataArraysLengthMismatch();
error ContractIsNotHATAirdrop();
error HATAirdropInitializationFailed();

using SafeERC20 for IERC20;

mapping(address => bool) public isAirdrop;

event TokensWithdrawn(address indexed _owner, uint256 _amount);
event HATAirdropCreated(address indexed _hatAirdrop, bytes _initData, IERC20 _token, uint256 _totalAmount);

function withdrawTokens(IERC20 _token, uint256 _amount) external onlyOwner {
address owner = msg.sender;
_token.safeTransfer(owner, _amount);
emit TokensWithdrawn(owner, _amount);
}

function redeemMultipleAirdrops(IHATAirdrop[] calldata _airdrops, uint256[] calldata _amounts, bytes32[][] calldata _proofs) external {
if (_airdrops.length != _amounts.length || _airdrops.length != _proofs.length) {
revert RedeemDataArraysLengthMismatch();
}

address caller = msg.sender;
for (uint256 i = 0; i < _airdrops.length;) {
if (!isAirdrop[address(_airdrops[i])]) {
revert ContractIsNotHATAirdrop();
}

_airdrops[i].redeem(caller, _amounts[i], _proofs[i]);

unchecked {
++i;
}
}
}

function createHATAirdrop(
address _implementation,
bytes calldata _initData,
IERC20 _token,
uint256 _totalAmount
) external onlyOwner returns (address result) {
result = Clones.cloneDeterministic(_implementation, keccak256(_initData));

// solhint-disable-next-line avoid-low-level-calls
(bool success,) = result.call(_initData);

if (!success) {
revert HATAirdropInitializationFailed();
}

isAirdrop[result] = true;

_token.safeApprove(result, _totalAmount);

emit HATAirdropCreated(result, _initData, _token, _totalAmount);
}

function predictHATAirdropAddress(
address _implementation,
bytes calldata _initData
) external view returns (address) {
return Clones.predictDeterministicAddress(_implementation, keccak256(_initData));
}
}
9 changes: 9 additions & 0 deletions contracts/tge/interfaces/IHATAirdrop.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
// Disclaimer https://github.com/hats-finance/hats-contracts/blob/main/DISCLAIMER.md

pragma solidity 0.8.16;


interface IHATAirdrop {
function redeem(address _account, uint256 _amount, bytes32[] calldata _proof) external;
}
51 changes: 51 additions & 0 deletions deploy/005_deploy_hatarbitrator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const { network } = require("hardhat");
const CONFIG = require("../config.js");

const func = async function (hre) {
const config = CONFIG[network.name];
const { deployments, getNamedAccounts } = hre;
const { deploy, execute, read } = deployments;

const { deployer } = await getNamedAccounts();

await deploy('HATArbitrator', {
from: deployer,
args: [
config.hatArbitratorConf.expertCommittee,
config.hatArbitratorConf.token,
config.hatArbitratorConf.bondsNeededToStartDispute,
config.hatArbitratorConf.minBondAmount,
config.hatArbitratorConf.resolutionChallengePeriod,
config.hatArbitratorConf.submitClaimRequestReviewPeriod
],
log: true,
});

await deploy('HATKlerosConnector', {
from: deployer,
args: [
config.hatKlerosConnectorConf.klerosArbitrator,
config.hatKlerosConnectorConf.arbitratorExtraData,
(await deployments.get('HATArbitrator')).address,
config.hatKlerosConnectorConf.metaEvidence,
config.hatKlerosConnectorConf.winnerMultiplier,
config.hatKlerosConnectorConf.loserMultiplier
],
log: true,
});

if((await read('HATArbitrator', {}, 'court')) !== (await deployments.get('HATKlerosConnector')).address) {
await execute('HATArbitrator', { from: deployer, log: true }, 'setCourt', (await deployments.get('HATKlerosConnector')).address);
}

if ((await read('HATArbitrator', {}, 'owner')) !== (await deployments.get('HATTimelockController')).address) {
await execute('HATArbitrator', { from: deployer, log: true }, 'transferOwnership', (await deployments.get('HATTimelockController')).address);
}

if ((await read('HATKlerosConnector', {}, 'owner')) !== (await deployments.get('HATTimelockController')).address) {
await execute('HATKlerosConnector', { from: deployer, log: true }, 'transferOwnership', (await deployments.get('HATTimelockController')).address);
}
};

module.exports = func;
func.tags = ['HATArbitrator'];
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const func = async function (hre) {
let bountyGovernanceHAT = config.hatVaultsRegistryConf.bountyGovernanceHAT;
let bountyHackerHATVested = config.hatVaultsRegistryConf.bountyHackerHATVested;
let swapToken = config.hatVaultsRegistryConf.swapToken;
let useKleros = config.hatVaultsRegistryConf.useKleros;
if (!swapToken || swapToken === "HATToken") {
swapToken = (await deployments.get('HATToken')).address;
}
Expand All @@ -21,7 +22,7 @@ const func = async function (hre) {
(await deployments.get('HATVault')).address,
(await deployments.get('HATClaimsManager')).address,
(await deployments.get('HATTimelockController')).address,
(await deployments.get('HATGovernanceArbitrator')).address,
useKleros ? (await deployments.get('HATArbitrator')).address : (await deployments.get('HATGovernanceArbitrator')).address,
swapToken,
bountyGovernanceHAT,
bountyHackerHATVested,
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,72 @@ const func = async function (hre) {
"Arbitrator owner is the HATTimelockController"
);

// Verify HATArbitrator
verify(
await read('HATArbitrator', {}, 'owner') === (await deployments.get('HATTimelockController')).address,
"Arbitrator owner is the HATTimelockController"
);

verify(
await read('HATArbitrator', {}, 'court') === (await deployments.get('HATKlerosConnector')).address,
"Arbitrator court is the HATKlerosConnector"
);

verify(
(await read('HATArbitrator', {}, 'expertCommittee')) === config.hatArbitratorConf.expertCommittee,
"HATArbitrator expert committee is correct (" + config.hatArbitratorConf.expertCommittee + ")"
);

verify(
(await read('HATArbitrator', {}, 'token')) === config.hatArbitratorConf.token,
"HATArbitrator token is correct (" + config.hatArbitratorConf.token + ")"
);

verify(
(await read('HATArbitrator', {}, 'bondsNeededToStartDispute')).toString() === config.hatArbitratorConf.bondsNeededToStartDispute.toString(),
"HATArbitrator bondsNeededToStartDispute is correct (" + config.hatArbitratorConf.bondsNeededToStartDispute + ")"
);

verify(
(await read('HATArbitrator', {}, 'minBondAmount')).toString() === config.hatArbitratorConf.minBondAmount.toString(),
"HATArbitrator minBondAmount is correct (" + config.hatArbitratorConf.minBondAmount + ")"
);

verify(
(await read('HATArbitrator', {}, 'resolutionChallengePeriod')).toString() === config.hatArbitratorConf.resolutionChallengePeriod.toString(),
"HATArbitrator resolutionChallengePeriod is correct (" + config.hatArbitratorConf.resolutionChallengePeriod + ")"
);

verify(
(await read('HATArbitrator', {}, 'submitClaimRequestReviewPeriod')).toString() === config.hatArbitratorConf.submitClaimRequestReviewPeriod.toString(),
"HATArbitrator submitClaimRequestReviewPeriod is correct (" + config.hatArbitratorConf.submitClaimRequestReviewPeriod + ")"
);

verify(
(await read('HATKlerosConnector', {}, 'klerosArbitrator')) === config.hatKlerosConnectorConf.klerosArbitrator,
"HATKlerosConnector kleros arbitrator is correct (" + config.hatKlerosConnectorConf.klerosArbitrator + ")"
);

verify(
(await read('HATKlerosConnector', {}, 'arbitratorExtraData')) === config.hatKlerosConnectorConf.arbitratorExtraData,
"HATKlerosConnector arbitratorExtraData is correct (" + config.hatKlerosConnectorConf.arbitratorExtraData + ")"
);

verify(
(await read('HATKlerosConnector', {}, 'hatArbitrator')) === (await deployments.get('HATArbitrator')).address,
"HATKlerosConnector hatArbitrator is HATArbitrator"
);

verify(
(await read('HATKlerosConnector', {}, 'winnerMultiplier')).toString() === config.hatKlerosConnectorConf.winnerMultiplier.toString(),
"HATKlerosConnector winnerMultiplier is correct (" + config.hatKlerosConnectorConf.winnerMultiplier + ")"
);

verify(
(await read('HATKlerosConnector', {}, 'loserMultiplier')).toString() === config.hatKlerosConnectorConf.loserMultiplier.toString(),
"HATKlerosConnector loserMultiplier is correct (" + config.hatKlerosConnectorConf.loserMultiplier + ")"
);

// Verify Reward Controller

// TODO: Verify reward controllers
Expand All @@ -187,6 +253,7 @@ const func = async function (hre) {
let bountyGovernanceHAT = config["hatVaultsRegistryConf"]["bountyGovernanceHAT"];
let bountyHackerHATVested = config["hatVaultsRegistryConf"]["bountyHackerHATVested"];
let swapToken = config["hatVaultsRegistryConf"]["swapToken"];
let useKleros = config["hatVaultsRegistryConf"]["useKleros"];
if (!swapToken || swapToken === "HATToken") {
swapToken = (await deployments.get('HATToken')).address;
}
Expand All @@ -197,8 +264,8 @@ const func = async function (hre) {
);

verify(
await read('HATVaultsRegistry', {}, 'defaultArbitrator') === (await deployments.get('HATGovernanceArbitrator')).address,
"HATVaultsRegistry default arbitrator is the Arbitrator"
await read('HATVaultsRegistry', {}, 'defaultArbitrator') === (await deployments.get(useKleros ? 'HATArbitrator' : 'HATGovernanceArbitrator')).address,
"HATVaultsRegistry default arbitrator is the HATArbitrator"
);

verify(
Expand Down
Loading

0 comments on commit dfb2e64

Please sign in to comment.