From 0b20c0ad3cdd6b79723814754193951389ab1290 Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Sun, 8 Jun 2025 22:12:43 +0530 Subject: [PATCH 01/13] feat : add scripts for deployment --- script/DeployAllContracts.s.sol | 99 ++++++++++++++++++++++++++ script/DeployOrganisationFactory.s.sol | 35 +++++++++ script/DeployTreeNftContract.s.sol | 1 - 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 script/DeployAllContracts.s.sol create mode 100644 script/DeployOrganisationFactory.s.sol diff --git a/script/DeployAllContracts.s.sol b/script/DeployAllContracts.s.sol new file mode 100644 index 0000000..5fa7d32 --- /dev/null +++ b/script/DeployAllContracts.s.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "forge-std/Script.sol"; +import "../src/TreeNft.sol"; +import "../src/token-contracts/CareToken.sol"; +import "../src/token-contracts/PlanterToken.sol"; +import "../src/token-contracts/VerifierToken.sol"; +import "../src/token-contracts/LegacyToken.sol"; +import "../src/OrganisationFactory.sol"; + +contract DeployAllContractsAtOnce is Script { + address public careTokenAddress; + address public planterTokenAddress; + address public verifierTokenAddress; + address public legacyTokenAddress; + address public treeNftAddress; + + function run() external { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + + console.log("\n========== DEPLOYMENT INITIALIZED =========="); + console.log("Deployer Address: ", deployer); + console.log("Deployer ETH Balance: ", deployer.balance); + console.log("============================================\n"); + + vm.startBroadcast(deployerPrivateKey); + + // STEP 1: Deploy token contracts + console.log(">> Step 1: Deploying ERC20 Token Contracts..."); + + CareToken careToken = new CareToken(deployer); + careTokenAddress = address(careToken); + console.log(" - CareToken deployed at: ", careTokenAddress); + + PlanterToken planterToken = new PlanterToken(deployer); + planterTokenAddress = address(planterToken); + console.log(" - PlanterToken deployed at: ", planterTokenAddress); + + VerifierToken verifierToken = new VerifierToken(deployer); + verifierTokenAddress = address(verifierToken); + console.log(" - VerifierToken deployed at: ", verifierTokenAddress); + + LegacyToken legacyToken = new LegacyToken(deployer); + legacyTokenAddress = address(legacyToken); + console.log(" - LegacyToken deployed at: ", legacyTokenAddress); + + console.log("\n>> Step 2: Deploying TreeNft Contract..."); + TreeNft treeNft = new TreeNft(careTokenAddress, planterTokenAddress, verifierTokenAddress, legacyTokenAddress); + treeNftAddress = address(treeNft); + console.log(" - TreeNft deployed at: ", treeNftAddress); + + console.log("\n>> Step 3: Transferring Token Ownership to TreeNft..."); + careToken.transferOwnership(treeNftAddress); + console.log(" - CareToken ownership transferred."); + planterToken.transferOwnership(treeNftAddress); + console.log(" - PlanterToken ownership transferred."); + verifierToken.transferOwnership(treeNftAddress); + console.log(" - VerifierToken ownership transferred."); + legacyToken.transferOwnership(treeNftAddress); + console.log(" - LegacyToken ownership transferred."); + + console.log("\n>> Step 4: Deploying OrganisationFactory..."); + OrganisationFactory orgFactory = new OrganisationFactory(treeNftAddress); + address orgFactoryAddress = address(orgFactory); + console.log(" - OrganisationFactory deployed at:", orgFactoryAddress); + + vm.stopBroadcast(); + + console.log("\n========== DEPLOYMENT SUMMARY =========="); + console.log("CareToken Address: ", careTokenAddress); + console.log("PlanterToken Address: ", planterTokenAddress); + console.log("VerifierToken Address: ", verifierTokenAddress); + console.log("LegacyToken Address: ", legacyTokenAddress); + console.log("TreeNft Address: ", treeNftAddress); + console.log("OrganisationFactory: ", orgFactoryAddress); + console.log("All token ownerships successfully transferred to TreeNft."); + console.log("=========================================\n"); + + verifyDeployment(); + } + + function verifyDeployment() internal view { + console.log(">> Verifying Deployment Integrity..."); + + TreeNft treeNft = TreeNft(treeNftAddress); + + require(address(treeNft.careTokenContract()) == careTokenAddress, "CareToken address mismatch"); + require(address(treeNft.planterTokenContract()) == planterTokenAddress, "PlanterToken address mismatch"); + require(address(treeNft.verifierTokenContract()) == verifierTokenAddress, "VerifierToken address mismatch"); + require(address(treeNft.legacyToken()) == legacyTokenAddress, "LegacyToken address mismatch"); + + CareToken careToken = CareToken(careTokenAddress); + require(careToken.owner() == treeNftAddress, "CareToken ownership not transferred"); + + console.log("Deployment verification passed.\n"); + } +} diff --git a/script/DeployOrganisationFactory.s.sol b/script/DeployOrganisationFactory.s.sol new file mode 100644 index 0000000..1e02e27 --- /dev/null +++ b/script/DeployOrganisationFactory.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "forge-std/Script.sol"; +import "../src/TreeNft.sol"; +import "../src/OrganisationFactory.sol"; + +contract DeployOrganisationFactory is Script { + function run() external { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + address treeNftAddress = vm.envAddress("TREE_NFT_ADDRESS"); + + console.log("\n========== DEPLOYMENT STARTED =========="); + console.log(">> Deployer Address: ", deployer); + console.log(">> Deployer Balance (wei): ", deployer.balance); + console.log(">> Linked TreeNFT Address: ", treeNftAddress); + console.log("========================================\n"); + + vm.startBroadcast(deployerPrivateKey); + + console.log("Deploying OrganisationFactory..."); + OrganisationFactory orgFactory = new OrganisationFactory(treeNftAddress); + address orgFactoryAddress = address(orgFactory); + console.log("OrganisationFactory deployed at:", orgFactoryAddress); + + vm.stopBroadcast(); + + console.log("\n========== DEPLOYMENT SUMMARY =========="); + console.log("OrganisationFactory Address: ", orgFactoryAddress); + console.log("Linked TreeNFT Address: ", treeNftAddress); + console.log("Deployment completed successfully."); + console.log("========================================\n"); + } +} diff --git a/script/DeployTreeNftContract.s.sol b/script/DeployTreeNftContract.s.sol index da161bb..4711637 100644 --- a/script/DeployTreeNftContract.s.sol +++ b/script/DeployTreeNftContract.s.sol @@ -54,7 +54,6 @@ contract DeployTreeNft is Script { console.log("VerifierToken ownership transferred to TreeNft"); legacyToken.transferOwnership(treeNftAddress); console.log("LegacyToken ownership transferred to TreeNft"); - vm.stopBroadcast(); console.log("\n=== DEPLOYMENT SUMMARY ==="); console.log("CareToken:", careTokenAddress); From 96b7022761f08a508e6062ccea7e2171eaed005c Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Sun, 8 Jun 2025 23:27:45 +0530 Subject: [PATCH 02/13] add: add necessary reverts --- script/DeployOrganisationFactory.s.sol | 2 ++ script/DeployTreeNftContract.s.sol | 12 +++++++++++- src/utils/errors.sol | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/script/DeployOrganisationFactory.s.sol b/script/DeployOrganisationFactory.s.sol index 1e02e27..ddfacd5 100644 --- a/script/DeployOrganisationFactory.s.sol +++ b/script/DeployOrganisationFactory.s.sol @@ -11,6 +11,8 @@ contract DeployOrganisationFactory is Script { address deployer = vm.addr(deployerPrivateKey); address treeNftAddress = vm.envAddress("TREE_NFT_ADDRESS"); + if (treeNftAddress.code.length <= 0) revert InvalidContractAddress(); + console.log("\n========== DEPLOYMENT STARTED =========="); console.log(">> Deployer Address: ", deployer); console.log(">> Deployer Balance (wei): ", deployer.balance); diff --git a/script/DeployTreeNftContract.s.sol b/script/DeployTreeNftContract.s.sol index 4711637..69ec602 100644 --- a/script/DeployTreeNftContract.s.sol +++ b/script/DeployTreeNftContract.s.sol @@ -76,7 +76,17 @@ contract DeployTreeNft is Script { require(address(treeNft.legacyToken()) == legacyTokenAddress, "LegacyToken address mismatch"); CareToken careToken = CareToken(careTokenAddress); - require(careToken.owner() == treeNftAddress, "CareToken ownership not transferred"); + if (careToken.owner() != treeNftAddress) revert OwnershipNotTransferred(); + + PlanterToken planterToken = PlanterToken(planterTokenAddress); + if (planterToken.owner() != treeNftAddress) revert OwnershipNotTransferred(); + + VerifierToken verifierToken = VerifierToken(verifierTokenAddress); + if (verifierToken.owner() != treeNftAddress) revert OwnershipNotTransferred(); + + LegacyToken legacyToken = LegacyToken(legacyTokenAddress); + if (legacyToken.owner() != treeNftAddress) revert OwnershipNotTransferred(); + console.log("Deployment verification successful!"); } } diff --git a/src/utils/errors.sol b/src/utils/errors.sol index f39ab9c..ea183bd 100644 --- a/src/utils/errors.sol +++ b/src/utils/errors.sol @@ -27,6 +27,7 @@ error InvalidProposalId(); error AlreadyVoted(); error InvalidInput(); error PaginationLimitExceeded(); +error InvalidContractAddress(); /// Request error InvalidRequestId(); @@ -46,3 +47,7 @@ error InvalidCoordinates(); /// User error UserAlreadyRegistered(); error UserNotRegistered(); + +/// Deploy + +error OwnershipNotTransferred(); From 12d8c390f714a4714a134504e1d8bdbf354d2341 Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Mon, 11 Aug 2025 11:19:32 +0530 Subject: [PATCH 03/13] add: getUserDetails function and ping function --- .DS_Store | Bin 8196 -> 8196 bytes script/DeployTreeNftContract.s.sol | 1 + src/Organisation.sol | 5 ++++ src/TreeNft.sol | 27 ++++++++++++++++++- src/utils/structs.sol | 15 +++++++++++ test/Organisation.t.sol | 11 ++++---- test/TreeNft.t.sol | 41 ++++++++++++++++------------- 7 files changed, 76 insertions(+), 24 deletions(-) diff --git a/.DS_Store b/.DS_Store index 636aa53ec0e02ba497589d01b5b38630325595bf..d30b09ce2fdd4508045bee08faffd2b50b8cb550 100644 GIT binary patch delta 38 ucmZp1XmOa}&nUAoU^hRb%w`?|ZpO{a1=E-}*Yb2RZ)TVH&ayF+jTr#!kPFoS delta 178 zcmZp1XmOa}¥U^hRb!e$-;ZpM0c20ex{hGK?fhJ1z;hSZ#N!{Frn+yVv&Fac6% zDsuB(T#|C~lYlZDvKbt!LmG}bVpEkugsOs!&1{0@%o|gFF>Pj-_|C$^lnyjX?>`u9 IO#H 180 * 1e6) revert InvalidCoordinates(); if (_longitude > 360 * 1e6) revert InvalidCoordinates(); @@ -274,6 +276,7 @@ contract Organisation { qrIpfsHash: _qrIpfshash, photos: photos, geoHash: geoHash, + metadata: _metadata, status: 0 }); if (checkOwnership(msg.sender)) { @@ -286,6 +289,7 @@ contract Organisation { proposal.species, proposal.imageUri, proposal.qrIpfsHash, + proposal.metadata, proposal.geoHash, proposal.photos ); @@ -398,6 +402,7 @@ contract Organisation { proposal.species, proposal.imageUri, proposal.qrIpfsHash, + proposal.metadata, proposal.geoHash, proposal.photos ); diff --git a/src/TreeNft.sol b/src/TreeNft.sol index 9500398..8db90f8 100644 --- a/src/TreeNft.sol +++ b/src/TreeNft.sol @@ -72,16 +72,17 @@ contract TreeNft is ERC721, Ownable { string memory species, string memory imageUri, string memory qrIpfsHash, + string memory metadata, string memory geoHash, string[] memory initialPhotos ) public { - // This function mints a new NFT for the user if (latitude > 180 * 1e6) revert InvalidCoordinates(); if (longitude > 360 * 1e6) revert InvalidCoordinates(); uint256 tokenId = s_tokenCounter; s_tokenCounter++; + _mint(msg.sender, tokenId); address[] memory ancestors = new address[](1); ancestors[0] = msg.sender; @@ -93,6 +94,7 @@ contract TreeNft is ERC721, Ownable { species, imageUri, qrIpfsHash, + metadata, initialPhotos, geoHash, ancestors, @@ -102,6 +104,7 @@ contract TreeNft is ERC721, Ownable { s_userToNFTs[msg.sender].push(tokenId); planterTokenContract.mint(msg.sender, tokenId); + } function tokenURI(uint256 tokenId) public view override returns (string memory) { @@ -362,6 +365,23 @@ contract TreeNft is ERC721, Ownable { s_userCounter++; } + function getUserProfile(address userAddress) public view returns (UserDetails memory userDetails) { + if (s_addressToUser[userAddress].userAddress == address(0)) revert UserNotRegistered(); + User memory storedUserDetails = s_addressToUser[userAddress]; + userDetails.name = storedUserDetails.name; + userDetails.dateJoined = storedUserDetails.dateJoined; + userDetails.profilePhotoIpfs = storedUserDetails.profilePhotoIpfs; + userDetails.userAddress = storedUserDetails.userAddress; + userDetails.reportedSpam = storedUserDetails.reportedSpam; + userDetails.verificationsRevoked = storedUserDetails.verificationsRevoked; + userDetails.careTokens = careTokenContract.balanceOf(userAddress); + userDetails.planterTokens = planterTokenContract.balanceOf(userAddress); + userDetails.legacyTokens = legacyToken.balanceOf(userAddress); + userDetails.verifierTokens = verifierTokenContract.balanceOf(userAddress); + return userDetails; + } + + function updateUserDetails(string memory _name, string memory _profilePhotoHash) public { // This function enables a user to change his user details @@ -379,4 +399,9 @@ contract TreeNft is ERC721, Ownable { function _exists(uint256 tokenId) internal view returns (bool) { return tokenId < s_tokenCounter && tokenId >= 0; } + + function ping() public pure returns (string memory){ + return "pong"; + } + } diff --git a/src/utils/structs.sol b/src/utils/structs.sol index 43fb348..3132bd4 100644 --- a/src/utils/structs.sol +++ b/src/utils/structs.sol @@ -43,6 +43,19 @@ struct User { uint256 reportedSpam; } +struct UserDetails { + address userAddress; + string profilePhotoIpfs; + string name; + uint256 dateJoined; + uint256 verificationsRevoked; + uint256 reportedSpam; + uint256 verifierTokens; + uint256 planterTokens; + uint256 legacyTokens; + uint256 careTokens; +} + struct Tree { uint256 latitude; uint256 longitude; @@ -51,6 +64,7 @@ struct Tree { string species; string imageUri; string qrIpfsHash; + string metadata; string[] photos; string geoHash; address[] ancestors; @@ -67,5 +81,6 @@ struct TreePlantingProposal { string qrIpfsHash; string[] photos; string geoHash; + string metadata; uint256 status; } diff --git a/test/Organisation.t.sol b/test/Organisation.t.sol index feaf4b8..f7a3776 100644 --- a/test/Organisation.t.sol +++ b/test/Organisation.t.sol @@ -41,6 +41,7 @@ contract OrganisationTest is Test { string constant DESCRIPTION2 = "This is a test organisation."; string constant PHOTO_IPFS_HASH2 = "QmTestPhotoHash"; string constant JOIN_REQUEST_DESCRIPTION = "I want to join this organisation"; + string constant METADATA = "ipfs://metaDataHash"; function setUp() public { vm.startPrank(owner); @@ -158,7 +159,7 @@ contract OrganisationTest is Test { imageHashes[0] = "QmProofHash"; vm.prank(user3); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEOHASH, imageHashes); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes); vm.stopPrank(); vm.prank(user2); @@ -190,7 +191,7 @@ contract OrganisationTest is Test { imageHashes[0] = "QmProofHash"; vm.prank(user3); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEOHASH, imageHashes); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes); vm.stopPrank(); vm.prank(user1); @@ -240,7 +241,7 @@ contract OrganisationTest is Test { vm.prank(user1); Organisation(orgAddress).plantTreeProposal( - LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, proofHashes, GEOHASH + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, proofHashes, GEOHASH ); vm.stopPrank(); @@ -270,7 +271,7 @@ contract OrganisationTest is Test { imageHashes[0] = "QmProofHash"; vm.prank(user3); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEOHASH, imageHashes); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes); vm.stopPrank(); vm.prank(user1); @@ -293,7 +294,7 @@ contract OrganisationTest is Test { string[] memory proofHashes = new string[](1); proofHashes[0] = "QmProofHash"; Organisation(orgAddress).plantTreeProposal( - LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, proofHashes, GEOHASH + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, proofHashes, GEOHASH ); vm.stopPrank(); diff --git a/test/TreeNft.t.sol b/test/TreeNft.t.sol index 6420559..add5c86 100644 --- a/test/TreeNft.t.sol +++ b/test/TreeNft.t.sol @@ -28,6 +28,7 @@ contract TreeNftTest is Test { uint256 constant LONGITUDE = 9876543; string constant SPECIES = "Oak Tree"; string constant IMAGE_URI = "ipfs://QmSampleImageHash"; + string constant METADATA = "ipfs://metaDataHash"; string constant QR_IPFS_HASH = "QmSampleQRHash"; string constant GEO_HASH = "u4pruydqqvj"; @@ -65,7 +66,7 @@ contract TreeNftTest is Test { initialPhotos[0] = "photo1.jpg"; initialPhotos[1] = "photo2.jpg"; - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA,GEO_HASH, initialPhotos); assertEq(treeNft.ownerOf(0), user1); assertEq(treeNft.balanceOf(user1), 1); @@ -90,7 +91,7 @@ contract TreeNftTest is Test { string[] memory initialPhotos = new string[](0); // Mint first NFT - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA , GEO_HASH, initialPhotos); // Mint second NFT treeNft.mintNft( @@ -99,6 +100,7 @@ contract TreeNftTest is Test { "Pine Tree", "ipfs://QmPineImage", "QmPineQR", + METADATA , "u4pruydqqvk", initialPhotos ); @@ -118,7 +120,7 @@ contract TreeNftTest is Test { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA , GEO_HASH, initialPhotos); string memory uri = treeNft.tokenURI(0); assertTrue(bytes(uri).length > 0); @@ -137,8 +139,8 @@ contract TreeNftTest is Test { vm.startPrank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); - treeNft.mintNft(LATITUDE + 1, LONGITUDE + 1, "Pine", IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA , GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE + 1, LONGITUDE + 1, "Pine", IMAGE_URI, QR_IPFS_HASH,METADATA, GEO_HASH, initialPhotos); vm.stopPrank(); Tree[] memory allTrees = treeNft.getAllNFTs(); @@ -159,6 +161,7 @@ contract TreeNftTest is Test { string(abi.encodePacked("Tree", vm.toString(i))), IMAGE_URI, QR_IPFS_HASH, + METADATA, GEO_HASH, initialPhotos ); @@ -196,6 +199,7 @@ contract TreeNftTest is Test { string(abi.encodePacked("UserTree", vm.toString(i))), IMAGE_URI, QR_IPFS_HASH, + METADATA, GEO_HASH, initialPhotos ); @@ -224,7 +228,7 @@ contract TreeNftTest is Test { function test_Verify() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.prank(verifier1); string[] memory proofHashes = new string[](2); @@ -256,7 +260,7 @@ contract TreeNftTest is Test { function test_VerifyTwiceSameVerifier() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.startPrank(verifier1); string[] memory proofHashes = new string[](1); @@ -274,7 +278,7 @@ contract TreeNftTest is Test { function test_MultipleVerifiers() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA, GEO_HASH, initialPhotos); vm.prank(verifier1); string[] memory proofHashes1 = new string[](1); proofHashes1[0] = "proof1"; @@ -295,7 +299,7 @@ contract TreeNftTest is Test { function test_RemoveVerification() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA, GEO_HASH, initialPhotos); vm.prank(verifier1); string[] memory proofHashes = new string[](1); @@ -314,7 +318,7 @@ contract TreeNftTest is Test { function test_RemoveVerificationNotOwner() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA , GEO_HASH, initialPhotos); vm.prank(verifier1); string[] memory proofHashes = new string[](1); @@ -329,8 +333,8 @@ contract TreeNftTest is Test { function test_GetVerifiedTreesByUser() public { vm.startPrank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, "Tree1", IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); - treeNft.mintNft(LATITUDE + 1, LONGITUDE + 1, "Tree2", IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, "Tree1", IMAGE_URI, QR_IPFS_HASH, METADATA ,GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE + 1, LONGITUDE + 1, "Tree2", IMAGE_URI, QR_IPFS_HASH,METADATA , GEO_HASH, initialPhotos); vm.stopPrank(); vm.startPrank(verifier1); @@ -356,6 +360,7 @@ contract TreeNftTest is Test { string(abi.encodePacked("Tree", vm.toString(i))), IMAGE_URI, QR_IPFS_HASH, + METADATA, GEO_HASH, initialPhotos ); @@ -381,7 +386,7 @@ contract TreeNftTest is Test { function test_GetTreeNftVerifiersPaginated() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, METADATA , initialPhotos); string[] memory proofHashes = new string[](1); proofHashes[0] = "proof"; @@ -412,7 +417,7 @@ contract TreeNftTest is Test { function test_MarkDead() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA, GEO_HASH, initialPhotos); vm.warp(block.timestamp + 366 days); vm.prank(user1); treeNft.markDead(0); @@ -425,7 +430,7 @@ contract TreeNftTest is Test { function testMarkDeadNotOwner() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA, GEO_HASH, initialPhotos); vm.prank(user2); vm.expectRevert(NotTreeOwner.selector); @@ -441,7 +446,7 @@ contract TreeNftTest is Test { function testMarkDeadAlreadyDead() public { vm.startPrank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.warp(block.timestamp + 366 days); treeNft.markDead(0); @@ -497,7 +502,7 @@ contract TreeNftTest is Test { vm.prank(user1); string[] memory initialPhotos = new string[](1); initialPhotos[0] = "initial_photo.jpg"; - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA ,GEO_HASH,initialPhotos); vm.prank(verifier1); string[] memory proofHashes = new string[](1); @@ -525,7 +530,7 @@ contract TreeNftTest is Test { uint256 gasBefore = gasleft(); vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); uint256 gasUsed = gasBefore - gasleft(); assertTrue(gasUsed < 500000, "Minting uses too much gas"); } From dfd0e3a4428402db13a65687712ab7dddcfc807c Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Mon, 11 Aug 2025 11:21:17 +0530 Subject: [PATCH 04/13] fix: fmt errors --- script/DeployTreeNftContract.s.sol | 1 - src/Organisation.sol | 1 - src/TreeNft.sol | 14 +++++-------- test/Organisation.t.sol | 2 +- test/TreeNft.t.sol | 32 ++++++++++++++++-------------- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/script/DeployTreeNftContract.s.sol b/script/DeployTreeNftContract.s.sol index c7018b5..69ec602 100644 --- a/script/DeployTreeNftContract.s.sol +++ b/script/DeployTreeNftContract.s.sol @@ -90,4 +90,3 @@ contract DeployTreeNft is Script { console.log("Deployment verification successful!"); } } - diff --git a/src/Organisation.sol b/src/Organisation.sol index b81897b..af19eec 100644 --- a/src/Organisation.sol +++ b/src/Organisation.sol @@ -261,7 +261,6 @@ contract Organisation { string memory _metadata, string[] memory photos, string memory geoHash - ) public { if (_latitude > 180 * 1e6) revert InvalidCoordinates(); if (_longitude > 360 * 1e6) revert InvalidCoordinates(); diff --git a/src/TreeNft.sol b/src/TreeNft.sol index 8db90f8..43ddf60 100644 --- a/src/TreeNft.sol +++ b/src/TreeNft.sol @@ -76,13 +76,12 @@ contract TreeNft is ERC721, Ownable { string memory geoHash, string[] memory initialPhotos ) public { - if (latitude > 180 * 1e6) revert InvalidCoordinates(); if (longitude > 360 * 1e6) revert InvalidCoordinates(); uint256 tokenId = s_tokenCounter; s_tokenCounter++; - + _mint(msg.sender, tokenId); address[] memory ancestors = new address[](1); ancestors[0] = msg.sender; @@ -104,7 +103,6 @@ contract TreeNft is ERC721, Ownable { s_userToNFTs[msg.sender].push(tokenId); planterTokenContract.mint(msg.sender, tokenId); - } function tokenURI(uint256 tokenId) public view override returns (string memory) { @@ -371,9 +369,9 @@ contract TreeNft is ERC721, Ownable { userDetails.name = storedUserDetails.name; userDetails.dateJoined = storedUserDetails.dateJoined; userDetails.profilePhotoIpfs = storedUserDetails.profilePhotoIpfs; - userDetails.userAddress = storedUserDetails.userAddress; - userDetails.reportedSpam = storedUserDetails.reportedSpam; - userDetails.verificationsRevoked = storedUserDetails.verificationsRevoked; + userDetails.userAddress = storedUserDetails.userAddress; + userDetails.reportedSpam = storedUserDetails.reportedSpam; + userDetails.verificationsRevoked = storedUserDetails.verificationsRevoked; userDetails.careTokens = careTokenContract.balanceOf(userAddress); userDetails.planterTokens = planterTokenContract.balanceOf(userAddress); userDetails.legacyTokens = legacyToken.balanceOf(userAddress); @@ -381,7 +379,6 @@ contract TreeNft is ERC721, Ownable { return userDetails; } - function updateUserDetails(string memory _name, string memory _profilePhotoHash) public { // This function enables a user to change his user details @@ -400,8 +397,7 @@ contract TreeNft is ERC721, Ownable { return tokenId < s_tokenCounter && tokenId >= 0; } - function ping() public pure returns (string memory){ + function ping() public pure returns (string memory) { return "pong"; } - } diff --git a/test/Organisation.t.sol b/test/Organisation.t.sol index f7a3776..86c548c 100644 --- a/test/Organisation.t.sol +++ b/test/Organisation.t.sol @@ -271,7 +271,7 @@ contract OrganisationTest is Test { imageHashes[0] = "QmProofHash"; vm.prank(user3); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes); vm.stopPrank(); vm.prank(user1); diff --git a/test/TreeNft.t.sol b/test/TreeNft.t.sol index add5c86..f7990df 100644 --- a/test/TreeNft.t.sol +++ b/test/TreeNft.t.sol @@ -66,7 +66,7 @@ contract TreeNftTest is Test { initialPhotos[0] = "photo1.jpg"; initialPhotos[1] = "photo2.jpg"; - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA,GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); assertEq(treeNft.ownerOf(0), user1); assertEq(treeNft.balanceOf(user1), 1); @@ -91,7 +91,7 @@ contract TreeNftTest is Test { string[] memory initialPhotos = new string[](0); // Mint first NFT - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA , GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); // Mint second NFT treeNft.mintNft( @@ -100,7 +100,7 @@ contract TreeNftTest is Test { "Pine Tree", "ipfs://QmPineImage", "QmPineQR", - METADATA , + METADATA, "u4pruydqqvk", initialPhotos ); @@ -120,7 +120,7 @@ contract TreeNftTest is Test { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA , GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); string memory uri = treeNft.tokenURI(0); assertTrue(bytes(uri).length > 0); @@ -139,8 +139,8 @@ contract TreeNftTest is Test { vm.startPrank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA , GEO_HASH, initialPhotos); - treeNft.mintNft(LATITUDE + 1, LONGITUDE + 1, "Pine", IMAGE_URI, QR_IPFS_HASH,METADATA, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE + 1, LONGITUDE + 1, "Pine", IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.stopPrank(); Tree[] memory allTrees = treeNft.getAllNFTs(); @@ -278,7 +278,7 @@ contract TreeNftTest is Test { function test_MultipleVerifiers() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.prank(verifier1); string[] memory proofHashes1 = new string[](1); proofHashes1[0] = "proof1"; @@ -299,7 +299,7 @@ contract TreeNftTest is Test { function test_RemoveVerification() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.prank(verifier1); string[] memory proofHashes = new string[](1); @@ -318,7 +318,7 @@ contract TreeNftTest is Test { function test_RemoveVerificationNotOwner() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA , GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.prank(verifier1); string[] memory proofHashes = new string[](1); @@ -333,8 +333,10 @@ contract TreeNftTest is Test { function test_GetVerifiedTreesByUser() public { vm.startPrank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, "Tree1", IMAGE_URI, QR_IPFS_HASH, METADATA ,GEO_HASH, initialPhotos); - treeNft.mintNft(LATITUDE + 1, LONGITUDE + 1, "Tree2", IMAGE_URI, QR_IPFS_HASH,METADATA , GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, "Tree1", IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); + treeNft.mintNft( + LATITUDE + 1, LONGITUDE + 1, "Tree2", IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos + ); vm.stopPrank(); vm.startPrank(verifier1); @@ -386,7 +388,7 @@ contract TreeNftTest is Test { function test_GetTreeNftVerifiersPaginated() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, METADATA , initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, METADATA, initialPhotos); string[] memory proofHashes = new string[](1); proofHashes[0] = "proof"; @@ -417,7 +419,7 @@ contract TreeNftTest is Test { function test_MarkDead() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.warp(block.timestamp + 366 days); vm.prank(user1); treeNft.markDead(0); @@ -430,7 +432,7 @@ contract TreeNftTest is Test { function testMarkDeadNotOwner() public { vm.prank(user1); string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH,METADATA, GEO_HASH, initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.prank(user2); vm.expectRevert(NotTreeOwner.selector); @@ -502,7 +504,7 @@ contract TreeNftTest is Test { vm.prank(user1); string[] memory initialPhotos = new string[](1); initialPhotos[0] = "initial_photo.jpg"; - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA ,GEO_HASH,initialPhotos); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.prank(verifier1); string[] memory proofHashes = new string[](1); From df64e50b891d5c3642885ccceccaa7b02a6441fa Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Tue, 12 Aug 2025 02:38:41 +0530 Subject: [PATCH 05/13] fix: planter token bug --- src/TreeNft.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/TreeNft.sol b/src/TreeNft.sol index 43ddf60..b8b2edf 100644 --- a/src/TreeNft.sol +++ b/src/TreeNft.sol @@ -102,7 +102,7 @@ contract TreeNft is ERC721, Ownable { ); s_userToNFTs[msg.sender].push(tokenId); - planterTokenContract.mint(msg.sender, tokenId); + planterTokenContract.mint(msg.sender, 1); } function tokenURI(uint256 tokenId) public view override returns (string memory) { @@ -364,6 +364,8 @@ contract TreeNft is ERC721, Ownable { } function getUserProfile(address userAddress) public view returns (UserDetails memory userDetails) { + // This function returns the details of the user + if (s_addressToUser[userAddress].userAddress == address(0)) revert UserNotRegistered(); User memory storedUserDetails = s_addressToUser[userAddress]; userDetails.name = storedUserDetails.name; From 99e3098ec0b0e59105a9f768e71153ede59c58c0 Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Tue, 12 Aug 2025 12:43:27 +0530 Subject: [PATCH 06/13] fix: add ID to tree struct --- src/TreeNft.sol | 1 + src/utils/structs.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/src/TreeNft.sol b/src/TreeNft.sol index b8b2edf..e7fce80 100644 --- a/src/TreeNft.sol +++ b/src/TreeNft.sol @@ -86,6 +86,7 @@ contract TreeNft is ERC721, Ownable { address[] memory ancestors = new address[](1); ancestors[0] = msg.sender; s_tokenIDtoTree[tokenId] = Tree( + tokenId, latitude, longitude, block.timestamp, diff --git a/src/utils/structs.sol b/src/utils/structs.sol index 3132bd4..4beb58c 100644 --- a/src/utils/structs.sol +++ b/src/utils/structs.sol @@ -57,6 +57,7 @@ struct UserDetails { } struct Tree { + uint256 id; uint256 latitude; uint256 longitude; uint256 planting; From 68a548b4102eae1f1b31fe1c5886c2cf5f1685da Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Fri, 15 Aug 2025 16:21:34 +0530 Subject: [PATCH 07/13] fix: fetch verification by id read function --- src/TreeNft.sol | 27 +++++++++++++++++---------- test/TreeNft.t.sol | 18 ++++++++++++++++-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/TreeNft.sol b/src/TreeNft.sol index e7fce80..1601176 100644 --- a/src/TreeNft.sol +++ b/src/TreeNft.sol @@ -228,16 +228,23 @@ contract TreeNft is ERC721, Ownable { } } - function removeVerification(uint256 _verificationId) public { - // This function enables the owner of the NFT to remove verifications as he pleases (in case of fraudalent verification spam) - - TreeNftVerification memory treeNftVerification = s_tokenIDtoTreeNftVerfication[_verificationId]; - if (msg.sender != ownerOf(treeNftVerification.treeNftId)) revert NotTreeOwner(); - treeNftVerification.isHidden = true; - User memory user = s_addressToUser[treeNftVerification.verifier]; - user.verificationsRevoked++; - s_addressToUser[treeNftVerification.verifier] = user; - emit VerificationRemoved(_verificationId, treeNftVerification.treeNftId, treeNftVerification.verifier); + function removeVerification(uint256 _tokenId, address verifier) public { + // Thsi function facilitates the owner of the tree nft to remove fraudulent verifiers + + if (msg.sender != ownerOf(_tokenId)) revert NotTreeOwner(); + uint256[] storage verificationIds = s_treeTokenIdToVerifications[_tokenId]; + for (uint256 i = 0; i < verificationIds.length; i++) { + TreeNftVerification storage treeNftVerification = s_tokenIDtoTreeNftVerfication[verificationIds[i]]; + if (treeNftVerification.verifier == verifier && !treeNftVerification.isHidden) { + treeNftVerification.isHidden = true; + + User storage user = s_addressToUser[verifier]; + user.verificationsRevoked++; + + emit VerificationRemoved(verificationIds[i], _tokenId, verifier); + break; + } + } } function getVerifiedTreesByUser(address verifier) public view returns (Tree[] memory) { diff --git a/test/TreeNft.t.sol b/test/TreeNft.t.sol index f7990df..f9df182 100644 --- a/test/TreeNft.t.sol +++ b/test/TreeNft.t.sol @@ -309,7 +309,21 @@ contract TreeNftTest is Test { vm.prank(user1); vm.expectEmit(true, true, true, false); emit TreeNft.VerificationRemoved(0, 0, verifier1); - treeNft.removeVerification(0); + treeNft.removeVerification(0, verifier1); + + TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); + assertEq(verifications.length, 0); + } + + function test_RemoveVerification2() public { + vm.prank(user1); + string[] memory initialPhotos = new string[](0); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); + + vm.prank(verifier1); + string[] memory proofHashes = new string[](1); + proofHashes[0] = "proof1"; + treeNft.verify(0, proofHashes, "Verification"); TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); assertEq(verifications.length, 1); @@ -327,7 +341,7 @@ contract TreeNftTest is Test { vm.prank(user2); vm.expectRevert(NotTreeOwner.selector); - treeNft.removeVerification(0); + treeNft.removeVerification(0, verifier1); } function test_GetVerifiedTreesByUser() public { From 517f4e4dcb2438fa793c46c0194d97e08f73f94f Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Sat, 20 Sep 2025 23:55:30 +0530 Subject: [PATCH 08/13] fix: minor bugs --- .DS_Store | Bin 8196 -> 8196 bytes script/DeployAllContracts.s.sol | 2 -- 2 files changed, 2 deletions(-) diff --git a/.DS_Store b/.DS_Store index d30b09ce2fdd4508045bee08faffd2b50b8cb550..bf772c717ed41140ecda3a237af5033de562d8df 100644 GIT binary patch delta 162 zcmZp1XmQw}DiG(;70bZDz`~%%kj{|FP?DSP;*yk;p9B=+*lZ-+RcmnE5mi0~uY5s< zVQ_MOZUIma19QcP&CLRHnB;f8vj%Y)a)34@A=@Ax%kgR1=E-)#5^PXrjIboroei7I Hg?o7bK1M95 delta 162 zcmZp1XmQw}Di9|;N0fnqfrUYjA)O(Up(Hoo#U&{xKM5$tv0&Xf{(^>Mj;Qh}c;yQ+ z41<&Na|?ia7?@=@Y;G2q!z90Y!fX(iAqQwf60!|!%SG8k8YbHbORzziF~X8ervx^a I3-|H>0C7AjZU6uP diff --git a/script/DeployAllContracts.s.sol b/script/DeployAllContracts.s.sol index 5fa7d32..9436e0d 100644 --- a/script/DeployAllContracts.s.sol +++ b/script/DeployAllContracts.s.sol @@ -26,8 +26,6 @@ contract DeployAllContractsAtOnce is Script { console.log("============================================\n"); vm.startBroadcast(deployerPrivateKey); - - // STEP 1: Deploy token contracts console.log(">> Step 1: Deploying ERC20 Token Contracts..."); CareToken careToken = new CareToken(deployer); From 2be0cb3ec32de063bc3c45e22b1fd7f703127f94 Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Sat, 11 Oct 2025 11:31:40 +0530 Subject: [PATCH 09/13] fix: verification logic --- src/TreeNft.sol | 64 +++++++++++++++++++--------- src/token-contracts/PlanterToken.sol | 9 +++- src/utils/errors.sol | 1 + src/utils/structs.sol | 15 ++++++- 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/TreeNft.sol b/src/TreeNft.sol index 1601176..7c99c4a 100644 --- a/src/TreeNft.sol +++ b/src/TreeNft.sol @@ -26,41 +26,35 @@ contract TreeNft is ERC721, Ownable { uint256 public minimumTimeToMarkTreeDead = 365 days; CareToken public careTokenContract; - PlanterToken public planterTokenContract; - VerifierToken public verifierTokenContract; LegacyToken public legacyToken; mapping(uint256 => Tree) private s_tokenIDtoTree; + mapping(uint256 => address) private s_tokenIDtoVerificationContracts; mapping(uint256 => address[]) private s_tokenIDtoVerifiers; mapping(address => uint256[]) private s_userToNFTs; + mapping(address => address) public s_userToPlanterTokenAddress; + mapping(address => address[]) public s_userToVerifierTokenAddresses; mapping(uint256 => mapping(address => bool)) private s_tokenIDtoUserVerification; mapping(address => uint256[]) private s_verifierToTokenIDs; mapping(uint256 => TreeNftVerification) private s_tokenIDtoTreeNftVerfication; mapping(uint256 => uint256[]) private s_treeTokenIdToVerifications; + mapping(address => TreeNftVerification[]) private s_userToVerifications; mapping(address => User) s_addressToUser; constructor( address _careTokenContract, - address _planterTokenContract, - address _verifierTokenContract, address _legacyTokenContract ) Ownable(msg.sender) ERC721("TreeNFT", "TREE") { + s_tokenCounter = 0; s_organisationCounter = 0; s_deathCounter = 0; s_treeNftVerification = 0; s_userCounter = 0; - if (_careTokenContract == address(0)) revert InvalidInput(); - if (_planterTokenContract == address(0)) revert InvalidInput(); - if (_verifierTokenContract == address(0)) revert InvalidInput(); - if (_legacyTokenContract == address(0)) revert InvalidInput(); - careTokenContract = CareToken(_careTokenContract); - planterTokenContract = PlanterToken(_planterTokenContract); - verifierTokenContract = VerifierToken(_verifierTokenContract); legacyToken = LegacyToken(_legacyTokenContract); } @@ -74,7 +68,8 @@ contract TreeNft is ERC721, Ownable { string memory qrIpfsHash, string memory metadata, string memory geoHash, - string[] memory initialPhotos + string[] memory initialPhotos, + uint256 numberOfTrees ) public { if (latitude > 180 * 1e6) revert InvalidCoordinates(); if (longitude > 360 * 1e6) revert InvalidCoordinates(); @@ -85,6 +80,8 @@ contract TreeNft is ERC721, Ownable { _mint(msg.sender, tokenId); address[] memory ancestors = new address[](1); ancestors[0] = msg.sender; + + s_tokenIDtoTree[tokenId] = Tree( tokenId, latitude, @@ -99,11 +96,11 @@ contract TreeNft is ERC721, Ownable { geoHash, ancestors, block.timestamp, - 0 + 0, + numberOfTrees ); s_userToNFTs[msg.sender].push(tokenId); - planterTokenContract.mint(msg.sender, 1); } function tokenURI(uint256 tokenId) public view override returns (string memory) { @@ -213,18 +210,29 @@ contract TreeNft is ERC721, Ownable { function verify(uint256 _tokenId, string[] memory _proofHashes, string memory _description) public { // This function allows a verifier to verify a tree + if (s_tokenIDtoUserVerification[_tokenId][msg.sender]) revert AlreadyVerified(); + if(s_userToPlanterTokenAddress[msg.sender] == address(0)) { + PlanterToken planterToken = new PlanterToken(msg.sender); + s_userToPlanterTokenAddress[msg.sender] = address(planterToken); + } + address planterTokenContract = s_userToPlanterTokenAddress[msg.sender]; + PlanterToken planterToken = PlanterToken(planterTokenContract); + if (!_exists(_tokenId)) revert InvalidTreeID(); - TreeNftVerification memory treeVerification = - TreeNftVerification(msg.sender, block.timestamp, _proofHashes, _description, false, _tokenId); + if (msg.sender == ownerOf(_tokenId)) revert CannotVerifyOwnTree(); if (!isVerified(_tokenId, msg.sender)) { + TreeNftVerification memory treeVerification = + TreeNftVerification(msg.sender, block.timestamp, _proofHashes, _description, false, _tokenId, planterTokenContract); + Tree memory tree = s_tokenIDtoTree[_tokenId]; s_tokenIDtoUserVerification[_tokenId][msg.sender] = true; s_tokenIDtoVerifiers[_tokenId].push(msg.sender); s_verifierToTokenIDs[msg.sender].push(_tokenId); s_tokenIDtoTreeNftVerfication[s_treeNftVerification] = treeVerification; s_treeTokenIdToVerifications[_tokenId].push(s_treeNftVerification); s_treeNftVerification++; - verifierTokenContract.mint(msg.sender, 1); - planterTokenContract.mint(ownerOf(_tokenId), 1); + planterToken.mint(ownerOf(_tokenId), tree.numberOfTrees); // give the planter tokens of the verifier to the owner of the tree + s_userToVerifierTokenAddresses[ownerOf(_tokenId)].push(planterTokenContract); + s_userToVerifications[msg.sender].push(treeVerification); } } @@ -383,12 +391,28 @@ contract TreeNft is ERC721, Ownable { userDetails.reportedSpam = storedUserDetails.reportedSpam; userDetails.verificationsRevoked = storedUserDetails.verificationsRevoked; userDetails.careTokens = careTokenContract.balanceOf(userAddress); - userDetails.planterTokens = planterTokenContract.balanceOf(userAddress); userDetails.legacyTokens = legacyToken.balanceOf(userAddress); - userDetails.verifierTokens = verifierTokenContract.balanceOf(userAddress); return userDetails; } + function getUserVerifierTokenDetails(address userAddress) public view returns (VerificationDetails[] memory verifierTokenDetails) { + // This function returns the verifier token address of the user + + TreeNftVerification[] memory userVerifications = s_userToVerifications[userAddress]; + for(uint i = 0; i < userVerifications.length; i++) { + PlanterToken planterToken = PlanterToken(userVerifications[i].verifierPlanterTokenAddress); + verifierTokenDetails[i] = VerificationDetails({ + verifier: userVerifications[i].verifier, + timestamp: userVerifications[i].timestamp, + proofHashes: userVerifications[i].proofHashes, + description: userVerifications[i].description, + isHidden: userVerifications[i].isHidden, + numberOfTrees: planterToken.balanceOf(userAddress), + verifierPlanterTokenAddress: userVerifications[i].verifierPlanterTokenAddress + }); + } + } + function updateUserDetails(string memory _name, string memory _profilePhotoHash) public { // This function enables a user to change his user details diff --git a/src/token-contracts/PlanterToken.sol b/src/token-contracts/PlanterToken.sol index d93e8c5..5927a7e 100644 --- a/src/token-contracts/PlanterToken.sol +++ b/src/token-contracts/PlanterToken.sol @@ -5,9 +5,16 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract PlanterToken is ERC20, Ownable { - constructor(address owner) Ownable(owner) ERC20("PlanterToken", "PRT") {} + address public planterAddress; + constructor(address _planter) Ownable(msg.sender) ERC20("PlanterToken", "PRT") { + planterAddress = _planter; + } function mint(address to, uint256 amount) external onlyOwner { _mint(to, amount); } + + function getPlanterAddress() external view returns (address) { + return planterAddress; + } } diff --git a/src/utils/errors.sol b/src/utils/errors.sol index ea183bd..34be9e2 100644 --- a/src/utils/errors.sol +++ b/src/utils/errors.sol @@ -43,6 +43,7 @@ error InvalidNameInput(); error InvalidTreeID(); error MinimumMarkDeadTimeNotReached(); error InvalidCoordinates(); +error CannotVerifyOwnTree(); /// User error UserAlreadyRegistered(); diff --git a/src/utils/structs.sol b/src/utils/structs.sol index 4beb58c..57d7ba0 100644 --- a/src/utils/structs.sol +++ b/src/utils/structs.sol @@ -21,6 +21,17 @@ struct TreeNftVerification { string description; bool isHidden; uint256 treeNftId; + address verifierPlanterTokenAddress; +} + +struct VerificationDetails { + address verifier; + uint256 timestamp; + string[] proofHashes; + string description; + bool isHidden; + uint256 numberOfTrees; + address verifierPlanterTokenAddress; } struct OrganisationVerificationRequest { @@ -50,12 +61,11 @@ struct UserDetails { uint256 dateJoined; uint256 verificationsRevoked; uint256 reportedSpam; - uint256 verifierTokens; - uint256 planterTokens; uint256 legacyTokens; uint256 careTokens; } + struct Tree { uint256 id; uint256 latitude; @@ -71,6 +81,7 @@ struct Tree { address[] ancestors; uint256 lastCareTimestamp; uint256 careCount; + uint256 numberOfTrees; } struct TreePlantingProposal { From 511853b566bab7ace7238d3da006661dd79c308c Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Sat, 11 Oct 2025 13:14:42 +0530 Subject: [PATCH 10/13] tests: updated verification process in TreeNFT.sol --- script/DeployAllContracts.s.sol | 22 +- script/DeployTreeNftContract.s.sol | 26 +- src/Organisation.sol | 12 +- src/TreeNft.sol | 49 +- src/token-contracts/VerifierToken.sol | 13 - src/utils/structs.sol | 1 + test/Organisation.t.sol | 21 +- test/OrganisationFactory.t.sol | 13 +- test/TreeNft.t.sol | 633 +++++++------------------- 9 files changed, 211 insertions(+), 579 deletions(-) delete mode 100644 src/token-contracts/VerifierToken.sol diff --git a/script/DeployAllContracts.s.sol b/script/DeployAllContracts.s.sol index 9436e0d..3650980 100644 --- a/script/DeployAllContracts.s.sol +++ b/script/DeployAllContracts.s.sol @@ -5,7 +5,6 @@ import "forge-std/Script.sol"; import "../src/TreeNft.sol"; import "../src/token-contracts/CareToken.sol"; import "../src/token-contracts/PlanterToken.sol"; -import "../src/token-contracts/VerifierToken.sol"; import "../src/token-contracts/LegacyToken.sol"; import "../src/OrganisationFactory.sol"; @@ -32,30 +31,18 @@ contract DeployAllContractsAtOnce is Script { careTokenAddress = address(careToken); console.log(" - CareToken deployed at: ", careTokenAddress); - PlanterToken planterToken = new PlanterToken(deployer); - planterTokenAddress = address(planterToken); - console.log(" - PlanterToken deployed at: ", planterTokenAddress); - - VerifierToken verifierToken = new VerifierToken(deployer); - verifierTokenAddress = address(verifierToken); - console.log(" - VerifierToken deployed at: ", verifierTokenAddress); - LegacyToken legacyToken = new LegacyToken(deployer); legacyTokenAddress = address(legacyToken); console.log(" - LegacyToken deployed at: ", legacyTokenAddress); console.log("\n>> Step 2: Deploying TreeNft Contract..."); - TreeNft treeNft = new TreeNft(careTokenAddress, planterTokenAddress, verifierTokenAddress, legacyTokenAddress); + TreeNft treeNft = new TreeNft(careTokenAddress,legacyTokenAddress); treeNftAddress = address(treeNft); console.log(" - TreeNft deployed at: ", treeNftAddress); console.log("\n>> Step 3: Transferring Token Ownership to TreeNft..."); careToken.transferOwnership(treeNftAddress); console.log(" - CareToken ownership transferred."); - planterToken.transferOwnership(treeNftAddress); - console.log(" - PlanterToken ownership transferred."); - verifierToken.transferOwnership(treeNftAddress); - console.log(" - VerifierToken ownership transferred."); legacyToken.transferOwnership(treeNftAddress); console.log(" - LegacyToken ownership transferred."); @@ -68,8 +55,6 @@ contract DeployAllContractsAtOnce is Script { console.log("\n========== DEPLOYMENT SUMMARY =========="); console.log("CareToken Address: ", careTokenAddress); - console.log("PlanterToken Address: ", planterTokenAddress); - console.log("VerifierToken Address: ", verifierTokenAddress); console.log("LegacyToken Address: ", legacyTokenAddress); console.log("TreeNft Address: ", treeNftAddress); console.log("OrganisationFactory: ", orgFactoryAddress); @@ -85,13 +70,14 @@ contract DeployAllContractsAtOnce is Script { TreeNft treeNft = TreeNft(treeNftAddress); require(address(treeNft.careTokenContract()) == careTokenAddress, "CareToken address mismatch"); - require(address(treeNft.planterTokenContract()) == planterTokenAddress, "PlanterToken address mismatch"); - require(address(treeNft.verifierTokenContract()) == verifierTokenAddress, "VerifierToken address mismatch"); require(address(treeNft.legacyToken()) == legacyTokenAddress, "LegacyToken address mismatch"); CareToken careToken = CareToken(careTokenAddress); require(careToken.owner() == treeNftAddress, "CareToken ownership not transferred"); + CareToken legacyToken = CareToken(legacyTokenAddress); + require(legacyToken.owner() == treeNftAddress, "LegacyToken ownership not transferred"); + console.log("Deployment verification passed.\n"); } } diff --git a/script/DeployTreeNftContract.s.sol b/script/DeployTreeNftContract.s.sol index 69ec602..14e2fb6 100644 --- a/script/DeployTreeNftContract.s.sol +++ b/script/DeployTreeNftContract.s.sol @@ -5,13 +5,10 @@ import "forge-std/Script.sol"; import "../src/TreeNft.sol"; import "../src/token-contracts/CareToken.sol"; import "../src/token-contracts/PlanterToken.sol"; -import "../src/token-contracts/VerifierToken.sol"; import "../src/token-contracts/LegacyToken.sol"; contract DeployTreeNft is Script { address public careTokenAddress; - address public planterTokenAddress; - address public verifierTokenAddress; address public legacyTokenAddress; address public treeNftAddress; @@ -29,36 +26,23 @@ contract DeployTreeNft is Script { careTokenAddress = address(careToken); console.log("CareToken deployed at:", careTokenAddress); - PlanterToken planterToken = new PlanterToken(deployer); - planterTokenAddress = address(planterToken); - console.log("PlanterToken deployed at:", planterTokenAddress); - - VerifierToken verifierToken = new VerifierToken(deployer); - verifierTokenAddress = address(verifierToken); - console.log("VerifierToken deployed at:", verifierTokenAddress); LegacyToken legacyToken = new LegacyToken(deployer); legacyTokenAddress = address(legacyToken); console.log("LegacyToken deployed at:", legacyTokenAddress); console.log("Step 2: Deploy TreeNft contract..."); - TreeNft treeNft = new TreeNft(careTokenAddress, planterTokenAddress, verifierTokenAddress, legacyTokenAddress); + TreeNft treeNft = new TreeNft(careTokenAddress, legacyTokenAddress); treeNftAddress = address(treeNft); console.log("TreeNft deployed at:", treeNftAddress); console.log("Step 3: Transfer ownership to TreeNft contract..."); careToken.transferOwnership(treeNftAddress); console.log("CareToken ownership transferred to TreeNft"); - planterToken.transferOwnership(treeNftAddress); - console.log("PlanterToken ownership transferred to TreeNft"); - verifierToken.transferOwnership(treeNftAddress); - console.log("VerifierToken ownership transferred to TreeNft"); legacyToken.transferOwnership(treeNftAddress); console.log("LegacyToken ownership transferred to TreeNft"); console.log("\n=== DEPLOYMENT SUMMARY ==="); console.log("CareToken:", careTokenAddress); - console.log("PlanterToken:", planterTokenAddress); - console.log("VerifierToken:", verifierTokenAddress); console.log("LegacyToken:", legacyTokenAddress); console.log("TreeNft:", treeNftAddress); console.log("All token ownerships transferred to TreeNft!"); @@ -71,19 +55,11 @@ contract DeployTreeNft is Script { TreeNft treeNft = TreeNft(treeNftAddress); require(address(treeNft.careTokenContract()) == careTokenAddress, "CareToken address mismatch"); - require(address(treeNft.planterTokenContract()) == planterTokenAddress, "PlanterToken address mismatch"); - require(address(treeNft.verifierTokenContract()) == verifierTokenAddress, "VerifierToken address mismatch"); require(address(treeNft.legacyToken()) == legacyTokenAddress, "LegacyToken address mismatch"); CareToken careToken = CareToken(careTokenAddress); if (careToken.owner() != treeNftAddress) revert OwnershipNotTransferred(); - PlanterToken planterToken = PlanterToken(planterTokenAddress); - if (planterToken.owner() != treeNftAddress) revert OwnershipNotTransferred(); - - VerifierToken verifierToken = VerifierToken(verifierTokenAddress); - if (verifierToken.owner() != treeNftAddress) revert OwnershipNotTransferred(); - LegacyToken legacyToken = LegacyToken(legacyTokenAddress); if (legacyToken.owner() != treeNftAddress) revert OwnershipNotTransferred(); diff --git a/src/Organisation.sol b/src/Organisation.sol index af19eec..97d89ae 100644 --- a/src/Organisation.sol +++ b/src/Organisation.sol @@ -260,7 +260,8 @@ contract Organisation { string memory _qrIpfshash, string memory _metadata, string[] memory photos, - string memory geoHash + string memory geoHash, + uint256 numberOfTrees ) public { if (_latitude > 180 * 1e6) revert InvalidCoordinates(); if (_longitude > 360 * 1e6) revert InvalidCoordinates(); @@ -276,7 +277,8 @@ contract Organisation { photos: photos, geoHash: geoHash, metadata: _metadata, - status: 0 + status: 0, + numberOfTrees: numberOfTrees }); if (checkOwnership(msg.sender)) { s_treeProposalYesVoters[s_treePlantingProposalCounter].push(msg.sender); @@ -290,7 +292,8 @@ contract Organisation { proposal.qrIpfsHash, proposal.metadata, proposal.geoHash, - proposal.photos + proposal.photos, + proposal.numberOfTrees ); } } @@ -403,7 +406,8 @@ contract Organisation { proposal.qrIpfsHash, proposal.metadata, proposal.geoHash, - proposal.photos + proposal.photos, + proposal.numberOfTrees ); } else if (s_treeProposalNoVoters[proposalID].length >= requiredVotes) { proposal.status = 2; diff --git a/src/TreeNft.sol b/src/TreeNft.sol index 7c99c4a..21c0e1f 100644 --- a/src/TreeNft.sol +++ b/src/TreeNft.sol @@ -15,13 +15,13 @@ import "./OrganisationFactory.sol"; import "./token-contracts/CareToken.sol"; import "./token-contracts/LegacyToken.sol"; import "./token-contracts/PlanterToken.sol"; -import "./token-contracts/VerifierToken.sol"; + contract TreeNft is ERC721, Ownable { - uint256 private s_tokenCounter; + uint256 private s_treeTokenCounter; uint256 private s_organisationCounter; uint256 private s_deathCounter; - uint256 private s_treeNftVerification; + uint256 private s_treeNftVerificationCounter; uint256 private s_userCounter; uint256 public minimumTimeToMarkTreeDead = 365 days; @@ -36,7 +36,7 @@ contract TreeNft is ERC721, Ownable { mapping(address => address[]) public s_userToVerifierTokenAddresses; mapping(uint256 => mapping(address => bool)) private s_tokenIDtoUserVerification; - mapping(address => uint256[]) private s_verifierToTokenIDs; + mapping(address => uint256[]) private s_verifierToTreeTokenIDs; mapping(uint256 => TreeNftVerification) private s_tokenIDtoTreeNftVerfication; mapping(uint256 => uint256[]) private s_treeTokenIdToVerifications; mapping(address => TreeNftVerification[]) private s_userToVerifications; @@ -48,10 +48,10 @@ contract TreeNft is ERC721, Ownable { address _legacyTokenContract ) Ownable(msg.sender) ERC721("TreeNFT", "TREE") { - s_tokenCounter = 0; + s_treeTokenCounter = 0; s_organisationCounter = 0; s_deathCounter = 0; - s_treeNftVerification = 0; + s_treeNftVerificationCounter = 0; s_userCounter = 0; careTokenContract = CareToken(_careTokenContract); @@ -74,8 +74,8 @@ contract TreeNft is ERC721, Ownable { if (latitude > 180 * 1e6) revert InvalidCoordinates(); if (longitude > 360 * 1e6) revert InvalidCoordinates(); - uint256 tokenId = s_tokenCounter; - s_tokenCounter++; + uint256 tokenId = s_treeTokenCounter; + s_treeTokenCounter++; _mint(msg.sender, tokenId); address[] memory ancestors = new address[](1); @@ -133,8 +133,8 @@ contract TreeNft is ERC721, Ownable { function getAllNFTs() public view returns (Tree[] memory) { // This function retrieves all NFTs in the contract - Tree[] memory allTrees = new Tree[](s_tokenCounter); - for (uint256 i = 0; i < s_tokenCounter; i++) { + Tree[] memory allTrees = new Tree[](s_treeTokenCounter); + for (uint256 i = 0; i < allTrees.length; i++) { allTrees[i] = s_tokenIDtoTree[i]; } return allTrees; @@ -148,7 +148,7 @@ contract TreeNft is ERC721, Ownable { // This function retrieves recent trees with pagination if (limit > 50) revert PaginationLimitExceeded(); - uint256 totalTrees = s_tokenCounter; + uint256 totalTrees = s_treeTokenCounter; if (offset >= totalTrees) return (new Tree[](0), totalTrees, false); uint256 available = totalTrees - offset; uint256 toReturn = available < limit ? available : limit; @@ -210,6 +210,11 @@ contract TreeNft is ERC721, Ownable { function verify(uint256 _tokenId, string[] memory _proofHashes, string memory _description) public { // This function allows a verifier to verify a tree + if (!_exists(_tokenId)) revert InvalidTreeID(); + address treeOwner = ownerOf(_tokenId); + Tree memory tree = s_tokenIDtoTree[_tokenId]; + if (msg.sender == treeOwner) revert CannotVerifyOwnTree(); + if (s_tokenIDtoUserVerification[_tokenId][msg.sender]) revert AlreadyVerified(); if(s_userToPlanterTokenAddress[msg.sender] == address(0)) { PlanterToken planterToken = new PlanterToken(msg.sender); @@ -218,26 +223,23 @@ contract TreeNft is ERC721, Ownable { address planterTokenContract = s_userToPlanterTokenAddress[msg.sender]; PlanterToken planterToken = PlanterToken(planterTokenContract); - if (!_exists(_tokenId)) revert InvalidTreeID(); - if (msg.sender == ownerOf(_tokenId)) revert CannotVerifyOwnTree(); if (!isVerified(_tokenId, msg.sender)) { TreeNftVerification memory treeVerification = TreeNftVerification(msg.sender, block.timestamp, _proofHashes, _description, false, _tokenId, planterTokenContract); - Tree memory tree = s_tokenIDtoTree[_tokenId]; s_tokenIDtoUserVerification[_tokenId][msg.sender] = true; s_tokenIDtoVerifiers[_tokenId].push(msg.sender); - s_verifierToTokenIDs[msg.sender].push(_tokenId); - s_tokenIDtoTreeNftVerfication[s_treeNftVerification] = treeVerification; - s_treeTokenIdToVerifications[_tokenId].push(s_treeNftVerification); - s_treeNftVerification++; - planterToken.mint(ownerOf(_tokenId), tree.numberOfTrees); // give the planter tokens of the verifier to the owner of the tree + s_verifierToTreeTokenIDs[msg.sender].push(_tokenId); + s_tokenIDtoTreeNftVerfication[s_treeNftVerificationCounter] = treeVerification; + s_treeTokenIdToVerifications[_tokenId].push(s_treeNftVerificationCounter); + s_treeNftVerificationCounter++; + planterToken.mint(ownerOf(_tokenId), tree.numberOfTrees); s_userToVerifierTokenAddresses[ownerOf(_tokenId)].push(planterTokenContract); s_userToVerifications[msg.sender].push(treeVerification); } } function removeVerification(uint256 _tokenId, address verifier) public { - // Thsi function facilitates the owner of the tree nft to remove fraudulent verifiers + // This function facilitates the owner of the tree nft to remove fraudulent verifiers if (msg.sender != ownerOf(_tokenId)) revert NotTreeOwner(); uint256[] storage verificationIds = s_treeTokenIdToVerifications[_tokenId]; @@ -248,7 +250,6 @@ contract TreeNft is ERC721, Ownable { User storage user = s_addressToUser[verifier]; user.verificationsRevoked++; - emit VerificationRemoved(verificationIds[i], _tokenId, verifier); break; } @@ -258,7 +259,7 @@ contract TreeNft is ERC721, Ownable { function getVerifiedTreesByUser(address verifier) public view returns (Tree[] memory) { // This function retrieves all trees verified by a specific verifier - uint256[] memory verifiedTokens = s_verifierToTokenIDs[verifier]; + uint256[] memory verifiedTokens = s_verifierToTreeTokenIDs[verifier]; Tree[] memory verifiedTrees = new Tree[](verifiedTokens.length); for (uint256 i = 0; i < verifiedTokens.length; i++) { uint256 tokenId = verifiedTokens[i]; @@ -274,7 +275,7 @@ contract TreeNft is ERC721, Ownable { { // Get the total number of trees verified by this verifier - uint256[] memory verifiedTokens = s_verifierToTokenIDs[verifier]; + uint256[] memory verifiedTokens = s_verifierToTreeTokenIDs[verifier]; totalCount = verifiedTokens.length; if (offset >= totalCount) { return (new Tree[](0), totalCount); @@ -428,7 +429,7 @@ contract TreeNft is ERC721, Ownable { } function _exists(uint256 tokenId) internal view returns (bool) { - return tokenId < s_tokenCounter && tokenId >= 0; + return tokenId < s_treeTokenCounter && tokenId >= 0; } function ping() public pure returns (string memory) { diff --git a/src/token-contracts/VerifierToken.sol b/src/token-contracts/VerifierToken.sol deleted file mode 100644 index 96c07f7..0000000 --- a/src/token-contracts/VerifierToken.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -contract VerifierToken is ERC20, Ownable { - constructor(address owner) Ownable(owner) ERC20("VerifierToken", "VRT") {} - - function mint(address to, uint256 amount) external onlyOwner { - _mint(to, amount); - } -} diff --git a/src/utils/structs.sol b/src/utils/structs.sol index 57d7ba0..7f0f413 100644 --- a/src/utils/structs.sol +++ b/src/utils/structs.sol @@ -95,4 +95,5 @@ struct TreePlantingProposal { string geoHash; string metadata; uint256 status; + uint256 numberOfTrees; } diff --git a/test/Organisation.t.sol b/test/Organisation.t.sol index 86c548c..fe09689 100644 --- a/test/Organisation.t.sol +++ b/test/Organisation.t.sol @@ -12,7 +12,6 @@ import "../src/TreeNft.sol"; import "../src/token-contracts/CareToken.sol"; import "../src/token-contracts/PlanterToken.sol"; -import "../src/token-contracts/VerifierToken.sol"; import "../src/token-contracts/LegacyToken.sol"; contract OrganisationTest is Test { @@ -20,7 +19,6 @@ contract OrganisationTest is Test { TreeNft private treeNft; CareToken public careToken; PlanterToken public planterToken; - VerifierToken public verifierToken; LegacyToken public legacyToken; address private owner = address(0x1); @@ -42,32 +40,27 @@ contract OrganisationTest is Test { string constant PHOTO_IPFS_HASH2 = "QmTestPhotoHash"; string constant JOIN_REQUEST_DESCRIPTION = "I want to join this organisation"; string constant METADATA = "ipfs://metaDataHash"; + uint256 constant NUMBER_OF_TREES = 1; function setUp() public { vm.startPrank(owner); careToken = new CareToken(owner); planterToken = new PlanterToken(owner); - verifierToken = new VerifierToken(owner); legacyToken = new LegacyToken(owner); - treeNft = new TreeNft(address(careToken), address(planterToken), address(verifierToken), address(legacyToken)); + treeNft = new TreeNft(address(careToken), address(legacyToken)); careToken.transferOwnership(address(treeNft)); planterToken.transferOwnership(address(treeNft)); - verifierToken.transferOwnership(address(treeNft)); legacyToken.transferOwnership(address(treeNft)); vm.stopPrank(); assertEq(careToken.owner(), address(treeNft)); - assertEq(planterToken.owner(), address(treeNft)); - assertEq(verifierToken.owner(), address(treeNft)); assertEq(legacyToken.owner(), address(treeNft)); assertEq(address(treeNft.careTokenContract()), address(careToken)); - assertEq(address(treeNft.planterTokenContract()), address(planterToken)); - assertEq(address(treeNft.verifierTokenContract()), address(verifierToken)); assertEq(address(treeNft.legacyToken()), address(legacyToken)); vm.startPrank(owner); @@ -159,7 +152,7 @@ contract OrganisationTest is Test { imageHashes[0] = "QmProofHash"; vm.prank(user3); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES); vm.stopPrank(); vm.prank(user2); @@ -191,7 +184,7 @@ contract OrganisationTest is Test { imageHashes[0] = "QmProofHash"; vm.prank(user3); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES); vm.stopPrank(); vm.prank(user1); @@ -241,7 +234,7 @@ contract OrganisationTest is Test { vm.prank(user1); Organisation(orgAddress).plantTreeProposal( - LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, proofHashes, GEOHASH + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, proofHashes, GEOHASH, NUMBER_OF_TREES ); vm.stopPrank(); @@ -271,7 +264,7 @@ contract OrganisationTest is Test { imageHashes[0] = "QmProofHash"; vm.prank(user3); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES); vm.stopPrank(); vm.prank(user1); @@ -294,7 +287,7 @@ contract OrganisationTest is Test { string[] memory proofHashes = new string[](1); proofHashes[0] = "QmProofHash"; Organisation(orgAddress).plantTreeProposal( - LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, proofHashes, GEOHASH + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, proofHashes, GEOHASH, NUMBER_OF_TREES ); vm.stopPrank(); diff --git a/test/OrganisationFactory.t.sol b/test/OrganisationFactory.t.sol index 142f665..3a42b8d 100644 --- a/test/OrganisationFactory.t.sol +++ b/test/OrganisationFactory.t.sol @@ -12,15 +12,12 @@ import "../src/TreeNft.sol"; import "../src/token-contracts/CareToken.sol"; import "../src/token-contracts/PlanterToken.sol"; -import "../src/token-contracts/VerifierToken.sol"; import "../src/token-contracts/LegacyToken.sol"; contract OrganisationFactoryTest is Test { OrganisationFactory private factory; TreeNft private treeNft; CareToken public careToken; - PlanterToken public planterToken; - VerifierToken public verifierToken; LegacyToken public legacyToken; address private owner = address(0x1); @@ -40,27 +37,19 @@ contract OrganisationFactoryTest is Test { vm.startPrank(owner); careToken = new CareToken(owner); - planterToken = new PlanterToken(owner); - verifierToken = new VerifierToken(owner); legacyToken = new LegacyToken(owner); - treeNft = new TreeNft(address(careToken), address(planterToken), address(verifierToken), address(legacyToken)); + treeNft = new TreeNft(address(careToken), address(legacyToken)); careToken.transferOwnership(address(treeNft)); - planterToken.transferOwnership(address(treeNft)); - verifierToken.transferOwnership(address(treeNft)); legacyToken.transferOwnership(address(treeNft)); vm.stopPrank(); assertEq(careToken.owner(), address(treeNft)); - assertEq(planterToken.owner(), address(treeNft)); - assertEq(verifierToken.owner(), address(treeNft)); assertEq(legacyToken.owner(), address(treeNft)); assertEq(address(treeNft.careTokenContract()), address(careToken)); - assertEq(address(treeNft.planterTokenContract()), address(planterToken)); - assertEq(address(treeNft.verifierTokenContract()), address(verifierToken)); assertEq(address(treeNft.legacyToken()), address(legacyToken)); vm.startPrank(owner); diff --git a/test/TreeNft.t.sol b/test/TreeNft.t.sol index f9df182..22de26f 100644 --- a/test/TreeNft.t.sol +++ b/test/TreeNft.t.sol @@ -1,553 +1,248 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import "forge-std/Test.sol"; +import "../lib/forge-std/src/Test.sol"; import "../src/TreeNft.sol"; import "../src/token-contracts/CareToken.sol"; -import "../src/token-contracts/PlanterToken.sol"; -import "../src/token-contracts/VerifierToken.sol"; import "../src/token-contracts/LegacyToken.sol"; +import "../src/token-contracts/PlanterToken.sol"; import "../src/utils/structs.sol"; import "../src/utils/errors.sol"; -contract TreeNftTest is Test { +contract TreeNftVerificationTest is Test { TreeNft public treeNft; CareToken public careToken; - PlanterToken public planterToken; - VerifierToken public verifierToken; LegacyToken public legacyToken; address public owner = address(0x1); - address public user1 = address(0x2); - address public user2 = address(0x3); - address public verifier1 = address(0x4); - address public verifier2 = address(0x5); - address public organisation = address(0x6); - - uint256 constant LATITUDE = 1234567; - uint256 constant LONGITUDE = 9876543; - string constant SPECIES = "Oak Tree"; - string constant IMAGE_URI = "ipfs://QmSampleImageHash"; - string constant METADATA = "ipfs://metaDataHash"; - string constant QR_IPFS_HASH = "QmSampleQRHash"; - string constant GEO_HASH = "u4pruydqqvj"; + address public planter = address(0x2); + address public verifier1 = address(0x3); + address public verifier2 = address(0x4); + + uint256 public constant LATITUDE = 45 * 1e6; + uint256 public constant LONGITUDE = 90 * 1e6; + string public constant SPECIES = "Oak"; + string public constant IMAGE_URI = "ipfs://image"; + string public constant QR_HASH = "ipfs://qr"; + string public constant METADATA = "metadata"; + string public constant GEOHASH = "geohash"; + uint256 public constant NUM_TREES = 10; function setUp() public { vm.startPrank(owner); - careToken = new CareToken(owner); - planterToken = new PlanterToken(owner); - verifierToken = new VerifierToken(owner); legacyToken = new LegacyToken(owner); - - treeNft = new TreeNft(address(careToken), address(planterToken), address(verifierToken), address(legacyToken)); - + treeNft = new TreeNft(address(careToken), address(legacyToken)); careToken.transferOwnership(address(treeNft)); - planterToken.transferOwnership(address(treeNft)); - verifierToken.transferOwnership(address(treeNft)); legacyToken.transferOwnership(address(treeNft)); - - vm.stopPrank(); - - assertEq(careToken.owner(), address(treeNft)); - assertEq(planterToken.owner(), address(treeNft)); - assertEq(verifierToken.owner(), address(treeNft)); - assertEq(legacyToken.owner(), address(treeNft)); - - assertEq(address(treeNft.careTokenContract()), address(careToken)); - assertEq(address(treeNft.planterTokenContract()), address(planterToken)); - assertEq(address(treeNft.verifierTokenContract()), address(verifierToken)); - assertEq(address(treeNft.legacyToken()), address(legacyToken)); - } - - function test_MintNft() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](2); - initialPhotos[0] = "photo1.jpg"; - initialPhotos[1] = "photo2.jpg"; - - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - - assertEq(treeNft.ownerOf(0), user1); - assertEq(treeNft.balanceOf(user1), 1); - - Tree memory tree = treeNft.getTreeDetailsbyID(0); - assertEq(tree.latitude, LATITUDE); - assertEq(tree.longitude, LONGITUDE); - assertEq(tree.species, SPECIES); - assertEq(tree.imageUri, IMAGE_URI); - assertEq(tree.qrIpfsHash, QR_IPFS_HASH); - assertEq(tree.geoHash, GEO_HASH); - assertEq(tree.death, type(uint256).max); - assertEq(tree.ancestors[0], user1); - assertEq(tree.photos.length, 2); - assertEq(tree.photos[0], "photo1.jpg"); - assertEq(tree.photos[1], "photo2.jpg"); - } - - function test_MintMultipleNfts() public { - vm.startPrank(user1); - - string[] memory initialPhotos = new string[](0); - - // Mint first NFT - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - - // Mint second NFT - treeNft.mintNft( - LATITUDE + 1000, - LONGITUDE + 1000, - "Pine Tree", - "ipfs://QmPineImage", - "QmPineQR", - METADATA, - "u4pruydqqvk", - initialPhotos - ); - vm.stopPrank(); - - assertEq(treeNft.balanceOf(user1), 2); - assertEq(treeNft.ownerOf(0), user1); - assertEq(treeNft.ownerOf(1), user1); - Tree[] memory userTrees = treeNft.getNFTsByUser(user1); - assertEq(userTrees.length, 2); - assertEq(userTrees[0].species, SPECIES); - assertEq(userTrees[1].species, "Pine Tree"); - } - - function test_TokenURI() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - - string memory uri = treeNft.tokenURI(0); - assertTrue(bytes(uri).length > 0); - assertEq( - keccak256(abi.encodePacked(substring(uri, 0, 29))), - keccak256(abi.encodePacked("data:application/json;base64,")) - ); - } - - function test_TokenURIInvalidToken() public { - vm.expectRevert(InvalidTreeID.selector); - treeNft.tokenURI(999); } - function test_GetAllNFTs() public { - vm.startPrank(user1); - string[] memory initialPhotos = new string[](0); - - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - treeNft.mintNft(LATITUDE + 1, LONGITUDE + 1, "Pine", IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - vm.stopPrank(); - - Tree[] memory allTrees = treeNft.getAllNFTs(); - assertEq(allTrees.length, 2); - assertEq(allTrees[0].species, SPECIES); - assertEq(allTrees[1].species, "Pine"); - } + function test_verifyTree() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); - function test_GetRecentTreesPaginated() public { - vm.startPrank(user1); - string[] memory initialPhotos = new string[](0); - - // Mint 5 trees - for (uint256 i = 0; i < 5; i++) { - treeNft.mintNft( - LATITUDE + i, - LONGITUDE + i, - string(abi.encodePacked("Tree", vm.toString(i))), - IMAGE_URI, - QR_IPFS_HASH, - METADATA, - GEO_HASH, - initialPhotos - ); - } - vm.stopPrank(); - (Tree[] memory trees, uint256 totalCount, bool hasMore) = treeNft.getRecentTreesPaginated(0, 3); - - assertEq(trees.length, 3); - assertEq(totalCount, 5); - assertTrue(hasMore); - assertEq(trees[0].species, "Tree4"); - assertEq(trees[1].species, "Tree3"); - assertEq(trees[2].species, "Tree2"); - (Tree[] memory remainingTrees, uint256 totalCount2, bool hasMore2) = treeNft.getRecentTreesPaginated(3, 3); - - assertEq(remainingTrees.length, 2); - assertEq(totalCount2, 5); - assertFalse(hasMore2); - assertEq(remainingTrees[0].species, "Tree1"); - assertEq(remainingTrees[1].species, "Tree0"); - } + vm.prank(verifier1); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified"); - function test_GetRecentTreesPaginatedLimitExceeded() public { - vm.expectRevert(PaginationLimitExceeded.selector); - treeNft.getRecentTreesPaginated(0, 51); + assertTrue(treeNft.isVerified(0, verifier1)); + + address planterTokenAddr = treeNft.s_userToPlanterTokenAddress(verifier1); + PlanterToken planterToken = PlanterToken(planterTokenAddr); + assertEq(planterToken.balanceOf(planter), NUM_TREES); } - function testGetNFTsByUserPaginated() public { - vm.startPrank(user1); - string[] memory initialPhotos = new string[](0); - for (uint256 i = 0; i < 4; i++) { - treeNft.mintNft( - LATITUDE + i, - LONGITUDE + i, - string(abi.encodePacked("UserTree", vm.toString(i))), - IMAGE_URI, - QR_IPFS_HASH, - METADATA, - GEO_HASH, - initialPhotos - ); - } - vm.stopPrank(); - (Tree[] memory trees, uint256 totalCount) = treeNft.getNFTsByUserPaginated(user1, 0, 2); - - assertEq(trees.length, 2); - assertEq(totalCount, 4); - assertEq(trees[0].species, "UserTree0"); - assertEq(trees[1].species, "UserTree1"); + function test_cannotVerifyOwnTree() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); - (Tree[] memory remainingTrees, uint256 totalCount2) = treeNft.getNFTsByUserPaginated(user1, 2, 2); - - assertEq(remainingTrees.length, 2); - assertEq(totalCount2, 4); - assertEq(remainingTrees[0].species, "UserTree2"); - assertEq(remainingTrees[1].species, "UserTree3"); - } - - function test_GetTreeDetailsbyIDInvalid() public { - vm.expectRevert(InvalidTreeID.selector); - treeNft.getTreeDetailsbyID(999); + vm.prank(planter); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + vm.expectRevert(CannotVerifyOwnTree.selector); + treeNft.verify(0, proofs, "verified"); } - function test_Verify() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); + function test_cannotVerifyTwice() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); vm.prank(verifier1); - string[] memory proofHashes = new string[](2); - proofHashes[0] = "proof1"; - proofHashes[1] = "proof2"; - - treeNft.verify(0, proofHashes, "Tree looks healthy"); - - assertTrue(treeNft.isVerified(0, verifier1)); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified"); - TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); - assertEq(verifications.length, 1); - assertEq(verifications[0].verifier, verifier1); - assertEq(verifications[0].description, "Tree looks healthy"); - assertEq(verifications[0].proofHashes.length, 2); - assertEq(verifications[0].proofHashes[0], "proof1"); - assertEq(verifications[0].proofHashes[1], "proof2"); - assertFalse(verifications[0].isHidden); - } - - function test_VerifyInvalidToken() public { vm.prank(verifier1); - string[] memory proofHashes = new string[](0); - - vm.expectRevert(InvalidTreeID.selector); - treeNft.verify(999, proofHashes, "Invalid tree"); + vm.expectRevert(AlreadyVerified.selector); + treeNft.verify(0, proofs, "verified again"); } - function test_VerifyTwiceSameVerifier() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - - vm.startPrank(verifier1); - string[] memory proofHashes = new string[](1); - proofHashes[0] = "proof1"; + function test_multipleVerifiers() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); - treeNft.verify(0, proofHashes, "First verification"); - treeNft.verify(0, proofHashes, "Second verification"); - vm.stopPrank(); - - TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); - assertEq(verifications.length, 1); - assertEq(verifications[0].description, "First verification"); - } - - function test_MultipleVerifiers() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); vm.prank(verifier1); - string[] memory proofHashes1 = new string[](1); - proofHashes1[0] = "proof1"; - treeNft.verify(0, proofHashes1, "Verifier 1 says OK"); + string[] memory proofs1 = new string[](1); + proofs1[0] = "proof1"; + treeNft.verify(0, proofs1, "verified by v1"); vm.prank(verifier2); - string[] memory proofHashes2 = new string[](1); - proofHashes2[0] = "proof2"; - treeNft.verify(0, proofHashes2, "Verifier 2 says OK"); + string[] memory proofs2 = new string[](1); + proofs2[0] = "proof2"; + treeNft.verify(0, proofs2, "verified by v2"); assertTrue(treeNft.isVerified(0, verifier1)); assertTrue(treeNft.isVerified(0, verifier2)); - TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); - assertEq(verifications.length, 2); + address planterToken1 = treeNft.s_userToPlanterTokenAddress(verifier1); + address planterToken2 = treeNft.s_userToPlanterTokenAddress(verifier2); + + assertEq(PlanterToken(planterToken1).balanceOf(planter), NUM_TREES); + assertEq(PlanterToken(planterToken2).balanceOf(planter), NUM_TREES); } - function test_RemoveVerification() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); + function test_removeVerification() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); vm.prank(verifier1); - string[] memory proofHashes = new string[](1); - proofHashes[0] = "proof1"; - treeNft.verify(0, proofHashes, "Verification"); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified"); - vm.prank(user1); - vm.expectEmit(true, true, true, false); - emit TreeNft.VerificationRemoved(0, 0, verifier1); + vm.prank(planter); treeNft.removeVerification(0, verifier1); TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); assertEq(verifications.length, 0); } - function test_RemoveVerification2() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); + function test_onlyOwnerCanRemoveVerification() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); vm.prank(verifier1); - string[] memory proofHashes = new string[](1); - proofHashes[0] = "proof1"; - treeNft.verify(0, proofHashes, "Verification"); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified"); - TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); - assertEq(verifications.length, 1); + vm.prank(verifier2); + vm.expectRevert(NotTreeOwner.selector); + treeNft.removeVerification(0, verifier1); } - function test_RemoveVerificationNotOwner() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); + function test_getTreeNftVerifiers() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); vm.prank(verifier1); - string[] memory proofHashes = new string[](1); - proofHashes[0] = "proof1"; - treeNft.verify(0, proofHashes, "Verification"); - - vm.prank(user2); - vm.expectRevert(NotTreeOwner.selector); - treeNft.removeVerification(0, verifier1); - } + string[] memory proofs1 = new string[](1); + proofs1[0] = "proof1"; + treeNft.verify(0, proofs1, "verified by v1"); - function test_GetVerifiedTreesByUser() public { - vm.startPrank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, "Tree1", IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - treeNft.mintNft( - LATITUDE + 1, LONGITUDE + 1, "Tree2", IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos - ); - vm.stopPrank(); + vm.prank(verifier2); + string[] memory proofs2 = new string[](1); + proofs2[0] = "proof2"; + treeNft.verify(0, proofs2, "verified by v2"); - vm.startPrank(verifier1); - string[] memory proofHashes = new string[](1); - proofHashes[0] = "proof"; - treeNft.verify(0, proofHashes, "OK"); - treeNft.verify(1, proofHashes, "OK"); - vm.stopPrank(); + TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); + assertEq(verifications.length, 2); + assertEq(verifications[0].verifier, verifier1); + assertEq(verifications[1].verifier, verifier2); + } + + // THE FIXED VERSION OF THE FAILING TEST + function test_getVerifiedTreesByUser() public { + string[] memory photos = new string[](1); + photos[0] = "photo1"; + + // Mint first tree - SEPARATE transaction + vm.prank(planter); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + // Mint second tree - SEPARATE transaction with NEW prank + vm.prank(planter); + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + + // Verify first tree - SEPARATE transaction + vm.prank(verifier1); + treeNft.verify(0, proofs, "verified tree 0"); + + // Verify second tree - SEPARATE transaction with NEW prank + vm.prank(verifier1); + treeNft.verify(1, proofs, "verified tree 1"); + // Get verified trees - no prank needed for view function Tree[] memory verifiedTrees = treeNft.getVerifiedTreesByUser(verifier1); + assertEq(verifiedTrees.length, 2); - assertEq(verifiedTrees[0].species, "Tree1"); - assertEq(verifiedTrees[1].species, "Tree2"); + assertEq(verifiedTrees[0].id, 0); + assertEq(verifiedTrees[1].id, 1); } - function test_GetVerifiedTreesByUserPaginated() public { - vm.startPrank(user1); - string[] memory initialPhotos = new string[](0); - for (uint256 i = 0; i < 5; i++) { - treeNft.mintNft( - LATITUDE + i, - LONGITUDE + i, - string(abi.encodePacked("Tree", vm.toString(i))), - IMAGE_URI, - QR_IPFS_HASH, - METADATA, - GEO_HASH, - initialPhotos - ); - } - vm.stopPrank(); - vm.startPrank(verifier1); - string[] memory proofHashes = new string[](1); - proofHashes[0] = "proof"; - for (uint256 i = 0; i < 5; i++) { - treeNft.verify(i, proofHashes, "OK"); - } - vm.stopPrank(); - - (Tree[] memory trees, uint256 totalCount) = treeNft.getVerifiedTreesByUserPaginated(verifier1, 0, 3); - - assertEq(trees.length, 3); - assertEq(totalCount, 5); - assertEq(trees[0].species, "Tree0"); - assertEq(trees[1].species, "Tree1"); - assertEq(trees[2].species, "Tree2"); - } + function test_verificationIncreasesRevocationCounter() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); - function test_GetTreeNftVerifiersPaginated() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, GEO_HASH, METADATA, initialPhotos); - - string[] memory proofHashes = new string[](1); - proofHashes[0] = "proof"; - - address[] memory verifiers = new address[](5); - verifiers[0] = address(0x10); - verifiers[1] = address(0x11); - verifiers[2] = address(0x12); - verifiers[3] = address(0x13); - verifiers[4] = address(0x14); - - for (uint256 i = 0; i < 5; i++) { - vm.prank(verifiers[i]); - treeNft.verify(0, proofHashes, string(abi.encodePacked("Verification", vm.toString(i)))); - } - - (TreeNftVerification[] memory verifications, uint256 totalCount, uint256 visibleCount) = - treeNft.getTreeNftVerifiersPaginated(0, 0, 3); - - assertEq(verifications.length, 3); - assertEq(totalCount, 5); - assertEq(visibleCount, 5); - assertEq(verifications[0].verifier, verifiers[0]); - assertEq(verifications[1].verifier, verifiers[1]); - assertEq(verifications[2].verifier, verifiers[2]); - } + vm.prank(verifier1); + treeNft.registerUserProfile("Verifier1", "ipfs://profile"); - function test_MarkDead() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - vm.warp(block.timestamp + 366 days); - vm.prank(user1); - treeNft.markDead(0); - - Tree memory tree = treeNft.getTreeDetailsbyID(0); - assertTrue(tree.death != type(uint256).max); - assertEq(tree.death, block.timestamp); - } + vm.prank(verifier1); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified"); - function testMarkDeadNotOwner() public { - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); + vm.prank(planter); + treeNft.removeVerification(0, verifier1); - vm.prank(user2); - vm.expectRevert(NotTreeOwner.selector); - treeNft.markDead(0); + UserDetails memory userDetails = treeNft.getUserProfile(verifier1); + assertEq(userDetails.verificationsRevoked, 1); } - function testMarkDeadInvalidToken() public { - vm.prank(user1); + function test_cannotVerifyInvalidTree() public { + vm.prank(verifier1); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; vm.expectRevert(InvalidTreeID.selector); - treeNft.markDead(999); + treeNft.verify(999, proofs, "verified"); } - function testMarkDeadAlreadyDead() public { - vm.startPrank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - vm.warp(block.timestamp + 366 days); - treeNft.markDead(0); + function test_planterTokenCreatedOnFirstVerification() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); - vm.expectRevert(TreeAlreadyDead.selector); - treeNft.markDead(0); - vm.stopPrank(); - } - - function testRegisterUserProfile() public { - vm.prank(user1); - treeNft.registerUserProfile("John Doe", "QmProfileHash"); - } - - function testRegisterUserProfileAlreadyRegistered() public { - vm.startPrank(user1); - treeNft.registerUserProfile("John Doe", "QmProfileHash"); - - vm.expectRevert(UserAlreadyRegistered.selector); - treeNft.registerUserProfile("Jane Doe", "QmProfileHash2"); - vm.stopPrank(); - } - - function testUpdateUserDetails() public { - vm.startPrank(user1); - treeNft.registerUserProfile("John Doe", "QmProfileHash"); - treeNft.updateUserDetails("John Smith", "QmNewProfileHash"); - vm.stopPrank(); - } - - function testUpdateUserDetailsNotRegistered() public { - vm.prank(user1); - vm.expectRevert(UserNotRegistered.selector); - treeNft.updateUserDetails("John Doe", "QmProfileHash"); - } - - /// Helper functions - function substring(string memory str, uint256 startIndex, uint256 endIndex) internal pure returns (string memory) { - bytes memory strBytes = bytes(str); - bytes memory result = new bytes(endIndex - startIndex); - for (uint256 i = startIndex; i < endIndex; i++) { - result[i - startIndex] = strBytes[i]; - } - return string(result); - } - - function testCompleteWorkflow() public { - vm.prank(user1); - treeNft.registerUserProfile("Tree Planter", "QmPlanterHash"); + address planterTokenBefore = treeNft.s_userToPlanterTokenAddress(verifier1); + assertEq(planterTokenBefore, address(0)); vm.prank(verifier1); - treeNft.registerUserProfile("Tree Verifier", "QmVerifierHash"); - - vm.prank(user1); - string[] memory initialPhotos = new string[](1); - initialPhotos[0] = "initial_photo.jpg"; - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - - vm.prank(verifier1); - string[] memory proofHashes = new string[](1); - proofHashes[0] = "verification_proof.jpg"; - treeNft.verify(0, proofHashes, "Tree is healthy and growing well"); - - Tree memory tree = treeNft.getTreeDetailsbyID(0); - assertEq(tree.species, SPECIES); - assertEq(treeNft.ownerOf(0), user1); - assertTrue(treeNft.isVerified(0, verifier1)); - - TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); - assertEq(verifications.length, 1); - assertEq(verifications[0].verifier, verifier1); - - vm.prank(user1); - vm.warp(block.timestamp + 366 days); - treeNft.markDead(0); - - Tree memory deadTree = treeNft.getTreeDetailsbyID(0); - assertTrue(deadTree.death != type(uint256).max); - } + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified"); - function testGasOptimization() public { - uint256 gasBefore = gasleft(); - vm.prank(user1); - string[] memory initialPhotos = new string[](0); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEO_HASH, initialPhotos); - uint256 gasUsed = gasBefore - gasleft(); - assertTrue(gasUsed < 500000, "Minting uses too much gas"); + address planterTokenAfter = treeNft.s_userToPlanterTokenAddress(verifier1); + assertTrue(planterTokenAfter != address(0)); } -} +} \ No newline at end of file From d096df0b44cfa2efa57c9f75ea8e10c1e10d5f8c Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Sat, 11 Oct 2025 18:40:02 +0530 Subject: [PATCH 11/13] fix: removeVerification in treeNFT.sol --- script/DeployAllContracts.s.sol | 2 +- script/DeployTreeNftContract.s.sol | 1 - src/TreeNft.sol | 86 +++++++-- src/token-contracts/PlanterToken.sol | 5 + src/utils/errors.sol | 1 + src/utils/structs.sol | 5 +- test/Organisation.t.sol | 12 +- test/TreeNft.t.sol | 267 +++++++++++++++++++++++++-- 8 files changed, 340 insertions(+), 39 deletions(-) diff --git a/script/DeployAllContracts.s.sol b/script/DeployAllContracts.s.sol index 3650980..b82f940 100644 --- a/script/DeployAllContracts.s.sol +++ b/script/DeployAllContracts.s.sol @@ -36,7 +36,7 @@ contract DeployAllContractsAtOnce is Script { console.log(" - LegacyToken deployed at: ", legacyTokenAddress); console.log("\n>> Step 2: Deploying TreeNft Contract..."); - TreeNft treeNft = new TreeNft(careTokenAddress,legacyTokenAddress); + TreeNft treeNft = new TreeNft(careTokenAddress, legacyTokenAddress); treeNftAddress = address(treeNft); console.log(" - TreeNft deployed at: ", treeNftAddress); diff --git a/script/DeployTreeNftContract.s.sol b/script/DeployTreeNftContract.s.sol index 14e2fb6..63bf833 100644 --- a/script/DeployTreeNftContract.s.sol +++ b/script/DeployTreeNftContract.s.sol @@ -26,7 +26,6 @@ contract DeployTreeNft is Script { careTokenAddress = address(careToken); console.log("CareToken deployed at:", careTokenAddress); - LegacyToken legacyToken = new LegacyToken(deployer); legacyTokenAddress = address(legacyToken); console.log("LegacyToken deployed at:", legacyTokenAddress); diff --git a/src/TreeNft.sol b/src/TreeNft.sol index 21c0e1f..c4efc75 100644 --- a/src/TreeNft.sol +++ b/src/TreeNft.sol @@ -16,7 +16,6 @@ import "./token-contracts/CareToken.sol"; import "./token-contracts/LegacyToken.sol"; import "./token-contracts/PlanterToken.sol"; - contract TreeNft is ERC721, Ownable { uint256 private s_treeTokenCounter; uint256 private s_organisationCounter; @@ -43,11 +42,10 @@ contract TreeNft is ERC721, Ownable { mapping(address => User) s_addressToUser; - constructor( - address _careTokenContract, - address _legacyTokenContract - ) Ownable(msg.sender) ERC721("TreeNFT", "TREE") { - + constructor(address _careTokenContract, address _legacyTokenContract) + Ownable(msg.sender) + ERC721("TreeNFT", "TREE") + { s_treeTokenCounter = 0; s_organisationCounter = 0; s_deathCounter = 0; @@ -81,7 +79,6 @@ contract TreeNft is ERC721, Ownable { address[] memory ancestors = new address[](1); ancestors[0] = msg.sender; - s_tokenIDtoTree[tokenId] = Tree( tokenId, latitude, @@ -215,8 +212,10 @@ contract TreeNft is ERC721, Ownable { Tree memory tree = s_tokenIDtoTree[_tokenId]; if (msg.sender == treeOwner) revert CannotVerifyOwnTree(); - if (s_tokenIDtoUserVerification[_tokenId][msg.sender]) revert AlreadyVerified(); - if(s_userToPlanterTokenAddress[msg.sender] == address(0)) { + if (s_tokenIDtoUserVerification[_tokenId][msg.sender]) { + revert AlreadyVerified(); + } + if (s_userToPlanterTokenAddress[msg.sender] == address(0)) { PlanterToken planterToken = new PlanterToken(msg.sender); s_userToPlanterTokenAddress[msg.sender] = address(planterToken); } @@ -224,8 +223,9 @@ contract TreeNft is ERC721, Ownable { PlanterToken planterToken = PlanterToken(planterTokenContract); if (!isVerified(_tokenId, msg.sender)) { - TreeNftVerification memory treeVerification = - TreeNftVerification(msg.sender, block.timestamp, _proofHashes, _description, false, _tokenId, planterTokenContract); + TreeNftVerification memory treeVerification = TreeNftVerification( + msg.sender, block.timestamp, _proofHashes, _description, false, _tokenId, planterTokenContract + ); s_tokenIDtoUserVerification[_tokenId][msg.sender] = true; s_tokenIDtoVerifiers[_tokenId].push(msg.sender); s_verifierToTreeTokenIDs[msg.sender].push(_tokenId); @@ -242,6 +242,29 @@ contract TreeNft is ERC721, Ownable { // This function facilitates the owner of the tree nft to remove fraudulent verifiers if (msg.sender != ownerOf(_tokenId)) revert NotTreeOwner(); + if (!s_tokenIDtoUserVerification[_tokenId][verifier]) { + revert VerificationNotFound(); + } + Tree memory tree = s_tokenIDtoTree[_tokenId]; + address treeOwner = ownerOf(_tokenId); + + s_tokenIDtoUserVerification[_tokenId][verifier] = false; + address[] storage verifiers = s_tokenIDtoVerifiers[_tokenId]; + for (uint256 i = 0; i < verifiers.length; i++) { + if (verifiers[i] == verifier) { + verifiers[i] = verifiers[verifiers.length - 1]; + verifiers.pop(); + break; + } + } + uint256[] storage verifiedTrees = s_verifierToTreeTokenIDs[verifier]; + for (uint256 i = 0; i < verifiedTrees.length; i++) { + if (verifiedTrees[i] == _tokenId) { + verifiedTrees[i] = verifiedTrees[verifiedTrees.length - 1]; + verifiedTrees.pop(); + break; + } + } uint256[] storage verificationIds = s_treeTokenIdToVerifications[_tokenId]; for (uint256 i = 0; i < verificationIds.length; i++) { TreeNftVerification storage treeNftVerification = s_tokenIDtoTreeNftVerfication[verificationIds[i]]; @@ -250,6 +273,23 @@ contract TreeNft is ERC721, Ownable { User storage user = s_addressToUser[verifier]; user.verificationsRevoked++; + address planterTokenAddr = s_userToPlanterTokenAddress[verifier]; + if (planterTokenAddr != address(0)) { + PlanterToken planterToken = PlanterToken(planterTokenAddr); + uint256 tokensToReturn = tree.numberOfTrees; + if (planterToken.balanceOf(treeOwner) >= tokensToReturn) { + planterToken.burnFrom(treeOwner, tokensToReturn); + address[] storage verifierTokenAddrs = s_userToVerifierTokenAddresses[treeOwner]; + for (uint256 j = 0; j < verifierTokenAddrs.length; j++) { + if (verifierTokenAddrs[j] == planterTokenAddr) { + verifierTokenAddrs[j] = verifierTokenAddrs[verifierTokenAddrs.length - 1]; + verifierTokenAddrs.pop(); + break; + } + } + } + } + emit VerificationRemoved(verificationIds[i], _tokenId, verifier); break; } @@ -359,7 +399,9 @@ contract TreeNft is ERC721, Ownable { // This function marks a tree as dead if (!_exists(tokenId)) revert InvalidTreeID(); - if (s_tokenIDtoTree[tokenId].death != type(uint256).max) revert TreeAlreadyDead(); + if (s_tokenIDtoTree[tokenId].death != type(uint256).max) { + revert TreeAlreadyDead(); + } if (ownerOf(tokenId) != msg.sender) revert NotTreeOwner(); if (s_tokenIDtoTree[tokenId].planting + minimumTimeToMarkTreeDead >= block.timestamp) { revert MinimumMarkDeadTimeNotReached(); @@ -374,7 +416,9 @@ contract TreeNft is ERC721, Ownable { function registerUserProfile(string memory _name, string memory _profilePhotoHash) public { // This function registers a user - if (s_addressToUser[msg.sender].userAddress != address(0)) revert UserAlreadyRegistered(); + if (s_addressToUser[msg.sender].userAddress != address(0)) { + revert UserAlreadyRegistered(); + } User memory user = User(msg.sender, _profilePhotoHash, _name, block.timestamp, 0, 0); s_addressToUser[msg.sender] = user; s_userCounter++; @@ -383,7 +427,9 @@ contract TreeNft is ERC721, Ownable { function getUserProfile(address userAddress) public view returns (UserDetails memory userDetails) { // This function returns the details of the user - if (s_addressToUser[userAddress].userAddress == address(0)) revert UserNotRegistered(); + if (s_addressToUser[userAddress].userAddress == address(0)) { + revert UserNotRegistered(); + } User memory storedUserDetails = s_addressToUser[userAddress]; userDetails.name = storedUserDetails.name; userDetails.dateJoined = storedUserDetails.dateJoined; @@ -396,11 +442,15 @@ contract TreeNft is ERC721, Ownable { return userDetails; } - function getUserVerifierTokenDetails(address userAddress) public view returns (VerificationDetails[] memory verifierTokenDetails) { + function getUserVerifierTokenDetails(address userAddress) + public + view + returns (VerificationDetails[] memory verifierTokenDetails) + { // This function returns the verifier token address of the user TreeNftVerification[] memory userVerifications = s_userToVerifications[userAddress]; - for(uint i = 0; i < userVerifications.length; i++) { + for (uint256 i = 0; i < userVerifications.length; i++) { PlanterToken planterToken = PlanterToken(userVerifications[i].verifierPlanterTokenAddress); verifierTokenDetails[i] = VerificationDetails({ verifier: userVerifications[i].verifier, @@ -417,7 +467,9 @@ contract TreeNft is ERC721, Ownable { function updateUserDetails(string memory _name, string memory _profilePhotoHash) public { // This function enables a user to change his user details - if (s_addressToUser[msg.sender].userAddress == address(0)) revert UserNotRegistered(); + if (s_addressToUser[msg.sender].userAddress == address(0)) { + revert UserNotRegistered(); + } s_addressToUser[msg.sender].name = _name; s_addressToUser[msg.sender].profilePhotoIpfs = _profilePhotoHash; } diff --git a/src/token-contracts/PlanterToken.sol b/src/token-contracts/PlanterToken.sol index 5927a7e..8d7efe4 100644 --- a/src/token-contracts/PlanterToken.sol +++ b/src/token-contracts/PlanterToken.sol @@ -6,6 +6,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract PlanterToken is ERC20, Ownable { address public planterAddress; + constructor(address _planter) Ownable(msg.sender) ERC20("PlanterToken", "PRT") { planterAddress = _planter; } @@ -14,6 +15,10 @@ contract PlanterToken is ERC20, Ownable { _mint(to, amount); } + function burnFrom(address from, uint256 amount) external onlyOwner { + _burn(from, amount); + } + function getPlanterAddress() external view returns (address) { return planterAddress; } diff --git a/src/utils/errors.sol b/src/utils/errors.sol index 34be9e2..d392f5e 100644 --- a/src/utils/errors.sol +++ b/src/utils/errors.sol @@ -44,6 +44,7 @@ error InvalidTreeID(); error MinimumMarkDeadTimeNotReached(); error InvalidCoordinates(); error CannotVerifyOwnTree(); +error VerificationNotFound(); /// User error UserAlreadyRegistered(); diff --git a/src/utils/structs.sol b/src/utils/structs.sol index 7f0f413..8ebad81 100644 --- a/src/utils/structs.sol +++ b/src/utils/structs.sol @@ -21,7 +21,7 @@ struct TreeNftVerification { string description; bool isHidden; uint256 treeNftId; - address verifierPlanterTokenAddress; + address verifierPlanterTokenAddress; } struct VerificationDetails { @@ -31,7 +31,7 @@ struct VerificationDetails { string description; bool isHidden; uint256 numberOfTrees; - address verifierPlanterTokenAddress; + address verifierPlanterTokenAddress; } struct OrganisationVerificationRequest { @@ -65,7 +65,6 @@ struct UserDetails { uint256 careTokens; } - struct Tree { uint256 id; uint256 latitude; diff --git a/test/Organisation.t.sol b/test/Organisation.t.sol index fe09689..d064e96 100644 --- a/test/Organisation.t.sol +++ b/test/Organisation.t.sol @@ -152,7 +152,9 @@ contract OrganisationTest is Test { imageHashes[0] = "QmProofHash"; vm.prank(user3); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES); + treeNft.mintNft( + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES + ); vm.stopPrank(); vm.prank(user2); @@ -184,7 +186,9 @@ contract OrganisationTest is Test { imageHashes[0] = "QmProofHash"; vm.prank(user3); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES); + treeNft.mintNft( + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES + ); vm.stopPrank(); vm.prank(user1); @@ -264,7 +268,9 @@ contract OrganisationTest is Test { imageHashes[0] = "QmProofHash"; vm.prank(user3); - treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES); + treeNft.mintNft( + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES + ); vm.stopPrank(); vm.prank(user1); diff --git a/test/TreeNft.t.sol b/test/TreeNft.t.sol index 22de26f..b6073b6 100644 --- a/test/TreeNft.t.sol +++ b/test/TreeNft.t.sol @@ -19,6 +19,9 @@ contract TreeNftVerificationTest is Test { address public verifier1 = address(0x3); address public verifier2 = address(0x4); + // Events for testing + event VerificationRemoved(uint256 indexed verificationId, uint256 indexed treeNftId, address indexed verifier); + uint256 public constant LATITUDE = 45 * 1e6; uint256 public constant LONGITUDE = 90 * 1e6; string public constant SPECIES = "Oak"; @@ -50,7 +53,7 @@ contract TreeNftVerificationTest is Test { treeNft.verify(0, proofs, "verified"); assertTrue(treeNft.isVerified(0, verifier1)); - + address planterTokenAddr = treeNft.s_userToPlanterTokenAddress(verifier1); PlanterToken planterToken = PlanterToken(planterTokenAddr); assertEq(planterToken.balanceOf(planter), NUM_TREES); @@ -106,7 +109,7 @@ contract TreeNftVerificationTest is Test { address planterToken1 = treeNft.s_userToPlanterTokenAddress(verifier1); address planterToken2 = treeNft.s_userToPlanterTokenAddress(verifier2); - + assertEq(PlanterToken(planterToken1).balanceOf(planter), NUM_TREES); assertEq(PlanterToken(planterToken2).balanceOf(planter), NUM_TREES); } @@ -121,10 +124,151 @@ contract TreeNftVerificationTest is Test { string[] memory proofs = new string[](1); proofs[0] = "proof1"; treeNft.verify(0, proofs, "verified"); + assertTrue(treeNft.isVerified(0, verifier1)); + address planterTokenAddr = treeNft.s_userToPlanterTokenAddress(verifier1); + PlanterToken planterToken = PlanterToken(planterTokenAddr); + assertEq(planterToken.balanceOf(planter), NUM_TREES); + + vm.prank(planter); + treeNft.removeVerification(0, verifier1); + assertFalse(treeNft.isVerified(0, verifier1)); + TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); + assertEq(verifications.length, 0); + Tree[] memory verifiedTrees = treeNft.getVerifiedTreesByUser(verifier1); + assertEq(verifiedTrees.length, 0); + assertEq(planterToken.balanceOf(planter), 0); + } + + function test_removeVerificationCompleteCleanup() public { + vm.prank(verifier1); + treeNft.registerUserProfile("Verifier1", "ipfs://profile1"); + + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + vm.prank(verifier1); + string[] memory proofs1 = new string[](1); + proofs1[0] = "proof1"; + treeNft.verify(0, proofs1, "verified by v1"); + + vm.prank(verifier2); + string[] memory proofs2 = new string[](1); + proofs2[0] = "proof2"; + treeNft.verify(0, proofs2, "verified by v2"); + assertTrue(treeNft.isVerified(0, verifier1)); + assertTrue(treeNft.isVerified(0, verifier2)); + TreeNftVerification[] memory verificationsBeforeRemoval = treeNft.getTreeNftVerifiers(0); + assertEq(verificationsBeforeRemoval.length, 2); + vm.prank(planter); + treeNft.removeVerification(0, verifier1); + assertFalse(treeNft.isVerified(0, verifier1)); + assertTrue(treeNft.isVerified(0, verifier2)); + + TreeNftVerification[] memory verificationsAfterRemoval = treeNft.getTreeNftVerifiers(0); + assertEq(verificationsAfterRemoval.length, 1); + assertEq(verificationsAfterRemoval[0].verifier, verifier2); + UserDetails memory verifier1Details = treeNft.getUserProfile(verifier1); + assertEq(verifier1Details.verificationsRevoked, 1); + } + + function test_removeVerificationTokenBurning() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + vm.prank(verifier1); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified"); + address planterTokenAddr = treeNft.s_userToPlanterTokenAddress(verifier1); + PlanterToken planterToken = PlanterToken(planterTokenAddr); + uint256 initialBalance = planterToken.balanceOf(planter); + assertEq(initialBalance, NUM_TREES); + vm.prank(planter); + treeNft.removeVerification(0, verifier1); + + uint256 finalBalance = planterToken.balanceOf(planter); + assertEq(finalBalance, 0); + } + function test_removeVerificationArrayCleanup() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, "Tree1", IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + vm.prank(planter); + treeNft.mintNft( + LATITUDE + 1000, LONGITUDE + 1000, "Tree2", IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES + ); + + vm.startPrank(verifier1); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified tree 0"); + treeNft.verify(1, proofs, "verified tree 1"); + vm.stopPrank(); + + Tree[] memory verifiedTreesBefore = treeNft.getVerifiedTreesByUser(verifier1); + assertEq(verifiedTreesBefore.length, 2); vm.prank(planter); treeNft.removeVerification(0, verifier1); + Tree[] memory verifiedTreesAfter = treeNft.getVerifiedTreesByUser(verifier1); + assertEq(verifiedTreesAfter.length, 1); + assertEq(verifiedTreesAfter[0].id, 1); + } + + function test_removeNonexistentVerification() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + vm.prank(planter); + vm.expectRevert(VerificationNotFound.selector); + treeNft.removeVerification(0, verifier1); + } + function test_removeVerificationEmitsEvent() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + vm.prank(verifier1); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified"); + vm.prank(planter); + vm.expectEmit(true, true, true, false); + emit VerificationRemoved(0, 0, verifier1); + treeNft.removeVerification(0, verifier1); + } + + function test_removeVerificationWithInsufficientTokens() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + vm.prank(verifier1); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified"); + + address planterTokenAddr = treeNft.s_userToPlanterTokenAddress(verifier1); + PlanterToken planterToken = PlanterToken(planterTokenAddr); + vm.prank(planter); + planterToken.transfer(address(0x999), NUM_TREES / 2); + + uint256 balanceBeforeRemoval = planterToken.balanceOf(planter); + assertLt(balanceBeforeRemoval, NUM_TREES); + vm.prank(planter); + treeNft.removeVerification(0, verifier1); + assertFalse(treeNft.isVerified(0, verifier1)); TreeNftVerification[] memory verifications = treeNft.getTreeNftVerifiers(0); assertEq(verifications.length, 0); } @@ -167,33 +311,26 @@ contract TreeNftVerificationTest is Test { assertEq(verifications[1].verifier, verifier2); } - // THE FIXED VERSION OF THE FAILING TEST function test_getVerifiedTreesByUser() public { string[] memory photos = new string[](1); photos[0] = "photo1"; - - // Mint first tree - SEPARATE transaction vm.prank(planter); treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); - - // Mint second tree - SEPARATE transaction with NEW prank + vm.prank(planter); treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); string[] memory proofs = new string[](1); proofs[0] = "proof1"; - - // Verify first tree - SEPARATE transaction + vm.prank(verifier1); treeNft.verify(0, proofs, "verified tree 0"); - - // Verify second tree - SEPARATE transaction with NEW prank + vm.prank(verifier1); treeNft.verify(1, proofs, "verified tree 1"); - // Get verified trees - no prank needed for view function Tree[] memory verifiedTrees = treeNft.getVerifiedTreesByUser(verifier1); - + assertEq(verifiedTrees.length, 2); assertEq(verifiedTrees[0].id, 0); assertEq(verifiedTrees[1].id, 1); @@ -245,4 +382,106 @@ contract TreeNftVerificationTest is Test { address planterTokenAfter = treeNft.s_userToPlanterTokenAddress(verifier1); assertTrue(planterTokenAfter != address(0)); } -} \ No newline at end of file + + function test_removeVerificationTwiceFails() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + vm.prank(verifier1); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified"); + + vm.prank(planter); + treeNft.removeVerification(0, verifier1); + + vm.prank(planter); + vm.expectRevert(VerificationNotFound.selector); + treeNft.removeVerification(0, verifier1); + } + + function test_removeVerificationAfterMultipleVerifications() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, "Tree1", IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + vm.prank(planter); + treeNft.mintNft( + LATITUDE + 1000, LONGITUDE + 1000, "Tree2", IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES + ); + + vm.prank(planter); + treeNft.mintNft( + LATITUDE + 2000, LONGITUDE + 2000, "Tree3", IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES + ); + + vm.startPrank(verifier1); + string[] memory proofs = new string[](1); + proofs[0] = "proof1"; + treeNft.verify(0, proofs, "verified tree 0"); + treeNft.verify(1, proofs, "verified tree 1"); + treeNft.verify(2, proofs, "verified tree 2"); + vm.stopPrank(); + + Tree[] memory verifiedTrees = treeNft.getVerifiedTreesByUser(verifier1); + assertEq(verifiedTrees.length, 3); + + vm.prank(planter); + treeNft.removeVerification(1, verifier1); + Tree[] memory remainingTrees = treeNft.getVerifiedTreesByUser(verifier1); + assertEq(remainingTrees.length, 2); + + bool hasTree0 = false; + bool hasTree2 = false; + for (uint256 i = 0; i < remainingTrees.length; i++) { + if (remainingTrees[i].id == 0) hasTree0 = true; + if (remainingTrees[i].id == 2) hasTree2 = true; + } + assertTrue(hasTree0, "Tree 0 should still be verified"); + assertTrue(hasTree2, "Tree 2 should still be verified"); + assertFalse(treeNft.isVerified(1, verifier1)); + } + + function test_removeVerificationPreservesOtherVerifiers() public { + vm.prank(planter); + string[] memory photos = new string[](1); + photos[0] = "photo1"; + treeNft.mintNft(LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, photos, NUM_TREES); + + vm.prank(verifier1); + string[] memory proofs1 = new string[](1); + proofs1[0] = "proof1"; + treeNft.verify(0, proofs1, "verified by v1"); + + vm.prank(verifier2); + string[] memory proofs2 = new string[](1); + proofs2[0] = "proof2"; + treeNft.verify(0, proofs2, "verified by v2"); + + address thirdVerifier = address(0x5); + vm.prank(thirdVerifier); + string[] memory proofs3 = new string[](1); + proofs3[0] = "proof3"; + treeNft.verify(0, proofs3, "verified by v3"); + + assertTrue(treeNft.isVerified(0, verifier1)); + assertTrue(treeNft.isVerified(0, verifier2)); + assertTrue(treeNft.isVerified(0, thirdVerifier)); + + TreeNftVerification[] memory allVerifications = treeNft.getTreeNftVerifiers(0); + assertEq(allVerifications.length, 3); + + vm.prank(planter); + treeNft.removeVerification(0, verifier2); + + assertTrue(treeNft.isVerified(0, verifier1)); + assertFalse(treeNft.isVerified(0, verifier2)); + assertTrue(treeNft.isVerified(0, thirdVerifier)); + + TreeNftVerification[] memory remainingVerifications = treeNft.getTreeNftVerifiers(0); + assertEq(remainingVerifications.length, 2); + } +} From 1e7d65955c03ebbc160cdf1d2030879018a76f3c Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Sat, 11 Oct 2025 19:29:48 +0530 Subject: [PATCH 12/13] minor bug fixes --- .github/workflows/test.yml | 3 ++- src/Organisation.sol | 8 ++++---- src/OrganisationFactory.sol | 4 ++-- src/TreeNft.sol | 28 ++++++++++++---------------- src/utils/structs.sol | 10 +++++----- test/Organisation.t.sol | 14 +++++++------- test/OrganisationFactory.t.sol | 24 ++++++++++++------------ test/TreeNft.t.sol | 14 +++++++------- 8 files changed, 51 insertions(+), 54 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 762a296..4a17816 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,7 @@ on: env: FOUNDRY_PROFILE: ci + FOUNDRY_DISABLE_NIGHTLY_WARNING: true jobs: check: @@ -23,7 +24,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly + version: stable - name: Show Forge version run: | diff --git a/src/Organisation.sol b/src/Organisation.sol index 97d89ae..3c2c200 100644 --- a/src/Organisation.sol +++ b/src/Organisation.sol @@ -257,7 +257,7 @@ contract Organisation { uint256 _longitude, string memory _species, string memory _imageURI, - string memory _qrIpfshash, + string memory _qrPhoto, string memory _metadata, string[] memory photos, string memory geoHash, @@ -273,7 +273,7 @@ contract Organisation { longitude: _longitude, species: _species, imageUri: _imageURI, - qrIpfsHash: _qrIpfshash, + qrPhoto: _qrPhoto, photos: photos, geoHash: geoHash, metadata: _metadata, @@ -289,7 +289,7 @@ contract Organisation { proposal.longitude, proposal.species, proposal.imageUri, - proposal.qrIpfsHash, + proposal.qrPhoto, proposal.metadata, proposal.geoHash, proposal.photos, @@ -403,7 +403,7 @@ contract Organisation { proposal.longitude, proposal.species, proposal.imageUri, - proposal.qrIpfsHash, + proposal.qrPhoto, proposal.metadata, proposal.geoHash, proposal.photos, diff --git a/src/OrganisationFactory.sol b/src/OrganisationFactory.sol index 987b7d8..e002b6b 100644 --- a/src/OrganisationFactory.sol +++ b/src/OrganisationFactory.sol @@ -110,7 +110,7 @@ contract OrganisationFactory is Ownable { contractAddress: orgAddress, name: name, description: description, - photoIpfsHash: photoIpfsHash, + organisationPhoto: photoIpfsHash, owners: owners, members: members, ownerCount: owners.length, @@ -123,7 +123,7 @@ contract OrganisationFactory is Ownable { contractAddress: organisationAddress, name: "ERROR: Unable to fetch", description: "ERROR: Contract call failed", - photoIpfsHash: "", + organisationPhoto: "", owners: new address[](0), members: new address[](0), ownerCount: 0, diff --git a/src/TreeNft.sol b/src/TreeNft.sol index c4efc75..8708207 100644 --- a/src/TreeNft.sol +++ b/src/TreeNft.sol @@ -63,7 +63,7 @@ contract TreeNft is ERC721, Ownable { uint256 longitude, string memory species, string memory imageUri, - string memory qrIpfsHash, + string memory qrPhoto, string memory metadata, string memory geoHash, string[] memory initialPhotos, @@ -87,7 +87,7 @@ contract TreeNft is ERC721, Ownable { type(uint256).max, species, imageUri, - qrIpfsHash, + qrPhoto, metadata, initialPhotos, geoHash, @@ -216,8 +216,8 @@ contract TreeNft is ERC721, Ownable { revert AlreadyVerified(); } if (s_userToPlanterTokenAddress[msg.sender] == address(0)) { - PlanterToken planterToken = new PlanterToken(msg.sender); - s_userToPlanterTokenAddress[msg.sender] = address(planterToken); + PlanterToken newPlanterToken = new PlanterToken(msg.sender); + s_userToPlanterTokenAddress[msg.sender] = address(newPlanterToken); } address planterTokenContract = s_userToPlanterTokenAddress[msg.sender]; PlanterToken planterToken = PlanterToken(planterTokenContract); @@ -232,7 +232,7 @@ contract TreeNft is ERC721, Ownable { s_tokenIDtoTreeNftVerfication[s_treeNftVerificationCounter] = treeVerification; s_treeTokenIdToVerifications[_tokenId].push(s_treeNftVerificationCounter); s_treeNftVerificationCounter++; - planterToken.mint(ownerOf(_tokenId), tree.numberOfTrees); + planterToken.mint(ownerOf(_tokenId), tree.numberOfTrees * 1e18); s_userToVerifierTokenAddresses[ownerOf(_tokenId)].push(planterTokenContract); s_userToVerifications[msg.sender].push(treeVerification); } @@ -276,7 +276,7 @@ contract TreeNft is ERC721, Ownable { address planterTokenAddr = s_userToPlanterTokenAddress[verifier]; if (planterTokenAddr != address(0)) { PlanterToken planterToken = PlanterToken(planterTokenAddr); - uint256 tokensToReturn = tree.numberOfTrees; + uint256 tokensToReturn = tree.numberOfTrees * 1e18; if (planterToken.balanceOf(treeOwner) >= tokensToReturn) { planterToken.burnFrom(treeOwner, tokensToReturn); address[] storage verifierTokenAddrs = s_userToVerifierTokenAddresses[treeOwner]; @@ -407,19 +407,19 @@ contract TreeNft is ERC721, Ownable { revert MinimumMarkDeadTimeNotReached(); } - legacyToken.mint(msg.sender, 1); + legacyToken.mint(msg.sender, 1 * 1e18); s_tokenIDtoTree[tokenId].death = block.timestamp; s_deathCounter++; } - function registerUserProfile(string memory _name, string memory _profilePhotoHash) public { + function registerUserProfile(string memory _name, string memory _profilePhoto) public { // This function registers a user if (s_addressToUser[msg.sender].userAddress != address(0)) { revert UserAlreadyRegistered(); } - User memory user = User(msg.sender, _profilePhotoHash, _name, block.timestamp, 0, 0); + User memory user = User(msg.sender, _profilePhoto, _name, block.timestamp, 0, 0); s_addressToUser[msg.sender] = user; s_userCounter++; } @@ -433,7 +433,7 @@ contract TreeNft is ERC721, Ownable { User memory storedUserDetails = s_addressToUser[userAddress]; userDetails.name = storedUserDetails.name; userDetails.dateJoined = storedUserDetails.dateJoined; - userDetails.profilePhotoIpfs = storedUserDetails.profilePhotoIpfs; + userDetails.profilePhoto = storedUserDetails.profilePhoto; userDetails.userAddress = storedUserDetails.userAddress; userDetails.reportedSpam = storedUserDetails.reportedSpam; userDetails.verificationsRevoked = storedUserDetails.verificationsRevoked; @@ -464,14 +464,14 @@ contract TreeNft is ERC721, Ownable { } } - function updateUserDetails(string memory _name, string memory _profilePhotoHash) public { + function updateUserDetails(string memory _name, string memory _profilePhoto) public { // This function enables a user to change his user details if (s_addressToUser[msg.sender].userAddress == address(0)) { revert UserNotRegistered(); } s_addressToUser[msg.sender].name = _name; - s_addressToUser[msg.sender].profilePhotoIpfs = _profilePhotoHash; + s_addressToUser[msg.sender].profilePhoto = _profilePhoto; } function isVerified(uint256 tokenId, address verifier) public view returns (bool) { @@ -483,8 +483,4 @@ contract TreeNft is ERC721, Ownable { function _exists(uint256 tokenId) internal view returns (bool) { return tokenId < s_treeTokenCounter && tokenId >= 0; } - - function ping() public pure returns (string memory) { - return "pong"; - } } diff --git a/src/utils/structs.sol b/src/utils/structs.sol index 8ebad81..77b2ebf 100644 --- a/src/utils/structs.sol +++ b/src/utils/structs.sol @@ -5,7 +5,7 @@ struct OrganisationDetails { address contractAddress; string name; string description; - string photoIpfsHash; + string organisationPhoto; address[] owners; address[] members; uint256 ownerCount; @@ -47,7 +47,7 @@ struct OrganisationVerificationRequest { struct User { address userAddress; - string profilePhotoIpfs; + string profilePhoto; string name; uint256 dateJoined; uint256 verificationsRevoked; @@ -56,7 +56,7 @@ struct User { struct UserDetails { address userAddress; - string profilePhotoIpfs; + string profilePhoto; string name; uint256 dateJoined; uint256 verificationsRevoked; @@ -73,7 +73,7 @@ struct Tree { uint256 death; string species; string imageUri; - string qrIpfsHash; + string qrPhoto; string metadata; string[] photos; string geoHash; @@ -89,7 +89,7 @@ struct TreePlantingProposal { uint256 longitude; string species; string imageUri; - string qrIpfsHash; + string qrPhoto; string[] photos; string geoHash; string metadata; diff --git a/test/Organisation.t.sol b/test/Organisation.t.sol index d064e96..17fb74b 100644 --- a/test/Organisation.t.sol +++ b/test/Organisation.t.sol @@ -30,7 +30,7 @@ contract OrganisationTest is Test { uint256 constant LONGITUDE = 9876543; string constant SPECIES = "Oak"; string constant IMAGE_URI = "https://example.com/tree.jpg"; - string constant QR_IPFS_HASH = "QmTestQrHash"; + string constant QR_HASH = "QmTestQrHash"; string constant GEOHASH = "u4pruydqqvj"; string constant NAME = "Test Organisation"; string constant DESCRIPTION = "This is a test organisation."; @@ -153,7 +153,7 @@ contract OrganisationTest is Test { vm.prank(user3); treeNft.mintNft( - LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES ); vm.stopPrank(); @@ -187,7 +187,7 @@ contract OrganisationTest is Test { vm.prank(user3); treeNft.mintNft( - LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES ); vm.stopPrank(); @@ -238,7 +238,7 @@ contract OrganisationTest is Test { vm.prank(user1); Organisation(orgAddress).plantTreeProposal( - LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, proofHashes, GEOHASH, NUMBER_OF_TREES + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, proofHashes, GEOHASH, NUMBER_OF_TREES ); vm.stopPrank(); @@ -252,7 +252,7 @@ contract OrganisationTest is Test { assertEq(proposal.longitude, LONGITUDE); assertEq(proposal.species, SPECIES); assertEq(proposal.imageUri, IMAGE_URI); - assertEq(proposal.qrIpfsHash, QR_IPFS_HASH); + assertEq(proposal.qrPhoto, QR_HASH); assertEq(proposal.geoHash, GEOHASH); } @@ -269,7 +269,7 @@ contract OrganisationTest is Test { vm.prank(user3); treeNft.mintNft( - LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, GEOHASH, imageHashes, NUMBER_OF_TREES ); vm.stopPrank(); @@ -293,7 +293,7 @@ contract OrganisationTest is Test { string[] memory proofHashes = new string[](1); proofHashes[0] = "QmProofHash"; Organisation(orgAddress).plantTreeProposal( - LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_IPFS_HASH, METADATA, proofHashes, GEOHASH, NUMBER_OF_TREES + LATITUDE, LONGITUDE, SPECIES, IMAGE_URI, QR_HASH, METADATA, proofHashes, GEOHASH, NUMBER_OF_TREES ); vm.stopPrank(); diff --git a/test/OrganisationFactory.t.sol b/test/OrganisationFactory.t.sol index 3a42b8d..d247a42 100644 --- a/test/OrganisationFactory.t.sol +++ b/test/OrganisationFactory.t.sol @@ -28,10 +28,10 @@ contract OrganisationFactoryTest is Test { string constant NAME1 = "Test Organisation"; string constant DESCRIPTION1 = "This is a test organisation."; - string constant PHOTO_IPFS_HASH1 = "QmTestPhotoHash"; + string constant PHOTO_HASH1 = "QmTestPhotoHash"; string constant NAME2 = "Test Organisation"; string constant DESCRIPTION2 = "This is a test organisation."; - string constant PHOTO_IPFS_HASH2 = "QmTestPhotoHash"; + string constant PHOTO_HASH2 = "QmTestPhotoHash"; function setUp() public { vm.startPrank(owner); @@ -69,7 +69,7 @@ contract OrganisationFactoryTest is Test { // This test checks if the createOrganisation function works correctly by creating an organisation and verifying its details. vm.prank(user1); - (uint256 orgId, address orgAddress) = factory.createOrganisation(NAME1, DESCRIPTION1, PHOTO_IPFS_HASH1); + (uint256 orgId, address orgAddress) = factory.createOrganisation(NAME1, DESCRIPTION1, PHOTO_HASH1); assertEq(orgId, 0); assertEq(factory.getOrganisationCount(), 1); ( @@ -84,7 +84,7 @@ contract OrganisationFactoryTest is Test { assert(organizationAddress == orgAddress); assertEq(name, NAME1); assertEq(description, DESCRIPTION1); - assertEq(photoIpfsHash, PHOTO_IPFS_HASH1); + assertEq(photoIpfsHash, PHOTO_HASH1); assertEq(owners[0], user1); assertEq(members.length, 1); assertEq(timeOfCreation, block.timestamp); @@ -94,10 +94,10 @@ contract OrganisationFactoryTest is Test { // This test checks if the getMyOrganisations function returns the correct organisation details for a user. vm.prank(user1); - factory.createOrganisation(NAME1, DESCRIPTION1, PHOTO_IPFS_HASH1); + factory.createOrganisation(NAME1, DESCRIPTION1, PHOTO_HASH1); vm.stopPrank(); vm.prank(user2); - factory.createOrganisation(NAME2, DESCRIPTION2, PHOTO_IPFS_HASH2); + factory.createOrganisation(NAME2, DESCRIPTION2, PHOTO_HASH2); vm.stopPrank(); vm.startPrank(user1); @@ -115,22 +115,22 @@ contract OrganisationFactoryTest is Test { // This test checks if the factory can return all organisations correctly. vm.prank(user1); - factory.createOrganisation(NAME1, DESCRIPTION1, PHOTO_IPFS_HASH1); + factory.createOrganisation(NAME1, DESCRIPTION1, PHOTO_HASH1); vm.stopPrank(); vm.prank(user2); - factory.createOrganisation(NAME2, DESCRIPTION2, PHOTO_IPFS_HASH2); + factory.createOrganisation(NAME2, DESCRIPTION2, PHOTO_HASH2); vm.stopPrank(); OrganisationDetails[] memory orgs = factory.getAllOrganisationDetails(); assertEq(orgs.length, 2); assertEq(orgs[0].name, NAME1); assertEq(orgs[0].description, DESCRIPTION1); - assertEq(orgs[0].photoIpfsHash, PHOTO_IPFS_HASH1); + assertEq(orgs[0].organisationPhoto, PHOTO_HASH1); assertEq(orgs[0].ownerCount, 1); assertEq(orgs[0].memberCount, 1); assertEq(orgs[1].name, NAME2); assertEq(orgs[1].description, DESCRIPTION2); - assertEq(orgs[1].photoIpfsHash, PHOTO_IPFS_HASH2); + assertEq(orgs[1].organisationPhoto, PHOTO_HASH2); assertEq(orgs[1].ownerCount, 1); assertEq(orgs[1].memberCount, 1); } @@ -139,10 +139,10 @@ contract OrganisationFactoryTest is Test { // This test checks if the factory can return all organisation IDs correctly. vm.prank(user1); - factory.createOrganisation(NAME1, DESCRIPTION1, PHOTO_IPFS_HASH1); + factory.createOrganisation(NAME1, DESCRIPTION1, PHOTO_HASH1); vm.stopPrank(); vm.prank(user2); - factory.createOrganisation(NAME2, DESCRIPTION2, PHOTO_IPFS_HASH2); + factory.createOrganisation(NAME2, DESCRIPTION2, PHOTO_HASH2); vm.stopPrank(); address[] memory orgAddresses = factory.getAllOrganisations(); diff --git a/test/TreeNft.t.sol b/test/TreeNft.t.sol index b6073b6..9783616 100644 --- a/test/TreeNft.t.sol +++ b/test/TreeNft.t.sol @@ -56,7 +56,7 @@ contract TreeNftVerificationTest is Test { address planterTokenAddr = treeNft.s_userToPlanterTokenAddress(verifier1); PlanterToken planterToken = PlanterToken(planterTokenAddr); - assertEq(planterToken.balanceOf(planter), NUM_TREES); + assertEq(planterToken.balanceOf(planter), NUM_TREES * 1e18); } function test_cannotVerifyOwnTree() public { @@ -110,8 +110,8 @@ contract TreeNftVerificationTest is Test { address planterToken1 = treeNft.s_userToPlanterTokenAddress(verifier1); address planterToken2 = treeNft.s_userToPlanterTokenAddress(verifier2); - assertEq(PlanterToken(planterToken1).balanceOf(planter), NUM_TREES); - assertEq(PlanterToken(planterToken2).balanceOf(planter), NUM_TREES); + assertEq(PlanterToken(planterToken1).balanceOf(planter), NUM_TREES * 1e18); + assertEq(PlanterToken(planterToken2).balanceOf(planter), NUM_TREES * 1e18); } function test_removeVerification() public { @@ -127,7 +127,7 @@ contract TreeNftVerificationTest is Test { assertTrue(treeNft.isVerified(0, verifier1)); address planterTokenAddr = treeNft.s_userToPlanterTokenAddress(verifier1); PlanterToken planterToken = PlanterToken(planterTokenAddr); - assertEq(planterToken.balanceOf(planter), NUM_TREES); + assertEq(planterToken.balanceOf(planter), NUM_TREES * 1e18); vm.prank(planter); treeNft.removeVerification(0, verifier1); @@ -186,7 +186,7 @@ contract TreeNftVerificationTest is Test { address planterTokenAddr = treeNft.s_userToPlanterTokenAddress(verifier1); PlanterToken planterToken = PlanterToken(planterTokenAddr); uint256 initialBalance = planterToken.balanceOf(planter); - assertEq(initialBalance, NUM_TREES); + assertEq(initialBalance, NUM_TREES * 1e18); vm.prank(planter); treeNft.removeVerification(0, verifier1); @@ -262,10 +262,10 @@ contract TreeNftVerificationTest is Test { address planterTokenAddr = treeNft.s_userToPlanterTokenAddress(verifier1); PlanterToken planterToken = PlanterToken(planterTokenAddr); vm.prank(planter); - planterToken.transfer(address(0x999), NUM_TREES / 2); + planterToken.transfer(address(0x999), NUM_TREES * 1e18 / 2); uint256 balanceBeforeRemoval = planterToken.balanceOf(planter); - assertLt(balanceBeforeRemoval, NUM_TREES); + assertLt(balanceBeforeRemoval, NUM_TREES * 1e18); vm.prank(planter); treeNft.removeVerification(0, verifier1); assertFalse(treeNft.isVerified(0, verifier1)); From 2be49a8d6b78dcb7c5d603be4ddb600c9179f50b Mon Sep 17 00:00:00 2001 From: IronJam11 Date: Sun, 12 Oct 2025 20:32:36 +0530 Subject: [PATCH 13/13] fix: add initiator paramater to tree planting proposal --- src/Organisation.sol | 3 ++- src/utils/structs.sol | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Organisation.sol b/src/Organisation.sol index 3c2c200..a47119d 100644 --- a/src/Organisation.sol +++ b/src/Organisation.sol @@ -278,7 +278,8 @@ contract Organisation { geoHash: geoHash, metadata: _metadata, status: 0, - numberOfTrees: numberOfTrees + numberOfTrees: numberOfTrees, + initiator: msg.sender }); if (checkOwnership(msg.sender)) { s_treeProposalYesVoters[s_treePlantingProposalCounter].push(msg.sender); diff --git a/src/utils/structs.sol b/src/utils/structs.sol index 77b2ebf..1444bb0 100644 --- a/src/utils/structs.sol +++ b/src/utils/structs.sol @@ -95,4 +95,5 @@ struct TreePlantingProposal { string metadata; uint256 status; uint256 numberOfTrees; + address initiator; }