From 83a51a3430364ba2ae8d03a946e4d4cd51d0234a Mon Sep 17 00:00:00 2001 From: Anurag Date: Fri, 13 Mar 2026 03:07:27 +0000 Subject: [PATCH 01/15] add: DeviceRegistry Testing cases --- contracts/test/DeviceRegistry.t.sol | 487 ++++++++++++++++++++++++++++ 1 file changed, 487 insertions(+) create mode 100644 contracts/test/DeviceRegistry.t.sol diff --git a/contracts/test/DeviceRegistry.t.sol b/contracts/test/DeviceRegistry.t.sol new file mode 100644 index 0000000..cf89bab --- /dev/null +++ b/contracts/test/DeviceRegistry.t.sol @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.31; + +import "forge-std/Test.sol"; +import "../src/DeviceRegistry.sol"; + +contract DeviceRegistryTest is Test { + DeviceRegistry private registry; + + // Mirror contract events so we can use vm.expectEmit with (emitter) + event DeviceRegistered( + address indexed deviceAddress, + string deviceId, + string publicKey, + address indexed registeredBy + ); + event DeviceUpdated(address indexed deviceAddress, string deviceId, bool isActive); + event DeviceDeactivated(address indexed deviceAddress, string deviceId); + + address private registrar; // address that registers devices + address private deviceAddress1; // first device wallet + address private deviceAddress2; // second device wallet + address private attacker; // unauthorized actor + + string private constant PUBLIC_KEY = + "0x04e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3"; + string private constant DEVICE_ID_1 = "device-001"; + string private constant DEVICE_ID_2 = "device-002"; + string private constant CAMERA_ID_1 = "camera-001"; + string private constant CAMERA_ID_2 = "camera-002"; + string private constant MODEL = "LensMint Pi"; + string private constant FW_V1 = "1.0.0"; // firmware version 1.0.0 + string private constant FW_V2 = "1.1.0"; // firmware version 1.1.0 + + function setUp() public { + registry = new DeviceRegistry(); + + registrar = address(this); + deviceAddress1 = address(0xD1); + deviceAddress2 = address(0xD2); + attacker = address(0xBAD); + } + + // ------------------------------------------------------------------------- + // registerDevice + // ------------------------------------------------------------------------- + + function test_RegisterDevice_Success() public { + bool ok = registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + assertTrue(ok, "registerDevice should return true"); + + DeviceRegistry.DeviceInfo memory info = registry.getDevice( + deviceAddress1 + ); + + assertEq(info.deviceAddress, deviceAddress1, "deviceAddress stored"); + assertEq(info.publicKey, PUBLIC_KEY, "publicKey stored"); + assertEq(info.deviceId, DEVICE_ID_1, "deviceId stored"); + assertEq(info.cameraId, CAMERA_ID_1, "cameraId stored"); + assertEq(info.model, MODEL, "model stored"); + assertEq(info.firmwareVersion, FW_V1, "firmware stored"); + assertEq(info.registeredBy, registrar, "registeredBy is caller"); + assertEq(info.registrationTime, block.timestamp, "registrationTime is current timestamp"); + assertTrue(info.isActive, "device should start active"); + + address deviceAddressById = registry.getDeviceByDeviceId(DEVICE_ID_1); + assertEq(deviceAddressById, deviceAddress1, "deviceIdToAddress mapping set"); + + uint256 totalDevices = registry.getTotalDevices(); + assertEq(totalDevices, 1, "one device registered"); + + address[] memory all = registry.getAllDevices(); + assertEq(all.length, 1, "one element in array"); + assertEq(all[0], deviceAddress1, "array contains device"); + } + + function test_RegisterDevice_Revert_InvalidDeviceAddress() public { + vm.expectRevert(DeviceRegistry.InvalidDeviceAddress.selector); + registry.registerDevice( + address(0), + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + } + + function test_RegisterDevice_Revert_PublicKeyRequired() public { + vm.expectRevert(DeviceRegistry.PublicKeyRequired.selector); + registry.registerDevice( + deviceAddress1, + "", + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + } + + function test_RegisterDevice_Revert_DeviceIdRequired() public { + vm.expectRevert(DeviceRegistry.DeviceIdRequired.selector); + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + "", + CAMERA_ID_1, + MODEL, + FW_V1 + ); + } + + function test_RegisterDevice_Revert_CameraIdRequired() public { + vm.expectRevert(DeviceRegistry.CameraIdRequired.selector); + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + "", + MODEL, + FW_V1 + ); + } + + function test_RegisterDevice_Revert_DeviceAlreadyRegistered() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.expectRevert(DeviceRegistry.DeviceAlreadyRegistered.selector); + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_2, + CAMERA_ID_2, + MODEL, + FW_V1 + ); + } + + function test_RegisterDevice_Revert_DeviceIdAlreadyInUse() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.expectRevert(DeviceRegistry.DeviceIdAlreadyInUse.selector); + registry.registerDevice( + deviceAddress2, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_2, + MODEL, + FW_V1 + ); + } + + // ------------------------------------------------------------------------- + // updateDevice + // ------------------------------------------------------------------------- + + function test_UpdateDevice_Success_ByRegistrar() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + bool ok = registry.updateDevice( + deviceAddress1, + FW_V2, + false + ); + assertTrue(ok, "updateDevice should return true"); + + DeviceRegistry.DeviceInfo memory info = registry.getDevice( + deviceAddress1 + ); + assertEq(info.firmwareVersion, FW_V2, "firmware updated"); + assertFalse(info.isActive, "isActive updated"); + } + + function test_UpdateDevice_Success_ByDeviceAddress() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.prank(deviceAddress1); + bool ok = registry.updateDevice( + deviceAddress1, + FW_V2, + true + ); + assertTrue(ok, "updateDevice should return true for device"); + + DeviceRegistry.DeviceInfo memory info = registry.getDevice( + deviceAddress1 + ); + assertEq(info.firmwareVersion, FW_V2, "firmware updated"); + assertTrue(info.isActive, "isActive updated"); + } + + function test_UpdateDevice_Revert_DeviceNotRegistered() public { + vm.expectRevert(DeviceRegistry.DeviceNotRegistered.selector); + registry.updateDevice(deviceAddress1, FW_V2, true); + } + + function test_UpdateDevice_Revert_FirmwareVersionRequired() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.expectRevert(DeviceRegistry.FirmwareVersionRequired.selector); + registry.updateDevice(deviceAddress1, "", true); + } + + function test_UpdateDevice_Revert_NotAuthorizedToUpdate() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.prank(attacker); + vm.expectRevert(DeviceRegistry.NotAuthorizedToUpdate.selector); + registry.updateDevice(deviceAddress1, FW_V2, true); + } + + // ------------------------------------------------------------------------- + // deactivateDevice + // ------------------------------------------------------------------------- + + function test_DeactivateDevice_Success_ByRegistrar() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + bool ok = registry.deactivateDevice(deviceAddress1); + assertTrue(ok, "deactivateDevice should return true"); + + bool active = registry.isDeviceActive(deviceAddress1); + assertFalse(active, "device should be inactive"); + } + + function test_DeactivateDevice_Success_ByDeviceAddress() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.prank(deviceAddress1); + bool ok = registry.deactivateDevice(deviceAddress1); + assertTrue(ok, "deactivateDevice should return true for device"); + + bool active = registry.isDeviceActive(deviceAddress1); + assertFalse(active, "device should be inactive"); + } + + function test_DeactivateDevice_Revert_DeviceNotRegistered() public { + vm.expectRevert(DeviceRegistry.DeviceNotRegistered.selector); + registry.deactivateDevice(deviceAddress1); + } + + function test_DeactivateDevice_Revert_NotAuthorizedToDeactivate() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.prank(attacker); + vm.expectRevert(DeviceRegistry.NotAuthorizedToDeactivate.selector); + registry.deactivateDevice(deviceAddress1); + } + + // ------------------------------------------------------------------------- + // isDeviceActive / getters + // ------------------------------------------------------------------------- + + function test_IsDeviceActive_FlagReflectsState() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + bool active = registry.isDeviceActive(deviceAddress1); + assertTrue(active, "device should start active"); + + registry.deactivateDevice(deviceAddress1); + active = registry.isDeviceActive(deviceAddress1); + assertFalse(active, "device should be inactive after deactivation"); + } + + function test_IsDeviceActive_UnregisteredReturnsFalse() public view { + bool active = registry.isDeviceActive(deviceAddress1); + assertFalse(active, "unregistered device should not be active"); + } + + // ------------------------------------------------------------------------- + // getDevice / getDeviceByDeviceId (edge cases) + // ------------------------------------------------------------------------- + + function test_GetDevice_UnregisteredReturnsEmptyStruct() public view { + DeviceRegistry.DeviceInfo memory info = registry.getDevice(deviceAddress1); + assertEq(info.deviceAddress, address(0), "unregistered device has zero address"); + assertEq(info.registeredBy, address(0), "unregistered has no registrar"); + assertEq(info.registrationTime, 0, "unregistered has zero time"); + assertFalse(info.isActive, "unregistered is not active"); + assertEq(bytes(info.deviceId).length, 0, "unregistered has empty deviceId"); + } + + function test_GetDeviceByDeviceId_UnknownReturnsZero() public view { + address addr = registry.getDeviceByDeviceId("nonexistent-id"); + assertEq(addr, address(0), "unknown deviceId should return zero address"); + } + + // ------------------------------------------------------------------------- + // getTotalDevices / getAllDevices (multiple devices) + // ------------------------------------------------------------------------- + + function test_GetTotalDevices_MultipleDevices() public { + assertEq(registry.getTotalDevices(), 0, "starts at zero"); + + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + assertEq(registry.getTotalDevices(), 1, "one after first register"); + + registry.registerDevice( + deviceAddress2, + PUBLIC_KEY, + DEVICE_ID_2, + CAMERA_ID_2, + MODEL, + FW_V1 + ); + assertEq(registry.getTotalDevices(), 2, "two after second register"); + } + + function test_GetAllDevices_OrderAndContent() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + registry.registerDevice( + deviceAddress2, + PUBLIC_KEY, + DEVICE_ID_2, + CAMERA_ID_2, + MODEL, + FW_V1 + ); + + address[] memory all = registry.getAllDevices(); + assertEq(all.length, 2, "two devices"); + assertEq(all[0], deviceAddress1, "first is device1"); + assertEq(all[1], deviceAddress2, "second is device2"); + } + + // ------------------------------------------------------------------------- + // Re-activation (deactivate then updateDevice(..., true)) + // ------------------------------------------------------------------------- + + function test_UpdateDevice_ReactivationAfterDeactivate() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + registry.deactivateDevice(deviceAddress1); + assertFalse(registry.isDeviceActive(deviceAddress1), "device inactive"); + + registry.updateDevice(deviceAddress1, FW_V2, true); + assertTrue(registry.isDeviceActive(deviceAddress1), "device active again"); + DeviceRegistry.DeviceInfo memory info = registry.getDevice(deviceAddress1); + assertEq(info.firmwareVersion, FW_V2, "firmware updated on reactivation"); + } + + // ------------------------------------------------------------------------- + // Events + // ------------------------------------------------------------------------- + + function test_RegisterDevice_EmitsDeviceRegistered() public { + vm.expectEmit(true, true, true, true, address(registry)); + emit DeviceRegistered(deviceAddress1, DEVICE_ID_1, PUBLIC_KEY, registrar); + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + } + + function test_UpdateDevice_EmitsDeviceUpdated() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.expectEmit(true, true, true, true, address(registry)); + emit DeviceUpdated(deviceAddress1, DEVICE_ID_1, false); + registry.updateDevice(deviceAddress1, FW_V2, false); + } + + function test_DeactivateDevice_EmitsDeviceDeactivated() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.expectEmit(true, true, true, true, address(registry)); + emit DeviceDeactivated(deviceAddress1, DEVICE_ID_1); + registry.deactivateDevice(deviceAddress1); + } +} + From 307216d0a6572f91402d13a921d8f278f0d34661 Mon Sep 17 00:00:00 2001 From: Anurag Date: Mon, 16 Mar 2026 17:31:33 +0000 Subject: [PATCH 02/15] test: LensMintERC1155 edge cases --- contracts/test/LensMintERC1155.t.sol | 250 +++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 contracts/test/LensMintERC1155.t.sol diff --git a/contracts/test/LensMintERC1155.t.sol b/contracts/test/LensMintERC1155.t.sol new file mode 100644 index 0000000..aa36de9 --- /dev/null +++ b/contracts/test/LensMintERC1155.t.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.31; + +import {Test} from "forge-std/Test.sol"; +import {LensMintERC1155} from "../src/LensMintERC1155.sol"; // solhint-disable-line +import {DeviceRegistry} from "../src/DeviceRegistry.sol"; // solhint-disable-line + +/// @notice Unit tests for LensMintERC1155 (constructor, mintOriginal, mintEdition, batchMintEditions, getters, access control). +contract LensMintERC1155Test is Test { + DeviceRegistry public deviceRegistry; + LensMintERC1155 public lensMint; + + address public owner; + address public device; + address public recipient; + address public stranger; + + string constant BASE_URI = "https://ipfs.io/ipfs/"; + string constant IPFS_HASH = "QmTest123"; + string constant IMAGE_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"; + string constant SIGNATURE = "0xsignature123"; + string constant PUBLIC_KEY = "0x04e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3"; + string constant DEVICE_ID = "device-001"; + string constant CAMERA_ID = "camera-001"; + string constant MODEL = "LensMint Pi"; + string constant FW = "1.0.0"; + + event TokenMinted( + uint256 indexed tokenId, address indexed deviceAddress, string deviceId, string ipfsHash, bool isOriginal + ); + event EditionMinted(uint256 indexed tokenId, uint256 indexed originalTokenId, address indexed to); + event BaseURIUpdated(string newBaseURI); + + function setUp() public { + deviceRegistry = new DeviceRegistry(); + lensMint = new LensMintERC1155(address(deviceRegistry), BASE_URI); + + owner = address(this); + device = address(0xD1); + recipient = address(0xD2); + stranger = address(0xBAD); + + deviceRegistry.registerDevice(device, PUBLIC_KEY, DEVICE_ID, CAMERA_ID, MODEL, FW); + } + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + function test_Constructor_Success() public view { + assertEq(address(lensMint.deviceRegistry()), address(deviceRegistry)); + assertEq(lensMint.baseURI(), BASE_URI); + assertEq(lensMint.owner(), owner); + assertEq(lensMint.totalTokens(), 0); + } + + function test_Constructor_Revert_DeviceRegistryAddressIsZero() public { + vm.expectRevert(LensMintERC1155.DeviceRegistryAddressIsZero.selector); + new LensMintERC1155(address(0), BASE_URI); + } + + function test_Constructor_Revert_BaseURIEmptyString() public { + vm.expectRevert(LensMintERC1155.BaseURIEmptyString.selector); + new LensMintERC1155(address(deviceRegistry), ""); + } + + // ------------------------------------------------------------------------- + // mintOriginal + // ------------------------------------------------------------------------- + + function test_MintOriginal_Success() public { + vm.prank(device); + uint256 tokenId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + + assertEq(tokenId, 1); + assertEq(lensMint.totalTokens(), 1); + assertEq(lensMint.balanceOf(recipient, tokenId), 1); + assertEq(lensMint.getEditionCount(tokenId), 1); + + LensMintERC1155.TokenMetadata memory m = lensMint.getTokenMetadata(tokenId); + assertEq(m.deviceAddress, device); + assertTrue(m.isOriginal); + assertEq(m.ipfsHash, IPFS_HASH); + assertEq(m.imageHash, IMAGE_HASH); + assertEq(m.signature, SIGNATURE); + assertEq(m.maxEditions, 0); + assertEq(m.originalTokenId, tokenId); + assertEq(m.deviceId, DEVICE_ID); + } + + function test_MintOriginal_Revert_DeviceNotRegisteredOrInactive() public { + vm.prank(stranger); + vm.expectRevert(LensMintERC1155.DeviceNotRegisteredOrInactive.selector); + lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + } + + function test_MintOriginal_Revert_WhenDeviceDeactivated() public { + deviceRegistry.deactivateDevice(device); + vm.prank(device); + vm.expectRevert(LensMintERC1155.DeviceNotRegisteredOrInactive.selector); + lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + } + + // ------------------------------------------------------------------------- + // mintEdition + // ------------------------------------------------------------------------- + + function test_MintEdition_Success() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + + vm.prank(device); + uint256 editionId = lensMint.mintEdition(recipient, originalId); + + assertEq(editionId, 2); + assertEq(lensMint.totalTokens(), 2); + assertEq(lensMint.balanceOf(recipient, editionId), 1); + assertEq(lensMint.getEditionCount(originalId), 2); + + LensMintERC1155.TokenMetadata memory m = lensMint.getTokenMetadata(editionId); + assertEq(m.deviceAddress, device); + assertFalse(m.isOriginal); + assertEq(m.originalTokenId, originalId); + assertEq(m.ipfsHash, IPFS_HASH); + } + + function test_MintEdition_Revert_TokenDoesNotExist() public { + vm.prank(device); + vm.expectRevert(LensMintERC1155.TokenDoesNotExist.selector); + lensMint.mintEdition(recipient, 999); + } + + function test_MintEdition_Revert_TokenIsNotAnOriginal() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + vm.prank(device); + uint256 editionId = lensMint.mintEdition(recipient, originalId); + + vm.prank(device); + vm.expectRevert(LensMintERC1155.TokenIsNotAnOriginal.selector); + lensMint.mintEdition(recipient, editionId); + } + + function test_MintEdition_Revert_MaxEditionsReached() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 1); + vm.prank(device); + lensMint.mintEdition(recipient, originalId); + + vm.prank(device); + vm.expectRevert(LensMintERC1155.MaxEditionsReached.selector); + lensMint.mintEdition(recipient, originalId); + } + + // ------------------------------------------------------------------------- + // batchMintEditions + // ------------------------------------------------------------------------- + + function test_BatchMintEditions_Success() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + + vm.prank(device); + uint256[] memory tokenIds = lensMint.batchMintEditions(recipient, originalId, 3); + + assertEq(tokenIds.length, 3); + assertEq(tokenIds[0], 2); + assertEq(tokenIds[1], 3); + assertEq(tokenIds[2], 4); + assertEq(lensMint.getEditionCount(originalId), 4); + assertEq(lensMint.balanceOf(recipient, 2), 1); + assertEq(lensMint.balanceOf(recipient, 3), 1); + assertEq(lensMint.balanceOf(recipient, 4), 1); + } + + function test_BatchMintEditions_Revert_QuantityMustBeGreaterThanZero() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + vm.prank(device); + vm.expectRevert(LensMintERC1155.QuantityMustBeGreaterThanZero.selector); + lensMint.batchMintEditions(recipient, originalId, 0); + } + + function test_BatchMintEditions_Revert_MaxEditionsReached() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 2); + vm.prank(device); + vm.expectRevert(LensMintERC1155.MaxEditionsReached.selector); + lensMint.batchMintEditions(recipient, originalId, 3); + } + + // ------------------------------------------------------------------------- + // uri / getTokenMetadata / getEditionCount + // ------------------------------------------------------------------------- + + function test_Uri_Revert_TokenDoesNotExist() public { + vm.expectRevert(LensMintERC1155.TokenDoesNotExist.selector); + lensMint.uri(1); + } + + function test_Uri_ReturnsCorrectFormat() public { + vm.prank(device); + uint256 tokenId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + assertEq(lensMint.uri(tokenId), string(abi.encodePacked(BASE_URI, "1"))); + } + + function test_GetTokenMetadata_NonExistentReturnsDefaults() public view { + LensMintERC1155.TokenMetadata memory m = lensMint.getTokenMetadata(1); + assertEq(m.deviceAddress, address(0)); + assertEq(m.originalTokenId, 0); + } + + function test_GetEditionCount_NonExistentReturnsZero() public view { + assertEq(lensMint.getEditionCount(1), 0); + } + + // ------------------------------------------------------------------------- + // setBaseURI (onlyOwner) + // ------------------------------------------------------------------------- + + function test_SetBaseURI_Success() public { + string memory newUri = "https://new.base/"; + vm.prank(owner); + lensMint.setBaseURI(newUri); + assertEq(lensMint.baseURI(), newUri); + } + + function test_SetBaseURI_Revert_NotOwner() public { + vm.prank(stranger); + vm.expectRevert(); + lensMint.setBaseURI("https://evil/"); + } + + // ------------------------------------------------------------------------- + // canDeviceMint + // ------------------------------------------------------------------------- + + function test_CanDeviceMint_TrueWhenActive() public view { + assertTrue(lensMint.canDeviceMint(device)); + } + + function test_CanDeviceMint_FalseWhenInactive() public { + deviceRegistry.deactivateDevice(device); + assertFalse(lensMint.canDeviceMint(device)); + } + + function test_CanDeviceMint_FalseWhenUnregistered() public view { + assertFalse(lensMint.canDeviceMint(stranger)); + } +} From b896f90601ed9e0f69fbe62c51c0a96fd83a7586 Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 09:33:26 +0000 Subject: [PATCH 03/15] debug: replace owner/reciepient address --- contracts/test/LensMintERC1155.t.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/test/LensMintERC1155.t.sol b/contracts/test/LensMintERC1155.t.sol index aa36de9..4cbe7bb 100644 --- a/contracts/test/LensMintERC1155.t.sol +++ b/contracts/test/LensMintERC1155.t.sol @@ -107,7 +107,7 @@ contract LensMintERC1155Test is Test { function test_MintEdition_Success() public { vm.prank(device); - uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); vm.prank(device); uint256 editionId = lensMint.mintEdition(recipient, originalId); @@ -132,7 +132,7 @@ contract LensMintERC1155Test is Test { function test_MintEdition_Revert_TokenIsNotAnOriginal() public { vm.prank(device); - uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); vm.prank(device); uint256 editionId = lensMint.mintEdition(recipient, originalId); @@ -143,7 +143,7 @@ contract LensMintERC1155Test is Test { function test_MintEdition_Revert_MaxEditionsReached() public { vm.prank(device); - uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 1); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 1); vm.prank(device); lensMint.mintEdition(recipient, originalId); @@ -158,7 +158,7 @@ contract LensMintERC1155Test is Test { function test_BatchMintEditions_Success() public { vm.prank(device); - uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); vm.prank(device); uint256[] memory tokenIds = lensMint.batchMintEditions(recipient, originalId, 3); @@ -175,7 +175,7 @@ contract LensMintERC1155Test is Test { function test_BatchMintEditions_Revert_QuantityMustBeGreaterThanZero() public { vm.prank(device); - uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); vm.prank(device); vm.expectRevert(LensMintERC1155.QuantityMustBeGreaterThanZero.selector); lensMint.batchMintEditions(recipient, originalId, 0); @@ -183,7 +183,7 @@ contract LensMintERC1155Test is Test { function test_BatchMintEditions_Revert_MaxEditionsReached() public { vm.prank(device); - uint256 originalId = lensMint.mintOriginal(owner, IPFS_HASH, IMAGE_HASH, SIGNATURE, 2); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 2); vm.prank(device); vm.expectRevert(LensMintERC1155.MaxEditionsReached.selector); lensMint.batchMintEditions(recipient, originalId, 3); From 1136ab9e2af701803037bb1901e8ab8e6806a14d Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 09:34:42 +0000 Subject: [PATCH 04/15] add: test files for LensMintERC1155.sol --- contracts/test/LensMintVerifier.t.sol | 150 ++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 contracts/test/LensMintVerifier.t.sol diff --git a/contracts/test/LensMintVerifier.t.sol b/contracts/test/LensMintVerifier.t.sol new file mode 100644 index 0000000..add2191 --- /dev/null +++ b/contracts/test/LensMintVerifier.t.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.31; + +import "forge-std/Test.sol"; +import "../src/LensMintVerifier.sol"; + +/// @notice Mock that matches IRiscZeroVerifier.verify(bytes,bytes32,bytes32) used by LensMintVerifier. +contract MockRiscZeroVerifier { + bool public shouldRevert; + + function setShouldRevert(bool _revert) external { + shouldRevert = _revert; + } + + function verify(bytes calldata, bytes32, bytes32) external view { + if (shouldRevert) revert("MockRiscZeroVerifier: verify failed"); + } +} + +/// @notice Unit tests for LensMintVerifier (constructor, submitMetadata validation, getters). +/// @dev Uses MockRiscZeroVerifier; full ZK proof flow is integration-only. +contract LensMintVerifierTest is Test { + LensMintVerifier public verifier; + MockRiscZeroVerifier public mockVerifier; + + bytes32 constant IMAGE_ID = bytes32(uint256(1)); + bytes32 constant NOTARY_KEY = keccak256("notary-key"); + bytes32 constant QUERIES_HASH = keccak256("queries-hash"); + string constant URL_PATTERN = "https://lensmint.example/"; + + function setUp() public { + mockVerifier = new MockRiscZeroVerifier(); + verifier = new LensMintVerifier(address(mockVerifier), IMAGE_ID, NOTARY_KEY, QUERIES_HASH, URL_PATTERN); + } + + function _validJournalData() internal view returns (bytes memory) { + return abi.encode( + NOTARY_KEY, + "GET", + "https://lensmint.example/claim/abc-123", // This is the URL pattern we expect + uint256(block.timestamp), + QUERIES_HASH, + "extracted-data-json" + ); + } + + // ------------------------------------------------------------------------- + // Constructor / immutables + // ------------------------------------------------------------------------- + + function test_Constructor_Success() public view { + assertEq(address(verifier.VERIFIER()), address(mockVerifier)); + assertEq(verifier.IMAGE_ID(), IMAGE_ID); + assertEq(verifier.EXPECTED_NOTARY_KEY_FINGERPRINT(), NOTARY_KEY); + assertEq(verifier.EXPECTED_QUERIES_HASH(), QUERIES_HASH); + assertEq(verifier.expectedUrlPattern(), URL_PATTERN); + } + + // ------------------------------------------------------------------------- + // getVerifiedMetadata / getClaimIdByTokenId (defaults) + // ------------------------------------------------------------------------- + + function test_GetVerifiedMetadata_UnknownClaimReturnsDefaults() public view { + LensMintVerifier.VerifiedMetadata memory m = verifier.getVerifiedMetadata("unknown"); + assertEq(m.timestamp, 0); + assertFalse(m.verified); + assertEq(bytes(m.signature).length, 0); + } + + function test_GetClaimIdByTokenId_UnknownReturnsEmpty() public view { + assertEq(verifier.getClaimIdByTokenId(1), ""); + } + + // ------------------------------------------------------------------------- + // submitMetadata validation (reverts before calling verifier) + // ------------------------------------------------------------------------- + + function test_SubmitMetadata_Revert_InvalidNotaryKeyFingerprint() public { + bytes memory journalData = abi.encode( + bytes32(0), "GET", "https://lensmint.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "data" + ); + bytes32 journalHash = sha256(journalData); + vm.expectRevert(LensMintVerifier.InvalidNotaryKeyFingerprint.selector); + verifier.submitMetadata("claim-1", journalData, abi.encode(journalHash)); + } + + function test_SubmitMetadata_Revert_InvalidUrl_WrongMethod() public { + bytes memory journalData = abi.encode( + NOTARY_KEY, "POST", "https://lensmint.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "data" + ); + vm.expectRevert(LensMintVerifier.InvalidUrl.selector); + verifier.submitMetadata("claim-1", journalData, ""); + } + + function test_SubmitMetadata_Revert_InvalidQueriesHash() public { + bytes memory journalData = abi.encode( + NOTARY_KEY, "GET", "https://lensmint.example/claim/x", uint256(block.timestamp), bytes32(0), "data" + ); + vm.expectRevert(LensMintVerifier.InvalidQueriesHash.selector); + verifier.submitMetadata("claim-1", journalData, ""); + } + + function test_SubmitMetadata_Revert_InvalidUrl_TooShort() public { + bytes memory journalData = abi.encode(NOTARY_KEY, "GET", "ht", uint256(block.timestamp), QUERIES_HASH, "data"); + vm.expectRevert(LensMintVerifier.InvalidUrl.selector); + verifier.submitMetadata("claim-1", journalData, ""); + } + + function test_SubmitMetadata_Revert_InvalidUrl_PatternMismatch() public { + bytes memory journalData = abi.encode( + NOTARY_KEY, "GET", "https://evil.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "data" + ); + vm.expectRevert(LensMintVerifier.InvalidUrl.selector); + verifier.submitMetadata("claim-1", journalData, ""); + } + + function test_SubmitMetadata_Revert_InvalidMetadata() public { + bytes memory journalData = abi.encode( + NOTARY_KEY, "GET", "https://lensmint.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "" + ); + vm.expectRevert(LensMintVerifier.InvalidMetadata.selector); + verifier.submitMetadata("claim-1", journalData, ""); + } + + // ------------------------------------------------------------------------- + // submitMetadata ZK verification (mock reverts => ZKProofVerificationFailed) + // ------------------------------------------------------------------------- + + function test_SubmitMetadata_Revert_ZKProofVerificationFailed() public { + mockVerifier.setShouldRevert(true); + bytes memory journalData = _validJournalData(); + bytes32 journalHash = sha256(journalData); + bytes memory seal = abi.encode(journalHash); + + vm.expectRevert(LensMintVerifier.ZKProofVerificationFailed.selector); + verifier.submitMetadata("claim-1", journalData, seal); + } + + function test_SubmitMetadata_Success_WhenMockSucceeds() public { + bytes memory journalData = _validJournalData(); + bytes32 journalHash = sha256(journalData); + bytes memory seal = abi.encode(journalHash); + + verifier.submitMetadata("claim-1", journalData, seal); + + LensMintVerifier.VerifiedMetadata memory m = verifier.getVerifiedMetadata("claim-1"); + assertTrue(m.verified); + assertEq(m.timestamp, block.timestamp); + } +} From a73fccef9074628b4e82cd1cdf81abc7cb8ff303 Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 09:36:04 +0000 Subject: [PATCH 05/15] fix: indent and structure --- contracts/test/MintEditionDebug.t.sol | 60 +++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/contracts/test/MintEditionDebug.t.sol b/contracts/test/MintEditionDebug.t.sol index d7c85c1..c3abce1 100644 --- a/contracts/test/MintEditionDebug.t.sol +++ b/contracts/test/MintEditionDebug.t.sol @@ -1,26 +1,26 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.31; -import "forge-std/Test.sol"; -import "forge-std/console.sol"; -import "../src/LensMintERC1155.sol"; -import "../src/DeviceRegistry.sol"; +import {Test} from "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; +import {LensMintERC1155} from "../src/LensMintERC1155.sol"; +import {DeviceRegistry} from "../src/DeviceRegistry.sol"; contract MintEditionDebugTest is Test { DeviceRegistry public deviceRegistry; LensMintERC1155 public lensMint; - + address public deviceAddress; address public recipient = 0x1B8b939710c5b61EA4ab0bD4524Cbe92c06bdA71; uint256 private deviceKey; - + function setUp() public { deviceRegistry = new DeviceRegistry(); lensMint = new LensMintERC1155(address(deviceRegistry), "https://ipfs.io/ipfs/"); - + deviceKey = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; deviceAddress = vm.addr(deviceKey); - + deviceRegistry.registerDevice( deviceAddress, "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", @@ -29,13 +29,13 @@ contract MintEditionDebugTest is Test { "Raspberry Pi 4", "1.0.0" ); - + deviceRegistry.updateDevice(deviceAddress, "1.0.0", true); } - + function testMintEdition() public { address owner = address(0x1234567890123456789012345678901234567890); - + vm.prank(deviceAddress); uint256 originalTokenId = lensMint.mintOriginal( owner, @@ -44,31 +44,31 @@ contract MintEditionDebugTest is Test { "0xsignature123", 0 ); - + console.log("Original Token ID:", originalTokenId); - + LensMintERC1155.TokenMetadata memory metadata = lensMint.getTokenMetadata(originalTokenId); console.log("Token deviceAddress:", metadata.deviceAddress); console.log("Token isOriginal:", metadata.isOriginal); console.log("Token maxEditions:", metadata.maxEditions); - + assertTrue(metadata.deviceAddress != address(0), "Token should exist"); assertTrue(metadata.isOriginal, "Token should be original"); - + uint256 editionCount = lensMint.getEditionCount(originalTokenId); console.log("Edition count before:", editionCount); - + vm.prank(deviceAddress); uint256 editionTokenId = lensMint.mintEdition(recipient, originalTokenId); - + console.log("Edition Token ID:", editionTokenId); - + uint256 balance = lensMint.balanceOf(recipient, editionTokenId); assertEq(balance, 1, "Recipient should have 1 edition"); - + console.log("Edition minted successfully!"); } - + function testMintEditionToMetaMaskAddress() public { address owner = address(0x1234567890123456789012345678901234567890); vm.prank(deviceAddress); @@ -79,37 +79,37 @@ contract MintEditionDebugTest is Test { "0xsignature123", 0 ); - + console.log("Original Token ID:", originalTokenId); console.log("Recipient address:", recipient); console.log("Recipient code length:", recipient.code.length); - + vm.prank(deviceAddress); uint256 editionTokenId = lensMint.mintEdition(recipient, originalTokenId); - + console.log("Edition Token ID:", editionTokenId); - + uint256 balance = lensMint.balanceOf(recipient, editionTokenId); assertEq(balance, 1, "Recipient should have 1 edition"); - + console.log("SUCCESS: Edition minted to MetaMask address!"); } - + function testMintEditionWithToken5() public { LensMintERC1155.TokenMetadata memory metadata = lensMint.getTokenMetadata(5); console.log("Token 5 deviceAddress:", metadata.deviceAddress); console.log("Token 5 isOriginal:", metadata.isOriginal); - + if (metadata.deviceAddress == address(0)) { console.log("ERROR: Token 5 does not exist"); return; } - + if (!metadata.isOriginal) { console.log("ERROR: Token 5 is not an original"); return; } - + vm.prank(deviceAddress); try lensMint.mintEdition(recipient, 5) returns (uint256 editionTokenId) { console.log("SUCCESS: Edition minted! Token ID:", editionTokenId); From 0e80eae159863b8a4c8cb079b17e04fb337336a1 Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 09:39:46 +0000 Subject: [PATCH 06/15] restucture: unit integration separated --- contracts/test/unit/DeviceRegistry.t.sol | 487 +++++++++++++++++++++ contracts/test/unit/LensMintERC1155.t.sol | 250 +++++++++++ contracts/test/unit/LensMintVerifier.t.sol | 150 +++++++ 3 files changed, 887 insertions(+) create mode 100644 contracts/test/unit/DeviceRegistry.t.sol create mode 100644 contracts/test/unit/LensMintERC1155.t.sol create mode 100644 contracts/test/unit/LensMintVerifier.t.sol diff --git a/contracts/test/unit/DeviceRegistry.t.sol b/contracts/test/unit/DeviceRegistry.t.sol new file mode 100644 index 0000000..854559a --- /dev/null +++ b/contracts/test/unit/DeviceRegistry.t.sol @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.31; + +import "forge-std/Test.sol"; +import "../../src/DeviceRegistry.sol"; + +contract DeviceRegistryTest is Test { + DeviceRegistry private registry; + + // Mirror contract events so we can use vm.expectEmit with (emitter) + event DeviceRegistered( + address indexed deviceAddress, + string deviceId, + string publicKey, + address indexed registeredBy + ); + event DeviceUpdated(address indexed deviceAddress, string deviceId, bool isActive); + event DeviceDeactivated(address indexed deviceAddress, string deviceId); + + address private registrar; // address that registers devices + address private deviceAddress1; // first device wallet + address private deviceAddress2; // second device wallet + address private attacker; // unauthorized actor + + string private constant PUBLIC_KEY = + "0x04e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3"; + string private constant DEVICE_ID_1 = "device-001"; + string private constant DEVICE_ID_2 = "device-002"; + string private constant CAMERA_ID_1 = "camera-001"; + string private constant CAMERA_ID_2 = "camera-002"; + string private constant MODEL = "LensMint Pi"; + string private constant FW_V1 = "1.0.0"; // firmware version 1.0.0 + string private constant FW_V2 = "1.1.0"; // firmware version 1.1.0 + + function setUp() public { + registry = new DeviceRegistry(); + + registrar = address(this); + deviceAddress1 = address(0xD1); + deviceAddress2 = address(0xD2); + attacker = address(0xBAD); + } + + // ------------------------------------------------------------------------- + // registerDevice + // ------------------------------------------------------------------------- + + function test_RegisterDevice_Success() public { + bool ok = registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + assertTrue(ok, "registerDevice should return true"); + + DeviceRegistry.DeviceInfo memory info = registry.getDevice( + deviceAddress1 + ); + + assertEq(info.deviceAddress, deviceAddress1, "deviceAddress stored"); + assertEq(info.publicKey, PUBLIC_KEY, "publicKey stored"); + assertEq(info.deviceId, DEVICE_ID_1, "deviceId stored"); + assertEq(info.cameraId, CAMERA_ID_1, "cameraId stored"); + assertEq(info.model, MODEL, "model stored"); + assertEq(info.firmwareVersion, FW_V1, "firmware stored"); + assertEq(info.registeredBy, registrar, "registeredBy is caller"); + assertEq(info.registrationTime, block.timestamp, "registrationTime is current timestamp"); + assertTrue(info.isActive, "device should start active"); + + address deviceAddressById = registry.getDeviceByDeviceId(DEVICE_ID_1); + assertEq(deviceAddressById, deviceAddress1, "deviceIdToAddress mapping set"); + + uint256 totalDevices = registry.getTotalDevices(); + assertEq(totalDevices, 1, "one device registered"); + + address[] memory all = registry.getAllDevices(); + assertEq(all.length, 1, "one element in array"); + assertEq(all[0], deviceAddress1, "array contains device"); + } + + function test_RegisterDevice_Revert_InvalidDeviceAddress() public { + vm.expectRevert(DeviceRegistry.InvalidDeviceAddress.selector); + registry.registerDevice( + address(0), + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + } + + function test_RegisterDevice_Revert_PublicKeyRequired() public { + vm.expectRevert(DeviceRegistry.PublicKeyRequired.selector); + registry.registerDevice( + deviceAddress1, + "", + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + } + + function test_RegisterDevice_Revert_DeviceIdRequired() public { + vm.expectRevert(DeviceRegistry.DeviceIdRequired.selector); + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + "", + CAMERA_ID_1, + MODEL, + FW_V1 + ); + } + + function test_RegisterDevice_Revert_CameraIdRequired() public { + vm.expectRevert(DeviceRegistry.CameraIdRequired.selector); + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + "", + MODEL, + FW_V1 + ); + } + + function test_RegisterDevice_Revert_DeviceAlreadyRegistered() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.expectRevert(DeviceRegistry.DeviceAlreadyRegistered.selector); + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_2, + CAMERA_ID_2, + MODEL, + FW_V1 + ); + } + + function test_RegisterDevice_Revert_DeviceIdAlreadyInUse() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.expectRevert(DeviceRegistry.DeviceIdAlreadyInUse.selector); + registry.registerDevice( + deviceAddress2, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_2, + MODEL, + FW_V1 + ); + } + + // ------------------------------------------------------------------------- + // updateDevice + // ------------------------------------------------------------------------- + + function test_UpdateDevice_Success_ByRegistrar() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + bool ok = registry.updateDevice( + deviceAddress1, + FW_V2, + false + ); + assertTrue(ok, "updateDevice should return true"); + + DeviceRegistry.DeviceInfo memory info = registry.getDevice( + deviceAddress1 + ); + assertEq(info.firmwareVersion, FW_V2, "firmware updated"); + assertFalse(info.isActive, "isActive updated"); + } + + function test_UpdateDevice_Success_ByDeviceAddress() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.prank(deviceAddress1); + bool ok = registry.updateDevice( + deviceAddress1, + FW_V2, + true + ); + assertTrue(ok, "updateDevice should return true for device"); + + DeviceRegistry.DeviceInfo memory info = registry.getDevice( + deviceAddress1 + ); + assertEq(info.firmwareVersion, FW_V2, "firmware updated"); + assertTrue(info.isActive, "isActive updated"); + } + + function test_UpdateDevice_Revert_DeviceNotRegistered() public { + vm.expectRevert(DeviceRegistry.DeviceNotRegistered.selector); + registry.updateDevice(deviceAddress1, FW_V2, true); + } + + function test_UpdateDevice_Revert_FirmwareVersionRequired() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.expectRevert(DeviceRegistry.FirmwareVersionRequired.selector); + registry.updateDevice(deviceAddress1, "", true); + } + + function test_UpdateDevice_Revert_NotAuthorizedToUpdate() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.prank(attacker); + vm.expectRevert(DeviceRegistry.NotAuthorizedToUpdate.selector); + registry.updateDevice(deviceAddress1, FW_V2, true); + } + + // ------------------------------------------------------------------------- + // deactivateDevice + // ------------------------------------------------------------------------- + + function test_DeactivateDevice_Success_ByRegistrar() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + bool ok = registry.deactivateDevice(deviceAddress1); + assertTrue(ok, "deactivateDevice should return true"); + + bool active = registry.isDeviceActive(deviceAddress1); + assertFalse(active, "device should be inactive"); + } + + function test_DeactivateDevice_Success_ByDeviceAddress() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.prank(deviceAddress1); + bool ok = registry.deactivateDevice(deviceAddress1); + assertTrue(ok, "deactivateDevice should return true for device"); + + bool active = registry.isDeviceActive(deviceAddress1); + assertFalse(active, "device should be inactive"); + } + + function test_DeactivateDevice_Revert_DeviceNotRegistered() public { + vm.expectRevert(DeviceRegistry.DeviceNotRegistered.selector); + registry.deactivateDevice(deviceAddress1); + } + + function test_DeactivateDevice_Revert_NotAuthorizedToDeactivate() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.prank(attacker); + vm.expectRevert(DeviceRegistry.NotAuthorizedToDeactivate.selector); + registry.deactivateDevice(deviceAddress1); + } + + // ------------------------------------------------------------------------- + // isDeviceActive / getters + // ------------------------------------------------------------------------- + + function test_IsDeviceActive_FlagReflectsState() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + bool active = registry.isDeviceActive(deviceAddress1); + assertTrue(active, "device should start active"); + + registry.deactivateDevice(deviceAddress1); + active = registry.isDeviceActive(deviceAddress1); + assertFalse(active, "device should be inactive after deactivation"); + } + + function test_IsDeviceActive_UnregisteredReturnsFalse() public view { + bool active = registry.isDeviceActive(deviceAddress1); + assertFalse(active, "unregistered device should not be active"); + } + + // ------------------------------------------------------------------------- + // getDevice / getDeviceByDeviceId (edge cases) + // ------------------------------------------------------------------------- + + function test_GetDevice_UnregisteredReturnsEmptyStruct() public view { + DeviceRegistry.DeviceInfo memory info = registry.getDevice(deviceAddress1); + assertEq(info.deviceAddress, address(0), "unregistered device has zero address"); + assertEq(info.registeredBy, address(0), "unregistered has no registrar"); + assertEq(info.registrationTime, 0, "unregistered has zero time"); + assertFalse(info.isActive, "unregistered is not active"); + assertEq(bytes(info.deviceId).length, 0, "unregistered has empty deviceId"); + } + + function test_GetDeviceByDeviceId_UnknownReturnsZero() public view { + address addr = registry.getDeviceByDeviceId("nonexistent-id"); + assertEq(addr, address(0), "unknown deviceId should return zero address"); + } + + // ------------------------------------------------------------------------- + // getTotalDevices / getAllDevices (multiple devices) + // ------------------------------------------------------------------------- + + function test_GetTotalDevices_MultipleDevices() public { + assertEq(registry.getTotalDevices(), 0, "starts at zero"); + + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + assertEq(registry.getTotalDevices(), 1, "one after first register"); + + registry.registerDevice( + deviceAddress2, + PUBLIC_KEY, + DEVICE_ID_2, + CAMERA_ID_2, + MODEL, + FW_V1 + ); + assertEq(registry.getTotalDevices(), 2, "two after second register"); + } + + function test_GetAllDevices_OrderAndContent() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + registry.registerDevice( + deviceAddress2, + PUBLIC_KEY, + DEVICE_ID_2, + CAMERA_ID_2, + MODEL, + FW_V1 + ); + + address[] memory all = registry.getAllDevices(); + assertEq(all.length, 2, "two devices"); + assertEq(all[0], deviceAddress1, "first is device1"); + assertEq(all[1], deviceAddress2, "second is device2"); + } + + // ------------------------------------------------------------------------- + // Re-activation (deactivate then updateDevice(..., true)) + // ------------------------------------------------------------------------- + + function test_UpdateDevice_ReactivationAfterDeactivate() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + registry.deactivateDevice(deviceAddress1); + assertFalse(registry.isDeviceActive(deviceAddress1), "device inactive"); + + registry.updateDevice(deviceAddress1, FW_V2, true); + assertTrue(registry.isDeviceActive(deviceAddress1), "device active again"); + DeviceRegistry.DeviceInfo memory info = registry.getDevice(deviceAddress1); + assertEq(info.firmwareVersion, FW_V2, "firmware updated on reactivation"); + } + + // ------------------------------------------------------------------------- + // Events + // ------------------------------------------------------------------------- + + function test_RegisterDevice_EmitsDeviceRegistered() public { + vm.expectEmit(true, true, true, true, address(registry)); + emit DeviceRegistered(deviceAddress1, DEVICE_ID_1, PUBLIC_KEY, registrar); + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + } + + function test_UpdateDevice_EmitsDeviceUpdated() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.expectEmit(true, true, true, true, address(registry)); + emit DeviceUpdated(deviceAddress1, DEVICE_ID_1, false); + registry.updateDevice(deviceAddress1, FW_V2, false); + } + + function test_DeactivateDevice_EmitsDeviceDeactivated() public { + registry.registerDevice( + deviceAddress1, + PUBLIC_KEY, + DEVICE_ID_1, + CAMERA_ID_1, + MODEL, + FW_V1 + ); + + vm.expectEmit(true, true, true, true, address(registry)); + emit DeviceDeactivated(deviceAddress1, DEVICE_ID_1); + registry.deactivateDevice(deviceAddress1); + } +} + diff --git a/contracts/test/unit/LensMintERC1155.t.sol b/contracts/test/unit/LensMintERC1155.t.sol new file mode 100644 index 0000000..4cbe7bb --- /dev/null +++ b/contracts/test/unit/LensMintERC1155.t.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.31; + +import {Test} from "forge-std/Test.sol"; +import {LensMintERC1155} from "../src/LensMintERC1155.sol"; // solhint-disable-line +import {DeviceRegistry} from "../src/DeviceRegistry.sol"; // solhint-disable-line + +/// @notice Unit tests for LensMintERC1155 (constructor, mintOriginal, mintEdition, batchMintEditions, getters, access control). +contract LensMintERC1155Test is Test { + DeviceRegistry public deviceRegistry; + LensMintERC1155 public lensMint; + + address public owner; + address public device; + address public recipient; + address public stranger; + + string constant BASE_URI = "https://ipfs.io/ipfs/"; + string constant IPFS_HASH = "QmTest123"; + string constant IMAGE_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"; + string constant SIGNATURE = "0xsignature123"; + string constant PUBLIC_KEY = "0x04e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3"; + string constant DEVICE_ID = "device-001"; + string constant CAMERA_ID = "camera-001"; + string constant MODEL = "LensMint Pi"; + string constant FW = "1.0.0"; + + event TokenMinted( + uint256 indexed tokenId, address indexed deviceAddress, string deviceId, string ipfsHash, bool isOriginal + ); + event EditionMinted(uint256 indexed tokenId, uint256 indexed originalTokenId, address indexed to); + event BaseURIUpdated(string newBaseURI); + + function setUp() public { + deviceRegistry = new DeviceRegistry(); + lensMint = new LensMintERC1155(address(deviceRegistry), BASE_URI); + + owner = address(this); + device = address(0xD1); + recipient = address(0xD2); + stranger = address(0xBAD); + + deviceRegistry.registerDevice(device, PUBLIC_KEY, DEVICE_ID, CAMERA_ID, MODEL, FW); + } + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + function test_Constructor_Success() public view { + assertEq(address(lensMint.deviceRegistry()), address(deviceRegistry)); + assertEq(lensMint.baseURI(), BASE_URI); + assertEq(lensMint.owner(), owner); + assertEq(lensMint.totalTokens(), 0); + } + + function test_Constructor_Revert_DeviceRegistryAddressIsZero() public { + vm.expectRevert(LensMintERC1155.DeviceRegistryAddressIsZero.selector); + new LensMintERC1155(address(0), BASE_URI); + } + + function test_Constructor_Revert_BaseURIEmptyString() public { + vm.expectRevert(LensMintERC1155.BaseURIEmptyString.selector); + new LensMintERC1155(address(deviceRegistry), ""); + } + + // ------------------------------------------------------------------------- + // mintOriginal + // ------------------------------------------------------------------------- + + function test_MintOriginal_Success() public { + vm.prank(device); + uint256 tokenId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + + assertEq(tokenId, 1); + assertEq(lensMint.totalTokens(), 1); + assertEq(lensMint.balanceOf(recipient, tokenId), 1); + assertEq(lensMint.getEditionCount(tokenId), 1); + + LensMintERC1155.TokenMetadata memory m = lensMint.getTokenMetadata(tokenId); + assertEq(m.deviceAddress, device); + assertTrue(m.isOriginal); + assertEq(m.ipfsHash, IPFS_HASH); + assertEq(m.imageHash, IMAGE_HASH); + assertEq(m.signature, SIGNATURE); + assertEq(m.maxEditions, 0); + assertEq(m.originalTokenId, tokenId); + assertEq(m.deviceId, DEVICE_ID); + } + + function test_MintOriginal_Revert_DeviceNotRegisteredOrInactive() public { + vm.prank(stranger); + vm.expectRevert(LensMintERC1155.DeviceNotRegisteredOrInactive.selector); + lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + } + + function test_MintOriginal_Revert_WhenDeviceDeactivated() public { + deviceRegistry.deactivateDevice(device); + vm.prank(device); + vm.expectRevert(LensMintERC1155.DeviceNotRegisteredOrInactive.selector); + lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + } + + // ------------------------------------------------------------------------- + // mintEdition + // ------------------------------------------------------------------------- + + function test_MintEdition_Success() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + + vm.prank(device); + uint256 editionId = lensMint.mintEdition(recipient, originalId); + + assertEq(editionId, 2); + assertEq(lensMint.totalTokens(), 2); + assertEq(lensMint.balanceOf(recipient, editionId), 1); + assertEq(lensMint.getEditionCount(originalId), 2); + + LensMintERC1155.TokenMetadata memory m = lensMint.getTokenMetadata(editionId); + assertEq(m.deviceAddress, device); + assertFalse(m.isOriginal); + assertEq(m.originalTokenId, originalId); + assertEq(m.ipfsHash, IPFS_HASH); + } + + function test_MintEdition_Revert_TokenDoesNotExist() public { + vm.prank(device); + vm.expectRevert(LensMintERC1155.TokenDoesNotExist.selector); + lensMint.mintEdition(recipient, 999); + } + + function test_MintEdition_Revert_TokenIsNotAnOriginal() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + vm.prank(device); + uint256 editionId = lensMint.mintEdition(recipient, originalId); + + vm.prank(device); + vm.expectRevert(LensMintERC1155.TokenIsNotAnOriginal.selector); + lensMint.mintEdition(recipient, editionId); + } + + function test_MintEdition_Revert_MaxEditionsReached() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 1); + vm.prank(device); + lensMint.mintEdition(recipient, originalId); + + vm.prank(device); + vm.expectRevert(LensMintERC1155.MaxEditionsReached.selector); + lensMint.mintEdition(recipient, originalId); + } + + // ------------------------------------------------------------------------- + // batchMintEditions + // ------------------------------------------------------------------------- + + function test_BatchMintEditions_Success() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + + vm.prank(device); + uint256[] memory tokenIds = lensMint.batchMintEditions(recipient, originalId, 3); + + assertEq(tokenIds.length, 3); + assertEq(tokenIds[0], 2); + assertEq(tokenIds[1], 3); + assertEq(tokenIds[2], 4); + assertEq(lensMint.getEditionCount(originalId), 4); + assertEq(lensMint.balanceOf(recipient, 2), 1); + assertEq(lensMint.balanceOf(recipient, 3), 1); + assertEq(lensMint.balanceOf(recipient, 4), 1); + } + + function test_BatchMintEditions_Revert_QuantityMustBeGreaterThanZero() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + vm.prank(device); + vm.expectRevert(LensMintERC1155.QuantityMustBeGreaterThanZero.selector); + lensMint.batchMintEditions(recipient, originalId, 0); + } + + function test_BatchMintEditions_Revert_MaxEditionsReached() public { + vm.prank(device); + uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 2); + vm.prank(device); + vm.expectRevert(LensMintERC1155.MaxEditionsReached.selector); + lensMint.batchMintEditions(recipient, originalId, 3); + } + + // ------------------------------------------------------------------------- + // uri / getTokenMetadata / getEditionCount + // ------------------------------------------------------------------------- + + function test_Uri_Revert_TokenDoesNotExist() public { + vm.expectRevert(LensMintERC1155.TokenDoesNotExist.selector); + lensMint.uri(1); + } + + function test_Uri_ReturnsCorrectFormat() public { + vm.prank(device); + uint256 tokenId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); + assertEq(lensMint.uri(tokenId), string(abi.encodePacked(BASE_URI, "1"))); + } + + function test_GetTokenMetadata_NonExistentReturnsDefaults() public view { + LensMintERC1155.TokenMetadata memory m = lensMint.getTokenMetadata(1); + assertEq(m.deviceAddress, address(0)); + assertEq(m.originalTokenId, 0); + } + + function test_GetEditionCount_NonExistentReturnsZero() public view { + assertEq(lensMint.getEditionCount(1), 0); + } + + // ------------------------------------------------------------------------- + // setBaseURI (onlyOwner) + // ------------------------------------------------------------------------- + + function test_SetBaseURI_Success() public { + string memory newUri = "https://new.base/"; + vm.prank(owner); + lensMint.setBaseURI(newUri); + assertEq(lensMint.baseURI(), newUri); + } + + function test_SetBaseURI_Revert_NotOwner() public { + vm.prank(stranger); + vm.expectRevert(); + lensMint.setBaseURI("https://evil/"); + } + + // ------------------------------------------------------------------------- + // canDeviceMint + // ------------------------------------------------------------------------- + + function test_CanDeviceMint_TrueWhenActive() public view { + assertTrue(lensMint.canDeviceMint(device)); + } + + function test_CanDeviceMint_FalseWhenInactive() public { + deviceRegistry.deactivateDevice(device); + assertFalse(lensMint.canDeviceMint(device)); + } + + function test_CanDeviceMint_FalseWhenUnregistered() public view { + assertFalse(lensMint.canDeviceMint(stranger)); + } +} diff --git a/contracts/test/unit/LensMintVerifier.t.sol b/contracts/test/unit/LensMintVerifier.t.sol new file mode 100644 index 0000000..add2191 --- /dev/null +++ b/contracts/test/unit/LensMintVerifier.t.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.31; + +import "forge-std/Test.sol"; +import "../src/LensMintVerifier.sol"; + +/// @notice Mock that matches IRiscZeroVerifier.verify(bytes,bytes32,bytes32) used by LensMintVerifier. +contract MockRiscZeroVerifier { + bool public shouldRevert; + + function setShouldRevert(bool _revert) external { + shouldRevert = _revert; + } + + function verify(bytes calldata, bytes32, bytes32) external view { + if (shouldRevert) revert("MockRiscZeroVerifier: verify failed"); + } +} + +/// @notice Unit tests for LensMintVerifier (constructor, submitMetadata validation, getters). +/// @dev Uses MockRiscZeroVerifier; full ZK proof flow is integration-only. +contract LensMintVerifierTest is Test { + LensMintVerifier public verifier; + MockRiscZeroVerifier public mockVerifier; + + bytes32 constant IMAGE_ID = bytes32(uint256(1)); + bytes32 constant NOTARY_KEY = keccak256("notary-key"); + bytes32 constant QUERIES_HASH = keccak256("queries-hash"); + string constant URL_PATTERN = "https://lensmint.example/"; + + function setUp() public { + mockVerifier = new MockRiscZeroVerifier(); + verifier = new LensMintVerifier(address(mockVerifier), IMAGE_ID, NOTARY_KEY, QUERIES_HASH, URL_PATTERN); + } + + function _validJournalData() internal view returns (bytes memory) { + return abi.encode( + NOTARY_KEY, + "GET", + "https://lensmint.example/claim/abc-123", // This is the URL pattern we expect + uint256(block.timestamp), + QUERIES_HASH, + "extracted-data-json" + ); + } + + // ------------------------------------------------------------------------- + // Constructor / immutables + // ------------------------------------------------------------------------- + + function test_Constructor_Success() public view { + assertEq(address(verifier.VERIFIER()), address(mockVerifier)); + assertEq(verifier.IMAGE_ID(), IMAGE_ID); + assertEq(verifier.EXPECTED_NOTARY_KEY_FINGERPRINT(), NOTARY_KEY); + assertEq(verifier.EXPECTED_QUERIES_HASH(), QUERIES_HASH); + assertEq(verifier.expectedUrlPattern(), URL_PATTERN); + } + + // ------------------------------------------------------------------------- + // getVerifiedMetadata / getClaimIdByTokenId (defaults) + // ------------------------------------------------------------------------- + + function test_GetVerifiedMetadata_UnknownClaimReturnsDefaults() public view { + LensMintVerifier.VerifiedMetadata memory m = verifier.getVerifiedMetadata("unknown"); + assertEq(m.timestamp, 0); + assertFalse(m.verified); + assertEq(bytes(m.signature).length, 0); + } + + function test_GetClaimIdByTokenId_UnknownReturnsEmpty() public view { + assertEq(verifier.getClaimIdByTokenId(1), ""); + } + + // ------------------------------------------------------------------------- + // submitMetadata validation (reverts before calling verifier) + // ------------------------------------------------------------------------- + + function test_SubmitMetadata_Revert_InvalidNotaryKeyFingerprint() public { + bytes memory journalData = abi.encode( + bytes32(0), "GET", "https://lensmint.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "data" + ); + bytes32 journalHash = sha256(journalData); + vm.expectRevert(LensMintVerifier.InvalidNotaryKeyFingerprint.selector); + verifier.submitMetadata("claim-1", journalData, abi.encode(journalHash)); + } + + function test_SubmitMetadata_Revert_InvalidUrl_WrongMethod() public { + bytes memory journalData = abi.encode( + NOTARY_KEY, "POST", "https://lensmint.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "data" + ); + vm.expectRevert(LensMintVerifier.InvalidUrl.selector); + verifier.submitMetadata("claim-1", journalData, ""); + } + + function test_SubmitMetadata_Revert_InvalidQueriesHash() public { + bytes memory journalData = abi.encode( + NOTARY_KEY, "GET", "https://lensmint.example/claim/x", uint256(block.timestamp), bytes32(0), "data" + ); + vm.expectRevert(LensMintVerifier.InvalidQueriesHash.selector); + verifier.submitMetadata("claim-1", journalData, ""); + } + + function test_SubmitMetadata_Revert_InvalidUrl_TooShort() public { + bytes memory journalData = abi.encode(NOTARY_KEY, "GET", "ht", uint256(block.timestamp), QUERIES_HASH, "data"); + vm.expectRevert(LensMintVerifier.InvalidUrl.selector); + verifier.submitMetadata("claim-1", journalData, ""); + } + + function test_SubmitMetadata_Revert_InvalidUrl_PatternMismatch() public { + bytes memory journalData = abi.encode( + NOTARY_KEY, "GET", "https://evil.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "data" + ); + vm.expectRevert(LensMintVerifier.InvalidUrl.selector); + verifier.submitMetadata("claim-1", journalData, ""); + } + + function test_SubmitMetadata_Revert_InvalidMetadata() public { + bytes memory journalData = abi.encode( + NOTARY_KEY, "GET", "https://lensmint.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "" + ); + vm.expectRevert(LensMintVerifier.InvalidMetadata.selector); + verifier.submitMetadata("claim-1", journalData, ""); + } + + // ------------------------------------------------------------------------- + // submitMetadata ZK verification (mock reverts => ZKProofVerificationFailed) + // ------------------------------------------------------------------------- + + function test_SubmitMetadata_Revert_ZKProofVerificationFailed() public { + mockVerifier.setShouldRevert(true); + bytes memory journalData = _validJournalData(); + bytes32 journalHash = sha256(journalData); + bytes memory seal = abi.encode(journalHash); + + vm.expectRevert(LensMintVerifier.ZKProofVerificationFailed.selector); + verifier.submitMetadata("claim-1", journalData, seal); + } + + function test_SubmitMetadata_Success_WhenMockSucceeds() public { + bytes memory journalData = _validJournalData(); + bytes32 journalHash = sha256(journalData); + bytes memory seal = abi.encode(journalHash); + + verifier.submitMetadata("claim-1", journalData, seal); + + LensMintVerifier.VerifiedMetadata memory m = verifier.getVerifiedMetadata("claim-1"); + assertTrue(m.verified); + assertEq(m.timestamp, block.timestamp); + } +} From eab220a61babef95f9e447fb0deed7240f619f43 Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 09:40:33 +0000 Subject: [PATCH 07/15] restructure: integration testing separated --- .../test/integration/MintEditionDebug.t.sol | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 contracts/test/integration/MintEditionDebug.t.sol diff --git a/contracts/test/integration/MintEditionDebug.t.sol b/contracts/test/integration/MintEditionDebug.t.sol new file mode 100644 index 0000000..c3abce1 --- /dev/null +++ b/contracts/test/integration/MintEditionDebug.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.31; + +import {Test} from "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; +import {LensMintERC1155} from "../src/LensMintERC1155.sol"; +import {DeviceRegistry} from "../src/DeviceRegistry.sol"; + +contract MintEditionDebugTest is Test { + DeviceRegistry public deviceRegistry; + LensMintERC1155 public lensMint; + + address public deviceAddress; + address public recipient = 0x1B8b939710c5b61EA4ab0bD4524Cbe92c06bdA71; + uint256 private deviceKey; + + function setUp() public { + deviceRegistry = new DeviceRegistry(); + lensMint = new LensMintERC1155(address(deviceRegistry), "https://ipfs.io/ipfs/"); + + deviceKey = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + deviceAddress = vm.addr(deviceKey); + + deviceRegistry.registerDevice( + deviceAddress, + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "device-123", + "camera-456", + "Raspberry Pi 4", + "1.0.0" + ); + + deviceRegistry.updateDevice(deviceAddress, "1.0.0", true); + } + + function testMintEdition() public { + address owner = address(0x1234567890123456789012345678901234567890); + + vm.prank(deviceAddress); + uint256 originalTokenId = lensMint.mintOriginal( + owner, + "QmTest123", + "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "0xsignature123", + 0 + ); + + console.log("Original Token ID:", originalTokenId); + + LensMintERC1155.TokenMetadata memory metadata = lensMint.getTokenMetadata(originalTokenId); + console.log("Token deviceAddress:", metadata.deviceAddress); + console.log("Token isOriginal:", metadata.isOriginal); + console.log("Token maxEditions:", metadata.maxEditions); + + assertTrue(metadata.deviceAddress != address(0), "Token should exist"); + assertTrue(metadata.isOriginal, "Token should be original"); + + uint256 editionCount = lensMint.getEditionCount(originalTokenId); + console.log("Edition count before:", editionCount); + + vm.prank(deviceAddress); + uint256 editionTokenId = lensMint.mintEdition(recipient, originalTokenId); + + console.log("Edition Token ID:", editionTokenId); + + uint256 balance = lensMint.balanceOf(recipient, editionTokenId); + assertEq(balance, 1, "Recipient should have 1 edition"); + + console.log("Edition minted successfully!"); + } + + function testMintEditionToMetaMaskAddress() public { + address owner = address(0x1234567890123456789012345678901234567890); + vm.prank(deviceAddress); + uint256 originalTokenId = lensMint.mintOriginal( + owner, + "QmTest123", + "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "0xsignature123", + 0 + ); + + console.log("Original Token ID:", originalTokenId); + console.log("Recipient address:", recipient); + console.log("Recipient code length:", recipient.code.length); + + vm.prank(deviceAddress); + uint256 editionTokenId = lensMint.mintEdition(recipient, originalTokenId); + + console.log("Edition Token ID:", editionTokenId); + + uint256 balance = lensMint.balanceOf(recipient, editionTokenId); + assertEq(balance, 1, "Recipient should have 1 edition"); + + console.log("SUCCESS: Edition minted to MetaMask address!"); + } + + function testMintEditionWithToken5() public { + LensMintERC1155.TokenMetadata memory metadata = lensMint.getTokenMetadata(5); + console.log("Token 5 deviceAddress:", metadata.deviceAddress); + console.log("Token 5 isOriginal:", metadata.isOriginal); + + if (metadata.deviceAddress == address(0)) { + console.log("ERROR: Token 5 does not exist"); + return; + } + + if (!metadata.isOriginal) { + console.log("ERROR: Token 5 is not an original"); + return; + } + + vm.prank(deviceAddress); + try lensMint.mintEdition(recipient, 5) returns (uint256 editionTokenId) { + console.log("SUCCESS: Edition minted! Token ID:", editionTokenId); + } catch Error(string memory reason) { + console.log("ERROR:", reason); + revert(reason); + } catch (bytes memory lowLevelData) { + console.log("ERROR: Low level error"); + console.logBytes(lowLevelData); + revert(); + } + } +} + From dd71a95d496e9086d15c4a98add2aacf43ee6ce8 Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 09:49:05 +0000 Subject: [PATCH 08/15] import fix to restucture and testing --- contracts/test/DeviceRegistry.t.sol | 487 ------------------ contracts/test/LensMintERC1155.t.sol | 250 --------- contracts/test/LensMintVerifier.t.sol | 150 ------ contracts/test/MintEditionDebug.t.sol | 126 ----- .../test/integration/MintEditionDebug.t.sol | 4 +- contracts/test/unit/LensMintERC1155.t.sol | 4 +- contracts/test/unit/LensMintVerifier.t.sol | 2 +- 7 files changed, 5 insertions(+), 1018 deletions(-) delete mode 100644 contracts/test/DeviceRegistry.t.sol delete mode 100644 contracts/test/LensMintERC1155.t.sol delete mode 100644 contracts/test/LensMintVerifier.t.sol delete mode 100644 contracts/test/MintEditionDebug.t.sol diff --git a/contracts/test/DeviceRegistry.t.sol b/contracts/test/DeviceRegistry.t.sol deleted file mode 100644 index cf89bab..0000000 --- a/contracts/test/DeviceRegistry.t.sol +++ /dev/null @@ -1,487 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.31; - -import "forge-std/Test.sol"; -import "../src/DeviceRegistry.sol"; - -contract DeviceRegistryTest is Test { - DeviceRegistry private registry; - - // Mirror contract events so we can use vm.expectEmit with (emitter) - event DeviceRegistered( - address indexed deviceAddress, - string deviceId, - string publicKey, - address indexed registeredBy - ); - event DeviceUpdated(address indexed deviceAddress, string deviceId, bool isActive); - event DeviceDeactivated(address indexed deviceAddress, string deviceId); - - address private registrar; // address that registers devices - address private deviceAddress1; // first device wallet - address private deviceAddress2; // second device wallet - address private attacker; // unauthorized actor - - string private constant PUBLIC_KEY = - "0x04e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3"; - string private constant DEVICE_ID_1 = "device-001"; - string private constant DEVICE_ID_2 = "device-002"; - string private constant CAMERA_ID_1 = "camera-001"; - string private constant CAMERA_ID_2 = "camera-002"; - string private constant MODEL = "LensMint Pi"; - string private constant FW_V1 = "1.0.0"; // firmware version 1.0.0 - string private constant FW_V2 = "1.1.0"; // firmware version 1.1.0 - - function setUp() public { - registry = new DeviceRegistry(); - - registrar = address(this); - deviceAddress1 = address(0xD1); - deviceAddress2 = address(0xD2); - attacker = address(0xBAD); - } - - // ------------------------------------------------------------------------- - // registerDevice - // ------------------------------------------------------------------------- - - function test_RegisterDevice_Success() public { - bool ok = registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - assertTrue(ok, "registerDevice should return true"); - - DeviceRegistry.DeviceInfo memory info = registry.getDevice( - deviceAddress1 - ); - - assertEq(info.deviceAddress, deviceAddress1, "deviceAddress stored"); - assertEq(info.publicKey, PUBLIC_KEY, "publicKey stored"); - assertEq(info.deviceId, DEVICE_ID_1, "deviceId stored"); - assertEq(info.cameraId, CAMERA_ID_1, "cameraId stored"); - assertEq(info.model, MODEL, "model stored"); - assertEq(info.firmwareVersion, FW_V1, "firmware stored"); - assertEq(info.registeredBy, registrar, "registeredBy is caller"); - assertEq(info.registrationTime, block.timestamp, "registrationTime is current timestamp"); - assertTrue(info.isActive, "device should start active"); - - address deviceAddressById = registry.getDeviceByDeviceId(DEVICE_ID_1); - assertEq(deviceAddressById, deviceAddress1, "deviceIdToAddress mapping set"); - - uint256 totalDevices = registry.getTotalDevices(); - assertEq(totalDevices, 1, "one device registered"); - - address[] memory all = registry.getAllDevices(); - assertEq(all.length, 1, "one element in array"); - assertEq(all[0], deviceAddress1, "array contains device"); - } - - function test_RegisterDevice_Revert_InvalidDeviceAddress() public { - vm.expectRevert(DeviceRegistry.InvalidDeviceAddress.selector); - registry.registerDevice( - address(0), - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - } - - function test_RegisterDevice_Revert_PublicKeyRequired() public { - vm.expectRevert(DeviceRegistry.PublicKeyRequired.selector); - registry.registerDevice( - deviceAddress1, - "", - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - } - - function test_RegisterDevice_Revert_DeviceIdRequired() public { - vm.expectRevert(DeviceRegistry.DeviceIdRequired.selector); - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - "", - CAMERA_ID_1, - MODEL, - FW_V1 - ); - } - - function test_RegisterDevice_Revert_CameraIdRequired() public { - vm.expectRevert(DeviceRegistry.CameraIdRequired.selector); - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - "", - MODEL, - FW_V1 - ); - } - - function test_RegisterDevice_Revert_DeviceAlreadyRegistered() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - vm.expectRevert(DeviceRegistry.DeviceAlreadyRegistered.selector); - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_2, - CAMERA_ID_2, - MODEL, - FW_V1 - ); - } - - function test_RegisterDevice_Revert_DeviceIdAlreadyInUse() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - vm.expectRevert(DeviceRegistry.DeviceIdAlreadyInUse.selector); - registry.registerDevice( - deviceAddress2, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_2, - MODEL, - FW_V1 - ); - } - - // ------------------------------------------------------------------------- - // updateDevice - // ------------------------------------------------------------------------- - - function test_UpdateDevice_Success_ByRegistrar() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - bool ok = registry.updateDevice( - deviceAddress1, - FW_V2, - false - ); - assertTrue(ok, "updateDevice should return true"); - - DeviceRegistry.DeviceInfo memory info = registry.getDevice( - deviceAddress1 - ); - assertEq(info.firmwareVersion, FW_V2, "firmware updated"); - assertFalse(info.isActive, "isActive updated"); - } - - function test_UpdateDevice_Success_ByDeviceAddress() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - vm.prank(deviceAddress1); - bool ok = registry.updateDevice( - deviceAddress1, - FW_V2, - true - ); - assertTrue(ok, "updateDevice should return true for device"); - - DeviceRegistry.DeviceInfo memory info = registry.getDevice( - deviceAddress1 - ); - assertEq(info.firmwareVersion, FW_V2, "firmware updated"); - assertTrue(info.isActive, "isActive updated"); - } - - function test_UpdateDevice_Revert_DeviceNotRegistered() public { - vm.expectRevert(DeviceRegistry.DeviceNotRegistered.selector); - registry.updateDevice(deviceAddress1, FW_V2, true); - } - - function test_UpdateDevice_Revert_FirmwareVersionRequired() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - vm.expectRevert(DeviceRegistry.FirmwareVersionRequired.selector); - registry.updateDevice(deviceAddress1, "", true); - } - - function test_UpdateDevice_Revert_NotAuthorizedToUpdate() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - vm.prank(attacker); - vm.expectRevert(DeviceRegistry.NotAuthorizedToUpdate.selector); - registry.updateDevice(deviceAddress1, FW_V2, true); - } - - // ------------------------------------------------------------------------- - // deactivateDevice - // ------------------------------------------------------------------------- - - function test_DeactivateDevice_Success_ByRegistrar() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - bool ok = registry.deactivateDevice(deviceAddress1); - assertTrue(ok, "deactivateDevice should return true"); - - bool active = registry.isDeviceActive(deviceAddress1); - assertFalse(active, "device should be inactive"); - } - - function test_DeactivateDevice_Success_ByDeviceAddress() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - vm.prank(deviceAddress1); - bool ok = registry.deactivateDevice(deviceAddress1); - assertTrue(ok, "deactivateDevice should return true for device"); - - bool active = registry.isDeviceActive(deviceAddress1); - assertFalse(active, "device should be inactive"); - } - - function test_DeactivateDevice_Revert_DeviceNotRegistered() public { - vm.expectRevert(DeviceRegistry.DeviceNotRegistered.selector); - registry.deactivateDevice(deviceAddress1); - } - - function test_DeactivateDevice_Revert_NotAuthorizedToDeactivate() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - vm.prank(attacker); - vm.expectRevert(DeviceRegistry.NotAuthorizedToDeactivate.selector); - registry.deactivateDevice(deviceAddress1); - } - - // ------------------------------------------------------------------------- - // isDeviceActive / getters - // ------------------------------------------------------------------------- - - function test_IsDeviceActive_FlagReflectsState() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - bool active = registry.isDeviceActive(deviceAddress1); - assertTrue(active, "device should start active"); - - registry.deactivateDevice(deviceAddress1); - active = registry.isDeviceActive(deviceAddress1); - assertFalse(active, "device should be inactive after deactivation"); - } - - function test_IsDeviceActive_UnregisteredReturnsFalse() public view { - bool active = registry.isDeviceActive(deviceAddress1); - assertFalse(active, "unregistered device should not be active"); - } - - // ------------------------------------------------------------------------- - // getDevice / getDeviceByDeviceId (edge cases) - // ------------------------------------------------------------------------- - - function test_GetDevice_UnregisteredReturnsEmptyStruct() public view { - DeviceRegistry.DeviceInfo memory info = registry.getDevice(deviceAddress1); - assertEq(info.deviceAddress, address(0), "unregistered device has zero address"); - assertEq(info.registeredBy, address(0), "unregistered has no registrar"); - assertEq(info.registrationTime, 0, "unregistered has zero time"); - assertFalse(info.isActive, "unregistered is not active"); - assertEq(bytes(info.deviceId).length, 0, "unregistered has empty deviceId"); - } - - function test_GetDeviceByDeviceId_UnknownReturnsZero() public view { - address addr = registry.getDeviceByDeviceId("nonexistent-id"); - assertEq(addr, address(0), "unknown deviceId should return zero address"); - } - - // ------------------------------------------------------------------------- - // getTotalDevices / getAllDevices (multiple devices) - // ------------------------------------------------------------------------- - - function test_GetTotalDevices_MultipleDevices() public { - assertEq(registry.getTotalDevices(), 0, "starts at zero"); - - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - assertEq(registry.getTotalDevices(), 1, "one after first register"); - - registry.registerDevice( - deviceAddress2, - PUBLIC_KEY, - DEVICE_ID_2, - CAMERA_ID_2, - MODEL, - FW_V1 - ); - assertEq(registry.getTotalDevices(), 2, "two after second register"); - } - - function test_GetAllDevices_OrderAndContent() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - registry.registerDevice( - deviceAddress2, - PUBLIC_KEY, - DEVICE_ID_2, - CAMERA_ID_2, - MODEL, - FW_V1 - ); - - address[] memory all = registry.getAllDevices(); - assertEq(all.length, 2, "two devices"); - assertEq(all[0], deviceAddress1, "first is device1"); - assertEq(all[1], deviceAddress2, "second is device2"); - } - - // ------------------------------------------------------------------------- - // Re-activation (deactivate then updateDevice(..., true)) - // ------------------------------------------------------------------------- - - function test_UpdateDevice_ReactivationAfterDeactivate() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - registry.deactivateDevice(deviceAddress1); - assertFalse(registry.isDeviceActive(deviceAddress1), "device inactive"); - - registry.updateDevice(deviceAddress1, FW_V2, true); - assertTrue(registry.isDeviceActive(deviceAddress1), "device active again"); - DeviceRegistry.DeviceInfo memory info = registry.getDevice(deviceAddress1); - assertEq(info.firmwareVersion, FW_V2, "firmware updated on reactivation"); - } - - // ------------------------------------------------------------------------- - // Events - // ------------------------------------------------------------------------- - - function test_RegisterDevice_EmitsDeviceRegistered() public { - vm.expectEmit(true, true, true, true, address(registry)); - emit DeviceRegistered(deviceAddress1, DEVICE_ID_1, PUBLIC_KEY, registrar); - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - } - - function test_UpdateDevice_EmitsDeviceUpdated() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - vm.expectEmit(true, true, true, true, address(registry)); - emit DeviceUpdated(deviceAddress1, DEVICE_ID_1, false); - registry.updateDevice(deviceAddress1, FW_V2, false); - } - - function test_DeactivateDevice_EmitsDeviceDeactivated() public { - registry.registerDevice( - deviceAddress1, - PUBLIC_KEY, - DEVICE_ID_1, - CAMERA_ID_1, - MODEL, - FW_V1 - ); - - vm.expectEmit(true, true, true, true, address(registry)); - emit DeviceDeactivated(deviceAddress1, DEVICE_ID_1); - registry.deactivateDevice(deviceAddress1); - } -} - diff --git a/contracts/test/LensMintERC1155.t.sol b/contracts/test/LensMintERC1155.t.sol deleted file mode 100644 index 4cbe7bb..0000000 --- a/contracts/test/LensMintERC1155.t.sol +++ /dev/null @@ -1,250 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.31; - -import {Test} from "forge-std/Test.sol"; -import {LensMintERC1155} from "../src/LensMintERC1155.sol"; // solhint-disable-line -import {DeviceRegistry} from "../src/DeviceRegistry.sol"; // solhint-disable-line - -/// @notice Unit tests for LensMintERC1155 (constructor, mintOriginal, mintEdition, batchMintEditions, getters, access control). -contract LensMintERC1155Test is Test { - DeviceRegistry public deviceRegistry; - LensMintERC1155 public lensMint; - - address public owner; - address public device; - address public recipient; - address public stranger; - - string constant BASE_URI = "https://ipfs.io/ipfs/"; - string constant IPFS_HASH = "QmTest123"; - string constant IMAGE_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"; - string constant SIGNATURE = "0xsignature123"; - string constant PUBLIC_KEY = "0x04e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3fbbef6e3b0b9c3a4f0b01e3"; - string constant DEVICE_ID = "device-001"; - string constant CAMERA_ID = "camera-001"; - string constant MODEL = "LensMint Pi"; - string constant FW = "1.0.0"; - - event TokenMinted( - uint256 indexed tokenId, address indexed deviceAddress, string deviceId, string ipfsHash, bool isOriginal - ); - event EditionMinted(uint256 indexed tokenId, uint256 indexed originalTokenId, address indexed to); - event BaseURIUpdated(string newBaseURI); - - function setUp() public { - deviceRegistry = new DeviceRegistry(); - lensMint = new LensMintERC1155(address(deviceRegistry), BASE_URI); - - owner = address(this); - device = address(0xD1); - recipient = address(0xD2); - stranger = address(0xBAD); - - deviceRegistry.registerDevice(device, PUBLIC_KEY, DEVICE_ID, CAMERA_ID, MODEL, FW); - } - - // ------------------------------------------------------------------------- - // Constructor - // ------------------------------------------------------------------------- - - function test_Constructor_Success() public view { - assertEq(address(lensMint.deviceRegistry()), address(deviceRegistry)); - assertEq(lensMint.baseURI(), BASE_URI); - assertEq(lensMint.owner(), owner); - assertEq(lensMint.totalTokens(), 0); - } - - function test_Constructor_Revert_DeviceRegistryAddressIsZero() public { - vm.expectRevert(LensMintERC1155.DeviceRegistryAddressIsZero.selector); - new LensMintERC1155(address(0), BASE_URI); - } - - function test_Constructor_Revert_BaseURIEmptyString() public { - vm.expectRevert(LensMintERC1155.BaseURIEmptyString.selector); - new LensMintERC1155(address(deviceRegistry), ""); - } - - // ------------------------------------------------------------------------- - // mintOriginal - // ------------------------------------------------------------------------- - - function test_MintOriginal_Success() public { - vm.prank(device); - uint256 tokenId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); - - assertEq(tokenId, 1); - assertEq(lensMint.totalTokens(), 1); - assertEq(lensMint.balanceOf(recipient, tokenId), 1); - assertEq(lensMint.getEditionCount(tokenId), 1); - - LensMintERC1155.TokenMetadata memory m = lensMint.getTokenMetadata(tokenId); - assertEq(m.deviceAddress, device); - assertTrue(m.isOriginal); - assertEq(m.ipfsHash, IPFS_HASH); - assertEq(m.imageHash, IMAGE_HASH); - assertEq(m.signature, SIGNATURE); - assertEq(m.maxEditions, 0); - assertEq(m.originalTokenId, tokenId); - assertEq(m.deviceId, DEVICE_ID); - } - - function test_MintOriginal_Revert_DeviceNotRegisteredOrInactive() public { - vm.prank(stranger); - vm.expectRevert(LensMintERC1155.DeviceNotRegisteredOrInactive.selector); - lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); - } - - function test_MintOriginal_Revert_WhenDeviceDeactivated() public { - deviceRegistry.deactivateDevice(device); - vm.prank(device); - vm.expectRevert(LensMintERC1155.DeviceNotRegisteredOrInactive.selector); - lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); - } - - // ------------------------------------------------------------------------- - // mintEdition - // ------------------------------------------------------------------------- - - function test_MintEdition_Success() public { - vm.prank(device); - uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); - - vm.prank(device); - uint256 editionId = lensMint.mintEdition(recipient, originalId); - - assertEq(editionId, 2); - assertEq(lensMint.totalTokens(), 2); - assertEq(lensMint.balanceOf(recipient, editionId), 1); - assertEq(lensMint.getEditionCount(originalId), 2); - - LensMintERC1155.TokenMetadata memory m = lensMint.getTokenMetadata(editionId); - assertEq(m.deviceAddress, device); - assertFalse(m.isOriginal); - assertEq(m.originalTokenId, originalId); - assertEq(m.ipfsHash, IPFS_HASH); - } - - function test_MintEdition_Revert_TokenDoesNotExist() public { - vm.prank(device); - vm.expectRevert(LensMintERC1155.TokenDoesNotExist.selector); - lensMint.mintEdition(recipient, 999); - } - - function test_MintEdition_Revert_TokenIsNotAnOriginal() public { - vm.prank(device); - uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); - vm.prank(device); - uint256 editionId = lensMint.mintEdition(recipient, originalId); - - vm.prank(device); - vm.expectRevert(LensMintERC1155.TokenIsNotAnOriginal.selector); - lensMint.mintEdition(recipient, editionId); - } - - function test_MintEdition_Revert_MaxEditionsReached() public { - vm.prank(device); - uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 1); - vm.prank(device); - lensMint.mintEdition(recipient, originalId); - - vm.prank(device); - vm.expectRevert(LensMintERC1155.MaxEditionsReached.selector); - lensMint.mintEdition(recipient, originalId); - } - - // ------------------------------------------------------------------------- - // batchMintEditions - // ------------------------------------------------------------------------- - - function test_BatchMintEditions_Success() public { - vm.prank(device); - uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); - - vm.prank(device); - uint256[] memory tokenIds = lensMint.batchMintEditions(recipient, originalId, 3); - - assertEq(tokenIds.length, 3); - assertEq(tokenIds[0], 2); - assertEq(tokenIds[1], 3); - assertEq(tokenIds[2], 4); - assertEq(lensMint.getEditionCount(originalId), 4); - assertEq(lensMint.balanceOf(recipient, 2), 1); - assertEq(lensMint.balanceOf(recipient, 3), 1); - assertEq(lensMint.balanceOf(recipient, 4), 1); - } - - function test_BatchMintEditions_Revert_QuantityMustBeGreaterThanZero() public { - vm.prank(device); - uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); - vm.prank(device); - vm.expectRevert(LensMintERC1155.QuantityMustBeGreaterThanZero.selector); - lensMint.batchMintEditions(recipient, originalId, 0); - } - - function test_BatchMintEditions_Revert_MaxEditionsReached() public { - vm.prank(device); - uint256 originalId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 2); - vm.prank(device); - vm.expectRevert(LensMintERC1155.MaxEditionsReached.selector); - lensMint.batchMintEditions(recipient, originalId, 3); - } - - // ------------------------------------------------------------------------- - // uri / getTokenMetadata / getEditionCount - // ------------------------------------------------------------------------- - - function test_Uri_Revert_TokenDoesNotExist() public { - vm.expectRevert(LensMintERC1155.TokenDoesNotExist.selector); - lensMint.uri(1); - } - - function test_Uri_ReturnsCorrectFormat() public { - vm.prank(device); - uint256 tokenId = lensMint.mintOriginal(recipient, IPFS_HASH, IMAGE_HASH, SIGNATURE, 0); - assertEq(lensMint.uri(tokenId), string(abi.encodePacked(BASE_URI, "1"))); - } - - function test_GetTokenMetadata_NonExistentReturnsDefaults() public view { - LensMintERC1155.TokenMetadata memory m = lensMint.getTokenMetadata(1); - assertEq(m.deviceAddress, address(0)); - assertEq(m.originalTokenId, 0); - } - - function test_GetEditionCount_NonExistentReturnsZero() public view { - assertEq(lensMint.getEditionCount(1), 0); - } - - // ------------------------------------------------------------------------- - // setBaseURI (onlyOwner) - // ------------------------------------------------------------------------- - - function test_SetBaseURI_Success() public { - string memory newUri = "https://new.base/"; - vm.prank(owner); - lensMint.setBaseURI(newUri); - assertEq(lensMint.baseURI(), newUri); - } - - function test_SetBaseURI_Revert_NotOwner() public { - vm.prank(stranger); - vm.expectRevert(); - lensMint.setBaseURI("https://evil/"); - } - - // ------------------------------------------------------------------------- - // canDeviceMint - // ------------------------------------------------------------------------- - - function test_CanDeviceMint_TrueWhenActive() public view { - assertTrue(lensMint.canDeviceMint(device)); - } - - function test_CanDeviceMint_FalseWhenInactive() public { - deviceRegistry.deactivateDevice(device); - assertFalse(lensMint.canDeviceMint(device)); - } - - function test_CanDeviceMint_FalseWhenUnregistered() public view { - assertFalse(lensMint.canDeviceMint(stranger)); - } -} diff --git a/contracts/test/LensMintVerifier.t.sol b/contracts/test/LensMintVerifier.t.sol deleted file mode 100644 index add2191..0000000 --- a/contracts/test/LensMintVerifier.t.sol +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.31; - -import "forge-std/Test.sol"; -import "../src/LensMintVerifier.sol"; - -/// @notice Mock that matches IRiscZeroVerifier.verify(bytes,bytes32,bytes32) used by LensMintVerifier. -contract MockRiscZeroVerifier { - bool public shouldRevert; - - function setShouldRevert(bool _revert) external { - shouldRevert = _revert; - } - - function verify(bytes calldata, bytes32, bytes32) external view { - if (shouldRevert) revert("MockRiscZeroVerifier: verify failed"); - } -} - -/// @notice Unit tests for LensMintVerifier (constructor, submitMetadata validation, getters). -/// @dev Uses MockRiscZeroVerifier; full ZK proof flow is integration-only. -contract LensMintVerifierTest is Test { - LensMintVerifier public verifier; - MockRiscZeroVerifier public mockVerifier; - - bytes32 constant IMAGE_ID = bytes32(uint256(1)); - bytes32 constant NOTARY_KEY = keccak256("notary-key"); - bytes32 constant QUERIES_HASH = keccak256("queries-hash"); - string constant URL_PATTERN = "https://lensmint.example/"; - - function setUp() public { - mockVerifier = new MockRiscZeroVerifier(); - verifier = new LensMintVerifier(address(mockVerifier), IMAGE_ID, NOTARY_KEY, QUERIES_HASH, URL_PATTERN); - } - - function _validJournalData() internal view returns (bytes memory) { - return abi.encode( - NOTARY_KEY, - "GET", - "https://lensmint.example/claim/abc-123", // This is the URL pattern we expect - uint256(block.timestamp), - QUERIES_HASH, - "extracted-data-json" - ); - } - - // ------------------------------------------------------------------------- - // Constructor / immutables - // ------------------------------------------------------------------------- - - function test_Constructor_Success() public view { - assertEq(address(verifier.VERIFIER()), address(mockVerifier)); - assertEq(verifier.IMAGE_ID(), IMAGE_ID); - assertEq(verifier.EXPECTED_NOTARY_KEY_FINGERPRINT(), NOTARY_KEY); - assertEq(verifier.EXPECTED_QUERIES_HASH(), QUERIES_HASH); - assertEq(verifier.expectedUrlPattern(), URL_PATTERN); - } - - // ------------------------------------------------------------------------- - // getVerifiedMetadata / getClaimIdByTokenId (defaults) - // ------------------------------------------------------------------------- - - function test_GetVerifiedMetadata_UnknownClaimReturnsDefaults() public view { - LensMintVerifier.VerifiedMetadata memory m = verifier.getVerifiedMetadata("unknown"); - assertEq(m.timestamp, 0); - assertFalse(m.verified); - assertEq(bytes(m.signature).length, 0); - } - - function test_GetClaimIdByTokenId_UnknownReturnsEmpty() public view { - assertEq(verifier.getClaimIdByTokenId(1), ""); - } - - // ------------------------------------------------------------------------- - // submitMetadata validation (reverts before calling verifier) - // ------------------------------------------------------------------------- - - function test_SubmitMetadata_Revert_InvalidNotaryKeyFingerprint() public { - bytes memory journalData = abi.encode( - bytes32(0), "GET", "https://lensmint.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "data" - ); - bytes32 journalHash = sha256(journalData); - vm.expectRevert(LensMintVerifier.InvalidNotaryKeyFingerprint.selector); - verifier.submitMetadata("claim-1", journalData, abi.encode(journalHash)); - } - - function test_SubmitMetadata_Revert_InvalidUrl_WrongMethod() public { - bytes memory journalData = abi.encode( - NOTARY_KEY, "POST", "https://lensmint.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "data" - ); - vm.expectRevert(LensMintVerifier.InvalidUrl.selector); - verifier.submitMetadata("claim-1", journalData, ""); - } - - function test_SubmitMetadata_Revert_InvalidQueriesHash() public { - bytes memory journalData = abi.encode( - NOTARY_KEY, "GET", "https://lensmint.example/claim/x", uint256(block.timestamp), bytes32(0), "data" - ); - vm.expectRevert(LensMintVerifier.InvalidQueriesHash.selector); - verifier.submitMetadata("claim-1", journalData, ""); - } - - function test_SubmitMetadata_Revert_InvalidUrl_TooShort() public { - bytes memory journalData = abi.encode(NOTARY_KEY, "GET", "ht", uint256(block.timestamp), QUERIES_HASH, "data"); - vm.expectRevert(LensMintVerifier.InvalidUrl.selector); - verifier.submitMetadata("claim-1", journalData, ""); - } - - function test_SubmitMetadata_Revert_InvalidUrl_PatternMismatch() public { - bytes memory journalData = abi.encode( - NOTARY_KEY, "GET", "https://evil.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "data" - ); - vm.expectRevert(LensMintVerifier.InvalidUrl.selector); - verifier.submitMetadata("claim-1", journalData, ""); - } - - function test_SubmitMetadata_Revert_InvalidMetadata() public { - bytes memory journalData = abi.encode( - NOTARY_KEY, "GET", "https://lensmint.example/claim/x", uint256(block.timestamp), QUERIES_HASH, "" - ); - vm.expectRevert(LensMintVerifier.InvalidMetadata.selector); - verifier.submitMetadata("claim-1", journalData, ""); - } - - // ------------------------------------------------------------------------- - // submitMetadata ZK verification (mock reverts => ZKProofVerificationFailed) - // ------------------------------------------------------------------------- - - function test_SubmitMetadata_Revert_ZKProofVerificationFailed() public { - mockVerifier.setShouldRevert(true); - bytes memory journalData = _validJournalData(); - bytes32 journalHash = sha256(journalData); - bytes memory seal = abi.encode(journalHash); - - vm.expectRevert(LensMintVerifier.ZKProofVerificationFailed.selector); - verifier.submitMetadata("claim-1", journalData, seal); - } - - function test_SubmitMetadata_Success_WhenMockSucceeds() public { - bytes memory journalData = _validJournalData(); - bytes32 journalHash = sha256(journalData); - bytes memory seal = abi.encode(journalHash); - - verifier.submitMetadata("claim-1", journalData, seal); - - LensMintVerifier.VerifiedMetadata memory m = verifier.getVerifiedMetadata("claim-1"); - assertTrue(m.verified); - assertEq(m.timestamp, block.timestamp); - } -} diff --git a/contracts/test/MintEditionDebug.t.sol b/contracts/test/MintEditionDebug.t.sol deleted file mode 100644 index c3abce1..0000000 --- a/contracts/test/MintEditionDebug.t.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.31; - -import {Test} from "forge-std/Test.sol"; -import {console} from "forge-std/console.sol"; -import {LensMintERC1155} from "../src/LensMintERC1155.sol"; -import {DeviceRegistry} from "../src/DeviceRegistry.sol"; - -contract MintEditionDebugTest is Test { - DeviceRegistry public deviceRegistry; - LensMintERC1155 public lensMint; - - address public deviceAddress; - address public recipient = 0x1B8b939710c5b61EA4ab0bD4524Cbe92c06bdA71; - uint256 private deviceKey; - - function setUp() public { - deviceRegistry = new DeviceRegistry(); - lensMint = new LensMintERC1155(address(deviceRegistry), "https://ipfs.io/ipfs/"); - - deviceKey = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; - deviceAddress = vm.addr(deviceKey); - - deviceRegistry.registerDevice( - deviceAddress, - "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - "device-123", - "camera-456", - "Raspberry Pi 4", - "1.0.0" - ); - - deviceRegistry.updateDevice(deviceAddress, "1.0.0", true); - } - - function testMintEdition() public { - address owner = address(0x1234567890123456789012345678901234567890); - - vm.prank(deviceAddress); - uint256 originalTokenId = lensMint.mintOriginal( - owner, - "QmTest123", - "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", - "0xsignature123", - 0 - ); - - console.log("Original Token ID:", originalTokenId); - - LensMintERC1155.TokenMetadata memory metadata = lensMint.getTokenMetadata(originalTokenId); - console.log("Token deviceAddress:", metadata.deviceAddress); - console.log("Token isOriginal:", metadata.isOriginal); - console.log("Token maxEditions:", metadata.maxEditions); - - assertTrue(metadata.deviceAddress != address(0), "Token should exist"); - assertTrue(metadata.isOriginal, "Token should be original"); - - uint256 editionCount = lensMint.getEditionCount(originalTokenId); - console.log("Edition count before:", editionCount); - - vm.prank(deviceAddress); - uint256 editionTokenId = lensMint.mintEdition(recipient, originalTokenId); - - console.log("Edition Token ID:", editionTokenId); - - uint256 balance = lensMint.balanceOf(recipient, editionTokenId); - assertEq(balance, 1, "Recipient should have 1 edition"); - - console.log("Edition minted successfully!"); - } - - function testMintEditionToMetaMaskAddress() public { - address owner = address(0x1234567890123456789012345678901234567890); - vm.prank(deviceAddress); - uint256 originalTokenId = lensMint.mintOriginal( - owner, - "QmTest123", - "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", - "0xsignature123", - 0 - ); - - console.log("Original Token ID:", originalTokenId); - console.log("Recipient address:", recipient); - console.log("Recipient code length:", recipient.code.length); - - vm.prank(deviceAddress); - uint256 editionTokenId = lensMint.mintEdition(recipient, originalTokenId); - - console.log("Edition Token ID:", editionTokenId); - - uint256 balance = lensMint.balanceOf(recipient, editionTokenId); - assertEq(balance, 1, "Recipient should have 1 edition"); - - console.log("SUCCESS: Edition minted to MetaMask address!"); - } - - function testMintEditionWithToken5() public { - LensMintERC1155.TokenMetadata memory metadata = lensMint.getTokenMetadata(5); - console.log("Token 5 deviceAddress:", metadata.deviceAddress); - console.log("Token 5 isOriginal:", metadata.isOriginal); - - if (metadata.deviceAddress == address(0)) { - console.log("ERROR: Token 5 does not exist"); - return; - } - - if (!metadata.isOriginal) { - console.log("ERROR: Token 5 is not an original"); - return; - } - - vm.prank(deviceAddress); - try lensMint.mintEdition(recipient, 5) returns (uint256 editionTokenId) { - console.log("SUCCESS: Edition minted! Token ID:", editionTokenId); - } catch Error(string memory reason) { - console.log("ERROR:", reason); - revert(reason); - } catch (bytes memory lowLevelData) { - console.log("ERROR: Low level error"); - console.logBytes(lowLevelData); - revert(); - } - } -} - diff --git a/contracts/test/integration/MintEditionDebug.t.sol b/contracts/test/integration/MintEditionDebug.t.sol index c3abce1..606eaa9 100644 --- a/contracts/test/integration/MintEditionDebug.t.sol +++ b/contracts/test/integration/MintEditionDebug.t.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.31; import {Test} from "forge-std/Test.sol"; import {console} from "forge-std/console.sol"; -import {LensMintERC1155} from "../src/LensMintERC1155.sol"; -import {DeviceRegistry} from "../src/DeviceRegistry.sol"; +import {LensMintERC1155} from "../../src/LensMintERC1155.sol"; +import {DeviceRegistry} from "../../src/DeviceRegistry.sol"; contract MintEditionDebugTest is Test { DeviceRegistry public deviceRegistry; diff --git a/contracts/test/unit/LensMintERC1155.t.sol b/contracts/test/unit/LensMintERC1155.t.sol index 4cbe7bb..af5fde2 100644 --- a/contracts/test/unit/LensMintERC1155.t.sol +++ b/contracts/test/unit/LensMintERC1155.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.31; import {Test} from "forge-std/Test.sol"; -import {LensMintERC1155} from "../src/LensMintERC1155.sol"; // solhint-disable-line -import {DeviceRegistry} from "../src/DeviceRegistry.sol"; // solhint-disable-line +import {LensMintERC1155} from "../../src/LensMintERC1155.sol"; // solhint-disable-line +import {DeviceRegistry} from "../../src/DeviceRegistry.sol"; // solhint-disable-line /// @notice Unit tests for LensMintERC1155 (constructor, mintOriginal, mintEdition, batchMintEditions, getters, access control). contract LensMintERC1155Test is Test { diff --git a/contracts/test/unit/LensMintVerifier.t.sol b/contracts/test/unit/LensMintVerifier.t.sol index add2191..ef4022f 100644 --- a/contracts/test/unit/LensMintVerifier.t.sol +++ b/contracts/test/unit/LensMintVerifier.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.31; import "forge-std/Test.sol"; -import "../src/LensMintVerifier.sol"; +import "../../src/LensMintVerifier.sol"; /// @notice Mock that matches IRiscZeroVerifier.verify(bytes,bytes32,bytes32) used by LensMintVerifier. contract MockRiscZeroVerifier { From 8dd5b8e36703043e97708c70126400279a842353 Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 10:19:45 +0000 Subject: [PATCH 09/15] Readme for running test and result --- contracts/test/README.md | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 contracts/test/README.md diff --git a/contracts/test/README.md b/contracts/test/README.md new file mode 100644 index 0000000..7174f95 --- /dev/null +++ b/contracts/test/README.md @@ -0,0 +1,44 @@ +# LensMint Contract Tests + +Tests are split into **unit** and **integration** folders. + +## Layout + +| Folder | Purpose | +|----------------|---------| +| `unit/` | Single-contract tests with mocks; fast, no cross-contract flow. | +| `integration/` | Multi-contract flows (e.g. DeviceRegistry + LensMintERC1155). | + +## Running tests + +```bash +# All tests +forge test + +# Unit only +forge test --match-path "test/unit/*.sol" + +# Integration only +forge test --match-path "test/integration/*.sol" + +# One contract +forge test --match-contract DeviceRegistryTest +forge test --match-contract LensMintERC1155Test +forge test --match-contract LensMintVerifierTest +forge test --match-contract MintEditionDebugTest +``` + +## Files + +- **unit/DeviceRegistry.t.sol** – DeviceRegistry (register, update, deactivate, getters, events). +- **unit/LensMintERC1155.t.sol** – LensMintERC1155 (constructor, mintOriginal, mintEdition, batchMintEditions, uri, setBaseURI, canDeviceMint). +- **unit/LensMintVerifier.t.sol** – LensMintVerifier (constructor, submitMetadata validation, getters) using `MockRiscZeroVerifier`. +- **integration/MintEditionDebug.t.sol** – Full flow: register device → mint original → mint edition(s). + + +## Test results + +The latest full `forge test` run (unit + integration) passed successfully. Summary screenshots are stored under `docs/images`: + +![Forge test summary](../../docs/images/test1.png) +![Forge test suites detail](../../docs/images/test2.png) \ No newline at end of file From 6ce7d6e676291209d52d196e95dbe7ab512e7637 Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 17:33:45 +0000 Subject: [PATCH 10/15] bug: NFT editions(copy) minting logic --- contracts/foundry.toml | 5 ++--- contracts/src/LensMintERC1155.sol | 18 ++++++++++-------- contracts/src/LensMintVerifier.sol | 8 ++++---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 0bf510a..65771e3 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -6,7 +6,7 @@ solc_version = "0.8.31" optimizer = true optimizer_runs = 200 via_ir = true -evm_version = "paris" +evm_version = "cancun" [profile.ci] fuzz = { runs = 100 } @@ -19,5 +19,4 @@ sepolia = "${SEPOLIA_RPC_URL}" mainnet = "${MAINNET_RPC_URL}" [etherscan] -sepolia = { key = "${ETHERSCAN_API_KEY}" } - +sepolia = { key = "${ETHERSCAN_API_KEY}" } \ No newline at end of file diff --git a/contracts/src/LensMintERC1155.sol b/contracts/src/LensMintERC1155.sol index e959234..6dc3b3a 100644 --- a/contracts/src/LensMintERC1155.sol +++ b/contracts/src/LensMintERC1155.sol @@ -53,13 +53,16 @@ contract LensMintERC1155 is ERC1155, Ownable { ///@dev Error to emit when the batch mint quantity is zero error QuantityMustBeGreaterThanZero(); + ///@dev Error to emit when the sender is not authorized to mint editions + error NotAuthorizedToMintEditions(); + ////////////////////////// /// STATE VARIABLES /// ////////////////////////// ///@dev Reference to device registry for validation DeviceRegistry public deviceRegistry; - + ///@dev Base URI for the token metadata string public baseURI; @@ -120,7 +123,6 @@ contract LensMintERC1155 is ERC1155, Ownable { baseURI = _baseURI; } - ///@notice Function to mint an original token ///@param _to The address to mint the token to ///@param _ipfsHash The IPFS hash of the token @@ -163,7 +165,6 @@ contract LensMintERC1155 is ERC1155, Ownable { return tokenId; } - ///@notice Function to mint an edition token ///@param _to The address to mint the token to ///@param _originalTokenId The ID of the original token @@ -176,7 +177,7 @@ contract LensMintERC1155 is ERC1155, Ownable { if (!original.isOriginal) { revert TokenIsNotAnOriginal(); } - if (original.maxEditions != 0 && editionCount[_originalTokenId] >= original.maxEditions) { + if (original.maxEditions != 0 && editionCount[_originalTokenId] > original.maxEditions) { revert MaxEditionsReached(); } @@ -204,7 +205,6 @@ contract LensMintERC1155 is ERC1155, Ownable { return tokenId; } - ///@notice Function to batch mint editions ///@param _to The address to mint the tokens to ///@param _originalTokenId The ID of the original token @@ -221,14 +221,17 @@ contract LensMintERC1155 is ERC1155, Ownable { if (!original.isOriginal) { revert TokenIsNotAnOriginal(); } + if (msg.sender != original.deviceAddress && msg.sender != owner()) { + revert NotAuthorizedToMintEditions(); + } if (_quantity == 0) { revert QuantityMustBeGreaterThanZero(); } uint256[] memory tokenIds = new uint256[](_quantity); - + for (uint256 i = 0; i < _quantity; i++) { - if (original.maxEditions != 0 && editionCount[_originalTokenId] >= original.maxEditions) { + if (original.maxEditions != 0 && editionCount[_originalTokenId] > original.maxEditions) { revert MaxEditionsReached(); } @@ -257,7 +260,6 @@ contract LensMintERC1155 is ERC1155, Ownable { return tokenIds; } - ///@notice Function to get the metadata of a token ///@param _tokenId The ID of the token ///@return TokenMetadata memory The metadata of the token diff --git a/contracts/src/LensMintVerifier.sol b/contracts/src/LensMintVerifier.sol index e027a50..59eab92 100644 --- a/contracts/src/LensMintVerifier.sol +++ b/contracts/src/LensMintVerifier.sol @@ -16,7 +16,7 @@ pragma solidity ^0.8.31; import {IRiscZeroVerifier} from "risc0-risc0-ethereum-3.0.0/IRiscZeroVerifier.sol"; contract LensMintVerifier { - + ///////////////////////// /// ERRORS /// ///////////////////////// @@ -35,7 +35,7 @@ contract LensMintVerifier { ///@dev Error to emit when the metadata is invalid error InvalidMetadata(); - + ////////////////////////// /// STATE VARIABLES /// ////////////////////////// @@ -108,7 +108,7 @@ contract LensMintVerifier { EXPECTED_QUERIES_HASH = _expectedQueriesHash; expectedUrlPattern = _expectedUrlPattern; } - + ///@notice Function to submit metadata for verification ///@param claimId The claim ID for the metadata ///@param journalData The journal data for the ZK proof @@ -170,7 +170,7 @@ contract LensMintVerifier { function getVerifiedMetadata(string memory claimId) external view returns (VerifiedMetadata memory) { return verifiedMetadata[claimId]; } - + ///@notice Function to get the claim ID for a token ID ///@param tokenId The token ID for the claim ID ///@return string The claim ID From ce058ac4aee3eff8e80a144ac0508811c8df68df Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 18:07:29 +0000 Subject: [PATCH 11/15] named imports for contracts --- contracts/src/LensMintERC1155.sol | 8 ++++---- contracts/test/unit/DeviceRegistry.t.sol | 4 ++-- contracts/test/unit/LensMintVerifier.t.sol | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/src/LensMintERC1155.sol b/contracts/src/LensMintERC1155.sol index 6dc3b3a..9e4aafa 100644 --- a/contracts/src/LensMintERC1155.sol +++ b/contracts/src/LensMintERC1155.sol @@ -13,10 +13,10 @@ pragma solidity ^0.8.31; # # ##############################################################################*/ -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "./DeviceRegistry.sol"; +import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {DeviceRegistry} from "./DeviceRegistry.sol"; /** * @title LensMintERC1155 diff --git a/contracts/test/unit/DeviceRegistry.t.sol b/contracts/test/unit/DeviceRegistry.t.sol index 854559a..005b03d 100644 --- a/contracts/test/unit/DeviceRegistry.t.sol +++ b/contracts/test/unit/DeviceRegistry.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.31; -import "forge-std/Test.sol"; -import "../../src/DeviceRegistry.sol"; +import {Test} from "forge-std/Test.sol"; +import {DeviceRegistry} from "../../src/DeviceRegistry.sol"; contract DeviceRegistryTest is Test { DeviceRegistry private registry; diff --git a/contracts/test/unit/LensMintVerifier.t.sol b/contracts/test/unit/LensMintVerifier.t.sol index ef4022f..104bd29 100644 --- a/contracts/test/unit/LensMintVerifier.t.sol +++ b/contracts/test/unit/LensMintVerifier.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.31; -import "forge-std/Test.sol"; -import "../../src/LensMintVerifier.sol"; +import {Test} from "forge-std/Test.sol"; +import {LensMintVerifier} from "../../src/LensMintVerifier.sol"; /// @notice Mock that matches IRiscZeroVerifier.verify(bytes,bytes32,bytes32) used by LensMintVerifier. contract MockRiscZeroVerifier { From 8391e056b10273f63312c4927b2027a7fb4edfec Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 17:33:45 +0000 Subject: [PATCH 12/15] bug: NFT editions(copy) minting logic --- contracts/foundry.toml | 5 ++--- contracts/src/LensMintERC1155.sol | 18 ++++++++++-------- contracts/src/LensMintVerifier.sol | 8 ++++---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 0bf510a..65771e3 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -6,7 +6,7 @@ solc_version = "0.8.31" optimizer = true optimizer_runs = 200 via_ir = true -evm_version = "paris" +evm_version = "cancun" [profile.ci] fuzz = { runs = 100 } @@ -19,5 +19,4 @@ sepolia = "${SEPOLIA_RPC_URL}" mainnet = "${MAINNET_RPC_URL}" [etherscan] -sepolia = { key = "${ETHERSCAN_API_KEY}" } - +sepolia = { key = "${ETHERSCAN_API_KEY}" } \ No newline at end of file diff --git a/contracts/src/LensMintERC1155.sol b/contracts/src/LensMintERC1155.sol index e959234..6dc3b3a 100644 --- a/contracts/src/LensMintERC1155.sol +++ b/contracts/src/LensMintERC1155.sol @@ -53,13 +53,16 @@ contract LensMintERC1155 is ERC1155, Ownable { ///@dev Error to emit when the batch mint quantity is zero error QuantityMustBeGreaterThanZero(); + ///@dev Error to emit when the sender is not authorized to mint editions + error NotAuthorizedToMintEditions(); + ////////////////////////// /// STATE VARIABLES /// ////////////////////////// ///@dev Reference to device registry for validation DeviceRegistry public deviceRegistry; - + ///@dev Base URI for the token metadata string public baseURI; @@ -120,7 +123,6 @@ contract LensMintERC1155 is ERC1155, Ownable { baseURI = _baseURI; } - ///@notice Function to mint an original token ///@param _to The address to mint the token to ///@param _ipfsHash The IPFS hash of the token @@ -163,7 +165,6 @@ contract LensMintERC1155 is ERC1155, Ownable { return tokenId; } - ///@notice Function to mint an edition token ///@param _to The address to mint the token to ///@param _originalTokenId The ID of the original token @@ -176,7 +177,7 @@ contract LensMintERC1155 is ERC1155, Ownable { if (!original.isOriginal) { revert TokenIsNotAnOriginal(); } - if (original.maxEditions != 0 && editionCount[_originalTokenId] >= original.maxEditions) { + if (original.maxEditions != 0 && editionCount[_originalTokenId] > original.maxEditions) { revert MaxEditionsReached(); } @@ -204,7 +205,6 @@ contract LensMintERC1155 is ERC1155, Ownable { return tokenId; } - ///@notice Function to batch mint editions ///@param _to The address to mint the tokens to ///@param _originalTokenId The ID of the original token @@ -221,14 +221,17 @@ contract LensMintERC1155 is ERC1155, Ownable { if (!original.isOriginal) { revert TokenIsNotAnOriginal(); } + if (msg.sender != original.deviceAddress && msg.sender != owner()) { + revert NotAuthorizedToMintEditions(); + } if (_quantity == 0) { revert QuantityMustBeGreaterThanZero(); } uint256[] memory tokenIds = new uint256[](_quantity); - + for (uint256 i = 0; i < _quantity; i++) { - if (original.maxEditions != 0 && editionCount[_originalTokenId] >= original.maxEditions) { + if (original.maxEditions != 0 && editionCount[_originalTokenId] > original.maxEditions) { revert MaxEditionsReached(); } @@ -257,7 +260,6 @@ contract LensMintERC1155 is ERC1155, Ownable { return tokenIds; } - ///@notice Function to get the metadata of a token ///@param _tokenId The ID of the token ///@return TokenMetadata memory The metadata of the token diff --git a/contracts/src/LensMintVerifier.sol b/contracts/src/LensMintVerifier.sol index e027a50..59eab92 100644 --- a/contracts/src/LensMintVerifier.sol +++ b/contracts/src/LensMintVerifier.sol @@ -16,7 +16,7 @@ pragma solidity ^0.8.31; import {IRiscZeroVerifier} from "risc0-risc0-ethereum-3.0.0/IRiscZeroVerifier.sol"; contract LensMintVerifier { - + ///////////////////////// /// ERRORS /// ///////////////////////// @@ -35,7 +35,7 @@ contract LensMintVerifier { ///@dev Error to emit when the metadata is invalid error InvalidMetadata(); - + ////////////////////////// /// STATE VARIABLES /// ////////////////////////// @@ -108,7 +108,7 @@ contract LensMintVerifier { EXPECTED_QUERIES_HASH = _expectedQueriesHash; expectedUrlPattern = _expectedUrlPattern; } - + ///@notice Function to submit metadata for verification ///@param claimId The claim ID for the metadata ///@param journalData The journal data for the ZK proof @@ -170,7 +170,7 @@ contract LensMintVerifier { function getVerifiedMetadata(string memory claimId) external view returns (VerifiedMetadata memory) { return verifiedMetadata[claimId]; } - + ///@notice Function to get the claim ID for a token ID ///@param tokenId The token ID for the claim ID ///@return string The claim ID From 5d02b5ba85e9f03577cb06ff9c5c251969c54237 Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 17 Mar 2026 18:40:16 +0000 Subject: [PATCH 13/15] Revert "bug: NFT editions(copy) minting logic" This reverts commit 6ce7d6e676291209d52d196e95dbe7ab512e7637. --- contracts/foundry.toml | 5 +++-- contracts/src/LensMintERC1155.sol | 18 ++++++++---------- contracts/src/LensMintVerifier.sol | 8 ++++---- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 65771e3..0bf510a 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -6,7 +6,7 @@ solc_version = "0.8.31" optimizer = true optimizer_runs = 200 via_ir = true -evm_version = "cancun" +evm_version = "paris" [profile.ci] fuzz = { runs = 100 } @@ -19,4 +19,5 @@ sepolia = "${SEPOLIA_RPC_URL}" mainnet = "${MAINNET_RPC_URL}" [etherscan] -sepolia = { key = "${ETHERSCAN_API_KEY}" } \ No newline at end of file +sepolia = { key = "${ETHERSCAN_API_KEY}" } + diff --git a/contracts/src/LensMintERC1155.sol b/contracts/src/LensMintERC1155.sol index 9e4aafa..8a2bec3 100644 --- a/contracts/src/LensMintERC1155.sol +++ b/contracts/src/LensMintERC1155.sol @@ -53,16 +53,13 @@ contract LensMintERC1155 is ERC1155, Ownable { ///@dev Error to emit when the batch mint quantity is zero error QuantityMustBeGreaterThanZero(); - ///@dev Error to emit when the sender is not authorized to mint editions - error NotAuthorizedToMintEditions(); - ////////////////////////// /// STATE VARIABLES /// ////////////////////////// ///@dev Reference to device registry for validation DeviceRegistry public deviceRegistry; - + ///@dev Base URI for the token metadata string public baseURI; @@ -123,6 +120,7 @@ contract LensMintERC1155 is ERC1155, Ownable { baseURI = _baseURI; } + ///@notice Function to mint an original token ///@param _to The address to mint the token to ///@param _ipfsHash The IPFS hash of the token @@ -165,6 +163,7 @@ contract LensMintERC1155 is ERC1155, Ownable { return tokenId; } + ///@notice Function to mint an edition token ///@param _to The address to mint the token to ///@param _originalTokenId The ID of the original token @@ -177,7 +176,7 @@ contract LensMintERC1155 is ERC1155, Ownable { if (!original.isOriginal) { revert TokenIsNotAnOriginal(); } - if (original.maxEditions != 0 && editionCount[_originalTokenId] > original.maxEditions) { + if (original.maxEditions != 0 && editionCount[_originalTokenId] >= original.maxEditions) { revert MaxEditionsReached(); } @@ -205,6 +204,7 @@ contract LensMintERC1155 is ERC1155, Ownable { return tokenId; } + ///@notice Function to batch mint editions ///@param _to The address to mint the tokens to ///@param _originalTokenId The ID of the original token @@ -221,17 +221,14 @@ contract LensMintERC1155 is ERC1155, Ownable { if (!original.isOriginal) { revert TokenIsNotAnOriginal(); } - if (msg.sender != original.deviceAddress && msg.sender != owner()) { - revert NotAuthorizedToMintEditions(); - } if (_quantity == 0) { revert QuantityMustBeGreaterThanZero(); } uint256[] memory tokenIds = new uint256[](_quantity); - + for (uint256 i = 0; i < _quantity; i++) { - if (original.maxEditions != 0 && editionCount[_originalTokenId] > original.maxEditions) { + if (original.maxEditions != 0 && editionCount[_originalTokenId] >= original.maxEditions) { revert MaxEditionsReached(); } @@ -260,6 +257,7 @@ contract LensMintERC1155 is ERC1155, Ownable { return tokenIds; } + ///@notice Function to get the metadata of a token ///@param _tokenId The ID of the token ///@return TokenMetadata memory The metadata of the token diff --git a/contracts/src/LensMintVerifier.sol b/contracts/src/LensMintVerifier.sol index 59eab92..e027a50 100644 --- a/contracts/src/LensMintVerifier.sol +++ b/contracts/src/LensMintVerifier.sol @@ -16,7 +16,7 @@ pragma solidity ^0.8.31; import {IRiscZeroVerifier} from "risc0-risc0-ethereum-3.0.0/IRiscZeroVerifier.sol"; contract LensMintVerifier { - + ///////////////////////// /// ERRORS /// ///////////////////////// @@ -35,7 +35,7 @@ contract LensMintVerifier { ///@dev Error to emit when the metadata is invalid error InvalidMetadata(); - + ////////////////////////// /// STATE VARIABLES /// ////////////////////////// @@ -108,7 +108,7 @@ contract LensMintVerifier { EXPECTED_QUERIES_HASH = _expectedQueriesHash; expectedUrlPattern = _expectedUrlPattern; } - + ///@notice Function to submit metadata for verification ///@param claimId The claim ID for the metadata ///@param journalData The journal data for the ZK proof @@ -170,7 +170,7 @@ contract LensMintVerifier { function getVerifiedMetadata(string memory claimId) external view returns (VerifiedMetadata memory) { return verifiedMetadata[claimId]; } - + ///@notice Function to get the claim ID for a token ID ///@param tokenId The token ID for the claim ID ///@return string The claim ID From f72ab153d5692330c3cb04f61bf928418651a4b3 Mon Sep 17 00:00:00 2001 From: Anurag Date: Thu, 26 Mar 2026 10:02:53 +0000 Subject: [PATCH 14/15] fix: removed private key-foundry deploy script and structured --- contracts/script/Deploy.s.sol | 15 ++----- contracts/script/DeployVerifier.s.sol | 31 ++++++--------- .../script/{ => ops}/DeployAndVerify.s.sol | 15 ++----- .../{ => ops}/DeployLensMintVerifier.s.sol | 39 +++++++------------ .../script/{ => ops}/RegisterDevice.s.sol | 14 +------ contracts/script/{ => ops}/SubmitProof.s.sol | 19 ++++----- 6 files changed, 44 insertions(+), 89 deletions(-) rename contracts/script/{ => ops}/DeployAndVerify.s.sol (75%) rename contracts/script/{ => ops}/DeployLensMintVerifier.s.sol (81%) rename contracts/script/{ => ops}/RegisterDevice.s.sol (75%) rename contracts/script/{ => ops}/SubmitProof.s.sol (73%) diff --git a/contracts/script/Deploy.s.sol b/contracts/script/Deploy.s.sol index fa19ec1..c708112 100644 --- a/contracts/script/Deploy.s.sol +++ b/contracts/script/Deploy.s.sol @@ -7,15 +7,11 @@ import {LensMintERC1155} from "../src/LensMintERC1155.sol"; contract DeployScript is Script { function run() external { - // Load deployer private key from environment - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - + // Signer must be supplied via forge CLI (--account, --keystore, --password, --private-key, etc.) console.log("Deploying contracts..."); - console.log("Deployer address:", deployer); - console.log("Deployer balance:", deployer.balance); + console.log("Signer: forge broadcast (see forge script --help)"); - vm.startBroadcast(deployerPrivateKey); + vm.startBroadcast(); console.log("\nDeploying DeviceRegistry..."); DeviceRegistry deviceRegistry = new DeviceRegistry(); @@ -23,10 +19,7 @@ contract DeployScript is Script { console.log("\nDeploying LensMintERC1155..."); string memory baseURI = "https://ipfs.io/ipfs/"; - LensMintERC1155 lensMint = new LensMintERC1155( - address(deviceRegistry), - baseURI - ); + LensMintERC1155 lensMint = new LensMintERC1155(address(deviceRegistry), baseURI); console.log("LensMintERC1155 deployed at:", address(lensMint)); vm.stopBroadcast(); diff --git a/contracts/script/DeployVerifier.s.sol b/contracts/script/DeployVerifier.s.sol index fc61542..f501022 100644 --- a/contracts/script/DeployVerifier.s.sol +++ b/contracts/script/DeployVerifier.s.sol @@ -7,29 +7,25 @@ import {RiscZeroMockVerifier} from "risc0-risc0-ethereum-3.0.0/test/RiscZeroMock contract DeployVerifierScript is Script { function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - string memory network = vm.envString("NETWORK"); address riscZeroVerifierAddress = vm.envOr("RISC_ZERO_VERIFIER_ADDRESS", address(0)); - + bytes32 imageId = vm.envBytes32("ZK_PROVER_GUEST_ID"); bytes32 notaryKeyFingerprint = vm.envBytes32("NOTARY_KEY_FINGERPRINT"); bytes32 queriesHash = vm.envBytes32("QUERIES_HASH"); string memory expectedUrl = vm.envString("EXPECTED_URL"); - + console.log("=== LensMintVerifier Deployment Configuration ==="); console.log("Network:", network); - console.log("Deployer address:", deployer); - console.log("Deployer balance:", deployer.balance); + console.log("Signer: forge broadcast (see forge script --help)"); console.log("Image ID:", vm.toString(imageId)); console.log("Notary Key Fingerprint:", vm.toString(notaryKeyFingerprint)); console.log("Queries Hash:", vm.toString(queriesHash)); console.log("Expected URL:", expectedUrl); console.log(""); - - vm.startBroadcast(deployerPrivateKey); - + + vm.startBroadcast(); + address verifierAddress; if (riscZeroVerifierAddress != address(0)) { console.log("Using existing RISC Zero verifier at:", riscZeroVerifierAddress); @@ -40,20 +36,15 @@ contract DeployVerifierScript is Script { verifierAddress = address(mockVerifier); console.log("RiscZeroMockVerifier deployed at:", verifierAddress); } - + console.log("\nDeploying LensMintVerifier..."); - LensMintVerifier verifier = new LensMintVerifier( - verifierAddress, - imageId, - notaryKeyFingerprint, - queriesHash, - expectedUrl - ); + LensMintVerifier verifier = + new LensMintVerifier(verifierAddress, imageId, notaryKeyFingerprint, queriesHash, expectedUrl); address verifierContractAddress = address(verifier); console.log("LensMintVerifier deployed at:", verifierContractAddress); - + vm.stopBroadcast(); - + console.log("\n=== Deployment Summary ==="); console.log("RISC Zero Verifier:", verifierAddress); console.log("LensMintVerifier:", verifierContractAddress); diff --git a/contracts/script/DeployAndVerify.s.sol b/contracts/script/ops/DeployAndVerify.s.sol similarity index 75% rename from contracts/script/DeployAndVerify.s.sol rename to contracts/script/ops/DeployAndVerify.s.sol index d463385..fc53007 100644 --- a/contracts/script/DeployAndVerify.s.sol +++ b/contracts/script/ops/DeployAndVerify.s.sol @@ -7,20 +7,16 @@ import {LensMintERC1155} from "../src/LensMintERC1155.sol"; contract DeployAndVerifyScript is Script { function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - string memory network = vm.envString("NETWORK"); string memory baseURI = vm.envOr("BASE_URI", string("https://ipfs.io/ipfs/")); - + console.log("=== Deployment Configuration ==="); console.log("Network:", network); - console.log("Deployer address:", deployer); - console.log("Deployer balance:", deployer.balance); + console.log("Signer: forge broadcast (see forge script --help)"); console.log("Base URI:", baseURI); console.log(""); - vm.startBroadcast(deployerPrivateKey); + vm.startBroadcast(); console.log("Deploying DeviceRegistry..."); DeviceRegistry deviceRegistry = new DeviceRegistry(); @@ -28,10 +24,7 @@ contract DeployAndVerifyScript is Script { console.log("DeviceRegistry deployed at:", deviceRegistryAddress); console.log("Deploying LensMintERC1155..."); - LensMintERC1155 lensMint = new LensMintERC1155( - deviceRegistryAddress, - baseURI - ); + LensMintERC1155 lensMint = new LensMintERC1155(deviceRegistryAddress, baseURI); address lensMintAddress = address(lensMint); console.log("LensMintERC1155 deployed at:", lensMintAddress); diff --git a/contracts/script/DeployLensMintVerifier.s.sol b/contracts/script/ops/DeployLensMintVerifier.s.sol similarity index 81% rename from contracts/script/DeployLensMintVerifier.s.sol rename to contracts/script/ops/DeployLensMintVerifier.s.sol index 4a1ec80..c338b89 100644 --- a/contracts/script/DeployLensMintVerifier.s.sol +++ b/contracts/script/ops/DeployLensMintVerifier.s.sol @@ -7,68 +7,59 @@ import {RiscZeroMockVerifier} from "risc0-risc0-ethereum-3.0.0/test/RiscZeroMock contract DeployLensMintVerifierScript is Script { function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - console.log("=== Deploying LensMintVerifier ==="); - console.log("Deployer:", deployer); - console.log("Balance:", deployer.balance); - - vm.startBroadcast(deployerPrivateKey); - + console.log("Signer: forge broadcast (see forge script --help)"); + + vm.startBroadcast(); + console.log("\nDeploying RiscZeroMockVerifier..."); RiscZeroMockVerifier mockVerifier = new RiscZeroMockVerifier(0xFFFFFFFF); address verifierAddress = address(mockVerifier); console.log("RiscZeroMockVerifier deployed at:", verifierAddress); - + bytes32 imageId; bytes32 notaryKeyFingerprint; bytes32 queriesHash; string memory expectedUrl; - + try vm.envBytes32("ZK_PROVER_GUEST_ID") returns (bytes32 _imageId) { imageId = _imageId; } catch { imageId = bytes32(uint256(0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef)); } - + try vm.envBytes32("NOTARY_KEY_FINGERPRINT") returns (bytes32 _notary) { notaryKeyFingerprint = _notary; } catch { notaryKeyFingerprint = bytes32(uint256(0xa7e62d7f17aa7a22c26bdb93b7ce9400e826ffb2c6f54e54d2ded015677499af)); } - + try vm.envBytes32("QUERIES_HASH") returns (bytes32 _hash) { queriesHash = _hash; } catch { queriesHash = bytes32(uint256(0xe71522e1a30a829226017e79ea606d946dae20d107ae9e310fd4e47dccf8ccfa)); } - + try vm.envString("EXPECTED_URL") returns (string memory _url) { expectedUrl = _url; } catch { expectedUrl = "https://lensmint.onrender.com/api/metadata/"; } - + console.log("\nConfiguration:"); console.log("Image ID:", vm.toString(imageId)); console.log("Notary Key Fingerprint:", vm.toString(notaryKeyFingerprint)); console.log("Queries Hash:", vm.toString(queriesHash)); console.log("Expected URL:", expectedUrl); - + console.log("\nDeploying LensMintVerifier..."); - LensMintVerifier verifier = new LensMintVerifier( - verifierAddress, - imageId, - notaryKeyFingerprint, - queriesHash, - expectedUrl - ); + LensMintVerifier verifier = + new LensMintVerifier(verifierAddress, imageId, notaryKeyFingerprint, queriesHash, expectedUrl); address verifierContractAddress = address(verifier); console.log("LensMintVerifier deployed at:", verifierContractAddress); - + vm.stopBroadcast(); - + console.log("\n=== Deployment Complete ==="); console.log("RISC Zero Verifier (Mock):", verifierAddress); console.log("LensMintVerifier:", verifierContractAddress); diff --git a/contracts/script/RegisterDevice.s.sol b/contracts/script/ops/RegisterDevice.s.sol similarity index 75% rename from contracts/script/RegisterDevice.s.sol rename to contracts/script/ops/RegisterDevice.s.sol index 01c4874..058d363 100644 --- a/contracts/script/RegisterDevice.s.sol +++ b/contracts/script/ops/RegisterDevice.s.sol @@ -6,9 +6,6 @@ import {DeviceRegistry} from "../src/DeviceRegistry.sol"; contract RegisterDeviceScript is Script { function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - address deviceRegistryAddress = vm.envAddress("DEVICE_REGISTRY_ADDRESS"); DeviceRegistry deviceRegistry = DeviceRegistry(deviceRegistryAddress); @@ -25,16 +22,9 @@ contract RegisterDeviceScript is Script { console.log("Camera ID:", cameraId); console.log("Model:", model); - vm.startBroadcast(deployerPrivateKey); + vm.startBroadcast(); - deviceRegistry.registerDevice( - deviceAddress, - publicKey, - deviceId, - cameraId, - model, - firmwareVersion - ); + deviceRegistry.registerDevice(deviceAddress, publicKey, deviceId, cameraId, model, firmwareVersion); vm.stopBroadcast(); diff --git a/contracts/script/SubmitProof.s.sol b/contracts/script/ops/SubmitProof.s.sol similarity index 73% rename from contracts/script/SubmitProof.s.sol rename to contracts/script/ops/SubmitProof.s.sol index e729dbb..a316dc3 100644 --- a/contracts/script/SubmitProof.s.sol +++ b/contracts/script/ops/SubmitProof.s.sol @@ -6,25 +6,22 @@ import {LensMintVerifier} from "../src/LensMintVerifier.sol"; contract SubmitProofScript is Script { function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - address verifierAddress = vm.envAddress("VERIFIER_CONTRACT_ADDRESS"); string memory proofFilePath = vm.envString("PROOF_FILE"); - + console.log("=== Submitting ZK Proof ==="); - console.log("Deployer:", deployer); + console.log("Signer: forge broadcast (see forge script --help)"); console.log("Verifier Contract:", verifierAddress); console.log("Proof File:", proofFilePath); - + string memory proofJson = vm.readFile(proofFilePath); - - vm.startBroadcast(deployerPrivateKey); - + + vm.startBroadcast(); + LensMintVerifier verifier = LensMintVerifier(verifierAddress); - + vm.stopBroadcast(); - + console.log("Proof submission complete"); } } From c61baa88ad2f4f45d8de55e6822fc9845e433d51 Mon Sep 17 00:00:00 2001 From: Anurag Date: Thu, 26 Mar 2026 10:04:36 +0000 Subject: [PATCH 15/15] add: shell script for multichain deployment with keystore --- contracts/.env.example | 50 +++++++ contracts/deployMultichain.sh | 263 ++++++++++++++++++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 contracts/.env.example create mode 100755 contracts/deployMultichain.sh diff --git a/contracts/.env.example b/contracts/.env.example new file mode 100644 index 0000000..fc3fb1c --- /dev/null +++ b/contracts/.env.example @@ -0,0 +1,50 @@ +# ----------------------------------------------------------------------------- +# LensMint Camera — Foundry deployment environment (example only) +# ----------------------------------------------------------------------------- +# Copy this to `contracts/.env` (and DO NOT commit `contracts/.env`). +# +# IMPORTANT +# - Never put private keys directly into this file. +# - The signer for broadcasting is passed to `forge script` via CLI flags: +# --account (Foundry/cast account name) +# OR --keystore + --password-file +# - This .env file is for public configuration like RPC URLs and verifier params. + +# RPC URLs (used by deployMultichain.sh per network name) +SEPOLIA_RPC_URL="https://YOUR_SEPOLIA_RPC_URL" +MAINNET_RPC_URL="https://YOUR_MAINNET_RPC_URL" + +# Optional networks (deployMultichain.sh will look for _RPC_URL) +ARBITRUM_RPC_URL="https://YOUR_ARBITRUM_RPC_URL" +POLYGON_RPC_URL="https://YOUR_POLYGON_RPC_URL" +BASE_RPC_URL="https://YOUR_BASE_RPC_URL" + +# ----------------------------------------------------------------------------- +# LensMintVerifier (only required if you deploy target "verifier") +# These are read by Solidity scripts via: +# vm.envString / vm.envBytes32 / vm.envOr +# ----------------------------------------------------------------------------- +EXPECTED_URL="https://lensmint.onrender.com/api/metadata/" # must match the contract's expected URL pattern + +# bytes32 values (0x... 32-byte hex) +ZK_PROVER_GUEST_ID="0x0000000000000000000000000000000000000000000000000000000000000000" +NOTARY_KEY_FINGERPRINT="0x0000000000000000000000000000000000000000000000000000000000000000" +QUERIES_HASH="0x000000000000000000000000000000000000000000000000000000000000000000" + +# Optional: if set, DeployVerifier will reuse an existing RISC Zero verifier address +RISC_ZERO_VERIFIER_ADDRESS="0x0000000000000000000000000000000000000000" + +# ----------------------------------------------------------------------------- +# Etherscan verification (only if you pass --verify) +# ----------------------------------------------------------------------------- +# Must match foundry.toml's [etherscan] section keys. +ETHERSCAN_API_KEY="YOUR_ETHERSCAN_API_KEY" + +# ----------------------------------------------------------------------------- +# Optional defaults for deployMultichain.sh (all can be overridden by CLI) +# ----------------------------------------------------------------------------- +# NETWORKS="sepolia" +# DEPLOY_TARGETS="core" +# BROADCAST="true" +# VERIFY="false" + diff --git a/contracts/deployMultichain.sh b/contracts/deployMultichain.sh new file mode 100755 index 0000000..5a25b19 --- /dev/null +++ b/contracts/deployMultichain.sh @@ -0,0 +1,263 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Defaults (can be overridden by flags or env) +NETWORKS="${NETWORKS:-sepolia}" +TARGETS="${DEPLOY_TARGETS:-core}" +BROADCAST="${BROADCAST:-true}" +DO_VERIFY="${VERIFY:-false}" + +# Signer (CLI) +ACCOUNT="" +KEYSTORE="" +PASSWORD="" +PASSWORD_FILE="" +PRIVATE_KEY_CLI="" + +# Verifier params (flags override env) +FLAG_EXPECTED_URL="" +FLAG_ZK_PROVER_GUEST_ID="" +FLAG_NOTARY_KEY_FINGERPRINT="" +FLAG_QUERIES_HASH="" +FLAG_RISC_ZERO_VERIFIER="" + +while [[ $# -gt 0 ]]; do + case $1 in + --networks) + NETWORKS="$2" + shift 2 + ;; + --targets) + TARGETS="$2" + shift 2 + ;; + --no-broadcast) + BROADCAST="false" + shift + ;; + --broadcast) + BROADCAST="true" + shift + ;; + --verify) + DO_VERIFY="true" + shift + ;; + --account) + ACCOUNT="$2" + shift 2 + ;; + --keystore) + KEYSTORE="$2" + shift 2 + ;; + --password) + PASSWORD="$2" + shift 2 + ;; + --password-file) + PASSWORD_FILE="$2" + shift 2 + ;; + --private-key) + PRIVATE_KEY_CLI="$2" + shift 2 + ;; + --expected-url) + FLAG_EXPECTED_URL="$2" + shift 2 + ;; + --zk-prover-guest-id) + FLAG_ZK_PROVER_GUEST_ID="$2" + shift 2 + ;; + --notary-key-fingerprint) + FLAG_NOTARY_KEY_FINGERPRINT="$2" + shift 2 + ;; + --queries-hash) + FLAG_QUERIES_HASH="$2" + shift 2 + ;; + --risc-zero-verifier) + FLAG_RISC_ZERO_VERIFIER="$2" + shift 2 + ;; + --help|-h) + cat <<'EOF' +deployMultichain.sh — deploy LensMint contracts via forge script + +USAGE + cd contracts && ./deployMultichain.sh [OPTIONS] + +OPTIONS + --networks LIST Comma-separated names (default: sepolia). Examples: sepolia,mainnet + --targets LIST core | verifier | core,verifier (default: core) + --broadcast Send txs (default) + --no-broadcast Simulate only; no signer required + --verify Pass --verify to forge (needs ETHERSCAN_API_KEY) + +SIGNER (required when broadcasting; same as forge script) + --account NAME + --keystore PATH [--password-file PATH | --password PASS] + --private-key HEX + +VERIFIER CONFIG (if --targets includes verifier; or set env vars) + --expected-url URL + --zk-prover-guest-id 0x... + --notary-key-fingerprint 0x... + --queries-hash 0x... + --risc-zero-verifier ADDR Optional existing RISC Zero verifier address + +ENVIRONMENT + SEPOLIA_RPC_URL, MAINNET_RPC_URL, ... For network name "foo", uses FOO_RPC_URL + EXPECTED_URL, ZK_PROVER_GUEST_ID, NOTARY_KEY_FINGERPRINT, QUERIES_HASH + RISC_ZERO_VERIFIER_ADDRESS (optional) + ETHERSCAN_API_KEY For --verify + NETWORKS, DEPLOY_TARGETS, BROADCAST, VERIFY Default overrides + +CAST / WALLET SETUP (example) + cast wallet import deployer --interactive + ./deployMultichain.sh --networks sepolia --account deployer + +EFFECTIVE COMMAND (per script) + forge script