diff --git a/proposals/mep-801.md b/proposals/mep-801.md index 7a4c972..94cf9a4 100644 --- a/proposals/mep-801.md +++ b/proposals/mep-801.md @@ -38,7 +38,7 @@ Every MEP-801 compliant contract must implement the following interface: ```solidity= pragma solidity ^0.8.18; -/// @title MEP-801 ISO Application Contract +/// @title MEP801 (ISO Applications) interface MEP801 { /// @dev This event gets emitted when a application added /// The parameters is the index of app, owner and name. diff --git a/proposals/mep-802.md b/proposals/mep-802.md index 16718a3..5228231 100644 --- a/proposals/mep-802.md +++ b/proposals/mep-802.md @@ -15,7 +15,7 @@ Specification allows user to create and manage the PID NFTs (Provisioning ID) wh ## Abstract -Each sensor device is provisioned on the ChirpVM with a help of a NFT. The NFT is a digital representation of the device and contains all the information needed by the ChirpVM to successufly establish a LPWAN communication channel. The ownership of the device is claimed by the ownership of the NFT. +Each sensor device is provisioned on the ChirpVM with a help of a NFT. The NFT is a digital representation of the device and contains all the information needed by the ChirpVM to successufly establish a LPWAN communication channel. The ownership of the device is minted by the ownership of the NFT. In regards to the above, the MEP-802 proposal will extend the ERC721 interface. @@ -33,197 +33,231 @@ The goal is to allow user to create, add and manage sensor devices as NFTs. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. -Every MEP-802 compliant contract must implement the ERC721 interface: +Every MEP-802 compliant contract must implement the the following interface and the ERC721 interface: ```solidity= pragma solidity ^0.8.0; -/// @title MEP-802 Provisioning Contract +/// @title MEP802 (ISO Sensor NFT) interface MEP802 /* is IERC721 */ { - /// @dev This event gets emitted when the provisioning contract is deployed - /// The parameters are the provisioning contract address - event ProvisioningContractDeployed(address indexed nftContractAddress); - - /// @dev This event gets emitted when a PID is produced. - /// The parameters are the email, amount and Application contract address, provisioning contract address, sensor profile contract address - event PIDProduced(string indexed _email, uint256 indexed _amount, address indexed _applicationContractAddress, address indexed _nftContractAddress, address indexed _sensorProfileContractAddress); - - /// @dev This event gets emitted when sensor NFT is minted. - /// The parameters are tokenID and pIDHash - event SensorNFTMinted(uint256 indexed _tokenID, bytes indexed pIDHash); - - /// @dev This event gets emitted when sensor NFT is minted. - /// The parameter is pIDHash - event SensorNFTClaimed(bytes indexed pIDHash); - - /// @notice Produce the PID, the PID will be sent to the email given - /// @param _email The email to receive the PID - /// @param _amount The amount of the PID - /// @param _applicationContractAddress The contract address of the application - /// @param _sensorProfileContractAddress the contract address of the sensor profile contract - function _producePID(string memory _email, uint256 _amount, address _applicationContractAddress, address indexed _sensorProfileContractAddress) external; - - /// @notice Mint a sensor NFT - /// @dev The tokenID should be generated on-chain - /// @param _pIDHash The hash of the PID - /// @param _tokenURI The uri that will be associated with the token - function mintSensorNFT(bytes _pIDHash, string memory _tokenURI) external; - - /// @notice Renews the validity of the device - /// @dev This method extends the validity period of the sensor. - /// This method takes in the pIDHashEVM (depends on which hash function chain is using), then match the NFT with the pIDHashEVM. - /// This method will check whether the user who calls this renew function is the owner of the NFT - /// This method will check whether the user has enough balance to finish this operation - /// For more info check the rationale. - /// @param _pIDHashEVM the pID EVM Hash gotten from pID - function renewDevice(bytes _pIDHashEVM) external payable; - - /// @notice Claims an NFT by transferring it to the caller - /// @dev This method calculates the hash of the _pid - /// and checks if it's the same on the NFT. - /// If correct, the NFT gets transferred to the caller. - /// This method starts the validity period of the sensor. - /// @param _pID the PID of the sensor to be claimed - /// @param _sensorProfileContractAddress the sensor profile contract address - function claimSensorNFT(bytes _pID, address _sensorProfileContractAddress) external payable; - - /// @notice Checks whether the sensor validity period expired - /// @param _tokenId The identifier of the NFT - /// @return True if the expiration period is greater than - /// the current height - function isValid(uint256 _tokenId) external view returns (bool); -} + /// @dev This event gets emitted when requesting to produce PIDs + /// The parameters is the index, email, number of PID, amount paid and profile index. + event ProducePidRequested( + uint256 idx, + address buyer, + string email, + uint256 numOfPid, + uint256 amountPaid, + uint256 profileIdx + ); -interface IERC721 { - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); + /// @dev This event gets emitted when a NFT price info added. + /// The parameters is the index, price and number of block. + event PriceInfoAdded(uint256 idx, uint256 _price, uint _numOfBlock); - event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); + /// @dev This event gets emitted when a sensor NFT minted + /// The parameters is the tokein ID, PID hash, paid amount and number of block. + event SensorNFTMinted( + uint256 tokenId, + uint256 pidZkevmHash, + address owner, + uint256 amountPaid, + uint numOfBlock + ); - event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + /// @dev This event gets emitted when a sensor NFT burned + /// The parameters is the index and PID hash. + event SensorNFTBurned(uint256 tokenId, uint256 pidZkevmHash); - function balanceOf(address _owner) external view returns (uint256); + /// @dev This event gets emitted when the application index of a sensor NFT changed + /// The parameters is the tokein ID, PID hash and the new applicaton index + event ApplicationIdxChanged( + uint256 tokenId, + uint256 pidZkevmHash, + uint256 applicationIdx + ); - function ownerOf(uint256 _tokenId) external view returns (address); + /// @dev This event gets emitted when a downlink enqueued + /// The parameters is the tokein ID, PID hash, data, option flags. + event DownlinkEnqueued( + uint256 tokenId, + uint256 pidZkevmHash, + string data, + uint8 flushOld, + uint8 isJson + ); - function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; + /// @notice Return the name of contract, use to identiry the version of the contract + function name2() external view returns (string memory); + + /// @notice Set PID unit price (onlyOwner) + /// @param _price The unit price of PID + function setPidUnitPrice(uint256 _price) external; + + /// @notice Request to produce PIDs and send to recipient + /// @param _email The recipient email address for the PID list + /// @param _numOfPid Number of PIDs to purchase + /// @param _profileIdx The Device Profile index of MEP803 + /// @return uint256 The new created record index (_pidRecordList) + function producePid( + string memory _email, + uint256 _numOfPid, + uint256 _profileIdx + ) external payable returns (uint256); + + /// @notice Get number of PID purchase record (onlyOwner) + /// @return uint256 Number of PID record + function getPidRecordCount() external view returns (uint256); + + /// @notice Get a PID purchase record (onlyOwner) + /// @param _idx The index of the record + /// @return PidRecord The PID purchase record + function getPidRecord( + uint256 _idx + ) external view returns (PidRecord memory); + + /// @notice Mint a Sensor NFT + /// @param _pidZkevmHash The PID hash (for zkevm) of the sensor + /// @param _priceIdx The price index (priceInfoList) for the price/block + /// @return uint256 The new minted token index + /// @param _uri The Token URI + function mintSensorNFT( + uint256 _pidZkevmHash, + uint256 _priceIdx, + string memory _uri + ) external payable returns (uint256); + + /// @notice Renew Sensor NFT (only Token owner) + /// @param _pidZkevmHash The PID hash (for zkevm) of the sensor + /// @param _priceIdx The price index (priceInfoList) for the price/block + function renewSensorNFT( + uint256 _pidZkevmHash, + uint256 _priceIdx + ) external payable; + + /// @notice Check Sensor NFT minted or not + /// @param _pidZkevmHash The PID hash (for zkevm) of the sensor + function isMinted(uint256 _pidZkevmHash) external view returns (bool); + + /// @notice Burn a Sensor NFT (onlyOwner) + /// @param _pidZkevmHash The PID hash (for zkevm) of the sensor + function burnSensorNFT(uint256 _pidZkevmHash) external; + + /// @notice Set Application Index (only Token owner) + /// @param _pidZkevmHash The PID hash (for zkevm) of the sensor + /// @param _applicationIdx The application index of MEP801 + function setApplicationIdx( + uint256 _pidZkevmHash, + uint256 _applicationIdx + ) external; + + /// @notice Get Application Index + /// @param _pidZkevmHash The PID hash (for zkevm) of the sensor + function getApplicationIdx( + uint256 _pidZkevmHash + ) external view returns (uint256); + + /// @notice Enqueue a downlink to the sensor (only Token owner) + /// @param _pidZkevmHash The PID hash (for zkevm) of the sensor + /// @param _data The data. + /// @param _flushOld Set to 1 to flush the queue before enqueue + /// @param _isJson 0: data is base64 encoded raw data, 1: JSON object + /// @notice When using JSON, it require a Downlink encoder at Device Profile codec. + function enqueueDownlink( + uint256 _pidZkevmHash, + string memory _data, + uint8 _flushOld, + uint8 _isJson + ) external; + + /// @notice Set Token URI + /// @param _pidZkevmHash The PID hash (for zkevm) of the sensor + /// @param _uri The Token URI + function setTokenURI(uint256 _pidZkevmHash, string memory _uri) external; + + /// @notice Set the base URI (onlyOwner) + /// @param _uri The Base URI + function setBaseURI(string memory _uri) external; + + /// @notice Get the base URI (onlyOwner) + function getBaseURI() external view returns (string memory); + + /// @notice Add price info for NFT minting (onlyOwner) + /// @param _price The Price + /// @param _numOfBlock Number of block to expiration + /// @return uint256 The new created price index + function addPriceInfo( + uint256 _price, + uint _numOfBlock + ) external returns (uint256); + + /// @notice Set price info for NFT minting (onlyOwner) + /// @param _idx The index of the price info at priceInfoList + /// @param _price The Price + /// @param _numOfBlock Number of block to expiration + /// @param _active The price info is active or not. + function setPriceInfo( + uint256 _idx, + uint256 _price, + uint _numOfBlock, + bool _active + ) external; +} +``` - function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; - function transferFrom(address _from, address _to, uint256 _tokenId) external payable; - function approve(address _approved, uint256 _tokenId) external payable; +And this public accessible state values: - function setApprovalForAll(address _operator, bool _approved) external; +``` +struct Sensor { + uint creationBlock; + uint expirationBlock; + uint256 lastAmount; + uint256 pidZkevmHash; + uint256 applicationIdx; + string uri; +} - function getApproved(uint256 _tokenId) external view returns (address); +struct NtfPrice { + uint256 price; + uint numOfBlock; + bool active; +} - function isApprovedForAll(address _owner, address _operator) external view returns (bool); +struct PidRecord { + string email; + uint256 numOfPid; + uint256 amountPaid; + uint256 profileIdx; } -interface IERC6551Registry { - /// @dev The registry SHALL emit the AccountCreated event upon successful account creation - event AccountCreated( - address account, - address implementation, - uint256 chainId, - address tokenContract, - uint256 tokenId, - uint256 salt - ); +contract Storage1 { + using Counters for Counters.Counter; - /// @dev Creates a token bound account for an ERC-721 token. - /// - /// If account has already been created, returns the account address without calling create2. - /// - /// If initData is not empty and account has not yet been created, calls account with - /// provided initData after creation. - /// - /// Emits AccountCreated event. - /// - /// @return the address of the account - function createAccount( - address implementation, - uint256 chainId, - address tokenContract, - uint256 tokenId, - uint256 salt, - bytes calldata initData - ) external returns (address); - - /// @dev Returns the computed address of a token bound account - /// - /// @return The computed address of the account - function account( - address implementation, - uint256 chainId, - address tokenContract, - uint256 tokenId, - uint256 salt - ) external view returns (address); -} + /// @notice PID unit price + uint256 public pidUnitPrice; -/// @dev the ERC-165 identifier for this interface is `0x400a0398` -interface IERC6551Account { - /// @dev Token bound accounts MUST implement a `receive` function. - /// - /// Token bound accounts MAY perform arbitrary logic to restrict conditions - /// under which Ether can be received. - receive() external payable; - - /// @dev Executes `call` on address `to`, with value `value` and calldata - /// `data`. - /// - /// MUST revert and bubble up errors if call fails. - /// - /// By default, token bound accounts MUST allow the owner of the ERC-721 token - /// which owns the account to execute arbitrary calls using `executeCall`. - /// - /// Token bound accounts MAY implement additional authorization mechanisms - /// which limit the ability of the ERC-721 token holder to execute calls. - /// - /// Token bound accounts MAY implement additional execution functions which - /// grant execution permissions to other non-owner accounts. - /// - /// @return The result of the call - function executeCall( - address to, - uint256 value, - bytes calldata data - ) external payable returns (bytes memory); - - /// @dev Returns identifier of the ERC-721 token which owns the - /// account - /// - /// The return value of this function MUST be constant - it MUST NOT change - /// over time. - /// - /// @return chainId The EIP-155 ID of the chain the ERC-721 token exists on - /// @return tokenContract The contract address of the ERC-721 token - /// @return tokenId The ID of the ERC-721 token - function token() - external - view - returns ( - uint256 chainId, - address tokenContract, - uint256 tokenId - ); - - /// @dev Returns the owner of the ERC-721 token which controls the account - /// if the token exists. - /// - /// This is value is obtained by calling `ownerOf` on the ERC-721 contract. - /// - /// @return Address of the owner of the ERC-721 token which owns the account - function owner() external view returns (address); - - /// @dev Returns a nonce value that is updated on every successful transaction - /// - /// @return The current account nonce - function nonce() external view returns (uint256); + /// @notice Price for mint a Sensor NFT + mapping(uint256 => NtfPrice) public priceInfoList; + Counters.Counter public numOfPriceInfo; + + /// @notice Sensors + Counters.Counter internal _sensorCount; + mapping(uint256 => Sensor) internal _sensorList; + mapping(uint256 => uint256) internal _pidToNftIdx; + + /// @notice PID production history + Counters.Counter internal _pidRecordCount; + mapping(uint256 => PidRecord) internal _pidRecordList; + + /// @notice URI + string internal _currentBaseURI; } ``` + + ## Rationale ### Sensor validity @@ -232,7 +266,7 @@ The sensor NFT validity acts as a way of constraining outdated (or not active) s ### Front-running attack -When the device owner gets the sensor physically, he needs to claim the NFT by calling the `claim` method with the PID from the device's QR code. +When the device owner gets the sensor physically, he needs to mint the Sensor NFT by calling the `mintSensorNFT` method with the hash of the PID from the device's QR code. A malicious observer looking at the pending TX pool can spot the claim transaction with the correct PID and replay the transaction with a higher gas price, effectively stealing the NFT from the original owner. diff --git a/proposals/mep-803.md b/proposals/mep-803.md index b30f46c..6eef210 100644 --- a/proposals/mep-803.md +++ b/proposals/mep-803.md @@ -44,7 +44,7 @@ Every MEP-803 compliant contract must implement the following interface: ```solidity= pragma solidity ^0.8.18; -/// @title MEP-803 Device Profile +/// @title MEP803 (ISO Sensor Profiles) interface IIMEP803 { /// @dev This event gets emitted when a device profile added /// The parameters is the index of profile, owner and name. @@ -58,14 +58,25 @@ interface IIMEP803 { /// The parameters is the index of app, owner and url. event ProfileUrlChanged(uint256 idx, address owner, string url); + /// @dev This event gets emitted when a device profile downlink price changed + /// The parameters is the index of app, owner and the new price. + event DownlinkPriceChanged( + uint256 idx, + address owner, + uint256 downlinkPrice + ); + /// @notice Return the name of contract, use to identiry the version of the contract function name() external view returns (string memory); /// @notice Create a device profile /// @param _name The name of of the device profile + /// @param _url The url of of the device profile + /// @param _downlinkPrice The price for Downlink, unit in MXC function createProfile( string memory _name, - string memory _url + string memory _url, + uint256 _downlinkPrice ) external returns (uint256); /// @notice rename a device profile @@ -78,8 +89,19 @@ interface IIMEP803 { /// @param _url The url of of the device profile function setProfileUrl(uint256 _idx, string memory _url) external; + /// @notice Set the Downlink Price of the device profile + /// @param _idx The device profile index + /// @param _downlinkPrice The price for Downlink, unit in MXC + function setDownlinkPrice(uint256 _idx, uint256 _downlinkPrice) external; + /// @notice Return the number of profiles belong to the sender. function ownerProfileIdxCount() external view returns (uint256); + + /// @notice Return a list of profiles belong to the sender. Up to 10. + /// @param _offset The starting index of owner's profile + function getOwnerProfileList( + uint256 _offset + ) external view returns (DeviceProfile[] memory); } ``` @@ -89,10 +111,12 @@ And this public accessible state values: ``` struct DeviceProfile { + uint256 idx; string name; address owner; string url; bool active; + uint256 downlinkPrice; } contract Storage1 { @@ -113,5 +137,6 @@ contract Storage1 { To create a Device Profile in ISO Application, the profile is created at the point of calling the `createProfile()` interface and event is emitted containing the device profile index, device profile name, business owner address and the URL. - Prior to the creating a Device Profile, the business owner (BO) needs to ready the device profile JSON file on a reliable internet storage (unauthorised accessible via HTTP or HTTPS). Then the BO could provides the URL when creating the device profile. + +Alternatively, BO could submit their Device Profiles to https://github.com/MatchX-GmbH/iso-device-profile for a better storage management.