From 53d6380c0d3de999d4bb3e50dbe9605e6a600c5b Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Mon, 16 Dec 2024 13:19:38 +0000 Subject: [PATCH 1/5] refactor: marketplace proxy --- contracts/AgentMech.sol | 34 +-- contracts/Karma.sol | 1 - contracts/MechFactoryBasic.sol | 6 +- contracts/MechManager.sol | 279 ------------------ contracts/MechMarketplace.sol | 169 +++++++++-- ...agerProxy.sol => MechMarketplaceProxy.sol} | 36 +-- .../nevermined/AgentMechSubscription.sol | 6 +- .../nevermined/MechFactorySubscription.sol | 6 +- contracts/interfaces/IErrorsMarketplace.sol | 10 +- 9 files changed, 190 insertions(+), 357 deletions(-) delete mode 100644 contracts/MechManager.sol rename contracts/{MechManagerProxy.sol => MechMarketplaceProxy.sol} (57%) diff --git a/contracts/AgentMech.sol b/contracts/AgentMech.sol index 0487857..483eaff 100644 --- a/contracts/AgentMech.sol +++ b/contracts/AgentMech.sol @@ -15,14 +15,6 @@ interface IERC721 { function ownerOf(uint256 tokenId) external view returns (address tokenOwner); } -// Mech manager interface -interface IMechManager { - /// @dev Gets the status of mech marketplace. - /// @param mechMarketplace Mech marketplace address. - /// @return status True, if the marketplace is whitelisted. - function mapMechMarketplaces(address mechMarketplace) external view returns (bool status); -} - /// @dev A Mech that is operated by the multisig of an Olas service contract OlasMech is Mech, IErrorsMech, ImmutableStorage { /// @param _serviceRegistry Address of the registry contract. @@ -103,8 +95,8 @@ contract AgentMech is OlasMech { bytes32 public immutable domainSeparator; // Original chain Id uint256 public immutable chainId; - // Mech Manager address - address public immutable mechManager; + // Mech marketplace address. + address public immutable mechMarketplace; // Minimum required price uint256 public price; @@ -131,11 +123,11 @@ contract AgentMech is OlasMech { mapping(address => uint256) public mapNonces; /// @dev AgentMech constructor. - /// @param _mechManager Mech manager address. + /// @param _mechMarketplace Mech marketplace address. /// @param _serviceRegistry Address of the token contract. /// @param _serviceId Service Id. /// @param _price The minimum required price. - constructor(address _mechManager, address _serviceRegistry, uint256 _serviceId, uint256 _price) + constructor(address _mechMarketplace, address _serviceRegistry, uint256 _serviceId, uint256 _price) OlasMech(_serviceRegistry, _serviceId) { // Check for the token to have the owner @@ -144,7 +136,7 @@ contract AgentMech is OlasMech { revert AgentNotFound(_serviceId); } - mechManager = _mechManager; + mechMarketplace = _mechMarketplace; // Record the price price = _price; @@ -252,10 +244,9 @@ contract AgentMech is OlasMech { /// @dev Delivers a request. /// @notice This function ultimately calls mech marketplace contract to finalize the delivery. - /// @param mechMarketplace Mech marketplace address. /// @param requestId Request id. /// @param data Self-descriptive opaque data-blob. - function _deliver(address mechMarketplace, uint256 requestId, bytes memory data) internal returns (bytes memory requestData) { + function _deliver(uint256 requestId, bytes memory data) internal returns (bytes memory requestData) { // Get an account to deliver request to address account = mapRequestAddresses[requestId]; @@ -301,7 +292,7 @@ contract AgentMech is OlasMech { function requestFromMarketplace(address account, uint256 payment, bytes memory data, uint256 requestId) external { // TODO Shall the mech marketplace be checked for being whitelisted by mechManager? Now it's enforced // Check for marketplace access - if (!IMechManager(mechManager).mapMechMarketplaces(msg.sender)) { + if (msg.sender != mechMarketplace) { revert MarketplaceNotAuthorized(msg.sender); } @@ -314,7 +305,7 @@ contract AgentMech is OlasMech { /// @param requestId Request Id. function revokeRequest(uint256 requestId) external { // Check for marketplace access - if (!IMechManager(mechManager).mapMechMarketplaces(msg.sender)) { + if (msg.sender != mechMarketplace) { revert MarketplaceNotAuthorized(msg.sender); } @@ -332,13 +323,11 @@ contract AgentMech is OlasMech { /// @dev Delivers a request by a marketplace. /// @notice This function ultimately calls mech marketplace contract to finalize the delivery. - /// @param mechMarketplace Mech marketplace address. /// @param requestId Request id. /// @param data Self-descriptive opaque data-blob. /// @param mechStakingInstance Mech staking instance address (optional). /// @param mechServiceId Mech operator service Id. function deliverToMarketplace( - address mechMarketplace, uint256 requestId, bytes memory data, address mechStakingInstance, @@ -350,13 +339,8 @@ contract AgentMech is OlasMech { } _locked = 2; - // Check for marketplace access - if (!IMechManager(mechManager).mapMechMarketplaces(mechMarketplace)) { - revert MarketplaceNotAuthorized(mechMarketplace); - } - // Request delivery - bytes memory requestData = _deliver(mechMarketplace, requestId, data); + bytes memory requestData = _deliver(requestId, data); // Mech marketplace delivery finalization if the request was not delivered already if (requestData.length > 0) { diff --git a/contracts/Karma.sol b/contracts/Karma.sol index ffc1664..dae3fed 100644 --- a/contracts/Karma.sol +++ b/contracts/Karma.sol @@ -37,7 +37,6 @@ contract Karma { // Contract owner address public owner; - // TODO This must be fetched from mech manager and be removed / inaccessible from here // Mapping of whitelisted marketplaces mapping(address => bool) public mapMechMarketplaces; // Mapping of mech address => karma diff --git a/contracts/MechFactoryBasic.sol b/contracts/MechFactoryBasic.sol index 26e7ff0..edda91b 100644 --- a/contracts/MechFactoryBasic.sol +++ b/contracts/MechFactoryBasic.sol @@ -11,13 +11,13 @@ contract MechFactoryBasic { string public constant VERSION = "0.1.0"; /// @dev Registers service as a mech. - /// @param mechManager Mech manager address. + /// @param mechMarketplace Mech marketplace address. /// @param serviceRegistry Service registry address. /// @param serviceId Service id. /// @param payload Mech creation payload. /// @return mech The created mech instance address. function createMech( - address mechManager, + address mechMarketplace, address serviceRegistry, uint256 serviceId, bytes memory payload @@ -34,7 +34,7 @@ contract MechFactoryBasic { bytes32 salt = keccak256(abi.encode(block.timestamp, msg.sender, serviceId)); // Service multisig is isOperator() for the mech - mech = address((new AgentMech){salt: salt}(mechManager, serviceRegistry, serviceId, price)); + mech = address((new AgentMech){salt: salt}(mechMarketplace, serviceRegistry, serviceId, price)); emit CreateBasicMech(mech, serviceId, price); } diff --git a/contracts/MechManager.sol b/contracts/MechManager.sol deleted file mode 100644 index a6af63d..0000000 --- a/contracts/MechManager.sol +++ /dev/null @@ -1,279 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; - -interface IMechFactory { - /// @dev Registers service as a mech. - /// @param mechManager Mech manager address. - /// @param serviceRegistry Service registry address. - /// @param serviceId Service id. - /// @param payload Mech creation payload. - /// @return mech The created mech instance address. - function createMech(address mechManager, address serviceRegistry, uint256 serviceId, bytes memory payload) - external returns (address mech); -} - -/// @dev Only `owner` has a privilege, but the `sender` was provided. -/// @param sender Sender address. -/// @param owner Required sender address as an owner. -error OwnerOnly(address sender, address owner); - -/// @dev Provided zero address. -error ZeroAddress(); - -/// @dev The contract is already initialized. -error AlreadyInitialized(); - -/// @dev Account is unauthorized. -/// @param account Account address. -error UnauthorizedAccount(address account); - -/// @dev Wrong length of two arrays. -/// @param numValues1 Number of values in a first array. -/// @param numValues2 Number of values in a second array. -error WrongArrayLength(uint256 numValues1, uint256 numValues2); - -/// @title MechManager - Smart contract for mech manager -/// @author Aleksandr Kuperman - -/// @author Andrey Lebedev - -contract MechManager { - event CreateMech(address indexed mech, uint256 indexed serviceId); - event ImplementationUpdated(address indexed implementation); - event OwnerUpdated(address indexed owner); - event SetMechMarketplaceStatuses(address[] mechMarketplaces, bool[] statuses); - event SetMechFactoryStatuses(address[] mechFactories, bool[] statuses); - - // Version number - string public constant VERSION = "1.0.0"; - // Code position in storage is keccak256("MECH_MANAGER_PROXY") = "0x4d988168e3618e8ed79943415869916bdedf776fc6197c43f9336905a622dab2" - bytes32 public constant MECH_MANAGER_PROXY = 0x4d988168e3618e8ed79943415869916bdedf776fc6197c43f9336905a622dab2; - - // Service registry contract address - address public immutable serviceRegistry; - - // Universal mech marketplace fee - uint256 fee; - // Number of undelivered requests - uint256 public numUndeliveredRequests; - // Number of total requests - uint256 public numTotalRequests; - - // Contract owner - address public owner; - - // Mapping of whitelisted mech marketplaces - mapping(address => bool) public mapMechMarketplaces; - // Mapping of whitelisted mech factories - mapping(address => bool) public mapMechFactories; - // Map of request counts for corresponding requester - mapping(address => uint256) public mapRequestCounts; - // Map of delivery counts for corresponding requester - mapping(address => uint256) public mapDeliveryCounts; - // Map of delivery counts for agent mech - mapping(address => uint256) public mapAgentMechDeliveryCounts; - // Map of delivery counts for corresponding mech service multisig - mapping(address => uint256) public mapMechServiceDeliveryCounts; - // Map of agent mech => its creating factory - mapping(address => address) public mapAgentMechFactories; - - /// @dev MechManager implementation constructor. - /// @param _serviceRegistry Service registry address. - constructor(address _serviceRegistry) { - serviceRegistry = _serviceRegistry; - } - - /// @dev MechManager initializer. - function initialize(uint256 _fee) external{ - if (owner != address(0)) { - revert AlreadyInitialized(); - } - - owner = msg.sender; - fee = _fee; - } - - /// @dev Changes the mechManager implementation contract address. - /// @param newImplementation New implementation contract address. - function changeImplementation(address newImplementation) external { - // Check for the ownership - if (msg.sender != owner) { - revert OwnerOnly(msg.sender, owner); - } - - // Check for zero address - if (newImplementation == address(0)) { - revert ZeroAddress(); - } - - // Store the mechManager implementation address - assembly { - sstore(MECH_MANAGER_PROXY, newImplementation) - } - - emit ImplementationUpdated(newImplementation); - } - - /// @dev Changes contract owner address. - /// @param newOwner Address of a new owner. - function changeOwner(address newOwner) external virtual { - // Check for the ownership - if (msg.sender != owner) { - revert OwnerOnly(msg.sender, owner); - } - - // Check for the zero address - if (newOwner == address(0)) { - revert ZeroAddress(); - } - - owner = newOwner; - emit OwnerUpdated(newOwner); - } - - /// @dev Registers service as a mech. - /// @param serviceId Service id. - /// @param mechFactory Mech factory address. - /// @return mech The created mech instance address. - function create(uint256 serviceId, address mechFactory, bytes memory payload) external returns (address mech) { - // Check for factory status - if (!mapMechFactories[mechFactory]) { - revert UnauthorizedAccount(mechFactory); - } - - mech = IMechFactory(mechFactory).createMech(address(this), serviceRegistry, serviceId, payload); - - // This should never be the case - if (mech == address(0)) { - revert ZeroAddress(); - } - - // Record factory that created agent mech - mapAgentMechFactories[mech] = mechFactory; - - emit CreateMech(mech, serviceId); - } - - /// @dev Sets mech marketplace statues. - /// @param mechMarketplaces Mech marketplace contract addresses. - /// @param statuses Corresponding whitelisting statues. - function setMechMarketplaceStatuses(address[] memory mechMarketplaces, bool[] memory statuses) external { - // Check for the ownership - if (msg.sender != owner) { - revert OwnerOnly(msg.sender, owner); - } - - if (mechMarketplaces.length != statuses.length) { - revert WrongArrayLength(mechMarketplaces.length, statuses.length); - } - - // Traverse all the mech marketplaces and statuses - for (uint256 i = 0; i < mechMarketplaces.length; ++i) { - if (mechMarketplaces[i] == address(0)) { - revert ZeroAddress(); - } - - mapMechMarketplaces[mechMarketplaces[i]] = statuses[i]; - } - - emit SetMechMarketplaceStatuses(mechMarketplaces, statuses); - } - - /// @dev Sets mech factory statues. - /// @param mechFactories Mech marketplace contract addresses. - /// @param statuses Corresponding whitelisting statues. - function setMechFactoryStatuses(address[] memory mechFactories, bool[] memory statuses) external { - // Check for the ownership - if (msg.sender != owner) { - revert OwnerOnly(msg.sender, owner); - } - - if (mechFactories.length != statuses.length) { - revert WrongArrayLength(mechFactories.length, statuses.length); - } - - // Traverse all the mech marketplaces and statuses - for (uint256 i = 0; i < mechFactories.length; ++i) { - if (mechFactories[i] == address(0)) { - revert ZeroAddress(); - } - - mapMechFactories[mechFactories[i]] = statuses[i]; - } - - emit SetMechFactoryStatuses(mechFactories, statuses); - } - - function increaseRequestsCounts(address requester) external { - // Check for marketplace access - if (!mapMechMarketplaces[msg.sender]) { - revert UnauthorizedAccount(msg.sender); - } - - // Record the request count - mapRequestCounts[requester]++; - // Increase the number of undelivered requests - numUndeliveredRequests++; - // Increase the total number of requests - numTotalRequests++; - } - - function increaseDeliveryCounts(address requester, address agentMech, address mechServiceMultisig) external { - // Check for marketplace access - if (!mapMechMarketplaces[msg.sender]) { - revert UnauthorizedAccount(msg.sender); - } - - // Decrease the number of undelivered requests - numUndeliveredRequests--; - // Increase the amount of requester delivered requests - mapDeliveryCounts[requester]++; - // Increase the amount of agent mech delivery counts - mapAgentMechDeliveryCounts[agentMech]++; - // Increase the amount of mech service multisig delivered requests - mapMechServiceDeliveryCounts[mechServiceMultisig]++; - } - - /// @dev Validates agent mech. - /// @param agentMech Agent mech address. - /// @return status True, if the mech is valid. - function checkMechValidity(address agentMech) external view returns (bool status) { - // TODO: shall we also check the status of the factory? - // TODO: if yes, what if the factory was de-whitelisted? all the mechs then become invalid - status = mapAgentMechFactories[agentMech] != address(0); - } - - /// @dev Gets the implementation address. - /// @return implementation Implementation address. - function getImplementation() external view returns (address implementation) { - assembly { - implementation := sload(MECH_MANAGER_PROXY) - } - } - - /// @dev Gets the requests count for a specific account. - /// @param account Account address. - /// @return Requests count. - function getRequestsCount(address account) external view returns (uint256) { - return mapRequestCounts[account]; - } - - /// @dev Gets the deliveries count for a specific account. - /// @param account Account address. - /// @return Deliveries count. - function getDeliveriesCount(address account) external view returns (uint256) { - return mapDeliveryCounts[account]; - } - - /// @dev Gets deliveries count for a specific agent mech. - /// @param agentMech Agent mech address. - /// @return Deliveries count. - function getAgentMechDeliveriesCount(address agentMech) external view returns (uint256) { - return mapAgentMechDeliveryCounts[agentMech]; - } - - /// @dev Gets deliveries count for a specific mech service multisig. - /// @param mechServiceMultisig Agent mech service multisig address. - /// @return Deliveries count. - function getMechServiceDeliveriesCount(address mechServiceMultisig) external view returns (uint256) { - return mapMechServiceDeliveryCounts[mechServiceMultisig]; - } -} \ No newline at end of file diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index b34fbb8..c351b33 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -7,11 +7,15 @@ import {IMech} from "./interfaces/IMech.sol"; import {IServiceRegistry} from "./interfaces/IServiceRegistry.sol"; import {IStaking, IStakingFactory} from "./interfaces/IStaking.sol"; -interface IMechManager { - function increaseRequestsCounts(address requester) external; - function increaseDeliveryCounts(address requester, address agentMech, address mechServiceMultisig) external; - // Universal mech marketplace fee - function fee() external returns (uint256); +interface IMechFactory { + /// @dev Registers service as a mech. + /// @param mechManager Mech manager address. + /// @param serviceRegistry Service registry address. + /// @param serviceId Service id. + /// @param payload Mech creation payload. + /// @return mech The created mech instance address. + function createMech(address mechManager, address serviceRegistry, uint256 serviceId, bytes memory payload) + external returns (address mech); } // Mech delivery info struct @@ -31,6 +35,10 @@ struct MechDelivery { /// @author Andrey Lebedev - /// @author Silvere Gangloff - contract MechMarketplace is IErrorsMarketplace { + event CreateMech(address indexed mech, uint256 indexed serviceId); + event ImplementationUpdated(address indexed implementation); + event OwnerUpdated(address indexed owner); + event SetMechFactoryStatuses(address[] mechFactories, bool[] statuses); event MarketplaceRequest(address indexed requester, address indexed requestedMech, uint256 requestId, bytes data); event MarketplaceDeliver(address indexed priorityMech, address indexed actualMech, address indexed requester, uint256 requestId, bytes data); @@ -45,6 +53,8 @@ contract MechMarketplace is IErrorsMarketplace { // Contract version number string public constant VERSION = "1.1.0"; + // Code position in storage is keccak256("MECH_MARKETPLACE_PROXY") = "0xe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca" + bytes32 public constant MECH_MARKETPLACE_PROXY = 0xe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca; // Domain separator type hash bytes32 public constant DOMAIN_SEPARATOR_TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); @@ -58,13 +68,13 @@ contract MechMarketplace is IErrorsMarketplace { uint256 public immutable maxResponseTimeout; // Mech karma contract address address public immutable karma; - // Mech manager contract address - address public immutable mechManager; // Staking factory contract address address public immutable stakingFactory; // Service registry contract address address public immutable serviceRegistry; + // Universal mech marketplace fee (max of 10_000 == 100%) + uint256 public fee; // Collected fees uint256 public collectedFees; // Number of undelivered requests @@ -74,16 +84,25 @@ contract MechMarketplace is IErrorsMarketplace { // Reentrancy lock uint256 internal _locked = 1; + // Contract owner + address public owner; + // Map of request payments mapping(uint256 => uint256) public mapRequestIdPayments; // Map of request counts for corresponding requester mapping(address => uint256) public mapRequestCounts; // Map of delivery counts for corresponding requester mapping(address => uint256) public mapDeliveryCounts; + // Map of delivery counts for agent mech + mapping(address => uint256) public mapAgentMechDeliveryCounts; // Map of delivery counts for corresponding mech service multisig mapping(address => uint256) public mapMechServiceDeliveryCounts; // Mapping of request Id => mech delivery information mapping(uint256 => MechDelivery) public mapRequestIdDeliveries; + // Mapping of whitelisted mech factories + mapping(address => bool) public mapMechFactories; + // Map of agent mech => its creating factory + mapping(address => address) public mapAgentMechFactories; // Mapping of account nonces mapping(address => uint256) public mapNonces; @@ -97,14 +116,11 @@ contract MechMarketplace is IErrorsMarketplace { address _serviceRegistry, address _stakingFactory, address _karma, - address _mechManager, uint256 _minResponseTimeout, uint256 _maxResponseTimeout ) { // Check for zero address - if (_serviceRegistry == address(0) || _stakingFactory == address(0) || _karma == address(0) || - _mechManager == address(0)) - { + if (_serviceRegistry == address(0) || _stakingFactory == address(0) || _karma == address(0)) { revert ZeroAddress(); } @@ -126,7 +142,6 @@ contract MechMarketplace is IErrorsMarketplace { serviceRegistry = _serviceRegistry; stakingFactory = _stakingFactory; karma = _karma; - mechManager = _mechManager; minResponseTimeout = _minResponseTimeout; maxResponseTimeout = _maxResponseTimeout; @@ -165,8 +180,8 @@ contract MechMarketplace is IErrorsMarketplace { // Process payment if (payment > 0) { - uint256 fee = IMechManager(mechManager).fee(); - payment = payment - (payment * fee) / 100; + uint256 localCollectedFee = (payment * fee) / 10_000; + payment = payment - localCollectedFee; // Transfer payment (bool success, ) = deliveryMech.call{value: msg.value}(""); @@ -175,14 +190,110 @@ contract MechMarketplace is IErrorsMarketplace { } // Update collected fees - collectedFees += fee; + collectedFees += localCollectedFee; - emit DeliveryPayment(requestId, deliveryMech, payment, fee); + emit DeliveryPayment(requestId, deliveryMech, payment, localCollectedFee); } _locked = 1; } + /// @dev MechMarketplace initializer. + function initialize(uint256 _fee) external{ + if (owner != address(0)) { + revert AlreadyInitialized(); + } + + owner = msg.sender; + fee = _fee; + } + + /// @dev Changes contract owner address. + /// @param newOwner Address of a new owner. + function changeOwner(address newOwner) external virtual { + // Check for the ownership + if (msg.sender != owner) { + revert OwnerOnly(msg.sender, owner); + } + + // Check for the zero address + if (newOwner == address(0)) { + revert ZeroAddress(); + } + + owner = newOwner; + emit OwnerUpdated(newOwner); + } + + /// @dev Changes the mechMarketplace implementation contract address. + /// @param newImplementation New implementation contract address. + function changeImplementation(address newImplementation) external { + // Check for the ownership + if (msg.sender != owner) { + revert OwnerOnly(msg.sender, owner); + } + + // Check for zero address + if (newImplementation == address(0)) { + revert ZeroAddress(); + } + + // Store the mechMarketplace implementation address + assembly { + sstore(MECH_MARKETPLACE_PROXY, newImplementation) + } + + emit ImplementationUpdated(newImplementation); + } + + /// @dev Registers service as a mech. + /// @param serviceId Service id. + /// @param mechFactory Mech factory address. + /// @return mech The created mech instance address. + function create(uint256 serviceId, address mechFactory, bytes memory payload) external returns (address mech) { + // Check for factory status + if (!mapMechFactories[mechFactory]) { + revert UnauthorizedAccount(mechFactory); + } + + mech = IMechFactory(mechFactory).createMech(address(this), serviceRegistry, serviceId, payload); + + // This should never be the case + if (mech == address(0)) { + revert ZeroAddress(); + } + + // Record factory that created agent mech + mapAgentMechFactories[mech] = mechFactory; + + emit CreateMech(mech, serviceId); + } + + /// @dev Sets mech factory statues. + /// @param mechFactories Mech marketplace contract addresses. + /// @param statuses Corresponding whitelisting statues. + function setMechFactoryStatuses(address[] memory mechFactories, bool[] memory statuses) external { + // Check for the ownership + if (msg.sender != owner) { + revert OwnerOnly(msg.sender, owner); + } + + if (mechFactories.length != statuses.length) { + revert WrongArrayLength(mechFactories.length, statuses.length); + } + + // Traverse all the mech marketplaces and statuses + for (uint256 i = 0; i < mechFactories.length; ++i) { + if (mechFactories[i] == address(0)) { + revert ZeroAddress(); + } + + mapMechFactories[mechFactories[i]] = statuses[i]; + } + + emit SetMechFactoryStatuses(mechFactories, statuses); + } + /// @dev Registers a request. /// @notice The request is going to be registered for a specified priority agent mech. /// @param data Self-descriptive opaque data-blob. @@ -276,9 +387,6 @@ contract MechMarketplace is IErrorsMarketplace { // Record request payment mapRequestIdPayments[requestId] = msg.value; - // Record request counts in global sets - IMechManager(mechManager).increaseRequestsCounts(msg.sender); - // Process request by a specified priority mech IMech(priorityMech).requestFromMarketplace(msg.sender, msg.value, data, requestId); @@ -357,12 +465,11 @@ contract MechMarketplace is IErrorsMarketplace { numUndeliveredRequests--; // Increase the amount of requester delivered requests mapDeliveryCounts[requester]++; + // Increase the amount of agent mech delivery counts + mapAgentMechDeliveryCounts[msg.sender]++; // Increase the amount of mech service multisig delivered requests mapMechServiceDeliveryCounts[mechServiceMultisig]++; - // Record request counts in global sets - IMechManager(mechManager).increaseDeliveryCounts(requester, msg.sender, mechServiceMultisig); - // Increase mech karma that delivers the request IKarma(karma).changeMechKarma(msg.sender, 1); @@ -484,6 +591,16 @@ contract MechMarketplace is IErrorsMarketplace { } } + // TODO Check if relevant + /// @dev Validates agent mech. + /// @param agentMech Agent mech address. + /// @return status True, if the mech is valid. + function checkMechValidity(address agentMech) external view returns (bool status) { + // TODO: shall we also check the status of the factory? + // TODO: if yes, what if the factory was de-whitelisted? all the mechs then become invalid + status = mapAgentMechFactories[agentMech] != address(0); + } + /// @dev Checks for requester validity. /// @dev requester Requester contract address. /// @param requesterStakingInstance Requester staking instance address. @@ -541,6 +658,14 @@ contract MechMarketplace is IErrorsMarketplace { return mapDeliveryCounts[account]; } + // TODO Check if needed + /// @dev Gets deliveries count for a specific agent mech. + /// @param agentMech Agent mech address. + /// @return Deliveries count. + function getAgentMechDeliveriesCount(address agentMech) external view returns (uint256) { + return mapAgentMechDeliveryCounts[agentMech]; + } + /// @dev Gets deliveries count for a specific mech service multisig. /// @param mechServiceMultisig Agent mech service multisig address. /// @return Deliveries count. diff --git a/contracts/MechManagerProxy.sol b/contracts/MechMarketplaceProxy.sol similarity index 57% rename from contracts/MechManagerProxy.sol rename to contracts/MechMarketplaceProxy.sol index fd1c0d1..ba613ac 100644 --- a/contracts/MechManagerProxy.sol +++ b/contracts/MechMarketplaceProxy.sol @@ -4,49 +4,49 @@ pragma solidity ^0.8.28; /// @dev Zero implementation address. error ZeroImplementationAddress(); -/// @dev Zero mechManager data. -error ZeroMechManagerData(); +/// @dev Zero initialization data. +error ZeroData(); /// @dev Proxy initialization failed. error InitializationFailed(); /* -* This is a MechManager proxy contract. +* This is a MechMarketplace proxy contract. * Proxy implementation is created based on the Universal Upgradeable Proxy Standard (UUPS) EIP-1822. * The implementation address must be located in a unique storage slot of the proxy contract. * The upgrade logic must be located in the implementation contract. -* Special mechManager implementation address slot is produced by hashing the "MECH_MANAGER_PROXY" +* Special mechMarketplace implementation address slot is produced by hashing the "MECH_MARKETPLACE_PROXY" * string in order to make the slot unique. * The fallback() implementation for all the delegatecall-s is inspired by the Gnosis Safe set of contracts. */ -/// @title MechManagerProxy - Smart contract for mech manager proxy +/// @title MechMarketplaceProxy - Smart contract for mech marketplace proxy /// @author Aleksandr Kuperman - /// @author Andrey Lebedev - -contract MechManagerProxy { - // Code position in storage is keccak256("MECH_MANAGER_PROXY") = "0x4d988168e3618e8ed79943415869916bdedf776fc6197c43f9336905a622dab2" - bytes32 public constant MECH_MANAGER_PROXY = 0x4d988168e3618e8ed79943415869916bdedf776fc6197c43f9336905a622dab2; +contract MechMarketplaceProxy { + // Code position in storage is keccak256("MECH_MARKETPLACE_PROXY") = "0xe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca" + bytes32 public constant MECH_MARKETPLACE_PROXY = 0xe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca; - /// @dev MechManagerProxy constructor. - /// @param implementation MechManager implementation address. - /// @param mechManagerData MechManager initialization data. - constructor(address implementation, bytes memory mechManagerData) { + /// @dev MechMarketplaceProxy constructor. + /// @param implementation MechMarketplace implementation address. + /// @param mechMarketplaceData MechMarketplace initialization data. + constructor(address implementation, bytes memory mechMarketplaceData) { // Check for the zero address, since the delegatecall works even with the zero one if (implementation == address(0)) { revert ZeroImplementationAddress(); } // Check for the zero data - if (mechManagerData.length == 0) { - revert ZeroMechManagerData(); + if (mechMarketplaceData.length == 0) { + revert ZeroData(); } - // Store the mechManager implementation address + // Store the mechMarketplace implementation address assembly { - sstore(MECH_MANAGER_PROXY, implementation) + sstore(MECH_MARKETPLACE_PROXY, implementation) } // Initialize proxy tokenomics storage - (bool success, ) = implementation.delegatecall(mechManagerData); + (bool success, ) = implementation.delegatecall(mechMarketplaceData); if (!success) { revert InitializationFailed(); } @@ -55,7 +55,7 @@ contract MechManagerProxy { /// @dev Delegatecall to all the incoming data. fallback() external { assembly { - let implementation := sload(MECH_MANAGER_PROXY) + let implementation := sload(MECH_MARKETPLACE_PROXY) calldatacopy(0, 0, calldatasize()) let success := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) diff --git a/contracts/integrations/nevermined/AgentMechSubscription.sol b/contracts/integrations/nevermined/AgentMechSubscription.sol index b822c7b..1579013 100644 --- a/contracts/integrations/nevermined/AgentMechSubscription.sol +++ b/contracts/integrations/nevermined/AgentMechSubscription.sol @@ -44,21 +44,21 @@ contract AgentMechSubscription is AgentMech { uint256 public subscriptionTokenId; /// @dev AgentMechSubscription constructor. - /// @param _mechManager Mech manager address. + /// @param _mechMarketplace Mech marketplace address. /// @param _registry Address of the token registry contract. /// @param _tokenId The token ID. /// @param _minCreditsPerRequest Minimum number of credits to pay for each request via a subscription. /// @param _subscriptionNFT Subscription address. /// @param _subscriptionTokenId Subscription token Id. constructor( - address _mechManager, + address _mechMarketplace, address _registry, uint256 _tokenId, uint256 _minCreditsPerRequest, address _subscriptionNFT, uint256 _subscriptionTokenId ) - AgentMech(_mechManager, _registry, _tokenId, _minCreditsPerRequest) + AgentMech(_mechMarketplace, _registry, _tokenId, _minCreditsPerRequest) { // Check for the subscription address if (_subscriptionNFT == address(0)) { diff --git a/contracts/integrations/nevermined/MechFactorySubscription.sol b/contracts/integrations/nevermined/MechFactorySubscription.sol index 53b782f..7e1db56 100644 --- a/contracts/integrations/nevermined/MechFactorySubscription.sol +++ b/contracts/integrations/nevermined/MechFactorySubscription.sol @@ -12,13 +12,13 @@ contract MechFactorySubscription { string public constant VERSION = "0.1.0"; /// @dev Registers service as a mech. - /// @param mechManager Mech manager address. + /// @param mechMarketplace Mech marketplace address. /// @param serviceRegistry Service registry address. /// @param serviceId Service id. /// @param payload Mech creation payload. /// @return mech The created mech instance address. function createMech( - address mechManager, + address mechMarketplace, address serviceRegistry, uint256 serviceId, bytes memory payload @@ -36,7 +36,7 @@ contract MechFactorySubscription { bytes32 salt = keccak256(abi.encode(block.timestamp, msg.sender, serviceId)); // Service multisig is isOperator() for the mech - mech = address((new AgentMechSubscription){salt: salt}(mechManager, serviceRegistry, serviceId, + mech = address((new AgentMechSubscription){salt: salt}(mechMarketplace, serviceRegistry, serviceId, minCreditsPerRequest, subscriptionNFT, subscriptionTokenId)); emit CreateSubscriptionMech(mech, serviceId, minCreditsPerRequest, subscriptionNFT, subscriptionTokenId); diff --git a/contracts/interfaces/IErrorsMarketplace.sol b/contracts/interfaces/IErrorsMarketplace.sol index 8f23d72..3828e7e 100644 --- a/contracts/interfaces/IErrorsMarketplace.sol +++ b/contracts/interfaces/IErrorsMarketplace.sol @@ -13,9 +13,13 @@ interface IErrorsMarketplace { /// @dev Provided zero value. error ZeroValue(); - /// @dev Agent does not exist. - /// @param agentId Agent Id. - error AgentNotFound(uint256 agentId); + /// @dev The contract is already initialized. + error AlreadyInitialized(); + + /// @dev Wrong length of two arrays. + /// @param numValues1 Number of values in a first array. + /// @param numValues2 Number of values in a second array. + error WrongArrayLength(uint256 numValues1, uint256 numValues2); /// @dev Not enough value paid. /// @param provided Provided amount. From 44cdd817286f563b2c0af2ea8698abd9034f9b6c Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Mon, 16 Dec 2024 13:26:22 +0000 Subject: [PATCH 2/5] refactor: adjust MM to mech payment --- contracts/AgentMech.sol | 2 +- contracts/MechMarketplace.sol | 73 ++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/contracts/AgentMech.sol b/contracts/AgentMech.sol index 483eaff..3be4534 100644 --- a/contracts/AgentMech.sol +++ b/contracts/AgentMech.sol @@ -95,7 +95,7 @@ contract AgentMech is OlasMech { bytes32 public immutable domainSeparator; // Original chain Id uint256 public immutable chainId; - // Mech marketplace address. + // Mech marketplace address address public immutable mechMarketplace; // Minimum required price diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index c351b33..1da4c71 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -73,6 +73,7 @@ contract MechMarketplace is IErrorsMarketplace { // Service registry contract address address public immutable serviceRegistry; + // TODO: if the fee is defined here, needs its changing function // Universal mech marketplace fee (max of 10_000 == 100%) uint256 public fee; // Collected fees @@ -165,39 +166,6 @@ contract MechMarketplace is IErrorsMarketplace { ); } - /// @dev Processes payment for request delivery. - /// @param deliveryMech Delivery agent mech address. - /// @param requestId Request id. - function _processPayment(address deliveryMech, uint256 requestId) internal virtual { - // Reentrancy guard - if (_locked > 1) { - revert ReentrancyGuard(); - } - _locked = 2; - - // Get request Id payment - uint256 payment = mapRequestIdPayments[requestId]; - - // Process payment - if (payment > 0) { - uint256 localCollectedFee = (payment * fee) / 10_000; - payment = payment - localCollectedFee; - - // Transfer payment - (bool success, ) = deliveryMech.call{value: msg.value}(""); - if (!success) { - revert TransferFailed(address(0), address(this), deliveryMech, payment); - } - - // Update collected fees - collectedFees += localCollectedFee; - - emit DeliveryPayment(requestId, deliveryMech, payment, localCollectedFee); - } - - _locked = 1; - } - /// @dev MechMarketplace initializer. function initialize(uint256 _fee) external{ if (owner != address(0)) { @@ -473,10 +441,45 @@ contract MechMarketplace is IErrorsMarketplace { // Increase mech karma that delivers the request IKarma(karma).changeMechKarma(msg.sender, 1); + emit MarketplaceDeliver(priorityMech, msg.sender, requester, requestId, requestData); + + _locked = 1; + } + + /// @dev Processes payment for request delivery. + /// @param requestId Request id. + function processPayment(uint256 requestId) external { + // Reentrancy guard + if (_locked > 1) { + revert ReentrancyGuard(); + } + _locked = 2; + + // Check that request was delivered by msg.sender + MechDelivery storage mechDelivery = mapRequestIdDeliveries[requestId]; + if (msg.sender != mechDelivery.deliveryMech) { + revert UnauthorizedAccount(msg.sender); + } + + // Get request Id payment + uint256 payment = mapRequestIdPayments[requestId]; + // Process payment - _processPayment(msg.sender, requestId); + if (payment > 0) { + uint256 localCollectedFee = (payment * fee) / 10_000; + payment = payment - localCollectedFee; - emit MarketplaceDeliver(priorityMech, msg.sender, requester, requestId, requestData); + // Transfer payment + (bool success, ) = msg.sender.call{value: payment}(""); + if (!success) { + revert TransferFailed(address(0), address(this), msg.sender, payment); + } + + // Update collected fees + collectedFees += localCollectedFee; + + emit DeliveryPayment(requestId, msg.sender, payment, localCollectedFee); + } _locked = 1; } From a9e87f162fd27ee3d0a76f335ee1b3989eed36e6 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Mon, 16 Dec 2024 13:38:53 +0000 Subject: [PATCH 3/5] refactor: adjust MM to mech payment --- contracts/MechMarketplace.sol | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index 1da4c71..9325450 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -27,7 +27,9 @@ struct MechDelivery { // Requester address address requester; // Response timeout window - uint32 responseTimeout; + uint256 responseTimeout; + // Payment amount + uint256 payment; } /// @title Mech Marketplace - Marketplace for posting and delivering requests served by agent mechs @@ -88,8 +90,6 @@ contract MechMarketplace is IErrorsMarketplace { // Contract owner address public owner; - // Map of request payments - mapping(uint256 => uint256) public mapRequestIdPayments; // Map of request counts for corresponding requester mapping(address => uint256) public mapRequestCounts; // Map of delivery counts for corresponding requester @@ -334,9 +334,11 @@ contract MechMarketplace is IErrorsMarketplace { // Record priorityMech and response timeout mechDelivery.priorityMech = priorityMech; // responseTimeout from relative time to absolute time - mechDelivery.responseTimeout = uint32(responseTimeout + block.timestamp); + mechDelivery.responseTimeout = responseTimeout + block.timestamp; // Record request account mechDelivery.requester = msg.sender; + // Record payment for request + mechDelivery.payment = msg.value; // Increase mech requester karma IKarma(karma).changeRequesterMechKarma(msg.sender, priorityMech, 1); @@ -348,13 +350,6 @@ contract MechMarketplace is IErrorsMarketplace { // Increase the total number of requests numTotalRequests++; - // Must never happen - if (mapRequestIdPayments[requestId] > 0) { - revert RequestPaid(requestId); - } - // Record request payment - mapRequestIdPayments[requestId] = msg.value; - // Process request by a specified priority mech IMech(priorityMech).requestFromMarketplace(msg.sender, msg.value, data, requestId); @@ -462,7 +457,7 @@ contract MechMarketplace is IErrorsMarketplace { } // Get request Id payment - uint256 payment = mapRequestIdPayments[requestId]; + uint256 payment = mechDelivery.payment; // Process payment if (payment > 0) { @@ -579,9 +574,8 @@ contract MechMarketplace is IErrorsMarketplace { revert ZeroValue(); } - // Check mech validity - bool status = IMech(mech).checkMechValidity(mech); - if (!status) { + // Check mech validity as it must be created and recorded via this marketplace + if (mapAgentMechFactories[mech] == address(0)) { revert UnauthorizedAccount(mech); } @@ -594,16 +588,6 @@ contract MechMarketplace is IErrorsMarketplace { } } - // TODO Check if relevant - /// @dev Validates agent mech. - /// @param agentMech Agent mech address. - /// @return status True, if the mech is valid. - function checkMechValidity(address agentMech) external view returns (bool status) { - // TODO: shall we also check the status of the factory? - // TODO: if yes, what if the factory was de-whitelisted? all the mechs then become invalid - status = mapAgentMechFactories[agentMech] != address(0); - } - /// @dev Checks for requester validity. /// @dev requester Requester contract address. /// @param requesterStakingInstance Requester staking instance address. From 42bb2cc2e147513ba112bd0a1b593fc21df1926f Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Mon, 16 Dec 2024 14:08:44 +0000 Subject: [PATCH 4/5] refactor: wrapping collected fees --- contracts/MechMarketplace.sol | 66 +++++++++++++++++++++++++++++++--- contracts/interfaces/IMech.sol | 5 --- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index 9325450..d9869a8 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -18,6 +18,18 @@ interface IMechFactory { external returns (address mech); } +interface IToken { + /// @dev Transfers the token amount. + /// @param to Address to transfer to. + /// @param amount The amount to transfer. + /// @return True if the function execution is successful. + function transfer(address to, uint256 amount) external returns (bool); +} + +interface IWrappedToken { + function deposit() external payable; +} + // Mech delivery info struct struct MechDelivery { // Priority mech address @@ -45,6 +57,7 @@ contract MechMarketplace is IErrorsMarketplace { event MarketplaceDeliver(address indexed priorityMech, address indexed actualMech, address indexed requester, uint256 requestId, bytes data); event DeliveryPayment(uint256 indexed requestId, address indexed deliveryMech, uint256 payment, uint256 fee); + event Drained(uint256 collectedFees); enum RequestStatus { DoesNotExist, @@ -74,6 +87,10 @@ contract MechMarketplace is IErrorsMarketplace { address public immutable stakingFactory; // Service registry contract address address public immutable serviceRegistry; + // Wrapped native token address + address public immutable wrappedNativeToken; + // Buy back burner address + address public immutable buyBackBurner; // TODO: if the fee is defined here, needs its changing function // Universal mech marketplace fee (max of 10_000 == 100%) @@ -84,6 +101,8 @@ contract MechMarketplace is IErrorsMarketplace { uint256 public numUndeliveredRequests; // Number of total requests uint256 public numTotalRequests; + // Number of created mechs + uint256 public numMechs; // Reentrancy lock uint256 internal _locked = 1; @@ -106,22 +125,31 @@ contract MechMarketplace is IErrorsMarketplace { mapping(address => address) public mapAgentMechFactories; // Mapping of account nonces mapping(address => uint256) public mapNonces; + // Set of mechs created by this marketplace + address[] public setMechs; + + // TODO: able to change min/max ResponseTimeout? /// @dev MechMarketplace constructor. /// @param _serviceRegistry Service registry contract address. /// @param _stakingFactory Staking factory contract address. /// @param _karma Karma proxy contract address. + /// @param _wrappedNativeToken Wrapped native token address. + /// @param _buyBackBurner Buy back burner address. /// @param _minResponseTimeout Min response time in sec. /// @param _maxResponseTimeout Max response time in sec. constructor( address _serviceRegistry, address _stakingFactory, address _karma, + address _wrappedNativeToken, + address _buyBackBurner, uint256 _minResponseTimeout, uint256 _maxResponseTimeout ) { // Check for zero address - if (_serviceRegistry == address(0) || _stakingFactory == address(0) || _karma == address(0)) { + if (_serviceRegistry == address(0) || _stakingFactory == address(0) || _karma == address(0) || + _wrappedNativeToken == address(0) || _buyBackBurner == address(0)) { revert ZeroAddress(); } @@ -143,6 +171,8 @@ contract MechMarketplace is IErrorsMarketplace { serviceRegistry = _serviceRegistry; stakingFactory = _stakingFactory; karma = _karma; + wrappedNativeToken = _wrappedNativeToken; + buyBackBurner = _buyBackBurner; minResponseTimeout = _minResponseTimeout; maxResponseTimeout = _maxResponseTimeout; @@ -176,6 +206,10 @@ contract MechMarketplace is IErrorsMarketplace { fee = _fee; } + function _wrap(uint256 amount) internal virtual { + IWrappedToken(wrappedNativeToken).deposit{value: amount}(); + } + /// @dev Changes contract owner address. /// @param newOwner Address of a new owner. function changeOwner(address newOwner) external virtual { @@ -233,6 +267,10 @@ contract MechMarketplace is IErrorsMarketplace { // Record factory that created agent mech mapAgentMechFactories[mech] = mechFactory; + // Add mech address into the global set + setMechs.push(mech); + // Adjust the global mech counter + numMechs = setMechs.length; emit CreateMech(mech, serviceId); } @@ -443,7 +481,7 @@ contract MechMarketplace is IErrorsMarketplace { /// @dev Processes payment for request delivery. /// @param requestId Request id. - function processPayment(uint256 requestId) external { + function processPayment(uint256 requestId) external virtual { // Reentrancy guard if (_locked > 1) { revert ReentrancyGuard(); @@ -470,6 +508,8 @@ contract MechMarketplace is IErrorsMarketplace { revert TransferFailed(address(0), address(this), msg.sender, payment); } + _wrap(localCollectedFee); + // Update collected fees collectedFees += localCollectedFee; @@ -479,11 +519,29 @@ contract MechMarketplace is IErrorsMarketplace { _locked = 1; } - // TODO: send to BBB, wrap first or manage on BBB side? + /// @dev Drains collected fees by sending them to a Buy back burner contract. function drain() external { - if (collectedFees == 0) { + // Reentrancy guard + if (_locked > 1) { + revert ReentrancyGuard(); + } + _locked = 2; + + uint256 localCollectedFees = collectedFees; + + // Check for zero value + if (localCollectedFees == 0) { revert ZeroValue(); } + + collectedFees = 0; + + // Transfer to Buy back burner + IToken(wrappedNativeToken).transfer(buyBackBurner, localCollectedFees); + + emit Drained(localCollectedFees); + + _locked = 1; } /// @dev Gets the already computed domain separator of recomputes one if the chain Id is different. diff --git a/contracts/interfaces/IMech.sol b/contracts/interfaces/IMech.sol index e04716b..f3a2d7a 100644 --- a/contracts/interfaces/IMech.sol +++ b/contracts/interfaces/IMech.sol @@ -17,9 +17,4 @@ interface IMech { /// @notice Only marketplace can call this function if the request is not delivered by the chosen priority mech. /// @param requestId Request Id. function revokeRequest(uint256 requestId) external; - - /// @dev Validates agent mech. - /// @param agentMech Agent mech address. - /// @return status True, if the mech is valid. - function checkMechValidity(address agentMech) external view returns (bool status); } \ No newline at end of file From ef4acbd5a43dfcb26a3d58c7faa5df61d98be738 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Mon, 16 Dec 2024 14:19:06 +0000 Subject: [PATCH 5/5] chore: linters --- contracts/Karma.sol | 2 ++ contracts/MechMarketplace.sol | 2 ++ contracts/MechMarketplaceProxy.sol | 9 +++++++++ 3 files changed, 13 insertions(+) diff --git a/contracts/Karma.sol b/contracts/Karma.sol index dae3fed..ae1f2f0 100644 --- a/contracts/Karma.sol +++ b/contracts/Karma.sol @@ -67,6 +67,7 @@ contract Karma { } // Store the karma implementation address + // solhint-disable-next-line avoid-low-level-calls assembly { sstore(KARMA_PROXY, newImplementation) } @@ -150,6 +151,7 @@ contract Karma { /// @dev Gets the implementation address. /// @return implementation Implementation address. function getImplementation() external view returns (address implementation) { + // solhint-disable-next-line avoid-low-level-calls assembly { implementation := sload(KARMA_PROXY) } diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index d9869a8..281df11 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -241,6 +241,7 @@ contract MechMarketplace is IErrorsMarketplace { } // Store the mechMarketplace implementation address + // solhint-disable-next-line avoid-low-level-calls assembly { sstore(MECH_MARKETPLACE_PROXY, newImplementation) } @@ -503,6 +504,7 @@ contract MechMarketplace is IErrorsMarketplace { payment = payment - localCollectedFee; // Transfer payment + // solhint-disable-next-line avoid-low-level-calls (bool success, ) = msg.sender.call{value: payment}(""); if (!success) { revert TransferFailed(address(0), address(this), msg.sender, payment); diff --git a/contracts/MechMarketplaceProxy.sol b/contracts/MechMarketplaceProxy.sol index ba613ac..fd71d76 100644 --- a/contracts/MechMarketplaceProxy.sol +++ b/contracts/MechMarketplaceProxy.sol @@ -65,4 +65,13 @@ contract MechMarketplaceProxy { return(0, returndatasize()) } } + + /// @dev Gets the implementation address. + /// @return implementation Implementation address. + function getImplementation() external view returns (address implementation) { + // solhint-disable-next-line avoid-low-level-calls + assembly { + implementation := sload(MECH_MARKETPLACE_PROXY) + } + } } \ No newline at end of file