From 78988b05ab0ca969d031a2bfe14daafd5a260681 Mon Sep 17 00:00:00 2001 From: Olumide Adenigba Date: Mon, 28 Apr 2025 01:17:20 +0100 Subject: [PATCH 1/3] chore: More tests --- README.md | 2 +- src/contracts/OrganizationContract.sol | 9 + test/OrganizationContract.t.sol | 643 +++++++++++++++++++++++-- test/OrganizationFactory.t.sol | 101 ++++ test/Token.t.sol | 50 +- 5 files changed, 757 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 62381df..7c43711 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# HR_Project_Web3Bridge +# PayTroix_Project_Web3Bridge This repository contains the smart contracts and related scripts for the HR_Project_Web3Bridge, a blockchain-based solution for managing organizations and contracts. The project leverages [Foundry](https://book.getfoundry.sh/) for development, testing, and deployment of Solidity smart contracts. diff --git a/src/contracts/OrganizationContract.sol b/src/contracts/OrganizationContract.sol index ef80210..0dbc29e 100644 --- a/src/contracts/OrganizationContract.sol +++ b/src/contracts/OrganizationContract.sol @@ -234,6 +234,7 @@ contract OrganizationContract { uint256 repaidAmount = recipient.advanceCollected; recipient.advanceCollected = 0; delete advanceRequests[_recipient]; + advanceRequests[_recipient].repaid = true; emit AdvanceRepaid(_recipient, repaidAmount); } @@ -262,6 +263,7 @@ contract OrganizationContract { returns (bool) { _onlyOwner(); + if (_recipients.length == 0 || _netAmounts.length == 0) revert CustomErrors.InvalidInput(); if (_recipients.length != _netAmounts.length) revert CustomErrors.InvalidInput(); if (_tokenAddress == address(0)) revert CustomErrors.InvalidAddress(); if (!isTokenSupported(_tokenAddress)) revert CustomErrors.TokenNotSupported(); @@ -360,6 +362,8 @@ contract OrganizationContract { Structs.Recipient storage recipient = recipients[_address]; recipient.name = _name; recipient.updatedAt = block.timestamp; + + emit RecipientUpdated(recipient.recipientId, _address, _name); } /** @@ -409,6 +413,7 @@ contract OrganizationContract { */ function setDefaultAdvanceLimit(uint256 _limit) public { _onlyOwner(); + if (_limit > type(uint256).max / 2) revert CustomErrors.InvalidAmount(); defaultAdvanceLimit = _limit; emit DefaultAdvanceLimitSet(_limit); } @@ -422,6 +427,10 @@ contract OrganizationContract { _onlyOwner(); if (_recipient == address(0)) revert CustomErrors.InvalidAddress(); if (recipients[_recipient].recipientId == 0) revert CustomErrors.RecipientNotFound(); + + // Check if limit exceeds salary + if (_limit >= recipients[_recipient].salaryAmount) revert CustomErrors.InvalidAmount(); + recipientAdvanceLimit[_recipient] = _limit; emit AdvanceLimitSet(_recipient, _limit); } diff --git a/test/OrganizationContract.t.sol b/test/OrganizationContract.t.sol index 16cd5d2..69f7d86 100644 --- a/test/OrganizationContract.t.sol +++ b/test/OrganizationContract.t.sol @@ -166,6 +166,85 @@ contract OrganizationContractTest is Test { assertGt(approvalDate, requestDate, "Approval date should be after request date"); } + function testRequestAdvanceWithMaxLimit() public { + // Create recipient with salary + uint256 salary = 1000; + org.createRecipient(recipient, "Test Recipient", salary); + + // Set advance limit to 50% of salary + uint256 advanceLimit = salary / 2; + org.setRecipientAdvanceLimit(recipient, advanceLimit); + + // Request maximum allowed advance + vm.prank(recipient); + org.requestAdvance(advanceLimit, address(token)); + + // Verify request details + (address requestRecipient, uint256 requestAmount,,,,, address requestToken) = org.advanceRequests(recipient); + assertEq(requestRecipient, recipient, "Recipient should be set correctly"); + assertEq(requestAmount, advanceLimit, "Amount should be set to maximum limit"); + assertEq(requestToken, address(token), "Token should be set correctly"); + } + + function test_RevertWhen_RequestZeroAdvance() public { + // Create recipient + org.createRecipient(recipient, "Test Recipient", 1000); + org.setRecipientAdvanceLimit(recipient, 500); + + // Try to request zero advance + vm.prank(recipient); + vm.expectRevert(CustomErrors.InvalidAmount.selector); + org.requestAdvance(0, address(token)); + } + + function test_RevertWhen_NonRecipientRequestsAdvance() public { + // Try to request advance without being a recipient + vm.prank(address(999)); + vm.expectRevert(CustomErrors.RecipientNotFound.selector); + org.requestAdvance(100, address(token)); + } + + function test_RevertWhen_RequestAdvanceAboveDefaultLimit() public { + // Create recipient with default advance limit (0.1 ether) + org.createRecipient(recipient, "Test Recipient", 1000); + + // Try to request advance above the default limit + vm.prank(recipient); + vm.expectRevert(CustomErrors.InvalidAmount.selector); + org.requestAdvance(0.2 ether, address(token)); // Request more than default 0.1 ether limit + } + + function testRequestAdvanceEventEmission() public { + // Create recipient and set limit + org.createRecipient(recipient, "Test Recipient", 1000); + org.setRecipientAdvanceLimit(recipient, 500); + + // Record events + vm.recordLogs(); + + // Request advance + vm.prank(recipient); + org.requestAdvance(300, address(token)); + + // Get emitted events + Vm.Log[] memory entries = vm.getRecordedLogs(); + + // Verify AdvanceRequested event + bytes32 expectedEventSig = keccak256("AdvanceRequested(address,uint256)"); + bool foundEvent = false; + + for (uint i = 0; i < entries.length; i++) { + if (entries[i].topics[0] == expectedEventSig) { + foundEvent = true; + assertEq(address(uint160(uint256(entries[i].topics[1]))), recipient, "Recipient address should match"); + assertEq(abi.decode(entries[i].data, (uint256)), 300, "Amount should match"); + break; + } + } + + assertTrue(foundEvent, "Should emit AdvanceRequested event"); + } + function test_RevertWhen_RequestAdvanceWithUnsupportedToken() public { org.createRecipient(recipient, "Test Recipient", 1000); vm.prank(recipient); @@ -370,21 +449,14 @@ contract OrganizationContractTest is Test { } function testUpdateOrganizationInfo() public { - string memory newName = "Updated Org"; - string memory newDesc = "Updated Description"; - - // Store initial timestamp - StructLib.Structs.Organization memory initialInfo = org.getOrganizationInfo(); - - // Advance time by 1 second - vm.warp(block.timestamp + 1); - - org.updateOrganizationInfo(newName, newDesc); - - StructLib.Structs.Organization memory info = org.getOrganizationInfo(); - assertEq(info.name, newName, "Organization name should be updated"); - assertEq(info.description, newDesc, "Organization description should be updated"); - assertTrue(info.updatedAt > initialInfo.createdAt, "Updated timestamp should be greater than created timestamp"); + string memory newName = "Updated Org Name"; + string memory newDescription = "Updated Description"; + + org.updateOrganizationInfo(newName, newDescription); + + (bytes32 id, string memory name, string memory description, , , ) = org.organizationInfo(); + assertEq(name, newName, "Organization name should be updated"); + assertEq(description, newDescription, "Organization description should be updated"); } function test_RevertWhen_UpdateOrgInfoEmptyName() public { @@ -490,34 +562,21 @@ contract OrganizationContractTest is Test { function testMultipleAdvanceRequests() public { org.createRecipient(recipient, "Test Recipient", 1000); - org.setRecipientAdvanceLimit(recipient, 500 ether); + org.setRecipientAdvanceLimit(recipient, 500); // First advance request vm.startPrank(recipient); - org.requestAdvance(200 ether, address(token)); - - // Should not be able to make another request before first is processed - vm.expectRevert(CustomErrors.InvalidRequest.selector); - org.requestAdvance(100 ether, address(token)); + org.requestAdvance(200, address(token)); + + // Approve first request vm.stopPrank(); - - // Approve first advance - uint256 advanceAmount = 200 ether; - uint256 advanceGrossAmount = (advanceAmount * 10000) / (10000 - org.transactionFee()); - token.mint(owner, advanceGrossAmount); - token.approve(address(org), advanceGrossAmount); org.approveAdvance(recipient); - // Make salary payment to clear advance - uint256 salaryNet = 1000 ether; - uint256 salaryGross = (salaryNet * 10000) / (10000 - org.transactionFee()); - token.mint(owner, salaryGross); - token.approve(address(org), salaryGross); - org.disburseToken(address(token), recipient, salaryNet); - - // Should be able to request new advance after repayment - vm.prank(recipient); - org.requestAdvance(300 ether, address(token)); + // Try second advance request (should fail as first one is not repaid) + vm.startPrank(recipient); + vm.expectRevert(CustomErrors.InvalidRequest.selector); + org.requestAdvance(300, address(token)); + vm.stopPrank(); } function testSetDefaultAdvanceLimit() public { @@ -530,15 +589,6 @@ contract OrganizationContractTest is Test { assertEq(org.recipientAdvanceLimit(newRecipient), newLimit, "New recipient should get default advance limit"); } - function test_RevertWhen_RequestZeroAdvance() public { - org.createRecipient(recipient, "Test Recipient", 1000); - org.setRecipientAdvanceLimit(recipient, 500 ether); - - vm.prank(recipient); - vm.expectRevert(CustomErrors.InvalidAmount.selector); - org.requestAdvance(0, address(token)); - } - function testRecipientCreatedEvent() public { vm.expectEmit(true, true, false, true); emit RecipientCreated( @@ -580,4 +630,505 @@ contract OrganizationContractTest is Test { emit BatchDisbursement(address(token), 2, totalGrossAmount - 1); // Account for rounding down org.batchDisburseToken(address(token), recipients, amounts); } + + function testPaymentHistoryTracking() public { + // Create recipient and disburse tokens + org.createRecipient(recipient, "Test Recipient", 1000); + uint256 amount = 100; + org.disburseToken(address(token), recipient, amount); + + // Get payment history + StructLib.Structs.Payment[] memory payments = org.getRecipientPayments(recipient); + assertEq(payments.length, 1, "Should have one payment record"); + assertEq(payments[0].recipient, recipient, "Payment recipient should match"); + assertEq(payments[0].amount, amount - (amount * org.transactionFee()) / 10000, "Payment amount should match"); + } + + function testFeeCalculationEdgeCases() public { + // Test with very small amounts + uint256 smallAmount = 1; + uint256 fee = org.calculateFee(smallAmount); + assertEq(fee, 0, "Fee should be 0 for very small amounts"); + + // Test with large amounts + uint256 largeAmount = type(uint256).max / 10001; // Prevent overflow + uint256 largeFee = org.calculateFee(largeAmount); + assertTrue(largeFee > 0, "Fee should be calculated for large amounts"); + + // Test gross amount calculation + uint256 netAmount = 1000; + uint256 grossAmount = org.calculateGrossAmount(netAmount); + uint256 calculatedFee = org.calculateFee(grossAmount); + assertEq(grossAmount - calculatedFee, netAmount, "Net amount calculation should be accurate"); + } + + function testReentrancyProtection() public { + // Create a malicious token that attempts reentrancy + MaliciousToken malToken = new MaliciousToken(); + factory.addToken("Malicious Token", address(malToken)); + + // Create recipient + org.createRecipient(recipient, "Test Recipient", 1000); + + // Fund the malicious token + malToken.mint(address(this), 1000 ether); + malToken.approve(address(org), type(uint256).max); + + // Set up the reentrancy attack + malToken.setTarget(address(org), recipient); + + // Attempt reentrancy attack + vm.expectRevert(CustomErrors.ReentrantCall.selector); + org.disburseToken(address(malToken), recipient, 100); + } + + function testAdvanceRepaymentScenarios() public { + // Create recipient with salary and advance limit + org.createRecipient(recipient, "Test Recipient", 1000); + org.setRecipientAdvanceLimit(recipient, 500); + + // Request and approve advance + vm.prank(recipient); + org.requestAdvance(300, address(token)); + org.approveAdvance(recipient); + + // Verify advance state + StructLib.Structs.Recipient memory recipientInfo = org.getRecipient(recipient); + assertEq(recipientInfo.advanceCollected, 300, "Advance should be recorded"); + + // Attempt to disburse less than advance amount + vm.expectRevert(CustomErrors.InvalidAmount.selector); + org.disburseToken(address(token), recipient, 200); + + // Disburse more than advance amount to trigger repayment + org.disburseToken(address(token), recipient, 1000); + + // Verify advance is cleared + recipientInfo = org.getRecipient(recipient); + assertEq(recipientInfo.advanceCollected, 0, "Advance should be cleared after repayment"); + + // Verify advance request is cleared + (,,,,,bool repaid,) = org.advanceRequests(recipient); + assertTrue(repaid, "Advance should be marked as repaid"); + } + + function testComplexBatchOperations() public { + // Create multiple recipients with different scenarios + address[] memory addresses = new address[](3); + string[] memory names = new string[](3); + uint256[] memory salaries = new uint256[](3); + + addresses[0] = address(10); + addresses[1] = address(11); + addresses[2] = address(12); + names[0] = "Recipient 1"; + names[1] = "Recipient 2"; + names[2] = "Recipient 3"; + salaries[0] = 1000; + salaries[1] = 2000; + salaries[2] = 3000; + + org.batchCreateRecipients(addresses, names, salaries); + + // Set up advances for some recipients + vm.prank(addresses[0]); + org.requestAdvance(100, address(token)); + org.approveAdvance(addresses[0]); + + vm.prank(addresses[1]); + org.requestAdvance(200, address(token)); + org.approveAdvance(addresses[1]); + + // Prepare disbursement amounts + address[] memory recipients = new address[](3); + uint256[] memory amounts = new uint256[](3); + + recipients[0] = addresses[0]; + recipients[1] = addresses[1]; + recipients[2] = addresses[2]; + amounts[0] = 500; // More than advance + amounts[1] = 150; // Less than advance + amounts[2] = 1000; // No advance + + // Test batch disbursement with mixed scenarios + vm.expectRevert(CustomErrors.InvalidAmount.selector); + org.batchDisburseToken(address(token), recipients, amounts); + } + + function testPermissions() public { + address nonOwner = address(123); + + // Test owner-only functions + vm.prank(nonOwner); + vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); + org.createRecipient(recipient, "Test", 1000); + + vm.prank(nonOwner); + vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); + org.disburseToken(address(token), recipient, 100); + + // Test factory-only functions + vm.prank(nonOwner); + vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); + org.setTransactionFee(60); + + vm.prank(nonOwner); + vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); + org.setFeeCollector(address(456)); + } + + function testUpdateRecipientSalary() public { + // Create recipient + org.createRecipient(recipient, "Test Recipient", 1000); + + // Store initial timestamp + StructLib.Structs.Recipient memory initial = org.getRecipient(recipient); + + // Advance time by 1 second + vm.warp(block.timestamp + 1); + + // Update salary + uint256 newSalary = 2000; + org.updateRecipientSalary(recipient, newSalary); + + // Verify update + StructLib.Structs.Recipient memory recipientInfo = org.getRecipient(recipient); + assertEq(recipientInfo.salaryAmount, newSalary, "Salary should be updated"); + assertTrue(recipientInfo.updatedAt > recipientInfo.createdAt, "Updated timestamp should be greater"); + } + + function test_RevertWhen_UpdateRecipientSalaryWithZeroAmount() public { + org.createRecipient(recipient, "Test Recipient", 1000); + vm.expectRevert(CustomErrors.InvalidAmount.selector); + org.updateRecipientSalary(recipient, 0); + } + + function test_RevertWhen_UpdateRecipientSalaryForNonExistentRecipient() public { + vm.expectRevert(CustomErrors.RecipientNotFound.selector); + org.updateRecipientSalary(address(999), 1000); + } + + function testAdvanceRepaymentWithMultiplePayments() public { + // Create recipient with salary and advance limit + org.createRecipient(recipient, "Test Recipient", 1000); + org.setRecipientAdvanceLimit(recipient, 500); + + // Request and approve advance + vm.startPrank(recipient); + org.requestAdvance(300, address(token)); + vm.stopPrank(); + org.approveAdvance(recipient); + + // Verify advance state + StructLib.Structs.Recipient memory recipientInfo = org.getRecipient(recipient); + assertEq(recipientInfo.advanceCollected, 300, "Advance should be recorded"); + + // Make partial payment that doesn't cover advance + vm.expectRevert(CustomErrors.InvalidAmount.selector); + org.disburseToken(address(token), recipient, 200); + + // Make payment that exactly covers advance (should fail) + vm.expectRevert(CustomErrors.InvalidAmount.selector); + org.disburseToken(address(token), recipient, 300); + + // Make payment that covers advance plus extra + org.disburseToken(address(token), recipient, 400); + + // Verify advance is cleared + recipientInfo = org.getRecipient(recipient); + assertEq(recipientInfo.advanceCollected, 0, "Advance should be cleared"); + + // Verify advance request is cleared and marked as repaid + (,,,,,bool repaid,) = org.advanceRequests(recipient); + assertTrue(repaid, "Advance should be marked as repaid"); + } + + function testFeeCalculationPrecision() public { + // Test with very small amounts + uint256 smallAmount = 1; + uint256 fee = org.calculateFee(smallAmount); + assertEq(fee, 0, "Fee should be 0 for very small amounts"); + + // Test with amount that would cause precision loss + uint256 amount = 10001; // This should result in a non-zero fee + fee = org.calculateFee(amount); + assertTrue(fee > 0, "Fee should be non-zero for larger amounts"); + + // Test with maximum possible amount + uint256 maxAmount = type(uint256).max / 10001; // Prevent overflow + fee = org.calculateFee(maxAmount); + assertTrue(fee > 0, "Fee should be calculated for large amounts"); + + // Verify fee calculation precision + uint256 netAmount = 1000; + uint256 grossAmount = org.calculateGrossAmount(netAmount); + fee = org.calculateFee(grossAmount); + assertEq(grossAmount - fee, netAmount, "Fee calculation should be precise"); + } + + function testEventEmissions() public { + // Test RecipientCreated event + vm.recordLogs(); + bytes32 recipientId = org.createRecipient(recipient, "Test Recipient", 1000); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + assertEq(entries.length, 1, "Should emit one event"); + + // Verify RecipientCreated event + bytes32 expectedEventSig = keccak256("RecipientCreated(bytes32,address,string)"); + assertEq(entries[0].topics[0], expectedEventSig, "Event signature should match"); + assertEq(bytes32(entries[0].topics[1]), recipientId, "Recipient ID should match"); + assertEq(address(uint160(uint256(entries[0].topics[2]))), recipient, "Recipient address should match"); + + // Test TokenDisbursed event + vm.recordLogs(); + org.disburseToken(address(token), recipient, 100); + + entries = vm.getRecordedLogs(); + bool foundTokenDisbursedEvent = false; + expectedEventSig = keccak256("TokenDisbursed(address,address,uint256)"); + + for (uint i = 0; i < entries.length; i++) { + if (entries[i].topics[0] == expectedEventSig) { + foundTokenDisbursedEvent = true; + assertEq(address(uint160(uint256(entries[i].topics[1]))), address(token), "Token address should match"); + assertEq(address(uint160(uint256(entries[i].topics[2]))), recipient, "Recipient address should match"); + break; + } + } + assertTrue(foundTokenDisbursedEvent, "Should emit TokenDisbursed event"); + } + + function testComplexStateTransitions() public { + // Create recipient + org.createRecipient(recipient, "Test Recipient", 1000); + StructLib.Structs.Recipient memory initial = org.getRecipient(recipient); + + // Advance time by 1 second + vm.warp(block.timestamp + 1); + + // Update name + org.updateRecipient(recipient, "Updated Name"); + StructLib.Structs.Recipient memory afterNameUpdate = org.getRecipient(recipient); + assertEq(afterNameUpdate.name, "Updated Name", "Name should be updated"); + assertTrue(afterNameUpdate.updatedAt > initial.createdAt, "Updated timestamp should be greater than created timestamp"); + + // Advance time by another second + vm.warp(block.timestamp + 1); + + // Update salary + org.updateRecipientSalary(recipient, 2000); + StructLib.Structs.Recipient memory afterSalaryUpdate = org.getRecipient(recipient); + assertEq(afterSalaryUpdate.salaryAmount, 2000, "Salary should be updated"); + assertTrue(afterSalaryUpdate.updatedAt > initial.createdAt, "Updated timestamp should be greater than created timestamp"); + } + + function testZeroAddressChecks() public { + // Test creating recipient with zero address + vm.expectRevert(CustomErrors.InvalidAddress.selector); + org.createRecipient(address(0), "Test Recipient", 1000); + + // Test batch create with zero address + address[] memory addresses = new address[](2); + string[] memory names = new string[](2); + uint256[] memory salaries = new uint256[](2); + addresses[0] = address(1); + addresses[1] = address(0); // Zero address + names[0] = "Recipient 1"; + names[1] = "Recipient 2"; + salaries[0] = 1000; + salaries[1] = 2000; + + vm.expectRevert(CustomErrors.InvalidAddress.selector); + org.batchCreateRecipients(addresses, names, salaries); + } + + function testEmptyArrayInputs() public { + // Test batch operations with mismatched array lengths + address[] memory addresses = new address[](1); + string[] memory names = new string[](2); + uint256[] memory amounts = new uint256[](1); + + vm.expectRevert(CustomErrors.InvalidInput.selector); + org.batchCreateRecipients(addresses, names, amounts); + + // Test batch disburse with mismatched arrays + vm.expectRevert(CustomErrors.InvalidInput.selector); + org.batchDisburseToken(address(token), addresses, new uint256[](2)); + } + + function testMaximumArrayLength() public { + // Test batch operations with reasonable array length + uint256 length = 5; + address[] memory addresses = new address[](length); + string[] memory names = new string[](length); + uint256[] memory salaries = new uint256[](length); + + for(uint i = 0; i < length; i++) { + addresses[i] = address(uint160(i + 1)); + names[i] = "Test"; + salaries[i] = 1000; + } + + // This should pass as it's a reasonable length + org.batchCreateRecipients(addresses, names, salaries); + + // Verify recipients were created + for(uint i = 0; i < length; i++) { + StructLib.Structs.Recipient memory recipient = org.getRecipient(addresses[i]); + assertTrue(recipient.recipientId != 0, "Recipient should exist"); + } + } + + function testAdvanceRequestEdgeCases() public { + org.createRecipient(recipient, "Test Recipient", 1000); + org.setRecipientAdvanceLimit(recipient, 500); + + // Test requesting advance with amount > salary + vm.prank(recipient); + vm.expectRevert(CustomErrors.InvalidAmount.selector); + org.requestAdvance(1001, address(token)); + + // Test requesting advance with amount = salary + vm.prank(recipient); + vm.expectRevert(CustomErrors.InvalidAmount.selector); + org.requestAdvance(1000, address(token)); + + // Test approving non-existent advance request + vm.expectRevert(CustomErrors.InvalidRequest.selector); + org.approveAdvance(address(999)); + + // Test requesting advance with unsupported token + vm.prank(recipient); + vm.expectRevert(CustomErrors.InvalidToken.selector); + org.requestAdvance(100, address(999)); + } + + function testRecipientNameValidation() public { + // Test with valid name + org.createRecipient(recipient, "Valid Name", 1000); + StructLib.Structs.Recipient memory createdRecipient = org.getRecipient(recipient); + assertTrue(createdRecipient.recipientId != 0, "Recipient should be created"); + + // Test with empty name + vm.expectRevert(CustomErrors.NameRequired.selector); + org.createRecipient(address(123), "", 1000); + + // Test updating with empty name + vm.expectRevert(CustomErrors.NameRequired.selector); + org.updateRecipient(recipient, ""); + } + + function testDisbursementEdgeCases() public { + org.createRecipient(recipient, "Test Recipient", 1000); + + // Test disbursement with zero amount + vm.expectRevert(CustomErrors.InvalidAmount.selector); + org.disburseToken(address(token), recipient, 0); + + // Test disbursement with unsupported token + vm.expectRevert(CustomErrors.TokenNotSupported.selector); + org.disburseToken(address(999), recipient, 100); + + // Test disbursement to non-existent recipient + vm.expectRevert(CustomErrors.RecipientNotFound.selector); + org.disburseToken(address(token), address(999), 100); + + // Test disbursement with zero address token + vm.expectRevert(CustomErrors.InvalidAddress.selector); + org.disburseToken(address(0), recipient, 100); + } + + function testAdvanceLimitEdgeCases() public { + // Test setting advance limit for non-existent recipient + vm.expectRevert(CustomErrors.RecipientNotFound.selector); + org.setRecipientAdvanceLimit(recipient, 1001); + + // Create recipient and test valid advance limit + org.createRecipient(recipient, "Test Recipient", 1000); + org.setRecipientAdvanceLimit(recipient, 500); + + // Test setting advance limit for zero address + vm.expectRevert(CustomErrors.InvalidAddress.selector); + org.setRecipientAdvanceLimit(address(0), 500); + } + + function testTransactionFeeEdgeCases() public { + // Test setting fee to maximum allowed value + factory.updateOrganizationTransactionFee(owner, 80); + assertEq(org.transactionFee(), 80, "Fee should be updated to maximum allowed"); + + // Test setting fee to zero + factory.updateOrganizationTransactionFee(owner, 0); + assertEq(org.transactionFee(), 0, "Fee should be updated to zero"); + + // Test fee calculation with zero fee + uint256 amount = 1000; + assertEq(org.calculateFee(amount), 0, "Fee should be zero when fee percentage is zero"); + assertEq(org.calculateGrossAmount(amount), amount, "Gross amount should equal net amount when fee is zero"); + } + + function testConstructorAndInitialState() public { + // Test constructor parameters + assertEq(factory.owner(), address(this), "Owner should be set correctly"); + assertEq(factory.feeCollector(), feeCollector, "Fee collector should be set correctly"); + + // Test initial state + assertGt(factory.getSupportedTokensCount(), 0, "Initial token count should be more than Zero"); + assertNotEq(factory.getOrganizationContract(address(this)), address(0), "org contract should not be zero"); + } + + function testDefaultAdvanceLimitOnDeployment() public { + // Deploy a new organization contract with a different owner + address newOwner = address(789); + vm.prank(newOwner); + address newOrgAddress = factory.createOrganization("New Org", "New Description"); + OrgContract.OrganizationContract newOrg = OrgContract.OrganizationContract(newOrgAddress); + + // Create a recipient to check if they get the default advance limit + address newRecipient = address(123); + vm.prank(newOwner); // Only owner can create recipient + newOrg.createRecipient(newRecipient, "Test Recipient", 1000); + + // Verify the default advance limit is 0.1 ether + assertEq(newOrg.recipientAdvanceLimit(newRecipient), 0.1 ether, "Default advance limit should be 0.1 ether"); + + // Verify we can request an advance up to this limit + vm.startPrank(newRecipient); + newOrg.requestAdvance(0.1 ether, address(token)); + vm.stopPrank(); + + // Verify the request was accepted + (address requestRecipient, uint256 requestAmount,,,,, address requestToken) = newOrg.advanceRequests(newRecipient); + assertEq(requestRecipient, newRecipient, "Request recipient should match"); + assertEq(requestAmount, 0.1 ether, "Request amount should match default limit"); + assertEq(requestToken, address(token), "Request token should match"); + } +} + +// Malicious token contract for testing reentrancy protection +contract MaliciousToken is MockERC20 { + OrganizationContract private targetContract; + address private targetRecipient; + bool private reentrancyAttempted; + + function transferFrom(address from, address to, uint256 amount) external override returns (bool) { + if (!reentrancyAttempted && msg.sender == address(targetContract)) { + reentrancyAttempted = true; + // Attempt reentrancy + targetContract.disburseToken(address(this), targetRecipient, amount); + } + + _balances[from] -= amount; + _balances[to] += amount; + _allowances[from][msg.sender] -= amount; + + return true; + } + + function setTarget(address _contract, address _recipient) external { + targetContract = OrganizationContract(_contract); + targetRecipient = _recipient; + reentrancyAttempted = false; + } } diff --git a/test/OrganizationFactory.t.sol b/test/OrganizationFactory.t.sol index b7bd86d..9103548 100644 --- a/test/OrganizationFactory.t.sol +++ b/test/OrganizationFactory.t.sol @@ -8,6 +8,15 @@ import "../src/libraries/structs.sol"; import "../src/libraries/errors.sol"; contract OrganizationFactoryTest is Test { + // Events from OrganizationFactory contract + event OrganizationCreated( + address indexed organizationAddress, + address indexed owner, + string name, + string description, + uint256 createdAt + ); + OrganizationFactory public factory; address public owner; address public user; @@ -189,4 +198,96 @@ contract OrganizationFactoryTest is Test { vm.expectRevert(CustomErrors.OrganizationNotFound.selector); factory.getOrganizationDetails(address(1)); } + + function testGetOrganizationContract() public { + // Initially should return zero address + assertEq(factory.getOrganizationContract(user), address(0), "Should return zero address for non-existent org"); + + // Create organization + vm.prank(user); + address orgAddress = factory.createOrganization("Test Org", "Test Description"); + + // Should return correct address after creation + assertEq(factory.getOrganizationContract(user), orgAddress, "Should return correct organization address"); + } + + function test_RevertWhen_CreateDuplicateOrganization() public { + // Create first organization + vm.startPrank(user); + factory.createOrganization("First Org", "First Description"); + + // Try to create second organization with same owner + vm.expectRevert(CustomErrors.OrganizationAlreadyExists.selector); + factory.createOrganization("Second Org", "Second Description"); + vm.stopPrank(); + } + + function testOrganizationCreatedEvent() public { + string memory name = "Test Org"; + string memory description = "Test Description"; + + // Test event emission + vm.recordLogs(); + address orgAddress = factory.createOrganization(name, description); + + // Get the emitted event + Vm.Log[] memory entries = vm.getRecordedLogs(); + assertEq(entries.length > 0, true, "Should emit at least one event"); + + // The event we're interested in should be the last one + Vm.Log memory lastEntry = entries[entries.length - 1]; + + // Verify event signature + bytes32 expectedEventSig = keccak256("OrganizationCreated(address,address,string,string,uint256)"); + assertEq(lastEntry.topics[0], expectedEventSig, "Event signature should match"); + + // Verify indexed parameters + assertEq(address(uint160(uint256(lastEntry.topics[1]))), orgAddress, "Organization address should match"); + assertEq(address(uint160(uint256(lastEntry.topics[2]))), address(this), "Owner address should match"); + + // Decode non-indexed parameters + (string memory emittedName, string memory emittedDesc, uint256 emittedTime) = + abi.decode(lastEntry.data, (string, string, uint256)); + + // Verify non-indexed parameters + assertEq(emittedName, name, "Organization name should match"); + assertEq(emittedDesc, description, "Organization description should match"); + assertEq(emittedTime, block.timestamp, "Creation timestamp should match"); + } + + function testConstructorAndInitialState() public { + // Test constructor parameters + assertEq(factory.owner(), address(this), "Owner should be set correctly"); + assertEq(factory.feeCollector(), feeCollector, "Fee collector should be set correctly"); + + // Test initial state + assertEq(factory.getSupportedTokensCount(), 0, "Initial token count should be zero"); + assertEq(factory.getOrganizationContract(address(this)), address(0), "Initial org contract should be zero"); + } + + function testCompleteOrganizationLifecycle() public { + // Create organization + string memory name = "Test Org"; + string memory description = "Test Description"; + address orgAddress = factory.createOrganization(name, description); + + // Add supported token + factory.addToken("Test Token", token); + + // Update organization settings + factory.updateOrganizationTransactionFee(address(this), 30); + factory.updateOrganizationFeeCollector(address(this), address(4)); + + // Verify final state + OrgContract.OrganizationContract org = OrgContract.OrganizationContract(orgAddress); + assertEq(org.transactionFee(), 30, "Transaction fee should be updated"); + assertEq(org.feeCollector(), address(4), "Fee collector should be updated"); + assertTrue(factory.isTokenSupported(token), "Token should be supported"); + + // Get and verify organization details + Structs.Organization memory orgDetails = factory.getOrganizationDetails(address(this)); + assertEq(orgDetails.name, name, "Name should match"); + assertEq(orgDetails.description, description, "Description should match"); + assertEq(orgDetails.owner, address(this), "Owner should match"); + } } diff --git a/test/Token.t.sol b/test/Token.t.sol index b96f4e3..a20f08c 100644 --- a/test/Token.t.sol +++ b/test/Token.t.sol @@ -6,6 +6,10 @@ import "../src/contracts/Tokens.sol"; import "../src/libraries/errors.sol"; contract TokenTest is Test { + // Events from TokenRegistry contract + event TokenAdded(address indexed tokenAddress, string name); + event TokenRemoved(address indexed tokenAddress); + TokenRegistry public tokenRegistry; address public owner; address public token1; @@ -19,8 +23,9 @@ contract TokenTest is Test { tokenRegistry = new TokenRegistry(); } - function testInitialState() public view { + function testInitialState() public { assertEq(tokenRegistry.supportedTokensCount(), 0, "Initial supported tokens count should be 0"); + assertEq(tokenRegistry.owner(), address(this), "Owner should be test contract"); } function testAddToken() public { @@ -79,4 +84,47 @@ contract TokenTest is Test { vm.expectRevert(CustomErrors.InvalidTokenAddress.selector); tokenRegistry.isTokenSupported(address(0)); } + + function testMultipleTokenManagement() public { + // Add multiple tokens + tokenRegistry.addToken("Token 1", token1); + tokenRegistry.addToken("Token 2", token2); + + assertEq(tokenRegistry.supportedTokensCount(), 2, "Should have two supported tokens"); + assertTrue(tokenRegistry.isTokenSupported(token1), "Token1 should be supported"); + assertTrue(tokenRegistry.isTokenSupported(token2), "Token2 should be supported"); + + // Remove one token + tokenRegistry.removeToken(token1); + + assertEq(tokenRegistry.supportedTokensCount(), 1, "Should have one supported token"); + assertFalse(tokenRegistry.isTokenSupported(token1), "Token1 should not be supported"); + assertTrue(tokenRegistry.isTokenSupported(token2), "Token2 should still be supported"); + } + + function test_RevertWhen_UnauthorizedAccess() public { + address nonOwner = address(123); + + vm.startPrank(nonOwner); + + vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); + tokenRegistry.addToken("Test Token", token1); + + vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); + tokenRegistry.removeToken(token1); + + vm.stopPrank(); + } + + function testTokenEvents() public { + string memory tokenName = "Test Token"; + + vm.expectEmit(true, false, false, true); + emit TokenAdded(token1, tokenName); + tokenRegistry.addToken(tokenName, token1); + + vm.expectEmit(true, false, false, false); + emit TokenRemoved(token1); + tokenRegistry.removeToken(token1); + } } From c4b760811ac757d32f5fd55760aaf3b8c3a9753b Mon Sep 17 00:00:00 2001 From: Olumide Adenigba Date: Mon, 28 Apr 2025 01:36:47 +0100 Subject: [PATCH 2/3] chore: fixed build warnings --- src/contracts/OrganizationContract.sol | 2 +- test/OrganizationContract.t.sol | 122 +++++++++++++------------ test/OrganizationFactory.t.sol | 8 +- test/Token.t.sol | 18 ++-- 4 files changed, 76 insertions(+), 74 deletions(-) diff --git a/src/contracts/OrganizationContract.sol b/src/contracts/OrganizationContract.sol index 0dbc29e..dc377d9 100644 --- a/src/contracts/OrganizationContract.sol +++ b/src/contracts/OrganizationContract.sol @@ -427,7 +427,7 @@ contract OrganizationContract { _onlyOwner(); if (_recipient == address(0)) revert CustomErrors.InvalidAddress(); if (recipients[_recipient].recipientId == 0) revert CustomErrors.RecipientNotFound(); - + // Check if limit exceeds salary if (_limit >= recipients[_recipient].salaryAmount) revert CustomErrors.InvalidAmount(); diff --git a/test/OrganizationContract.t.sol b/test/OrganizationContract.t.sol index 69f7d86..92c52ed 100644 --- a/test/OrganizationContract.t.sol +++ b/test/OrganizationContract.t.sol @@ -211,7 +211,7 @@ contract OrganizationContractTest is Test { // Try to request advance above the default limit vm.prank(recipient); vm.expectRevert(CustomErrors.InvalidAmount.selector); - org.requestAdvance(0.2 ether, address(token)); // Request more than default 0.1 ether limit + org.requestAdvance(0.2 ether, address(token)); // Request more than default 0.1 ether limit } function testRequestAdvanceEventEmission() public { @@ -228,12 +228,12 @@ contract OrganizationContractTest is Test { // Get emitted events Vm.Log[] memory entries = vm.getRecordedLogs(); - + // Verify AdvanceRequested event bytes32 expectedEventSig = keccak256("AdvanceRequested(address,uint256)"); bool foundEvent = false; - - for (uint i = 0; i < entries.length; i++) { + + for (uint256 i = 0; i < entries.length; i++) { if (entries[i].topics[0] == expectedEventSig) { foundEvent = true; assertEq(address(uint160(uint256(entries[i].topics[1]))), recipient, "Recipient address should match"); @@ -241,7 +241,7 @@ contract OrganizationContractTest is Test { break; } } - + assertTrue(foundEvent, "Should emit AdvanceRequested event"); } @@ -451,10 +451,10 @@ contract OrganizationContractTest is Test { function testUpdateOrganizationInfo() public { string memory newName = "Updated Org Name"; string memory newDescription = "Updated Description"; - + org.updateOrganizationInfo(newName, newDescription); - - (bytes32 id, string memory name, string memory description, , , ) = org.organizationInfo(); + + (, string memory name, string memory description,,,) = org.organizationInfo(); assertEq(name, newName, "Organization name should be updated"); assertEq(description, newDescription, "Organization description should be updated"); } @@ -567,7 +567,7 @@ contract OrganizationContractTest is Test { // First advance request vm.startPrank(recipient); org.requestAdvance(200, address(token)); - + // Approve first request vm.stopPrank(); org.approveAdvance(recipient); @@ -636,7 +636,7 @@ contract OrganizationContractTest is Test { org.createRecipient(recipient, "Test Recipient", 1000); uint256 amount = 100; org.disburseToken(address(token), recipient, amount); - + // Get payment history StructLib.Structs.Payment[] memory payments = org.getRecipientPayments(recipient); assertEq(payments.length, 1, "Should have one payment record"); @@ -644,7 +644,7 @@ contract OrganizationContractTest is Test { assertEq(payments[0].amount, amount - (amount * org.transactionFee()) / 10000, "Payment amount should match"); } - function testFeeCalculationEdgeCases() public { + function testFeeCalculationEdgeCases() public view { // Test with very small amounts uint256 smallAmount = 1; uint256 fee = org.calculateFee(smallAmount); @@ -654,7 +654,7 @@ contract OrganizationContractTest is Test { uint256 largeAmount = type(uint256).max / 10001; // Prevent overflow uint256 largeFee = org.calculateFee(largeAmount); assertTrue(largeFee > 0, "Fee should be calculated for large amounts"); - + // Test gross amount calculation uint256 netAmount = 1000; uint256 grossAmount = org.calculateGrossAmount(netAmount); @@ -666,17 +666,17 @@ contract OrganizationContractTest is Test { // Create a malicious token that attempts reentrancy MaliciousToken malToken = new MaliciousToken(); factory.addToken("Malicious Token", address(malToken)); - + // Create recipient org.createRecipient(recipient, "Test Recipient", 1000); - + // Fund the malicious token malToken.mint(address(this), 1000 ether); malToken.approve(address(org), type(uint256).max); - + // Set up the reentrancy attack malToken.setTarget(address(org), recipient); - + // Attempt reentrancy attack vm.expectRevert(CustomErrors.ReentrantCall.selector); org.disburseToken(address(malToken), recipient, 100); @@ -686,29 +686,29 @@ contract OrganizationContractTest is Test { // Create recipient with salary and advance limit org.createRecipient(recipient, "Test Recipient", 1000); org.setRecipientAdvanceLimit(recipient, 500); - + // Request and approve advance vm.prank(recipient); org.requestAdvance(300, address(token)); org.approveAdvance(recipient); - + // Verify advance state StructLib.Structs.Recipient memory recipientInfo = org.getRecipient(recipient); assertEq(recipientInfo.advanceCollected, 300, "Advance should be recorded"); - + // Attempt to disburse less than advance amount vm.expectRevert(CustomErrors.InvalidAmount.selector); org.disburseToken(address(token), recipient, 200); - + // Disburse more than advance amount to trigger repayment org.disburseToken(address(token), recipient, 1000); - + // Verify advance is cleared recipientInfo = org.getRecipient(recipient); assertEq(recipientInfo.advanceCollected, 0, "Advance should be cleared after repayment"); - + // Verify advance request is cleared - (,,,,,bool repaid,) = org.advanceRequests(recipient); + (,,,,, bool repaid,) = org.advanceRequests(recipient); assertTrue(repaid, "Advance should be marked as repaid"); } @@ -717,7 +717,7 @@ contract OrganizationContractTest is Test { address[] memory addresses = new address[](3); string[] memory names = new string[](3); uint256[] memory salaries = new uint256[](3); - + addresses[0] = address(10); addresses[1] = address(11); addresses[2] = address(12); @@ -727,29 +727,29 @@ contract OrganizationContractTest is Test { salaries[0] = 1000; salaries[1] = 2000; salaries[2] = 3000; - + org.batchCreateRecipients(addresses, names, salaries); - + // Set up advances for some recipients vm.prank(addresses[0]); org.requestAdvance(100, address(token)); org.approveAdvance(addresses[0]); - + vm.prank(addresses[1]); org.requestAdvance(200, address(token)); org.approveAdvance(addresses[1]); - + // Prepare disbursement amounts address[] memory recipients = new address[](3); uint256[] memory amounts = new uint256[](3); - + recipients[0] = addresses[0]; recipients[1] = addresses[1]; recipients[2] = addresses[2]; - amounts[0] = 500; // More than advance - amounts[1] = 150; // Less than advance + amounts[0] = 500; // More than advance + amounts[1] = 150; // Less than advance amounts[2] = 1000; // No advance - + // Test batch disbursement with mixed scenarios vm.expectRevert(CustomErrors.InvalidAmount.selector); org.batchDisburseToken(address(token), recipients, amounts); @@ -757,21 +757,21 @@ contract OrganizationContractTest is Test { function testPermissions() public { address nonOwner = address(123); - + // Test owner-only functions vm.prank(nonOwner); vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); org.createRecipient(recipient, "Test", 1000); - + vm.prank(nonOwner); vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); org.disburseToken(address(token), recipient, 100); - + // Test factory-only functions vm.prank(nonOwner); vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); org.setTransactionFee(60); - + vm.prank(nonOwner); vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); org.setFeeCollector(address(456)); @@ -794,7 +794,7 @@ contract OrganizationContractTest is Test { // Verify update StructLib.Structs.Recipient memory recipientInfo = org.getRecipient(recipient); assertEq(recipientInfo.salaryAmount, newSalary, "Salary should be updated"); - assertTrue(recipientInfo.updatedAt > recipientInfo.createdAt, "Updated timestamp should be greater"); + assertTrue(recipientInfo.updatedAt > initial.updatedAt, "Updated timestamp should be greater than initial timestamp"); } function test_RevertWhen_UpdateRecipientSalaryWithZeroAmount() public { @@ -839,18 +839,18 @@ contract OrganizationContractTest is Test { assertEq(recipientInfo.advanceCollected, 0, "Advance should be cleared"); // Verify advance request is cleared and marked as repaid - (,,,,,bool repaid,) = org.advanceRequests(recipient); + (,,,,, bool repaid,) = org.advanceRequests(recipient); assertTrue(repaid, "Advance should be marked as repaid"); } - function testFeeCalculationPrecision() public { + function testFeeCalculationPrecision() public view { // Test with very small amounts uint256 smallAmount = 1; uint256 fee = org.calculateFee(smallAmount); assertEq(fee, 0, "Fee should be 0 for very small amounts"); // Test with amount that would cause precision loss - uint256 amount = 10001; // This should result in a non-zero fee + uint256 amount = 10001; // This should result in a non-zero fee fee = org.calculateFee(amount); assertTrue(fee > 0, "Fee should be non-zero for larger amounts"); @@ -873,7 +873,7 @@ contract OrganizationContractTest is Test { Vm.Log[] memory entries = vm.getRecordedLogs(); assertEq(entries.length, 1, "Should emit one event"); - + // Verify RecipientCreated event bytes32 expectedEventSig = keccak256("RecipientCreated(bytes32,address,string)"); assertEq(entries[0].topics[0], expectedEventSig, "Event signature should match"); @@ -887,8 +887,8 @@ contract OrganizationContractTest is Test { entries = vm.getRecordedLogs(); bool foundTokenDisbursedEvent = false; expectedEventSig = keccak256("TokenDisbursed(address,address,uint256)"); - - for (uint i = 0; i < entries.length; i++) { + + for (uint256 i = 0; i < entries.length; i++) { if (entries[i].topics[0] == expectedEventSig) { foundTokenDisbursedEvent = true; assertEq(address(uint160(uint256(entries[i].topics[1]))), address(token), "Token address should match"); @@ -911,7 +911,9 @@ contract OrganizationContractTest is Test { org.updateRecipient(recipient, "Updated Name"); StructLib.Structs.Recipient memory afterNameUpdate = org.getRecipient(recipient); assertEq(afterNameUpdate.name, "Updated Name", "Name should be updated"); - assertTrue(afterNameUpdate.updatedAt > initial.createdAt, "Updated timestamp should be greater than created timestamp"); + assertTrue( + afterNameUpdate.updatedAt > initial.createdAt, "Updated timestamp should be greater than created timestamp" + ); // Advance time by another second vm.warp(block.timestamp + 1); @@ -920,7 +922,10 @@ contract OrganizationContractTest is Test { org.updateRecipientSalary(recipient, 2000); StructLib.Structs.Recipient memory afterSalaryUpdate = org.getRecipient(recipient); assertEq(afterSalaryUpdate.salaryAmount, 2000, "Salary should be updated"); - assertTrue(afterSalaryUpdate.updatedAt > initial.createdAt, "Updated timestamp should be greater than created timestamp"); + assertTrue( + afterSalaryUpdate.updatedAt > initial.createdAt, + "Updated timestamp should be greater than created timestamp" + ); } function testZeroAddressChecks() public { @@ -933,7 +938,7 @@ contract OrganizationContractTest is Test { string[] memory names = new string[](2); uint256[] memory salaries = new uint256[](2); addresses[0] = address(1); - addresses[1] = address(0); // Zero address + addresses[1] = address(0); // Zero address names[0] = "Recipient 1"; names[1] = "Recipient 2"; salaries[0] = 1000; @@ -964,7 +969,7 @@ contract OrganizationContractTest is Test { string[] memory names = new string[](length); uint256[] memory salaries = new uint256[](length); - for(uint i = 0; i < length; i++) { + for (uint256 i = 0; i < length; i++) { addresses[i] = address(uint160(i + 1)); names[i] = "Test"; salaries[i] = 1000; @@ -974,9 +979,9 @@ contract OrganizationContractTest is Test { org.batchCreateRecipients(addresses, names, salaries); // Verify recipients were created - for(uint i = 0; i < length; i++) { - StructLib.Structs.Recipient memory recipient = org.getRecipient(addresses[i]); - assertTrue(recipient.recipientId != 0, "Recipient should exist"); + for (uint256 i = 0; i < length; i++) { + StructLib.Structs.Recipient memory recipientData = org.getRecipient(addresses[i]); + assertTrue(recipientData.recipientId != 0, "Recipient should exist"); } } @@ -1047,7 +1052,7 @@ contract OrganizationContractTest is Test { // Create recipient and test valid advance limit org.createRecipient(recipient, "Test Recipient", 1000); org.setRecipientAdvanceLimit(recipient, 500); - + // Test setting advance limit for zero address vm.expectRevert(CustomErrors.InvalidAddress.selector); org.setRecipientAdvanceLimit(address(0), 500); @@ -1068,7 +1073,7 @@ contract OrganizationContractTest is Test { assertEq(org.calculateGrossAmount(amount), amount, "Gross amount should equal net amount when fee is zero"); } - function testConstructorAndInitialState() public { + function testConstructorAndInitialState() public view { // Test constructor parameters assertEq(factory.owner(), address(this), "Owner should be set correctly"); assertEq(factory.feeCollector(), feeCollector, "Fee collector should be set correctly"); @@ -1087,7 +1092,7 @@ contract OrganizationContractTest is Test { // Create a recipient to check if they get the default advance limit address newRecipient = address(123); - vm.prank(newOwner); // Only owner can create recipient + vm.prank(newOwner); // Only owner can create recipient newOrg.createRecipient(newRecipient, "Test Recipient", 1000); // Verify the default advance limit is 0.1 ether @@ -1099,7 +1104,8 @@ contract OrganizationContractTest is Test { vm.stopPrank(); // Verify the request was accepted - (address requestRecipient, uint256 requestAmount,,,,, address requestToken) = newOrg.advanceRequests(newRecipient); + (address requestRecipient, uint256 requestAmount,,,,, address requestToken) = + newOrg.advanceRequests(newRecipient); assertEq(requestRecipient, newRecipient, "Request recipient should match"); assertEq(requestAmount, 0.1 ether, "Request amount should match default limit"); assertEq(requestToken, address(token), "Request token should match"); @@ -1111,21 +1117,21 @@ contract MaliciousToken is MockERC20 { OrganizationContract private targetContract; address private targetRecipient; bool private reentrancyAttempted; - + function transferFrom(address from, address to, uint256 amount) external override returns (bool) { if (!reentrancyAttempted && msg.sender == address(targetContract)) { reentrancyAttempted = true; // Attempt reentrancy targetContract.disburseToken(address(this), targetRecipient, amount); } - + _balances[from] -= amount; _balances[to] += amount; _allowances[from][msg.sender] -= amount; - + return true; } - + function setTarget(address _contract, address _recipient) external { targetContract = OrganizationContract(_contract); targetRecipient = _recipient; diff --git a/test/OrganizationFactory.t.sol b/test/OrganizationFactory.t.sol index 9103548..99d2709 100644 --- a/test/OrganizationFactory.t.sol +++ b/test/OrganizationFactory.t.sol @@ -10,11 +10,7 @@ import "../src/libraries/errors.sol"; contract OrganizationFactoryTest is Test { // Events from OrganizationFactory contract event OrganizationCreated( - address indexed organizationAddress, - address indexed owner, - string name, - string description, - uint256 createdAt + address indexed organizationAddress, address indexed owner, string name, string description, uint256 createdAt ); OrganizationFactory public factory; @@ -246,7 +242,7 @@ contract OrganizationFactoryTest is Test { assertEq(address(uint160(uint256(lastEntry.topics[2]))), address(this), "Owner address should match"); // Decode non-indexed parameters - (string memory emittedName, string memory emittedDesc, uint256 emittedTime) = + (string memory emittedName, string memory emittedDesc, uint256 emittedTime) = abi.decode(lastEntry.data, (string, string, uint256)); // Verify non-indexed parameters diff --git a/test/Token.t.sol b/test/Token.t.sol index a20f08c..87d16b6 100644 --- a/test/Token.t.sol +++ b/test/Token.t.sol @@ -89,14 +89,14 @@ contract TokenTest is Test { // Add multiple tokens tokenRegistry.addToken("Token 1", token1); tokenRegistry.addToken("Token 2", token2); - + assertEq(tokenRegistry.supportedTokensCount(), 2, "Should have two supported tokens"); assertTrue(tokenRegistry.isTokenSupported(token1), "Token1 should be supported"); assertTrue(tokenRegistry.isTokenSupported(token2), "Token2 should be supported"); - + // Remove one token tokenRegistry.removeToken(token1); - + assertEq(tokenRegistry.supportedTokensCount(), 1, "Should have one supported token"); assertFalse(tokenRegistry.isTokenSupported(token1), "Token1 should not be supported"); assertTrue(tokenRegistry.isTokenSupported(token2), "Token2 should still be supported"); @@ -104,25 +104,25 @@ contract TokenTest is Test { function test_RevertWhen_UnauthorizedAccess() public { address nonOwner = address(123); - + vm.startPrank(nonOwner); - + vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); tokenRegistry.addToken("Test Token", token1); - + vm.expectRevert(CustomErrors.UnauthorizedAccess.selector); tokenRegistry.removeToken(token1); - + vm.stopPrank(); } function testTokenEvents() public { string memory tokenName = "Test Token"; - + vm.expectEmit(true, false, false, true); emit TokenAdded(token1, tokenName); tokenRegistry.addToken(tokenName, token1); - + vm.expectEmit(true, false, false, false); emit TokenRemoved(token1); tokenRegistry.removeToken(token1); From 2932976a0f222ae9492af66335c3b28c1047f84d Mon Sep 17 00:00:00 2001 From: Olumide Adenigba Date: Mon, 28 Apr 2025 02:09:47 +0100 Subject: [PATCH 3/3] chore: fixed workflows --- test/OrganizationContract.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/OrganizationContract.t.sol b/test/OrganizationContract.t.sol index 92c52ed..ecfffcb 100644 --- a/test/OrganizationContract.t.sol +++ b/test/OrganizationContract.t.sol @@ -794,7 +794,9 @@ contract OrganizationContractTest is Test { // Verify update StructLib.Structs.Recipient memory recipientInfo = org.getRecipient(recipient); assertEq(recipientInfo.salaryAmount, newSalary, "Salary should be updated"); - assertTrue(recipientInfo.updatedAt > initial.updatedAt, "Updated timestamp should be greater than initial timestamp"); + assertTrue( + recipientInfo.updatedAt > initial.updatedAt, "Updated timestamp should be greater than initial timestamp" + ); } function test_RevertWhen_UpdateRecipientSalaryWithZeroAmount() public {