// SPDX-License-Identifier: Apache-2.0pragmasolidity ^0.8.3;import"../libraries/Authorizable.sol";
import"../libraries/MerkleRewards.sol";
// A merkle rewards contract with an expiration timecontractAirdropisMerkleRewards, Authorizable{
// The time after which the token cannot be claimeduint256publicimmutable expiration;
/// @notice Constructs the contract and sets state and immutable variables/// @param _governance The address which can withdraw funds when the drop expires/// @param _merkleRoot The root a keccak256 merkle tree with leaves which are address amount pairs/// @param _token The erc20 contract which will be sent to the people with claims on the contract/// @param _expiration The unix second timestamp when the airdrop expires/// @param _lockingVault The governance vault which this deposits to on behalf of usersconstructor(address _governance,
bytes32 _merkleRoot,
IERC20 _token,
uint256 _expiration,
ILockingVault _lockingVault
) MerkleRewards(_merkleRoot, _token, _lockingVault) {
// Set expiration immutable and governance to the owner
expiration = _expiration;
setOwner(_governance);
}
/// @notice Allows governance to remove the funds in this contract once the airdrop is over./// Claims aren't blocked the airdrop ending at expiration is optional and gov has to/// manually end it./// @param destination The treasury contract which will hold the freed tokensfunctionreclaim(address destination) externalonlyOwner{
require(block.timestamp> expiration, "Not expired");
uint256 unclaimed = token.balanceOf(address(this));
token.transfer(destination, unclaimed);
}
/// @notice Claims an amount of tokens which are in the tree and send them to the user/// @param amount The amount of tokens to claim/// @param totalGrant The total amount of tokens the user was granted/// @param merkleProof The merkle de-commitment which proves the user is in the merkle root/// @param destination The address which will be credited with fundsfunctionclaim(uint256 amount,
uint256 totalGrant,
bytes32[] calldata merkleProof,
address destination
) externalvirtualoverride{
revert("Not Allowed to claim");
}
}
Contract Source Code
File 2 of 6: Authorizable.sol
// SPDX-License-Identifier: Apache-2.0pragmasolidity >=0.7.0;contractAuthorizable{
// This contract allows a flexible authorization scheme// The owner who can change authorization statusaddresspublic owner;
// A mapping from an address to its authorization statusmapping(address=>bool) public authorized;
/// @dev We set the deployer to the ownerconstructor() {
owner =msg.sender;
}
/// @dev This modifier checks if the msg.sender is the ownermodifieronlyOwner() {
require(msg.sender== owner, "Sender not owner");
_;
}
/// @dev This modifier checks if an address is authorizedmodifieronlyAuthorized() {
require(isAuthorized(msg.sender), "Sender not Authorized");
_;
}
/// @dev Returns true if an address is authorized/// @param who the address to check/// @return true if authorized false if notfunctionisAuthorized(address who) publicviewreturns (bool) {
return authorized[who];
}
/// @dev Privileged function authorize an address/// @param who the address to authorizefunctionauthorize(address who) externalonlyOwner() {
_authorize(who);
}
/// @dev Privileged function to de authorize an address/// @param who The address to remove authorization fromfunctiondeauthorize(address who) externalonlyOwner() {
authorized[who] =false;
}
/// @dev Function to change owner/// @param who The new owner addressfunctionsetOwner(address who) publiconlyOwner() {
owner = who;
}
/// @dev Inheritable function which authorizes someone/// @param who the address to authorizefunction_authorize(address who) internal{
authorized[who] =true;
}
}
Contract Source Code
File 3 of 6: IERC20.sol
// SPDX-License-Identifier: Apache-2.0pragmasolidity ^0.8.3;interfaceIERC20{
functionsymbol() externalviewreturns (stringmemory);
functionbalanceOf(address account) externalviewreturns (uint256);
// Note this is non standard but nearly all ERC20 have exposed decimal functionsfunctiondecimals() externalviewreturns (uint8);
functiontransfer(address recipient, uint256 amount)
externalreturns (bool);
functionallowance(address owner, address spender)
externalviewreturns (uint256);
functionapprove(address spender, uint256 amount) externalreturns (bool);
functiontransferFrom(address sender,
address recipient,
uint256 amount
) externalreturns (bool);
eventTransfer(addressindexedfrom, addressindexed to, uint256 value);
eventApproval(addressindexed owner,
addressindexed spender,
uint256 value
);
}
Contract Source Code
File 4 of 6: ILockingVault.sol
// SPDX-License-Identifier: Apache-2.0pragmasolidity ^0.8.3;import"./IERC20.sol";
interfaceILockingVault{
/// @notice Deposits and delegates voting power to an address provided with the call/// @param fundedAccount The address to credit this deposit to/// @param amount The amount of token which is deposited/// @param firstDelegation First delegation addressfunctiondeposit(address fundedAccount,
uint256 amount,
address firstDelegation
) external;
/// @notice Removes tokens from this contract and the voting power they represent/// @param amount The amount of token to withdrawfunctionwithdraw(uint256 amount) external;
/// @notice The token for this locking vaultfunctiontoken() externalreturns (IERC20);
}
Contract Source Code
File 5 of 6: MerkleProof.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/cryptography/MerkleProof.sol)pragmasolidity ^0.8.0;/**
* @dev These functions deal with verification of Merkle Trees proofs.
*
* The proofs can be generated using the JavaScript library
* https://github.com/miguelmota/merkletreejs[merkletreejs].
* Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
*
* See `test/utils/cryptography/MerkleProof.test.js` for some examples.
*/libraryMerkleProof{
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/functionverify(bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internalpurereturns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merklee tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/functionprocessProof(bytes32[] memory proof, bytes32 leaf) internalpurereturns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i =0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
computedHash =keccak256(abi.encodePacked(computedHash, proofElement));
} else {
// Hash(current element of the proof + current computed hash)
computedHash =keccak256(abi.encodePacked(proofElement, computedHash));
}
}
return computedHash;
}
}
Contract Source Code
File 6 of 6: MerkleRewards.sol
// SPDX-License-Identifier: Apache-2.0pragmasolidity ^0.8.3;import"@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import"../interfaces/IERC20.sol";
import"../interfaces/ILockingVault.sol";
abstractcontractAbstractMerkleRewards{
// The merkle root with deposits encoded into it as hash [address, amount]// Assumed to be a node sorted treebytes32public rewardsRoot;
// The token to pay out
IERC20 publicimmutable token;
// The historic user claimsmapping(address=>uint256) public claimed;
// The locking gov vault
ILockingVault public lockingVault;
/// @notice Constructs the contract and sets state and immutable variables/// @param _rewardsRoot The root a keccak256 merkle tree with leaves which are address amount pairs/// @param _token The erc20 contract which will be sent to the people with claims on the contract/// @param _lockingVault The governance vault which this deposits to on behalf of usersconstructor(bytes32 _rewardsRoot,
IERC20 _token,
ILockingVault _lockingVault
) {
rewardsRoot = _rewardsRoot;
token = _token;
lockingVault = _lockingVault;
// We approve the locking vault so that it we can deposit on behalf of users
_token.approve(address(lockingVault), type(uint256).max);
}
/// @notice Claims an amount of tokens which are in the tree and moves them directly into/// governance/// @param amount The amount of tokens to claim/// @param delegate The address the user will delegate to, WARNING - should not be zero/// @param totalGrant The total amount of tokens the user was granted/// @param merkleProof The merkle de-commitment which proves the user is in the merkle root/// @param destination The address which will be credited with fundsfunctionclaimAndDelegate(uint256 amount,
address delegate,
uint256 totalGrant,
bytes32[] calldata merkleProof,
address destination
) external{
// No delegating to zerorequire(delegate !=address(0), "Zero addr delegation");
// Validate the withdraw
_validateWithdraw(amount, totalGrant, merkleProof);
// Deposit for this sender into governance locking vault
lockingVault.deposit(destination, amount, delegate);
}
/// @notice Claims an amount of tokens which are in the tree and send them to the user/// @param amount The amount of tokens to claim/// @param totalGrant The total amount of tokens the user was granted/// @param merkleProof The merkle de-commitment which proves the user is in the merkle root/// @param destination The address which will be credited with fundsfunctionclaim(uint256 amount,
uint256 totalGrant,
bytes32[] calldata merkleProof,
address destination
) externalvirtual{
// Validate the withdraw
_validateWithdraw(amount, totalGrant, merkleProof);
// Transfer to the user
token.transfer(destination, amount);
}
/// @notice Validate a withdraw attempt by checking merkle proof and ensuring the user has not/// previously withdrawn/// @param amount The amount of tokens being claimed/// @param totalGrant The total amount of tokens the user was granted/// @param merkleProof The merkle de-commitment which proves the user is in the merkle rootfunction_validateWithdraw(uint256 amount,
uint256 totalGrant,
bytes32[] memory merkleProof
) internal{
// Hash the user plus the total grant amountbytes32 leafHash =keccak256(abi.encodePacked(msg.sender, totalGrant));
// Verify the proof for this leafrequire(
MerkleProof.verify(merkleProof, rewardsRoot, leafHash),
"Invalid Proof"
);
// Check that this claim won't give them more than the total grant then// increase the stored claim amountrequire(claimed[msg.sender] + amount <= totalGrant, "Claimed too much");
claimed[msg.sender] += amount;
}
}
// Deployable version of the abstractcontractMerkleRewardsisAbstractMerkleRewards{
/// @notice Constructs the contract and sets state and immutable variables/// @param _rewardsRoot The root a keccak256 merkle tree with leaves which are address amount pairs/// @param _token The erc20 contract which will be sent to the people with claims on the contract/// @param _lockingVault The governance vault which this deposits to on behalf of usersconstructor(bytes32 _rewardsRoot,
IERC20 _token,
ILockingVault _lockingVault
) AbstractMerkleRewards(_rewardsRoot, _token, _lockingVault) {}
}