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