Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -19,5 +19,4 @@ sepolia = "${SEPOLIA_RPC_URL}"
mainnet = "${MAINNET_RPC_URL}"

[etherscan]
sepolia = { key = "${ETHERSCAN_API_KEY}" }

sepolia = { key = "${ETHERSCAN_API_KEY}" }
26 changes: 14 additions & 12 deletions contracts/src/LensMintERC1155.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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();
}

Expand Down Expand Up @@ -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
Expand All @@ -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();
}

Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions contracts/src/LensMintVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pragma solidity ^0.8.31;
import {IRiscZeroVerifier} from "risc0-risc0-ethereum-3.0.0/IRiscZeroVerifier.sol";

contract LensMintVerifier {

/////////////////////////
/// ERRORS ///
/////////////////////////
Expand All @@ -35,7 +35,7 @@ contract LensMintVerifier {

///@dev Error to emit when the metadata is invalid
error InvalidMetadata();

//////////////////////////
/// STATE VARIABLES ///
//////////////////////////
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions contracts/test/README.md
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Loading