Skip to content

Commit

Permalink
nft contract with basic tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Birua committed Oct 8, 2024
1 parent def342b commit 8a6dab2
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 0 deletions.
87 changes: 87 additions & 0 deletions src/BondNFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;

import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC1155Supply} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";

contract BondNFT is ERC1155, Ownable, ERC1155Supply {
struct Metadata {
uint256 value;
uint256 couponValue;
uint256 issueTimestamp;
uint256 expirationTimestamp;
string ISIN;
}

// Custom event
event MintAllowanceSet(address user, uint256 id, uint256 allowedAmount);

// Custom errors
error NftMintingNotAllowed();
error NftMintingLimitExceeded(uint256);
error NftInsufficientBalanceToBurn();

mapping(uint256 => Metadata) public metadata;

// Mapping to store the allowed mints per user per token ID
mapping(address => mapping(uint256 => uint256)) public allowedMints;

// Mapping to track how many mints have been used per user per token ID
mapping(address => mapping(uint256 => uint256)) public mintedPerUser;

constructor(address initialOwner, string memory _uri) ERC1155(_uri) Ownable(initialOwner) {}

function setURI(string memory newuri) public onlyOwner {
_setURI(newuri);
}

function setMetaData(uint256 id, Metadata memory _metadata) external onlyOwner {
metadata[id] = _metadata;
}

function getMetaData(uint256 id) external view returns (Metadata memory) {
return metadata[id];
}

// Function to set allowed mints for a user per token ID
function setAllowedMints(address user, uint256 id, uint256 allowedAmount) external onlyOwner {
allowedMints[user][id] = allowedAmount;
emit MintAllowanceSet(user, id, allowedAmount);
}

// Function to mint tokens, ensuring the user has remaining allowed mints
function mint(uint256 id, uint256 amount, bytes memory data) public {
if (allowedMints[msg.sender][id] == 0) revert NftMintingNotAllowed();
if (mintedPerUser[msg.sender][id] + amount > allowedMints[msg.sender][id]) {
revert NftMintingLimitExceeded(allowedMints[msg.sender][id] - mintedPerUser[msg.sender][id]);
}

// Track the number of minted tokens per user for the given ID
mintedPerUser[msg.sender][id] += amount;

_mint(msg.sender, id, amount, data);
}

// Function to burn tokens, ensuring only the token owner can burn
function burn(uint256 id, uint256 amount) public {
if (balanceOf(msg.sender, id) < amount) revert NftInsufficientBalanceToBurn();

// Burn the tokens
_burn(msg.sender, id, amount);
}

// The following functions are overrides required by Solidity.
function _update(address from, address to, uint256[] memory ids, uint256[] memory values)
internal
override(ERC1155, ERC1155Supply)
{
super._update(from, to, ids, values);
}

// View function to get how many mints are remaining for a user per token ID
function remainingMints(address user, uint256 id) external view returns (uint256) {
return allowedMints[user][id] - mintedPerUser[user][id];
}
}
87 changes: 87 additions & 0 deletions test/BondNFT.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Test, console} from "forge-std/Test.sol";
import {BondNFT} from "../src/BondNFT.sol";

contract BondNFTTest is Test {
BondNFT public bondNFT;
address public owner;
address public account1;

function setUp() public {
owner = address(this);
account1 = address(1);
bondNFT = new BondNFT(owner, "https://example.com/{id}.json");
bondNFT.setAllowedMints(account1, 1, 10);
}

function testInitialOwner() public view {
assertEq(bondNFT.owner(), owner);
}

function testSetURI() public {
string memory newURI = "https://newexample.com/{id}.json";
bondNFT.setURI(newURI);
assertEq(bondNFT.uri(1), newURI);
}

function testSetMetaData() public {
BondNFT.Metadata memory metadata = BondNFT.Metadata({
value: 100,
couponValue: 5,
issueTimestamp: block.timestamp,
expirationTimestamp: block.timestamp + 365 days,
ISIN: "US1234567890"
});
bondNFT.setMetaData(1, metadata);
(uint256 value, uint256 couponValue, uint256 issueTimestamp, uint256 expirationTimestamp, string memory ISIN) =
bondNFT.metadata(1);
assertEq(value, metadata.value);
assertEq(couponValue, metadata.couponValue);
assertEq(issueTimestamp, metadata.issueTimestamp);
assertEq(expirationTimestamp, metadata.expirationTimestamp);
assertEq(ISIN, metadata.ISIN);
}

function testMint() public {
uint256 id = 1;
uint256 amount = 10;
vm.prank(account1);
bondNFT.mint(id, amount, "");
assertEq(bondNFT.balanceOf(account1, id), amount);
}

function testBurn() public {
uint256 id = 1;
uint256 amount = 10;
vm.prank(account1);
bondNFT.mint(id, amount, "");
vm.prank(account1);
bondNFT.burn(id, amount);
assertEq(bondNFT.balanceOf(account1, id), 0);
}

// Test minting with no allowed mints
function testMintNotAllowed() public {
vm.prank(owner);
vm.expectRevert();
bondNFT.mint(1, 10, "");
}

// Test minting with exceeded allowed mints
function testMintLimitExceeded() public {
bondNFT.setAllowedMints(account1, 1, 5);
vm.prank(account1);
bondNFT.mint(1, 5, "");
vm.expectRevert();
bondNFT.mint(1, 1, "");
}

// Test burning with insufficient balance
function testBurnInsufficientBalance() public {
vm.prank(account1);
vm.expectRevert();
bondNFT.burn(1, 100);
}
}

0 comments on commit 8a6dab2

Please sign in to comment.