From d41d1ca149789778d2792c734817cc5baf3f29da Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Fri, 23 Aug 2024 17:33:42 +0100 Subject: [PATCH 1/9] refactor: checking mech and requester staking validity --- contracts/AgentFactory.sol | 13 - contracts/AgentMech.sol | 225 +++++++++++++----- contracts/MechMarketplace.sol | 222 +++++++++-------- .../nevermined/AgentFactorySubscription.sol | 13 - 4 files changed, 272 insertions(+), 201 deletions(-) diff --git a/contracts/AgentFactory.sol b/contracts/AgentFactory.sol index 638523d..9d337c9 100644 --- a/contracts/AgentFactory.sol +++ b/contracts/AgentFactory.sol @@ -12,14 +12,6 @@ interface IAgentRegistry { function create(address agentOwner, bytes32 agentHash) external returns (uint256 agentId); } -// Mech Marketplace interface -interface IMechMarketplace { - /// @dev Sets mech registration status. - /// @param mech Mech address. - /// @param status True, if registered, false otherwise. - function setMechRegistrationStatus(address mech, bool status) external; -} - /// @title Agent Factory - Periphery smart contract for managing agent and mech creation contract AgentFactory is GenericManager { event CreateMech(address indexed mech, uint256 indexed agentId, uint256 indexed price); @@ -59,11 +51,6 @@ contract AgentFactory is GenericManager { // agentOwner is isOperator() for the mech mech = address((new AgentMech){salt: salt}(agentRegistry, agentId, price, mechMarketplace)); - // Register mech in a marketplace, if specified - if (mechMarketplace != address(0)) { - IMechMarketplace(mechMarketplace).setMechRegistrationStatus(mech, true); - } - emit CreateMech(mech, agentId, price); } } diff --git a/contracts/AgentMech.sol b/contracts/AgentMech.sol index 2a00eeb..c173c0a 100644 --- a/contracts/AgentMech.sol +++ b/contracts/AgentMech.sol @@ -17,15 +17,11 @@ struct MechDelivery { // Mech Marketplace interface interface IMechMarketplace { - /// @dev Sets mech registration status. - /// @param mech Mech address. - /// @param status True, if registered, false otherwise. - function setMechRegistrationStatus(address mech, bool status) external; - /// @dev Delivers a request. /// @param requestId Request id. /// @param requestData Self-descriptive opaque data-blob. - function deliver(uint256 requestId, bytes memory requestData) external; + /// @param deliveryMechServiceId Mech operator service Id. + function deliverMarketplace(uint256 requestId, bytes memory requestData, uint256 deliveryMechServiceId) external; /// @dev Gets mech delivery info. /// @param requestId Request Id. @@ -49,6 +45,10 @@ error ZeroAddress(); /// @param manager Required sender address as a manager. error MarketplaceOnly(address sender, address manager); +/// @dev Mech marketplace exists. +/// @param mechMarketplace Mech marketplace address. +error MarketplaceExists(address mechMarketplace); + /// @dev Agent does not exist. /// @param agentId Agent Id. error AgentNotFound(uint256 agentId); @@ -81,26 +81,39 @@ contract AgentMech is ERC721Mech { // Agent mech version number string public constant VERSION = "1.1.0"; + // Domain separator type hash + bytes32 public constant DOMAIN_SEPARATOR_TYPE_HASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + // Original domain separator value + bytes32 public immutable domainSeparator; + // Original chain Id + uint256 public immutable chainId; // Minimum required price uint256 public price; - // Number of undelivered requests + // Number of undelivered requests by this mech uint256 public numUndeliveredRequests; - // Number of total requests + // Number of total requests by this mech uint256 public numTotalRequests; + // Number of total deliveries by this mech + uint256 public numTotalDeliveries; // Mech marketplace address address public mechMarketplace; // Reentrancy lock uint256 internal _locked = 1; - // Map of requests counts for corresponding addresses - mapping(address => uint256) public mapRequestsCounts; + // Map of request counts for corresponding addresses + mapping(address => uint256) public mapRequestCounts; + // Map of delivery counts for corresponding addresses + mapping(address => uint256) public mapDeliveryCounts; // Map of undelivered requests counts for corresponding addresses mapping(address => uint256) public mapUndeliveredRequestsCounts; // Cyclical map of request Ids mapping(uint256 => uint256[2]) public mapRequestIds; // Map of request Id => sender address mapping(uint256 => address) public mapRequestAddresses; + // Mapping of account nonces + mapping(address => uint256) public mapNonces; /// @dev AgentMech constructor. /// @param _token Address of the token contract. @@ -125,6 +138,25 @@ contract AgentMech is ERC721Mech { mechMarketplace = _mechMarketplace; // Record the price price = _price; + + // Record chain Id + chainId = block.chainid; + // Compute domain separator + domainSeparator = _computeDomainSeparator(); + } + + /// @dev Computes domain separator hash. + /// @return Hash of the domain separator based on its name, version, chain Id and contract address. + function _computeDomainSeparator() internal view returns (bytes32) { + return keccak256( + abi.encode( + DOMAIN_SEPARATOR_TYPE_HASH, + keccak256("AgentMech"), + keccak256(abi.encode(VERSION)), + block.chainid, + address(this) + ) + ); } /// @dev Performs actions before the request is posted. @@ -168,41 +200,20 @@ contract AgentMech is ERC721Mech { delete mapRequestAddresses[requestId]; } - /// @dev Changes mech marketplace address. - /// @param newMechMarketplace New mech marketplace address. - function changeMechMarketplace(address newMechMarketplace) external onlyOperator { - address currentMarketplace = mechMarketplace; - - // Deregister mech from the current marketplace - // Note that in order to prevent malicious self-registration in all possible marketplaces, - // the mech needs to go through the governance whitelisting procedure if it changes the marketplace - if (currentMarketplace != address(0) && currentMarketplace != newMechMarketplace) { - IMechMarketplace(currentMarketplace).setMechRegistrationStatus(address(this), false); - } - - mechMarketplace = newMechMarketplace; - emit MechMarketplaceUpdated(newMechMarketplace); - } - /// @dev Registers a request. - /// @notice This function is called by the marketplace contract since this mech was specified as a priority one. /// @param account Requester account address. /// @param data Self-descriptive opaque data-blob. /// @param requestId Request Id. - function request( + function _request( address account, bytes memory data, uint256 requestId - ) external payable { - if (mechMarketplace != address(0) && msg.sender != mechMarketplace) { - revert MarketplaceOnly(msg.sender, mechMarketplace); - } - + ) internal { // Check the request payment _preRequest(msg.value, requestId, data); // Increase the requests count supplied by the sender - mapRequestsCounts[account]++; + mapRequestCounts[account]++; mapUndeliveredRequestsCounts[account]++; // Record the requestId => sender correspondence mapRequestAddresses[requestId] = account; @@ -229,6 +240,66 @@ contract AgentMech is ERC721Mech { emit Request(account, requestId, data); } + /// @dev Delivers a request. + /// @notice This function ultimately calls mech marketplace contract to finalize the delivery. + /// @param requestId Request id. + /// @param data Self-descriptive opaque data-blob. + function _deliver(uint256 requestId, bytes memory data) internal returns (bytes memory requestData) { + // Get an account to deliver request to + address account = mapRequestAddresses[requestId]; + // The account is zero if the delivery mech is different from a priority mech, or if request does not exist + if (account == address(0)) { + if (mechMarketplace != address(0)) { + account = IMechMarketplace(mechMarketplace).getMechDeliveryInfo(requestId).account; + } + + // Check if request exists in the mech marketplace or locally in the mech + if (account == address(0)) { + revert RequestIdNotFound(requestId); + } + } else { + // The account is non-zero if it is delivered by the priority mech + _cleanRequestInfo(account, requestId); + } + + // Perform a pre-delivery of the data if it needs additional parsing + requestData = _preDeliver(account, requestId, data); + + // Increase the total number of deliveries, as the request is delivered by this mech + mapDeliveryCounts[account]++; + numTotalDeliveries++; + + emit Deliver(msg.sender, requestId, requestData); + } + + /// @dev Registers a request without a marketplace. + /// @param data Self-descriptive opaque data-blob. + /// @return requestId Request Id. + function request(bytes memory data) external payable returns (uint256 requestId) { + if (mechMarketplace != address(0)) { + revert MarketplaceExists(mechMarketplace); + } + + // Get the local request Id + requestId = getRequestId(msg.sender, data, mapNonces[msg.sender]); + mapNonces[msg.sender]++; + + _request(msg.sender, data, requestId); + } + + /// @dev Registers a request. + /// @notice This function is called by the marketplace contract since this mech was specified as a priority one. + /// @param account Requester account address. + /// @param data Self-descriptive opaque data-blob. + /// @param requestId Request Id. + function requestMarketplace(address account, bytes memory data, uint256 requestId) external payable { + if (mechMarketplace != address(0) && msg.sender != mechMarketplace) { + revert MarketplaceOnly(msg.sender, mechMarketplace); + } + + _request(account, data, requestId); + } + /// @dev Revokes the request from the mech that does not deliver it. /// @notice Only marketplace can call this function if the request is not delivered by the chosen priority mech. /// @param requestId Request Id. @@ -244,9 +315,6 @@ contract AgentMech is ERC721Mech { if (account == address(0)) { revert ZeroAddress(); } - // Decrease the total number of requests by this mech - mapRequestsCounts[account]--; - numTotalRequests--; // Clean request info _cleanRequestInfo(account, requestId); @@ -265,37 +333,32 @@ contract AgentMech is ERC721Mech { } _locked = 2; - // Get an account to deliver request to - address account = mapRequestAddresses[requestId]; - // The account is zero if the delivery mech is different from a priority mech, or if request does not exist - if (account == address(0)) { - if (mechMarketplace != address(0)) { - account = IMechMarketplace(mechMarketplace).getMechDeliveryInfo(requestId).account; - } + // Request delivery + _deliver(requestId, data); - // Check if request exists in the mech marketplace - if (account == address(0)) { - revert RequestIdNotFound(requestId); - } + _locked = 1; + } - // Increase the total number of requests, as the request is delivered by this mech - mapRequestsCounts[account]++; - numTotalRequests++; - } else { - // The account is non-zero if it is delivered by the priority mech - _cleanRequestInfo(account, requestId); + /// @dev Delivers a request. + /// @notice This function ultimately calls mech marketplace contract to finalize the delivery. + /// @param requestId Request id. + /// @param data Self-descriptive opaque data-blob. + /// @param mechServiceId Mech operator service Id. + function deliverMarketplace(uint256 requestId, bytes memory data, uint256 mechServiceId) external onlyOperator { + // Reentrancy guard + if (_locked > 1) { + revert ReentrancyGuard(); } + _locked = 2; - // Perform a pre-delivery of the data if it needs additional parsing - bytes memory requestData = _preDeliver(account, requestId, data); + // Request delivery + bytes memory requestData = _deliver(requestId, data); // Mech marketplace delivery finalization if (mechMarketplace != address(0)) { - IMechMarketplace(mechMarketplace).deliver(requestId, requestData); + IMechMarketplace(mechMarketplace).deliverMarketplace(requestId, requestData, mechServiceId); } - emit Deliver(msg.sender, requestId, requestData); - _locked = 1; } @@ -306,11 +369,49 @@ contract AgentMech is ERC721Mech { emit PriceUpdated(newPrice); } + /// @dev Gets the already computed domain separator of recomputes one if the chain Id is different. + /// @return Original or recomputed domain separator. + function getDomainSeparator() public view returns (bytes32) { + return block.chainid == chainId ? domainSeparator : _computeDomainSeparator(); + } + + /// @dev Gets the request Id. + /// @param account Account address. + /// @param data Self-descriptive opaque data-blob. + /// @param nonce Nonce. + /// @return requestId Corresponding request Id. + function getRequestId( + address account, + bytes memory data, + uint256 nonce + ) public view returns (uint256 requestId) { + requestId = uint256(keccak256( + abi.encodePacked( + "\x19\x01", + getDomainSeparator(), + keccak256( + abi.encode( + account, + data, + nonce + ) + ) + ) + )); + } + /// @dev Gets the requests count for a specific account. /// @param account Account address. - /// @return requestsCount Requests count. - function getRequestsCount(address account) external view returns (uint256 requestsCount) { - requestsCount = mapRequestsCounts[account]; + /// @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 Deliveried count. + function getDeliveriedCount(address account) external view returns (uint256) { + return mapDeliveryCounts[account]; } /// @dev Gets the set of undelivered request Ids with Nonce. diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index d6e4e87..90c0fb3 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -3,11 +3,14 @@ pragma solidity ^0.8.25; // Agent Mech interface interface IMech { + /// @dev Checks if the signer is the mech operator. + function isOperator(address signer) external view returns (bool); + /// @dev Registers a request. /// @param account Requester account address. /// @param data Self-descriptive opaque data-blob. /// @param requestId Request Id. - function request(address account, bytes memory data, uint256 requestId) external payable; + function requestMarketplace(address account, bytes memory data, uint256 requestId) external payable; /// @dev Revokes the request from the mech that does not deliver it. /// @notice Only marketplace can call this function if the request is not delivered by the chosen priority mech. @@ -29,6 +32,40 @@ interface IKarma { function changeRequesterMechKarma(address requester, address mech, int256 karmaChange) external; } +// Staking interface +interface IStaking { + enum StakingState { + Unstaked, + Staked, + Evicted + } + + // Service Info struct + struct ServiceInfo { + // Service multisig address + address multisig; + // Service owner + address owner; + // Service multisig nonces + uint256[] nonces; + // Staking start time + uint256 tsStart; + // Accumulated service staking reward + uint256 reward; + // Accumulated inactivity that might lead to the service eviction + uint256 inactivity; + } + + /// @dev Gets the service staking state. + /// @param requesterServiceId. + /// @return stakingState Staking state of the service. + function getStakingState(uint256 requesterServiceId) external view returns (StakingState stakingState); + /// @dev Gets staked service info. + /// @param requesterServiceId Service Id. + /// @return sInfo Struct object with the corresponding service info. + function getServiceInfo(uint256 requesterServiceId) external view returns (ServiceInfo memory); +} + /// @dev Only `owner` has a privilege, but the `sender` was provided. /// @param sender Sender address. /// @param owner Required sender address as an owner. @@ -122,44 +159,51 @@ contract MechMarketplace { bytes32 public immutable domainSeparator; // Original chain Id uint256 public immutable chainId; + // Minimum response time + uint256 public immutable minResponseTimeout; + // Maximum response time + uint256 public immutable maxResponseTimeout; + // Approved mech bytecode hash + bytes32 public immutable mechBytecodeHash; // Mech karma contract address address public immutable karmaProxy; + // Agent mech factory contract address + address public immutable mechStakingInstance; // Number of undelivered requests uint256 public numUndeliveredRequests; // Number of total requests uint256 public numTotalRequests; - // Minimum response time - uint256 public minResponseTimeout; - // Maximum response time - uint256 public maxResponseTimeout; + // Number of total deliveries + uint256 public numTotalDeliveries; // Reentrancy lock uint256 internal _locked = 1; - // Contract owner - address public owner; - // Agent mech factory contract address - address public factory; // Mapping of request Id => mech delivery information mapping(uint256 => MechDelivery) public mapRequestIdDeliveries; // Mapping of account nonces mapping(address => uint256) public mapNonces; - // Mapping of registered mechs - mapping(address => bool) public mapMechRegistrations; /// @dev MechMarketplace constructor. - /// @param _factory Agent mech factory address. + /// @param _mechStakingInstance Agent mech staking instance address. /// @param _karmaProxy Karma proxy contract address. /// @param _minResponseTimeout Min response time in sec. /// @param _maxResponseTimeout Max response time in sec. - constructor(address _factory, address _karmaProxy, uint256 _minResponseTimeout, uint256 _maxResponseTimeout) { + /// @param _agentMechBytecodeHash Approved agent mech bytecode hash. + constructor( + address _mechStakingInstance, + address _karmaProxy, + uint256 _minResponseTimeout, + uint256 _maxResponseTimeout, + bytes32 _agentMechBytecodeHash + ) { // Check for zero address - if (_factory == address(0) || _karmaProxy == address(0)) { + if (_mechStakingInstance == address(0) || _karmaProxy == address(0)) { revert ZeroAddress(); } // Check for zero values - if (_minResponseTimeout == 0 || _maxResponseTimeout == 0) { + if (_minResponseTimeout == 0 || _maxResponseTimeout == 0 || mechBytecodeHash == 0) { revert ZeroValue(); } @@ -173,11 +217,11 @@ contract MechMarketplace { revert Overflow(_maxResponseTimeout, type(uint32).max); } - owner = msg.sender; - factory = _factory; + mechStakingInstance = _mechStakingInstance; karmaProxy = _karmaProxy; minResponseTimeout = _minResponseTimeout; maxResponseTimeout = _maxResponseTimeout; + mechBytecodeHash = _agentMechBytecodeHash; // Record chain Id chainId = block.chainid; @@ -199,103 +243,46 @@ contract MechMarketplace { ); } - /// @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 mech factory address. - /// @param newFactory New mech factory address. - function changeFactory(address newFactory) external { - // Check for the contract ownership - if (msg.sender != owner) { - revert OwnerOnly(msg.sender, owner); - } - - // Check for zero address - if (newFactory == address(0)) { - revert ZeroAddress(); - } - - factory = newFactory; - emit FactoryUpdated(newFactory); - } - - /// @dev Changes min and max response timeout values. - /// @param newMinResponseTimeout New min response timeout. - /// @param newMaxResponseTimeout New max response timeout. - function changeMinMaxResponseTimeout(uint256 newMinResponseTimeout, uint256 newMaxResponseTimeout) external { - // Check contract ownership - if (msg.sender != owner) { - revert OwnerOnly(msg.sender, owner); - } - - // Check for zero values - if (newMinResponseTimeout == 0 || newMaxResponseTimeout == 0) { - revert ZeroValue(); - } - - // Check for sanity values - if (newMinResponseTimeout > newMaxResponseTimeout) { - revert Overflow(newMinResponseTimeout, newMaxResponseTimeout); - } - - // responseTimeout limits - if (newMaxResponseTimeout > type(uint32).max) { - revert Overflow(newMaxResponseTimeout, type(uint32).max); - } - - minResponseTimeout = newMinResponseTimeout; - maxResponseTimeout = newMaxResponseTimeout; - - emit MinMaxResponseTimeoutUpdated(newMinResponseTimeout, newMaxResponseTimeout); - } - - /// @dev Sets mech registration status. - /// @param mech Mech address. - /// @param status True, if registered, false otherwise. - function setMechRegistrationStatus(address mech, bool status) external { - // Check for the agent mech factory access, contract ownership or mech itself (when changing its marketplace) - if (msg.sender != factory && msg.sender != owner && msg.sender != mech) { - revert OwnerOnly(msg.sender, owner); + /// @dev Checks agent mech for contract validity. + /// @param mech Agent mech address. + /// @param mechServiceId Mech operator service Id. + function checkMech(address mech, uint256 mechServiceId) public view { + // Check that the mech address corresponds to the authorized bytecode hash + bytes32 mechHash = keccak256(mech.code); + if (mechHash != mechBytecodeHash) { + revert UnauthorizedAccount(mech); } - // Prevent mech calling this function by exec() and trying to whitelist itself - if (msg.sender == mech && status) { - revert UnauthorizedAccount(msg.sender); + // Check if the mech service is staked + IStaking.StakingState state = IStaking(mechStakingInstance).getStakingState(mechServiceId); + if (state != IStaking.StakingState.Staked) { + revert(); } - // Check that mech is a contract - if (mech.code.length == 0) { - revert NotContract(mech); + // Get the staked service info + IStaking.ServiceInfo memory serviceInfo = IStaking(mechStakingInstance).getServiceInfo(mechServiceId); + // Check that staked service multisig is the priority mech operator + if (!IMech(mech).isOperator(serviceInfo.multisig)) { + revert UnauthorizedAccount(mech); } - - mapMechRegistrations[mech] = status; - emit MechRegistrationStatusChanged(mech, status); } /// @dev Registers a request. /// @notice The request is going to be registered by a specified priority agent mech. /// @param data Self-descriptive opaque data-blob. /// @param priorityMech Address of a priority mech. + /// @param priorityMechServiceId Priority mech operator service Id. /// @param responseTimeout Relative response time in sec. + /// @param requesterStakingInstance Staking instance of a service whose multisig posts a request. + /// @param requesterServiceId Corresponding service Id in the staking contract. /// @return requestId Request Id. function request( bytes memory data, address priorityMech, - uint256 responseTimeout + uint256 priorityMechServiceId, + uint256 responseTimeout, + address requesterStakingInstance, + uint256 requesterServiceId ) external payable returns (uint256 requestId) { // Reentrancy guard if (_locked > 1) { @@ -307,14 +294,6 @@ contract MechMarketplace { if (priorityMech == address(0)) { revert ZeroAddress(); } - // Agent mech itself cannot post a request - if (mapMechRegistrations[msg.sender]) { - revert UnauthorizedAccount(msg.sender); - } - // Check that priority mech is registered - if (!mapMechRegistrations[priorityMech]) { - revert UnauthorizedAccount(priorityMech); - } // responseTimeout bounds if (responseTimeout < minResponseTimeout || responseTimeout > maxResponseTimeout) { revert OutOfBounds(responseTimeout, minResponseTimeout, maxResponseTimeout); @@ -328,6 +307,22 @@ contract MechMarketplace { revert ZeroValue(); } + // Check agent mech + checkMech(priorityMech, priorityMechServiceId); + + // Check if the requester service is staked + IStaking.StakingState state = IStaking(requesterStakingInstance).getStakingState(requesterServiceId); + if (state != IStaking.StakingState.Staked) { + revert(); + } + + // Get the staked service info + IStaking.ServiceInfo memory serviceInfo = IStaking(requesterStakingInstance).getServiceInfo(requesterServiceId); + // Check staked service multisig + if (serviceInfo.multisig != msg.sender) { + revert OwnerOnly(msg.sender, serviceInfo.multisig); + } + // Get the request Id requestId = getRequestId(msg.sender, data, mapNonces[msg.sender]); @@ -353,7 +348,7 @@ contract MechMarketplace { numTotalRequests++; // Process request by a specified priority mech - IMech(priorityMech).request{value: msg.value}(msg.sender, data, requestId); + IMech(priorityMech).requestMarketplace{value: msg.value}(msg.sender, data, requestId); emit MarketplaceRequest(msg.sender, priorityMech, requestId, data); @@ -364,16 +359,16 @@ contract MechMarketplace { /// @notice This function can only be called by the agent mech delivering the request. /// @param requestId Request id. /// @param requestData Self-descriptive opaque data-blob. - function deliver(uint256 requestId, bytes memory requestData) external { + /// @param deliveryMechServiceId Mech operator service Id. + function deliverMarketplace(uint256 requestId, bytes memory requestData, uint256 deliveryMechServiceId) external { // Reentrancy guard if (_locked > 1) { revert ReentrancyGuard(); } _locked = 2; - if (!mapMechRegistrations[msg.sender]) { - revert UnauthorizedAccount(msg.sender); - } + // Check delivery agent mech + checkMech(msg.sender, deliveryMechServiceId); // Get mech delivery info struct MechDelivery storage mechDelivery = mapRequestIdDeliveries[requestId]; @@ -409,6 +404,8 @@ contract MechMarketplace { // Decrease the number of undelivered requests numUndeliveredRequests--; + // Increase the number of deliveries + numTotalDeliveries++; // Increase mech karma that delivers the request IKarma(karmaProxy).changeMechKarma(msg.sender, 1); @@ -433,8 +430,7 @@ contract MechMarketplace { address account, bytes memory data, uint256 nonce - ) public view returns (uint256 requestId) - { + ) public view returns (uint256 requestId) { requestId = uint256(keccak256( abi.encodePacked( "\x19\x01", diff --git a/contracts/integrations/nevermined/AgentFactorySubscription.sol b/contracts/integrations/nevermined/AgentFactorySubscription.sol index a895e89..ad5a8bb 100644 --- a/contracts/integrations/nevermined/AgentFactorySubscription.sol +++ b/contracts/integrations/nevermined/AgentFactorySubscription.sol @@ -12,14 +12,6 @@ interface IAgentRegistry { function create(address agentOwner, bytes32 agentHash) external returns (uint256 agentId); } -// Mech Marketplace interface -interface IMechMarketplace { - /// @dev Sets mech registration status. - /// @param mech Mech address. - /// @param status True, if registered, false otherwise. - function setMechRegistrationStatus(address mech, bool status) external; -} - /// @title Agent Factory Subscription - Periphery smart contract for managing agent and mech creation with subscription contract AgentFactorySubscription is GenericManager { event CreateMech( @@ -70,11 +62,6 @@ contract AgentFactorySubscription is GenericManager { mech = address((new AgentMechSubscription){salt: salt}(agentRegistry, agentId, minCreditsPerRequest, subscriptionNFT, subscriptionTokenId, mechMarketplace)); - // Register mech in a marketplace, if specified - if (mechMarketplace != address(0)) { - IMechMarketplace(mechMarketplace).setMechRegistrationStatus(mech, true); - } - emit CreateMech(mech, agentId, minCreditsPerRequest, subscriptionNFT, subscriptionTokenId); } } From b33a15aa04ee6d12cd53c92c0a5febfafbd89b2c Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Fri, 23 Aug 2024 18:07:34 +0100 Subject: [PATCH 2/9] doc: comments --- contracts/AgentMech.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/AgentMech.sol b/contracts/AgentMech.sol index c173c0a..4d2cdcf 100644 --- a/contracts/AgentMech.sol +++ b/contracts/AgentMech.sol @@ -284,6 +284,7 @@ contract AgentMech is ERC721Mech { requestId = getRequestId(msg.sender, data, mapNonces[msg.sender]); mapNonces[msg.sender]++; + // Perform a request _request(msg.sender, data, requestId); } @@ -293,10 +294,12 @@ contract AgentMech is ERC721Mech { /// @param data Self-descriptive opaque data-blob. /// @param requestId Request Id. function requestMarketplace(address account, bytes memory data, uint256 requestId) external payable { - if (mechMarketplace != address(0) && msg.sender != mechMarketplace) { + // Check for marketplace access + if (msg.sender != mechMarketplace) { revert MarketplaceOnly(msg.sender, mechMarketplace); } + // Perform a request _request(account, data, requestId); } @@ -323,7 +326,6 @@ contract AgentMech is ERC721Mech { } /// @dev Delivers a request. - /// @notice This function ultimately calls mech marketplace contract to finalize the delivery. /// @param requestId Request id. /// @param data Self-descriptive opaque data-blob. function deliver(uint256 requestId, bytes memory data) external onlyOperator { From 98595e0340b8896ce274fb3b0cb83b9684c41442 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Fri, 23 Aug 2024 20:36:32 +0100 Subject: [PATCH 3/9] refactor: check request status for local mechs --- contracts/AgentMech.sol | 26 +++++++++++++++++++++++++- contracts/MechMarketplace.sol | 12 ++++++++---- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/contracts/AgentMech.sol b/contracts/AgentMech.sol index 4d2cdcf..d32b5d0 100644 --- a/contracts/AgentMech.sol +++ b/contracts/AgentMech.sol @@ -79,6 +79,12 @@ contract AgentMech is ERC721Mech { event RevokeRequest(address indexed sender, uint256 requestId); event PriceUpdated(uint256 price); + enum RequestStatus { + DoesNotExist, + Requested, + Delivered + } + // Agent mech version number string public constant VERSION = "1.1.0"; // Domain separator type hash @@ -197,7 +203,6 @@ contract AgentMech is ERC721Mech { // Delete the delivered element from the map delete mapRequestIds[requestId]; - delete mapRequestAddresses[requestId]; } /// @dev Registers a request. @@ -416,6 +421,25 @@ contract AgentMech is ERC721Mech { return mapDeliveryCounts[account]; } + /// @dev Gets the request Id status registered in this agent mech. + /// @notice If marketplace is not zero, use the same function in the mech marketplace contract. + /// @param requestId Request Id. + /// @return status Request status. + function getRequestStatus(uint256 requestId) external view returns (RequestStatus status) { + // Request exists if it was recorded in the requestId => account map + if (mapRequestAddresses[requestId] != address(0)) { + // Get the request info + uint256[2] memory requestIds = mapRequestIds[requestId]; + // Check if the request Id was already delivered: previous and next request Ids are zero, + // and the zero's element previous request Id is not equal to the provided request Id + if (requestIds[0] == 0 && requestIds[1] == 0 && mapRequestIds[0][0] != requestId) { + status = RequestStatus.Delivered; + } else { + status = RequestStatus.Requested; + } + } + } + /// @dev Gets the set of undelivered request Ids with Nonce. /// @param size Maximum batch size of a returned requests Id set. If the size is zero, the whole set is returned. /// @param offset The number of skipped requests that are not going to be part of the returned requests Id set. diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index 90c0fb3..0d830b2 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -106,6 +106,11 @@ error ReentrancyGuard(); /// @param account Account address. error UnauthorizedAccount(address account); +/// @dev Specified service Id is not staked. +/// @param stakingInstance Staking contract instance. +/// @param serviceId Service Id. +error ServiceNotStaked(address stakingInstance, uint256 serviceId); + /// @dev Provided value is out of bounds. /// @param provided value. /// @param min Minimum possible value. @@ -256,7 +261,7 @@ contract MechMarketplace { // Check if the mech service is staked IStaking.StakingState state = IStaking(mechStakingInstance).getStakingState(mechServiceId); if (state != IStaking.StakingState.Staked) { - revert(); + revert ServiceNotStaked(mechStakingInstance, mechServiceId); } // Get the staked service info @@ -313,7 +318,7 @@ contract MechMarketplace { // Check if the requester service is staked IStaking.StakingState state = IStaking(requesterStakingInstance).getStakingState(requesterServiceId); if (state != IStaking.StakingState.Staked) { - revert(); + revert ServiceNotStaked(requesterStakingInstance, requesterServiceId); } // Get the staked service info @@ -379,7 +384,6 @@ contract MechMarketplace { revert ZeroAddress(); } - address account = mechDelivery.account; // Check that the request is not already delivered if (mechDelivery.deliveryMech != address(0)) { revert AlreadyDelivered(requestId); @@ -410,7 +414,7 @@ contract MechMarketplace { // Increase mech karma that delivers the request IKarma(karmaProxy).changeMechKarma(msg.sender, 1); - emit MarketplaceDeliver(priorityMech, msg.sender, account, requestId, requestData); + emit MarketplaceDeliver(priorityMech, msg.sender, mechDelivery.account, requestId, requestData); _locked = 1; } From 4b32df736562c7bd08d500e6f99d1bd74235cdc2 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Fri, 23 Aug 2024 20:42:08 +0100 Subject: [PATCH 4/9] refactor and chore: several checks --- contracts/AgentMech.sol | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/contracts/AgentMech.sol b/contracts/AgentMech.sol index d32b5d0..990985c 100644 --- a/contracts/AgentMech.sol +++ b/contracts/AgentMech.sol @@ -330,7 +330,7 @@ contract AgentMech is ERC721Mech { emit RevokeRequest(account, requestId); } - /// @dev Delivers a request. + /// @dev Delivers a request without a marketplace. /// @param requestId Request id. /// @param data Self-descriptive opaque data-blob. function deliver(uint256 requestId, bytes memory data) external onlyOperator { @@ -340,13 +340,18 @@ contract AgentMech is ERC721Mech { } _locked = 2; + // Check for the marketplace existence + if (mechMarketplace != address(0)) { + revert MarketplaceExists(mechMarketplace); + } + // Request delivery _deliver(requestId, data); _locked = 1; } - /// @dev Delivers a request. + /// @dev Delivers a request by a marketplace. /// @notice This function ultimately calls mech marketplace contract to finalize the delivery. /// @param requestId Request id. /// @param data Self-descriptive opaque data-blob. @@ -358,13 +363,16 @@ contract AgentMech is ERC721Mech { } _locked = 2; + // Check for zero address + if (mechMarketplace == address(0)) { + revert ZeroAddress(); + } + // Request delivery bytes memory requestData = _deliver(requestId, data); // Mech marketplace delivery finalization - if (mechMarketplace != address(0)) { - IMechMarketplace(mechMarketplace).deliverMarketplace(requestId, requestData, mechServiceId); - } + IMechMarketplace(mechMarketplace).deliverMarketplace(requestId, requestData, mechServiceId); _locked = 1; } From 7ac463b59bc286ef419b6938c36d008bf254be34 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Fri, 23 Aug 2024 20:51:20 +0100 Subject: [PATCH 5/9] chore: typo --- contracts/AgentMech.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/AgentMech.sol b/contracts/AgentMech.sol index 990985c..bc07b23 100644 --- a/contracts/AgentMech.sol +++ b/contracts/AgentMech.sol @@ -425,7 +425,7 @@ contract AgentMech is ERC721Mech { /// @dev Gets the deliveries count for a specific account. /// @param account Account address. /// @return Deliveried count. - function getDeliveriedCount(address account) external view returns (uint256) { + function getDeliveriesCount(address account) external view returns (uint256) { return mapDeliveryCounts[account]; } From 839e9dc666cf0acf026753aadb8c76bb3d492470 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Fri, 23 Aug 2024 23:06:59 +0100 Subject: [PATCH 6/9] refactor: deleting unnecessary code --- contracts/MechMarketplace.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index 0d830b2..481823b 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -179,8 +179,6 @@ contract MechMarketplace { uint256 public numUndeliveredRequests; // Number of total requests uint256 public numTotalRequests; - // Number of total deliveries - uint256 public numTotalDeliveries; // Reentrancy lock uint256 internal _locked = 1; @@ -408,8 +406,6 @@ contract MechMarketplace { // Decrease the number of undelivered requests numUndeliveredRequests--; - // Increase the number of deliveries - numTotalDeliveries++; // Increase mech karma that delivers the request IKarma(karmaProxy).changeMechKarma(msg.sender, 1); From 50aa9d31c28690abd747a57de8542421a657f1e6 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Mon, 26 Aug 2024 12:59:29 +0100 Subject: [PATCH 7/9] refactor: staking factory to check mech and requester staking instances --- contracts/AgentMech.sol | 86 ++++++++++--------- contracts/MechMarketplace.sol | 151 +++++++++++++++++++++------------- 2 files changed, 143 insertions(+), 94 deletions(-) diff --git a/contracts/AgentMech.sol b/contracts/AgentMech.sol index bc07b23..186009f 100644 --- a/contracts/AgentMech.sol +++ b/contracts/AgentMech.sol @@ -20,8 +20,14 @@ interface IMechMarketplace { /// @dev Delivers a request. /// @param requestId Request id. /// @param requestData Self-descriptive opaque data-blob. + /// @param deliveryMechStakingInstance Delivery mech staking instance address. /// @param deliveryMechServiceId Mech operator service Id. - function deliverMarketplace(uint256 requestId, bytes memory requestData, uint256 deliveryMechServiceId) external; + function deliverMarketplace( + uint256 requestId, + bytes memory requestData, + address deliveryMechStakingInstance, + uint256 deliveryMechServiceId + ) external; /// @dev Gets mech delivery info. /// @param requestId Request Id. @@ -108,11 +114,11 @@ contract AgentMech is ERC721Mech { // Reentrancy lock uint256 internal _locked = 1; - // Map of request counts for corresponding addresses + // Map of request counts for corresponding addresses in this agent mech mapping(address => uint256) public mapRequestCounts; - // Map of delivery counts for corresponding addresses + // Map of delivery counts for corresponding addresses in this agent mech mapping(address => uint256) public mapDeliveryCounts; - // Map of undelivered requests counts for corresponding addresses + // Map of undelivered requests counts for corresponding addresses in this agent mech mapping(address => uint256) public mapUndeliveredRequestsCounts; // Cyclical map of request Ids mapping(uint256 => uint256[2]) public mapRequestIds; @@ -174,37 +180,6 @@ contract AgentMech is ERC721Mech { } } - /// @dev Performs actions before the delivery of a request. - /// @param data Self-descriptive opaque data-blob. - /// @return requestData Data for the request processing. - function _preDeliver(address, uint256, bytes memory data) internal virtual returns (bytes memory requestData) { - requestData = data; - } - - /// @dev Cleans the request info from all the relevant storage. - /// @param account Requester account address. - /// @param requestId Request Id. - function _cleanRequestInfo(address account, uint256 requestId) internal { - // Decrease the number of undelivered requests - mapUndeliveredRequestsCounts[account]--; - numUndeliveredRequests--; - - // Remove delivered request Id from the request Ids map - uint256[2] memory requestIds = mapRequestIds[requestId]; - // Check if the request Id is invalid (non existent or delivered): previous and next request Ids are zero, - // and the zero's element previous request Id is not equal to the provided request Id - if (requestIds[0] == 0 && requestIds[1] == 0 && mapRequestIds[0][0] != requestId) { - revert RequestIdNotFound(requestId); - } - - // Re-link previous and next elements between themselves - mapRequestIds[requestIds[0]][1] = requestIds[1]; - mapRequestIds[requestIds[1]][0] = requestIds[0]; - - // Delete the delivered element from the map - delete mapRequestIds[requestId]; - } - /// @dev Registers a request. /// @param account Requester account address. /// @param data Self-descriptive opaque data-blob. @@ -245,6 +220,37 @@ contract AgentMech is ERC721Mech { emit Request(account, requestId, data); } + /// @dev Performs actions before the delivery of a request. + /// @param data Self-descriptive opaque data-blob. + /// @return requestData Data for the request processing. + function _preDeliver(address, uint256, bytes memory data) internal virtual returns (bytes memory requestData) { + requestData = data; + } + + /// @dev Cleans the request info from all the relevant storage. + /// @param account Requester account address. + /// @param requestId Request Id. + function _cleanRequestInfo(address account, uint256 requestId) internal { + // Decrease the number of undelivered requests + mapUndeliveredRequestsCounts[account]--; + numUndeliveredRequests--; + + // Remove delivered request Id from the request Ids map + uint256[2] memory requestIds = mapRequestIds[requestId]; + // Check if the request Id is invalid (non existent or delivered): previous and next request Ids are zero, + // and the zero's element previous request Id is not equal to the provided request Id + if (requestIds[0] == 0 && requestIds[1] == 0 && mapRequestIds[0][0] != requestId) { + revert RequestIdNotFound(requestId); + } + + // Re-link previous and next elements between themselves + mapRequestIds[requestIds[0]][1] = requestIds[1]; + mapRequestIds[requestIds[1]][0] = requestIds[0]; + + // Delete the delivered element from the map + delete mapRequestIds[requestId]; + } + /// @dev Delivers a request. /// @notice This function ultimately calls mech marketplace contract to finalize the delivery. /// @param requestId Request id. @@ -355,8 +361,14 @@ contract AgentMech is ERC721Mech { /// @notice This function ultimately calls mech marketplace contract to finalize the delivery. /// @param requestId Request id. /// @param data Self-descriptive opaque data-blob. + /// @param mechStakingInstance Mech staking instance address. /// @param mechServiceId Mech operator service Id. - function deliverMarketplace(uint256 requestId, bytes memory data, uint256 mechServiceId) external onlyOperator { + function deliverMarketplace( + uint256 requestId, + bytes memory data, + address mechStakingInstance, + uint256 mechServiceId + ) external onlyOperator { // Reentrancy guard if (_locked > 1) { revert ReentrancyGuard(); @@ -372,7 +384,7 @@ contract AgentMech is ERC721Mech { bytes memory requestData = _deliver(requestId, data); // Mech marketplace delivery finalization - IMechMarketplace(mechMarketplace).deliverMarketplace(requestId, requestData, mechServiceId); + IMechMarketplace(mechMarketplace).deliverMarketplace(requestId, requestData, mechStakingInstance, mechServiceId); _locked = 1; } diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index 481823b..5b69495 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -66,6 +66,14 @@ interface IStaking { function getServiceInfo(uint256 requesterServiceId) external view returns (ServiceInfo memory); } +// Staking factory interface +interface IStakingFactory { + /// @dev Verifies a service staking contract instance. + /// @param instance Service staking proxy instance. + /// @return True, if verification is successful. + function verifyInstance(address instance) external view returns (bool); +} + /// @dev Only `owner` has a privilege, but the `sender` was provided. /// @param sender Sender address. /// @param owner Required sender address as an owner. @@ -132,8 +140,8 @@ struct MechDelivery { address priorityMech; // Delivery mech address address deliveryMech; - // Account address sending the request - address account; + // Requester address + address requester; // Response timeout window uint32 responseTimeout; } @@ -168,12 +176,10 @@ contract MechMarketplace { uint256 public immutable minResponseTimeout; // Maximum response time uint256 public immutable maxResponseTimeout; - // Approved mech bytecode hash - bytes32 public immutable mechBytecodeHash; // Mech karma contract address address public immutable karmaProxy; - // Agent mech factory contract address - address public immutable mechStakingInstance; + // Staking factory contract address + address public immutable stakingFactory; // Number of undelivered requests uint256 public numUndeliveredRequests; @@ -182,31 +188,31 @@ contract MechMarketplace { // Reentrancy lock uint256 internal _locked = 1; + // Map of request counts for corresponding addresses + mapping(address => uint256) public mapRequestCounts; // Mapping of request Id => mech delivery information mapping(uint256 => MechDelivery) public mapRequestIdDeliveries; // Mapping of account nonces mapping(address => uint256) public mapNonces; /// @dev MechMarketplace constructor. - /// @param _mechStakingInstance Agent mech staking instance address. + /// @param _stakingFactory Staking factory contract address. /// @param _karmaProxy Karma proxy contract address. /// @param _minResponseTimeout Min response time in sec. /// @param _maxResponseTimeout Max response time in sec. - /// @param _agentMechBytecodeHash Approved agent mech bytecode hash. constructor( - address _mechStakingInstance, + address _stakingFactory, address _karmaProxy, uint256 _minResponseTimeout, - uint256 _maxResponseTimeout, - bytes32 _agentMechBytecodeHash + uint256 _maxResponseTimeout ) { // Check for zero address - if (_mechStakingInstance == address(0) || _karmaProxy == address(0)) { + if (_stakingFactory == address(0) || _karmaProxy == address(0)) { revert ZeroAddress(); } // Check for zero values - if (_minResponseTimeout == 0 || _maxResponseTimeout == 0 || mechBytecodeHash == 0) { + if (_minResponseTimeout == 0 || _maxResponseTimeout == 0) { revert ZeroValue(); } @@ -220,11 +226,10 @@ contract MechMarketplace { revert Overflow(_maxResponseTimeout, type(uint32).max); } - mechStakingInstance = _mechStakingInstance; + stakingFactory = _stakingFactory; karmaProxy = _karmaProxy; minResponseTimeout = _minResponseTimeout; maxResponseTimeout = _maxResponseTimeout; - mechBytecodeHash = _agentMechBytecodeHash; // Record chain Id chainId = block.chainid; @@ -246,34 +251,11 @@ contract MechMarketplace { ); } - /// @dev Checks agent mech for contract validity. - /// @param mech Agent mech address. - /// @param mechServiceId Mech operator service Id. - function checkMech(address mech, uint256 mechServiceId) public view { - // Check that the mech address corresponds to the authorized bytecode hash - bytes32 mechHash = keccak256(mech.code); - if (mechHash != mechBytecodeHash) { - revert UnauthorizedAccount(mech); - } - - // Check if the mech service is staked - IStaking.StakingState state = IStaking(mechStakingInstance).getStakingState(mechServiceId); - if (state != IStaking.StakingState.Staked) { - revert ServiceNotStaked(mechStakingInstance, mechServiceId); - } - - // Get the staked service info - IStaking.ServiceInfo memory serviceInfo = IStaking(mechStakingInstance).getServiceInfo(mechServiceId); - // Check that staked service multisig is the priority mech operator - if (!IMech(mech).isOperator(serviceInfo.multisig)) { - revert UnauthorizedAccount(mech); - } - } - /// @dev Registers a request. /// @notice The request is going to be registered by a specified priority agent mech. /// @param data Self-descriptive opaque data-blob. /// @param priorityMech Address of a priority mech. + /// @param priorityMechStakingInstance Address of a priority mech staking instance. /// @param priorityMechServiceId Priority mech operator service Id. /// @param responseTimeout Relative response time in sec. /// @param requesterStakingInstance Staking instance of a service whose multisig posts a request. @@ -282,6 +264,7 @@ contract MechMarketplace { function request( bytes memory data, address priorityMech, + address priorityMechStakingInstance, uint256 priorityMechServiceId, uint256 responseTimeout, address requesterStakingInstance, @@ -311,20 +294,10 @@ contract MechMarketplace { } // Check agent mech - checkMech(priorityMech, priorityMechServiceId); - - // Check if the requester service is staked - IStaking.StakingState state = IStaking(requesterStakingInstance).getStakingState(requesterServiceId); - if (state != IStaking.StakingState.Staked) { - revert ServiceNotStaked(requesterStakingInstance, requesterServiceId); - } + checkMech(priorityMech, priorityMechStakingInstance, priorityMechServiceId); - // Get the staked service info - IStaking.ServiceInfo memory serviceInfo = IStaking(requesterStakingInstance).getServiceInfo(requesterServiceId); - // Check staked service multisig - if (serviceInfo.multisig != msg.sender) { - revert OwnerOnly(msg.sender, serviceInfo.multisig); - } + // Check requester + checkRequester(msg.sender, requesterStakingInstance, requesterServiceId); // Get the request Id requestId = getRequestId(msg.sender, data, mapNonces[msg.sender]); @@ -340,11 +313,13 @@ contract MechMarketplace { // responseTimeout from relative time to absolute time mechDelivery.responseTimeout = uint32(responseTimeout + block.timestamp); // Record request account - mechDelivery.account = msg.sender; + mechDelivery.requester = msg.sender; // Increase mech requester karma IKarma(karmaProxy).changeRequesterMechKarma(msg.sender, priorityMech, 1); + // Record the request count + mapRequestCounts[msg.sender]++; // Increase the number of undelivered requests numUndeliveredRequests++; // Increase the total number of requests @@ -362,16 +337,30 @@ contract MechMarketplace { /// @notice This function can only be called by the agent mech delivering the request. /// @param requestId Request id. /// @param requestData Self-descriptive opaque data-blob. + /// @param deliveryMechStakingInstance Delivery mech staking instance address. /// @param deliveryMechServiceId Mech operator service Id. - function deliverMarketplace(uint256 requestId, bytes memory requestData, uint256 deliveryMechServiceId) external { + function deliverMarketplace( + uint256 requestId, + bytes memory requestData, + address deliveryMechStakingInstance, + uint256 deliveryMechServiceId + ) external { // Reentrancy guard if (_locked > 1) { revert ReentrancyGuard(); } _locked = 2; - // Check delivery agent mech - checkMech(msg.sender, deliveryMechServiceId); + // Check agent mech + checkMech(msg.sender, deliveryMechStakingInstance, deliveryMechServiceId); + + // Get the staked service info for the mech + IStaking.ServiceInfo memory serviceInfo = + IStaking(deliveryMechStakingInstance).getServiceInfo(deliveryMechServiceId); + // Check that staked service multisig is the priority mech operator + if (!IMech(msg.sender).isOperator(serviceInfo.multisig)) { + revert UnauthorizedAccount(msg.sender); + } // Get mech delivery info struct MechDelivery storage mechDelivery = mapRequestIdDeliveries[requestId]; @@ -410,7 +399,7 @@ contract MechMarketplace { // Increase mech karma that delivers the request IKarma(karmaProxy).changeMechKarma(msg.sender, 1); - emit MarketplaceDeliver(priorityMech, msg.sender, mechDelivery.account, requestId, requestData); + emit MarketplaceDeliver(priorityMech, msg.sender, mechDelivery.requester, requestId, requestData); _locked = 1; } @@ -446,6 +435,54 @@ contract MechMarketplace { )); } + /// @dev Checks for staking instance contract validity. + /// @param stakingInstance Staking instance address. + /// @param serviceId Service Id. + function checkStakingInstance(address stakingInstance, uint256 serviceId) public view { + // Check that the mech staking instance is valid + if (!IStakingFactory(stakingFactory).verifyInstance(stakingInstance)) { + revert UnauthorizedAccount(stakingInstance); + } + + // Check if the mech service is staked + IStaking.StakingState state = IStaking(stakingInstance).getStakingState(serviceId); + if (state != IStaking.StakingState.Staked) { + revert ServiceNotStaked(stakingInstance, serviceId); + } + } + + /// @dev Checks for mech validity. + /// @dev mech Agent mech contract address. + /// @param mechStakingInstance Agent mech staking instance address. + /// @param mechServiceId Agent mech service Id. + function checkMech(address mech, address mechStakingInstance, uint256 mechServiceId) public view { + // Check staking instance + checkStakingInstance(mechStakingInstance, mechServiceId); + + // Get the staked service info for the mech + IStaking.ServiceInfo memory serviceInfo = IStaking(mechStakingInstance).getServiceInfo(mechServiceId); + // Check that staked service multisig is the priority mech operator + if (!IMech(mech).isOperator(serviceInfo.multisig)) { + revert UnauthorizedAccount(mech); + } + } + + /// @dev Checks for requester validity. + /// @dev requester Requester contract address. + /// @param requesterStakingInstance Requester staking instance address. + /// @param requesterServiceId Requester service Id. + function checkRequester(address requester, address requesterStakingInstance, uint256 requesterServiceId) public view { + // Check staking instance + checkStakingInstance(requesterStakingInstance, requesterServiceId); + + // Get the requester staked service info + IStaking.ServiceInfo memory serviceInfo = IStaking(requesterStakingInstance).getServiceInfo(requesterServiceId); + // Check staked service multisig + if (serviceInfo.multisig != requester) { + revert OwnerOnly(requester, serviceInfo.multisig); + } + } + /// @dev Gets the request Id status. /// @param requestId Request Id. /// @return status Request status. From 9ad474184717bddc76e4442480f74fa24de02929 Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Mon, 26 Aug 2024 16:08:33 +0100 Subject: [PATCH 8/9] refactor: adding deliveries counter in the marketplace --- contracts/AgentMech.sol | 2 +- contracts/MechMarketplace.sol | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/contracts/AgentMech.sol b/contracts/AgentMech.sol index 186009f..5619697 100644 --- a/contracts/AgentMech.sol +++ b/contracts/AgentMech.sol @@ -436,7 +436,7 @@ contract AgentMech is ERC721Mech { /// @dev Gets the deliveries count for a specific account. /// @param account Account address. - /// @return Deliveried count. + /// @return Deliveries count. function getDeliveriesCount(address account) external view returns (uint256) { return mapDeliveryCounts[account]; } diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index 5b69495..99afe68 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -190,6 +190,8 @@ contract MechMarketplace { // Map of request counts for corresponding addresses mapping(address => uint256) public mapRequestCounts; + // Map of delivery counts for corresponding addresses + mapping(address => uint256) public mapDeliveryCounts; // Mapping of request Id => mech delivery information mapping(uint256 => MechDelivery) public mapRequestIdDeliveries; // Mapping of account nonces @@ -393,13 +395,17 @@ contract MechMarketplace { // Record the actual delivery mech mechDelivery.deliveryMech = msg.sender; + address requester = mechDelivery.requester; + // Decrease the number of undelivered requests numUndeliveredRequests--; + // Increase the amount of delivered requests + mapDeliveryCounts[requester]++; // Increase mech karma that delivers the request IKarma(karmaProxy).changeMechKarma(msg.sender, 1); - emit MarketplaceDeliver(priorityMech, msg.sender, mechDelivery.requester, requestId, requestData); + emit MarketplaceDeliver(priorityMech, msg.sender, requester, requestId, requestData); _locked = 1; } @@ -503,6 +509,20 @@ contract MechMarketplace { } } + /// @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 mech delivery info. /// @param requestId Request Id. /// @return Mech delivery info. From f4fc4b9fd3f4f8eb8ee7e3f4e589601f2c1bb6df Mon Sep 17 00:00:00 2001 From: Aleksandr Kuperman Date: Mon, 26 Aug 2024 16:10:36 +0100 Subject: [PATCH 9/9] refactor: adding deliveries counter in the marketplace --- contracts/MechMarketplace.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/MechMarketplace.sol b/contracts/MechMarketplace.sol index 99afe68..b1436ff 100644 --- a/contracts/MechMarketplace.sol +++ b/contracts/MechMarketplace.sol @@ -395,11 +395,10 @@ contract MechMarketplace { // Record the actual delivery mech mechDelivery.deliveryMech = msg.sender; - address requester = mechDelivery.requester; - // Decrease the number of undelivered requests numUndeliveredRequests--; // Increase the amount of delivered requests + address requester = mechDelivery.requester; mapDeliveryCounts[requester]++; // Increase mech karma that delivers the request