This contract has been partially verified via Sourcify.
View contract in Sourcify repository
- Contract name:
- Marketplace
- Optimization enabled
- true
- Compiler version
- v0.8.4+commit.c7e474f2
- Optimization runs
- 200
- EVM Version
- istanbul
- Verified at
- 2025-07-31T15:52:47.022303Z
Marketplace.sol
// File: contracts/Identity/IOwned.sol
pragma solidity >0.6.0 <0.9.0;
interface IOwned {
function owner() external returns (address);
}
// File: contracts/Identity/IOfferable.sol
pragma solidity >=0.8.4 <=0.9.0;
interface IOfferable {
function offer(address _offeredTo) external;
function acceptOffer() external;
function rejectOffer() external;
function cancelOffer() external;
function sendTransaction(
address to,
bytes memory data,
uint256 value
) external returns (bool success);
}
// File: @openzeppelin/contracts/utils/introspection/IERC165.sol
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// File: @openzeppelin/contracts/utils/introspection/ERC165.sol
pragma solidity ^0.8.0;
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool)
{
return interfaceId == type(IERC165).interfaceId;
}
}
// File: contracts/Identity/OfferableIdentity.sol
pragma solidity >=0.8.4 <=0.9.0;
contract OfferableIdentity is IOfferable, IOwned, ERC165 {
address public override owner;
address manager;
address public offeredTo;
event TransactionSent(bytes indexed data, uint256 indexed value);
function init(address _owner) external {
require(
manager == address(0),
"OfferableIdentity: Identity can be initialize only once"
);
owner = _owner;
manager = msg.sender;
IdentityManager(manager).identityCreated(owner);
}
modifier isOwner() {
require(msg.sender == owner, "OfferableIdentity: Only owner allowed");
_;
}
modifier isManager() {
require(
msg.sender == manager,
"OfferableIdentity: Only manager allowed"
);
_;
}
modifier isOfferedTo() {
require(
offeredTo != address(0),
"OfferableIdentity: Proxy is not offered"
);
require(
msg.sender == offeredTo,
"OfferableIdentity: Proxy offered to other account"
);
_;
}
function offer(address _offeredTo) external override isOwner {
offeredTo = _offeredTo;
IdentityManager(manager).identityOffered(offeredTo);
}
function acceptOffer() external override isOfferedTo {
owner = offeredTo;
IdentityManager(manager).identityAccepted(offeredTo);
closeOffer();
}
function rejectOffer() external override isOfferedTo {
IdentityManager(manager).identityRejected(offeredTo);
closeOffer();
}
function cancelOffer() external override isOwner {
IdentityManager(manager).identityOfferCanceled(offeredTo);
closeOffer();
}
function closeOffer() internal {
offeredTo = address(0);
}
function sendTransaction(
address to,
bytes memory data,
uint256 value
) external override isOwner returns (bool success) {
(success, ) = to.call{gas: gasleft() - 5000, value: value}(data);
require(success, "OfferableIdentity: Error calling other contract");
emit TransactionSent(data, value);
}
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool)
{
return
interfaceId == type(IOfferable).interfaceId ||
interfaceId == type(IOwned).interfaceId ||
super.supportsInterface(interfaceId);
}
}
// File: @openzeppelin/contracts/proxy/Clones.sol
pragma solidity ^0.8.0;
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*
* _Available since v3.4._
*/
library Clones {
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
assembly {
let ptr := mload(0x40)
mstore(
ptr,
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
)
mstore(add(ptr, 0x14), shl(0x60, implementation))
mstore(
add(ptr, 0x28),
0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
)
instance := create(0, ptr, 0x37)
}
require(instance != address(0), "ERC1167: create failed");
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple time will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt)
internal
returns (address instance)
{
assembly {
let ptr := mload(0x40)
mstore(
ptr,
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
)
mstore(add(ptr, 0x14), shl(0x60, implementation))
mstore(
add(ptr, 0x28),
0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
)
instance := create2(0, ptr, 0x37, salt)
}
require(instance != address(0), "ERC1167: create2 failed");
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
assembly {
let ptr := mload(0x40)
mstore(
ptr,
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
)
mstore(add(ptr, 0x14), shl(0x60, implementation))
mstore(
add(ptr, 0x28),
0x5af43d82803e903d91602b57fd5bf3ff00000000000000000000000000000000
)
mstore(add(ptr, 0x38), shl(0x60, deployer))
mstore(add(ptr, 0x4c), salt)
mstore(add(ptr, 0x6c), keccak256(ptr, 0x37))
predicted := keccak256(add(ptr, 0x37), 0x55)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(address implementation, bytes32 salt)
internal
view
returns (address predicted)
{
return predictDeterministicAddress(implementation, salt, address(this));
}
}
// File: contracts/Identity/IdentityManager.sol
pragma solidity >=0.8.4 <=0.9.0;
contract IdentityManager {
address libraryAddress;
bytes4 private constant INIT_SELECTOR =
bytes4(keccak256(bytes("init(address)")));
struct Identity {
bool created;
bool verified;
bool compliant;
bool offered;
address owner;
}
mapping(address => Identity) identities;
event IdentityCreated(
address indexed identity,
address indexed owner,
uint256 indexed at
);
event IdentityOffered(
address indexed identity,
address indexed owner,
address offeredTo,
uint256 indexed at
);
event IdentityTransferred(
address indexed identity,
address indexed owner,
uint256 indexed at
);
event IdentityOfferRejected(
address indexed identity,
address owner,
address indexed offeredTo,
uint256 indexed at
);
event IdentityOfferCanceled(
address indexed identity,
address indexed owner,
address oferedto,
uint256 indexed at
);
constructor(address _libraryAddress) {
libraryAddress = _libraryAddress;
}
modifier isOfferable() {
require(
ERC165Checker.supportsInterface(
msg.sender,
type(IOfferable).interfaceId
),
"Only Offerable Identity allowed"
);
_;
}
modifier isOffered() {
require(
identities[msg.sender].offered,
"IdentityManager: Identity is not offered"
);
_;
}
function verified(address identity) public view returns (bool) {
return identities[identity].verified;
}
function compliant(address identity) public view returns (bool) {
return identities[identity].compliant;
}
function created(address identity) internal view returns (bool) {
return identities[identity].created;
}
function offered(address identity) internal view returns (bool) {
return identities[identity].offered;
}
function identityOwner(address identity) public view returns (address) {
return identities[identity].owner;
}
function createIdentity(address _owner) external {
address identity = Clones.clone(libraryAddress);
identities[identity].created = true;
bytes memory initData = abi.encodeWithSelector(INIT_SELECTOR, _owner);
Address.functionCall(
identity,
initData,
"IdentityManager: Can't initialize cloned identity"
);
}
function identityCreated(address _owner) external isOfferable {
if (created(msg.sender)) {
identities[msg.sender].verified = true;
} else {
identities[msg.sender].compliant = true;
}
require(
identityOwner(msg.sender) == address(0),
"IdentityManager: Identity already has been registered"
);
identities[msg.sender].owner = _owner;
emit IdentityCreated(msg.sender, _owner, block.timestamp);
}
function identityOffered(address _offeredTo) external {
require(
verified(msg.sender) || compliant(msg.sender),
"IdentityManager: Not compliant identity can't be offered"
);
identities[msg.sender].offered = true;
emit IdentityOffered(
msg.sender,
identityOwner(msg.sender),
_offeredTo,
block.timestamp
);
}
function identityAccepted(address _owner) external isOffered {
identities[msg.sender].owner = _owner;
identities[msg.sender].offered = false;
emit IdentityTransferred(msg.sender, _owner, block.timestamp);
}
function identityRejected(address _offeredTo) external isOffered {
identities[msg.sender].offered = false;
emit IdentityOfferRejected(
msg.sender,
identityOwner(msg.sender),
_offeredTo,
block.timestamp
);
}
function identityOfferCanceled(address _offeredTo) external isOffered {
identities[msg.sender].offered = false;
emit IdentityOfferCanceled(
msg.sender,
identityOwner(msg.sender),
_offeredTo,
block.timestamp
);
}
}
// File: @openzeppelin/contracts/utils/introspection/ERC165Checker.sol
pragma solidity ^0.8.0;
/**
* @dev Library used to query support of an interface declared via {IERC165}.
*
* Note that these functions return the actual result of the query: they do not
* `revert` if an interface is not supported. It is up to the caller to decide
* what to do in these cases.
*/
library ERC165Checker {
// As per the EIP-165 spec, no interface should ever match 0xffffffff
bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;
/**
* @dev Returns true if `account` supports the {IERC165} interface,
*/
function supportsERC165(address account) internal view returns (bool) {
// Any contract that implements ERC165 must explicitly indicate support of
// InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
return
_supportsERC165Interface(account, type(IERC165).interfaceId) &&
!_supportsERC165Interface(account, _INTERFACE_ID_INVALID);
}
/**
* @dev Returns true if `account` supports the interface defined by
* `interfaceId`. Support for {IERC165} itself is queried automatically.
*
* See {IERC165-supportsInterface}.
*/
function supportsInterface(address account, bytes4 interfaceId)
internal
view
returns (bool)
{
// query support of both ERC165 as per the spec and support of _interfaceId
return
supportsERC165(account) &&
_supportsERC165Interface(account, interfaceId);
}
/**
* @dev Returns a boolean array where each value corresponds to the
* interfaces passed in and whether they're supported or not. This allows
* you to batch check interfaces for a contract where your expectation
* is that some interfaces may not be supported.
*
* See {IERC165-supportsInterface}.
*
* _Available since v3.4._
*/
function getSupportedInterfaces(
address account,
bytes4[] memory interfaceIds
) internal view returns (bool[] memory) {
// an array of booleans corresponding to interfaceIds and whether they're supported or not
bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);
// query support of ERC165 itself
if (supportsERC165(account)) {
// query support of each interface in interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
interfaceIdsSupported[i] = _supportsERC165Interface(
account,
interfaceIds[i]
);
}
}
return interfaceIdsSupported;
}
/**
* @dev Returns true if `account` supports all the interfaces defined in
* `interfaceIds`. Support for {IERC165} itself is queried automatically.
*
* Batch-querying can lead to gas savings by skipping repeated checks for
* {IERC165} support.
*
* See {IERC165-supportsInterface}.
*/
function supportsAllInterfaces(
address account,
bytes4[] memory interfaceIds
) internal view returns (bool) {
// query support of ERC165 itself
if (!supportsERC165(account)) {
return false;
}
// query support of each interface in _interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
if (!_supportsERC165Interface(account, interfaceIds[i])) {
return false;
}
}
// all interfaces supported
return true;
}
/**
* @notice Query if a contract implements an interface, does not check ERC165 support
* @param account The address of the contract to query for support of an interface
* @param interfaceId The interface identifier, as specified in ERC-165
* @return true if the contract at account indicates support of the interface with
* identifier interfaceId, false otherwise
* @dev Assumes that account contains a contract that supports ERC165, otherwise
* the behavior of this method is undefined. This precondition can be checked
* with {supportsERC165}.
* Interface identification is specified in ERC-165.
*/
function _supportsERC165Interface(address account, bytes4 interfaceId)
private
view
returns (bool)
{
bytes memory encodedParams = abi.encodeWithSelector(
IERC165.supportsInterface.selector,
interfaceId
);
(bool success, bytes memory result) = account.staticcall{gas: 30000}(
encodedParams
);
if (result.length < 32) return false;
return success && abi.decode(result, (bool));
}
}
// File: @openzeppelin/contracts/utils/Address.sol
pragma solidity ^0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(
address(this).balance >= amount,
"Address: insufficient balance"
);
(bool success, ) = recipient.call{value: amount}("");
require(
success,
"Address: unable to send value, recipient may have reverted"
);
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data)
internal
returns (bytes memory)
{
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return
functionCallWithValue(
target,
data,
value,
"Address: low-level call with value failed"
);
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(
address(this).balance >= value,
"Address: insufficient balance for call"
);
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(
data
);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data)
internal
view
returns (bytes memory)
{
return
functionStaticCall(
target,
data,
"Address: low-level static call failed"
);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data)
internal
returns (bytes memory)
{
return
functionDelegateCall(
target,
data,
"Address: low-level delegate call failed"
);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// File: contracts/Marketplace/Marketplace.sol
pragma solidity >=0.8.4 <0.9.0;
/**
* @title Marketplace
* @dev The Marketplace is a registry for holding offers, demands, and agreements, called matches.
* @dev Improvements:
* @dev - Add a cron string for both offers and demands to indicate the timeslot selected.
* @dev - Create a server to handle and dispatch the requests from the frontend.
* @dev - Remove most of the data stored on chain to move it to the server or on IPFS.
* @dev - Define the role of aggregators.
*/
contract Marketplace {
IdentityManager private _identityManager;
struct Offer {
uint256 matches; // number of matches already approved for this offer. An offer can be matched multiple times
// string cron; // cron schedule of when the asset is able to provide the service
uint256 volume; // volume provided by the seller in KW
uint256 remainingVolume; // volume that is still available for the offer
uint256 price; // price the seller is willing to sell at in ct/KWh
}
struct Demand {
bool isMatched; // wether the demand has been matched or not. A demand can only be matched once
uint256 volume; // volume provided by the seller in KW
uint256 price; // price the buyer is willing to buy at in ct/KWh
}
struct Match {
address asset; // address (DID) of the seller
address buyer; // address (DID) of the buyer
uint256 volume; // volume requested by the buyer in KW
uint256 price; // price the seller is willing to sell at in ct/KWh
bool isAccepted; // wether the match has been accepted or is still pending as a proposal
}
uint256 private _currentMatchId = 0;
// List of all offers in the marketplace by asset (identified by its address/DID)
mapping(address => Offer) public offers;
// List of all demands in the marketplace by interested user
mapping(address => Demand) public demands;
// List of all matches proposed but not yet accepted by matchId
mapping(uint256 => Match) public matches;
/**************************************************************************
* Events
**************************************************************************/
/**
* @notice A new offer has been added to the marketplace
* @param asset address (DID) of the asset
* @param volume volume provided by the seller in KW
* @param price price the seller is willing to sell at in ct/KWh
*/
event OfferCreated(address indexed asset, uint256 volume, uint256 price);
/**
* @notice A previously issued offer has been cancelled
* @param asset address (DID) of the asset
*/
event OfferCancelled(address indexed asset);
/**
* @notice A new demand has been added to the marketplace
* @param buyer address of the buyer
* @param volume volume requested by the buyer in KW
* @param price price the buyer is willing to buy at in ct/KWh
*/
event DemandCreated(address indexed buyer, uint256 volume, uint256 price);
/**
* @notice A previously issued demand has been cancelled
* @param buyer address of the buyer
*/
event DemandCancelled(address indexed buyer);
/**
* @notice A new match has been proposed by the aggregator
* @param matchId unique identifier of the match
* @param asset address (DID) of the seller
* @param buyer address of the buyer
*/
event MatchProposed(
uint256 indexed matchId,
address indexed asset,
address indexed buyer
);
/**
* @notice A proposed match has been cacelled by the aggregator
* @param matchId unique identifier of the match
*/
event MatchCancelled(uint256 indexed matchId);
/**
* @notice A proposed match has been accepted by the buyer
* @param matchId unique identifier of the match
*/
event MatchAccepted(uint256 indexed matchId);
/**
* @notice A proposed match has been rejected by the buyer
* @param matchId unique identifier of the match
*/
event MatchRejected(uint256 indexed matchId);
/**
* @notice A previously accepted match has been deleted by the buyer or by the asset owner
* @param matchId unique identifier of the match
*/
event MatchDeleted(uint256 indexed matchId);
/**************************************************************************
* Modifiers
**************************************************************************/
/**
* @notice Make sure both volume and price are greater than 0
* @param _volume volume provided by the seller in KW
* @param _price price the seller is willing to sell at in ct/KWh
*/
modifier validProposal(uint256 _volume, uint256 _price) {
require(_volume > 0, "Capacity must be greater than 0");
require(_price > 0, "Price must be greater than 0");
_;
}
/**
* @notice Make sure the action is carried out by the aggregator
* todo the _aggregator should be a variable or should be set according to some logic
* @param _aggregator address of the aggregator
*/
modifier isAggregator(address _aggregator) {
require(
msg.sender == _aggregator,
"This action can only be carried out by an aggregator"
);
_;
}
/**
* @notice Make sure the action is carried out by the asset owner
* @param _asset address (DID) of the asset
*/
modifier isAssetOwner(address _asset) {
require(_asset != address(0), "Asset address cannot be 0x0");
require(
msg.sender == _identityManager.identityOwner(_asset),
"This action can only be carried out by the asset owner"
);
_;
}
/**
* @notice Make sure the asset is not already matched
* @param _asset address (DID) of the asset
*/
modifier isAssetUnmatched(address _asset) {
require(
offers[_asset].matches == 0,
"This action can only be carried out when the asset is unmatched"
);
_;
}
/**
* @notice Make sure the demand is not already matched
*/
modifier isDemandUnmatched() {
require(
!demands[msg.sender].isMatched,
"This action can only be carried out when the demand is unmatched"
);
_;
}
/**
* @notice The match exists
* @param _matchId unique identifier of the match
*/
modifier matchExists(uint256 _matchId) {
require(matches[_matchId].volume > 0, "The match doesn't exists");
_;
}
/**
* @notice The match has already been accepted by the buyer
* @param _matchId unique identifier of the match
*/
modifier isMatchAccepted(uint256 _matchId) {
require(matches[_matchId].volume > 0, "The match doesn't exists");
require(
matches[_matchId].isAccepted,
"The match must still be accepted"
);
_;
}
/**************************************************************************
* Constructor
**************************************************************************/
/**
* @notice Constructor of the Marketplace contract
* @param _identityManagerAddress address of the IdentityManager contract,
* used to check the validity of the assets and their owners
*/
constructor(address _identityManagerAddress) {
_identityManager = IdentityManager(_identityManagerAddress);
}
/**************************************************************************
* Internal functions
**************************************************************************/
/**
* @notice Updates both the offers and the demands signaling the removal of the match
* @param _matchId id of the match to remove
*/
function cleanupAfterMatchRemoval(uint256 _matchId) internal {
Match memory _match = matches[_matchId];
offers[_match.asset].remainingVolume += _match.volume;
offers[_match.asset].matches--;
demands[_match.buyer].isMatched = false;
delete matches[_matchId];
}
/**************************************************************************
* External functions
**************************************************************************/
/**
* @notice Create a new offer linked to a specific asset
* @dev The address that submits the offer must be the owner of the asset
* @dev The volume must be greater than 0
* @dev The price must be greater than 0
* @dev The offed must not be already matched, for it will be overwritten
* @param _asset address (DID) of the asset
* @param _volume volume of the asset in KW
* @param _price price of the energy provided in ct/KWh
*/
function createOffer(
address _asset,
uint256 _volume,
uint256 _price
)
external
validProposal(_volume, _price)
isAssetOwner(_asset)
isAssetUnmatched(_asset)
{
offers[_asset] = Offer(0, _volume, _volume, _price);
emit OfferCreated(_asset, _volume, _price);
}
/**
* @notice Cancels a previously issued offer. Can only be performed if the offer is not matched
* @dev The address that cancels the offer must be the owner of the asset
* @dev The offer must exist
* @dev The offer must not be matched
* @param _asset address (DID) of the asset
*/
function cancelOffer(address _asset)
external
isAssetOwner(_asset)
isAssetUnmatched(_asset)
{
require(offers[_asset].volume != 0, "Offer does not exist");
delete offers[_asset];
emit OfferCancelled(_asset);
}
/**
* @notice Create a new demand
* @dev The volume must be greater than 0
* @dev The price must be greater than 0
* @dev The demand must not be already matched, for it will be overwritten
* @param _volume volume of the asset in KW
* @param _price price of the energy provided in ct/KWh
*/
function createDemand(uint256 _volume, uint256 _price)
external
validProposal(_volume, _price)
isDemandUnmatched
{
demands[msg.sender] = Demand(false, _volume, _price);
emit DemandCreated(msg.sender, _volume, _price);
}
/**
* @notice Cancels a previously issued demand
* @dev The demand must exist
* @dev The demand must not be matched
*/
function cancelDemand() external isDemandUnmatched {
require(demands[msg.sender].volume != 0, "Demand does not exist");
delete demands[msg.sender];
emit DemandCancelled(msg.sender);
}
/**
* @notice Propose a match between an offer and a demand
* @dev The demand must exist
* @dev The demand must not be matched
* @dev The offer must exist
* @dev The offer must not be matched
* @dev The volume of the demand must be greater than the volume of the offer
* @dev The price of the demand must be greater than the price of the offer
* todo Stronger check to make sure the offer volume is respected from multiple matches
* @param _asset address (DID) of the asset
* @param _buyer address of the buyer
* @param _buyer address of the buyer
* @param _buyer address of the buyer
*/
function proposeMatch(
address _asset,
address _buyer,
uint256 _volume,
uint256 _price
) external isAggregator(_buyer) {
require(offers[_asset].volume > 0, "Offer does not exist");
require(demands[_buyer].volume > 0, "Demand does not exist");
require(!demands[_buyer].isMatched, "Demand is already matched");
require(offers[_asset].price <= _price, "Demand price is too low");
require(
offers[_asset].remainingVolume >= _volume,
"Offer remaining volume is too low"
);
_currentMatchId++;
matches[_currentMatchId] = Match(
_asset,
_buyer,
_volume,
_price,
false
);
offers[_asset].matches++;
offers[_asset].remainingVolume -= _volume;
demands[_buyer].isMatched = true;
emit MatchProposed(_currentMatchId, _asset, _buyer);
}
/**
* @notice Cancel a previously proposed match
* @dev Only the aggregator can perform this operation
* @dev The match must exist
* @param _matchId id of the match to cancel
*/
function cancelProposedMatch(uint256 _matchId)
external
isAggregator(matches[_matchId].buyer)
matchExists(_matchId)
{
cleanupAfterMatchRemoval(_matchId);
emit MatchCancelled(_matchId);
}
/**
* @notice Accept a previously proposed match
* @dev Only the buyer can perform this operation
* @dev The match must exist
* @param _matchId id of the match to accept
*/
function acceptMatch(uint256 _matchId) external matchExists(_matchId) {
require(
matches[_matchId].buyer == msg.sender,
"Only the buyer can accept the match"
);
matches[_matchId].isAccepted = true;
emit MatchAccepted(_matchId);
}
/**
* @notice Reject a previously proposed match
* @dev Only the buyer can perform this operation
* @dev The match must exist
* @param _matchId id of the match to reject
*/
function rejectMatch(uint256 _matchId) external matchExists(_matchId) {
require(
matches[_matchId].buyer == msg.sender ||
_identityManager.identityOwner(matches[_matchId].asset) ==
msg.sender,
"The operation can be performed only by the buyer or the asset owner"
);
cleanupAfterMatchRemoval(_matchId);
emit MatchRejected(_matchId);
}
/**
* @notice Delete a previously accepted match
* @dev Both the buyer and the asset owner can perform this operation
* @dev The match must exist and be accepted
* @param _matchId id of the match to delete
*/
function deleteMatch(uint256 _matchId) external isMatchAccepted(_matchId) {
require(
matches[_matchId].isAccepted,
"Match must be accepted or rejected first"
);
require(
matches[_matchId].buyer == msg.sender ||
_identityManager.identityOwner(matches[_matchId].asset) ==
msg.sender,
"The operation can be performed only by the buyer or the asset owner"
);
cleanupAfterMatchRemoval(_matchId);
emit MatchDeleted(_matchId);
}
}
Contract ABI
[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"address","name":"_identityManagerAddress","internalType":"address"}]},{"type":"event","name":"DemandCancelled","inputs":[{"type":"address","name":"buyer","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"DemandCreated","inputs":[{"type":"address","name":"buyer","internalType":"address","indexed":true},{"type":"uint256","name":"volume","internalType":"uint256","indexed":false},{"type":"uint256","name":"price","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"MatchAccepted","inputs":[{"type":"uint256","name":"matchId","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"MatchCancelled","inputs":[{"type":"uint256","name":"matchId","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"MatchDeleted","inputs":[{"type":"uint256","name":"matchId","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"MatchProposed","inputs":[{"type":"uint256","name":"matchId","internalType":"uint256","indexed":true},{"type":"address","name":"asset","internalType":"address","indexed":true},{"type":"address","name":"buyer","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"MatchRejected","inputs":[{"type":"uint256","name":"matchId","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"OfferCancelled","inputs":[{"type":"address","name":"asset","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"OfferCreated","inputs":[{"type":"address","name":"asset","internalType":"address","indexed":true},{"type":"uint256","name":"volume","internalType":"uint256","indexed":false},{"type":"uint256","name":"price","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"acceptMatch","inputs":[{"type":"uint256","name":"_matchId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"cancelDemand","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"cancelOffer","inputs":[{"type":"address","name":"_asset","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"cancelProposedMatch","inputs":[{"type":"uint256","name":"_matchId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"createDemand","inputs":[{"type":"uint256","name":"_volume","internalType":"uint256"},{"type":"uint256","name":"_price","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"createOffer","inputs":[{"type":"address","name":"_asset","internalType":"address"},{"type":"uint256","name":"_volume","internalType":"uint256"},{"type":"uint256","name":"_price","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"deleteMatch","inputs":[{"type":"uint256","name":"_matchId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"isMatched","internalType":"bool"},{"type":"uint256","name":"volume","internalType":"uint256"},{"type":"uint256","name":"price","internalType":"uint256"}],"name":"demands","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"asset","internalType":"address"},{"type":"address","name":"buyer","internalType":"address"},{"type":"uint256","name":"volume","internalType":"uint256"},{"type":"uint256","name":"price","internalType":"uint256"},{"type":"bool","name":"isAccepted","internalType":"bool"}],"name":"matches","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"matches","internalType":"uint256"},{"type":"uint256","name":"volume","internalType":"uint256"},{"type":"uint256","name":"remainingVolume","internalType":"uint256"},{"type":"uint256","name":"price","internalType":"uint256"}],"name":"offers","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"proposeMatch","inputs":[{"type":"address","name":"_asset","internalType":"address"},{"type":"address","name":"_buyer","internalType":"address"},{"type":"uint256","name":"_volume","internalType":"uint256"},{"type":"uint256","name":"_price","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"rejectMatch","inputs":[{"type":"uint256","name":"_matchId","internalType":"uint256"}]}]
Deployed ByteCode
0x608060405234801561001057600080fd5b50600436106100b35760003560e01c80636353d0ce116100715780636353d0ce146101e45780639512ada2146101ec5780639b8a74f0146101ff578063bebcd21e14610212578063dd29757a14610225578063f11610231461027557600080fd5b8062fbf79d146100b85780631b6b2601146100cd5780631c6c7128146100e0578063220ec9d2146100f3578063413bf38f146101065780634768d4ef14610162575b600080fd5b6100cb6100c63660046112ea565b610288565b005b6100cb6100db36600461137b565b610617565b6100cb6100ee36600461132f565b61076e565b6100cb610101366004611363565b6109e5565b61013d6101143660046112ab565b600260208190526000918252604090912080546001820154928201546003909201549092919084565b6040805194855260208501939093529183015260608201526080015b60405180910390f35b6101af610170366004611363565b6004602081905260009182526040909120805460018201546002830154600384015493909401546001600160a01b039283169492909116929060ff1685565b604080516001600160a01b0396871681529590941660208601529284019190915260608301521515608082015260a001610159565b6100cb610a88565b6100cb6101fa366004611363565b610b5b565b6100cb61020d3660046112ab565b610d74565b6100cb610220366004611363565b610f6c565b6102586102333660046112ab565b60036020526000908152604090208054600182015460029092015460ff909116919083565b604080519315158452602084019290925290820152606001610159565b6100cb610283366004611363565b61105a565b82336001600160a01b038216146102ba5760405162461bcd60e51b81526004016102b190611405565b60405180910390fd5b6001600160a01b0385166000908152600260205260409020600101546103195760405162461bcd60e51b815260206004820152601460248201527313d999995c88191bd95cc81b9bdd08195e1a5cdd60621b60448201526064016102b1565b6001600160a01b0384166000908152600360205260409020600101546103795760405162461bcd60e51b815260206004820152601560248201527411195b585b9908191bd95cc81b9bdd08195e1a5cdd605a1b60448201526064016102b1565b6001600160a01b03841660009081526003602052604090205460ff16156103e25760405162461bcd60e51b815260206004820152601960248201527f44656d616e6420697320616c7265616479206d6174636865640000000000000060448201526064016102b1565b6001600160a01b03851660009081526002602052604090206003015482101561044d5760405162461bcd60e51b815260206004820152601760248201527f44656d616e6420707269636520697320746f6f206c6f7700000000000000000060448201526064016102b1565b6001600160a01b038516600090815260026020819052604090912001548311156104c35760405162461bcd60e51b815260206004820152602160248201527f4f666665722072656d61696e696e6720766f6c756d6520697320746f6f206c6f6044820152607760f81b60648201526084016102b1565b600180549060006104d38361159f565b90915550506040805160a0810182526001600160a01b0380881680835287821660208085019182528486018981526060860189815260006080880181815260018054835260048087528b84209a518b54908b166001600160a01b0319918216178c559751918b01805492909a169190971617909755915160028089019190915590516003880155945195909201805495151560ff199096169590951790945590815291529081208054916105868361159f565b90915550506001600160a01b038516600090815260026020819052604082200180548592906105b6908490611571565b90915550506001600160a01b03808516600081815260036020526040808220805460ff19166001908117909155549051929389169290917fe4d8e0f7da725faec426061c134daa691fa7ab36a63d5a70093e1408a3b7006d91a45050505050565b8181600082116106695760405162461bcd60e51b815260206004820152601f60248201527f4361706163697479206d7573742062652067726561746572207468616e20300060448201526064016102b1565b600081116106b95760405162461bcd60e51b815260206004820152601c60248201527f5072696365206d7573742062652067726561746572207468616e20300000000060448201526064016102b1565b3360009081526003602052604090205460ff16156106e95760405162461bcd60e51b81526004016102b19061150d565b60408051606081018252600080825260208083018881528385018881523380855260038452938690209451855460ff191690151517855590516001850155516002909301929092558251878152918201869052917fc87629af897fb7ab00e4fc3e1f1a7979fc163449b23c2c82f5ff24f1380766ab910160405180910390a250505050565b8181600082116107c05760405162461bcd60e51b815260206004820152601f60248201527f4361706163697479206d7573742062652067726561746572207468616e20300060448201526064016102b1565b600081116108105760405162461bcd60e51b815260206004820152601c60248201527f5072696365206d7573742062652067726561746572207468616e20300000000060448201526064016102b1565b846001600160a01b0381166108675760405162461bcd60e51b815260206004820152601b60248201527f417373657420616464726573732063616e6e6f7420626520307830000000000060448201526064016102b1565b6000546040516310e67a9d60e31b81526001600160a01b03838116600483015290911690638733d4e89060240160206040518083038186803b1580156108ac57600080fd5b505afa1580156108c0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108e491906112ce565b6001600160a01b0316336001600160a01b0316146109145760405162461bcd60e51b81526004016102b1906114c9565b6001600160a01b03861660009081526002602052604090205486901561094c5760405162461bcd60e51b81526004016102b190611447565b60408051608081018252600080825260208083018a81528385018b8152606085018b81526001600160a01b038e16808652600280865295889020965187559251600187015590519385019390935591516003909301929092558251898152918201889052917fe899a6e29fab942958a400b4db4c5dec136446a7966a69e0011735027a33fff3910160405180910390a250505050505050565b6000818152600460205260409020600101546001600160a01b0316338114610a1f5760405162461bcd60e51b81526004016102b190611405565b6000828152600460205260409020600201548290610a4f5760405162461bcd60e51b81526004016102b190611492565b610a588361119e565b60405183907f700135b4fe8746e2d2c85a9baa43c62887740aebfeb3a439f71a083fe5d5675990600090a2505050565b3360009081526003602052604090205460ff1615610ab85760405162461bcd60e51b81526004016102b19061150d565b33600090815260036020526040902060010154610b0f5760405162461bcd60e51b815260206004820152601560248201527411195b585b9908191bd95cc81b9bdd08195e1a5cdd605a1b60448201526064016102b1565b33600081815260036020526040808220805460ff1916815560018101839055600201829055517fbb0484f4cc4b64d8fcffeafa625bb8a5513907608f51d2f670592eabc26df8699190a2565b6000818152600460205260409020600201548190610b8b5760405162461bcd60e51b81526004016102b190611492565b6000818152600460208190526040909120015460ff16610bed5760405162461bcd60e51b815260206004820181905260248201527f546865206d61746368206d757374207374696c6c20626520616363657074656460448201526064016102b1565b6000828152600460208190526040909120015460ff16610c605760405162461bcd60e51b815260206004820152602860248201527f4d61746368206d757374206265206163636570746564206f722072656a656374604482015267195908199a5c9cdd60c21b60648201526084016102b1565b6000828152600460205260409020600101546001600160a01b0316331480610d20575060008054838252600460208190526040928390205492516310e67a9d60e31b81526001600160a01b0393841691810191909152339290911690638733d4e89060240160206040518083038186803b158015610cdd57600080fd5b505afa158015610cf1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1591906112ce565b6001600160a01b0316145b610d3c5760405162461bcd60e51b81526004016102b19061139c565b610d458261119e565b60405182907f4a5a134b29f0b3201b6013517626460a42e26046e6827189dc25ed7de0a87b6390600090a25050565b806001600160a01b038116610dcb5760405162461bcd60e51b815260206004820152601b60248201527f417373657420616464726573732063616e6e6f7420626520307830000000000060448201526064016102b1565b6000546040516310e67a9d60e31b81526001600160a01b03838116600483015290911690638733d4e89060240160206040518083038186803b158015610e1057600080fd5b505afa158015610e24573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e4891906112ce565b6001600160a01b0316336001600160a01b031614610e785760405162461bcd60e51b81526004016102b1906114c9565b6001600160a01b038216600090815260026020526040902054829015610eb05760405162461bcd60e51b81526004016102b190611447565b6001600160a01b038316600090815260026020526040902060010154610f0f5760405162461bcd60e51b815260206004820152601460248201527313d999995c88191bd95cc81b9bdd08195e1a5cdd60621b60448201526064016102b1565b6001600160a01b0383166000818152600260208190526040808320838155600181018490559182018390556003909101829055517f58c450e79e079445cb4236ae6f118053d819a6d6477d4b4e91b3552a8ef00fc89190a2505050565b6000818152600460205260409020600201548190610f9c5760405162461bcd60e51b81526004016102b190611492565b6000828152600460205260409020600101546001600160a01b031633146110115760405162461bcd60e51b815260206004820152602360248201527f4f6e6c79207468652062757965722063616e2061636365707420746865206d616044820152620e8c6d60eb1b60648201526084016102b1565b6000828152600460208190526040808320909101805460ff191660011790555183917f73715571185459f4f39409a64e51c86a3d41bb71cf95d02554c190501fdf948891a25050565b600081815260046020526040902060020154819061108a5760405162461bcd60e51b81526004016102b190611492565b6000828152600460205260409020600101546001600160a01b031633148061114a575060008054838252600460208190526040928390205492516310e67a9d60e31b81526001600160a01b0393841691810191909152339290911690638733d4e89060240160206040518083038186803b15801561110757600080fd5b505afa15801561111b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061113f91906112ce565b6001600160a01b0316145b6111665760405162461bcd60e51b81526004016102b19061139c565b61116f8261119e565b60405182907f745319c15fac54211db76094086fd5c643ecd171d1ce911b754e852944b6f2e490600090a25050565b6000818152600460208181526040808420815160a08101835281546001600160a01b0390811680835260018401549091168286015260028084015483860181905260038501546060850152939096015460ff1615156080830152865292849052908420909201805491939091611215908490611559565b909155505080516001600160a01b0316600090815260026020526040812080549161123f83611588565b90915550506020908101516001600160a01b0316600090815260038083526040808320805460ff19908116909155948352600493849052822080546001600160a01b03199081168255600182018054909116905560028101839055908101919091550180549091169055565b6000602082840312156112bc578081fd5b81356112c7816115d0565b9392505050565b6000602082840312156112df578081fd5b81516112c7816115d0565b600080600080608085870312156112ff578283fd5b843561130a816115d0565b9350602085013561131a816115d0565b93969395505050506040820135916060013590565b600080600060608486031215611343578283fd5b833561134e816115d0565b95602085013595506040909401359392505050565b600060208284031215611374578081fd5b5035919050565b6000806040838503121561138d578182fd5b50508035926020909101359150565b60208082526043908201527f546865206f7065726174696f6e2063616e20626520706572666f726d6564206f60408201527f6e6c7920627920746865206275796572206f7220746865206173736574206f776060820152623732b960e91b608082015260a00190565b60208082526034908201526000805160206115e983398151915260408201527337baba10313c9030b71030b3b3b932b3b0ba37b960611b606082015260800190565b6020808252603f908201526000805160206115e983398151915260408201527f6f7574207768656e2074686520617373657420697320756e6d61746368656400606082015260800190565b60208082526018908201527f546865206d6174636820646f65736e2774206578697374730000000000000000604082015260600190565b60208082526036908201526000805160206115e983398151915260408201527537baba10313c903a34329030b9b9b2ba1037bbb732b960511b606082015260800190565b602080825260409082018190526000805160206115e9833981519152908201527f6f7574207768656e207468652064656d616e6420697320756e6d617463686564606082015260800190565b6000821982111561156c5761156c6115ba565b500190565b600082821015611583576115836115ba565b500390565b600081611597576115976115ba565b506000190190565b60006000198214156115b3576115b36115ba565b5060010190565b634e487b7160e01b600052601160045260246000fd5b6001600160a01b03811681146115e557600080fd5b5056fe5468697320616374696f6e2063616e206f6e6c79206265206361727269656420a2646970667358221220f1579a35f81940d705f9be09fcf172caf73adfe3ffefd8b9f7f32aceba2af0f264736f6c63430008040033