diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..2ed6bef1 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,15 @@ +# Get a list of staged files that are added, copied, modified, or renamed +STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.sol' || true) + +# Exit early if no files are staged +if [ -z "$STAGED_FILES" ]; then + echo "No relevant files to format." + exit 0 +fi + +# Run prettier check +echo "Running Prettier check..." +yarn prettier --check --ignore-unknown --plugin=prettier-plugin-solidity $STAGED_FILES + +echo "Running solhint check..." +yarn solhint $STAGED_FILES diff --git a/contracts/merkle/MerkleProof.sol b/contracts/merkle/MerkleProof.sol index 20ad63f5..a44584f6 100644 --- a/contracts/merkle/MerkleProof.sol +++ b/contracts/merkle/MerkleProof.sol @@ -2,30 +2,26 @@ pragma solidity ^0.8.24; /* - * Merkle Proof library as seen on: - * https://github.com/gnosis/safe-token-distribution/blob/master/tooling/contracts/MerkleProof.sol - */ + * Merkle Proof library as seen on: + * https://github.com/gnosis/safe-token-distribution/blob/master/tooling/contracts/MerkleProof.sol + */ library MerkleProof { - function verify( - bytes32[] calldata proof, - bytes32 root, - bytes32 leaf - ) internal pure returns (bool) { - bytes32 computed = leaf; - for (uint256 i = 0; i < proof.length; i++) { - computed = hashPair(computed, proof[i]); + function verify(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + bytes32 computed = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computed = hashPair(computed, proof[i]); + } + return computed == root; } - return computed == root; - } - function hashPair(bytes32 a, bytes32 b) private pure returns (bytes32 value) { - (a, b) = (a < b) ? (a, b) : (b, a); + function hashPair(bytes32 a, bytes32 b) private pure returns (bytes32 value) { + (a, b) = (a < b) ? (a, b) : (b, a); - /// @solidity memory-safe-assembly - assembly { - mstore(0x00, a) - mstore(0x20, b) - value := keccak256(0x00, 0x40) + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } } - } } diff --git a/contracts/passport/PassportRegistry.sol b/contracts/passport/PassportRegistry.sol index 3eb3e8a6..46a5441f 100644 --- a/contracts/passport/PassportRegistry.sol +++ b/contracts/passport/PassportRegistry.sol @@ -309,7 +309,7 @@ contract PassportRegistry is Ownable, Pausable { walletActive[wallet] = true; idActive[id] = true; idSource[id] = source; - + uint256 result = sourcePassports[source] + 1; sourcePassports[source] = result; diff --git a/contracts/passport/PassportSources.sol b/contracts/passport/PassportSources.sol index 58aaed0d..deb5edeb 100644 --- a/contracts/passport/PassportSources.sol +++ b/contracts/passport/PassportSources.sol @@ -14,4 +14,4 @@ contract PassportSources is Ownable { function removeSource(string memory name) external onlyOwner { sources[name] = address(0); } -} \ No newline at end of file +} diff --git a/contracts/talent/TalentCommunitySale.sol b/contracts/talent/TalentCommunitySale.sol index dd7a9066..c053c970 100644 --- a/contracts/talent/TalentCommunitySale.sol +++ b/contracts/talent/TalentCommunitySale.sol @@ -7,115 +7,115 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; contract TalentCommunitySale is Ownable, ReentrancyGuard { - using Math for uint256; - - IERC20 public paymentToken; - uint256 private tokenDecimals; - address public receivingWallet; - - uint32 public constant TIER1_MAX_BUYS = 100; - uint32 public constant TIER2_MAX_BUYS = 580; - uint32 public constant TIER3_MAX_BUYS = 1250; - uint32 public constant TIER4_MAX_BUYS = 520; - - uint32 public tier1Bought; - uint32 public tier2Bought; - uint32 public tier3Bought; - uint32 public tier4Bought; - - uint256 public totalRaised; - - bool public saleActive; - - event Tier1Bought(address indexed buyer, uint256 amount); - event Tier2Bought(address indexed buyer, uint256 amount); - event Tier3Bought(address indexed buyer, uint256 amount); - event Tier4Bought(address indexed buyer, uint256 amount); - - mapping(address => bool) public listOfBuyers; - - constructor( - address initialOwner, - address _paymentToken, - address _receivingWallet, - uint256 _tokenDecimals - ) Ownable(initialOwner) { - paymentToken = IERC20(_paymentToken); - receivingWallet = _receivingWallet; - tokenDecimals = _tokenDecimals; - totalRaised = 0; - saleActive = false; - } - - function enableSale() external onlyOwner { - saleActive = true; - } - - function disableSale() external onlyOwner { - saleActive = false; - } - - function buyTier1() external nonReentrant { - require(saleActive, "TalentCommunitySale: Sale is not active"); - require( - paymentToken.allowance(msg.sender, address(this)) >= 100 * 10**tokenDecimals, - "TalentCommunitySale: Insufficient allowance" - ); - require(tier1Bought < TIER1_MAX_BUYS, "TalentCommunitySale: Tier 1 sold out"); - require(!listOfBuyers[msg.sender], "TalentCommunitySale: Address already bought"); - require(paymentToken.transferFrom(msg.sender, receivingWallet, 100 * 10**tokenDecimals), "Transfer failed"); - - tier1Bought++; - listOfBuyers[msg.sender] = true; - totalRaised += 100 * 10**tokenDecimals; - emit Tier1Bought(msg.sender, 100 * 10**tokenDecimals); - } - - function buyTier2() external nonReentrant { - require(saleActive, "TalentCommunitySale: Sale is not active"); - require( - paymentToken.allowance(msg.sender, address(this)) >= 250 * 10**tokenDecimals, - "TalentCommunitySale: Insufficient allowance" - ); - require(tier2Bought < TIER2_MAX_BUYS, "TalentCommunitySale: Tier 2 sold out"); - require(!listOfBuyers[msg.sender], "TalentCommunitySale: Address already bought"); - require(paymentToken.transferFrom(msg.sender, receivingWallet, 250 * 10**tokenDecimals), "Transfer failed"); - - tier2Bought++; - listOfBuyers[msg.sender] = true; - totalRaised += 250 * 10**tokenDecimals; - emit Tier2Bought(msg.sender, 250 * 10**tokenDecimals); - } - - function buyTier3() external nonReentrant { - require(saleActive, "TalentCommunitySale: Sale is not active"); - require( - paymentToken.allowance(msg.sender, address(this)) >= 500 * 10**tokenDecimals, - "TalentCommunitySale: Insufficient allowance" - ); - require(tier3Bought < TIER3_MAX_BUYS, "TalentCommunitySale: Tier 3 sold out"); - require(!listOfBuyers[msg.sender], "TalentCommunitySale: Address already bought"); - require(paymentToken.transferFrom(msg.sender, receivingWallet, 500 * 10**tokenDecimals), "Transfer failed"); - - tier3Bought++; - listOfBuyers[msg.sender] = true; - totalRaised += 500 * 10**tokenDecimals; - emit Tier3Bought(msg.sender, 500 * 10**tokenDecimals); - } - - function buyTier4() external nonReentrant { - require(saleActive, "TalentCommunitySale: Sale is not active"); - require( - paymentToken.allowance(msg.sender, address(this)) >= 1000 * 10**tokenDecimals, - "TalentCommunitySale: Insufficient allowance" - ); - require(tier4Bought < TIER4_MAX_BUYS, "TalentCommunitySale: Tier 4 sold out"); - require(!listOfBuyers[msg.sender], "TalentCommunitySale: Address already bought"); - require(paymentToken.transferFrom(msg.sender, receivingWallet, 1000 * 10**tokenDecimals), "Transfer failed"); - - tier4Bought++; - listOfBuyers[msg.sender] = true; - totalRaised += 1000 * 10**tokenDecimals; - emit Tier4Bought(msg.sender, 1000 * 10**tokenDecimals); - } + using Math for uint256; + + IERC20 public paymentToken; + uint256 private tokenDecimals; + address public receivingWallet; + + uint32 public constant TIER1_MAX_BUYS = 100; + uint32 public constant TIER2_MAX_BUYS = 580; + uint32 public constant TIER3_MAX_BUYS = 1250; + uint32 public constant TIER4_MAX_BUYS = 520; + + uint32 public tier1Bought; + uint32 public tier2Bought; + uint32 public tier3Bought; + uint32 public tier4Bought; + + uint256 public totalRaised; + + bool public saleActive; + + event Tier1Bought(address indexed buyer, uint256 amount); + event Tier2Bought(address indexed buyer, uint256 amount); + event Tier3Bought(address indexed buyer, uint256 amount); + event Tier4Bought(address indexed buyer, uint256 amount); + + mapping(address => bool) public listOfBuyers; + + constructor( + address initialOwner, + address _paymentToken, + address _receivingWallet, + uint256 _tokenDecimals + ) Ownable(initialOwner) { + paymentToken = IERC20(_paymentToken); + receivingWallet = _receivingWallet; + tokenDecimals = _tokenDecimals; + totalRaised = 0; + saleActive = false; + } + + function enableSale() external onlyOwner { + saleActive = true; + } + + function disableSale() external onlyOwner { + saleActive = false; + } + + function buyTier1() external nonReentrant { + require(saleActive, "TalentCommunitySale: Sale is not active"); + require( + paymentToken.allowance(msg.sender, address(this)) >= 100 * 10 ** tokenDecimals, + "TalentCommunitySale: Insufficient allowance" + ); + require(tier1Bought < TIER1_MAX_BUYS, "TalentCommunitySale: Tier 1 sold out"); + require(!listOfBuyers[msg.sender], "TalentCommunitySale: Address already bought"); + require(paymentToken.transferFrom(msg.sender, receivingWallet, 100 * 10 ** tokenDecimals), "Transfer failed"); + + tier1Bought++; + listOfBuyers[msg.sender] = true; + totalRaised += 100 * 10 ** tokenDecimals; + emit Tier1Bought(msg.sender, 100 * 10 ** tokenDecimals); + } + + function buyTier2() external nonReentrant { + require(saleActive, "TalentCommunitySale: Sale is not active"); + require( + paymentToken.allowance(msg.sender, address(this)) >= 250 * 10 ** tokenDecimals, + "TalentCommunitySale: Insufficient allowance" + ); + require(tier2Bought < TIER2_MAX_BUYS, "TalentCommunitySale: Tier 2 sold out"); + require(!listOfBuyers[msg.sender], "TalentCommunitySale: Address already bought"); + require(paymentToken.transferFrom(msg.sender, receivingWallet, 250 * 10 ** tokenDecimals), "Transfer failed"); + + tier2Bought++; + listOfBuyers[msg.sender] = true; + totalRaised += 250 * 10 ** tokenDecimals; + emit Tier2Bought(msg.sender, 250 * 10 ** tokenDecimals); + } + + function buyTier3() external nonReentrant { + require(saleActive, "TalentCommunitySale: Sale is not active"); + require( + paymentToken.allowance(msg.sender, address(this)) >= 500 * 10 ** tokenDecimals, + "TalentCommunitySale: Insufficient allowance" + ); + require(tier3Bought < TIER3_MAX_BUYS, "TalentCommunitySale: Tier 3 sold out"); + require(!listOfBuyers[msg.sender], "TalentCommunitySale: Address already bought"); + require(paymentToken.transferFrom(msg.sender, receivingWallet, 500 * 10 ** tokenDecimals), "Transfer failed"); + + tier3Bought++; + listOfBuyers[msg.sender] = true; + totalRaised += 500 * 10 ** tokenDecimals; + emit Tier3Bought(msg.sender, 500 * 10 ** tokenDecimals); + } + + function buyTier4() external nonReentrant { + require(saleActive, "TalentCommunitySale: Sale is not active"); + require( + paymentToken.allowance(msg.sender, address(this)) >= 1000 * 10 ** tokenDecimals, + "TalentCommunitySale: Insufficient allowance" + ); + require(tier4Bought < TIER4_MAX_BUYS, "TalentCommunitySale: Tier 4 sold out"); + require(!listOfBuyers[msg.sender], "TalentCommunitySale: Address already bought"); + require(paymentToken.transferFrom(msg.sender, receivingWallet, 1000 * 10 ** tokenDecimals), "Transfer failed"); + + tier4Bought++; + listOfBuyers[msg.sender] = true; + totalRaised += 1000 * 10 ** tokenDecimals; + emit Tier4Bought(msg.sender, 1000 * 10 ** tokenDecimals); + } } diff --git a/contracts/talent/TalentProtocolToken.sol b/contracts/talent/TalentProtocolToken.sol index 2796c40f..a49b6c95 100644 --- a/contracts/talent/TalentProtocolToken.sol +++ b/contracts/talent/TalentProtocolToken.sol @@ -8,30 +8,27 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; contract TalentProtocolToken is ERC20, ERC20Burnable, Pausable, Ownable { - // Mint 1B tokens to the initial owner and pause the contract - constructor(address initialOwner) - ERC20("TalentProtocolToken", "TALENT") - Ownable(initialOwner) - { - _mint(initialOwner, 1_000_000_000 ether); - _pause(); - } + // Mint 1B tokens to the initial owner and pause the contract + constructor(address initialOwner) ERC20("TalentProtocolToken", "TALENT") Ownable(initialOwner) { + _mint(initialOwner, 1_000_000_000 ether); + _pause(); + } - function _update(address from, address to, uint256 value) internal override(ERC20) { - require(to != address(this), "TalentProtocolToken: cannot transfer tokens to self"); - require(!paused() || owner() == _msgSender(), "Token transfer is not enabled while paused"); - super._update(from, to, value); - } + function _update(address from, address to, uint256 value) internal override(ERC20) { + require(to != address(this), "TalentProtocolToken: cannot transfer tokens to self"); + require(!paused() || owner() == _msgSender(), "Token transfer is not enabled while paused"); + super._update(from, to, value); + } - // Function to pause token transfers - function pause() external onlyOwner { - require(!paused(), "Token is already paused"); - _pause(); - } + // Function to pause token transfers + function pause() external onlyOwner { + require(!paused(), "Token is already paused"); + _pause(); + } - // Function to unpause token transfers - function unpause() external onlyOwner { - require(paused(), "Token is not paused"); - _unpause(); - } + // Function to unpause token transfers + function unpause() external onlyOwner { + require(paused(), "Token is not paused"); + _unpause(); + } } diff --git a/contracts/talent/TalentRewardClaim.sol b/contracts/talent/TalentRewardClaim.sol index e2f2cbd7..caf5f1ce 100644 --- a/contracts/talent/TalentRewardClaim.sol +++ b/contracts/talent/TalentRewardClaim.sol @@ -10,172 +10,159 @@ import "../passport/PassportBuilderScore.sol"; import "../merkle/MerkleProof.sol"; contract TalentRewardClaim is Ownable, ReentrancyGuard { - using Math for uint256; - - TalentProtocolToken public talentToken; - PassportBuilderScore public passportBuilderScore; - address public holdingWallet; - uint256 public constant WEEKLY_CLAIM_AMOUNT = 2000 ether; - uint256 public constant WEEK_DURATION = 7 days; - uint256 public constant MAX_CLAIM_WEEKS = 104; - uint256 public startTime; // Track the start time - bytes32 public merkleRoot; // Track the merkle root with the information of user owed amounts - - struct UserInfo { - uint256 amountClaimed; - uint256 lastClaimed; - } - - mapping(address => UserInfo) public userInfo; - - event TokensClaimed(address indexed user, uint256 amount); - event TokensBurned(address indexed user, uint256 amount); - event StartTimeSet(uint256 startTime); - event UserInitialized(address indexed user, uint256 amount, uint256 lastClaimed); - - constructor( - TalentProtocolToken _talentToken, - PassportBuilderScore _passportBuilderScore, - address _holdingWallet, - address initialOwner, - bytes32 _merkleRoot - ) Ownable(initialOwner) { - merkleRoot = _merkleRoot; - talentToken = _talentToken; - passportBuilderScore = _passportBuilderScore; - holdingWallet = _holdingWallet; - } - - /** - * @notice Initializes the user information via changing the root of the merkle tree. - * @dev Can only be called by the owner. This function sets up the root of the merkle tree - * that was calculated with the wallet and amount owed for each user. - * @param _newMerkleRoot The new merkle root to be set. - */ - function setMerkleRoot( - bytes32 _newMerkleRoot - ) external onlyOwner { - merkleRoot = _newMerkleRoot; - } - - /** - * @notice Sets the start time for token claims. - * @dev Can only be called by the owner. This function initializes the startTime variable with the provided value. - * @param _startTime The timestamp representing the start time for token claims. - */ - function setStartTime(uint256 _startTime) external onlyOwner { - startTime = _startTime; - emit StartTimeSet(_startTime); - } - - /** - * @notice Allows users to claim their owed tokens. - * @dev Can only be called once the setup is complete and the start time is set. This function calculates - * the number of weeks since the last claim and allows users to claim tokens based on their builder score. - * It also burns tokens for missed weeks if applicable. - * @dev Uses the nonReentrant modifier to prevent reentrancy attacks. - */ - function claimTokens( - bytes32[] calldata merkleProof, - uint256 amountAllocated - ) external nonReentrant { - require(startTime > 0, "Start time not set"); - - verify(merkleProof, amountAllocated); - - address beneficiary = msg.sender; - uint256 amountToClaim = calculate(beneficiary, amountAllocated); - - UserInfo storage user = userInfo[msg.sender]; - require(amountToClaim > 0, "No tokens owed"); - - uint256 passportId = passportBuilderScore.passportRegistry().passportId(beneficiary); - uint256 builderScore = passportBuilderScore.getScore(passportId); - - uint256 claimMultiplier = (builderScore > 40) ? 5 : 1; - uint256 maxPerWeekAmountForUser = WEEKLY_CLAIM_AMOUNT * claimMultiplier; - - // calculate number of weeks that have passed since start time - uint256 weeksPassed = (block.timestamp - startTime) / WEEK_DURATION; - uint256 weeksSinceLastClaim = 0; - - if (user.lastClaimed != 0) { - weeksSinceLastClaim = (block.timestamp - user.lastClaimed) / WEEK_DURATION; - require(weeksSinceLastClaim > 0, "Can only claim once per week"); - } else { - weeksSinceLastClaim = weeksPassed; + using Math for uint256; + + TalentProtocolToken public talentToken; + PassportBuilderScore public passportBuilderScore; + address public holdingWallet; + uint256 public constant WEEKLY_CLAIM_AMOUNT = 2000 ether; + uint256 public constant WEEK_DURATION = 7 days; + uint256 public constant MAX_CLAIM_WEEKS = 104; + uint256 public startTime; // Track the start time + bytes32 public merkleRoot; // Track the merkle root with the information of user owed amounts + + struct UserInfo { + uint256 amountClaimed; + uint256 lastClaimed; } - uint256 amountToBurn = 0; - uint256 amountToTransfer = 0; - - if (weeksPassed >= MAX_CLAIM_WEEKS) { - // Calculate the number of weeks missed - uint256 weeksMissed = 0; - if (user.lastClaimed != 0) { - weeksMissed = weeksPassed - weeksSinceLastClaim; - } else { - weeksMissed = weeksPassed; - } - - // Burn the equivalent amount of tokens for the missed weeks - amountToBurn = Math.min(WEEKLY_CLAIM_AMOUNT * weeksMissed, amountToClaim); - user.amountClaimed += amountToBurn; - - // Transfer the remaining owed amount to the user - amountToTransfer = amountToClaim - amountToBurn; - user.amountClaimed += amountToTransfer; - user.lastClaimed = block.timestamp; - } else { - amountToBurn = Math.min(WEEKLY_CLAIM_AMOUNT * (weeksSinceLastClaim - 1), amountToClaim); - user.amountClaimed += amountToBurn; - - amountToTransfer = Math.min(maxPerWeekAmountForUser, amountToClaim - amountToBurn); - user.amountClaimed += amountToTransfer; - - user.lastClaimed = block.timestamp; + mapping(address => UserInfo) public userInfo; + + event TokensClaimed(address indexed user, uint256 amount); + event TokensBurned(address indexed user, uint256 amount); + event StartTimeSet(uint256 startTime); + event UserInitialized(address indexed user, uint256 amount, uint256 lastClaimed); + + constructor( + TalentProtocolToken _talentToken, + PassportBuilderScore _passportBuilderScore, + address _holdingWallet, + address initialOwner, + bytes32 _merkleRoot + ) Ownable(initialOwner) { + merkleRoot = _merkleRoot; + talentToken = _talentToken; + passportBuilderScore = _passportBuilderScore; + holdingWallet = _holdingWallet; } - if (amountToTransfer > 0) { - talentToken.transferFrom(holdingWallet, msg.sender, amountToTransfer); - emit TokensClaimed(msg.sender, amountToTransfer); + /** + * @notice Initializes the user information via changing the root of the merkle tree. + * @dev Can only be called by the owner. This function sets up the root of the merkle tree + * that was calculated with the wallet and amount owed for each user. + * @param _newMerkleRoot The new merkle root to be set. + */ + function setMerkleRoot(bytes32 _newMerkleRoot) external onlyOwner { + merkleRoot = _newMerkleRoot; } - if (amountToBurn > 0) { - talentToken.burnFrom(holdingWallet, amountToBurn); - emit TokensBurned(msg.sender, amountToBurn); + + /** + * @notice Sets the start time for token claims. + * @dev Can only be called by the owner. This function initializes the startTime variable with the provided value. + * @param _startTime The timestamp representing the start time for token claims. + */ + function setStartTime(uint256 _startTime) external onlyOwner { + startTime = _startTime; + emit StartTimeSet(_startTime); + } + + /** + * @notice Allows users to claim their owed tokens. + * @dev Can only be called once the setup is complete and the start time is set. This function calculates + * the number of weeks since the last claim and allows users to claim tokens based on their builder score. + * It also burns tokens for missed weeks if applicable. + * @dev Uses the nonReentrant modifier to prevent reentrancy attacks. + */ + function claimTokens(bytes32[] calldata merkleProof, uint256 amountAllocated) external nonReentrant { + require(startTime > 0, "Start time not set"); + + verify(merkleProof, amountAllocated); + + address beneficiary = msg.sender; + uint256 amountToClaim = calculate(beneficiary, amountAllocated); + + UserInfo storage user = userInfo[msg.sender]; + require(amountToClaim > 0, "No tokens owed"); + + uint256 passportId = passportBuilderScore.passportRegistry().passportId(beneficiary); + uint256 builderScore = passportBuilderScore.getScore(passportId); + + uint256 claimMultiplier = (builderScore > 40) ? 5 : 1; + uint256 maxPerWeekAmountForUser = WEEKLY_CLAIM_AMOUNT * claimMultiplier; + + // calculate number of weeks that have passed since start time + uint256 weeksPassed = (block.timestamp - startTime) / WEEK_DURATION; + uint256 weeksSinceLastClaim = 0; + + if (user.lastClaimed != 0) { + weeksSinceLastClaim = (block.timestamp - user.lastClaimed) / WEEK_DURATION; + require(weeksSinceLastClaim > 0, "Can only claim once per week"); + } else { + weeksSinceLastClaim = weeksPassed; + } + + uint256 amountToBurn = 0; + uint256 amountToTransfer = 0; + + if (weeksPassed >= MAX_CLAIM_WEEKS) { + // Calculate the number of weeks missed + uint256 weeksMissed = 0; + if (user.lastClaimed != 0) { + weeksMissed = weeksPassed - weeksSinceLastClaim; + } else { + weeksMissed = weeksPassed; + } + + // Burn the equivalent amount of tokens for the missed weeks + amountToBurn = Math.min(WEEKLY_CLAIM_AMOUNT * weeksMissed, amountToClaim); + user.amountClaimed += amountToBurn; + + // Transfer the remaining owed amount to the user + amountToTransfer = amountToClaim - amountToBurn; + user.amountClaimed += amountToTransfer; + user.lastClaimed = block.timestamp; + } else { + amountToBurn = Math.min(WEEKLY_CLAIM_AMOUNT * (weeksSinceLastClaim - 1), amountToClaim); + user.amountClaimed += amountToBurn; + + amountToTransfer = Math.min(maxPerWeekAmountForUser, amountToClaim - amountToBurn); + user.amountClaimed += amountToTransfer; + + user.lastClaimed = block.timestamp; + } + + if (amountToTransfer > 0) { + talentToken.transferFrom(holdingWallet, msg.sender, amountToTransfer); + emit TokensClaimed(msg.sender, amountToTransfer); + } + if (amountToBurn > 0) { + talentToken.burnFrom(holdingWallet, amountToBurn); + emit TokensBurned(msg.sender, amountToBurn); + } + } + + function tokensClaimed(address user) external view returns (uint256) { + return userInfo[user].amountClaimed; + } + + function lastClaimed(address user) external view returns (uint256) { + return userInfo[user].lastClaimed; + } + + function verify(bytes32[] calldata proof, uint256 amountAllocated) internal view { + // Computing proof using leaf double hashing + // https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack/ + + bytes32 root = merkleRoot; + bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, amountAllocated)))); + + require(MerkleProof.verify(proof, root, leaf), "Invalid Allocation Proof"); + } + + function calculate(address beneficiary, uint256 amountAllocated) internal view returns (uint256 amountToClaim) { + UserInfo storage user = userInfo[beneficiary]; + assert(user.amountClaimed <= amountAllocated); + + amountToClaim = amountAllocated - user.amountClaimed; } - } - - function tokensClaimed(address user) external view returns (uint256) { - return userInfo[user].amountClaimed; - } - - function lastClaimed(address user) external view returns (uint256) { - return userInfo[user].lastClaimed; - } - - function verify( - bytes32[] calldata proof, - uint256 amountAllocated - ) internal view { - // Computing proof using leaf double hashing - // https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack/ - - bytes32 root = merkleRoot; - bytes32 leaf = keccak256( - bytes.concat(keccak256(abi.encode(msg.sender, amountAllocated))) - ); - - require(MerkleProof.verify(proof, root, leaf), "Invalid Allocation Proof"); - } - - function calculate( - address beneficiary, - uint256 amountAllocated - ) internal view returns (uint256 amountToClaim) { - UserInfo storage user = userInfo[beneficiary]; - assert(user.amountClaimed <= amountAllocated); - - amountToClaim = amountAllocated - user.amountClaimed; - } } diff --git a/contracts/talent/TalentTGEUnlock.sol b/contracts/talent/TalentTGEUnlock.sol index 4490d36d..02089a92 100644 --- a/contracts/talent/TalentTGEUnlock.sol +++ b/contracts/talent/TalentTGEUnlock.sol @@ -18,11 +18,7 @@ contract TalentTGEUnlock is Ownable { bool public isContractEnabled; mapping(address => uint256) public claimed; - constructor( - address _token, - bytes32 _merkleRoot, - address owner - ) Ownable(owner) { + constructor(address _token, bytes32 _merkleRoot, address owner) Ownable(owner) { token = _token; merkleRoot = _merkleRoot; isContractEnabled = false; @@ -36,10 +32,7 @@ contract TalentTGEUnlock is Ownable { isContractEnabled = true; } - function claim( - bytes32[] calldata merkleProofClaim, - uint256 amountAllocated - ) external { + function claim(bytes32[] calldata merkleProofClaim, uint256 amountAllocated) external { require(isContractEnabled, "Contracts are disabled"); require(claimed[msg.sender] == 0, "Already claimed"); verifyAmount(merkleProofClaim, amountAllocated); @@ -53,25 +46,14 @@ contract TalentTGEUnlock is Ownable { emit Claimed(beneficiary, amountToClaim, 0); } - function verifyAmount( - bytes32[] calldata proof, - uint256 amountAllocated - ) internal view { + function verifyAmount(bytes32[] calldata proof, uint256 amountAllocated) internal view { bytes32 root = merkleRoot; - bytes32 leaf = keccak256( - bytes.concat(keccak256(abi.encode(msg.sender, amountAllocated))) - ); - - require( - MerkleProof.verify(proof, root, leaf), - "Invalid Allocation Proof" - ); + bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, amountAllocated)))); + + require(MerkleProof.verify(proof, root, leaf), "Invalid Allocation Proof"); } - function calculate( - address beneficiary, - uint256 amountAllocated - ) internal view returns (uint256 amountToClaim) { + function calculate(address beneficiary, uint256 amountAllocated) internal view returns (uint256 amountToClaim) { uint256 amountClaimed = claimed[beneficiary]; assert(amountClaimed <= amountAllocated); amountToClaim = amountAllocated - amountClaimed; diff --git a/package.json b/package.json index c435e753..4565977a 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "dotenv": "^16.0.3", "eslint": "^8.19.0", "ethereum-waffle": "3.4.0", + "husky": "^9.1.6", "prettier": "^2.4.1", "prettier-plugin-solidity": "^1.0.0-beta.18", "solc": "^0.8.25" @@ -56,6 +57,7 @@ "build": "hardhat compile", "test": "hardhat test", "lint": "solhint contracts/**.sol && prettier --check contracts/**/*.sol", - "docgen": "solidity-docgen --solc-module solc-0.8.25 --templates=docgen/templates" + "docgen": "solidity-docgen --solc-module solc-0.8.25 --templates=docgen/templates", + "prepare": "husky" } } diff --git a/yarn.lock b/yarn.lock index dc4afcaf..a95d0f60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5730,6 +5730,11 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +husky@^9.1.6: + version "9.1.6" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.6.tgz#e23aa996b6203ab33534bdc82306b0cf2cb07d6c" + integrity sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A== + hyperlinker@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e"