// 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);
}
}