Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: split delivery rates according to mech type #56

Merged
merged 16 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions abis/0.8.28/MechMarketplace.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions abis/0.8.28/MechMarketplaceProxy.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"type": "error"
},
{
"stateMutability": "nonpayable",
"stateMutability": "payable",
"type": "fallback"
},
{
Expand Down Expand Up @@ -65,8 +65,8 @@
"type": "function"
}
],
"bytecode": "0x608060405234801561000f575f5ffd5b5060405161034438038061034483398101604081905261002e9161012d565b6001600160a01b0382166100555760405163d02c623d60e01b815260040160405180910390fd5b80515f036100765760405163c922446b60e01b815260040160405180910390fd5b817fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca555f826001600160a01b0316826040516100b291906101fc565b5f60405180830381855af49150503d805f81146100ea576040519150601f19603f3d011682016040523d82523d5f602084013e6100ef565b606091505b505090508061011157604051630337323560e31b815260040160405180910390fd5b505050610212565b634e487b7160e01b5f52604160045260245ffd5b5f5f6040838503121561013e575f5ffd5b82516001600160a01b0381168114610154575f5ffd5b60208401519092506001600160401b0381111561016f575f5ffd5b8301601f8101851361017f575f5ffd5b80516001600160401b0381111561019857610198610119565b604051601f8201601f19908116603f011681016001600160401b03811182821017156101c6576101c6610119565b6040528181528282016020018710156101dd575f5ffd5b8160208401602083015e5f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b6101258061021f5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c8063aaf10f42146070578063e8eca22d1460bc575b7fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca54365f5f375f5f365f845af490503d5f5f3e80606b573d5ffd5b503d5ff35b7fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca5460405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b60e27fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca81565b60405190815260200160b356fea2646970667358221220bad1bba0929384aed9089f42d3d55d954e78fa90a17404fff9014e353150642464736f6c634300081c0033",
"deployedBytecode": "0x6080604052348015600e575f5ffd5b50600436106030575f3560e01c8063aaf10f42146070578063e8eca22d1460bc575b7fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca54365f5f375f5f365f845af490503d5f5f3e80606b573d5ffd5b503d5ff35b7fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca5460405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b60e27fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca81565b60405190815260200160b356fea2646970667358221220bad1bba0929384aed9089f42d3d55d954e78fa90a17404fff9014e353150642464736f6c634300081c0033",
"bytecode": "0x608060405234801561000f575f5ffd5b5060405161034f38038061034f83398101604081905261002e9161012d565b6001600160a01b0382166100555760405163d02c623d60e01b815260040160405180910390fd5b80515f036100765760405163c922446b60e01b815260040160405180910390fd5b817fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca555f826001600160a01b0316826040516100b291906101fc565b5f60405180830381855af49150503d805f81146100ea576040519150601f19603f3d011682016040523d82523d5f602084013e6100ef565b606091505b505090508061011157604051630337323560e31b815260040160405180910390fd5b505050610212565b634e487b7160e01b5f52604160045260245ffd5b5f5f6040838503121561013e575f5ffd5b82516001600160a01b0381168114610154575f5ffd5b60208401519092506001600160401b0381111561016f575f5ffd5b8301601f8101851361017f575f5ffd5b80516001600160401b0381111561019857610198610119565b604051601f8201601f19908116603f011681016001600160401b03811182821017156101c6576101c6610119565b6040528181528282016020018710156101dd575f5ffd5b8160208401602083015e5f602083830101528093505050509250929050565b5f82518060208501845e5f920191825250919050565b6101308061021f5f395ff3fe6080604052600436106025575f3560e01c8063aaf10f42146065578063e8eca22d1460bc575b7fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca54365f5f375f5f365f845af490503d5f5f3e806060573d5ffd5b503d5ff35b348015606f575f5ffd5b507fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca5460405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801560c6575f5ffd5b5060ed7fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca81565b60405190815260200160b356fea26469706673582212204e392dd24d9dd94b41b34333e0dcc6dc067fd550a1b66868ddd7b53154c6592a64736f6c634300081c0033",
"deployedBytecode": "0x6080604052600436106025575f3560e01c8063aaf10f42146065578063e8eca22d1460bc575b7fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca54365f5f375f5f365f845af490503d5f5f3e806060573d5ffd5b503d5ff35b348015606f575f5ffd5b507fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca5460405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801560c6575f5ffd5b5060ed7fe6194b93a7bff0a54130ed8cd277223408a77f3e48bb5104a9db96d334f962ca81565b60405190815260200160b356fea26469706673582212204e392dd24d9dd94b41b34333e0dcc6dc067fd550a1b66868ddd7b53154c6592a64736f6c634300081c0033",
"linkReferences": {},
"deployedLinkReferences": {}
}
332 changes: 332 additions & 0 deletions contracts/BalanceTrackerFixedPrice.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IMech} from "./interfaces/IMech.sol";

interface IMechMarketplace {
function fee() external returns(uint256);
}

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

/// @dev Transfers the token amount that was previously approved up until the maximum allowance.
/// @param from Account address to transfer from.
/// @param to Account address to transfer to.
/// @param amount Amount to transfer to.
/// @return True if the function execution is successful.
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

interface IWrappedToken {
function deposit() external payable;
}

/// @dev Only `manager` has a privilege, but the `sender` was provided.
/// @param sender Sender address.
/// @param manager Required sender address as a manager.
error ManagerOnly(address sender, address manager);

/// @dev Provided zero address.
error ZeroAddress();

/// @dev Provided zero value.
error ZeroValue();

/// @dev Not enough balance to cover costs.
/// @param current Current balance.
/// @param required Required balance.
error InsufficientBalance(uint256 current, uint256 required);

/// @dev Caught reentrancy violation.
error ReentrancyGuard();

/// @dev Account is unauthorized.
/// @param account Account address.
error UnauthorizedAccount(address account);

/// @dev No incoming msg.value is allowed.
/// @param amount Value amount.
error NoDepositAllowed(uint256 amount);

/// @dev Payload length is incorrect.
/// @param provided Provided payload length.
/// @param expected Expected payload length.
error InvalidPayloadLength(uint256 provided, uint256 expected);

/// @dev Failure of a transfer.
/// @param token Address of a token.
/// @param from Address `from`.
/// @param to Address `to`.
/// @param amount Amount value.
error TransferFailed(address token, address from, address to, uint256 amount);

contract BalanceTrackerFixedPrice {
event MechPaymentCalculated(address indexed mech, uint256 indexed requestId, uint256 deliveryRate, uint256 rateDiff);
event Deposit(address indexed account, address indexed token, uint256 amount);
event Withdraw(address indexed account, address indexed token, uint256 amount);
event Drained(address indexed token, uint256 collectedFees);

// Mech marketplace address
address public immutable mechMarketplace;

Check warning on line 75 in contracts/BalanceTrackerFixedPrice.sol

View workflow job for this annotation

GitHub Actions / build

Immutable variables name are set to be in capitalized SNAKE_CASE
// Wrapped native token address
address public immutable wrappedNativeToken;

Check warning on line 77 in contracts/BalanceTrackerFixedPrice.sol

View workflow job for this annotation

GitHub Actions / build

Immutable variables name are set to be in capitalized SNAKE_CASE
// Buy back burner address
address public immutable buyBackBurner;

Check warning on line 79 in contracts/BalanceTrackerFixedPrice.sol

View workflow job for this annotation

GitHub Actions / build

Immutable variables name are set to be in capitalized SNAKE_CASE
// Reentrancy lock
uint256 internal _locked = 1;

// Map of requester => map of (token => current debit balance)
mapping(address => mapping(address => uint256)) public mapRequesterBalances;
// Map of mech => map of (token => current debit balance)
mapping(address => mapping(address => uint256)) public mapMechBalances;
// Map of token => collected fees
mapping(address => uint256) public mapCollectedFees;
// Map of requestId => token
mapping(uint256 => address) public mapRequestIdTokens;

/// @dev BalanceTrackerFixedPrice constructor.
/// @param _mechMarketplace Mech marketplace address.
/// @param _wrappedNativeToken Wrapped native token address.
/// @param _buyBackBurner Buy back burner address.
constructor(address _mechMarketplace, address _wrappedNativeToken, address _buyBackBurner) {
// Check for zero address
if (_mechMarketplace == address(0) || _wrappedNativeToken == address(0) || _buyBackBurner == address(0)) {
revert ZeroAddress();
}

mechMarketplace = _mechMarketplace;
wrappedNativeToken = _wrappedNativeToken;
buyBackBurner = _buyBackBurner;
}

function _wrap(uint256 amount) internal virtual {
IWrappedToken(wrappedNativeToken).deposit{value: amount}();
}

// Check and record delivery rate
function checkAndRecordDeliveryRate(
address mech,
address requester,
uint256 requestId,
bytes memory paymentData
) external payable {
// Check for marketplace access
if (msg.sender != mechMarketplace) {
revert ManagerOnly(msg.sender, mechMarketplace);
}

// Get mech max delivery rate
uint256 maxDeliveryRate = IMech(mech).maxDeliveryRate();

// Get payment token
address token;
if (paymentData.length == 32) {
// Extract token address
token = abi.decode(paymentData, (address));
if (token != address(0) && msg.value > 0) {
revert NoDepositAllowed(msg.value);
}
} else if (paymentData.length > 0) {
revert InvalidPayloadLength(paymentData.length, 32);
}

kupermind marked this conversation as resolved.
Show resolved Hide resolved
// Get account balance
uint256 balance = mapRequesterBalances[requester][token];

// Check the request delivery rate for a fixed price
if (balance < maxDeliveryRate) {
revert InsufficientBalance(balance, maxDeliveryRate);
}

// Record request token
mapRequestIdTokens[requestId] = token;

// Adjust account balance
balance -= maxDeliveryRate;
mapRequesterBalances[requester][token] = balance;
}

/// @dev Finalizes mech delivery rate based on requested and actual ones.
/// @param mech Delivery mech address.
/// @param requester Requester address.
/// @param requestId Request Id.
/// @param maxDeliveryRate Requested max delivery rate.
function finalizeDeliveryRate(address mech, address requester, uint256 requestId, uint256 maxDeliveryRate) external {
// Check for marketplace access
if (msg.sender != mechMarketplace) {
revert ManagerOnly(msg.sender, mechMarketplace);
}

// Get actual delivery rate
uint256 actualDeliveryRate = IMech(mech).getFinalizedDeliveryRate(requestId);

// Check for zero value
if (actualDeliveryRate == 0) {
revert ZeroValue();
}

// Get token associated with request Id
address token = mapRequestIdTokens[requestId];

uint256 rateDiff;
if (maxDeliveryRate > actualDeliveryRate) {
// Return back requester overpayment debit
rateDiff = maxDeliveryRate - actualDeliveryRate;
mapRequesterBalances[requester][token] += rateDiff;
} else {
actualDeliveryRate = maxDeliveryRate;
}

// Record payment into mech balance
mapMechBalances[mech][token] += actualDeliveryRate;

emit MechPaymentCalculated(mech, requestId, actualDeliveryRate, rateDiff);
}

// TODO buyBackBurner does not account for other tokens but WETH, OLAS
kupermind marked this conversation as resolved.
Show resolved Hide resolved
/// @dev Drains collected fees by sending them to a Buy back burner contract.
function drain(address token) external {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;

uint256 localCollectedFees = mapCollectedFees[token];

// TODO Limits
kupermind marked this conversation as resolved.
Show resolved Hide resolved
// Check for zero value
if (localCollectedFees == 0) {
revert ZeroValue();
}

mapCollectedFees[token] = 0;

// Check token address
if (token == address (0)) {
// Wrap native tokens
_wrap(localCollectedFees);
// Transfer to Buy back burner
IToken(wrappedNativeToken).transfer(buyBackBurner, localCollectedFees);
} else {
IToken(token).transfer(buyBackBurner, localCollectedFees);
}

emit Drained(token, localCollectedFees);

_locked = 1;

Check warning on line 222 in contracts/BalanceTrackerFixedPrice.sol

View workflow job for this annotation

GitHub Actions / build

Possible reentrancy vulnerabilities. Avoid state changes after transfer
}

function _withdraw(address token, uint256 balance) internal {
bool success;
// Transfer mech balance
if (token == address(0)) {
// solhint-disable-next-line avoid-low-level-calls
(success, ) = msg.sender.call{value: balance}("");
} else {
IToken(token).transfer(msg.sender, balance);
}

// Check transfer
if (!success) {
revert TransferFailed(token, address(this), msg.sender, balance);
}
}

/// @dev Processes mech payment by withdrawing funds.
function processPayment(address token) external returns (uint256 mechPayment, uint256 marketplaceFee) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;

// Get mech balance
uint256 balance = mapMechBalances[msg.sender][token];
// TODO limits?
kupermind marked this conversation as resolved.
Show resolved Hide resolved
if (balance == 0) {
revert ZeroValue();
}

// Calculate mech payment and marketplace fee
uint256 fee = IMechMarketplace(mechMarketplace).fee();
marketplaceFee = (balance * fee) / 10_000;
kupermind marked this conversation as resolved.
Show resolved Hide resolved
mechPayment = balance - marketplaceFee;

// Check for zero value, although this must never happen
if (mechPayment == 0) {
revert ZeroValue();
}

// Adjust marketplace fee
mapCollectedFees[token] += marketplaceFee;

// Clear balances
mapMechBalances[msg.sender][token] = 0;

// Process withdraw
_withdraw(token, balance);

emit Withdraw(msg.sender, token, balance);

_locked = 1;
}

/// @dev Withdraws funds for a specific requester account.
function withdraw(address token) external {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;

// Get account balance
uint256 balance = mapRequesterBalances[msg.sender][token];
// TODO limits?
if (balance == 0) {
revert ZeroValue();
}

// Clear balances
mapRequesterBalances[msg.sender][token] = 0;

// Process withdraw
_withdraw(token, balance);

emit Withdraw(msg.sender, token, balance);

_locked = 1;
}

// Deposits token funds for requester.
function deposit(address token, uint256 amount) external {
// TODO Accept deposits from mechs as well?
kupermind marked this conversation as resolved.
Show resolved Hide resolved

if (token == address(0)) {
revert ZeroAddress();
}

// TODO: safe transfer?
kupermind marked this conversation as resolved.
Show resolved Hide resolved
IToken(token).transferFrom(msg.sender, address(0), amount);

// Update account balances
mapRequesterBalances[msg.sender][address(0)] += amount;

emit Deposit(msg.sender, token, amount);
}

// Deposits native funds for requester.
receive() external payable {

Check warning on line 324 in contracts/BalanceTrackerFixedPrice.sol

View workflow job for this annotation

GitHub Actions / build

Fallback function must be simple
// TODO Accept deposits from mechs as well?

// Update account balances
mapRequesterBalances[msg.sender][address(0)] += msg.value;

emit Deposit(msg.sender, address(0), msg.value);
}
}
10 changes: 5 additions & 5 deletions contracts/MechFactoryFixedPrice.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ error IncorrectDataLength(uint256 provided, uint256 expected);

/// @title Mech Factory Basic - Periphery smart contract for managing basic mech creation
contract MechFactoryFixedPrice {
event CreateBasicMech(address indexed mech, uint256 indexed serviceId, uint256 indexed price);
event CreateFixedPriceMech(address indexed mech, uint256 indexed serviceId, uint256 maxDeliveryRate);

// Agent factory version number
string public constant VERSION = "0.1.0";
Expand All @@ -32,15 +32,15 @@ contract MechFactoryFixedPrice {
revert IncorrectDataLength(payload.length, 32);
}

// Decode price
uint256 price = abi.decode(payload, (uint256));
// Decode max delivery rate
uint256 maxDeliveryRate = abi.decode(payload, (uint256));

// Get salt
bytes32 salt = keccak256(abi.encode(block.timestamp, msg.sender, serviceId));

// Service multisig is isOperator() for the mech
mech = address((new MechFixedPrice){salt: salt}(mechMarketplace, serviceRegistry, serviceId, price));
mech = address((new MechFixedPrice){salt: salt}(mechMarketplace, serviceRegistry, serviceId, maxDeliveryRate));

emit CreateBasicMech(mech, serviceId, price);
emit CreateFixedPriceMech(mech, serviceId, maxDeliveryRate);
}
}
Loading
Loading