DeedNFT
Overview
The DeedNFT contract is responsible for representing real assets and physical properties as NFTs on the blockchain. Each NFT is uniquely identifiable and holds comprehensive metadata, including the asset's type, operating agreements, definitions, configurations, and a URI pointing to the asset's detailed information. The contract leverages advanced features such as role-based access control, upgradeability, pausable operations, and validator integration to ensure security, flexibility, and scalability.
Key features of the DeedNFT contract:
Inherits from ERC721 and AccessControl: Ensures compliance with the ERC721 standard and enables robust role-based access control using OpenZeppelin's
AccessControlUpgradeable
.Upgradeability: Implements the UUPS (Universal Upgradeable Proxy Standard) pattern via
UUPSUpgradeable
, allowing seamless contract upgrades without disrupting existing state or functionality.Pausable Operations: Incorporates
PausableUpgradeable
to allow administrators to pause and unpause contract operations in case of emergencies or vulnerabilities.Validator Integration: Integrates with external validator and registry contracts (
IValidator
andIValidatorRegistry
) to ensure that only registered and compliant validators can mint and validate deeds.Comprehensive Metadata Management: Utilizes an enhanced
DeedInfo
structure to store detailed metadata, including operating agreements, definitions, configurations, and associated validators.Batch Operations: Supports batch minting and burning of DeedNFTs, improving efficiency for large-scale operations.
Role Definitions: Defines specific roles such as
DEFAULT_ADMIN_ROLE
andVALIDATOR_ROLE
for precise access management.Event Emissions: Emits detailed events for actions like minting, burning, validation changes, and metadata updates, facilitating transparency and off-chain tracking.
Minting and Burning of DeedNFT Tokens
Minting & Burning
The implementation of DeedNFT minting and burning are facilitated using the _mint
and _burn
functions provided by the OpenZeppelin ERC721 contract. However, the new contract introduces enhanced access controls, batch operations, and validator integrations to ensure secure and efficient token management.
Single Minting and Burning
solidityCopy codefunction mintAsset(
address owner,
AssetType assetType,
string memory ipfsDetailsHash,
string memory operatingAgreement,
string memory definition,
string memory configuration,
address validator
) external onlyRole(VALIDATOR_ROLE) whenNotPaused returns (uint256) {
// Minting logic with validations
}
function burnAsset(uint256 deedId)
public
onlyDeedOwner(deedId)
whenNotPaused
{
_burn(deedId);
delete deedInfoMap[deedId];
emit DeedNFTBurned(deedId);
}
Batch Minting and Burning
solidityCopy codefunction mintBatchAssets(
address[] memory owners,
AssetType[] memory assetTypes,
string[] memory ipfsDetailsHashes,
string[] memory operatingAgreements,
string[] memory definitions,
string[] memory configurations,
address[] memory validators
) external onlyRole(VALIDATOR_ROLE) whenNotPaused returns (uint256[] memory) {
// Batch minting logic with validations
}
function burnBatchAssets(uint256[] memory deedIds)
external
whenNotPaused
{
for (uint256 i = 0; i < deedIds.length; i++) {
uint256 deedId = deedIds[i];
require(
ownerOf(deedId) == msg.sender,
"DeedNFT: Caller is not the owner of all deeds"
);
burnAsset(deedId);
}
}
Token URI for Storing Metadata Related to the Property Ownership Deed
DeedNFT Metadata
The DeedNFT metadata is stored off-chain in a JSON file and includes comprehensive information about the property ownership deed. The metadata structure is enhanced to accommodate detailed descriptions and configurations, ensuring each deed is richly described and easily verifiable.
Metadata Structure Includes:
Property Ownership Deed ID: Unique identifier for the deed.
Asset Type: Category of the asset (e.g., Land, Vehicle, Estate, Commercial Equipment).
Operating Agreement: Details of the operating agreement associated with the deed.
Definition: Detailed definition of the asset.
Configuration: Configuration data related to the asset.
Property Address: Physical address of the property.
Owner Details: Information about the current owner.
Legal Description: Legal description of the property.
Date of Ownership Transfer: Timestamp of when ownership was transferred.
Property Value: Monetary value of the property.
The metadata is referenced using the token URI, which is a URL pointing to the JSON file. The token URI can be set and updated using the updateMetadata
function, ensuring that metadata remains current and accurate.
Example Metadata Setting
solidityCopy codefunction updateMetadata(
uint256 deedId,
string memory ipfsDetailsHash,
string memory operatingAgreement,
string memory definition,
string memory configuration
) external onlyValidatorOrOwner(deedId) whenNotPaused {
// Metadata update logic with validations
_setTokenURI(deedId, ipfsDetailsHash);
// Additional metadata fields update
emit DeedNFTMetadataUpdated(deedId);
}
Role-Based Access Control
Role Definitions
The DeedNFT contract employs a robust role-based access control system using OpenZeppelin's AccessControlUpgradeable
. This system ensures that only authorized entities can perform sensitive operations like minting, burning, and validating deeds.
Defined Roles:
DEFAULT_ADMIN_ROLE:
Description: Grants administrative privileges, including the ability to manage roles, pause/unpause contract operations, and authorize contract upgrades.
Permissions: Add or remove minters and validators, set default validator and registry addresses.
VALIDATOR_ROLE:
Description: Assigned to validator contracts or accounts responsible for minting and validating deeds.
Permissions: Mint DeedNFTs, validate or invalidate deeds, update metadata.
Managing Roles
Adding a Validator
solidityCopy codefunction grantRole(bytes32 role, address account) public onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
Removing a Validator
solidityCopy codefunction revokeRole(bytes32 role, address account) public onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
Example Usage
solidityCopy code_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(VALIDATOR_ROLE, msg.sender);
Upgradeability
UUPS Upgradeable Pattern
The DeedNFT contract is designed with upgradeability in mind, utilizing the UUPS (Universal Upgradeable Proxy Standard) pattern. This approach allows the contract to be upgraded to new implementations without altering the proxy address or losing the existing state.
Authorization for Upgrades
solidityCopy codefunction _authorizeUpgrade(address newImplementation)
internal
override
onlyRole(DEFAULT_ADMIN_ROLE)
{
// Authorization logic handled by onlyRole modifier
}
Initializing Upgrades
During the initialization phase, the contract sets up essential parameters and roles to ensure smooth upgrade processes in the future.
solidityCopy codefunction initialize(
address _defaultValidator,
address _validatorRegistry
) public initializer {
__ERC721_init("DeedNFT", "DEED");
__ERC721URIStorage_init();
__AccessControl_init();
__Pausable_init();
__UUPSUpgradeable_init(); // Initialize UUPSUpgradeable
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(VALIDATOR_ROLE, msg.sender);
defaultValidator = _defaultValidator;
validatorRegistry = _validatorRegistry;
nextDeedId = 1;
}
Pausable Operations
Emergency Stop Mechanism
The DeedNFT contract incorporates a pausable mechanism, allowing administrators to halt all contract operations in case of emergencies or detected vulnerabilities. This feature enhances the contract's security and resilience.
Pausing the Contract
solidityCopy codefunction pause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_pause();
emit Paused(msg.sender);
}
Unpausing the Contract
solidityCopy codefunction unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
emit Unpaused(msg.sender);
}
Effects of Pausing
When the contract is paused:
Minting and Burning: All minting and burning functions are halted.
Metadata Updates: Updates to deed metadata are disabled.
Validation: Validation and invalidation of deeds are restricted.
Transfer Operations: Transfers of DeedNFTs are prevented.
Validator and Registry Integration
External Validator Contracts
The DeedNFT contract integrates with external validator contracts to ensure that only legitimate and compliant validators can mint and validate deeds. This integration enhances the trustworthiness and reliability of the DeedNFT system.
Validator Roles and Registration
Validator Registration: Validators must be registered in the external
IValidatorRegistry
before they can interact with the DeedNFT contract.Interface Compliance: Validators must implement the
IValidator
interface, ensuring they adhere to required standards and functionalities.
Setting Default Validator and Registry
solidityCopy codefunction setDefaultValidator(address validator)
external
onlyRole(DEFAULT_ADMIN_ROLE)
{
require(validator != address(0), "DeedNFT: Invalid validator address");
require(
IValidatorRegistry(validatorRegistry).isValidatorRegistered(
validator
),
"DeedNFT: Validator is not registered"
);
defaultValidator = validator;
}
function setValidatorRegistry(address registry)
external
onlyRole(DEFAULT_ADMIN_ROLE)
{
require(registry != address(0), "DeedNFT: Invalid registry address");
validatorRegistry = registry;
}
Validator Functionality
Validators are responsible for:
Minting Deeds: Creating DeedNFTs with validated metadata.
Validating Deeds: Ensuring the authenticity and compliance of deeds.
Metadata Management: Updating and managing deed metadata in collaboration with asset owners.
Comprehensive Metadata Managemen
Enhanced DeedInfo
Structure
DeedInfo
StructureThe DeedInfo
struct encapsulates detailed information about each deed, facilitating comprehensive metadata management and validation.
solidityCopy codestruct DeedInfo {
AssetType assetType;
bool isValidated;
string operatingAgreement;
string definition;
string configuration;
address validator;
uint256[5] __gap; // Reserved for future use
}
Metadata Update Function
solidityCopy codefunction updateMetadata(
uint256 deedId,
string memory ipfsDetailsHash,
string memory operatingAgreement,
string memory definition,
string memory configuration
) external onlyValidatorOrOwner(deedId) whenNotPaused {
require(
bytes(ipfsDetailsHash).length > 0,
"DeedNFT: IPFS details hash is required"
);
require(
bytes(operatingAgreement).length > 0,
"DeedNFT: Operating agreement is required"
);
require(
bytes(definition).length > 0,
"DeedNFT: Definition is required"
);
// Validator address determination
DeedInfo storage deedInfo = deedInfoMap[deedId];
address validatorAddress = deedInfo.validator != address(0)
? deedInfo.validator
: defaultValidator;
require(
validatorAddress != address(0),
"DeedNFT: No validator available"
);
require(
IERC165Upgradeable(validatorAddress).supportsInterface(
type(IValidator).interfaceId
),
"DeedNFT: Validator does not support IValidator interface"
);
// Operating agreement validation
string memory agreementName = IValidator(validatorAddress)
.operatingAgreementName(operatingAgreement);
require(
bytes(agreementName).length > 0,
"DeedNFT: Invalid operating agreement"
);
_setTokenURI(deedId, ipfsDetailsHash);
deedInfo.operatingAgreement = operatingAgreement;
deedInfo.definition = definition;
deedInfo.configuration = configuration;
if (!hasRole(VALIDATOR_ROLE, msg.sender)) {
deedInfo.isValidated = false;
}
emit DeedNFTMetadataUpdated(deedId);
}
Metadata Storage
The metadata is stored off-chain in a JSON file and includes the following information:
Property Ownership Deed ID
Asset Type
Operating Agreement
Definition
Configuration
Property Address
Owner Details
Legal Description of the Property
Date of Ownership Transfer
Property Value
The metadata is referenced using the token URI, which is a URL pointing to the JSON file. The token URI can be set and updated using the updateMetadata
function, ensuring that metadata remains current and accurate.
Getter Functions
Retrieve Deed Information
solidityCopy codefunction getDeedInfo(uint256 deedId)
external
view
deedExists(deedId)
returns (DeedInfo memory)
{
return deedInfoMap[deedId];
}
Check Subdivisibility of Assets
solidityCopy codefunction canSubdivide(uint256 deedId)
external
view
deedExists(deedId)
returns (bool)
{
AssetType assetType = deedInfoMap[deedId].assetType;
return assetType == AssetType.Land || assetType == AssetType.Estate;
}
Token URI Retrieval
The tokenURI
function is overridden to integrate with the validator contract, allowing dynamic URI generation based on validation logic.
solidityCopy codefunction tokenURI(uint256 deedId)
public
view
override
deedExists(deedId)
returns (string memory)
{
DeedInfo storage deedInfo = deedInfoMap[deedId];
address validatorAddress = deedInfo.validator != address(0)
? deedInfo.validator
: defaultValidator;
require(
validatorAddress != address(0),
"DeedNFT: No validator available"
);
require(
validatorAddress.isContract(),
"DeedNFT: Validator address is not a contract"
);
require(
IERC165Upgradeable(validatorAddress).supportsInterface(
type(IValidator).interfaceId
),
"DeedNFT: Validator does not support IValidator interface"
);
IValidator validator = IValidator(validatorAddress);
return validator.tokenURI(deedId);
}
Access Control Management
Adding and Removing Validators
Administrators can manage validators using the grantRole
and revokeRole
functions provided by AccessControlUpgradeable
.
Adding a Validator
solidityCopy codefunction addValidator(address validator) external onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(VALIDATOR_ROLE, validator);
}
Removing a Validator
solidityCopy codefunction removeValidator(address validator) external onlyRole(DEFAULT_ADMIN_ROLE) {
revokeRole(VALIDATOR_ROLE, validator);
}
Events
Event Emissions
The DeedNFT contract emits detailed events for various actions, facilitating transparency and off-chain tracking.
Minting Event
solidityCopy codeevent DeedNFTMinted(
uint256 indexed deedId,
DeedInfo deedInfo,
address indexed minter,
address validator
);
Burning Event
solidityCopy codeevent DeedNFTBurned(uint256 indexed deedId);
Validation Change Event
solidityCopy codeevent DeedNFTValidatedChanged(uint256 indexed deedId, bool isValid);
Metadata Update Event
solidityCopy codeevent DeedNFTMetadataUpdated(uint256 indexed deedId);
Example Usage
solidityCopy codeemit DeedNFTMinted(deedId, deedInfo, msg.sender, validator);
emit DeedNFTBurned(deedId);
emit DeedNFTValidatedChanged(deedId, isValid);
emit DeedNFTMetadataUpdated(deedId);
Interface and Token URI Handling
Interface Support
The supportsInterface
function is overridden to ensure compatibility with multiple interfaces, including ERC721 and AccessControl.
solidityCopy codefunction supportsInterface(bytes4 interfaceId)
public
view
override(ERC721URIStorageUpgradeable, AccessControlUpgradeable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
Dynamic Token URI
The tokenURI
function integrates with validator contracts to enable dynamic generation and management of token URIs, enhancing flexibility and ensuring that metadata remains consistent with validation logic.
By integrating these advanced features and adhering to best practices, the DeedNFT contract offers a more secure, flexible, and scalable solution for representing real assets and physical properties as NFTs on the blockchain.
Last updated
Was this helpful?