From 37b73f5c153371f87556b18230d552f2d2d2b68d Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 28 Nov 2024 16:10:37 -0800 Subject: [PATCH] formatting --- src/Badge.sol | 27 +++-- src/Governance.sol | 66 +++------- src/LiquidityPool.sol | 126 ++++++++++---------- src/PlatformToken.sol | 46 +++---- src/Project.sol | 32 +++-- src/QuadraticFunding.sol | 37 +++--- src/UserProfile.sol | 2 +- test/Badge.t.sol | 14 +-- test/Governance.t.sol | 57 ++++----- test/LiquidityPool.t.sol | 232 ++++++++++++++++++------------------ test/PlatformToken.t.sol | 44 +++---- test/Project.t.sol | 62 +++++----- test/QuadraticFunding.t.sol | 46 +++---- test/UserProfile.t.sol | 10 +- 14 files changed, 385 insertions(+), 416 deletions(-) diff --git a/src/Badge.sol b/src/Badge.sol index 4369bdc..ca53b56 100644 --- a/src/Badge.sol +++ b/src/Badge.sol @@ -13,18 +13,19 @@ contract Badge is ERC721, Ownable { // Badge types and their requirements enum BadgeType { - EARLY_SUPPORTER, // First 100 users to back a project - POWER_BACKER, // Backed more than 5 projects - LIQUIDITY_PROVIDER, // Provided significant liquidity - GOVERNANCE_ACTIVE // Participated in multiple proposals + EARLY_SUPPORTER, // First 100 users to back a project + POWER_BACKER, // Backed more than 5 projects + LIQUIDITY_PROVIDER, // Provided significant liquidity + GOVERNANCE_ACTIVE // Participated in multiple proposals + } // Mapping from token ID to badge type mapping(uint256 => BadgeType) public badgeTypes; - + // Mapping from address to badge type to whether they have earned it mapping(address => mapping(BadgeType => bool)) public hasBadge; - + // Mapping from badge type to its benefits multiplier (in basis points, 100 = 1%) mapping(BadgeType => uint256) public badgeBenefits; @@ -34,10 +35,10 @@ contract Badge is ERC721, Ownable { constructor() ERC721("Platform Achievement Badge", "BADGE") Ownable() { _tokenIds = 0; // Set initial badge benefits - badgeBenefits[BadgeType.EARLY_SUPPORTER] = 500; // 5% discount - badgeBenefits[BadgeType.POWER_BACKER] = 1000; // 10% discount + badgeBenefits[BadgeType.EARLY_SUPPORTER] = 500; // 5% discount + badgeBenefits[BadgeType.POWER_BACKER] = 1000; // 10% discount badgeBenefits[BadgeType.LIQUIDITY_PROVIDER] = 1500; // 15% discount - badgeBenefits[BadgeType.GOVERNANCE_ACTIVE] = 750; // 7.5% discount + badgeBenefits[BadgeType.GOVERNANCE_ACTIVE] = 750; // 7.5% discount } /** @@ -50,7 +51,7 @@ contract Badge is ERC721, Ownable { _tokenIds++; uint256 newTokenId = _tokenIds; - + _safeMint(recipient, newTokenId); badgeTypes[newTokenId] = badgeType; hasBadge[recipient][badgeType] = true; @@ -76,14 +77,14 @@ contract Badge is ERC721, Ownable { */ function getTotalBenefits(address user) external view returns (uint256) { uint256 totalBenefit = 0; - - for (uint i = 0; i <= uint(type(BadgeType).max); i++) { + + for (uint256 i = 0; i <= uint256(type(BadgeType).max); i++) { BadgeType badgeType = BadgeType(i); if (hasBadge[user][badgeType]) { totalBenefit += badgeBenefits[badgeType]; } } - + // Cap total benefit at 25% return totalBenefit > 2500 ? 2500 : totalBenefit; } diff --git a/src/Governance.sol b/src/Governance.sol index b6fa1d7..90eac07 100644 --- a/src/Governance.sol +++ b/src/Governance.sol @@ -29,24 +29,15 @@ contract Governance is Ownable, ReentrancyGuard { uint256 public proposalCount; uint256 public constant VOTING_PERIOD = 7 days; uint256 public constant EXECUTION_DELAY = 2 days; - uint256 public constant PROPOSAL_THRESHOLD = 100 * 10**18; // 100 tokens needed to create proposal - + uint256 public constant PROPOSAL_THRESHOLD = 100 * 10 ** 18; // 100 tokens needed to create proposal + mapping(uint256 => Proposal) public proposals; mapping(uint256 => uint256) public proposalExecutionTime; event ProposalCreated( - uint256 indexed proposalId, - address indexed proposer, - string description, - uint256 startTime, - uint256 endTime - ); - event VoteCast( - address indexed voter, - uint256 indexed proposalId, - bool support, - uint256 weight + uint256 indexed proposalId, address indexed proposer, string description, uint256 startTime, uint256 endTime ); + event VoteCast(address indexed voter, uint256 indexed proposalId, bool support, uint256 weight); event ProposalExecuted(uint256 indexed proposalId); constructor(address _platformToken) Ownable() { @@ -59,15 +50,8 @@ contract Governance is Ownable, ReentrancyGuard { * @param target Address of contract to call if proposal passes * @param callData Function call data to execute if proposal passes */ - function createProposal( - string memory description, - address target, - bytes memory callData - ) external { - require( - platformToken.balanceOf(msg.sender) >= PROPOSAL_THRESHOLD, - "Insufficient tokens to create proposal" - ); + function createProposal(string memory description, address target, bytes memory callData) external { + require(platformToken.balanceOf(msg.sender) >= PROPOSAL_THRESHOLD, "Insufficient tokens to create proposal"); require(target != address(0), "Invalid target address"); uint256 startTime = block.timestamp; @@ -83,13 +67,7 @@ contract Governance is Ownable, ReentrancyGuard { newProposal.target = target; newProposal.callData = callData; - emit ProposalCreated( - proposalCount, - msg.sender, - description, - startTime, - endTime - ); + emit ProposalCreated(proposalCount, msg.sender, description, startTime, endTime); } /** @@ -111,7 +89,7 @@ contract Governance is Ownable, ReentrancyGuard { require(votes > 0, "No voting power"); proposal.hasVoted[msg.sender] = true; - + if (support) { proposal.forVotes += votes; } else { @@ -130,19 +108,19 @@ contract Governance is Ownable, ReentrancyGuard { require(block.timestamp > proposal.endTime, "Voting period not ended"); require(!proposal.executed, "Proposal already executed"); require(proposal.forVotes > proposal.againstVotes, "Proposal rejected"); - + // Check execution delay if (proposalExecutionTime[proposalId] == 0) { proposalExecutionTime[proposalId] = block.timestamp + EXECUTION_DELAY; return; } - + require(block.timestamp >= proposalExecutionTime[proposalId], "Execution delay not met"); proposal.executed = true; - + // Execute the proposal's action - (bool success, ) = proposal.target.call(proposal.callData); + (bool success,) = proposal.target.call(proposal.callData); require(success, "Proposal execution failed"); emit ProposalExecuted(proposalId); @@ -157,20 +135,12 @@ contract Governance is Ownable, ReentrancyGuard { * @return endTime End time of voting period * @return executed Whether the proposal has been executed */ - function getProposal(uint256 proposalId) external view returns ( - uint256 forVotes, - uint256 againstVotes, - uint256 startTime, - uint256 endTime, - bool executed - ) { + function getProposal(uint256 proposalId) + external + view + returns (uint256 forVotes, uint256 againstVotes, uint256 startTime, uint256 endTime, bool executed) + { Proposal storage proposal = proposals[proposalId]; - return ( - proposal.forVotes, - proposal.againstVotes, - proposal.startTime, - proposal.endTime, - proposal.executed - ); + return (proposal.forVotes, proposal.againstVotes, proposal.startTime, proposal.endTime, proposal.executed); } } diff --git a/src/LiquidityPool.sol b/src/LiquidityPool.sol index 1f189cb..1f2ae6c 100644 --- a/src/LiquidityPool.sol +++ b/src/LiquidityPool.sol @@ -11,25 +11,25 @@ import "@openzeppelin/contracts/utils/math/SafeMath.sol"; /// @notice Automated Market Maker (AMM) for ETH/BACKR trading contract LiquidityPool is ReentrancyGuard, Pausable, Ownable { using SafeMath for uint256; - + PlatformToken public token; - + // Fee denominator (1000 = 0.3% fee) uint256 public constant FEE_DENOMINATOR = 1000; uint256 public constant FEE_NUMERATOR = 3; uint256 public immutable MINIMUM_LIQUIDITY; - + uint256 public totalLiquidity; mapping(address => uint256) public liquidityBalance; - + uint256 public ethReserve; uint256 public tokenReserve; - + event LiquidityAdded(address indexed provider, uint256 ethAmount, uint256 tokenAmount, uint256 liquidity); event LiquidityRemoved(address indexed provider, uint256 ethAmount, uint256 tokenAmount, uint256 liquidity); event TokensPurchased(address indexed buyer, uint256 ethIn, uint256 tokensOut); event TokensSold(address indexed seller, uint256 tokensIn, uint256 ethOut); - + error InsufficientLiquidity(); error InsufficientInputAmount(); error InsufficientOutputAmount(); @@ -37,109 +37,109 @@ contract LiquidityPool is ReentrancyGuard, Pausable, Ownable { error TransferFailed(); error UnbalancedLiquidityRatios(); error InsufficientTokenAmount(); - + // Constructor now accepts minimum liquidity parameter constructor(address _token, uint256 _minimumLiquidity) { token = PlatformToken(_token); MINIMUM_LIQUIDITY = _minimumLiquidity == 0 ? 1000 : _minimumLiquidity; } - + /// @notice Add liquidity to the pool /// @param _tokenAmount Amount of tokens to add function addLiquidity(uint256 _tokenAmount) external payable whenNotPaused nonReentrant { if (_tokenAmount == 0 || msg.value == 0) revert InsufficientInputAmount(); - + uint256 liquidity; - + if (totalLiquidity == 0) { liquidity = _sqrt(msg.value * _tokenAmount); if (liquidity <= MINIMUM_LIQUIDITY) revert InsufficientLiquidity(); - + // Transfer tokens first bool success = token.transferFrom(msg.sender, address(this), _tokenAmount); if (!success) revert TransferFailed(); - + // Mint MINIMUM_LIQUIDITY to address(this) _mint(address(this), MINIMUM_LIQUIDITY); liquidity -= MINIMUM_LIQUIDITY; - + // Update reserves ethReserve = msg.value; tokenReserve = _tokenAmount; } else { uint256 ethRatio = (msg.value * 1e18) / ethReserve; uint256 tokenRatio = (_tokenAmount * 1e18) / tokenReserve; - + // Check ratios match within 1% slippage if (ethRatio > tokenRatio * 101 / 100 || tokenRatio > ethRatio * 101 / 100) { revert UnbalancedLiquidityRatios(); } - + // Calculate liquidity tokens to mint liquidity = (msg.value * totalLiquidity) / ethReserve; - + // Transfer tokens bool success = token.transferFrom(msg.sender, address(this), _tokenAmount); if (!success) revert TransferFailed(); - + // Update reserves ethReserve += msg.value; tokenReserve += _tokenAmount; } - + // Mint liquidity tokens to provider _mint(msg.sender, liquidity); - + emit LiquidityAdded(msg.sender, msg.value, _tokenAmount, liquidity); } - + /// @notice Remove liquidity from the pool /// @param _liquidity Amount of liquidity tokens to burn function removeLiquidity(uint256 _liquidity) external whenNotPaused nonReentrant { if (_liquidity == 0) revert InsufficientInputAmount(); if (_liquidity > liquidityBalance[msg.sender]) revert InsufficientLiquidity(); - + // Calculate token amounts uint256 ethAmount = (_liquidity * ethReserve) / totalLiquidity; uint256 tokenAmount = (_liquidity * tokenReserve) / totalLiquidity; - + // Update state first to prevent reentrancy _burn(msg.sender, _liquidity); ethReserve -= ethAmount; tokenReserve -= tokenAmount; - + // Transfer tokens bool success = token.transfer(msg.sender, tokenAmount); if (!success) revert TransferFailed(); - + // Transfer ETH last (success,) = msg.sender.call{value: ethAmount}(""); if (!success) revert TransferFailed(); - + emit LiquidityRemoved(msg.sender, ethAmount, tokenAmount, _liquidity); } - + /// @notice Calculate output amount for a swap /// @param _inputAmount Amount of input token /// @param _inputReserve Reserve of input token /// @param _outputReserve Reserve of output token - function getOutputAmount( - uint256 _inputAmount, - uint256 _inputReserve, - uint256 _outputReserve - ) public pure returns (uint256) { + function getOutputAmount(uint256 _inputAmount, uint256 _inputReserve, uint256 _outputReserve) + public + pure + returns (uint256) + { if (_inputAmount == 0) revert InsufficientInputAmount(); if (_inputReserve == 0 || _outputReserve == 0) revert InsufficientLiquidity(); // Calculate output amount with fee using SafeMath // First divide to reduce the number size, then multiply uint256 inputAmountWithFee = _inputAmount.mul(FEE_DENOMINATOR.sub(FEE_NUMERATOR)).div(FEE_DENOMINATOR); - + // Calculate output amount using SafeMath // Rearrange formula to minimize large numbers: (out * input) / (reserve + input) uint256 numerator = _outputReserve.mul(inputAmountWithFee); uint256 denominator = _inputReserve.add(inputAmountWithFee); - + return numerator.div(denominator); } @@ -148,14 +148,14 @@ contract LiquidityPool is ReentrancyGuard, Pausable, Ownable { function swapETHForTokens(uint256 _minTokens) external payable whenNotPaused nonReentrant { if (msg.value == 0) revert InsufficientInputAmount(); if (ethReserve == 0 || tokenReserve == 0) revert InsufficientLiquidity(); - + uint256 tokensOut = getOutputAmount(msg.value, ethReserve, tokenReserve); if (tokensOut < _minTokens) revert InsufficientOutputAmount(); - + // Store old reserves for k-value check uint256 oldEthReserve = ethReserve; uint256 oldTokenReserve = tokenReserve; - + // Update reserves first using SafeMath ethReserve = oldEthReserve.add(msg.value); tokenReserve = oldTokenReserve.sub(tokensOut); @@ -164,11 +164,11 @@ contract LiquidityPool is ReentrancyGuard, Pausable, Ownable { uint256 k = ethReserve.mul(tokenReserve); uint256 previousK = oldEthReserve.mul(oldTokenReserve); if (k < previousK) revert InvalidK(); - + // Transfer tokens last to prevent reentrancy bool success = token.transfer(msg.sender, tokensOut); if (!success) revert TransferFailed(); - + emit TokensPurchased(msg.sender, msg.value, tokensOut); } @@ -178,14 +178,14 @@ contract LiquidityPool is ReentrancyGuard, Pausable, Ownable { function swapTokensForETH(uint256 _tokenAmount, uint256 _minETH) external whenNotPaused nonReentrant { if (_tokenAmount == 0) revert InsufficientInputAmount(); if (ethReserve == 0 || tokenReserve == 0) revert InsufficientLiquidity(); - + uint256 ethOut = getOutputAmount(_tokenAmount, tokenReserve, ethReserve); if (ethOut < _minETH) revert InsufficientOutputAmount(); // Update reserves before external call tokenReserve = tokenReserve.add(_tokenAmount); ethReserve = ethReserve.sub(ethOut); - + // Transfer tokens first bool success = token.transferFrom(msg.sender, address(this), _tokenAmount); if (!success) revert TransferFailed(); @@ -196,12 +196,12 @@ contract LiquidityPool is ReentrancyGuard, Pausable, Ownable { if (k < previousK) revert InvalidK(); // Transfer ETH last - (success, ) = msg.sender.call{value: ethOut}(""); + (success,) = msg.sender.call{value: ethOut}(""); if (!success) revert TransferFailed(); - + emit TokensSold(msg.sender, _tokenAmount, ethOut); } - + /// @notice Get current exchange rate /// @return Amount of tokens per ETH function getExchangeRate() external view returns (uint256) { @@ -209,55 +209,57 @@ contract LiquidityPool is ReentrancyGuard, Pausable, Ownable { // Return tokens per ETH normalized to 18 decimals return (tokenReserve * 1e18) / ethReserve; } - + /// @notice Internal function to mint liquidity tokens function _mint(address _to, uint256 _amount) internal { liquidityBalance[_to] = liquidityBalance[_to].add(_amount); totalLiquidity = totalLiquidity.add(_amount); } - + /// @notice Internal function to burn liquidity tokens function _burn(address _from, uint256 _amount) internal { liquidityBalance[_from] = liquidityBalance[_from].sub(_amount); totalLiquidity = totalLiquidity.sub(_amount); } - + /// @notice Calculate fee adjusted input amount /// @param _inputAmount Raw input amount before fee function _getFeeAdjustedInput(uint256 _inputAmount) internal pure returns (uint256) { return _inputAmount.mul(FEE_DENOMINATOR.sub(FEE_NUMERATOR)); } - + /// @notice Validate and calculate liquidity for initial deposit - function _calculateInitialLiquidity( - uint256 _ethAmount, - uint256 _tokenAmount - ) internal view returns (uint256 liquidity, uint256 actualTokenAmount) { + function _calculateInitialLiquidity(uint256 _ethAmount, uint256 _tokenAmount) + internal + view + returns (uint256 liquidity, uint256 actualTokenAmount) + { if (_ethAmount == 0 || _tokenAmount == 0) revert InsufficientInputAmount(); actualTokenAmount = _tokenAmount; liquidity = _sqrt(_ethAmount * actualTokenAmount); if (liquidity <= MINIMUM_LIQUIDITY) revert InsufficientLiquidity(); return (liquidity, actualTokenAmount); } - + /// @notice Calculate liquidity and token amount for subsequent deposits - function _calculateLiquidity( - uint256 _ethAmount, - uint256 _tokenAmount - ) internal view returns (uint256 liquidity, uint256 actualTokenAmount) { + function _calculateLiquidity(uint256 _ethAmount, uint256 _tokenAmount) + internal + view + returns (uint256 liquidity, uint256 actualTokenAmount) + { if (_ethAmount == 0 || _tokenAmount == 0) revert InsufficientInputAmount(); - + uint256 ethRatio = (_ethAmount * 1e18) / ethReserve; uint256 tokenRatio = (_tokenAmount * 1e18) / tokenReserve; if (ethRatio != tokenRatio) revert UnbalancedLiquidityRatios(); - + actualTokenAmount = (_ethAmount * tokenReserve) / ethReserve; if (actualTokenAmount > _tokenAmount) revert InsufficientTokenAmount(); - + liquidity = (_ethAmount * totalLiquidity) / ethReserve; return (liquidity, actualTokenAmount); } - + /// @notice Internal function to calculate square root function _sqrt(uint256 x) internal pure returns (uint256) { if (x == 0) return 0; @@ -269,7 +271,7 @@ contract LiquidityPool is ReentrancyGuard, Pausable, Ownable { } return y; } - + /// @notice Pause the contract /// @dev Only callable by owner function pause() external onlyOwner { @@ -281,6 +283,6 @@ contract LiquidityPool is ReentrancyGuard, Pausable, Ownable { function unpause() external onlyOwner { _unpause(); } - + receive() external payable {} } diff --git a/src/PlatformToken.sol b/src/PlatformToken.sol index 51412fa..1a94f18 100644 --- a/src/PlatformToken.sol +++ b/src/PlatformToken.sol @@ -8,45 +8,45 @@ contract PlatformToken { string public constant symbol = "BACKR"; uint8 public constant decimals = 18; uint256 public totalSupply; - + mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; mapping(address => uint256) public stakedBalance; mapping(address => uint256) public stakeTimestamp; - + address public owner; - uint256 public constant INITIAL_SUPPLY = 1_000_000 * 10**18; // 1 million tokens + uint256 public constant INITIAL_SUPPLY = 1_000_000 * 10 ** 18; // 1 million tokens uint256 public constant MIN_STAKE_DURATION = 7 days; uint256 public constant STAKE_REWARD_RATE = 5; // 5% annual reward - + event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); event Staked(address indexed user, uint256 amount); event Unstaked(address indexed user, uint256 amount, uint256 reward); - + error InsufficientBalance(); error InsufficientAllowance(); error StakingDurationNotMet(); error InsufficientStakedBalance(); error TransferFailed(); - + constructor() { owner = msg.sender; _mint(msg.sender, INITIAL_SUPPLY); } - + /// @notice Transfer tokens to a specified address /// @param _to The address to transfer to /// @param _value The amount to be transferred function transfer(address _to, uint256 _value) public returns (bool) { if (balanceOf[msg.sender] < _value) revert InsufficientBalance(); - + balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; emit Transfer(msg.sender, _to, _value); return true; } - + /// @notice Approve the passed address to spend the specified amount of tokens /// @param _spender The address which will spend the funds /// @param _value The amount of tokens to be spent @@ -55,7 +55,7 @@ contract PlatformToken { emit Approval(msg.sender, _spender, _value); return true; } - + /// @notice Transfer tokens from one address to another /// @param _from address The address which you want to send tokens from /// @param _to address The address which you want to transfer to @@ -63,26 +63,26 @@ contract PlatformToken { function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { if (balanceOf[_from] < _value) revert InsufficientBalance(); if (allowance[_from][msg.sender] < _value) revert InsufficientAllowance(); - + balanceOf[_from] -= _value; balanceOf[_to] += _value; allowance[_from][msg.sender] -= _value; emit Transfer(_from, _to, _value); return true; } - + /// @notice Stake tokens for rewards /// @param _amount The amount of tokens to stake function stake(uint256 _amount) public { if (balanceOf[msg.sender] < _amount) revert InsufficientBalance(); - + balanceOf[msg.sender] -= _amount; stakedBalance[msg.sender] += _amount; stakeTimestamp[msg.sender] = block.timestamp; - + emit Staked(msg.sender, _amount); } - + /// @notice Unstake tokens and claim rewards function unstake() public { uint256 stakedAmount = stakedBalance[msg.sender]; @@ -90,35 +90,35 @@ contract PlatformToken { if (block.timestamp < stakeTimestamp[msg.sender] + MIN_STAKE_DURATION) { revert StakingDurationNotMet(); } - + uint256 reward = calculateReward(msg.sender); stakedBalance[msg.sender] = 0; - + // Transfer staked amount and reward balanceOf[msg.sender] += stakedAmount + reward; _mint(msg.sender, reward); - + emit Unstaked(msg.sender, stakedAmount, reward); } - + /// @notice Calculate staking reward for an address /// @param _staker Address of the staker /// @return Reward amount function calculateReward(address _staker) public view returns (uint256) { uint256 stakingDuration = block.timestamp - stakeTimestamp[_staker]; if (stakingDuration < MIN_STAKE_DURATION) return 0; - + // Calculate reward based on 5% annual rate uint256 reward = (stakedBalance[_staker] * STAKE_REWARD_RATE) / 100; - + // Adjust reward based on staking duration if (stakingDuration < 365 days) { reward = (reward * stakingDuration) / (365 days); } - + return reward; } - + /// @notice Internal function to mint new tokens /// @param _to Address to mint tokens to /// @param _amount Amount of tokens to mint diff --git a/src/Project.sol b/src/Project.sol index c7a11b4..2ad4334 100644 --- a/src/Project.sol +++ b/src/Project.sol @@ -70,8 +70,8 @@ contract Project { if (!userProfile.hasProfile(msg.sender)) revert UserNotRegistered(); if (bytes(_title).length == 0 || _milestoneDescriptions.length == 0) revert InvalidProjectParameters(); if ( - _milestoneDescriptions.length != _milestoneFunding.length || - _milestoneFunding.length != _milestoneVotesRequired.length + _milestoneDescriptions.length != _milestoneFunding.length + || _milestoneFunding.length != _milestoneVotesRequired.length ) revert InvalidProjectParameters(); uint256 projectId = totalProjects++; @@ -140,11 +140,11 @@ contract Project { function _releaseFunds(uint256 _projectId, uint256 _milestoneId) internal { ProjectDetails storage project = projects[_projectId]; Milestone storage milestone = project.milestones[_milestoneId]; - + uint256 amount = milestone.fundingRequired; if (address(this).balance < amount) revert InsufficientFunds(); - (bool sent, ) = project.creator.call{value: amount}(""); + (bool sent,) = project.creator.call{value: amount}(""); require(sent, "Failed to send funds"); emit FundsReleased(_projectId, _milestoneId, amount); @@ -153,13 +153,17 @@ contract Project { /// @notice Get milestone details /// @param _projectId ID of the project /// @param _milestoneId ID of the milestone - function getMilestone(uint256 _projectId, uint256 _milestoneId) external view returns ( - string memory description, - uint256 fundingRequired, - uint256 votesRequired, - uint256 votesReceived, - bool isCompleted - ) { + function getMilestone(uint256 _projectId, uint256 _milestoneId) + external + view + returns ( + string memory description, + uint256 fundingRequired, + uint256 votesRequired, + uint256 votesReceived, + bool isCompleted + ) + { if (!projects[_projectId].isActive) revert ProjectNotFound(); if (_milestoneId >= projects[_projectId].milestoneCount) revert MilestoneNotFound(); @@ -177,7 +181,11 @@ contract Project { /// @param _projectId ID of the project /// @param _milestoneId ID of the milestone /// @param _voter Address to check - function hasVotedForMilestone(uint256 _projectId, uint256 _milestoneId, address _voter) external view returns (bool) { + function hasVotedForMilestone(uint256 _projectId, uint256 _milestoneId, address _voter) + external + view + returns (bool) + { return projects[_projectId].milestones[_milestoneId].hasVoted[_voter]; } diff --git a/src/QuadraticFunding.sol b/src/QuadraticFunding.sol index 462b570..1ff69b3 100644 --- a/src/QuadraticFunding.sol +++ b/src/QuadraticFunding.sol @@ -26,7 +26,9 @@ contract QuadraticFunding { // Events event RoundStarted(uint256 indexed roundId, uint256 matchingPool); - event ContributionAdded(uint256 indexed roundId, uint256 indexed projectId, address indexed contributor, uint256 amount); + event ContributionAdded( + uint256 indexed roundId, uint256 indexed projectId, address indexed contributor, uint256 amount + ); event RoundFinalized(uint256 indexed roundId, uint256 totalMatching); event MatchingFundsDistributed(uint256 indexed roundId, uint256 indexed projectId, uint256 amount); @@ -99,20 +101,20 @@ contract QuadraticFunding { uint256 projectId = projectIds[i]; uint256 sqrtContributions = _sqrt(round.projectContributions[projectId]); uint256 matchingAmount; - + if (i == projectIds.length - 1) { // Last project gets remaining funds to avoid rounding issues matchingAmount = round.matchingPool - round.matchingAmount[projectIds[0]]; } else { matchingAmount = (round.matchingPool * sqrtContributions) / totalSquareRoots; } - + round.matchingAmount[projectId] = matchingAmount; - + // Transfer matching funds to project contract (bool sent,) = address(projectContract).call{value: matchingAmount}(""); require(sent, "Failed to send matching funds"); - + emit MatchingFundsDistributed(roundId, projectId, matchingAmount); } @@ -124,9 +126,7 @@ contract QuadraticFunding { function isRoundActive() public view returns (bool) { if (currentRound == 0) return false; Round storage round = rounds[currentRound - 1]; - return block.timestamp >= round.startTime && - block.timestamp <= round.endTime && - !round.isFinalized; + return block.timestamp >= round.startTime && block.timestamp <= round.endTime && !round.isFinalized; } /// @notice Get the total contribution amount for a project in a round @@ -147,7 +147,11 @@ contract QuadraticFunding { /// @param _roundId Round ID /// @param _projectId Project ID /// @param _contributor Contributor address - function getContribution(uint256 _roundId, uint256 _projectId, address _contributor) external view returns (uint256) { + function getContribution(uint256 _roundId, uint256 _projectId, address _contributor) + external + view + returns (uint256) + { return rounds[_roundId].contributions[_projectId][_contributor]; } @@ -155,15 +159,15 @@ contract QuadraticFunding { /// @param x Number to calculate square root of function _sqrt(uint256 x) internal pure returns (uint256) { if (x == 0) return 0; - + uint256 z = (x + 1) / 2; uint256 y = x; - + while (z < y) { y = z; z = (x / z + z) / 2; } - + return y; } @@ -172,14 +176,15 @@ contract QuadraticFunding { function _getProjectsInRound(uint256 _roundId) internal view returns (uint256[] memory) { Round storage round = rounds[_roundId]; uint256 count = 0; - + // First pass: count projects - for (uint256 i = 0; i < 1000; i++) { // Arbitrary limit for gas considerations + for (uint256 i = 0; i < 1000; i++) { + // Arbitrary limit for gas considerations if (round.projectContributions[i] > 0) { count++; } } - + // Second pass: collect project IDs uint256[] memory projects = new uint256[](count); uint256 index = 0; @@ -189,7 +194,7 @@ contract QuadraticFunding { index++; } } - + return projects; } } diff --git a/src/UserProfile.sol b/src/UserProfile.sol index 789636d..8cb6c03 100644 --- a/src/UserProfile.sol +++ b/src/UserProfile.sol @@ -69,7 +69,7 @@ contract UserProfile { function updateReputation(address _user, uint256 _newScore) external { // TODO: Add access control to restrict this to authorized contracts if (!profiles[_user].isRegistered) revert ProfileDoesNotExist(); - + profiles[_user].reputationScore = _newScore; emit ReputationUpdated(_user, _newScore); } diff --git a/test/Badge.t.sol b/test/Badge.t.sol index 2c2108f..be3bcda 100644 --- a/test/Badge.t.sol +++ b/test/Badge.t.sol @@ -20,14 +20,14 @@ contract BadgeTest is Test { function testAwardBadge() public { badge.awardBadge(alice, Badge.BadgeType.EARLY_SUPPORTER); - + assertTrue(badge.hasSpecificBadge(alice, Badge.BadgeType.EARLY_SUPPORTER)); assertEq(badge.balanceOf(alice), 1); } function testFailDuplicateBadge() public { badge.awardBadge(alice, Badge.BadgeType.EARLY_SUPPORTER); - + // This should fail badge.awardBadge(alice, Badge.BadgeType.EARLY_SUPPORTER); } @@ -35,10 +35,10 @@ contract BadgeTest is Test { function testUpdateBadgeBenefit() public { uint256 newBenefit = 2000; // 20% badge.updateBadgeBenefit(Badge.BadgeType.EARLY_SUPPORTER, newBenefit); - + // Award badge to alice badge.awardBadge(alice, Badge.BadgeType.EARLY_SUPPORTER); - + // Check total benefits assertEq(badge.getTotalBenefits(alice), newBenefit); } @@ -47,10 +47,10 @@ contract BadgeTest is Test { // Award multiple badges to alice badge.awardBadge(alice, Badge.BadgeType.EARLY_SUPPORTER); badge.awardBadge(alice, Badge.BadgeType.POWER_BACKER); - + // Calculate expected benefits (5% + 10% = 15%) uint256 expectedBenefit = 1500; - + assertEq(badge.getTotalBenefits(alice), expectedBenefit); } @@ -60,7 +60,7 @@ contract BadgeTest is Test { badge.awardBadge(alice, Badge.BadgeType.POWER_BACKER); badge.awardBadge(alice, Badge.BadgeType.LIQUIDITY_PROVIDER); badge.awardBadge(alice, Badge.BadgeType.GOVERNANCE_ACTIVE); - + // Total would be 37.5%, but should be capped at 25% assertEq(badge.getTotalBenefits(alice), 2500); } diff --git a/test/Governance.t.sol b/test/Governance.t.sol index 63b6ad5..186332e 100644 --- a/test/Governance.t.sol +++ b/test/Governance.t.sol @@ -8,7 +8,7 @@ import "../src/PlatformToken.sol"; // Mock contract for testing proposal execution contract MockTarget { uint256 public value; - + function setValue(uint256 _value) external { value = _value; } @@ -35,8 +35,8 @@ contract GovernanceTest is Test { // Transfer some tokens for testing vm.startPrank(owner); - token.transfer(alice, 1000 * 10**18); - token.transfer(bob, 500 * 10**18); + token.transfer(alice, 1000 * 10 ** 18); + token.transfer(bob, 500 * 10 ** 18); vm.stopPrank(); } @@ -45,14 +45,9 @@ contract GovernanceTest is Test { token.approve(address(governance), type(uint256).max); bytes memory callData = abi.encodeWithSignature("setValue(uint256)", 42); governance.createProposal("Test Proposal", address(mockTarget), callData); - - ( - uint256 forVotes, - uint256 againstVotes, - uint256 _startTime, - uint256 _endTime, - bool executed - ) = governance.getProposal(1); + + (uint256 forVotes, uint256 againstVotes, uint256 _startTime, uint256 _endTime, bool executed) = + governance.getProposal(1); assertEq(forVotes, 0); assertEq(againstVotes, 0); @@ -67,17 +62,17 @@ contract GovernanceTest is Test { token.approve(address(governance), type(uint256).max); bytes memory callData = abi.encodeWithSignature("setValue(uint256)", 42); governance.createProposal("Test Proposal", address(mockTarget), callData); - + // Alice votes with 1000 tokens governance.castVote(1, true); - + // Transfer 500 tokens to bob after voting - token.transfer(bob, 500 * 10**18); + token.transfer(bob, 500 * 10 ** 18); vm.stopPrank(); // Check that Alice's vote still counts as 1000 tokens despite transfer - (uint256 forVotes, uint256 againstVotes,,, ) = governance.getProposal(1); - assertEq(forVotes, 1000 * 10**18); + (uint256 forVotes, uint256 againstVotes,,,) = governance.getProposal(1); + assertEq(forVotes, 1000 * 10 ** 18); assertEq(againstVotes, 0); } @@ -87,7 +82,7 @@ contract GovernanceTest is Test { vm.startPrank(alice); token.approve(address(governance), type(uint256).max); governance.createProposal("Test Proposal", address(mockTarget), callData); - + // Alice votes in favor governance.castVote(1, true); vm.stopPrank(); @@ -96,12 +91,12 @@ contract GovernanceTest is Test { vm.startPrank(bob); token.approve(address(governance), type(uint256).max); governance.castVote(1, false); - - (uint256 forVotes, uint256 againstVotes,,, ) = governance.getProposal(1); - + + (uint256 forVotes, uint256 againstVotes,,,) = governance.getProposal(1); + // Alice has 1000 tokens, Bob has 500 tokens - assertEq(forVotes, 1000 * 10**18); - assertEq(againstVotes, 500 * 10**18); + assertEq(forVotes, 1000 * 10 ** 18); + assertEq(againstVotes, 500 * 10 ** 18); vm.stopPrank(); } @@ -116,20 +111,20 @@ contract GovernanceTest is Test { // Fast forward past voting period vm.warp(block.timestamp + 8 days); - + // Try to execute immediately (should set execution time) governance.executeProposal(1); - + // Verify not executed yet (,,,, bool executed) = governance.getProposal(1); assertEq(executed, false); - + // Fast forward past execution delay vm.warp(block.timestamp + EXECUTION_DELAY + 1); - + // Execute proposal governance.executeProposal(1); - + // Verify executed (,,,, executed) = governance.getProposal(1); assertEq(executed, true); @@ -142,10 +137,10 @@ contract GovernanceTest is Test { vm.startPrank(alice); token.approve(address(governance), type(uint256).max); governance.createProposal("Test Proposal", address(mockTarget), callData); - + // Vote once governance.castVote(1, true); - + // Try to vote again (should fail) governance.castVote(1, true); } @@ -174,10 +169,10 @@ contract GovernanceTest is Test { // Fast forward past voting period vm.warp(block.timestamp + 8 days); - + // Set execution time governance.executeProposal(1); - + // Try to execute before delay (should fail) governance.executeProposal(1); } diff --git a/test/LiquidityPool.t.sol b/test/LiquidityPool.t.sol index e9115a6..cbf5af7 100644 --- a/test/LiquidityPool.t.sol +++ b/test/LiquidityPool.t.sol @@ -12,33 +12,33 @@ contract LiquidityPoolTest is Test { address public owner; address public user1; address public user2; - - uint256 constant INITIAL_POOL_TOKENS = 100_000 * 10**18; + + uint256 constant INITIAL_POOL_TOKENS = 100_000 * 10 ** 18; uint256 constant MIN_LIQUIDITY = 1000; - + event LiquidityAdded(address indexed provider, uint256 ethAmount, uint256 tokenAmount, uint256 liquidity); event LiquidityRemoved(address indexed provider, uint256 ethAmount, uint256 tokenAmount, uint256 liquidity); event TokensPurchased(address indexed buyer, uint256 ethIn, uint256 tokensOut); event TokensSold(address indexed seller, uint256 tokensIn, uint256 ethOut); - + function setUp() public { owner = makeAddr("owner"); user1 = makeAddr("user1"); user2 = makeAddr("user2"); - + vm.startPrank(owner); // Deploy token and pool token = new PlatformToken(); pool = new LiquidityPool(address(token), MIN_LIQUIDITY); - + // Transfer tokens to users for testing - token.transfer(user1, 100_000 * 10**18); - token.transfer(user2, 50_000 * 10**18); - + token.transfer(user1, 100_000 * 10 ** 18); + token.transfer(user2, 50_000 * 10 ** 18); + // Approve pool to spend owner's tokens token.approve(address(pool), type(uint256).max); vm.stopPrank(); - + // Have users approve pool vm.prank(user1); token.approve(address(pool), type(uint256).max); @@ -49,30 +49,30 @@ contract LiquidityPoolTest is Test { function test_PauseUnpause() public { vm.startPrank(owner); vm.deal(owner, 100 ether); - + // Add initial liquidity uint256 ethAmount = 10 ether; - uint256 tokenAmount = 10_000 * 10**18; - + uint256 tokenAmount = 10_000 * 10 ** 18; + // Ensure owner has enough tokens and has approved the pool require(token.balanceOf(owner) >= tokenAmount * 2, "Owner needs more tokens"); token.approve(address(pool), type(uint256).max); - + pool.addLiquidity{value: ethAmount}(tokenAmount); - + // Pause the contract pool.pause(); - + // Try to add liquidity while paused vm.expectRevert("Pausable: paused"); pool.addLiquidity{value: ethAmount}(tokenAmount); - + // Unpause pool.unpause(); - + // Should work after unpause pool.addLiquidity{value: ethAmount}(tokenAmount); - + vm.stopPrank(); } @@ -85,47 +85,47 @@ contract LiquidityPoolTest is Test { function test_SwapWithSlippage() public { // Add initial liquidity (uint256 initialEthReserve, uint256 initialTokenReserve) = _addInitialLiquidity(); - + // Debug initial state console2.log("Initial ETH Reserve:", initialEthReserve); console2.log("Initial Token Reserve:", initialTokenReserve); console2.log("Owner Token Balance:", token.balanceOf(owner)); console2.log("Pool Token Balance:", token.balanceOf(address(pool))); - + // Verify initial state assertEq(pool.ethReserve(), initialEthReserve, "Initial ETH reserve incorrect"); assertEq(pool.tokenReserve(), initialTokenReserve, "Initial token reserve incorrect"); - + vm.startPrank(user1); vm.deal(user1, 10 ether); token.approve(address(pool), type(uint256).max); - + // Calculate expected output uint256 ethIn = 1 ether; uint256 expectedOut = pool.getOutputAmount(ethIn, pool.ethReserve(), pool.tokenReserve()); console2.log("ETH Input:", ethIn); console2.log("Expected Token Output:", expectedOut); - + // Try with too high minimum (should fail) vm.expectRevert(abi.encodeWithSignature("InsufficientOutputAmount()")); pool.swapETHForTokens{value: ethIn}(expectedOut + 1); - + // Should succeed with correct minimum uint256 initialBalance = token.balanceOf(user1); console2.log("User1 Initial Token Balance:", initialBalance); - + pool.swapETHForTokens{value: ethIn}(expectedOut); - + uint256 finalBalance = token.balanceOf(user1); console2.log("User1 Final Token Balance:", finalBalance); console2.log("Token Balance Change:", finalBalance - initialBalance); - + // Verify the swap assertEq(token.balanceOf(user1) - initialBalance, expectedOut, "Incorrect token output amount"); assertEq(address(pool).balance, initialEthReserve + ethIn, "Incorrect pool ETH balance"); assertEq(pool.ethReserve(), initialEthReserve + ethIn, "Incorrect ETH reserve after swap"); assertEq(pool.tokenReserve(), initialTokenReserve - expectedOut, "Incorrect token reserve after swap"); - + vm.stopPrank(); } @@ -134,22 +134,22 @@ contract LiquidityPoolTest is Test { vm.startPrank(owner); vm.deal(owner, 100 ether); token.approve(address(pool), type(uint256).max); - pool.addLiquidity{value: 10 ether}(10_000 * 10**18); + pool.addLiquidity{value: 10 ether}(10_000 * 10 ** 18); vm.stopPrank(); - + // Deploy malicious contract that attempts reentrancy ReentrancyAttacker attacker = new ReentrancyAttacker(payable(address(pool))); - + // Give attacker some ETH and tokens vm.deal(address(attacker), 2 ether); vm.startPrank(owner); - token.transfer(address(attacker), 1000 * 10**18); + token.transfer(address(attacker), 1000 * 10 ** 18); vm.stopPrank(); - + // Approve tokens for the attacker vm.startPrank(address(attacker)); token.approve(address(pool), type(uint256).max); - + // Attempt attack (should fail with TransferFailed due to state changes) vm.expectRevert(abi.encodeWithSignature("TransferFailed()")); attacker.attack(); @@ -159,17 +159,17 @@ contract LiquidityPoolTest is Test { function test_ConstantProduct() public { // Add initial liquidity _addInitialLiquidity(); - + uint256 initialK = pool.ethReserve() * pool.tokenReserve(); - + // Perform swap vm.startPrank(user1); vm.deal(user1, 10 ether); pool.swapETHForTokens{value: 1 ether}(0); vm.stopPrank(); - + uint256 finalK = pool.ethReserve() * pool.tokenReserve(); - + // K should be maintained or increased (due to fees) assertGe(finalK, initialK, "Constant product invariant violated"); } @@ -177,170 +177,162 @@ contract LiquidityPoolTest is Test { /// @notice Helper to add initial liquidity with default values function _addInitialLiquidity() internal returns (uint256 ethAmount, uint256 tokenAmount) { ethAmount = 10 ether; - tokenAmount = 10_000 * 10**18; - + tokenAmount = 10_000 * 10 ** 18; + vm.startPrank(owner); vm.deal(owner, ethAmount); // Give owner the required ETH - + // Ensure owner has enough tokens and has approved the pool require(token.balanceOf(owner) >= tokenAmount, "Owner needs more tokens"); token.approve(address(pool), type(uint256).max); - + pool.addLiquidity{value: ethAmount}(tokenAmount); vm.stopPrank(); - + return (ethAmount, tokenAmount); } - + /// @notice Helper to calculate expected liquidity function _calculateExpectedLiquidity(uint256 ethAmount, uint256 tokenAmount) internal pure returns (uint256) { return _sqrt(ethAmount * tokenAmount); } - + function test_AddInitialLiquidity() public { vm.startPrank(owner); vm.deal(owner, 1000 ether); // Give owner some ETH - + uint256 ethAmount = 100 ether; - uint256 tokenAmount = 100_000 * 10**18; - + uint256 tokenAmount = 100_000 * 10 ** 18; + uint256 expectedLiquidity = _sqrt(ethAmount * tokenAmount); uint256 providerLiquidity = expectedLiquidity - MIN_LIQUIDITY; - + vm.expectEmit(true, true, true, true); emit LiquidityAdded(owner, ethAmount, tokenAmount, providerLiquidity); - + pool.addLiquidity{value: ethAmount}(tokenAmount); - + assertEq(address(pool).balance, ethAmount, "Incorrect ETH balance"); assertEq(token.balanceOf(address(pool)), tokenAmount, "Incorrect token balance"); assertEq(pool.liquidityBalance(address(pool)), MIN_LIQUIDITY, "Incorrect pool liquidity balance"); assertEq(pool.liquidityBalance(owner), providerLiquidity, "Incorrect owner liquidity balance"); assertEq(pool.totalLiquidity(), expectedLiquidity, "Incorrect total liquidity"); - + vm.stopPrank(); } - + function test_AddLiquidityUnbalancedRatio() public { // First add initial liquidity vm.startPrank(owner); vm.deal(owner, 100 ether); - + uint256 initialEth = 10 ether; - uint256 initialTokens = 10_000 * 10**18; + uint256 initialTokens = 10_000 * 10 ** 18; pool.addLiquidity{value: initialEth}(initialTokens); vm.stopPrank(); - + // Try to add unbalanced liquidity vm.startPrank(user1); vm.deal(user1, 100 ether); vm.expectRevert(abi.encodeWithSignature("UnbalancedLiquidityRatios()")); - pool.addLiquidity{value: 5 ether}(15_000 * 10**18); + pool.addLiquidity{value: 5 ether}(15_000 * 10 ** 18); vm.stopPrank(); } - + /// @notice Updated test_AddLiquidityInsufficientTokens to expect UnbalancedLiquidityRatios() function test_AddLiquidityInsufficientTokens() public { // First add initial liquidity vm.startPrank(owner); vm.deal(owner, 100 ether); - + uint256 initialEth = 10 ether; - uint256 initialTokens = 10_000 * 10**18; + uint256 initialTokens = 10_000 * 10 ** 18; pool.addLiquidity{value: initialEth}(initialTokens); vm.stopPrank(); - + // Try to add with insufficient tokens vm.startPrank(user1); vm.deal(user1, 100 ether); - + // Calculate required tokens for 10 ETH based on current ratio uint256 requiredTokens = (10 ether * initialTokens) / initialEth; - uint256 insufficientTokens = requiredTokens - 1000 * 10**18; // Less than required - + uint256 insufficientTokens = requiredTokens - 1000 * 10 ** 18; // Less than required + vm.expectRevert(abi.encodeWithSignature("UnbalancedLiquidityRatios()")); pool.addLiquidity{value: 10 ether}(insufficientTokens); vm.stopPrank(); } - + function test_SwapETHForTokens() public { // First add initial liquidity vm.startPrank(owner); vm.deal(owner, 100 ether); - + uint256 initialEth = 10 ether; - uint256 initialTokens = 1_000 * 10**18; // Reduced from 10_000 to prevent overflow + uint256 initialTokens = 1_000 * 10 ** 18; // Reduced from 10_000 to prevent overflow pool.addLiquidity{value: initialEth}(initialTokens); vm.stopPrank(); - + // Now swap ETH for tokens vm.startPrank(user1); vm.deal(user1, 1 ether); - + // Calculate minimum tokens to receive (with 0.3% fee) - uint256 expectedOutput = pool.getOutputAmount( - 1 ether, - initialEth, - initialTokens - ); - + uint256 expectedOutput = pool.getOutputAmount(1 ether, initialEth, initialTokens); + // Use SafeMath for slippage calculation uint256 minTokens = expectedOutput * 99 / 100; // 1% slippage tolerance - + // Store initial balances uint256 initialTokenBalance = token.balanceOf(user1); uint256 initialEthBalance = user1.balance; uint256 initialPoolETHReserve = pool.ethReserve(); - + // Perform swap pool.swapETHForTokens{value: 1 ether}(minTokens); - + // Verify balances using SafeMath uint256 finalTokenBalance = token.balanceOf(user1); uint256 tokensReceived = finalTokenBalance - initialTokenBalance; uint256 finalEthBalance = user1.balance; uint256 ethSpent = initialEthBalance - finalEthBalance; - + assertGe(tokensReceived, minTokens, "Received tokens less than minimum"); assertEq(tokensReceived, expectedOutput, "Incorrect token output"); assertEq(pool.ethReserve(), initialPoolETHReserve + ethSpent, "Incorrect pool ETH reserve"); vm.stopPrank(); } - + function test_SwapTokensForETH() public { // First add initial liquidity vm.startPrank(owner); vm.deal(owner, 100 ether); - + uint256 initialEth = 10 ether; - uint256 initialTokens = 10_000 * 10**18; // 10,000 tokens - + uint256 initialTokens = 10_000 * 10 ** 18; // 10,000 tokens + // Add initial liquidity pool.addLiquidity{value: initialEth}(initialTokens); - + // Transfer some tokens to user1 - token.transfer(user1, 1_000 * 10**18); + token.transfer(user1, 1_000 * 10 ** 18); vm.stopPrank(); - + // Now swap tokens for ETH vm.startPrank(user1); - token.approve(address(pool), 1_000 * 10**18); + token.approve(address(pool), 1_000 * 10 ** 18); uint256 minETH = 0.9 ether; uint256 initialETHBalance = user1.balance; uint256 initialPoolETHReserve = pool.ethReserve(); - - uint256 expectedOutput = pool.getOutputAmount( - 1_000 * 10**18, - initialTokens, - initialEth - ); - - pool.swapTokensForETH(1_000 * 10**18, minETH); - + + uint256 expectedOutput = pool.getOutputAmount(1_000 * 10 ** 18, initialTokens, initialEth); + + pool.swapTokensForETH(1_000 * 10 ** 18, minETH); + // Verify balances uint256 finalETHBalance = user1.balance; uint256 ethReceived = finalETHBalance - initialETHBalance; - + assertGe(ethReceived, minETH, "Received ETH less than minimum"); assertEq(ethReceived, expectedOutput, "Incorrect ETH output"); assertEq(pool.ethReserve(), initialPoolETHReserve - ethReceived, "Incorrect pool ETH reserve"); @@ -350,60 +342,62 @@ contract LiquidityPoolTest is Test { function test_RemoveLiquidity() public { vm.startPrank(owner); vm.deal(owner, 100 ether); // Give owner enough ETH - + uint256 ethAmount = 10 ether; - uint256 tokenAmount = 10_000 * 10**18; - + uint256 tokenAmount = 10_000 * 10 ** 18; + // Add liquidity pool.addLiquidity{value: ethAmount}(tokenAmount); uint256 liquidity = pool.liquidityBalance(owner); - + // Store initial balances uint256 initialETHBalance = owner.balance; uint256 initialTokenBalance = token.balanceOf(owner); - + // Remove half of liquidity uint256 liquidityToRemove = liquidity / 2; pool.removeLiquidity(liquidityToRemove); - + // Calculate expected returns (approximately half of initial amounts) uint256 expectedETH = (ethAmount * liquidityToRemove) / liquidity; uint256 expectedTokens = (tokenAmount * liquidityToRemove) / liquidity; - + // Verify balances changed correctly assertApproxEqAbs(owner.balance - initialETHBalance, expectedETH, 100, "Incorrect ETH returned"); - assertApproxEqAbs(token.balanceOf(owner) - initialTokenBalance, expectedTokens, 100_000, "Incorrect tokens returned"); - + assertApproxEqAbs( + token.balanceOf(owner) - initialTokenBalance, expectedTokens, 100_000, "Incorrect tokens returned" + ); + // Verify remaining liquidity assertEq(pool.liquidityBalance(owner), liquidity - liquidityToRemove, "Incorrect remaining liquidity"); vm.stopPrank(); } - + function testFail_RemoveTooMuchLiquidity() public { vm.startPrank(owner); - pool.addLiquidity{value: 10 ether}(10_000 * 10**18); + pool.addLiquidity{value: 10 ether}(10_000 * 10 ** 18); uint256 liquidity = pool.liquidityBalance(owner); pool.removeLiquidity(liquidity + 1); vm.stopPrank(); } - + function test_GetExchangeRate() public { // First add some initial liquidity to have a valid exchange rate vm.startPrank(owner); vm.deal(owner, 100 ether); // Give owner some ETH - + uint256 ethAmount = 10 ether; - uint256 tokenAmount = 10_000 * 10**18; // 10,000 tokens - + uint256 tokenAmount = 10_000 * 10 ** 18; // 10,000 tokens + // Add initial liquidity pool.addLiquidity{value: ethAmount}(tokenAmount); - + // Exchange rate should be 1000 tokens per ETH (10000/10) uint256 rate = pool.getExchangeRate(); - assertEq(rate, 1000 * 10**18, "Incorrect exchange rate"); + assertEq(rate, 1000 * 10 ** 18, "Incorrect exchange rate"); vm.stopPrank(); } - + /// @notice Updated helper to calculate square root function _sqrt(uint256 x) internal pure returns (uint256) { if (x == 0) return 0; @@ -432,16 +426,16 @@ contract ReentrancyAttacker { attacking = false; // Try to reenter with another swap while still processing the first one // This will fail because the state has already been updated - pool.swapTokensForETH(100 * 10**18, 0); + pool.swapTokensForETH(100 * 10 ** 18, 0); } } function attack() external { // First approve tokens token.approve(address(pool), type(uint256).max); - + // Then attempt swap that will trigger receive() attacking = true; - pool.swapTokensForETH(100 * 10**18, 0); + pool.swapTokensForETH(100 * 10 ** 18, 0); } } diff --git a/test/PlatformToken.t.sol b/test/PlatformToken.t.sol index 7d901bf..b43346d 100644 --- a/test/PlatformToken.t.sol +++ b/test/PlatformToken.t.sol @@ -9,78 +9,78 @@ contract PlatformTokenTest is Test { address public owner; address public user1; address public user2; - + function setUp() public { owner = makeAddr("owner"); user1 = makeAddr("user1"); user2 = makeAddr("user2"); - + vm.startPrank(owner); token = new PlatformToken(); vm.stopPrank(); } - + function test_InitialSupply() public view { - assertEq(token.totalSupply(), 1_000_000 * 10**18); - assertEq(token.balanceOf(owner), 1_000_000 * 10**18); + assertEq(token.totalSupply(), 1_000_000 * 10 ** 18); + assertEq(token.balanceOf(owner), 1_000_000 * 10 ** 18); } - + function test_Transfer() public { vm.startPrank(owner); token.transfer(user1, 1000); assertEq(token.balanceOf(user1), 1000); vm.stopPrank(); } - + function test_Approve() public { vm.startPrank(owner); token.approve(user1, 1000); assertEq(token.allowance(owner, user1), 1000); vm.stopPrank(); } - + function test_TransferFrom() public { vm.startPrank(owner); token.approve(user1, 1000); vm.stopPrank(); - + vm.startPrank(user1); token.transferFrom(owner, user2, 500); assertEq(token.balanceOf(user2), 500); assertEq(token.allowance(owner, user1), 500); vm.stopPrank(); } - + function test_Staking() public { // Transfer some tokens to user1 vm.startPrank(owner); - token.transfer(user1, 1000 * 10**18); + token.transfer(user1, 1000 * 10 ** 18); vm.stopPrank(); - + // Stake tokens vm.startPrank(user1); - token.stake(500 * 10**18); - assertEq(token.balanceOf(user1), 500 * 10**18); - assertEq(token.stakedBalance(user1), 500 * 10**18); - + token.stake(500 * 10 ** 18); + assertEq(token.balanceOf(user1), 500 * 10 ** 18); + assertEq(token.stakedBalance(user1), 500 * 10 ** 18); + // Warp time forward by 1 year vm.warp(block.timestamp + 365 days); - + // Unstake and check rewards token.unstake(); // Should receive original stake (500) plus 5% annual reward (25) - assertEq(token.balanceOf(user1), 1050 * 10**18); // 500 + 500 + (500 * 5%) = 1050 + assertEq(token.balanceOf(user1), 1050 * 10 ** 18); // 500 + 500 + (500 * 5%) = 1050 assertEq(token.stakedBalance(user1), 0); vm.stopPrank(); } - + function testFail_UnstakeBeforeMinDuration() public { vm.startPrank(owner); - token.transfer(user1, 1000 * 10**18); + token.transfer(user1, 1000 * 10 ** 18); vm.stopPrank(); - + vm.startPrank(user1); - token.stake(500 * 10**18); + token.stake(500 * 10 ** 18); // Try to unstake immediately token.unstake(); vm.stopPrank(); diff --git a/test/Project.t.sol b/test/Project.t.sol index c22654c..097a92a 100644 --- a/test/Project.t.sol +++ b/test/Project.t.sol @@ -8,76 +8,70 @@ import {UserProfile} from "../src/UserProfile.sol"; contract ProjectTest is Test { Project public project; UserProfile public userProfile; - + address public creator; address public contributor1; address public contributor2; - + function setUp() public { creator = makeAddr("creator"); contributor1 = makeAddr("contributor1"); contributor2 = makeAddr("contributor2"); - + // Deploy contracts userProfile = new UserProfile(); project = new Project(address(userProfile)); - + // Create user profiles vm.startPrank(creator); userProfile.createProfile("creator", "Project Creator"); vm.stopPrank(); - + vm.startPrank(contributor1); userProfile.createProfile("contributor1", "Project Contributor 1"); vm.stopPrank(); - + vm.startPrank(contributor2); userProfile.createProfile("contributor2", "Project Contributor 2"); vm.stopPrank(); } - + function test_CreateProject() public { string[] memory descriptions = new string[](2); descriptions[0] = "Milestone 1"; descriptions[1] = "Milestone 2"; - + uint256[] memory funding = new uint256[](2); funding[0] = 1 ether; funding[1] = 2 ether; - + uint256[] memory votes = new uint256[](2); votes[0] = 2; votes[1] = 3; - + vm.startPrank(creator); - project.createProject( - "Test Project", - "A test project description", - descriptions, - funding, - votes - ); - - (string memory desc, uint256 fundingReq, uint256 votesReq, uint256 votesRec, bool completed) = + project.createProject("Test Project", "A test project description", descriptions, funding, votes); + + (string memory desc, uint256 fundingReq, uint256 votesReq, uint256 votesRec, bool completed) = project.getMilestone(0, 0); - + assertEq(desc, "Milestone 1"); assertEq(fundingReq, 1 ether); assertEq(votesReq, 2); assertEq(votesRec, 0); assertEq(completed, false); } - + function testFail_CreateProjectWithoutProfile() public { address noProfile = makeAddr("noProfile"); string[] memory descriptions = new string[](1); uint256[] memory funding = new uint256[](1); uint256[] memory votes = new uint256[](1); - + vm.startPrank(noProfile); project.createProject("Test", "Description", descriptions, funding, votes); } - + function test_ContributeToProject() public { // Create project string[] memory descriptions = new string[](1); @@ -86,17 +80,17 @@ contract ProjectTest is Test { funding[0] = 1 ether; uint256[] memory votes = new uint256[](1); votes[0] = 2; - + vm.startPrank(creator); project.createProject("Test", "Description", descriptions, funding, votes); vm.stopPrank(); - + // Contribute vm.deal(contributor1, 2 ether); vm.startPrank(contributor1); project.contributeToProject{value: 1 ether}(0); } - + function test_CompleteMilestone() public { // Create project string[] memory descriptions = new string[](1); @@ -105,28 +99,28 @@ contract ProjectTest is Test { funding[0] = 1 ether; uint256[] memory votes = new uint256[](1); votes[0] = 2; - + vm.startPrank(creator); project.createProject("Test", "Description", descriptions, funding, votes); vm.stopPrank(); - + // Fund project vm.deal(contributor1, 2 ether); vm.startPrank(contributor1); project.contributeToProject{value: 1 ether}(0); project.voteMilestone(0, 0); vm.stopPrank(); - + // Second vote to complete milestone vm.startPrank(contributor2); project.voteMilestone(0, 0); vm.stopPrank(); - + // Check milestone completion - (, , , , bool completed) = project.getMilestone(0, 0); + (,,,, bool completed) = project.getMilestone(0, 0); assertTrue(completed); } - + function testFail_DoubleVote() public { // Create project string[] memory descriptions = new string[](1); @@ -135,11 +129,11 @@ contract ProjectTest is Test { funding[0] = 1 ether; uint256[] memory votes = new uint256[](1); votes[0] = 2; - + vm.startPrank(creator); project.createProject("Test", "Description", descriptions, funding, votes); vm.stopPrank(); - + vm.startPrank(contributor1); project.voteMilestone(0, 0); project.voteMilestone(0, 0); // Should fail diff --git a/test/QuadraticFunding.t.sol b/test/QuadraticFunding.t.sol index 21b666d..12199f9 100644 --- a/test/QuadraticFunding.t.sol +++ b/test/QuadraticFunding.t.sol @@ -10,38 +10,38 @@ contract QuadraticFundingTest is Test { QuadraticFunding public qf; Project public project; UserProfile public userProfile; - + address public admin; address public creator; address public contributor1; address public contributor2; - + function setUp() public { admin = makeAddr("admin"); creator = makeAddr("creator"); contributor1 = makeAddr("contributor1"); contributor2 = makeAddr("contributor2"); - + vm.startPrank(admin); // Deploy contracts userProfile = new UserProfile(); project = new Project(address(userProfile)); qf = new QuadraticFunding(payable(address(project))); vm.stopPrank(); - + // Create user profiles vm.startPrank(creator); userProfile.createProfile("creator", "Project Creator"); vm.stopPrank(); - + vm.startPrank(contributor1); userProfile.createProfile("contributor1", "Contributor 1"); vm.stopPrank(); - + vm.startPrank(contributor2); userProfile.createProfile("contributor2", "Contributor 2"); vm.stopPrank(); - + // Create a test project string[] memory descriptions = new string[](1); descriptions[0] = "Milestone 1"; @@ -49,12 +49,12 @@ contract QuadraticFundingTest is Test { funding[0] = 1 ether; uint256[] memory votes = new uint256[](1); votes[0] = 2; - + vm.startPrank(creator); project.createProject("Test Project", "Description", descriptions, funding, votes); vm.stopPrank(); } - + function test_StartRound() public { vm.deal(admin, 10 ether); vm.startPrank(admin); @@ -62,7 +62,7 @@ contract QuadraticFundingTest is Test { assertTrue(qf.isRoundActive()); vm.stopPrank(); } - + function testFail_StartRoundWithActiveRound() public { vm.deal(admin, 20 ether); vm.startPrank(admin); @@ -70,72 +70,72 @@ contract QuadraticFundingTest is Test { qf.startRound{value: 10 ether}(); // Should fail vm.stopPrank(); } - + function test_Contribute() public { // Start round vm.deal(admin, 10 ether); vm.startPrank(admin); qf.startRound{value: 10 ether}(); vm.stopPrank(); - + // Make contributions vm.deal(contributor1, 1 ether); vm.startPrank(contributor1); qf.contribute{value: 1 ether}(0); vm.stopPrank(); - + assertEq(qf.getProjectContributions(0, 0), 1 ether); assertEq(qf.getContribution(0, 0, contributor1), 1 ether); } - + function test_FinalizeRound() public { // Start round vm.deal(admin, 10 ether); vm.startPrank(admin); qf.startRound{value: 10 ether}(); vm.stopPrank(); - + // Make contributions vm.deal(contributor1, 4 ether); vm.startPrank(contributor1); qf.contribute{value: 4 ether}(0); vm.stopPrank(); - + vm.deal(contributor2, 1 ether); vm.startPrank(contributor2); qf.contribute{value: 1 ether}(1); vm.stopPrank(); - + // Warp time to end round vm.warp(block.timestamp + 15 days); - + // Finalize round vm.startPrank(admin); qf.finalizeRound(); vm.stopPrank(); - + // Check matching amounts uint256 project0Matching = qf.getMatchingAmount(0, 0); uint256 project1Matching = qf.getMatchingAmount(0, 1); - + assertTrue(project0Matching > 0); assertTrue(project1Matching > 0); assertEq(project0Matching + project1Matching, 10 ether); } - + function testFail_ContributeInactiveRound() public { vm.deal(contributor1, 1 ether); vm.startPrank(contributor1); qf.contribute{value: 1 ether}(0); // Should fail vm.stopPrank(); } - + function testFail_FinalizeActiveRound() public { // Start round vm.deal(admin, 10 ether); vm.startPrank(admin); qf.startRound{value: 10 ether}(); - + // Try to finalize before round ends qf.finalizeRound(); // Should fail vm.stopPrank(); diff --git a/test/UserProfile.t.sol b/test/UserProfile.t.sol index 3aface1..0f6e850 100644 --- a/test/UserProfile.t.sol +++ b/test/UserProfile.t.sol @@ -18,7 +18,7 @@ contract UserProfileTest is Test { function test_CreateProfile() public { vm.startPrank(user1); userProfile.createProfile("alice", "Web3 developer"); - + UserProfile.Profile memory profile = userProfile.getProfile(user1); assertEq(profile.username, "alice"); assertEq(profile.bio, "Web3 developer"); @@ -36,7 +36,7 @@ contract UserProfileTest is Test { vm.startPrank(user1); userProfile.createProfile("alice", "Web3 developer"); userProfile.updateProfile("alice_updated", "Senior Web3 developer"); - + UserProfile.Profile memory profile = userProfile.getProfile(user1); assertEq(profile.username, "alice_updated"); assertEq(profile.bio, "Senior Web3 developer"); @@ -49,7 +49,7 @@ contract UserProfileTest is Test { function test_HasProfile() public { assertFalse(userProfile.hasProfile(user1)); - + vm.startPrank(user1); userProfile.createProfile("alice", "Web3 developer"); assertTrue(userProfile.hasProfile(user1)); @@ -57,11 +57,11 @@ contract UserProfileTest is Test { function test_TotalUsers() public { assertEq(userProfile.totalUsers(), 0); - + vm.startPrank(user1); userProfile.createProfile("alice", "Web3 developer"); assertEq(userProfile.totalUsers(), 1); - + vm.startPrank(user2); userProfile.createProfile("bob", "Smart contract developer"); assertEq(userProfile.totalUsers(), 2);