Smart Contract Specification
A description of the smart contracts that rule the DecentraLabs ecosystem
This repository details the specification of a lab booking, access and sharing decentralized solution using Solidity smart contracts that includes role-based access control using OpenZeppelin libraries.
🧩 Diamond Structure
The proposed architecture leverages a diamond proxy (EIP-2535), behind which various smart contracts are deployed. This approach is chosen for two key reasons:
To enable seamless updates and enhancements to the contracts without disrupting client functionality.
To maintain a degree of control during the initial phases of development.
Once the diamond proxy is deployed, the account responsible for the deployment gains the ability to add other accounts, referred to as “providers”. Providers are empowered to list or register online laboratories, which can later be modified, deleted, or transferred to a different provider.
Users, on the other hand, are able to reserve laboratories listed by the providers, as well as cancel any of their existing reservations. Additionally, users can access laboratories they have previously reserved, as long as the reservation remains valid. This functionality is achieved through the implementation of a new proposal: reservable token, which allows for the reservation of the use of an ERC721 token over a time interval, improving upon existing standards.
The implementation of a dedicated payable token, $LAB, through ERC-20, is not covered within the scope of this document. This token is deployed separately from the diamond proxy and a basic specificacion can be found here LabERC20.sol.
The system is divided into multiple facets, each handling specific responsibilities:
ProviderFacet: Handles admin and provider roles.LabFacet: Manages the lab entities.ReservationFacet: Manages reservations/bookings.
👤 Roles & Actors
Contract Owner – Holds
DEFAULT_ADMIN_ROLE. Deploys the diamond proxy and initialises facets. Only the owner can grant or revoke provider roles and perform administrative functions.Provider – Holds
PROVIDER_ROLE. Providers can mint new lab NFTs, update or delete their labs, list/unlist them for reservation, and claim funds from completed bookings. Providers are assigned by the owner using ProviderFacet.User – Any address that can browse listed labs, request reservations and cancel their own pending bookings. Renters pay the reservation price in $LAB at the time of request.
📦 Data Models
Provider
account
address
Wallet address
base.name
string
Provider name
base.email
string
Email address
base.country
string
Country
Lab (Cyber Physical System implemented as an NFT)
labId
uint
Unique ID
base.uri
string
Off-chain metadata URI
base.price
uint96
Price in $LAB tokens
base.auth
string
Authentication service URI
base.accessURI
string
Lab services acess URI
base.accessKey
string
Key or ID used for routing/access
Reservation (Booking presented as a new reservable token)
reservationKey
bytes32
Unique key associated with the reservation
labId
uint
Associated lab ID
renter
address
Renter address
price
uint96
Rental price
start
uint32
Start time (Unix epoch)
end
uint32
End time (Unix epoch)
status
status
State of the reservation: 0 = PENDING, 1 = BOOKED, 2 = USED, 3 = COLLECTED, 4 = CANCELLED
⚙️ Functional Requirements
Below, each implemented function is listed.
ProviderFacet:
initialize: Sets up the initial contract state and assigns admin roles
addProvider: Grants PROVIDER_ROLE to a specified account and mints $LAB tokens.
removeProvider: Removes the caller’s PROVIDER_ROLE if conditions are met.
updateProvider: Updates the caller's provider information (name, email, country).
isLabProvider: Checks if a given account holds the PROVIDER_ROLE.
getLabProviders: Retrieves a list of all lab providers.
Since ProviderFacet extends OpenZeppelin’s OpenZeppelin’s AccessControlUpgradeable.sol contract, it inherits all role-based access control functionalities provided by the OpenZeppelin library.
LabFacet:
initialize: Sets up the ERC721 token with the provided name and symbol. Initializes the labId to 0.
addLab: Allows the lab provider to create and register a new lab with metadata securely stored on-chain.
setTokenURI: Allows the lab provider to set or update the URI for the file containing the off-chain metadata of a specific lab token ID.
tokenURI: Returns the URI for the off-chain metadata of a given lab/token ID. Used for compliance with ERC721 standards.
updateLab: Allows the lab provider to update metadata stored on-chain, including the token URI, which links to the metadata stored off-chain.
deleteLab: Allows the lab provider to delete an existing lab.
getLab: Retrieves the information about the lab structure (on-chain metadata) corresponding to the provided lab ID.
getLabsPaginated: Returns a paginated list of lab IDs and the total supply. Limit input must be between 1 and 100.
Since LabFacet extends OpenZeppelin’s OpenZeppelin’s ERC721EnumerableUpgradeable.sol contract, it inherits all the standard ERC-721 enumerable functionalities, including token enumeration and indexing capabilities.
ReservationFacet:
reservationRequest: Allows a user to request a booking for a lab.
confirmReservationRequest: The lab provider or authorized account confirms a pending reservation request for a lab.
denyReservationRequest: Denies a pending reservation request and refunds the payment if necessary.
cancelReservationRequest: Allows a user to cancel a previously requested reservation and refunds the payment if necessary.
cancelBooking: Allows a user or the lab provider to cancel an existing confirmed booking.
requestFunds: Allows lab providers to claim funds from used or expired reservations.
getLabTokenAddress: Returns the address of the $LAB token contract, set at ProviderFacet initialization.
getSafeBalance: Retrieves the total balance of $LAB funds held in the contract.
Since ReservationFacet implements the enumerable extension of the newly proposed Reservable Token, as defined in ReservableTokenEnumerable.sol, it also inherits all its functions.
Use case Specification Detailed information on how each specific use case is executed is provided below:
💎 ProviderFacet:
Use case
INITIALIZE
Definition
function initialize(string memory _name, string memory _email, string memory _country, address _labERC20) public initializer
Actors
Contract owner
Purpose
Initializes the smart contract setting, the initial admin role and the ERC20 token external contract.
Summary
Only the contract owner can initialize the contract
Preconditions
Have a WALLET and sufficient funds. Can only be executed once.
Postconditions
The contract becomes initialized
Events
Emits a {RoleGranted} event if the role is successfully granted.
Use case
ADD PROVIDER
Definition
function addProvider(string memory _name, address _account, string memory _email, string memory _country) external defaultAdminRole
Actors
Contract owner
Purpose
Adds a new provider by granting the PROVIDER_ROLE and minting tokens for the specified account.
Summary
Only the contract owner can add a new provider.
Preconditions
The account must not already have the PROVIDER_ROLE.
Postconditions
The account receives the PROVIDER_ROLE and 1000 $LAB ERC20 tokens are minted for them.
Events
Emits an {ProviderAdded} event if the provider is successfully added.
Use case
REMOVE SPECIFIC PROVIDER
Definition
function removeProvider(address _provider) external defaultAdminRole
Actors
Contract owner
Purpose
Removes a specified provider from the provider list if they do not have any lab.
Summary
Only the contract owner can remove a provider from the list.
Preconditions
The provider must not own any lab.
Postconditions
The specified provider's role is revoked if conditions are met.
Events
Emits an {ProviderRemoved} event if the provider is successfully removed.
Use case
UPDATE PROVIDER
Definition
function updateProvider(string memory _name, string memory _email, string memory _country) external onlyRole(PROVIDER_ROLE)
Actors
Provider
Purpose
Updates the provider information for the caller, modifying their name, email, and country details.
Summary
Only a provider can update their own information.
Preconditions
The caller must be an existing provider.
Postconditions
The provider's information is updated with the new details provided.
Events
Emits an {ProviderUpdated} event if the provider is successfully updated.
🔍 View Functions
The functions listed below are queries that do not modify the state of the variables:
isLabProvider
function isLabProvider(address _account) external view returns (bool)
Checks if the given account is a lab provider.
bool
getLabProviders
function getLabProviders() external view returns (Provider[] memory)
Retrieves the list of all lab providers.
Provider array
📢 Events
The following table lists the events emitted by ProviderFacet.
ProviderAdded
Emitted when a new provider is added to the system.
(address _account) Address of the provider
(string _name) Name of the provider
(string _email) Email address
(string _country) Country
ProviderAddedWithoutTokens
Emitted when a provider is added but the initial token mint fails (e.g., supply cap reached)
(address _account) Address of the provider
(string _reason) Reason why the lab provider didn't received tokens
ProviderRemoved
Emitted when a provider is removed.
(address _account) Address of the provider
ProviderUpdated
Emitted when a provider's information is updated.
(address _account) Address of the provider
(string _name) Name of the provider
(string _email) Email address
(string _country) Country
💎 LabFacet:
Use case
INITIALIZE
Definition
function initialize(string memory _name, string memory _symbol) public initializer
Actors
Contract owner
Purpose
Sets up the ERC721 token with the provided name and symbol, and initializes the labId to 0.
Summary
Only the contract owner can initialize the contract
Preconditions
Have a WALLET and sufficient funds. Can only be executed once
Postconditions
The ERC721 token associated with de labs is initializes
Use case
ADD LAB
Definition
function addLab(string memory _uri, uint96 _price, string memory _auth, string memory _accessURI, string memory _accessKey) external isLabProvider
Actors
Providers
Purpose
Allows the contract provider to add a new lab with the on-chain stored metadata
Summary
Only a provider can add a new lab
Preconditions
Caller must be the lab provider
Postconditions
A new lab is registered with the given metadata
Events
Emits a {LabAdded} event if successful
Use case
SET TOKEN URI
Definition
function setTokenURI(uint256 _labId, string memory _tokenURI) external
Actors
Providers
Purpose
Allows the lab provider to set or update the URI for a specific lab ID
Summary
Only the lab provider can modify the lab URI
Preconditions
Caller must be the lab provider; the lab ID must exist
Postconditions
The specified lab's URI is updated
Events
Emits a {LabURISet} event if successful
Use case
UPDATE LAB
Definition
function updateLab(uint _labId, string memory _uri, uint96 _price, string memory _auth, string memory _accessURI, string memory _accessKey) external onlyLabProvider(_labId)
Actors
Provider
Purpose
Allows the lab provider to update the on-chain stored metadata of an existing lab
Summary
Only the lab provider can modify lab details
Preconditions
Caller must be the lab provider; lab ID must exist
Postconditions
The specified lab's metadata are updated
Events
Emits a {LabUpdated} event if successful
Use case
DELETE LAB
Definition
function deleteLab(uint _labId) external onlyLabProvider(_labId)
Actors
Providers
Purpose
Allows the lab provider to delete an existing lab
Summary
Only the lab provider can remove a lab
Preconditions
Caller must be the lab provider; lab ID must exist and be removable
Postconditions
The specified lab is removed from the system
Events
Emits a {LabDeleted} event if successful
🔍 View Functions
The functions listed below are queries that do not modify the state of the variables:
getLab
function getLab(uint _labId) public view returns (Lab memory)
Retrieves the lab associated with the given lab ID.
Lab
getAllLabs
function getAllLabs() public view returns (uint256[] memory)
Retrieves the list of the all labs ID.
ID (uint256) array
tokenURI
function tokenURI(uint256 _labId) public view returns (string memory)
Retrieves the URI associated with a specific lab ID.
URI string
📢 Events
The following table lists the events emitted by the LabFacet.
LabAdded
Emitted when a new lab is added to the system.
(uint256 _labId) Lab identifier
(address _provider) Provider address
(string _uri) Metadata URI
(uint96 _price) Lab price
(string _auth) Authorization details
(string _accessURI) Access URI
(string _accessKey) Access key
LabUpdated
Emitted when a lab is updated.
(uint256 _labId) Lab identifier
(string _uri) Updated URI
(uint96 _price) Updated price
(string _auth) Updated auth
(string _accessURI) Updated access URI
(string _accessKey) Updated access key
LabDeleted
Emitted when a lab is deleted.
(uint256 _labId) Lab identifier
LabURISet
Emitted when the URI of a lab is set.
(uint256 _labId) Lab identifier
(string _uri) URI of the lab
💎 ReservationFacet:
Use case
RESERVATION REQUEST
Definition
function reservationRequest(uint256 _labId, uint32 _start, uint32 _end) external exists(_labId) override
Actors
Users
Purpose
Initiates a new reservation request
Summary
Creates a reservation request with specified details
Preconditions
The lab must exists
Postconditions
Reservation state is updated to PENDING
Events
{ReservationRequested}
Use case
CONFIM RESERVATION REQUEST
Definition
function confimReservationRequest(bytes32 _reservationKey) external defaultAdminRole reservationPending(_reservationKey) override
Actors
Providers or authorized account
Purpose
Confirm and book the reservation
Summary
Creates a reservation request with specified details
Preconditions
Caller must be the lab provider (for now, the DEFAULT_ADMIN_ROLE)
Postconditions
State is updated to BOOKED
Events
Emits a {ReservationConfirmed} event if successful
Use case
DENY RESERVATION REQUEST
Definition
function denyReservationRequest(bytes32 _reservationKey) external defaultAdminRole reservationPending(_reservationKey) override
Actors
Providers or authorized account
Purpose
Denies a pending reservation request
Summary
Reservation request is marked as denied
Preconditions
Caller must be the lab provider (for now, the DEFAULT_ADMIN_ROLE)
Postconditions
State is updated to CANCELLED
Events
Emits a {ReservationRequestDenied} event if successful
Use case
CANCEL RESERVATION REQUEST
Definition
function cancelReservationRequest(bytes32 _reservationKey) external override
Actors
Users
Purpose
Allows cancellation of an existing reservation
Summary
Cancels an active reservation
Preconditions
Caller must be the renter
Postconditions
State is updated to CANCELLED
Events
Emits a {ReservationRequestCanceled} event if successful
Use case
CANCEL BOOKING
Definition
function cancelBooking(bytes32 _reservationKey) external override
Actors
Users, providers
Purpose
Allows cancellation of an existing booking
Summary
Cancels an active booking
Preconditions
Caller must be the renter or the lab provider
Postconditions
State is updated to CANCELLED
Events
Emits a {BookingCanceled} event if successful
Use case
REQUEST FUNDS
Definition
function requestFunds() external isLabProvider
Actors
Providers
Purpose
Allows lab providers to claim funds from used or expired reservations
Summary
Creates a reservation request with specified details
Preconditions
Caller must be a registered lab provider
Postconditions
Transfers the total amount of $LAB tokens from all eligible reservations to the provider
Events
None
🔍 View Functions
The functions listed below are queries that do not modify the state of the variables:
getLabTokenAddress
function getLabTokenAddress() external view returns (address)
Returns the address of the $LAB ERC20 token
getSafeBalance
function getSafeBalance() public view returns (uint256)
Returns the current balance of Lab tokens held by this contract
uint256
📢 Events
The following table lists the events emitted by the ProviderFacet.
ReservationRequested
Emitted when a user submits a new reservation request.
(address) renter
(uint256) tokenId
(uint256) start
(uint256) end
(bytes32) reservationKey
ReservationConfirmed
Emitted when a reservation request is confirmed.
(bytes32) reservationKey
(uint256) tokenId
ReservationRequestDenied
Emitted when a reservation request is denied.
(bytes32) reservationKey
(uint256) tokenId
ReservationRequestCanceled
Emitted when a reservation request is canceled by the user.
(bytes32) reservationKey
(uint256) tokenId
BookingCanceled
Emitted when a confirmed booking is canceled.
(bytes32) reservationKey
(uint256) tokenId
LabListed
Emitted when a lab is listed
(uint256) tokenId
(address) owner
LabUnlisted
Emitted when a lab is unlisted
(uint256) tokenId
(address) owner
💳 Token Integration
The project uses a dedicated ERC-20 token: $LAB for all transactions. The token is deployed outside the diamond proxy.
Last updated