-
-
Save krakovia-evm/9079fa8908a520e75d3a376f8b937b58 to your computer and use it in GitHub Desktop.
Cradle claim contract
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
@title Vested Claims | |
@license MIT | |
@author Krakovia | |
@notice This contract is used to distribute tokens to users, 31% TGE, 69% linear vesting | |
""" | |
interface IERC20: | |
def transfer(_to: address, _value: uint256) -> bool: nonpayable | |
vesting_start_time: public(uint256) | |
vesting_end_time: public(uint256) | |
merkle_root: public(bytes32) | |
token: public(address) | |
owner: public(address) | |
claimed_amount: public(HashMap[address, uint256]) | |
event Claimed: | |
user: indexed(address) | |
amount: uint256 | |
event MerkleRootUpdated: | |
merkle_root: indexed(bytes32) | |
event TokensRescued: | |
to: indexed(address) | |
amount: uint256 | |
@deploy | |
def __init__(merkle_root: bytes32, token: address, vesting_start_time: uint256, vesting_end_time: uint256): | |
""" | |
@notice Initialize the contract with the merkle root, token address, vesting start and end time | |
@param merkle_root: bytes32, the merkle root of the vesting list | |
@param token: address, the address of the token contract | |
@param vesting_start_time: uint256, the start time of the vesting | |
@param vesting_end_time: uint256, the end time of the vesting | |
""" | |
self.merkle_root = merkle_root | |
self.token = token | |
self.vesting_start_time = vesting_start_time | |
self.vesting_end_time = vesting_end_time | |
self.owner = msg.sender | |
log MerkleRootUpdated(merkle_root) | |
def onlyOwner(): | |
""" | |
@notice This function is used to restrict access to the owner only | |
""" | |
assert msg.sender == self.owner, "Only owner can call this function" | |
@pure | |
def _hash_pair(a: bytes32, b: bytes32) -> bytes32: | |
""" | |
@notice This function is used to hash a pair of bytes32 | |
@param a: bytes32 | |
@param b: bytes32 | |
@return bytes32: the hash of the pair | |
""" | |
if convert(a, uint256) < convert(b, uint256): | |
return keccak256(concat(a, b)) | |
return keccak256(concat(b, a)) | |
def _verify_proof(proof: DynArray[bytes32, 20], leaf: bytes32) -> bool: | |
""" | |
@notice This function is used to verify the merkle proof | |
@param proof: DynArray[bytes32, 20], the merkle proof | |
@param leaf: bytes32, the leaf node | |
@return bool: True if the proof is valid | |
""" | |
computed_hash: bytes32 = leaf | |
for proof_element: bytes32 in proof: | |
computed_hash = self._hash_pair(computed_hash, proof_element) | |
return computed_hash == self.merkle_root | |
def verify_proof(user: address, amount: uint256, proof: DynArray[bytes32, 20]) -> bool: | |
""" | |
@notice This function is used to verify the merkle proof | |
@param user: address, the address of the user | |
@param amount: uint256, the amount of tokens | |
@param proof: DynArray[bytes32, 20], the merkle proof | |
@return bool: True if the proof is valid | |
""" | |
return self._verify_proof( | |
proof, | |
keccak256( | |
concat( | |
convert(user, bytes20), | |
convert(amount, bytes32) | |
) | |
) | |
) | |
@view | |
def _calculate_vested_amount(total_amount: uint256) -> uint256: | |
""" | |
@notice This function is used to calculate the vested amount | |
@param total_amount: uint256, the total amount of tokens | |
@return vested: uint256, the vested amount | |
""" | |
current_time: uint256 = block.timestamp | |
start_time: uint256 = self.vesting_start_time | |
end_time: uint256 = self.vesting_end_time | |
vested: uint256 = 0 | |
if current_time >= end_time: | |
return total_amount | |
vesting_duration: uint256 = end_time - start_time | |
elapsed: uint256 = current_time - start_time | |
instant_release: uint256 = (total_amount * 31) // 100 | |
linear_vesting: uint256 = (total_amount * 69) // 100 | |
vested = instant_release + (linear_vesting * elapsed) // vesting_duration | |
return vested | |
@external | |
def set_merkle_root(merkle_root: bytes32): | |
""" | |
@notice This function is used to set the merkle root | |
@param merkle_root bytes32, the new merkle root | |
@dev This function can only be called by the owner | |
""" | |
self.onlyOwner() | |
self.merkle_root = merkle_root | |
log MerkleRootUpdated(merkle_root) | |
@external | |
def rescue_tokens(to: address, amount: uint256): | |
""" | |
@notice This function is used to rescue tokens from the contract | |
@param to address, the address to send the tokens to | |
@param amount uint256, the amount of tokens to send | |
@dev this is a "better safe then sorry" function, use it only in case of emergency | |
@dev This function can only be called by the owner | |
""" | |
self.onlyOwner() | |
log TokensRescued(to, amount) | |
_success: bool = extcall IERC20(self.token).transfer(to, amount) | |
assert _success, "Transfer failed" | |
@external | |
def claim(user: address, total_amount: uint256, proof: DynArray[bytes32, 20]) -> bool: | |
""" | |
@notice This function is used to claim the tokens | |
@dev Anyone can claim for any user | |
@param user address, the address of the user | |
@param total_amount uint256, the total amount of tokens | |
@param proof DynArray[bytes32, 20], the merkle proof | |
@return bool True if the claim is successful | |
""" | |
# Checks | |
assert self.verify_proof(user, total_amount, proof), "Invalid proof" | |
assert block.timestamp >= self.vesting_start_time, "Claiming is not available yet" | |
claimable: uint256 = 0 | |
current_amount: uint256 = self.claimed_amount[user] | |
vested: uint256 = self._calculate_vested_amount(total_amount) | |
# Calculate how much the user can claim now | |
if vested > current_amount: | |
claimable = vested - current_amount | |
assert claimable > 0, "Nothing to claim" | |
# Update the claimed amount - Effects | |
self.claimed_amount[user] += claimable | |
# invariant: claimed amount should always be less than or equal to amount (better safe then sorry) | |
assert current_amount + claimable <= total_amount, "Claimed amount exceeds total amount" | |
log Claimed(user, claimable) | |
# Transfer the claimable amount to the user - Interactions | |
_success: bool = extcall IERC20(self.token).transfer(user, claimable) | |
assert _success, "Transfer failed" | |
return True | |
@view | |
@external | |
def claimable_amount(user: address, total_amount: uint256) -> uint256: | |
""" | |
@notice this function is needed on the frontend to show the claimable amount | |
@param user address, the address of the user | |
@param total_amount uint256, the total amount of tokens | |
@return claimable uint256, the amount of tokens that can be claimed | |
@dev the data is NOT verified against the merkle root | |
@dev no on-chain contract should/will use this function | |
""" | |
assert block.timestamp >= self.vesting_start_time, "Claiming is not available yet" | |
claimable: uint256 = 0 | |
current_amount: uint256 = self.claimed_amount[user] | |
vested: uint256 = self._calculate_vested_amount(total_amount) | |
# Calculate how much the user can claim now | |
if vested > current_amount: | |
claimable = vested - current_amount | |
return claimable |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment