CarbonCreditLib Library¶
Overview¶
The CarbonCreditLib library provides core utilities and data structures for managing carbon credits associated with ERC721 tokens within the Gemforce platform. This library implements the Diamond Standard storage pattern and provides essential functions for carbon credit initialization, retirement, and status management.
Key Features¶
- Diamond Storage Pattern: Secure storage isolation using Diamond Standard
- Token Balance Management: Track carbon credit balances per ERC721 token
- Credit Retirement: Permanent retirement of carbon credits to prevent double-counting
- Status Tracking: Monitor active vs. retired carbon credit status
- Token Validation: Ensure token existence before operations
- Gas Optimization: Efficient storage layout and operations
Library Definition¶
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
enum CarbonCreditStatus {
ACTIVE,
RETIRED
}
import "@openzeppelin/contracts/interfaces/IERC721.sol";
library CarbonCreditLib {
struct CarbonCreditStorage {
mapping(uint256 => uint256) tokenBalances;
}
bytes32 constant CARBON_CREDIT_STORAGE_POSITION = keccak256("diamond.standard.carbon.credit.storage");
function carbonCreditStorage() internal pure returns (CarbonCreditStorage storage cs);
function _tokenExists(uint256 tokenId) internal view returns (bool);
function initializeBalance(CarbonCreditStorage storage self, uint256 tokenId, uint256 initialBalance) internal;
function retireCredits(CarbonCreditStorage storage self, uint256 tokenId, uint256 amount) internal;
function getBalance(CarbonCreditStorage storage self, uint256 tokenId) internal view returns (uint256);
function getCarbonCreditStatus(CarbonCreditStorage storage self, uint256 tokenId) internal view returns (CarbonCreditStatus);
}
Data Structures¶
CarbonCreditStatus Enum¶
enum CarbonCreditStatus {
ACTIVE, // Token has remaining carbon credits
RETIRED // All carbon credits have been permanently retired
}
Purpose: Represents the current status of carbon credits for a token.
Values:
- ACTIVE (0): Token has remaining carbon credits available
- RETIRED (1): All carbon credits have been permanently retired
CarbonCreditStorage Struct¶
struct CarbonCreditStorage {
mapping(uint256 => uint256) tokenBalances;
}
Purpose: Diamond storage structure for carbon credit data.
Fields:
- tokenBalances: Mapping from token ID to carbon credit balance
Storage Management¶
carbonCreditStorage()¶
function carbonCreditStorage() internal pure returns (CarbonCreditStorage storage cs)
Purpose: Access the Diamond storage slot for carbon credit data.
Returns: Storage reference to the carbon credit storage structure
Implementation:
function carbonCreditStorage() internal pure returns (CarbonCreditStorage storage cs) {
bytes32 position = CARBON_CREDIT_STORAGE_POSITION;
assembly {
cs.slot := position
}
}
Storage Position: keccak256("diamond.standard.carbon.credit.storage")
Usage Example:
// Access carbon credit storage in a facet
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
uint256 balance = cs.tokenBalances[tokenId];
Core Functions¶
Token Validation¶
_tokenExists()¶
function _tokenExists(uint256 tokenId) internal view returns (bool)
Purpose: Verify that an ERC721 token exists by checking if it has an owner.
Parameters:
- tokenId (uint256): The ID of the token to check
Returns: Boolean indicating whether the token exists
Implementation Details:
- Uses try-catch to safely call ownerOf()
- Returns false if the call reverts (token doesn't exist)
- Returns true if owner is not zero address
Example Usage:
// Check if token exists before operations
uint256 tokenId = 1;
bool exists = CarbonCreditLib._tokenExists(tokenId);
require(exists, "Token does not exist");
Balance Management¶
initializeBalance()¶
function initializeBalance(CarbonCreditStorage storage self, uint256 tokenId, uint256 initialBalance) internal
Purpose: Initialize carbon credit balance for a specific ERC721 token.
Parameters:
- self (CarbonCreditStorage storage): Storage reference
- tokenId (uint256): The ID of the ERC721 token
- initialBalance (uint256): The initial carbon credit balance
Requirements: - Token must not already have carbon credits initialized - Token must exist (have a valid owner) - Initial balance must be greater than zero
Validation Logic:
require(self.tokenBalances[tokenId] == 0, "Carbon credits already initialized");
require(_tokenExists(tokenId), "Token does not exist");
require(initialBalance > 0, "Initial balance must be greater than zero");
Example Usage:
// Initialize carbon credits for a newly minted environmental NFT
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
uint256 tokenId = 1;
uint256 initialCredits = 1000;
CarbonCreditLib.initializeBalance(cs, tokenId, initialCredits);
retireCredits()¶
function retireCredits(CarbonCreditStorage storage self, uint256 tokenId, uint256 amount) internal
Purpose: Permanently retire a specified amount of carbon credits from a token.
Parameters:
- self (CarbonCreditStorage storage): Storage reference
- tokenId (uint256): The ID of the ERC721 token
- amount (uint256): The amount of carbon credits to retire
Requirements: - Amount must be greater than zero - Token must have sufficient carbon credit balance - Retirement is permanent and irreversible
Validation Logic:
require(amount > 0, "Amount must be greater than zero");
require(self.tokenBalances[tokenId] >= amount, "Insufficient balance");
Example Usage:
// Retire carbon credits to offset emissions
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
uint256 tokenId = 1;
uint256 retireAmount = 250;
CarbonCreditLib.retireCredits(cs, tokenId, retireAmount);
Query Functions¶
getBalance()¶
function getBalance(CarbonCreditStorage storage self, uint256 tokenId) internal view returns (uint256)
Purpose: Get the current carbon credit balance for a specific token.
Parameters:
- self (CarbonCreditStorage storage): Storage reference
- tokenId (uint256): The ID of the ERC721 token
Returns: Current carbon credit balance for the token
Example Usage:
// Check current carbon credit balance
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
uint256 tokenId = 1;
uint256 balance = CarbonCreditLib.getBalance(cs, tokenId);
getCarbonCreditStatus()¶
function getCarbonCreditStatus(CarbonCreditStorage storage self, uint256 tokenId) internal view returns (CarbonCreditStatus)
Purpose: Get the current status of carbon credits for a token.
Parameters:
- self (CarbonCreditStorage storage): Storage reference
- tokenId (uint256): The ID of the ERC721 token
Returns: CarbonCreditStatus enum value
Logic:
- Returns RETIRED if balance is zero
- Returns ACTIVE if balance is greater than zero
Example Usage:
// Check carbon credit status
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
uint256 tokenId = 1;
CarbonCreditStatus status = CarbonCreditLib.getCarbonCreditStatus(cs, tokenId);
if (status == CarbonCreditStatus.ACTIVE) {
// Token has active carbon credits
} else {
// All credits have been retired
}
Integration Examples¶
Environmental NFT Contract¶
// Environmental NFT contract using CarbonCreditLib
contract EnvironmentalNFT is ERC721, IDiamondCut {
using CarbonCreditLib for CarbonCreditLib.CarbonCreditStorage;
event CarbonCreditsInitialized(uint256 indexed tokenId, uint256 initialBalance);
event CarbonCreditsRetired(uint256 indexed tokenId, uint256 amount, uint256 remainingBalance);
function mintWithCarbonCredits(
address to,
uint256 tokenId,
uint256 carbonCredits,
string memory uri
) external onlyMinter {
// Mint the NFT
_mint(to, tokenId);
_setTokenURI(tokenId, uri);
// Initialize carbon credits
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
CarbonCreditLib.initializeBalance(cs, tokenId, carbonCredits);
emit CarbonCreditsInitialized(tokenId, carbonCredits);
}
function retireCarbonCredits(uint256 tokenId, uint256 amount) external {
require(ownerOf(tokenId) == msg.sender, "Not token owner");
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
uint256 balanceBefore = CarbonCreditLib.getBalance(cs, tokenId);
CarbonCreditLib.retireCredits(cs, tokenId, amount);
uint256 balanceAfter = CarbonCreditLib.getBalance(cs, tokenId);
emit CarbonCreditsRetired(tokenId, amount, balanceAfter);
}
function getCarbonCreditInfo(uint256 tokenId) external view returns (
uint256 balance,
CarbonCreditStatus status
) {
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
balance = CarbonCreditLib.getBalance(cs, tokenId);
status = CarbonCreditLib.getCarbonCreditStatus(cs, tokenId);
}
modifier onlyMinter() {
// Implementation would check for minter role
_;
}
}
Carbon Credit Marketplace¶
// Marketplace for trading environmental NFTs with carbon credits
contract CarbonCreditMarketplace {
using CarbonCreditLib for CarbonCreditLib.CarbonCreditStorage;
struct Listing {
uint256 tokenId;
address seller;
uint256 price;
uint256 carbonCredits;
bool active;
}
mapping(uint256 => Listing) public listings;
uint256 public nextListingId;
event NFTListed(uint256 indexed listingId, uint256 indexed tokenId, uint256 carbonCredits, uint256 price);
event NFTPurchased(uint256 indexed listingId, address indexed buyer, uint256 creditsRetired);
function listNFT(
uint256 tokenId,
uint256 price,
address nftContract
) external returns (uint256 listingId) {
require(IERC721(nftContract).ownerOf(tokenId) == msg.sender, "Not token owner");
// Get carbon credit information
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
uint256 carbonCredits = CarbonCreditLib.getBalance(cs, tokenId);
require(carbonCredits > 0, "No carbon credits associated");
listingId = nextListingId++;
listings[listingId] = Listing({
tokenId: tokenId,
seller: msg.sender,
price: price,
carbonCredits: carbonCredits,
active: true
});
emit NFTListed(listingId, tokenId, carbonCredits, price);
}
function purchaseNFT(
uint256 listingId,
uint256 creditsToRetire,
address nftContract
) external payable {
Listing storage listing = listings[listingId];
require(listing.active, "Listing not active");
require(msg.value >= listing.price, "Insufficient payment");
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
uint256 currentCredits = CarbonCreditLib.getBalance(cs, listing.tokenId);
require(creditsToRetire <= currentCredits, "Cannot retire more credits than available");
// Transfer NFT
IERC721(nftContract).safeTransferFrom(listing.seller, msg.sender, listing.tokenId);
// Retire carbon credits if requested
if (creditsToRetire > 0) {
CarbonCreditLib.retireCredits(cs, listing.tokenId, creditsToRetire);
}
// Transfer payment
payable(listing.seller).transfer(msg.value);
listing.active = false;
emit NFTPurchased(listingId, msg.sender, creditsToRetire);
}
function getListingCarbonInfo(uint256 listingId) external view returns (
uint256 currentBalance,
CarbonCreditStatus status,
uint256 listedCredits
) {
Listing memory listing = listings[listingId];
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
currentBalance = CarbonCreditLib.getBalance(cs, listing.tokenId);
status = CarbonCreditLib.getCarbonCreditStatus(cs, listing.tokenId);
listedCredits = listing.carbonCredits;
}
}
Carbon Offset Tracking System¶
// System for tracking carbon offset activities
contract CarbonOffsetTracker {
using CarbonCreditLib for CarbonCreditLib.CarbonCreditStorage;
struct OffsetRecord {
address offsetter;
uint256 tokenId;
uint256 amount;
uint256 timestamp;
string purpose;
}
mapping(uint256 => OffsetRecord) public offsetRecords;
mapping(address => uint256[]) public userOffsets;
uint256 public nextRecordId;
event CarbonOffset(
uint256 indexed recordId,
address indexed offsetter,
uint256 indexed tokenId,
uint256 amount,
string purpose
);
function recordCarbonOffset(
uint256 tokenId,
uint256 amount,
string memory purpose,
address nftContract
) external returns (uint256 recordId) {
require(IERC721(nftContract).ownerOf(tokenId) == msg.sender, "Not token owner");
CarbonCreditLib.CarbonCreditStorage storage cs = CarbonCreditLib.carbonCreditStorage();
// Verify sufficient balance
uint256 currentBalance = CarbonCreditLib.getBalance(cs, tokenId);
require(currentBalance >= amount, "Insufficient carbon credits");
// Retire the credits
CarbonCreditLib.retireCredits(cs, tokenId, amount);
// Record the offset
recordId = nextRecordId++;
offsetRecords[recordId] = OffsetRecord({
offsetter: msg.sender,
tokenId: tokenId,
amount: amount,
timestamp: block.timestamp,
purpose: purpose
});
userOffsets[msg.sender].push(recordId);
emit CarbonOffset(recordId, msg.sender, tokenId, amount, purpose);
}
function getUserOffsetSummary(address user) external view returns (
uint256 totalOffsetsCount,
uint256 totalCreditsRetired,
uint256[] memory recordIds
) {
recordIds = userOffsets[user];
totalOffsetsCount = recordIds.length;
for (uint256 i = 0; i < recordIds.length; i++) {
totalCreditsRetired += offsetRecords[recordIds[i]].amount;
}
}
function getTokenOffsetHistory(uint256 tokenId) external view returns (
uint256[] memory recordIds,
uint256 totalRetired
) {
// Find all offset records for this token
uint256 count = 0;
for (uint256 i = 0; i < nextRecordId; i++) {
if (offsetRecords[i].tokenId == tokenId) {
count++;
}
}
recordIds = new uint256[](count);
uint256 index = 0;
for (uint256 i = 0; i < nextRecordId; i++) {
if (offsetRecords[i].tokenId == tokenId) {
recordIds[index] = i;
totalRetired += offsetRecords[i].amount;
index++;
}
}
}
}
Security Considerations¶
Storage Security¶
- Uses Diamond Standard storage pattern for isolation
- Prevents storage collisions with unique storage position
- Secure access control through internal functions only
Validation Security¶
- Validates token existence and ownership before operations
- Ensures sufficient balance for retirement
- Prevents double-counting of credits
Operational Security¶
- Immutable retirement ensures permanent offset
- Transparent tracking for auditability
- Efficient operations minimize attack surface
Gas Optimization¶
Storage Efficiency¶
- The
CarbonCreditStoragestruct is designed to minimize storage slot usage. - Carbon credit balances are stored efficiently using a single mapping.
Function Efficiency¶
_tokenExistsuses try-catch for safe external calls.initializeBalanceandretireCreditsperform direct storage updates.
Error Handling¶
Common Errors¶
Cclib: Inital balance requires more than zero tokens: Initial balance is zero.Cclib: Token has no carbon credits: Attempting to operate on a token without initialized credits.Cclib: Insufficient balance: Amount to retire exceeds available carbon credit balance.Cclib: Already initialized: Attempting to initialize credits for an already initialized token.Cclib: Amount is zero: Attempting to retire zero tokens.
Best Practices¶
Integration Guidelines¶
- Ensure proper NFT ownership and transfer mechanisms in dApps.
- Validate carbon credit amounts and status before initiating operations.
- Integrate with off-chain carbon registries for full traceability.
- Use the library's batch functions for efficient high-volume operations.
Development Guidelines¶
- Write comprehensive unit tests for all CarbonCreditLib functions.
- Implement robust error handling and user feedback mechanisms in dApps.
- Monitor events for real-time tracking and analytics of carbon credit movements.
- Follow secure coding practices for all smart contract interactions.
Related Documentation¶
- Carbon Credit Facet - Reference for the CarbonCreditFacet implementation.
- [ICarbonCredit Interface ../../smart-contracts/interfaces/icarbon-credit.md) - Interface definition.
- EIP-DRAFT-Carbon-Credit-Standard - The full EIP specification.
- Diamond Standard Overview - Overview of the Diamond Standard.
- Developer Guides: Automated Testing Setup