Blockchain / Global / Address
Contract
0x3Cb2CBb0CeB96c9456b11DbC7ab73c4848F9a14c
- Balance
- 0 SOMI
- Transactions sent
- 1
- Type
- Contract · 24,513 bytes of code
MatchingEngine Verified
- Compiler
- solc 0.8.24+commit.e11b9ed9
- Language
- Solidity
- Optimization
- Yes, 200 runs
- EVM version
- cancun
- License
- —
- Verified
- 2026-10-12
src/exchange/MatchingEngine.sol
and 14 more files
Solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;
import {IOrderbookFactory} from "./interfaces/IOrderbookFactory.sol";
import {IOrderbook, ExchangeOrderbook} from "./interfaces/IOrderbook.sol";
import {TransferHelper} from "./libraries/TransferHelper.sol";
import {IWETH} from "./interfaces/IWETH.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {IMatchingEngine} from "./interfaces/IMatchingEngine.sol";
interface IProtocol {
function feeOf(
address base,
address quote,
address account,
bool isMaker
) external view returns (uint32 feeNum);
function isSubscribed(
address account
) external view returns (bool isSubscribed);
function terminalName(
address terminal
) external view returns (string memory terminalName);
}
// Onchain Matching engine for the orders
contract MatchingEngine is ReentrancyGuard, AccessControl, IMatchingEngine {
// Market maker role
bytes32 private constant MARKET_MAKER_ROLE = keccak256("MARKET_MAKER_ROLE");
// fee recipient for point storage
address public feeTo;
// incentive address
address public incentive;
uint32 public constant DENOM = 100000000;
// base fee in numerator for DENOM=100000000
uint32 private defaultMakerFee = 100000;
// base taker fee in numerator for DENOM=100000000
uint32 private defaultTakerFee = 100000;
// Factories
address public orderbookFactory;
// WETH
address public WETH;
// default buy spread
uint32 dfltMktBuy;
// default sell spread
uint32 dfltMktSell;
// default buy spread
uint32 dfltLmtBuy;
// default sell spread
uint32 dfltLmtSEll;
// bool on initialization
bool init;
// max matches
uint32 maxMatches;
// history id count
uint16 count = 0;
// Spread limit setting
mapping(address => DefaultSpread) public mktSpreadLimits;
// Spread limit setting
mapping(address => DefaultSpread) public lmtSpreadLimits;
// Listing Info setting
mapping(address => uint256) public listingDates;
event OrderCanceled(
address pair,
uint256 id,
bool isBid,
address indexed owner,
uint256 amount
);
event NewMarketPrice(address pair, uint256 price, bool isBid);
event ListingCostSet(address payment, uint256 amount);
/**
* @dev This event is emitted when an order is successfully matched with a counterparty.
* @param pair The address of the order book contract to get base and quote asset contract address.
* @param orderHistoryId the unique identifier of order history submitted by the user in a block.
* @param id The unique identifier of the matched maker order in bid/ask order database.
* @param isBid A boolean indicating whether the matched order is a bid (true) or ask (false).
* @param price The price at which the order is matched.
* @param total The total amount of the asset being traded in the match.
* @param clear Whether the order is cleared.
* @param orderMatch The order match result.
*/
event OrderMatched(
address pair,
uint16 orderHistoryId,
uint256 id,
bool isBid,
uint256 price,
uint256 total,
bool clear,
OrderMatch orderMatch
);
event OrderPlaced(
address pair,
uint16 orderHistoryId,
uint256 id,
address owner,
bool isBid,
uint256 price,
uint256 withoutFee,
uint256 placed
);
event PairAdded(
address pair,
TransferHelper.TokenInfo base,
TransferHelper.TokenInfo quote,
uint256 listingPrice,
uint256 listingDate,
string supportedTerminals
);
event PairUpdated(
address pair,
address base,
address quote,
uint256 listingPrice,
uint256 listingDate
);
event PairCreate2(address deployer, bytes bytecode);
error TooManyMatches(uint256 n);
error InvalidTerminal(address terminal);
error OrderSizeTooSmall(uint256 amount, uint256 minRequired);
error InvalidRole(bytes32 role, address sender);
error InvalidPair(address base, address quote, address pair);
error PairNotListedYet(
address base,
address quote,
uint256 listingDate,
uint256 timeNow
);
error PairDoesNotExist(address base, address quote, address pair);
error AmountIsZero();
error FactoryNotInitialized(address factory);
error AlreadyInitialized(bool init);
receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MARKET_MAKER_ROLE, msg.sender);
}
/**
* @dev Initialize the matching engine with orderbook factory and listing requirements.
* It can be called only once.
* @param orderbookFactory_ address of orderbook factory
* @param feeTo_ address to receive fee
* @param WETH_ address of wrapped ether contract
*
* Requirements:
* - `msg.sender` must have the default admin role.
*/
function initialize(
address orderbookFactory_,
address feeTo_,
address WETH_
) external {
if (init) {
revert AlreadyInitialized(init);
}
orderbookFactory = orderbookFactory_;
feeTo = feeTo_;
WETH = WETH_;
dfltMktBuy = 100000;
dfltMktSell = 100000;
dfltLmtBuy = 3000000;
dfltLmtSEll = 3000000;
// get impl address of orderbook contract to predict address
address impl = IOrderbookFactory(orderbookFactory_).impl();
// Orderbook factory must be initialized first to locate pairs
if (impl == address(0)) {
revert FactoryNotInitialized(orderbookFactory_);
}
bytes memory bytecode = IOrderbookFactory(orderbookFactory_)
.getByteCode();
init = true;
maxMatches = 20;
emit PairCreate2(orderbookFactory, bytecode);
}
// admin functions
function setFeeTo(
address feeTo_
) external override onlyRole(DEFAULT_ADMIN_ROLE) returns (bool success) {
feeTo = feeTo_;
return true;
}
function setIncentive(
address incentive_
) external onlyRole(DEFAULT_ADMIN_ROLE) returns (bool success) {
incentive = incentive_;
return true;
}
function setDefaultFee(
bool isMaker,
uint32 fee_
) external onlyRole(DEFAULT_ADMIN_ROLE) returns (bool success) {
if (isMaker) {
defaultMakerFee = fee_;
} else {
defaultTakerFee = fee_;
}
return true;
}
function setMaxMatches(
uint32 n
) external onlyRole(DEFAULT_ADMIN_ROLE) returns (bool success) {
maxMatches = n;
return true;
}
/**
* @dev Set the listing cost for a token. Each pair costs minimum 2GB of data storage in a month, costing 0.1 ETH.
* @param terminal terminal name
* @param payment address of payment token
* @param amount amount of token
*
* Requirements:
* - `msg.sender` must have the default admin role.
*/
function setListingCost(
string memory terminal,
address payment,
uint256 amount
) external onlyRole(DEFAULT_ADMIN_ROLE) returns (uint256) {
IOrderbookFactory(orderbookFactory).setListingCost(
terminal,
payment,
amount
);
emit ListingCostSet(payment, amount);
return amount;
}
function setDefaultSpread(
uint32 buy,
uint32 sell,
bool isMkt
) external override onlyRole(DEFAULT_ADMIN_ROLE) returns (bool success) {
if (isMkt) {
dfltMktBuy = buy;
dfltMktSell = sell;
} else {
dfltLmtBuy = buy;
dfltLmtSEll = sell;
}
return true;
}
// market maker functions
function setSpread(
address base,
address quote,
uint32 buy,
uint32 sell,
bool isMkt
) external override returns (bool success) {
// get pair
address pair = IOrderbookFactory(orderbookFactory).getPair(base, quote);
if (pair == address(0)) {
revert PairDoesNotExist(base, quote, pair);
}
if (!hasRole(MARKET_MAKER_ROLE, _msgSender())) {
if (!hasRole(bytes32(abi.encodePacked(pair)), _msgSender())) {
revert InvalidRole(
bytes32(abi.encodePacked(pair)),
_msgSender()
);
}
}
_setSpread(pair, buy, sell, isMkt);
return true;
}
// user functions
function _nextHistoryId() internal view returns (uint16) {
return count == 0 || count == type(uint16).max ? 1 : count + 1;
}
function _marketBuy(
address base,
address quote,
uint256 quoteAmount,
bool isMaker,
uint32 n,
address recipient,
uint32 slippageLimit,
uint16 orderHistoryId
) internal returns (OrderResult memory result) {
OrderData memory orderData;
// reuse quoteAmount variable as minRequired from _deposit to avoid stack too deep error
(orderData.withoutFee, orderData.pair, orderData.lmp) = _deposit(
base,
quote,
0,
quoteAmount,
true
);
// get spread limits
orderData.spreadLimit = slippageLimit <=
getSpread(orderData.pair, true, true)
? slippageLimit
: getSpread(orderData.pair, true, true);
orderData.lmp = mktPrice(base, quote);
// reuse quoteAmount for storing amount after taking fees
quoteAmount = orderData.withoutFee;
// reuse withoutFee variable for storing remaining amount due to stack too deep error
(
orderData.withoutFee,
orderData.bidHead,
orderData.askHead
) = _limitOrder(
orderData.pair,
orderData.withoutFee,
quote,
recipient,
true,
(orderData.lmp * (DENOM + orderData.spreadLimit)) / DENOM,
n,
orderHistoryId
);
// reuse orderData.bidHead argument for storing make price
orderData.bidHead = _detMarketBuyMakePrice(
orderData.pair,
orderData.bidHead,
orderData.askHead,
orderData.spreadLimit
);
// add make order on market price, reuse orderData.ls for storing placed Order id
orderData.makeId = _detMake(
base,
quote,
orderData.pair,
orderData.withoutFee,
orderData.bidHead,
true,
isMaker,
recipient
);
// check if order id is made
if (orderData.makeId > 0) {
//if made, set last market price to orderData.bidHead only if orderData.bidHead is greater than lmp
if (orderData.bidHead > orderData.lmp) {
IOrderbook(orderData.pair).setLmp(orderData.bidHead);
emit NewMarketPrice(orderData.pair, orderData.bidHead, true);
}
emit OrderPlaced(
orderData.pair,
orderHistoryId,
orderData.makeId,
recipient,
true,
orderData.bidHead,
quoteAmount,
orderData.withoutFee
);
result.makePrice = orderData.bidHead;
result.placed = orderData.withoutFee;
result.id = orderData.makeId;
} else {
result.makePrice = orderData.bidHead;
result.placed = 0;
result.id = 0;
}
return result;
}
/**
* @dev Executes a market buy order, with spread limit of 1% for price actions.
* buys the base asset using the quote asset at the best available price in the orderbook up to `n` orders,
* and make an order at the market price.
* @param base The address of the base asset for the trading pair
* @param quote The address of the quote asset for the trading pair
* @param quoteAmount The amount of quote asset to be used for the market buy order
* @param isMaker Boolean indicating if a order should be made at the market price in orderbook
* @param n The maximum number of orders to match in the orderbook
* @param recipient The address of the order owner
* @param slippageLimit Slippage limit in basis points
* @return result Result of the order, makePrice is the next price to be set if placed and id is 0
*/
function marketBuy(
address base,
address quote,
uint256 quoteAmount,
bool isMaker,
uint32 n,
address recipient,
uint32 slippageLimit
) external override nonReentrant returns (OrderResult memory result) {
count = _nextHistoryId();
return
_marketBuy(
base,
quote,
quoteAmount,
isMaker,
n,
recipient,
slippageLimit,
count
);
}
function _detMarketBuyMakePrice(
address orderbook,
uint256 bidHead,
uint256 askHead,
uint32 spread
) internal view returns (uint256 price) {
uint256 up;
uint256 lmp = IOrderbook(orderbook).lmp();
if (askHead == 0 && bidHead == 0) {
// lmp must exist unless there has been no order in orderbook
if (lmp != 0) {
up = (lmp * (DENOM + spread)) / DENOM;
return up;
}
} else if (askHead == 0 && bidHead != 0) {
if (lmp != 0) {
uint256 temp = (bidHead >= lmp ? bidHead : lmp);
up = (temp * (DENOM + spread)) / DENOM;
return up;
}
up = (bidHead * (DENOM + spread)) / DENOM;
return up;
} else if (askHead != 0 && bidHead == 0) {
if (lmp != 0) {
up = (lmp * (DENOM + spread)) / DENOM;
return askHead >= up ? up : askHead;
}
return askHead;
} else {
if (lmp != 0) {
uint256 temp = (bidHead >= lmp ? bidHead : lmp);
up = (temp * (DENOM + spread)) / DENOM;
return askHead >= up ? up : askHead;
}
return askHead;
}
}
function _marketSell(
address base,
address quote,
uint256 baseAmount,
bool isMaker,
uint32 n,
address recipient,
uint32 slippageLimit,
uint16 orderHistoryId
) internal returns (OrderResult memory result) {
OrderData memory orderData;
(orderData.withoutFee, orderData.pair, orderData.lmp) = _deposit(
base,
quote,
0,
baseAmount,
false
);
// get spread limits
orderData.spreadLimit = slippageLimit <=
getSpread(orderData.pair, false, true)
? slippageLimit
: getSpread(orderData.pair, false, true);
orderData.lmp = mktPrice(base, quote);
// reuse baseAmount for storing without fee
baseAmount = orderData.withoutFee;
// reuse withoutFee variable for storing remaining amount after matching due to stack too deep error
(
orderData.withoutFee,
orderData.bidHead,
orderData.askHead
) = _limitOrder(
orderData.pair,
orderData.withoutFee,
base,
recipient,
false,
(orderData.lmp * (DENOM - orderData.spreadLimit)) / DENOM,
n,
orderHistoryId
);
// reuse orderData.askHead argument for storing make price
orderData.askHead = _detMarketSellMakePrice(
orderData.pair,
orderData.bidHead,
orderData.askHead,
orderData.spreadLimit
);
orderData.makeId = _detMake(
base,
quote,
orderData.pair,
orderData.withoutFee,
orderData.askHead,
false,
isMaker,
recipient
);
// check if order id is made
if (orderData.makeId > 0) {
//if made, set last market price to orderData.askHead only if askHead is smaller than lmp
if (orderData.askHead < orderData.lmp) {
IOrderbook(orderData.pair).setLmp(orderData.askHead);
emit NewMarketPrice(orderData.pair, orderData.askHead, false);
}
emit OrderPlaced(
orderData.pair,
orderHistoryId,
orderData.makeId,
recipient,
false,
orderData.askHead,
baseAmount,
orderData.withoutFee
);
result.makePrice = orderData.askHead;
result.placed = orderData.withoutFee;
result.id = orderData.makeId;
} else {
result.makePrice = orderData.askHead;
result.placed = 0;
result.id = 0;
}
return result;
}
/**
* @dev Executes a market sell order, with spread limit of 5% for price actions.
* sells the base asset for the quote asset at the best available price in the orderbook up to `n` orders,
* and make an order at the market price.
* @param base The address of the base asset for the trading pair
* @param quote The address of the quote asset for the trading pair
* @param baseAmount The amount of base asset to be sold in the market sell order
* @param isMaker Boolean indicating if an order should be made at the market price in orderbook
* @param n The maximum number of orders to match in the orderbook
* @param recipient recipient of order for trading
* @param slippageLimit slippage limit from market order in basis point
* @return result Result of the order, makePrice is the next price to be set if placed and id is 0
*/
function marketSell(
address base,
address quote,
uint256 baseAmount,
bool isMaker,
uint32 n,
address recipient,
uint32 slippageLimit
) external override nonReentrant returns (OrderResult memory result) {
count = _nextHistoryId();
return
_marketSell(
base,
quote,
baseAmount,
isMaker,
n,
recipient,
slippageLimit,
count
);
}
function _detMarketSellMakePrice(
address orderbook,
uint256 bidHead,
uint256 askHead,
uint32 spread
) internal view returns (uint256 price) {
uint256 down;
uint256 lmp = IOrderbook(orderbook).lmp();
if (askHead == 0 && bidHead == 0) {
// lmp must exist unless there has been no order in orderbook
if (lmp != 0) {
down = (lmp * (DENOM - spread)) / DENOM;
return down == 0 ? 1 : down;
}
} else if (askHead == 0 && bidHead != 0) {
if (lmp != 0) {
down = (lmp * (DENOM - spread)) / DENOM;
down = down <= bidHead ? bidHead : down;
return down == 0 ? 1 : down;
}
return bidHead;
} else if (askHead != 0 && bidHead == 0) {
if (lmp != 0) {
uint256 temp = lmp <= askHead ? lmp : askHead;
down = (temp * (DENOM - spread)) / DENOM;
return down == 0 ? 1 : down;
}
down = (askHead * (DENOM - spread)) / DENOM;
return down == 0 ? 1 : down;
} else {
if (lmp != 0) {
uint256 temp = lmp <= askHead ? lmp : askHead;
down = (temp * (DENOM - spread)) / DENOM;
down = down <= bidHead ? bidHead : down;
return down == 0 ? 1 : down;
}
return bidHead;
}
}
/**
* @dev Executes a market buy order, with spread limit of 5% for price actions.
* buys the base asset using the quote asset at the best available price in the orderbook up to `n` orders,
* and make an order at the market price with quote asset as native Ethereum(or other network currencies).
* @param base The address of the base asset for the trading pair
* @param isMaker Boolean indicating if a order should be made at the market price in orderbook
* @param n The maximum number of orders to match in the orderbook
* @param recipient The address of the recipient to receive traded asset and claim ownership of made order
* @param slippageLimit Slippage limit in basis points
* @return result Result of the order
*/
function marketBuyETH(
address base,
bool isMaker,
uint32 n,
address recipient,
uint32 slippageLimit
) external payable override returns (OrderResult memory result) {
IWETH(WETH).deposit{value: msg.value}();
count = _nextHistoryId();
return
_marketBuy(
base,
WETH,
msg.value,
isMaker,
n,
recipient,
slippageLimit,
count
);
}
/**
* @dev Executes a market sell order,
* sells the base asset for the quote asset at the best available price in the orderbook up to `n` orders,
* and make an order at the market price with base asset as native Ethereum(or other network currencies).
* @param quote The address of the quote asset for the trading pair
* @param isMaker Boolean indicating if an order should be made at the market price in orderbook
* @param n The maximum number of orders to match in the orderbook
* @param recipient The address of the recipient to receive traded asset and claim ownership of made order
* @param slippageLimit Slippage limit in basis points
* @return result Result of the order
*/
function marketSellETH(
address quote,
bool isMaker,
uint32 n,
address recipient,
uint32 slippageLimit
) external payable override returns (OrderResult memory result) {
IWETH(WETH).deposit{value: msg.value}();
count = _nextHistoryId();
return
_marketSell(
WETH,
quote,
msg.value,
isMaker,
n,
recipient,
slippageLimit,
count
);
}
function _limitBuy(
address base,
address quote,
uint256 price,
uint256 quoteAmount,
bool isMaker,
uint32 n,
address recipient,
uint16 orderHistoryId
) internal returns (OrderResult memory result) {
OrderData memory orderData;
(orderData.withoutFee, orderData.pair, orderData.lmp) = _deposit(
base,
quote,
price,
quoteAmount,
true
);
// get spread limits
orderData.spreadLimit = getSpread(orderData.pair, true, false);
// reuse quoteAmount for storing amount without fee
quoteAmount = orderData.withoutFee;
// reuse withoutFee variable for storing remaining amount after matching due to stack too deep error
(
orderData.withoutFee,
orderData.bidHead,
orderData.askHead
) = _limitOrder(
orderData.pair,
orderData.withoutFee,
quote,
recipient,
true,
price >= (orderData.lmp * (DENOM + orderData.spreadLimit)) / DENOM
? (orderData.lmp * (DENOM + orderData.spreadLimit)) / DENOM
: price,
n,
orderHistoryId
);
// reuse price variable for storing make price, determine
(price, orderData.lmp) = _detLimitBuyMakePrice(
orderData.pair,
price,
orderData.bidHead,
orderData.askHead,
orderData.spreadLimit
);
orderData.makeId = _detMake(
base,
quote,
orderData.pair,
orderData.withoutFee,
price,
true,
isMaker,
recipient
);
// check if order id is made
if (orderData.makeId > 0) {
// if made, set last market price to price only if price is higher than lmp
if (price > orderData.lmp) {
IOrderbook(orderData.pair).setLmp(price);
emit NewMarketPrice(orderData.pair, price, true);
}
emit OrderPlaced(
orderData.pair,
orderHistoryId,
orderData.makeId,
recipient,
true,
price,
quoteAmount,
orderData.withoutFee
);
result.makePrice = price;
result.placed = orderData.withoutFee;
result.id = orderData.makeId;
} else {
result.makePrice = price;
result.placed = 0;
result.id = 0;
}
return result;
}
/**
* @dev Executes a limit buy order, with spread limit of 5% for price actions.
* places a limit order in the orderbook for buying the base asset using the quote asset at a specified price,
* and make an order at the limit price.
* @param base The address of the base asset for the trading pair
* @param quote The address of the quote asset for the trading pair
* @param price The price, base/quote regardless of decimals of the assets in the pair represented with 8 decimals (if 1000, base is 1000x quote)
* @param quoteAmount The amount of quote asset to be used for the limit buy order
* @param isMaker Boolean indicating if an order should be made at the limit price
* @param n The maximum number of orders to match in the orderbook
* @param recipient The address of the recipient to receive traded asset and claim ownership of made order
* @return result Result of the order, makePrice is the next price to be set if placed and id is 0
*/
function limitBuy(
address base,
address quote,
uint256 price,
uint256 quoteAmount,
bool isMaker,
uint32 n,
address recipient
) external override nonReentrant returns (OrderResult memory result) {
count = _nextHistoryId();
return
_limitBuy(
base,
quote,
price,
quoteAmount,
isMaker,
n,
recipient,
count
);
}
function _detLimitBuyMakePrice(
address orderbook,
uint256 lp,
uint256 bidHead,
uint256 askHead,
uint32 spread
) internal view returns (uint256 price, uint256 lmp) {
uint256 up;
lmp = IOrderbook(orderbook).lmp();
if (askHead == 0 && bidHead == 0) {
if (lmp != 0) {
up = (lmp * (DENOM + spread)) / DENOM;
return (lp >= up ? up : lp, lmp);
}
return (lp, lmp);
} else if (askHead == 0 && bidHead != 0) {
if (lmp != 0) {
up = (lmp * (DENOM + spread)) / DENOM;
return (lp >= up ? up : lp, lmp);
}
up = (bidHead * (DENOM + spread)) / DENOM;
return (lp >= up ? up : lp, lmp);
} else if (askHead != 0 && bidHead == 0) {
if (lmp != 0) {
up = (lmp * (DENOM + spread)) / DENOM;
up = lp >= up ? up : lp;
return (up >= askHead ? askHead : up, lmp);
}
up = (askHead * (DENOM + spread)) / DENOM;
up = lp >= up ? up : lp;
return (up >= askHead ? askHead : up, lmp);
} else {
if (lmp != 0) {
up = (lmp * (DENOM + spread)) / DENOM;
up = lp >= up ? up : lp;
return (up >= askHead ? askHead : up, lmp);
}
// upper limit on make price must not go above ask price
return (lp >= askHead ? askHead : lp, lmp);
}
}
function _limitSell(
address base,
address quote,
uint256 price,
uint256 baseAmount,
bool isMaker,
uint32 n,
address recipient,
uint16 orderHistoryId
) internal returns (OrderResult memory result) {
OrderData memory orderData;
(orderData.withoutFee, orderData.pair, orderData.lmp) = _deposit(
base,
quote,
price,
baseAmount,
false
);
// get spread limit
orderData.spreadLimit = getSpread(orderData.pair, false, false);
// reuse baseAmount for storing amount without fee
baseAmount = orderData.withoutFee;
// reuse withoutFee variable for storing remaining amount after matching due to stack too deep error
(
orderData.withoutFee,
orderData.bidHead,
orderData.askHead
) = _limitOrder(
orderData.pair,
orderData.withoutFee,
base,
recipient,
false,
price <= (orderData.lmp * (DENOM - orderData.spreadLimit)) / DENOM
? (orderData.lmp * (DENOM - orderData.spreadLimit)) / DENOM
: price,
n,
orderHistoryId
);
// reuse price variable for make price
(price, orderData.lmp) = _detLimitSellMakePrice(
orderData.pair,
price,
orderData.bidHead,
orderData.askHead,
orderData.spreadLimit
);
orderData.makeId = _detMake(
base,
quote,
orderData.pair,
orderData.withoutFee,
price,
false,
isMaker,
recipient
);
if (orderData.makeId > 0) {
// if made, set last market price to price only if price is lower than lmp
if (price < orderData.lmp) {
IOrderbook(orderData.pair).setLmp(price);
emit NewMarketPrice(orderData.pair, price, false);
}
emit OrderPlaced(
orderData.pair,
orderHistoryId,
orderData.makeId,
recipient,
false,
price,
baseAmount,
orderData.withoutFee
);
result.makePrice = price;
result.placed = orderData.withoutFee;
result.id = orderData.makeId;
} else {
result.makePrice = price;
result.placed = 0;
result.id = 0;
}
return result;
}
/**
* @dev Executes a limit sell order, with spread limit of 5% for price actions.
* places a limit order in the orderbook for selling the base asset for the quote asset at a specified price,
* and makes an order at the limit price.
* @param base The address of the base asset for the trading pair
* @param quote The address of the quote asset for the trading pair
* @param price The price, base/quote regardless of decimals of the assets in the pair represented with 8 decimals (if 1000, base is 1000x quote)
* @param baseAmount The amount of base asset to be used for the limit sell order
* @param isMaker Boolean indicating if an order should be made at the limit price
* @param n The maximum number of orders to match in the orderbook
* @return result Result of the order, makePrice is the next price to be set if placed and id is 0
*/
function limitSell(
address base,
address quote,
uint256 price,
uint256 baseAmount,
bool isMaker,
uint32 n,
address recipient
) external override nonReentrant returns (OrderResult memory result) {
count = _nextHistoryId();
return
_limitSell(
base,
quote,
price,
baseAmount,
isMaker,
n,
recipient,
1
);
}
function _detLimitSellMakePrice(
address orderbook,
uint256 lp,
uint256 bidHead,
uint256 askHead,
uint32 spread
) internal view returns (uint256 price, uint256 lmp) {
uint256 down;
lmp = IOrderbook(orderbook).lmp();
if (askHead == 0 && bidHead == 0) {
if (lmp != 0) {
down = (lmp * (DENOM - spread)) / DENOM;
return (lp <= down ? down : lp, lmp);
}
return (lp, lmp);
} else if (askHead == 0 && bidHead != 0) {
if (lmp != 0) {
down = (lmp * (DENOM - spread)) / DENOM;
down = lp <= down ? down : lp;
return (down <= bidHead ? bidHead : down, lmp);
}
down = (bidHead * (DENOM - spread)) / DENOM;
down = lp <= down ? down : lp;
return (down <= bidHead ? bidHead : down, lmp);
} else if (askHead != 0 && bidHead == 0) {
if (lmp != 0) {
down = (lmp * (DENOM - spread)) / DENOM;
return (lp <= down ? down : lp, lmp);
}
down = (askHead * (DENOM - spread)) / DENOM;
return (lp <= down ? down : lp, lmp);
} else {
if (lmp != 0) {
down = (lmp * (DENOM - spread)) / DENOM;
return (lp <= down ? down : lp, lmp);
}
// lower limit price on sell cannot be lower than bid head price
return (down <= bidHead ? bidHead : lp, lmp);
}
}
/**
* @dev Executes a limit buy order, with spread limit of 5% for price actions.
* places a limit order in the orderbook for buying the base asset using the quote asset at a specified price,
* and make an order at the limit price with quote asset as native Ethereum(or network currencies).
* @param base The address of the base asset for the trading pair
* @param isMaker Boolean indicating if a order should be made at the market price in orderbook
* @param n The maximum number of orders to match in the orderbook
* @param recipient The address of the recipient to receive traded asset and claim ownership of made order
* @return result Result of the order
*/
function limitBuyETH(
address base,
uint256 price,
bool isMaker,
uint32 n,
address recipient
) external payable returns (OrderResult memory result) {
IWETH(WETH).deposit{value: msg.value}();
count = _nextHistoryId();
return
_limitBuy(
base,
WETH,
price,
msg.value,
isMaker,
n,
recipient,
count
);
}
/**
* @dev Executes a limit sell order, with spread limit of 5% for price actions.
* places a limit order in the orderbook for selling the base asset for the quote asset at a specified price,
* and makes an order at the limit price with base asset as native Ethereum(or network currencies).
* @param quote The address of the quote asset for the trading pair
* @param isMaker Boolean indicating if an order should be made at the market price in orderbook
* @param n The maximum number of orders to match in the orderbook
* @param recipient The address of the recipient to receive traded asset and claim ownership of made order
* @return result Result of the order
*/
function limitSellETH(
address quote,
uint256 price,
bool isMaker,
uint32 n,
address recipient
) external payable returns (OrderResult memory result) {
IWETH(WETH).deposit{value: msg.value}();
count = _nextHistoryId();
return
_limitSell(
WETH,
quote,
price,
msg.value,
isMaker,
n,
recipient,
count
);
}
/**
* @dev Creates an orderbook for a new trading pair and returns its address
* @param base The address of the base asset for the trading pair
* @param quote The address of the quote asset for the trading pair
* @param listingPrice The initial market price for the trading pair
* @param listingDate The listing Date for the trading pair
* @return book The address of the newly created orderbook
*/
function addPairETH(
address base,
address quote,
uint256 listingPrice,
uint256 listingDate
) external payable override returns (address book) {
IWETH(WETH).deposit{value: msg.value}();
return addPair(base, quote, listingPrice, listingDate, WETH);
}
/**
* @dev Creates an orderbook for a new trading pair and returns its address
* @param base The address of the base asset for the trading pair
* @param quote The address of the quote asset for the trading pair
* @param listingPrice The initial market price for the trading pair
* @param listingDate The listing Date for the trading pair
* @return pair The address of the newly created orderbook
*/
function addPair(
address base,
address quote,
uint256 listingPrice,
uint256 listingDate,
address payment
) public override returns (address pair) {
string memory terminalName = _listingDeposit(payment, msg.sender);
// create orderbook for the pair
pair = IOrderbookFactory(orderbookFactory).createBook(base, quote);
IOrderbook(pair).setLmp(listingPrice);
// set buy/sell spread to default suspension rate in basis point(bps)
_setSpread(pair, dfltMktBuy, dfltMktSell, true);
_setSpread(pair, dfltLmtBuy, dfltLmtSEll, false);
_setListingDate(pair, listingDate);
TransferHelper.TokenInfo memory baseInfo = TransferHelper.getTokenInfo(
base
);
TransferHelper.TokenInfo memory quoteInfo = TransferHelper.getTokenInfo(
quote
);
emit PairAdded(
pair,
baseInfo,
quoteInfo,
listingPrice,
listingDate,
terminalName
);
emit NewMarketPrice(pair, listingPrice, true);
return pair;
}
/**
* @dev Update the market price of a trading pair.`
* @param base The address of the base asset for the trading pair
* @param quote The address of the quote asset for the trading pair
* @param listingPrice The initial market price for the trading pair
* @param listingDate The listing Date for the trading pair
*/
function updatePair(
address base,
address quote,
uint256 listingPrice,
uint256 listingDate
) external override returns (address pair) {
// check if the list request is done by
if (!hasRole(MARKET_MAKER_ROLE, _msgSender())) {
revert InvalidRole(MARKET_MAKER_ROLE, _msgSender());
}
// create orderbook for the pair
pair = getPair(base, quote);
IOrderbook(pair).setLmp(listingPrice);
emit PairUpdated(pair, base, quote, listingPrice, listingDate);
emit NewMarketPrice(pair, listingPrice, true);
return pair;
}
function _cancelOrder(
address base,
address quote,
bool isBid,
uint32 orderId,
address sender
) internal returns (uint256) {
address orderbook = IOrderbookFactory(orderbookFactory).getPair(
base,
quote
);
if (orderbook == address(0)) {
revert InvalidPair(base, quote, orderbook);
}
try IOrderbook(orderbook).cancelOrder(isBid, orderId, sender) returns (
uint256 refunded
) {
emit OrderCanceled(orderbook, orderId, isBid, sender, refunded);
return refunded;
} catch {
return 0;
}
}
function _createOrder(
CreateOrderInput memory createOrderData,
uint256 nativeValue
) internal returns (OrderResult memory result, uint256 leftover) {
count = _nextHistoryId();
leftover = nativeValue;
if (createOrderData.isBid) {
if (createOrderData.quote == WETH) {
// Convert ETH to WETH for internal call
IWETH(WETH).deposit{value: createOrderData.amount}();
leftover -= createOrderData.amount;
}
if (createOrderData.isLimit) {
result = _limitBuy(
createOrderData.base,
createOrderData.quote,
createOrderData.price,
createOrderData.amount,
true,
createOrderData.n,
createOrderData.recipient,
count
);
} else {
result = _marketBuy(
createOrderData.base,
createOrderData.quote,
createOrderData.amount,
true,
createOrderData.n,
createOrderData.recipient,
dfltMktBuy,
count
);
}
} else {
if (createOrderData.base == WETH) {
// Convert ETH to WETH for internal call
IWETH(WETH).deposit{value: createOrderData.amount}();
leftover -= createOrderData.amount;
}
if (createOrderData.isLimit) {
result = _limitSell(
createOrderData.base,
createOrderData.quote,
createOrderData.price,
createOrderData.amount,
true,
createOrderData.n,
createOrderData.recipient,
count
);
} else {
result = _marketSell(
createOrderData.base,
createOrderData.quote,
createOrderData.amount,
true,
createOrderData.n,
createOrderData.recipient,
dfltMktSell,
count
);
}
}
return (result, leftover);
}
/**
* @dev Creates an order in an orderbook for the given trading pair. When creating the order using ETH, make sure the total amount and msg.value is same.
* @param createOrderData The input data for creating an order
* @return result The result of the order
*/
function createOrder(
CreateOrderInput memory createOrderData
) public payable override nonReentrant returns (OrderResult memory result) {
uint256 leftover = msg.value;
(result, leftover) = _createOrder(createOrderData, leftover);
if (leftover > 0) {
TransferHelper.safeTransferETH(msg.sender, leftover);
}
return result;
}
function createOrders(
CreateOrderInput[] memory createOrderData
) external payable override returns (OrderResult[] memory results) {
results = new OrderResult[](createOrderData.length);
uint256 leftover = msg.value;
for (uint32 i = 0; i < createOrderData.length; i++) {
(results[i], leftover) = _createOrder(
createOrderData[i],
leftover
);
}
if (leftover > 0) {
TransferHelper.safeTransferETH(msg.sender, leftover);
}
return results;
}
function _updateOrder(
CreateOrderInput memory updateOrderData
) internal returns (OrderResult memory result) {
address orderbook = IOrderbookFactory(orderbookFactory).getPair(
updateOrderData.base,
updateOrderData.quote
);
if (orderbook == address(0)) {
revert InvalidPair(
updateOrderData.base,
updateOrderData.quote,
orderbook
);
}
_cancelOrder(
updateOrderData.base,
updateOrderData.quote,
updateOrderData.isBid,
updateOrderData.orderId,
_msgSender()
);
if (updateOrderData.amount == 0) {
result.makePrice = 0;
result.placed = 0;
result.id = 0;
return result;
}
uint256 leftover = msg.value;
(result, leftover) = _createOrder(updateOrderData, leftover);
if (leftover > 0) {
TransferHelper.safeTransferETH(msg.sender, leftover);
}
return result;
}
function updateOrder(
CreateOrderInput memory updateOrderData
) external nonReentrant returns (OrderResult memory result) {
return _updateOrder(updateOrderData);
}
function updateOrders(
CreateOrderInput[] memory updateOrderData
) external payable returns (OrderResult[] memory results) {
results = new OrderResult[](updateOrderData.length);
for (uint32 i = 0; i < updateOrderData.length; i++) {
results[i] = _updateOrder(updateOrderData[i]);
}
return results;
}
/**
* @dev Cancels an order in an orderbook by the given order ID and order type.
* @param base The address of the base asset for the trading pair
* @param quote The address of the quote asset for the trading pair
* @param isBid Boolean indicating if the order to cancel is an ask order
* @param orderId The ID of the order to cancel
* @return refunded Refunded amount from order
*/
function cancelOrder(
address base,
address quote,
bool isBid,
uint32 orderId
) public nonReentrant returns (uint256) {
return _cancelOrder(base, quote, isBid, orderId, _msgSender());
}
function cancelOrders(
CancelOrderInput[] memory cancelOrderData
) external returns (uint256[] memory refunded) {
refunded = new uint256[](cancelOrderData.length);
for (uint32 i = 0; i < cancelOrderData.length; i++) {
refunded[i] = _cancelOrder(
cancelOrderData[i].base,
cancelOrderData[i].quote,
cancelOrderData[i].isBid,
cancelOrderData[i].orderId,
_msgSender()
);
}
return refunded;
}
/**
* @dev Returns an order in the ask/bid orderbook for the given trading pair with order id.
* @param base The address of the base asset for the trading pair.
* @param quote The address of the quote asset for the trading pair.
* @param isBid Boolean indicating if the orderbook to retrieve orders from is an ask orderbook.
* @param orderId The order id to retrieve.
*/
function getOrder(
address base,
address quote,
bool isBid,
uint32 orderId
) public view override returns (ExchangeOrderbook.Order memory) {
address orderbook = getPair(base, quote);
return IOrderbook(orderbook).getOrder(isBid, orderId);
}
function feeOf(
address base,
address quote,
address account,
bool isMaker
) external view returns (uint32 feeNum) {
if (incentive == address(0x0)) {
return _dfltFee(isMaker);
} else {
try
IProtocol(incentive).feeOf(base, quote, account, isMaker)
returns (uint32 num) {
return num;
} catch {
return _dfltFee(isMaker);
}
}
}
/**
* @dev Returns the address of the orderbook for the given base and quote asset addresses.
* @param base The address of the base asset for the trading pair.
* @param quote The address of the quote asset for the trading pair.
* @return book The address of the orderbook.
*/
function getPair(
address base,
address quote
) public view returns (address book) {
return IOrderbookFactory(orderbookFactory).getPair(base, quote);
}
function heads(
address base,
address quote
) external view returns (uint256 bidHead, uint256 askHead) {
address orderbook = getPair(base, quote);
return IOrderbook(orderbook).heads();
}
function mktPrice(
address base,
address quote
) public view returns (uint256) {
address orderbook = getPair(base, quote);
return IOrderbook(orderbook).mktPrice();
}
/**
* @dev return converted amount from base to quote or vice versa
* @param base address of base asset
* @param quote address of quote asset
* @param amount amount of base or quote asset
* @param isBid if true, amount is quote asset, otherwise base asset
* @return converted converted amount from base to quote or vice versa.
* if true, amount is quote asset, otherwise base asset
* if orderbook does not exist, return 0
*/
function convert(
address base,
address quote,
uint256 amount,
bool isBid
) public view returns (uint256 converted) {
address orderbook = getPair(base, quote);
if (base == quote) {
return amount;
} else if (orderbook == address(0)) {
return 0;
} else {
return IOrderbook(orderbook).assetValue(amount, isBid);
}
}
function _setListingDate(
address book,
uint256 listingDate
) internal returns (bool success) {
listingDates[book] = listingDate;
return true;
}
function _setSpread(
address pair,
uint32 buy,
uint32 sell,
bool isMkt
) internal returns (bool success) {
if (isMkt) {
mktSpreadLimits[pair] = DefaultSpread(buy, sell);
} else {
lmtSpreadLimits[pair] = DefaultSpread(buy, sell);
}
return true;
}
function getSpread(
address pair,
bool isBuy,
bool isMkt
) public view returns (uint32 spreadLimit) {
DefaultSpread memory spread;
spread = isMkt ? mktSpreadLimits[pair] : lmtSpreadLimits[pair];
if (isBuy) {
return spread.buy;
} else {
return spread.sell;
}
}
/**
* @dev Internal function which makes an order on the orderbook.
* @param pair The address of the orderbook contract for the trading pair
* @param withoutFee The remaining amount of the asset after the market order has been executed
* @param price The price, base/quote regardless of decimals of the assets in the pair represented with 8 decimals (if 1000, base is 1000x quote)
* @param isBid Boolean indicating if the order is a buy (false) or a sell (true)
* @param recipient The address of the recipient to receive traded asset and claim ownership of made order
*/
function _makeOrder(
address pair,
uint256 withoutFee,
uint256 price,
bool isBid,
address recipient
) internal returns (uint32 id) {
bool foundDmt;
// create order
if (isBid) {
(id, foundDmt) = IOrderbook(pair).placeBid(
recipient,
price,
withoutFee
);
} else {
(id, foundDmt) = IOrderbook(pair).placeAsk(
recipient,
price,
withoutFee
);
}
if (foundDmt) {
// emit canceling dormant order
ExchangeOrderbook.Order memory order = IOrderbook(pair).removeDmt(
isBid
);
emit OrderCanceled(
pair,
id,
isBid,
order.owner,
order.depositAmount
);
}
return id;
}
/**
* @dev Match bid if `isBid` is true, match ask if `isBid` is false.
*/
function _matchAt(
MatchAtInput memory matchAtInput
) internal returns (uint256 remaining, uint32 k) {
if (matchAtInput.n > maxMatches) {
revert TooManyMatches(matchAtInput.n);
}
remaining = matchAtInput.amount;
while (
remaining > 0 &&
!IOrderbook(matchAtInput.pair).isEmpty(
!matchAtInput.isBid,
matchAtInput.price
) &&
matchAtInput.i < matchAtInput.n
) {
// fpop OrderLinkedList by price, if ask you get bid order, if bid you get ask order. Get quote asset on bid order on buy, base asset on ask order on sell
(uint32 orderId, uint256 required, bool clear) = IOrderbook(
matchAtInput.pair
).fpop(!matchAtInput.isBid, matchAtInput.price, remaining);
// order exists, and amount is not 0
if (remaining <= required) {
// execute order
TransferHelper.safeTransfer(
matchAtInput.give,
matchAtInput.pair,
remaining
);
OrderMatch memory orderMatch = IOrderbook(matchAtInput.pair)
.execute(
orderId,
!matchAtInput.isBid,
matchAtInput.recipient,
remaining,
clear
);
// emit event order matched
emit OrderMatched(
matchAtInput.pair,
matchAtInput.orderHistoryId,
orderId,
matchAtInput.isBid,
matchAtInput.price,
matchAtInput.total,
clear,
orderMatch
);
// end loop as remaining is 0
return (0, matchAtInput.n);
}
// order is null
else if (required == 0) {
++matchAtInput.i;
continue;
}
// remaining >= depositAmount
else {
remaining -= required;
TransferHelper.safeTransfer(
matchAtInput.give,
matchAtInput.pair,
required
);
IMatchingEngine.OrderMatch memory orderMatch = IOrderbook(
matchAtInput.pair
).execute(
orderId,
!matchAtInput.isBid,
matchAtInput.recipient,
required,
clear
);
// emit event order matched
emit OrderMatched(
matchAtInput.pair,
matchAtInput.orderHistoryId,
orderId,
matchAtInput.isBid,
matchAtInput.price,
matchAtInput.total,
clear,
orderMatch
);
++matchAtInput.i;
}
}
k = matchAtInput.i;
return (remaining, k);
}
/**
* @dev Executes limit order by matching orders in the orderbook based on the provided limit price.
* @param pair The address of the orderbook to execute the limit order on.
* @param amount The amount of asset to trade.
* @param give The address of the asset to be traded.
* @param recipient The address to receive asset after matching a trade
* @param isBid True if the order is an ask (sell) order, false if it is a bid (buy) order.
* @param limitPrice The maximum price at which the order can be executed.
* @param n The maximum number of matches to execute.
* @return remaining The remaining amount of asset that was not traded.
*/
function _limitOrder(
address pair,
uint256 amount,
address give,
address recipient,
bool isBid,
uint256 limitPrice,
uint32 n,
uint16 orderHistoryId
) internal returns (uint256 remaining, uint256 bidHead, uint256 askHead) {
remaining = amount;
uint256 lmp = IOrderbook(pair).lmp();
bidHead = IOrderbook(pair).clearEmptyHead(true);
askHead = IOrderbook(pair).clearEmptyHead(false);
uint32 i = 0;
// In LimitBuy
if (isBid) {
if (lmp != 0) {
if (askHead != 0 && limitPrice < askHead) {
return (remaining, bidHead, askHead);
} else if (askHead == 0) {
return (remaining, bidHead, askHead);
}
}
// check if there is any matching ask order until matching ask order price is lower than the limit bid Price
while (
remaining > 0 && askHead != 0 && askHead <= limitPrice && i < n
) {
lmp = askHead;
(remaining, i) = _matchAt(
MatchAtInput({
pair: pair,
give: give,
recipient: recipient,
isBid: isBid,
amount: remaining,
total: amount,
price: askHead,
i: i,
n: n,
orderHistoryId: orderHistoryId
})
);
// i == 0 when orders are all empty and only head price is left
askHead = i == 0 ? 0 : IOrderbook(pair).clearEmptyHead(false);
}
// update heads
bidHead = IOrderbook(pair).clearEmptyHead(true);
}
// In LimitSell
else {
// check limit ask price is within 20% spread of last matched price
if (lmp != 0) {
if (bidHead != 0 && limitPrice > bidHead) {
return (remaining, bidHead, askHead);
} else if (bidHead == 0) {
return (remaining, bidHead, askHead);
}
}
while (
remaining > 0 && bidHead != 0 && bidHead >= limitPrice && i < n
) {
lmp = bidHead;
(remaining, i) = _matchAt(
MatchAtInput({
pair: pair,
give: give,
recipient: recipient,
isBid: isBid,
amount: remaining,
total: amount,
price: bidHead,
i: i,
n: n,
orderHistoryId: orderHistoryId
})
);
// i == 0 when orders are all empty and only head price is left
bidHead = i == 0 ? 0 : IOrderbook(pair).clearEmptyHead(true);
}
// update heads
askHead = IOrderbook(pair).clearEmptyHead(false);
}
// set new market price as the orders are matched
if (lmp != 0) {
IOrderbook(pair).setLmp(lmp);
emit NewMarketPrice(pair, lmp, isBid);
}
return (remaining, bidHead, askHead); // return bidHead, and askHead
}
/**
* @dev Determines if an order can be made at the market price,
* and if so, makes the an order on the orderbook.
* If an order cannot be made, transfers the remaining asset to either the orderbook or the user.
* @param base The address of the base asset for the trading pair
* @param quote The address of the quote asset for the trading pair
* @param pair The address of the orderbook contract for the trading pair
* @param remaining The remaining amount of the asset after the market order has been taken
* @param price The price used to determine if an order can be made
* @param isBid Boolean indicating if the order was a buy (true) or a sell (false)
* @param isMaker Boolean indicating if an order is for storing in orderbook or just take profit after matching trades
* @param recipient The address to receive asset after matching a trade and making an order
* @return id placed order id
*/
function _detMake(
address base,
address quote,
address pair,
uint256 remaining,
uint256 price,
bool isBid,
bool isMaker,
address recipient
) internal returns (uint32 id) {
if (remaining > 0) {
address stopTo = isMaker ? pair : recipient;
TransferHelper.safeTransfer(
isBid ? quote : base,
stopTo,
remaining
);
if (isMaker) {
id = _makeOrder(pair, remaining, price, isBid, recipient);
return id;
}
}
}
/**
* @dev Deposit amount of asset to the contract with the given asset information and subtracts the fee.
* @param base The address of the base asset.
* @param quote The address of the quote asset.
* @param amount The amount of asset to deposit.
* @param isBid Whether it is an ask order or not.
* If ask, the quote asset is transferred to the contract.
* @return withoutFee The amount of asset without the fee.
* @return pair The address of the orderbook for the given asset pair.
*/
function _deposit(
address base,
address quote,
uint256 price,
uint256 amount,
bool isBid
) internal returns (uint256 withoutFee, address pair, uint256 lmp) {
// check if amount is zero
if (amount == 0) {
revert AmountIsZero();
}
// get orderbook address from the base and quote asset
pair = getPair(base, quote);
// check infalid pair
if (pair == address(0)) {
revert InvalidPair(base, quote, pair);
}
// check if the pair is listed
if (listingDates[pair] > block.timestamp) {
revert PairNotListedYet(
base,
quote,
listingDates[pair],
block.timestamp
);
}
// check if amount is valid in case of both market and limit
uint256 converted = _convert(pair, price, amount, !isBid);
uint256 minRequired = _convert(pair, price, 1, !isBid);
if (converted <= minRequired) {
revert OrderSizeTooSmall(converted, minRequired);
}
if (isBid) {
// transfer input asset give user to this contract
if (quote != WETH) {
TransferHelper.safeTransferFrom(
quote,
msg.sender,
address(this),
amount
);
} else {
if (msg.value == 0) {
TransferHelper.safeTransferFrom(
quote,
msg.sender,
address(this),
amount
);
}
}
} else {
// transfer input asset give user to this contract
if (base != WETH) {
TransferHelper.safeTransferFrom(
base,
msg.sender,
address(this),
amount
);
} else {
if (msg.value == 0) {
TransferHelper.safeTransferFrom(
quote,
msg.sender,
address(this),
amount
);
}
}
}
lmp = IOrderbook(pair).lmp();
return (amount, pair, lmp);
}
/**
* @dev Deposit amount of asset to the contract with the given asset information and subtracts the fee.
* @param payment The address of the payment asset.
* @param sender The address of the sender.
*/
function _listingDeposit(
address payment,
address sender
) internal returns (string memory terminalName) {
// check if the sender is admin
if (hasRole(MARKET_MAKER_ROLE, sender)) {
return "standard";
}
// check if the sender is supported terminal, only terminals can list pairs
terminalName = IProtocol(incentive).terminalName(sender);
if (keccak256(bytes(terminalName)) == keccak256(bytes(""))) {
revert InvalidTerminal(msg.sender);
}
uint256 amount = IOrderbookFactory(orderbookFactory).getListingCost(
terminalName,
payment
);
// check if amount is zero
if (amount == 0) {
revert AmountIsZero();
}
if (payment != WETH) {
TransferHelper.safeTransferFrom(
payment,
msg.sender,
address(this),
amount
);
}
TransferHelper.safeTransfer(payment, feeTo, amount);
return terminalName;
}
function _dfltFee(bool isMaker) internal view returns (uint32) {
return isMaker ? defaultMakerFee : defaultTakerFee;
}
/**
* @dev return converted amount from base to quote or vice versa
* @param pair address of orderbook
* @param price price of base/quote regardless of decimals of the assets in the pair represented with 8 decimals (if 1000, base is 1000x quote) proposed by a trader
* @param amount amount of base or quote asset
* @param isBid if true, amount is quote asset, otherwise base asset
* @return converted converted amount from base to quote or vice versa.
* if true, amount is quote asset, otherwise base asset
* if orderbook does not exist, return 0
*/
function _convert(
address pair,
uint256 price,
uint256 amount,
bool isBid
) internal view returns (uint256 converted) {
if (pair == address(0)) {
return 0;
} else {
return
price == 0
? IOrderbook(pair).assetValue(amount, isBid)
: IOrderbook(pair).convert(price, amount, isBid);
}
}
}
Other files
lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/access/AccessControl.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.3.0) (access/AccessControl.sol) pragma solidity ^0.8.20; import {IAccessControl} from "./IAccessControl.sol"; import {Context} from "../utils/Context.sol"; import {IERC165, ERC165} from "../utils/introspection/ERC165.sol"; /** * @dev Contract module that allows children to implement role-based access * control mechanisms. This is a lightweight version that doesn't allow enumerating role * members except through off-chain means by accessing the contract event logs. Some * applications may benefit from on-chain enumerability, for those cases see * {AccessControlEnumerable}. * * Roles are referred to by their `bytes32` identifier. These should be exposed * in the external API and be unique. The best way to achieve this is by * using `public constant` hash digests: * * ```solidity * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); * ``` * * Roles can be used to represent a set of permissions. To restrict access to a * function call, use {hasRole}: * * ```solidity * function foo() public { * require(hasRole(MY_ROLE, msg.sender)); * ... * } * ``` * * Roles can be granted and revoked dynamically via the {grantRole} and * {revokeRole} functions. Each role has an associated admin role, and only * accounts that have a role's admin role can call {grantRole} and {revokeRole}. * * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means * that only accounts with this role will be able to grant or revoke other * roles. More complex role relationships can be created by using * {_setRoleAdmin}. * * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to * grant and revoke this role. Extra precautions should be taken to secure * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules} * to enforce additional security measures for this role. */ abstract contract AccessControl is Context, IAccessControl, ERC165 { struct RoleData { mapping(address account => bool) hasRole; bytes32 adminRole; } mapping(bytes32 role => RoleData) private _roles; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; /** * @dev Modifier that checks that an account has a specific role. Reverts * with an {AccessControlUnauthorizedAccount} error including the required role. */ modifier onlyRole(bytes32 role) { _checkRole(role); _; } /// @inheritdoc IERC165 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId); } /** * @dev Returns `true` if `account` has been granted `role`. */ function hasRole(bytes32 role, address account) public view virtual returns (bool) { return _roles[role].hasRole[account]; } /** * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()` * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier. */ function _checkRole(bytes32 role) internal view virtual { _checkRole(role, _msgSender()); } /** * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account` * is missing `role`. */ function _checkRole(bytes32 role, address account) internal view virtual { if (!hasRole(role, account)) { revert AccessControlUnauthorizedAccount(account, role); } } /** * @dev Returns the admin role that controls `role`. See {grantRole} and * {revokeRole}. * * To change a role's admin, use {_setRoleAdmin}. */ function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) { return _roles[role].adminRole; } /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. * * Requirements: * * - the caller must have ``role``'s admin role. * * May emit a {RoleGranted} event. */ function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { _grantRole(role, account); } /** * @dev Revokes `role` from `account`. * * If `account` had been granted `role`, emits a {RoleRevoked} event. * * Requirements: * * - the caller must have ``role``'s admin role. * * May emit a {RoleRevoked} event. */ function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { _revokeRole(role, account); } /** * @dev Revokes `role` from the calling account. * * Roles are often managed via {grantRole} and {revokeRole}: this function's * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * * If the calling account had been revoked `role`, emits a {RoleRevoked} * event. * * Requirements: * * - the caller must be `callerConfirmation`. * * May emit a {RoleRevoked} event. */ function renounceRole(bytes32 role, address callerConfirmation) public virtual { if (callerConfirmation != _msgSender()) { revert AccessControlBadConfirmation(); } _revokeRole(role, callerConfirmation); } /** * @dev Sets `adminRole` as ``role``'s admin role. * * Emits a {RoleAdminChanged} event. */ function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { bytes32 previousAdminRole = getRoleAdmin(role); _roles[role].adminRole = adminRole; emit RoleAdminChanged(role, previousAdminRole, adminRole); } /** * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted. * * Internal function without access restriction. * * May emit a {RoleGranted} event. */ function _grantRole(bytes32 role, address account) internal virtual returns (bool) { if (!hasRole(role, account)) { _roles[role].hasRole[account] = true; emit RoleGranted(role, account, _msgSender()); return true; } else { return false; } } /** * @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked. * * Internal function without access restriction. * * May emit a {RoleRevoked} event. */ function _revokeRole(bytes32 role, address account) internal virtual returns (bool) { if (hasRole(role, account)) { _roles[role].hasRole[account] = false; emit RoleRevoked(role, account, _msgSender()); return true; } else { return false; } } }lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/access/IAccessControl.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.3.0) (access/IAccessControl.sol) pragma solidity >=0.8.4; /** * @dev External interface of AccessControl declared to support ERC-165 detection. */ interface IAccessControl { /** * @dev The `account` is missing a role. */ error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); /** * @dev The caller of a function is not the expected one. * * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}. */ error AccessControlBadConfirmation(); /** * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` * * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite * {RoleAdminChanged} not being emitted to signal this. */ event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); /** * @dev Emitted when `account` is granted `role`. * * `sender` is the account that originated the contract call. This account bears the admin role (for the granted role). * Expected in cases where the role was granted using the internal {AccessControl-_grantRole}. */ event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); /** * @dev Emitted when `account` is revoked `role`. * * `sender` is the account that originated the contract call: * - if using `revokeRole`, it is the admin role bearer * - if using `renounceRole`, it is the role bearer (i.e. `account`) */ event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); /** * @dev Returns `true` if `account` has been granted `role`. */ function hasRole(bytes32 role, address account) external view returns (bool); /** * @dev Returns the admin role that controls `role`. See {grantRole} and * {revokeRole}. * * To change a role's admin, use {AccessControl-_setRoleAdmin}. */ function getRoleAdmin(bytes32 role) external view returns (bytes32); /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function grantRole(bytes32 role, address account) external; /** * @dev Revokes `role` from `account`. * * If `account` had been granted `role`, emits a {RoleRevoked} event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function revokeRole(bytes32 role, address account) external; /** * @dev Revokes `role` from the calling account. * * Roles are often managed via {grantRole} and {revokeRole}: this function's * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * * If the calling account had been granted `role`, emits a {RoleRevoked} * event. * * Requirements: * * - the caller must be `callerConfirmation`. */ function renounceRole(bytes32 role, address callerConfirmation) external; }lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Context.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) pragma solidity ^0.8.20; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } function _contextSuffixLength() internal view virtual returns (uint256) { return 0; } }lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol) pragma solidity ^0.8.20; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at, * consider using {ReentrancyGuardTransient} instead. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant NOT_ENTERED = 1; uint256 private constant ENTERED = 2; uint256 private _status; /** * @dev Unauthorized reentrant call. */ error ReentrancyGuardReentrantCall(); constructor() { _status = NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be NOT_ENTERED if (_status == ENTERED) { revert ReentrancyGuardReentrantCall(); } // Any calls to nonReentrant after this point will fail _status = ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == ENTERED; } }lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol) pragma solidity ^0.8.20; import {IERC165} from "./IERC165.sol"; /** * @dev Implementation of the {IERC165} interface. * * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check * for the additional interface id that will be supported. For example: * * ```solidity * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); * } * ``` */ abstract contract ERC165 is IERC165 { /// @inheritdoc IERC165 function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { return interfaceId == type(IERC165).interfaceId; } }lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol) pragma solidity >=0.4.16; /** * @dev Interface of the ERC-165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[ERC]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }node_modules/@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol
// SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.0; // interfaces import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; /** * @title The interface for ERC725Y sub-standard, a generic data key/value store. * @dev ERC725Y provides the ability to set arbitrary data key/value pairs that can be changed over time. * It is intended to standardise certain data key/value pairs to allow automated read and writes from/to the contract storage. */ interface IERC725Y is IERC165 { /** * @notice The following data key/value pair has been changed in the ERC725Y storage: Data key: `dataKey`, data value: `dataValue`. * @dev Emitted when data at a specific `dataKey` was changed to a new value `dataValue`. * @param dataKey The data key for which a bytes value is set. * @param dataValue The value to set for the given data key. */ event DataChanged(bytes32 indexed dataKey, bytes dataValue); /** * @notice Reading the ERC725Y storage for data key `dataKey` returned the following value: `dataValue`. * @dev Get in the ERC725Y storage the bytes data stored at a specific data key `dataKey`. * @param dataKey The data key for which to retrieve the value. * @return dataValue The bytes value stored under the specified data key. */ function getData( bytes32 dataKey ) external view returns (bytes memory dataValue); /** * @notice Reading the ERC725Y storage for data keys `dataKeys` returned the following values: `dataValues`. * @dev Get in the ERC725Y storage the bytes data stored at multiple data keys `dataKeys`. * @param dataKeys The array of keys which values to retrieve * @return dataValues The array of data stored at multiple keys */ function getDataBatch( bytes32[] memory dataKeys ) external view returns (bytes[] memory dataValues); /** * @notice Setting the following data key value pair in the ERC725Y storage. Data key: `dataKey`, data value: `dataValue`. * * @dev Sets a single bytes value `dataValue` in the ERC725Y storage for a specific data key `dataKey`. * The function is marked as payable to enable flexibility on child contracts. For instance to implement * a fee mechanism for setting specific data. * * @param dataKey The data key for which to set a new value. * @param dataValue The new bytes value to set. */ function setData(bytes32 dataKey, bytes memory dataValue) external payable; /** * @notice Setting the following data key value pairs in the ERC725Y storage. Data keys: `dataKeys`, data values: `dataValues`. * * @dev Batch data setting function that behaves the same as {setData} but allowing to set multiple data key/value pairs in the ERC725Y storage in the same transaction. * * @param dataKeys An array of data keys to set bytes values for. * @param dataValues An array of bytes values to set for each `dataKeys`. */ function setDataBatch( bytes32[] memory dataKeys, bytes[] memory dataValues ) external payable; }node_modules/@lukso/lsp7-contracts/contracts/ILSP7DigitalAsset.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; /** * @title Interface of the LSP7 - Digital Asset standard, a fungible digital asset. */ interface ILSP7DigitalAsset is IERC165, IERC725Y { // --- Events /** * @dev Emitted when the `from` transferred successfully `amount` of tokens to `to`. * @param operator The address of the operator that executed the transfer. * @param from The address which tokens were sent from (balance decreased by `-amount`). * @param to The address that received the tokens (balance increased by `+amount`). * @param amount The amount of tokens transferred. * @param force if the transferred enforced the `to` recipient address to be a contract that implements the LSP1 standard or not. * @param data Any additional data included by the caller during the transfer, and sent in the LSP1 hooks to the `from` and `to` addresses. */ event Transfer( address indexed operator, address indexed from, address indexed to, uint256 amount, bool force, bytes data ); /** * @dev Emitted when `tokenOwner` enables `operator` for `amount` tokens. * @param operator The address authorized as an operator * @param tokenOwner The token owner * @param amount The amount of tokens `operator` address has access to from `tokenOwner` * @param operatorNotificationData The data to notify the operator about via LSP1. */ event OperatorAuthorizationChanged( address indexed operator, address indexed tokenOwner, uint256 indexed amount, bytes operatorNotificationData ); /** * @dev Emitted when `tokenOwner` disables `operator` for `amount` tokens and set its {`authorizedAmountFor(...)`} to `0`. * @param operator The address revoked from operating * @param tokenOwner The token owner * @param notified Bool indicating whether the operator has been notified or not * @param operatorNotificationData The data to notify the operator about via LSP1. */ event OperatorRevoked( address indexed operator, address indexed tokenOwner, bool indexed notified, bytes operatorNotificationData ); // --- Token queries /** * @dev Returns the number of decimals used to get its user representation. * If the asset contract has been set to be non-divisible via the `isNonDivisible_` parameter in * the `constructor`, the decimals returned wiil be `0`. Otherwise `18` is the common value. * * @custom:notice This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {balanceOf} and {transfer}. * * @return the number of decimals. If `0` is returned, the asset is non-divisible. */ function decimals() external view returns (uint8); /** * @dev Returns the number of existing tokens that have been minted in this contract. * @return The number of existing tokens. */ function totalSupply() external view returns (uint256); // --- Token owner queries /** * @dev Get the number of tokens owned by `tokenOwner`. * If the token is divisible (the {decimals} function returns `18`), the amount returned should be divided * by 1e18 to get a better picture of the actual balance of the `tokenOwner`. * * _Example:_ * * ``` * balanceOf(someAddress) -> 42_000_000_000_000_000_000 / 1e18 = 42 tokens * ``` * * @param tokenOwner The address of the token holder to query the balance for. * @return The amount of tokens owned by `tokenOwner`. */ function balanceOf(address tokenOwner) external view returns (uint256); // --- Operator functionality /** * @dev Sets an `amount` of tokens that an `operator` has access from the caller's balance (allowance). See {authorizedAmountFor}. * Notify the operator based on the LSP1-UniversalReceiver standard * * @param operator The address to authorize as an operator. * @param amount The allowance amount of tokens operator has access to. * @param operatorNotificationData The data to notify the operator about via LSP1. * * @custom:requirements * - `operator` cannot be the zero address. * * @custom:events {OperatorAuthorizationChanged} when allowance is given to a new operator or * an existing operator's allowance is updated. */ function authorizeOperator( address operator, uint256 amount, bytes memory operatorNotificationData ) external; /** * @dev Enables `tokenOwner` to remove `operator` for its tokens, disallowing it to send any amount of tokens on its behalf. * This function also allows the `operator` to remove itself if it is the caller of this function * * @param operator The address to revoke as an operator. * @param tokenOwner The address of the token owner. * @param notify Boolean indicating whether to notify the operator or not. * @param operatorNotificationData The data to notify the operator about via LSP1. * * @custom:requirements * - caller MUST be `operator` or `tokenOwner` * - `operator` cannot be the zero address. * * @custom:events {OperatorRevoked} event with address of the operator being revoked for the caller (token holder). */ function revokeOperator( address operator, address tokenOwner, bool notify, bytes memory operatorNotificationData ) external; /** * @custom:info This function in the LSP7 contract can be used as a prevention mechanism * against double spending allowance vulnerability. * * @notice Increase the allowance of `operator` by +`addedAmount` * * @dev Atomically increases the allowance granted to `operator` by the caller. * This is an alternative approach to {authorizeOperator} that can be used as a mitigation * for the double spending allowance problem. * Notify the operator based on the LSP1-UniversalReceiver standard * * @param operator The operator to increase the allowance for `msg.sender` * @param addedAmount The additional amount to add on top of the current operator's allowance * * @custom:requirements * - `operator` cannot be the same address as `msg.sender` * - `operator` cannot be the zero address. * * @custom:events {OperatorAuthorizationChanged} indicating the updated allowance */ function increaseAllowance( address operator, uint256 addedAmount, bytes memory operatorNotificationData ) external; /** * @custom:info This function in the LSP7 contract can be used as a prevention mechanism * against the double spending allowance vulnerability. * * @notice Decrease the allowance of `operator` by -`subtractedAmount` * * @dev Atomically decreases the allowance granted to `operator` by the caller. * This is an alternative approach to {authorizeOperator} that can be used as a mitigation * for the double spending allowance problem. * Notify the operator based on the LSP1-UniversalReceiver standard * * @custom:events * - {OperatorAuthorizationChanged} event indicating the updated allowance after decreasing it. * - {OperatorRevoked} event if `subtractedAmount` is the full allowance, * indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. * * @param operator The operator to decrease allowance for `msg.sender` * @param tokenOwner The address of the token owner. * @param subtractedAmount The amount to decrease by in the operator's allowance. * * @custom:requirements * - `operator` cannot be the zero address. * - `operator` must have allowance for the caller of at least `subtractedAmount`. */ function decreaseAllowance( address operator, address tokenOwner, uint256 subtractedAmount, bytes memory operatorNotificationData ) external; /** * @dev Get the amount of tokens `operator` address has access to from `tokenOwner`. * Operators can send and burn tokens on behalf of their owners. * * @param operator The operator's address to query the authorized amount for. * @param tokenOwner The token owner that `operator` has allowance on. * * @return The amount of tokens the `operator`'s address has access on the `tokenOwner`'s balance. * * @custom:info If this function is called with the same address for `operator` and `tokenOwner`, it will simply read the `tokenOwner`'s balance * (since a tokenOwner is its own operator). */ function authorizedAmountFor( address operator, address tokenOwner ) external view returns (uint256); /** * @dev Returns all `operator` addresses that are allowed to transfer or burn on behalf of `tokenOwner`. * * @param tokenOwner The token owner to get the operators for. * @return An array of operators allowed to transfer or burn tokens on behalf of `tokenOwner`. */ function getOperatorsOf( address tokenOwner ) external view returns (address[] memory); // --- Transfer functionality /** * @dev Transfers an `amount` of tokens from the `from` address to the `to` address and notify both sender and recipients via the LSP1 {`universalReceiver(...)`} function. * If the tokens are transferred by an operator on behalf of a token holder, the allowance for the operator will be decreased by `amount` once the token transfer * has been completed (See {authorizedAmountFor}). * * @param from The sender address. * @param to The recipient address. * @param amount The amount of tokens to transfer. * @param force When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. * @param data Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. * * @custom:requirements * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `from` and `to` cannot be the same address (`from` cannot send tokens to itself). * - `from` MUST have a balance of at least `amount` tokens. * - If the caller is not `from`, it must be an operator for `from` with an allowance of at least `amount` of tokens. * * @custom:events * - {Transfer} event when tokens get successfully transferred. * - if the transfer is triggered by an operator, either the {OperatorAuthorizationChanged} event will be emitted with the updated allowance or the {OperatorRevoked} * event will be emitted if the operator has no more allowance left. * * @custom:hint The `force` parameter **MUST be set to `true`** to transfer tokens to Externally Owned Accounts (EOAs) * or contracts that do not implement the LSP1 Universal Receiver Standard. Otherwise the function will revert making the transfer fail. * * @custom:info if the `to` address is a contract that implements LSP1, it will always be notified via its `universalReceiver(...)` function, regardless if `force` is set to `true` or `false`. * * @custom:warning Be aware that when either the sender or the recipient can have logic that revert in their `universalReceiver(...)` function when being notified. * This even if the `force` was set to `true`. */ function transfer( address from, address to, uint256 amount, bool force, bytes memory data ) external; /** * @dev Same as {`transfer(...)`} but transfer multiple tokens based on the arrays of `from`, `to`, `amount`. * * @custom:info If any transfer in the batch fail or revert, the whole call will revert. * * @param from An array of sending addresses. * @param to An array of receiving addresses. * @param amount An array of amount of tokens to transfer for each `from -> to` transfer. * @param force For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. * @param data An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. * * @custom:requirements * - `from`, `to`, `amount` lists MUST be of the same length. * - no values in `from` can be the zero address. * - no values in `to` can be the zero address. * - each `amount` tokens MUST be owned by `from`. * - for each transfer, if the caller is not `from`, it MUST be an operator for `from` with access to at least `amount` tokens. * * @custom:events {Transfer} event **for each token transfer**. */ function transferBatch( address[] memory from, address[] memory to, uint256[] memory amount, bool[] memory force, bytes[] memory data ) external; /** * @notice Executing the following batch of abi-encoded function calls on the contract: `data`. * * @dev Allows a caller to batch different function calls in one call. Perform a `delegatecall` on self, to call different functions with preserving the context. * @param data An array of ABI encoded function calls to be called on the contract. * @return results An array of abi-encoded data returned by the functions executed. */ function batchCalls( bytes[] calldata data ) external returns (bytes[] memory results); }src/exchange/interfaces/IMatchingEngine.sol
// SPDX-License-Identifier: BUSL-1.1 import {ExchangeOrderbook} from "../libraries/ExchangeOrderbook.sol"; pragma solidity ^0.8.24; interface IMatchingEngine { struct OrderData { /// Amount after removing fee uint256 withoutFee; /// Orderbook contract address address pair; /// Head price on bid orderbook, the highest bid price uint256 bidHead; /// Head price on ask orderbook, the lowest ask price uint256 askHead; /// Market price on pair uint256 lmp; /// Spread(volatility) limit on limit/market | buy/sell for market suspensions(e.g. circuit breaker, tick) uint32 spreadLimit; /// Make order id uint32 makeId; /// Whether an order deposit has been cleared bool clear; } struct DefaultSpread { /// Buy spread limit uint32 buy; /// Sell spread limit uint32 sell; } struct OrderMatch { address sender; address owner; uint256 baseAmount; uint256 quoteAmount; uint256 baseFee; uint256 quoteFee; uint64 tradeId; } struct OrderResult { uint256 makePrice; uint256 placed; uint32 id; } struct CancelOrderInput { address base; address quote; bool isBid; uint32 orderId; } struct CreateOrderInput { address base; address quote; bool isBid; bool isLimit; uint32 orderId; uint256 price; uint256 amount; uint32 n; address recipient; } struct MatchAtInput { address pair; address give; address recipient; bool isBid; uint256 amount; uint256 total; uint256 price; uint32 i; uint32 n; uint16 orderHistoryId; } // admin functions function setFeeTo(address feeTo_) external returns (bool success); function setDefaultFee(bool isMaker, uint32 fee_) external returns (bool success); function setDefaultSpread(uint32 buy, uint32 sell, bool isMkt) external returns (bool success); function setSpread(address base, address quote, uint32 buy, uint32 sell, bool isMkt) external returns (bool success); function updatePair(address base, address quote, uint256 listingPrice, uint256 listingDate) external returns (address pair); // user functions function marketBuy( address base, address quote, uint256 quoteAmount, bool isMaker, uint32 n, address recipient, uint32 slippageLimit ) external returns (OrderResult memory result); function marketSell( address base, address quote, uint256 baseAmount, bool isMaker, uint32 n, address recipient, uint32 slippageLimit ) external returns (OrderResult memory result); function marketBuyETH(address base, bool isMaker, uint32 n, address recipient, uint32 slippageLimit) external payable returns (OrderResult memory result); function marketSellETH(address quote, bool isMaker, uint32 n, address recipient, uint32 slippageLimit) external payable returns (OrderResult memory result); function limitBuy( address base, address quote, uint256 price, uint256 quoteAmount, bool isMaker, uint32 n, address recipient ) external returns (OrderResult memory result); function limitSell( address base, address quote, uint256 price, uint256 baseAmount, bool isMaker, uint32 n, address recipient ) external returns (OrderResult memory result); function limitBuyETH(address base, uint256 price, bool isMaker, uint32 n, address recipient) external payable returns (OrderResult memory result); function limitSellETH(address quote, uint256 price, bool isMaker, uint32 n, address recipient) external payable returns (OrderResult memory result); function addPair(address base, address quote, uint256 listingPrice, uint256 listingDate, address payment) external returns (address pair); function addPairETH(address base, address quote, uint256 listingPrice, uint256 listingDate) external payable returns (address book); function createOrder(CreateOrderInput memory createOrderData) external payable returns (OrderResult memory result); function createOrders(CreateOrderInput[] memory createOrderData) external payable returns (OrderResult[] memory results); function updateOrders(CreateOrderInput[] memory createOrderData) external payable returns (OrderResult[] memory results); function cancelOrder(address base, address quote, bool isBid, uint32 orderId) external returns (uint256 refunded); function cancelOrders(CancelOrderInput[] memory cancelOrders) external returns (uint256[] memory refunded); function getOrder(address base, address quote, bool isBid, uint32 orderId) external view returns (ExchangeOrderbook.Order memory); function getPair(address base, address quote) external view returns (address book); function heads(address base, address quote) external view returns (uint256 bidHead, uint256 askHead); function mktPrice(address base, address quote) external view returns (uint256); function convert(address base, address quote, uint256 amount, bool isBid) external view returns (uint256 converted); function feeTo() external view returns (address); function incentive() external view returns (address); function feeOf(address base, address quote, address account, bool isMaker) external view returns (uint32 feeNum); }src/exchange/interfaces/IOrderbook.sol
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.24; import {IMatchingEngine, ExchangeOrderbook} from "./IMatchingEngine.sol"; interface IOrderbook { function initialize(uint256 id_, address base_, address quote_, address engine_) external; function setLmp(uint256 price) external; function placeAsk(address owner, uint256 price, uint256 amount) external returns (uint32 id, bool foundDmt); function placeBid(address owner, uint256 price, uint256 amount) external returns (uint32 id, bool foundDmt); function removeDmt(bool isBid) external returns (ExchangeOrderbook.Order memory order); function cancelOrder(bool isBid, uint32 orderId, address owner) external returns (uint256 remaining); function execute(uint32 orderId, bool isBid, address sender, uint256 amount, bool clear) external returns (IMatchingEngine.OrderMatch memory orderMatch); function clearEmptyHead(bool isBid) external returns (uint256 head); function fpop(bool isBid, uint256 price, uint256 remaining) external returns (uint32 orderId, uint256 required, bool clear); function getRequired(bool isBid, uint256 price, uint32 orderId) external view returns (uint256 required); function lmp() external view returns (uint256); function heads() external view returns (uint256, uint256); function askHead() external view returns (uint256); function bidHead() external view returns (uint256); function orderHead(bool isBid, uint256 price) external view returns (uint32); function mktPrice() external view returns (uint256); function getPrices(bool isBid, uint32 n) external view returns (uint256[] memory); function nextPrice(bool isBid, uint256 price) external view returns (uint256 next); function nextOrder(bool isBid, uint256 price, uint32 orderId) external view returns (uint32 next); function sfpop(bool isBid, uint256 price, uint32 orderId, bool isHead) external view returns (uint32 id, uint256 required, bool clear); function getPricesPaginated(bool isBid, uint32 start, uint32 end) external view returns (uint256[] memory); function getOrderIds(bool isBid, uint256 price, uint32 n) external view returns (uint32[] memory); function getOrders(bool isBid, uint256 price, uint32 n) external view returns (ExchangeOrderbook.Order[] memory); function getOrdersPaginated(bool isBid, uint256 price, uint32 start, uint32 end) external view returns (ExchangeOrderbook.Order[] memory); function getOrder(bool isBid, uint32 orderId) external view returns (ExchangeOrderbook.Order memory); function getBaseQuote() external view returns (address base, address quote); function assetValue(uint256 amount, bool isBid) external view returns (uint256 converted); function nextMakeId(bool isBid) external view returns (uint32); function isEmpty(bool isBid, uint256 price) external view returns (bool); function convertMarket(uint256 amount, bool isBid) external view returns (uint256 converted); function convert(uint256 price, uint256 amount, bool isBid) external view returns (uint256 converted); }src/exchange/interfaces/IOrderbookFactory.sol
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.24; interface IOrderbookFactory { struct Pair { address base; address quote; } function engine() external view returns (address); function impl() external view returns (address); function createBook(address base_, address quote_) external returns (address pair); function setListingCost(string memory terminal, address payment, uint256 amount) external returns (uint256); function isClone(address vault) external view returns (bool cloned); function getPair(address base, address quote) external view returns (address book); function getPairs(uint256 start, uint256 end) external view returns (Pair[] memory); function getPairsWithIds(uint256[] memory ids) external view returns (Pair[] memory pairs); function getPairNames(uint256 start, uint256 end) external view returns (string[] memory names); function getPairNamesWithIds(uint256[] memory ids) external view returns (string[] memory names); function getBaseQuote(address pair) external view returns (address base, address quote); function getByteCode() external view returns (bytes memory bytecode); function getListingCost(string memory terminal, address payment) external view returns (uint256 amount); }src/exchange/interfaces/IWETH.sol
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.24; interface IWETH { function deposit() external payable; function transfer(address to, uint256 value) external returns (bool); function withdraw(uint256) external; }src/exchange/libraries/ExchangeOrderbook.sol
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.24; library ExchangeOrderbook { // Order struct struct Order { address owner; uint256 price; uint256 depositAmount; } // Order Linked List struct OrderStorage { /// Hashmap-style linked list of prices to route orders // key: price, value: order indices linked hashmap mapping(uint256 => mapping(uint32 => uint32)) list; mapping(uint32 => Order) orders; // Head of the linked list(i.e. lowest ask price / highest bid price) mapping(uint256 => uint32) head; // count of the orders, used for array allocation uint32 count; address engine; Order dormantOrder; } error OrderIdIsZero(uint32 id); error PriceIsZero(uint256 price); // for orders, lower depositAmount are next, higher depositAmount comes first function _insertId(OrderStorage storage self, uint256 price, uint32 id, uint256 amount) internal { uint32 last = 0; uint32 head = self.head[price]; mapping(uint32 => uint32) storage list = self.list[price]; mapping(uint32 => Order) storage orders = self.orders; // insert order to the linked list // if the list is empty if (head == 0 || amount > self.orders[head].depositAmount) { self.head[price] = id; list[id] = head; return; } // Traverse through list until we find the right spot where id's deposit amount is higher than next while (head != 0) { // what if order deposit amount is bigger than the next order's deposit amount? uint32 next = list[head]; if (amount < orders[next].depositAmount) { // Keep traversing head = list[head]; last = next; } else if (amount > orders[next].depositAmount) { // This is either order is cancelled or order is at the end of the list if (orders[next].depositAmount == 0) { // Insert order at the end of the list list[head] = id; list[id] = 0; return; } // Insert order in the middle of the list list[head] = id; list[id] = next; return; } // what if there is same order with same deposit amount? else if (amount == orders[next].depositAmount) { list[id] = list[next]; list[next] = id; return; } } } // pop front function _fpop(OrderStorage storage self, uint256 price) internal returns (uint256) { uint32 first = self.head[price]; if (first == 0) { return 0; } uint32 next = self.list[price][first]; self.head[price] = next; delete self.list[price][first]; return first; } function _createOrder(OrderStorage storage self, address owner, uint256 price, uint256 depositAmount) internal returns (uint32 id, bool foundDmt) { if (price == 0) { revert PriceIsZero(price); } Order memory order = Order({owner: owner, price: price, depositAmount: depositAmount}); // set foundDmt to false by default foundDmt = false; // In order to prevent order overflow, order id must start from 1 self.count = self.count == 0 || self.count == type(uint32).max ? 1 : self.count + 1; // check if the order already exists if (self.orders[self.count].owner != address(0)) { // store canceling order to dormantOrder self.dormantOrder = self.orders[self.count]; // cancel the dormant order and set foundDmt to true _deleteOrder(self, self.count); foundDmt = true; } // insert order self.orders[self.count] = order; return (self.count, foundDmt == true); } function _decreaseOrder(OrderStorage storage self, uint32 id, uint256 amount, uint256 dust, bool clear) internal returns (uint256 sendFund, uint256 deletePrice) { uint256 decreased = self.orders[id].depositAmount < amount ? 0 : self.orders[id].depositAmount - amount; // remove dust if (decreased <= dust || clear) { decreased = self.orders[id].depositAmount; deletePrice = _deleteOrder(self, id); return (decreased, deletePrice); } else { self.orders[id].depositAmount = decreased; return (amount, deletePrice); } } function _deleteOrder(OrderStorage storage self, uint32 id) internal returns (uint256 deletePrice) { uint256 price = self.orders[id].price; uint32 last = 0; uint32 head = self.head[price]; uint32 next; uint16 i; mapping(uint32 => uint32) storage list = self.list[price]; // delete id in the order linked list if (head == id) { self.head[price] = list[head]; delete list[id]; } else { // search for the order id in the linked list while (head != 0) { next = list[head]; if (next == id) { list[head] = list[next]; delete list[id]; break; } last = head; head = next; ++i; } } // delete order delete self.orders[id]; return self.head[price] == 0 ? price : 0; } function _nextMakeId(OrderStorage storage self) internal view returns (uint32) { return self.count == 0 || self.count == type(uint32).max ? 1 : self.count + 1; } // show n order ids at the price in the orderbook function _getOrderIds(OrderStorage storage self, uint256 price, uint32 n) internal view returns (uint32[] memory) { uint32 head = self.head[price]; uint32[] memory orders = new uint32[](n); uint32 i = 0; while (head != 0 && i < n) { orders[i] = head; head = self.list[price][head]; i++; } return orders; } function _getOrders(OrderStorage storage self, uint256 price, uint32 n) internal view returns (Order[] memory) { uint32 head = self.head[price]; Order[] memory orders = new Order[](n); uint32 i = 0; while (head != 0 && i < n) { orders[i] = self.orders[head]; head = self.list[price][head]; i++; } return orders; } function _getOrdersPaginated(OrderStorage storage self, uint256 price, uint32 start, uint32 end) internal view returns (Order[] memory) { uint32 head = self.head[price]; Order[] memory orders = new Order[](end - start); uint32 i = 0; while (head != 0 && i < start) { head = self.list[price][head]; i++; } if (head == 0) { return orders; } while (head != 0 && i < end) { orders[i] = self.orders[head]; head = self.list[price][head]; i++; } return orders; } function _head(OrderStorage storage self, uint256 price) internal view returns (uint32) { return self.head[price]; } function _isEmpty(OrderStorage storage self, uint256 price) internal view returns (bool) { return self.head[price] == 0; } function _next(OrderStorage storage self, uint256 price, uint32 curr) internal view returns (uint32) { return self.list[price][curr]; } function _getOrder(OrderStorage storage self, uint32 id) internal view returns (Order memory) { return self.orders[id]; } }src/exchange/libraries/TransferHelper.sol
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.24; import {ILSP7DigitalAsset} from "@lukso/lsp7-contracts/contracts/ILSP7DigitalAsset.sol"; // helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false library TransferHelper { struct TokenInfo { address token; uint8 decimals; string name; string symbol; uint256 totalSupply; } function safeApprove(address token, address to, uint256 value) internal { // bytes4(keccak256(bytes("approve(address,uint256)"))); (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), "AF"); } function safeTransfer(address token, address to, uint256 value) internal { // bytes4(keccak256(bytes("transfer(address,uint256)"))); (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), "TF"); } function safeTransferFrom(address token, address from, address to, uint256 value) internal { // bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), "TFF"); } function safeTransferETH(address to, uint256 value) internal { (bool success,) = to.call{value: value}(new bytes(0)); require(success, "ETF"); } function name(address token) internal view returns (string memory) { // bytes4(keccak256(bytes("name()"))); (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(0x06fdde03)); require(success, "NF"); return abi.decode(data, (string)); } function symbol(address token) internal view returns (string memory) { // bytes4(keccak256(bytes("symbol()"))); (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(0x95d89b41)); require(success, "SF"); return abi.decode(data, (string)); } function totalSupply(address token) internal view returns (uint256) { // bytes4(keccak256(bytes("totalSupply()"))); (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(0x18160ddd)); require(success, "TSF"); return abi.decode(data, (uint256)); } function decimals(address token) internal view returns (uint8) { // bytes4(keccak256(bytes("decimals()"))); (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(0x313ce567)); require(success, "DF"); return abi.decode(data, (uint8)); } function getTokenInfo(address token) internal view returns (TokenInfo memory tokenInfo) { tokenInfo.token = token; tokenInfo.name = name(token); tokenInfo.symbol = symbol(token); tokenInfo.totalSupply = totalSupply(token); tokenInfo.decimals = decimals(token); return tokenInfo; } function lsp7Transfer(address token, address from, address to, uint256 value) internal { // bytes4(keccak256(bytes("transfer(address,address,uint256,bool,bytes)"))); (bool success, bytes memory data) = token.call(abi.encodeWithSelector(ILSP7DigitalAsset.transfer.selector, from, to, value, true, "")); // Suggest using this function for abi-encoding // (bool success, bytes memory data) = token.call(abi.encodeCall(ILSP7DigitalAsset.transfer, from, to, value, true, "")); require(success && (data.length == 0), "AF"); } }
ABI · 73 entries
json
[
{
"type": "constructor",
"inputs": [],
"stateMutability": "nonpayable"
},
{
"name": "AccessControlBadConfirmation",
"type": "error",
"inputs": []
},
{
"name": "AccessControlUnauthorizedAccount",
"type": "error",
"inputs": [
{
"name": "account",
"type": "address",
"internalType": "address"
},
{
"name": "neededRole",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"name": "AlreadyInitialized",
"type": "error",
"inputs": [
{
"name": "init",
"type": "bool",
"internalType": "bool"
}
]
},
{
"name": "AmountIsZero",
"type": "error",
"inputs": []
},
{
"name": "FactoryNotInitialized",
"type": "error",
"inputs": [
{
"name": "factory",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "InvalidPair",
"type": "error",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "pair",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "InvalidRole",
"type": "error",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "sender",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "InvalidTerminal",
"type": "error",
"inputs": [
{
"name": "terminal",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "OrderSizeTooSmall",
"type": "error",
"inputs": [
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "minRequired",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "PairDoesNotExist",
"type": "error",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "pair",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "PairNotListedYet",
"type": "error",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "listingDate",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "timeNow",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ReentrancyGuardReentrantCall",
"type": "error",
"inputs": []
},
{
"name": "TooManyMatches",
"type": "error",
"inputs": [
{
"name": "n",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ListingCostSet",
"type": "event",
"inputs": [
{
"name": "payment",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "amount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "NewMarketPrice",
"type": "event",
"inputs": [
{
"name": "pair",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "price",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "isBid",
"type": "bool",
"indexed": false,
"internalType": "bool"
}
],
"anonymous": false
},
{
"name": "OrderCanceled",
"type": "event",
"inputs": [
{
"name": "pair",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "id",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "isBid",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "owner",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "amount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "OrderMatched",
"type": "event",
"inputs": [
{
"name": "pair",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "orderHistoryId",
"type": "uint16",
"indexed": false,
"internalType": "uint16"
},
{
"name": "id",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "isBid",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "price",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "total",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "clear",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "orderMatch",
"type": "tuple",
"indexed": false,
"components": [
{
"name": "sender",
"type": "address",
"internalType": "address"
},
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "baseAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "baseFee",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteFee",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "tradeId",
"type": "uint64",
"internalType": "uint64"
}
],
"internalType": "struct IMatchingEngine.OrderMatch"
}
],
"anonymous": false
},
{
"name": "OrderPlaced",
"type": "event",
"inputs": [
{
"name": "pair",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "orderHistoryId",
"type": "uint16",
"indexed": false,
"internalType": "uint16"
},
{
"name": "id",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "owner",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "isBid",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "price",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "withoutFee",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "PairAdded",
"type": "event",
"inputs": [
{
"name": "pair",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "base",
"type": "tuple",
"indexed": false,
"components": [
{
"name": "token",
"type": "address",
"internalType": "address"
},
{
"name": "decimals",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "name",
"type": "string",
"internalType": "string"
},
{
"name": "symbol",
"type": "string",
"internalType": "string"
},
{
"name": "totalSupply",
"type": "uint256",
"internalType": "uint256"
}
],
"internalType": "struct TransferHelper.TokenInfo"
},
{
"name": "quote",
"type": "tuple",
"indexed": false,
"components": [
{
"name": "token",
"type": "address",
"internalType": "address"
},
{
"name": "decimals",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "name",
"type": "string",
"internalType": "string"
},
{
"name": "symbol",
"type": "string",
"internalType": "string"
},
{
"name": "totalSupply",
"type": "uint256",
"internalType": "uint256"
}
],
"internalType": "struct TransferHelper.TokenInfo"
},
{
"name": "listingPrice",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "listingDate",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "supportedTerminals",
"type": "string",
"indexed": false,
"internalType": "string"
}
],
"anonymous": false
},
{
"name": "PairCreate2",
"type": "event",
"inputs": [
{
"name": "deployer",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "bytecode",
"type": "bytes",
"indexed": false,
"internalType": "bytes"
}
],
"anonymous": false
},
{
"name": "PairUpdated",
"type": "event",
"inputs": [
{
"name": "pair",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "base",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"indexed": false,
"internalType": "address"
},
{
"name": "listingPrice",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "listingDate",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "RoleAdminChanged",
"type": "event",
"inputs": [
{
"name": "role",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "previousAdminRole",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "newAdminRole",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
}
],
"anonymous": false
},
{
"name": "RoleGranted",
"type": "event",
"inputs": [
{
"name": "role",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "account",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "sender",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "RoleRevoked",
"type": "event",
"inputs": [
{
"name": "role",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "account",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "sender",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "DEFAULT_ADMIN_ROLE",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"name": "DENOM",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"name": "WETH",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "addPair",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "listingPrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "listingDate",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "payment",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "pair",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "nonpayable"
},
{
"name": "addPairETH",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "listingPrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "listingDate",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "book",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "payable"
},
{
"name": "cancelOrder",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "isBid",
"type": "bool",
"internalType": "bool"
},
{
"name": "orderId",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"name": "cancelOrders",
"type": "function",
"inputs": [
{
"name": "cancelOrderData",
"type": "tuple[]",
"components": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "isBid",
"type": "bool",
"internalType": "bool"
},
{
"name": "orderId",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.CancelOrderInput[]"
}
],
"outputs": [
{
"name": "refunded",
"type": "uint256[]",
"internalType": "uint256[]"
}
],
"stateMutability": "nonpayable"
},
{
"name": "convert",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "isBid",
"type": "bool",
"internalType": "bool"
}
],
"outputs": [
{
"name": "converted",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "createOrder",
"type": "function",
"inputs": [
{
"name": "createOrderData",
"type": "tuple",
"components": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "isBid",
"type": "bool",
"internalType": "bool"
},
{
"name": "isLimit",
"type": "bool",
"internalType": "bool"
},
{
"name": "orderId",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "price",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
}
],
"internalType": "struct IMatchingEngine.CreateOrderInput"
}
],
"outputs": [
{
"name": "result",
"type": "tuple",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult"
}
],
"stateMutability": "payable"
},
{
"name": "createOrders",
"type": "function",
"inputs": [
{
"name": "createOrderData",
"type": "tuple[]",
"components": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "isBid",
"type": "bool",
"internalType": "bool"
},
{
"name": "isLimit",
"type": "bool",
"internalType": "bool"
},
{
"name": "orderId",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "price",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
}
],
"internalType": "struct IMatchingEngine.CreateOrderInput[]"
}
],
"outputs": [
{
"name": "results",
"type": "tuple[]",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult[]"
}
],
"stateMutability": "payable"
},
{
"name": "feeOf",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "account",
"type": "address",
"internalType": "address"
},
{
"name": "isMaker",
"type": "bool",
"internalType": "bool"
}
],
"outputs": [
{
"name": "feeNum",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"name": "feeTo",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "getOrder",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "isBid",
"type": "bool",
"internalType": "bool"
},
{
"name": "orderId",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"components": [
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "price",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "depositAmount",
"type": "uint256",
"internalType": "uint256"
}
],
"internalType": "struct ExchangeOrderbook.Order"
}
],
"stateMutability": "view"
},
{
"name": "getPair",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "book",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "getRoleAdmin",
"type": "function",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"name": "getSpread",
"type": "function",
"inputs": [
{
"name": "pair",
"type": "address",
"internalType": "address"
},
{
"name": "isBuy",
"type": "bool",
"internalType": "bool"
},
{
"name": "isMkt",
"type": "bool",
"internalType": "bool"
}
],
"outputs": [
{
"name": "spreadLimit",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"name": "grantRole",
"type": "function",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "hasRole",
"type": "function",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "heads",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "bidHead",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "askHead",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "incentive",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "initialize",
"type": "function",
"inputs": [
{
"name": "orderbookFactory_",
"type": "address",
"internalType": "address"
},
{
"name": "feeTo_",
"type": "address",
"internalType": "address"
},
{
"name": "WETH_",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "limitBuy",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "price",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "quoteAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "isMaker",
"type": "bool",
"internalType": "bool"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "result",
"type": "tuple",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult"
}
],
"stateMutability": "nonpayable"
},
{
"name": "limitBuyETH",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "price",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "isMaker",
"type": "bool",
"internalType": "bool"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "result",
"type": "tuple",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult"
}
],
"stateMutability": "payable"
},
{
"name": "limitSell",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "price",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "baseAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "isMaker",
"type": "bool",
"internalType": "bool"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "result",
"type": "tuple",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult"
}
],
"stateMutability": "nonpayable"
},
{
"name": "limitSellETH",
"type": "function",
"inputs": [
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "price",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "isMaker",
"type": "bool",
"internalType": "bool"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "result",
"type": "tuple",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult"
}
],
"stateMutability": "payable"
},
{
"name": "listingDates",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "lmtSpreadLimits",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "buy",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "sell",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"name": "marketBuy",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "quoteAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "isMaker",
"type": "bool",
"internalType": "bool"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
},
{
"name": "slippageLimit",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [
{
"name": "result",
"type": "tuple",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult"
}
],
"stateMutability": "nonpayable"
},
{
"name": "marketBuyETH",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "isMaker",
"type": "bool",
"internalType": "bool"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
},
{
"name": "slippageLimit",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [
{
"name": "result",
"type": "tuple",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult"
}
],
"stateMutability": "payable"
},
{
"name": "marketSell",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "baseAmount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "isMaker",
"type": "bool",
"internalType": "bool"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
},
{
"name": "slippageLimit",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [
{
"name": "result",
"type": "tuple",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult"
}
],
"stateMutability": "nonpayable"
},
{
"name": "marketSellETH",
"type": "function",
"inputs": [
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "isMaker",
"type": "bool",
"internalType": "bool"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
},
{
"name": "slippageLimit",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [
{
"name": "result",
"type": "tuple",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult"
}
],
"stateMutability": "payable"
},
{
"name": "mktPrice",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "mktSpreadLimits",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "buy",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "sell",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"name": "orderbookFactory",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "renounceRole",
"type": "function",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "callerConfirmation",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "revokeRole",
"type": "function",
"inputs": [
{
"name": "role",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setDefaultFee",
"type": "function",
"inputs": [
{
"name": "isMaker",
"type": "bool",
"internalType": "bool"
},
{
"name": "fee_",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "setDefaultSpread",
"type": "function",
"inputs": [
{
"name": "buy",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "sell",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "isMkt",
"type": "bool",
"internalType": "bool"
}
],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "setFeeTo",
"type": "function",
"inputs": [
{
"name": "feeTo_",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "setIncentive",
"type": "function",
"inputs": [
{
"name": "incentive_",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "setListingCost",
"type": "function",
"inputs": [
{
"name": "terminal",
"type": "string",
"internalType": "string"
},
{
"name": "payment",
"type": "address",
"internalType": "address"
},
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"name": "setMaxMatches",
"type": "function",
"inputs": [
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "setSpread",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "buy",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "sell",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "isMkt",
"type": "bool",
"internalType": "bool"
}
],
"outputs": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "supportsInterface",
"type": "function",
"inputs": [
{
"name": "interfaceId",
"type": "bytes4",
"internalType": "bytes4"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "updateOrder",
"type": "function",
"inputs": [
{
"name": "updateOrderData",
"type": "tuple",
"components": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "isBid",
"type": "bool",
"internalType": "bool"
},
{
"name": "isLimit",
"type": "bool",
"internalType": "bool"
},
{
"name": "orderId",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "price",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
}
],
"internalType": "struct IMatchingEngine.CreateOrderInput"
}
],
"outputs": [
{
"name": "result",
"type": "tuple",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult"
}
],
"stateMutability": "nonpayable"
},
{
"name": "updateOrders",
"type": "function",
"inputs": [
{
"name": "updateOrderData",
"type": "tuple[]",
"components": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "isBid",
"type": "bool",
"internalType": "bool"
},
{
"name": "isLimit",
"type": "bool",
"internalType": "bool"
},
{
"name": "orderId",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "price",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "n",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "recipient",
"type": "address",
"internalType": "address"
}
],
"internalType": "struct IMatchingEngine.CreateOrderInput[]"
}
],
"outputs": [
{
"name": "results",
"type": "tuple[]",
"components": [
{
"name": "makePrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "placed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "id",
"type": "uint32",
"internalType": "uint32"
}
],
"internalType": "struct IMatchingEngine.OrderResult[]"
}
],
"stateMutability": "payable"
},
{
"name": "updatePair",
"type": "function",
"inputs": [
{
"name": "base",
"type": "address",
"internalType": "address"
},
{
"name": "quote",
"type": "address",
"internalType": "address"
},
{
"name": "listingPrice",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "listingDate",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "pair",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "nonpayable"
},
{
"type": "receive",
"stateMutability": "payable"
}
]