Example: Real Estate Tokenization Library
The Evire framework provides an ecosystem for developing applications in various specialized fields. By incorporating libraries, Evire boosts its capabilities in managing Real World Assets (RWA) by ensuring the integration and effective management of physical infrastructure assets.
These specialized libraries are components of the Evire framework, offering customizable and scalable solutions tailored to meet specific project needs. They support functions like maintaining data accuracy, enabling real-time monitoring, and decision-making by empowering developers to create secure and efficient dApps.
Through these libraries, Evire simplifies the integration of data sources by improving real-time monitoring and operational efficiency. This strategy not only streamlines development processes but also guarantees adherence to industry standards and regulatory mandates ultimately promoting innovation and reliability in managing physical infrastructure on the blockchain.
To illustrate how Evire’s Asset Tokenization libraries can be utilized in a smart contract, we’ll provide an example of a library that facilitates the tokenization of real-world assets. This example will focus on a basic implementation for tokenizing real estate assets.
Example: Real Estate Tokenization Library
This library provides a foundation for building a real estate tokenization platform within the Evire RWA framework. It addresses key aspects such as property representation, investor management, token issuance, and sales processes while incorporating important security and compliance features.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
library RealEstateTokenizationLibrary {
using Counters for Counters.Counter;
struct Property {
uint256 id;
string location;
uint256 totalValue;
uint256 totalShares;
bool isTokenized;
address propertyToken;
}
struct Investor {
bool isWhitelisted;
uint256 totalInvestment;
uint256[] ownedProperties;
}
struct TokenizationConfig {
uint256 minInvestment;
uint256 maxInvestmentPerUser;
uint256 whitelistDuration;
uint256 publicSaleDuration;
}
event PropertyTokenized(uint256 indexed propertyId, address indexed tokenAddress);
event InvestmentMade(address indexed investor, uint256 indexed propertyId, uint256 amount);
event WhitelistStatusChanged(address indexed investor, bool status);
event PropertySaleStarted(uint256 indexed propertyId, uint256 startTime, uint256 endTime);
event PropertySaleEnded(uint256 indexed propertyId, uint256 totalRaised);
function createProperty(
mapping(uint256 => Property) storage properties,
Counters.Counter storage propertyIdCounter,
string memory _location,
uint256 _totalValue,
uint256 _totalShares
) internal returns (uint256) {
uint256 newPropertyId = propertyIdCounter.current();
properties[newPropertyId] = Property({
id: newPropertyId,
location: _location,
totalValue: _totalValue,
totalShares: _totalShares,
isTokenized: false,
propertyToken: address(0)
});
propertyIdCounter.increment();
return newPropertyId;
}
function tokenizeProperty(
mapping(uint256 => Property) storage properties,
uint256 _propertyId,
string memory _tokenName,
string memory _tokenSymbol
) internal returns (address) {
require(!properties[_propertyId].isTokenized, "Property already tokenized");
PropertyToken newToken = new PropertyToken(_tokenName, _tokenSymbol, properties[_propertyId].totalShares);
properties[_propertyId].isTokenized = true;
properties[_propertyId].propertyToken = address(newToken);
emit PropertyTokenized(_propertyId, address(newToken));
return address(newToken);
}
function whitelistInvestor(
mapping(address => Investor) storage investors,
address _investor,
bool _status
) internal {
investors[_investor].isWhitelisted = _status;
emit WhitelistStatusChanged(_investor, _status);
}
function invest(
mapping(uint256 => Property) storage properties,
mapping(address => Investor) storage investors,
uint256 _propertyId,
uint256 _amount,
TokenizationConfig memory _config
) internal {
Property storage property = properties[_propertyId];
require(property.isTokenized, "Property not tokenized");
require(_amount >= _config.minInvestment, "Investment below minimum");
require(investors[msg.sender].totalInvestment + _amount <= _config.maxInvestmentPerUser, "Exceeds max investment per user");
uint256 sharesToMint = (_amount * property.totalShares) / property.totalValue;
PropertyToken(property.propertyToken).mint(msg.sender, sharesToMint);
investors[msg.sender].totalInvestment += _amount;
investors[msg.sender].ownedProperties.push(_propertyId);
emit InvestmentMade(msg.sender, _propertyId, _amount);
}
function startPropertySale(
mapping(uint256 => Property) storage properties,
uint256 _propertyId,
TokenizationConfig memory _config
) internal {
Property storage property = properties[_propertyId];
require(property.isTokenized, "Property not tokenized");
uint256 saleEndTime = block.timestamp + _config.whitelistDuration + _config.publicSaleDuration;
emit PropertySaleStarted(_propertyId, block.timestamp, saleEndTime);
}
function endPropertySale(
mapping(uint256 => Property) storage properties,
uint256 _propertyId
) internal {
Property storage property = properties[_propertyId];
require(property.isTokenized, "Property not tokenized");
uint256 totalRaised = PropertyToken(property.propertyToken).totalSupply() * property.totalValue / property.totalShares;
emit PropertySaleEnded(_propertyId, totalRaised);
}
}
contract PropertyToken is ERC20, Ownable, Pausable, ReentrancyGuard {
uint256 private _totalShares;
constructor(string memory name, string memory symbol, uint256 totalShares) ERC20(name, symbol) {
_totalShares = totalShares;
}
function mint(address to, uint256 amount) external onlyOwner {
require(totalSupply() + amount <= _totalShares, "Exceeds total shares");
_mint(to, amount);
}
function burn(uint256 amount) external {
_burn(msg.sender, amount);
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
function transfer(address recipient, uint256 amount) public virtual override whenNotPaused returns (bool) {
return super.transfer(recipient, amount);
}
function transferFrom(address sender, address recipient, uint256 amount) public virtual override whenNotPaused returns (bool) {
return super.transferFrom(sender, recipient, amount);
}
}
Explanation
1. Library Structure: The RealEstateTokenizationLibrary
is a Solidity library that provides functions for managing real estate tokenization. It uses OpenZeppelin contracts for standard implementations of ERC721 (for the property NFT), ERC20 (for the property tokens), Ownable, Pausable and ReentrancyGuard.
2. Data Structures:
Property
: Represents a real estate property with its details, including location, total value and tokenization status.Investor
: Stores information about investors, including whitelist status and owned properties.TokenizationConfig
: Holds configuration parameters for the tokenization process.
3. Key Functions:
createProperty
: Creates a new property entry with a unique ID.tokenizeProperty
: Tokenizes a property by creating a new ERC20 token contract for it.whitelistInvestor
: Manages the whitelist status of investors.invest
: Allows investors to purchase tokens for a specific property.startPropertySale
andendPropertySale
: Manage the sale process for tokenized properties.
4. PropertyToken Contract: This is an ERC20 token contract specifically designed for representing shares in a property. It includes features like:
- Minting new tokens (restricted to the owner)
- Burning tokens
- Pausing and unpausing transfers (for compliance purposes)
5. Advanced Features:
- Fractional Ownership: The library supports dividing a property into multiple shares, allowing for fractional ownership.
- Compliance: Includes whitelisting functionality and investment limits to ensure regulatory compliance.
- Flexible Sale Structure: Supports both whitelist period and public sale period with configurable durations.
6. Security Measures:
- Uses OpenZeppelin’s ReentrancyGuard to prevent reentrancy attacks.
- Implements Pausable functionality to halt transfers if needed.
- Utilizes Ownable for access control on critical functions.
7. Events: The library emits various events (e.g., PropertyTokenized
, InvestmentMade
) to provide transparency and enable off-chain tracking of on-chain actions.
Sample of Usage
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./RealEstateTokenizationLibrary.sol";
contract EvireRealEstateTokenizationPlatform is ReentrancyGuard, AccessControl {
using RealEstateTokenizationLibrary for *;
using SafeMath for uint256;
using Counters for Counters.Counter;
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant PROPERTY_MANAGER_ROLE = keccak256("PROPERTY_MANAGER_ROLE");
bytes32 public constant COMPLIANCE_OFFICER_ROLE = keccak256("COMPLIANCE_OFFICER_ROLE");
mapping(uint256 => RealEstateTokenizationLibrary.Property) public properties;
mapping(address => RealEstateTokenizationLibrary.Investor) public investors;
mapping(uint256 => RealEstateTokenizationLibrary.TokenizationConfig) public propertyConfigs;
mapping(uint256 => mapping(address => uint256)) public propertyInvestments;
Counters.Counter private propertyIdCounter;
IERC20 public platformToken;
uint256 public platformFeePercentage;
uint256 public constant MAX_PLATFORM_FEE_PERCENTAGE = 500; // 5%
struct AuctionInfo {
uint256 startTime;
uint256 endTime;
uint256 reservePrice;
uint256 highestBid;
address highestBidder;
}
mapping(uint256 => AuctionInfo) public propertyAuctions;
event PropertyCreated(uint256 indexed propertyId, string location, uint256 totalValue);
event PropertyAuctionStarted(uint256 indexed propertyId, uint256 startTime, uint256 endTime, uint256 reservePrice);
event PropertyAuctionEnded(uint256 indexed propertyId, address winner, uint256 winningBid);
event PlatformFeeUpdated(uint256 newFeePercentage);
event EmergencyShutdown(address initiator);
event EmergencyResumed(address initiator);
bool public emergencyShutdown;
modifier notInEmergency() {
require(!emergencyShutdown, "Platform is in emergency shutdown");
_;
}
constructor(address _platformToken) {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(ADMIN_ROLE, msg.sender);
platformToken = IERC20(_platformToken);
platformFeePercentage = 100; // 1%
}
function createProperty(string memory _location, uint256 _totalValue, uint256 _totalShares)
external
onlyRole(PROPERTY_MANAGER_ROLE)
notInEmergency
{
uint256 newPropertyId = properties.createProperty(properties, propertyIdCounter, _location, _totalValue, _totalShares);
emit PropertyCreated(newPropertyId, _location, _totalValue);
}
function tokenizeProperty(uint256 _propertyId, string memory _tokenName, string memory _tokenSymbol)
external
onlyRole(PROPERTY_MANAGER_ROLE)
notInEmergency
{
address tokenAddress = properties.tokenizeProperty(properties, _propertyId, _tokenName, _tokenSymbol);
require(tokenAddress != address(0), "Tokenization failed");
}
function setPropertyConfig(
uint256 _propertyId,
uint256 _minInvestment,
uint256 _maxInvestmentPerUser,
uint256 _whitelistDuration,
uint256 _publicSaleDuration
)
external
onlyRole(PROPERTY_MANAGER_ROLE)
notInEmergency
{
propertyConfigs[_propertyId] = RealEstateTokenizationLibrary.TokenizationConfig({
minInvestment: _minInvestment,
maxInvestmentPerUser: _maxInvestmentPerUser,
whitelistDuration: _whitelistDuration,
publicSaleDuration: _publicSaleDuration
});
}
function whitelistInvestor(address _investor, bool _status)
external
onlyRole(COMPLIANCE_OFFICER_ROLE)
notInEmergency
{
investors.whitelistInvestor(investors, _investor, _status);
}
function startPropertySale(uint256 _propertyId)
external
onlyRole(PROPERTY_MANAGER_ROLE)
notInEmergency
{
properties.startPropertySale(properties, _propertyId, propertyConfigs[_propertyId]);
}
function invest(uint256 _propertyId, uint256 _amount)
external
nonReentrant
notInEmergency
{
require(investors[msg.sender].isWhitelisted, "Investor not whitelisted");
require(block.timestamp <= properties[_propertyId].saleEndTime, "Sale has ended");
uint256 platformFee = _amount.mul(platformFeePercentage).div(10000);
uint256 investmentAmount = _amount.sub(platformFee);
require(platformToken.transferFrom(msg.sender, address(this), _amount), "Platform token transfer failed");
require(platformToken.transfer(owner(), platformFee), "Platform fee transfer failed");
properties.invest(properties, investors, _propertyId, investmentAmount, propertyConfigs[_propertyId]);
propertyInvestments[_propertyId][msg.sender] = propertyInvestments[_propertyId][msg.sender].add(investmentAmount);
}
function endPropertySale(uint256 _propertyId)
external
onlyRole(PROPERTY_MANAGER_ROLE)
notInEmergency
{
require(block.timestamp > properties[_propertyId].saleEndTime, "Sale has not ended yet");
properties.endPropertySale(properties, _propertyId);
}
function startPropertyAuction(uint256 _propertyId, uint256 _duration, uint256 _reservePrice)
external
onlyRole(PROPERTY_MANAGER_ROLE)
notInEmergency
{
require(properties[_propertyId].isTokenized, "Property not tokenized");
require(block.timestamp > properties[_propertyId].saleEndTime, "Initial sale not completed");
propertyAuctions[_propertyId] = AuctionInfo({
startTime: block.timestamp,
endTime: block.timestamp.add(_duration),
reservePrice: _reservePrice,
highestBid: 0,
highestBidder: address(0)
});
emit PropertyAuctionStarted(_propertyId, block.timestamp, block.timestamp.add(_duration), _reservePrice);
}
function placeBid(uint256 _propertyId, uint256 _bidAmount)
external
nonReentrant
notInEmergency
{
AuctionInfo storage auction = propertyAuctions[_propertyId];
require(block.timestamp >= auction.startTime && block.timestamp <= auction.endTime, "Auction not active");
require(_bidAmount > auction.highestBid && _bidAmount >= auction.reservePrice, "Bid too low");
if (auction.highestBidder != address(0)) {
require(platformToken.transfer(auction.highestBidder, auction.highestBid), "Refund to previous bidder failed");
}
require(platformToken.transferFrom(msg.sender, address(this), _bidAmount), "Bid transfer failed");
auction.highestBid = _bidAmount;
auction.highestBidder = msg.sender;
}
function endPropertyAuction(uint256 _propertyId)
external
onlyRole(PROPERTY_MANAGER_ROLE)
notInEmergency
{
AuctionInfo storage auction = propertyAuctions[_propertyId];
require(block.timestamp > auction.endTime, "Auction not ended yet");
if (auction.highestBidder != address(0)) {
// Transfer property ownership to the highest bidder
address propertyToken = properties[_propertyId].propertyToken;
uint256 totalSupply = IERC20(propertyToken).totalSupply();
require(IERC20(propertyToken).transfer(auction.highestBidder, totalSupply), "Property token transfer failed");
// Distribute auction proceeds to token holders
for (uint256 i = 0; i < investors[_propertyId].length; i++) {
address investor = investors[_propertyId][i];
uint256 investorShare = propertyInvestments[_propertyId][investor].mul(auction.highestBid).div(properties[_propertyId].totalValue);
require(platformToken.transfer(investor, investorShare), "Proceeds distribution failed");
}
emit PropertyAuctionEnded(_propertyId, auction.highestBidder, auction.highestBid);
} else {
emit PropertyAuctionEnded(_propertyId, address(0), 0);
}
delete propertyAuctions[_propertyId];
}
function updatePlatformFee(uint256 _newFeePercentage)
external
onlyRole(ADMIN_ROLE)
{
require(_newFeePercentage <= MAX_PLATFORM_FEE_PERCENTAGE, "Fee too high");
platformFeePercentage = _newFeePercentage;
emit PlatformFeeUpdated(_newFeePercentage);
}
function initiateEmergencyShutdown()
external
onlyRole(ADMIN_ROLE)
{
emergencyShutdown = true;
emit EmergencyShutdown(msg.sender);
}
function resumeFromEmergency()
external
onlyRole(ADMIN_ROLE)
{
emergencyShutdown = false;
emit EmergencyResumed(msg.sender);
}
function withdrawStuckTokens(address _token, uint256 _amount)
external
onlyRole(ADMIN_ROLE)
{
require(_token != address(platformToken), "Cannot withdraw platform tokens");
IERC20(_token).transfer(msg.sender, _amount);
}
}
Explanation
This smart contract, named EvireRealEstateTokenizationPlatform
, is a comprehensive implementation of a real estate tokenization platform using the previously created RealEstateTokenizationLibrary
. It showcases advanced features and best practices in smart contract development. Let's break down its key components and functionalities:
1. Inheritance and Imports:
- The contract inherits from OpenZeppelin’s
ReentrancyGuard
andAccessControl
for enhanced security and role-based access control. - It imports the
RealEstateTokenizationLibrary
and other necessary OpenZeppelin contracts.
2. State Variables and Mappings:
- Uses mappings to store property, investor and auction information.
- Implements a counter for property IDs.
- Stores platform-specific information like the platform token and fee percentage.
3. Role-Based Access Control:
- Defines multiple roles: ADMIN_ROLE, PROPERTY_MANAGER_ROLE and COMPLIANCE_OFFICER_ROLE.
- Different functions are restricted to specific roles for better security and separation of concerns.
4. Core Functionalities:
- Property Creation and Tokenization
- Investor Whitelisting
- Investment Processing
- Property Sale Management
- Auction Mechanism for Secondary Market
5. Advanced Features:
- Platform Fee: Implements a customizable platform fee for each investment.
- Emergency Shutdown: Allows admins to pause the platform in case of emergencies.
- Auction Mechanism: Enables secondary market trading through a bidding process.
- Token Distribution: Handles the distribution of auction proceeds to token holders.
6. Security Measures:
- Uses
ReentrancyGuard
to prevent reentrancy attacks. - Implements role-based access control for sensitive functions.
- Includes checks for valid states and conditions throughout the contract.
7. Events:
- Emits various events for important actions, enabling off-chain tracking and transparency.
8. Modifiers:
- Implements a custom
notInEmergency
modifier to restrict functions during an emergency shutdown.
Purpose
The purpose of this Smart contract is to provide a comprehensive, secure, and flexible platform for real estate tokenization within the Evire ecosystem. It aims to:
- Facilitate the tokenization of real estate properties, allowing for fractional ownership.
- Manage the entire lifecycle of tokenized properties, from creation to sale and secondary market trading.
- Ensure regulatory compliance through investor whitelisting and configurable investment limits.
- Provide a transparent and secure way for investors to participate in real estate investments.
- Enable property managers to manage tokenized properties and their sales efficiently.
- Implement a fair and transparent auction mechanism for secondary market trading.
- Protect the platform and its users through various security measures and an emergency shutdown capability.
This contract demonstrates the practical application of the RealEstateTokenizationLibrary
in a complex, real-world scenario. It showcases how the library can be integrated into a larger system to create a full-fledged tokenization platform.
The contract’s complexity and feature set make it suitable for handling various aspects of real estate tokenization, from initial offering to secondary market trading, while maintaining security and regulatory compliance.
The RWA framework along with its libraries like the RealEstateTokenizationLibrary
greatly improves the creation and control of decentralized applications centered on Real World Assets. These libraries offer customizable and expandable solutions that uphold data accuracy, enable real-time monitoring, and streamline decision-making processes.
By incorporating data sources, operational efficiency is enhanced while ensuring adherence to industry standards and regulatory mandates. This holistic approach not only simplifies the development and oversight of infrastructure assets on the blockchain but also encourages innovation and dependability, enabling the advancement of secure and efficient decentralized applications.