diff --git a/contracts/orderbook/Exchange.sol b/contracts/orderbook/Exchange.sol new file mode 100644 index 0000000..650679a --- /dev/null +++ b/contracts/orderbook/Exchange.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./Orderbook.sol"; + +contract Exchange is OrderBook, ReentrancyGuard { + /** + * .contructor + * @dev Exchange constructor + * @param _tokenA address of token A + * @param _tokenB address of token B + */ + constructor(address _tokenA, address _tokenB) { + tokenA = _tokenA; + tokenB = _tokenB; + } + + /** + * .placeBuyOrder + * @dev match buy order with existing sell oreders, the remaining volume is created as a buy order + * @param price bid price in tokenB + * @param volume bid amount in token A + */ + function placeBuyOrder(uint256 price, uint256 volume) public nonReentrant returns(uint256) { + require(price > 0, "Invalid Price"); + require(volume > 0, "Invalid Volume"); + require(balanceOf[msg.sender][tokenB] >= price * (volume / 10 ** ERC20(tokenA).decimals()), "Not enough balance"); + + (uint256 remainVolume) = _matchSellOrders(msg.sender, price, volume); + + if (remainVolume > 0) { + _insertBuyOrder(msg.sender, price, remainVolume); + } + + return currentOrderId; + } + + /** + * .placeSellOrder + * @dev match sell order with existing buy oreders, the remaining volume is created as a sell order + * @param price ask price in tokenB + * @param volume ask amount in token A + */ + function placeSellOrder(uint256 price, uint256 volume) public nonReentrant returns(uint256) { + require(price > 0, "Invalid Price"); + require(volume > 0, "Invalid Volume"); + require(balanceOf[msg.sender][tokenA] >= volume, "Not enough balance"); + + (uint256 remainVolume) = _matchBuyOrders(msg.sender, price, volume); + + if (remainVolume > 0){ + _insertSellOrder(msg.sender, price, remainVolume); + } + + return currentOrderId; + } + + /** + * .deposit + * @dev make an ERC20 from deposit from the sender to this contract given the token and amount + * @param token address of the ERC20 token to deposit + * @param amount total value of the deposit + * @notice it's mandatory to perform an approve call before calling this function. + */ + function deposit(address token, uint256 amount) public nonReentrant { + require(token == tokenA || token == tokenB, "Invalid token"); + require(amount > 0, "Invalid amount"); + + _deposit(msg.sender, token, amount); + } + + /** + * .withdraw + * @dev make an ERC20 withdraw from this contract to the sender given the token and amount + * @param token address of the ERC20 token to withdraw + * @param amount total value of the withdraw + */ + function withdraw(address token, uint256 amount) public nonReentrant { + require(token == tokenA || token == tokenB, "Invalid token"); + require(amount > 0, "Invalid amount"); + require(balanceOf[msg.sender][token] >= amount, "Not enough balance"); + + _withdraw(msg.sender, token, amount); + } + + /** + * .cancelOrder + * @dev cancel an order by id + * @param orderId uint256 id of the order + * @param isBuyOrder boolean flag wheter the order is buy or sell + * @notice only creator of the order can call this function + */ + function cancelOrder(uint256 orderId, bool isBuyOrder) public nonReentrant { + Order storage order = isBuyOrder ? buyOrders[orderId] : sellOrders[orderId]; + + require(order.trader != address(0), "Order do not exists" ); + require(order.trader == msg.sender, "Only the order creator can cancel this order"); + require(order.volume > 0, "Order already cancelled or fulfilled"); + + isBuyOrder + ? _cancelBuyOrder(order) + : _cancelSellOrder(order); + } +} diff --git a/contracts/orderbook/ExchangeFactory.sol b/contracts/orderbook/ExchangeFactory.sol new file mode 100644 index 0000000..a7e41ef --- /dev/null +++ b/contracts/orderbook/ExchangeFactory.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Exchange} from "./Exchange.sol"; + +/** + * @title Exchange Factory + * + * The contract which allows to deploy Exchanges with different token pairs + * and track contract addresses. + */ +contract ExchangeFactory { + // Available Exchanges + mapping(address exchange => bool) public availableExchanges; + + // Used salt => deployed Exchange + mapping(bytes32 => address) public exchangeDeployed; + + // emited when an exchagne is deployed + event ExchangeDeployed(address exchange, address tokenA, address tokenB, address deployer); + + /** + * @dev Deploys an Exchange using CREATE2 opcode. + * + * @param tokenA address of source token. + * @param tokenB address of target token + * @return exchange address of the deployed Exchange. + */ + function deployExchange( + address tokenA, + address tokenB + ) external returns (address exchange) { + bytes32 salt = bytes32(keccak256(abi.encodePacked(msg.sender, tokenA, tokenB))); + require(exchangeDeployed[salt] == address(0), "Exchange already deployed"); + + exchange = _deployExchange(salt, tokenA, tokenB); + + exchangeDeployed[salt] = exchange; + availableExchanges[exchange] = true; + + emit ExchangeDeployed(exchange, tokenA, tokenB, msg.sender); + } + + /** + * @dev Creates deployment data for the CREATE2 opcode. + * + * @return The the address of the contract created. + */ + function _deployExchange( + bytes32 salt, + address tokenA, + address tokenB + ) private returns (address) { + bytes memory _code = type(Exchange).creationCode; + bytes memory _constructData = abi.encode( + tokenA, + tokenB + ); + bytes memory deploymentData = abi.encodePacked(_code, _constructData); + return _deploy(salt, deploymentData); + } + + /** + * @dev Deploy function with create2 opcode call. + * + * @return The the address of the contract created. + */ + function _deploy(bytes32 salt, bytes memory bytecode) private returns (address) { + address addr; + // solhint-disable-next-line no-inline-assembly + assembly { + let encoded_data := add(0x20, bytecode) // load initialization code. + let encoded_size := mload(bytecode) // load init code's length. + addr := create2(callvalue(), encoded_data, encoded_size, salt) + if iszero(extcodesize(addr)) { + revert(0, 0) + } + } + return addr; + } + + /** + * @dev Checks if Exchange is available. + * + * @return The bool flag of exchanges's availability. + */ + function isExchangeAvailable(address exchange) external view returns (bool) { + return availableExchanges[exchange]; + } +} diff --git a/contracts/orderbook/Orderbook.sol b/contracts/orderbook/Orderbook.sol new file mode 100644 index 0000000..e56f81b --- /dev/null +++ b/contracts/orderbook/Orderbook.sol @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; + +abstract contract OrderBook { + address public tokenA; + address public tokenB; + mapping(uint256 => Order) public buyOrders; + mapping(uint256 => Order) public sellOrders; + mapping(address => mapping(address => uint256)) public balanceOf; + uint256 public firstBuyOrderId; + uint256 public firstSellOrderId; + uint256 public currentOrderId; + + event Trade(uint256 tradedVolume, uint256 price, address indexed buyer, address seller); + event NewOrder(bool isBuy, uint256 orderId, address trader, uint256 price, uint256 volume); + event Deposit(address indexed trader, address token, uint256 amount); + event Withdraw(address indexed trader, address token, uint256 amount); + event OrderCanceled(bool isBuy, uint256 indexed orderId, address indexed trader); + + struct Order { + uint256 id; + uint256 price; + uint256 volume; + address trader; + uint256 next; + } + + function _insertBuyOrder(address trader, uint256 price, uint256 volume) internal { + currentOrderId++; + uint256 currentId = firstBuyOrderId; + uint256 lastId = 0; + + while (currentId != 0 && buyOrders[currentId].price > price) { + lastId = currentId; + currentId = buyOrders[currentId].next; + } + + buyOrders[currentOrderId] = Order({ + id: currentOrderId, + price: price, + volume: volume, + trader: trader, + next: currentId + }); + + balanceOf[trader][tokenB] -= price * (volume / 10 ** ERC20(tokenA).decimals()); + emit NewOrder(true, currentOrderId, trader, price, volume); + + if (lastId == 0) { + firstBuyOrderId = currentOrderId; + } else { + buyOrders[lastId].next = currentOrderId; + } + + } + + function _insertSellOrder(address trader, uint256 price, uint256 volume) internal { + currentOrderId++; // Increment order ID for the next order + uint256 currentId = firstSellOrderId; + uint256 lastId = 0; + + while (currentId != 0 && sellOrders[currentId].price < price) { + lastId = currentId; + currentId = sellOrders[currentId].next; + } + + sellOrders[currentOrderId] = Order({ + id: currentOrderId, + price: price, + volume: volume, + trader: trader, + next: currentId + }); + + balanceOf[trader][tokenA] -= volume; + emit NewOrder(false, currentOrderId, trader, price, volume); + + if (lastId == 0) { + firstSellOrderId = currentOrderId; + } else { + sellOrders[lastId].next = currentOrderId; + } + + } + + function _matchBuyOrders( + address sellTrader, + uint256 sellPrice, + uint256 sellVolume + ) internal returns (uint256) { + uint256 currentBuyId = firstBuyOrderId; + uint256 decimals = ERC20(tokenA).decimals(); + + while (currentBuyId != 0 && sellVolume > 0) { + Order storage buyOrder = buyOrders[currentBuyId]; + + if (sellPrice <= buyOrder.price) { + uint256 tradedVolume = (buyOrder.volume < sellVolume) ? buyOrder.volume : sellVolume; + uint256 tradedPrice = sellPrice; + + balanceOf[sellTrader][tokenB] += tradedPrice * (tradedVolume / 10 ** decimals); + balanceOf[sellTrader][tokenA] -= tradedVolume; + balanceOf[buyOrder.trader][tokenA] += tradedVolume; + + sellVolume -= tradedVolume; + buyOrder.volume -= tradedVolume; + + emit Trade(tradedVolume, buyOrder.price, sellTrader, buyOrder.trader); + + if (buyOrder.volume == 0) { + uint256 nextId = buyOrder.next; + delete buyOrders[currentBuyId]; // Remove the order after it is fully matched + currentBuyId = nextId; + } + } else { + break; // No more matches possible + } + } + + firstBuyOrderId = currentBuyId; // Update the first buy order ID + return sellVolume; + } + + function _matchSellOrders( + address buyTrader, + uint256 buyPrice, + uint256 buyVolume + ) internal returns (uint256) { + uint256 currentSellId = firstSellOrderId; + uint256 decimals = ERC20(tokenA).decimals(); + + while (currentSellId != 0 && buyVolume > 0) { + Order storage sellOrder = sellOrders[currentSellId]; + + // emit Trade(1, 1, address(0), address(this)); + if (buyPrice >= sellOrder.price) { + uint256 tradedVolume = (sellOrder.volume < buyVolume) ? sellOrder.volume : buyVolume; + uint256 tradedPrice = sellOrder.price; + + + balanceOf[buyTrader][tokenB] -= tradedPrice * (tradedVolume / 10 ** decimals); + balanceOf[buyTrader][tokenA] += tradedVolume; + balanceOf[sellOrder.trader][tokenB] += tradedPrice * (tradedVolume / 10 ** decimals); + + buyVolume -= tradedVolume; + sellOrder.volume -= tradedVolume; + + emit Trade(tradedVolume, sellOrder.price, buyTrader, sellOrder.trader); + + if (sellOrder.volume == 0) { + uint256 nextId = sellOrder.next; + delete sellOrders[currentSellId]; // Remove the order after it is fully matched + currentSellId = nextId; + } + } else { + // since the sell order book is sorter by the lowest price + // no more matches possible and we interrupt the loop. + break; + } + } + + firstSellOrderId = currentSellId; // Update the first sell order ID + return buyVolume; + } + + function _deposit(address trader, address token, uint256 amount) internal { + ERC20(token).transferFrom(trader, address(this), amount); + balanceOf[trader][token] += amount; + emit Deposit(trader, token, amount); + } + + function _withdraw(address trader, address token, uint256 amount) internal { + balanceOf[trader][token] -= amount; + ERC20(token).transfer(trader, amount); + emit Withdraw(trader, token, amount); + } + + function _cancelSellOrder(Order storage sellOrder) internal { + if (sellOrder.id == firstSellOrderId) { + firstSellOrderId = sellOrder.next; + } else { + // Find the previous order + Order storage currentOrder = sellOrders[firstSellOrderId]; + + while (currentOrder.next != sellOrder.id) { + require(currentOrder.next != 0, "Order not found"); + currentOrder = sellOrders[currentOrder.next]; + } + // Adjust pointers + currentOrder.next = sellOrder.next; + } + + // refund balance to trader + balanceOf[sellOrder.trader][tokenA] += sellOrder.volume; + sellOrder.volume = 0; + + emit OrderCanceled(false, sellOrder.id, msg.sender); + } + + function _cancelBuyOrder(Order storage buyOrder) internal { + if (buyOrder.id == firstBuyOrderId) { + firstBuyOrderId = buyOrder.next; + } else { + // Find the previous order + Order storage currentOrder = buyOrders[firstBuyOrderId]; + + while (currentOrder.next != buyOrder.id) { + require(currentOrder.next != 0, "Order not found"); + currentOrder = buyOrders[currentOrder.next]; + } + // Adjust pointers + currentOrder.next = buyOrder.next; + } + + // refund balance to trader + balanceOf[buyOrder.trader][tokenB] += buyOrder.price * (buyOrder.volume / 10 ** ERC20(tokenA).decimals()); + buyOrder.volume = 0; + + emit OrderCanceled(true, buyOrder.id, msg.sender); + } +} diff --git a/contracts/orderbook/hts/ExchangeFactoryHTS.sol b/contracts/orderbook/hts/ExchangeFactoryHTS.sol new file mode 100644 index 0000000..0606eb6 --- /dev/null +++ b/contracts/orderbook/hts/ExchangeFactoryHTS.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {ExchangeHTS} from "./ExchangeHTS.sol"; + +/** + * @title Exchange Factory + * + * The contract which allows to deploy Exchanges with different token pairs + * and track contract addresses. + */ +contract ExchangeFactoryHTS { + // Available Exchanges + mapping(address exchange => bool) public availableExchanges; + + // Used salt => deployed Exchange + mapping(bytes32 => address) public exchangeDeployed; + + // emited when an exchagne is deployed + event ExchangeDeployed(address exchange, address tokenA, address tokenB, address deployer); + + /** + * @dev Deploys an Exchange using CREATE2 opcode. + * + * @param tokenA address of source token. + * @param tokenB address of target token + * @return exchange address of the deployed Exchange. + */ + function deployExchange( + address tokenA, + address tokenB + ) external returns (address exchange) { + bytes32 salt = bytes32(keccak256(abi.encodePacked(msg.sender, tokenA, tokenB))); + require(exchangeDeployed[salt] == address(0), "Exchange already deployed"); + + exchange = _deployExchange(salt, tokenA, tokenB); + + exchangeDeployed[salt] = exchange; + availableExchanges[exchange] = true; + + emit ExchangeDeployed(exchange, tokenA, tokenB, msg.sender); + } + + /** + * @dev Creates deployment data for the CREATE2 opcode. + * + * @return The the address of the contract created. + */ + function _deployExchange( + bytes32 salt, + address tokenA, + address tokenB + ) private returns (address) { + bytes memory _code = type(ExchangeHTS).creationCode; + bytes memory _constructData = abi.encode( + tokenA, + tokenB + ); + bytes memory deploymentData = abi.encodePacked(_code, _constructData); + return _deploy(salt, deploymentData); + } + + /** + * @dev Deploy function with create2 opcode call. + * + * @return The the address of the contract created. + */ + function _deploy(bytes32 salt, bytes memory bytecode) private returns (address) { + address addr; + // solhint-disable-next-line no-inline-assembly + assembly { + let encoded_data := add(0x20, bytecode) // load initialization code. + let encoded_size := mload(bytecode) // load init code's length. + addr := create2(callvalue(), encoded_data, encoded_size, salt) + if iszero(extcodesize(addr)) { + revert(0, 0) + } + } + return addr; + } + + /** + * @dev Checks if Exchange is available. + * + * @return The bool flag of exchanges's availability. + */ + function isExchangeAvailable(address exchange) external view returns (bool) { + return availableExchanges[exchange]; + } +} diff --git a/contracts/orderbook/hts/ExchangeHTS.sol b/contracts/orderbook/hts/ExchangeHTS.sol new file mode 100644 index 0000000..d3e0a3b --- /dev/null +++ b/contracts/orderbook/hts/ExchangeHTS.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../../common/safe-HTS/SafeHTS.sol"; +import "./OrderBookHTS.sol"; + +contract ExchangeHTS is OrderBookHTS, ReentrancyGuard { + /** + * .constructor + * @dev Exchange constructor + * @param _tokenA address of token A + * @param _tokenB address of token B + */ + constructor(address _tokenA, address _tokenB) { + tokenA = _tokenA; + tokenB = _tokenB; + + SafeHTS.safeAssociateToken(tokenA, address(this)); + SafeHTS.safeAssociateToken(tokenB, address(this)); + } + + /** + * .placeBuyOrder + * @dev match buy order with existing sell orders, the remaining volume is created as a buy order + * @param price bid price in tokenB + * @param volume bid amount in token A + */ + function placeBuyOrder(int64 price, int64 volume) public nonReentrant returns(uint256) { + require(price > 0, "Invalid Price"); + require(volume > 0, "Invalid Volume"); + require(balanceOf[msg.sender][tokenB] >= price * (volume / 10 ** 8), "Not enough balance"); + + int64 remainVolume = _matchSellOrders(msg.sender, price, volume); + + if (remainVolume > 0) { + _insertBuyOrder(msg.sender, price, remainVolume); + } + + return currentOrderId; + } + + /** + * .placeSellOrder + * @dev match sell order with existing buy orders, the remaining volume is created as a sell order + * @param price ask price in tokenB + * @param volume ask amount in token A + */ + function placeSellOrder(int64 price, int64 volume) public nonReentrant returns(uint256) { + require(price > 0, "Invalid Price"); + require(volume > 0, "Invalid Volume"); + require(balanceOf[msg.sender][tokenA] >= volume, "Not enough balance"); + + int64 remainVolume = _matchBuyOrders(msg.sender, price, volume); + + if (remainVolume > 0) { + _insertSellOrder(msg.sender, price, remainVolume); + } + + return currentOrderId; + } + + /** + * .deposit + * @dev make an ERC20 deposit from the sender to this contract given the token and amount + * @param token address of the ERC20 token to deposit + * @param amount total value of the deposit + * @notice it's mandatory to perform an approve call before calling this function. + */ + function deposit(address token, int64 amount) public nonReentrant { + require(token == tokenA || token == tokenB, "Invalid token"); + require(amount > 0, "Invalid amount"); + + _deposit(msg.sender, token, amount); + } + + /** + * .withdraw + * @dev make an ERC20 withdraw from this contract to the sender given the token and amount + * @param token address of the ERC20 token to withdraw + * @param amount total value of the withdrawal + */ + function withdraw(address token, int64 amount) public nonReentrant { + require(token == tokenA || token == tokenB, "Invalid token"); + require(amount > 0, "Invalid amount"); + require(balanceOf[msg.sender][token] >= amount, "Not enough balance"); + + _withdraw(msg.sender, token, amount); + } + + /** + * .cancelOrder + * @dev cancel an order by id + * @param orderId uint256 id of the order + * @param isBuyOrder boolean flag whether the order is buy or sell + * @notice only creator of the order can call this function + */ + function cancelOrder(uint256 orderId, bool isBuyOrder) public nonReentrant { + Order storage order = isBuyOrder ? buyOrders[orderId] : sellOrders[orderId]; + + require(order.trader != address(0), "Order does not exist"); + require(order.trader == msg.sender, "Only the order creator can cancel this order"); + require(order.volume > 0, "Order already cancelled or fulfilled"); + + if (isBuyOrder) { + _cancelBuyOrder(order); + } else { + _cancelSellOrder(order); + } + } +} diff --git a/contracts/orderbook/hts/OrderBookHTS.sol b/contracts/orderbook/hts/OrderBookHTS.sol new file mode 100644 index 0000000..603af9b --- /dev/null +++ b/contracts/orderbook/hts/OrderBookHTS.sol @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; +import "../../common/safe-HTS/SafeHTS.sol"; + +abstract contract OrderBookHTS { + address public tokenA; + address public tokenB; + mapping(uint256 => Order) public buyOrders; + mapping(uint256 => Order) public sellOrders; + mapping(address => mapping(address => int64)) public balanceOf; + uint256 public firstBuyOrderId; + uint256 public firstSellOrderId; + uint256 public currentOrderId; + + event Trade(int64 tradedVolume, int64 price, address buyer, address seller); + event NewOrder(bool isBuy, uint256 orderId, address trader, int64 price, int64 volume); + event Deposit(address trader, address token, int64 amount); + event Withdraw(address trader, address token, int64 amount); + event OrderCanceled(bool isBuy, uint256 orderId, address trader); + + struct Order { + uint256 id; + int64 price; + int64 volume; + address trader; + uint256 next; + } + + function _insertBuyOrder(address trader, int64 price, int64 volume) internal { + currentOrderId++; // Increment order ID for the next order + uint256 currentId = firstBuyOrderId; + uint256 lastId = 0; + + while (currentId != 0 && buyOrders[currentId].price > price) { + lastId = currentId; + currentId = buyOrders[currentId].next; + } + + buyOrders[currentOrderId] = Order({ + id: currentOrderId, + price: price, + volume: volume, + trader: trader, + next: currentId + }); + + balanceOf[trader][tokenB] -= price * (volume / 10 ** 8); + emit NewOrder(true, currentOrderId, trader, price, volume); + + if (lastId == 0) { + firstBuyOrderId = currentOrderId; + } else { + buyOrders[lastId].next = currentOrderId; + } + + } + + function _insertSellOrder(address trader, int64 price, int64 volume) internal { + currentOrderId++; // Increment order ID for the next order + uint256 currentId = firstSellOrderId; + uint256 lastId = 0; + + while (currentId != 0 && sellOrders[currentId].price < price) { + lastId = currentId; + currentId = sellOrders[currentId].next; + } + + sellOrders[currentOrderId] = Order({ + id: currentOrderId, + price: price, + volume: volume, + trader: trader, + next: currentId + }); + + balanceOf[trader][tokenA] -= volume; + emit NewOrder(false, currentOrderId, trader, price, volume); + + if (lastId == 0) { + firstSellOrderId = currentOrderId; + } else { + sellOrders[lastId].next = currentOrderId; + } + + } + + function _matchBuyOrders( + address sellTrader, + int64 sellPrice, + int64 sellVolume + ) internal returns (int64) { + uint256 currentBuyId = firstBuyOrderId; + uint256 maxIterations = 10; // Define a maximum number of iterations per call + uint256 iterations = 0; + + while (currentBuyId != 0 && sellVolume > 0 && iterations < maxIterations) { + Order storage buyOrder = buyOrders[currentBuyId]; + + if (sellPrice <= buyOrder.price) { + int64 tradedVolume = (buyOrder.volume < sellVolume) ? buyOrder.volume : sellVolume; + int64 tradedPrice = sellPrice; + + balanceOf[sellTrader][tokenB] += tradedPrice * (tradedVolume / 10 ** 8); + balanceOf[sellTrader][tokenA] -= tradedVolume; + balanceOf[buyOrder.trader][tokenA] += tradedVolume; + + sellVolume -= tradedVolume; + buyOrder.volume -= tradedVolume; + + emit Trade(tradedVolume, buyOrder.price, sellTrader, buyOrder.trader); + + if (buyOrder.volume == 0) { + uint256 nextId = buyOrder.next; + delete buyOrders[currentBuyId]; // Remove the order after it is fully matched + currentBuyId = nextId; + } + } else { + break; + } + iterations++; + } + + firstBuyOrderId = currentBuyId; // Update the first buy order ID + return sellVolume; + } + + function _matchSellOrders( + address buyTrader, + int64 buyPrice, + int64 buyVolume + ) internal returns (int64) { + uint256 currentSellId = firstSellOrderId; + uint256 maxIterations = 10; // Define a maximum number of iterations per call + uint256 iterations = 0; + + while (currentSellId != 0 && buyVolume > 0 && iterations < maxIterations) { + Order storage sellOrder = sellOrders[currentSellId]; + + if (buyPrice >= sellOrder.price) { + int64 tradedVolume = (sellOrder.volume < buyVolume) ? sellOrder.volume : buyVolume; + int64 tradedPrice = sellOrder.price; + + balanceOf[buyTrader][tokenB] -= tradedPrice * (tradedVolume / 10 ** 8); + balanceOf[buyTrader][tokenA] += tradedVolume; + balanceOf[sellOrder.trader][tokenB] += tradedPrice * (tradedVolume / 10 ** 8); + + buyVolume -= tradedVolume; + sellOrder.volume -= tradedVolume; + + emit Trade(tradedVolume, sellOrder.price, buyTrader, sellOrder.trader); + + if (sellOrder.volume == 0) { + uint256 nextId = sellOrder.next; + delete sellOrders[currentSellId]; // Remove the order after it is fully matched + currentSellId = nextId; + } + } else { + break; + } + iterations++; + } + + firstSellOrderId = currentSellId; // Update the first sell order ID + return buyVolume; + } + + function _deposit(address trader, address token, int64 amount) internal { + SafeHTS.safeTransferToken(token, trader, address(this), amount); + balanceOf[trader][token] += amount; + emit Deposit(trader, token, amount); + } + + function _withdraw(address trader, address token, int64 amount) internal { + balanceOf[trader][token] -= amount; + SafeHTS.safeTransferToken(token, address(this), trader, amount); + emit Withdraw(trader, token, amount); + } + + function _cancelSellOrder(Order storage sellOrder) internal { + if (sellOrder.id == firstSellOrderId) { + firstSellOrderId = sellOrder.next; + } else { + Order storage currentOrder = sellOrders[firstSellOrderId]; + + while (currentOrder.next != sellOrder.id) { + require(currentOrder.next != 0, "Order not found"); + currentOrder = sellOrders[currentOrder.next]; + } + currentOrder.next = sellOrder.next; + } + + balanceOf[sellOrder.trader][tokenA] += sellOrder.volume; + sellOrder.volume = 0; + + emit OrderCanceled(false, sellOrder.id, msg.sender); + } + + function _cancelBuyOrder(Order storage buyOrder) internal { + if (buyOrder.id == firstBuyOrderId) { + firstBuyOrderId = buyOrder.next; + } else { + Order storage currentOrder = buyOrders[firstBuyOrderId]; + + while (currentOrder.next != buyOrder.id) { + require(currentOrder.next != 0, "Order not found"); + currentOrder = buyOrders[currentOrder.next]; + } + currentOrder.next = buyOrder.next; + } + + balanceOf[buyOrder.trader][tokenB] += buyOrder.price * (buyOrder.volume / 10 ** 8); + buyOrder.volume = 0; + + emit OrderCanceled(true, buyOrder.id, msg.sender); + } +} diff --git a/data/abis/Exchange.json b/data/abis/Exchange.json new file mode 100644 index 0000000..d59f60b --- /dev/null +++ b/data/abis/Exchange.json @@ -0,0 +1,444 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "Exchange", + "sourceName": "contracts/orderbook/Exchange.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenB", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "isBuy", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "volume", + "type": "uint256" + } + ], + "name": "NewOrder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "isBuy", + "type": "bool" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "OrderCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "tradedVolume", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "seller", + "type": "address" + } + ], + "name": "Trade", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "buyOrders", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "next", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isBuyOrder", + "type": "bool" + } + ], + "name": "cancelOrder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "currentOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "firstBuyOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "firstSellOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + } + ], + "name": "placeBuyOrder", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + } + ], + "name": "placeSellOrder", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sellOrders", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "next", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenA", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x60806040523480156200001157600080fd5b5060405162001a2b38038062001a2b833981016040819052620000349162000087565b60016008819055600080546001600160a01b03199081166001600160a01b039586161790915581541691909216179055620000bf565b80516001600160a01b03811681146200008257600080fd5b919050565b600080604083850312156200009b57600080fd5b620000a6836200006a565b9150620000b6602084016200006a565b90509250929050565b61195c80620000cf6000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c8063925931fc1161008c578063ee36d4ab11610066578063ee36d4ab1461022c578063f3fef3a31461023f578063f7888aec14610252578063f88d20471461027d57600080fd5b8063925931fc14610207578063a4406bcd14610210578063bb110bf91461022357600080fd5b80630fc63d10146100d457806335cea2881461010457806347e7ef241461017e5780634a8393f3146101935780635f64b55b146101dd57806372fb0777146101f0575b600080fd5b6000546100e7906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61014d610112366004611631565b60026020819052600091825260409091208054600182015492820154600383015460049093015491939290916001600160a01b039091169085565b604080519586526020860194909452928401919091526001600160a01b03166060830152608082015260a0016100fb565b61019161018c366004611666565b610290565b005b61014d6101a1366004611631565b6003602081905260009182526040909120805460018201546002830154938301546004909301549193909290916001600160a01b039091169085565b6001546100e7906001600160a01b031681565b6101f960065481565b6040519081526020016100fb565b6101f960075481565b6101f961021e366004611690565b61035c565b6101f960055481565b6101f961023a366004611690565b610460565b61019161024d366004611666565b6105e3565b6101f96102603660046116b2565b600460209081526000928352604080842090915290825290205481565b61019161028b3660046116f6565b6106df565b61029861085b565b6000546001600160a01b03838116911614806102c157506001546001600160a01b038381169116145b6103025760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b60448201526064015b60405180910390fd5b600081116103435760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b60448201526064016102f9565b61034e338383610885565b6103586001600855565b5050565b600061036661085b565b600083116103a65760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064016102f9565b600082116103e75760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b60448201526064016102f9565b33600090815260046020908152604080832083546001600160a01b0316845290915290205482111561042b5760405162461bcd60e51b81526004016102f990611726565b6000610438338585610987565b9050801561044b5761044b338583610c11565b505060075461045a6001600855565b92915050565b600061046a61085b565b600083116104aa5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064016102f9565b600082116104eb5760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b60448201526064016102f9565b60008054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561053c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105609190611752565b61056b90600a611876565b6105759083611885565b61057f90846118a7565b3360009081526004602090815260408083206001546001600160a01b0316845290915290205410156105c35760405162461bcd60e51b81526004016102f990611726565b60006105d0338585610d97565b9050801561044b5761044b338583611047565b6105eb61085b565b6000546001600160a01b038381169116148061061457506001546001600160a01b038381169116145b6106505760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b60448201526064016102f9565b600081116106915760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b60448201526064016102f9565b3360009081526004602090815260408083206001600160a01b03861684529091529020548111156106d45760405162461bcd60e51b81526004016102f990611726565b61034e33838361125b565b6106e761085b565b600081610701576000838152600360205260409020610710565b60008381526002602052604090205b60038101549091506001600160a01b03166107635760405162461bcd60e51b81526020600482015260136024820152724f7264657220646f206e6f742065786973747360681b60448201526064016102f9565b60038101546001600160a01b031633146107d45760405162461bcd60e51b815260206004820152602c60248201527f4f6e6c7920746865206f726465722063726561746f722063616e2063616e636560448201526b36103a3434b99037b93232b960a11b60648201526084016102f9565b60008160020154116108345760405162461bcd60e51b8152602060048201526024808201527f4f7264657220616c72656164792063616e63656c6c6564206f722066756c66696044820152631b1b195960e21b60648201526084016102f9565b81610847576108428161134e565b610850565b61085081611476565b506103586001600855565b60026008540361087e57604051633ee5aeb560e01b815260040160405180910390fd5b6002600855565b6040516323b872dd60e01b81526001600160a01b038481166004830152306024830152604482018390528316906323b872dd906064016020604051808303816000875af11580156108da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108fe91906118be565b506001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906109369084906118db565b9091555050604080516001600160a01b038481168252602082018490528516917f5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f6291015b60405180910390a2505050565b600554600080546040805163313ce56760e01b8152905192939284926001600160a01b03169163313ce5679160048083019260209291908290030181865afa1580156109d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109fb9190611752565b60ff1690505b8115801590610a105750600084115b15610c0657600082815260026020526040902060018101548611610bfa57600085826002015410610a415785610a47565b81600201545b905086610a5584600a6118ee565b610a5f9083611885565b610a6990826118a7565b6001600160a01b03808b16600090815260046020908152604080832060015490941683529290529081208054909190610aa39084906118db565b90915550506001600160a01b03808a166000908152600460209081526040808320835490941683529290529081208054849290610ae19084906118fa565b909155505060038301546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054849290610b249084906118db565b90915550610b34905082886118fa565b965081836002016000828254610b4a91906118fa565b9091555050600183015460038401546040805185815260208101939093526001600160a01b03918216908301528a16907f83cc6330e30db670fe04b07b602bd9cba4da39e5037267131b5d60cecba29f619060600160405180910390a28260020154600003610bf35760048084015460009687526002602081905260408820888155600181018990559081018890556003810180546001600160a01b0319169055909101959095555b5050610c00565b50610c06565b50610a01565b506005555092915050565b60078054906000610c218361190d565b909155505060065460005b8115801590610c4b575060008281526003602052604090206001015484115b15610c69575060008181526003602052604090206004015490610c2c565b6040805160a08101825260075480825260208083018881528385018881526001600160a01b038b811660608701818152608088018b8152600097885260038088528a892099518a55955160018a0155935160028901555193870180546001600160a01b03191694831694909417909355905160049586015590835292815283822082549093168252919091529081208054859290610d089084906118fa565b9091555050600754604080516000815260208101929092526001600160a01b038716828201526060820186905260808201859052517f286a2d7a29403e4e0f090c310eeb9b565090f3731608308fe263443ca910e9d59181900360a00190a180600003610d7a57600754600655610d90565b6007546000828152600360205260409020600401555b5050505050565b600654600080546040805163313ce56760e01b8152905192939284926001600160a01b03169163313ce5679160048083019260209291908290030181865afa158015610de7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0b9190611752565b60ff1690505b8115801590610e205750600084115b1561103c5760008281526003602052604090206001810154861061103057600085826002015410610e515785610e57565b81600201545b6001830154909150610e6a84600a6118ee565b610e749083611885565b610e7e90826118a7565b6001600160a01b03808b16600090815260046020908152604080832060015490941683529290529081208054909190610eb89084906118fa565b90915550506001600160a01b03808a166000908152600460209081526040808320835490941683529290529081208054849290610ef69084906118db565b90915550610f07905084600a6118ee565b610f119083611885565b610f1b90826118a7565b60038401546001600160a01b03908116600090815260046020908152604080832060015490941683529290529081208054909190610f5a9084906118db565b90915550610f6a905082886118fa565b965081836002016000828254610f8091906118fa565b9091555050600183015460038401546040805185815260208101939093526001600160a01b03918216908301528a16907f83cc6330e30db670fe04b07b602bd9cba4da39e5037267131b5d60cecba29f619060600160405180910390a282600201546000036110295760048084015460009687526003602081905260408820888155600181018990556002810189905590810180546001600160a01b0319169055909101959095555b5050611036565b5061103c565b50610e11565b506006555092915050565b600780549060006110578361190d565b909155505060055460005b8115801590611081575060008281526002602052604090206001015484105b1561109f575060008181526002602052604090206004015490611062565b6040805160a08101825260075480825260208083018881528385018881526001600160a01b038b811660608701908152608087018a815260009687526002808752898820985189559451600189015592519387019390935591516003860180546001600160a01b031916918416919091179055516004948501559154845163313ce56760e01b8152945192169363313ce5679381810193918290030181865afa158015611150573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111749190611752565b61117f90600a611876565b6111899084611885565b61119390856118a7565b6001600160a01b038087166000908152600460209081526040808320600154909416835292905290812080549091906111cd9084906118fa565b9091555050600754604080516001815260208101929092526001600160a01b038716828201526060820186905260808201859052517f286a2d7a29403e4e0f090c310eeb9b565090f3731608308fe263443ca910e9d59181900360a00190a18060000361123f57600754600555610d90565b6007546000828152600260205260409020600401555050505050565b6001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906112929084906118fa565b909155505060405163a9059cbb60e01b81526001600160a01b0384811660048301526024820183905283169063a9059cbb906044016020604051808303816000875af11580156112e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061130a91906118be565b50604080516001600160a01b038481168252602082018490528516917f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb910161097a565b6006548154036113655760048101546006556113ea565b60065460009081526003602052604090205b81546004820154146113e05780600401546000036113c95760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b60448201526064016102f9565b600401546000908152600360205260409020611377565b6004808301549101555b600281015460038201546001600160a01b03908116600090815260046020908152604080832083549094168352929052908120805490919061142d9084906118db565b9091555050600060028201819055815460405191825233917fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a4906020015b60405180910390a350565b60055481540361148d576004810154600555611512565b60055460009081526002602052604090205b81546004820154146115085780600401546000036114f15760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b60448201526064016102f9565b60040154600090815260026020526040902061149f565b6004808301549101555b60008054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611563573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115879190611752565b61159290600a611876565b81600201546115a19190611885565b81600101546115b091906118a7565b60038201546001600160a01b039081166000908152600460209081526040808320600154909416835292905290812080549091906115ef9084906118db565b9091555050600060028201558054604051600181523391907fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a49060200161146b565b60006020828403121561164357600080fd5b5035919050565b80356001600160a01b038116811461166157600080fd5b919050565b6000806040838503121561167957600080fd5b6116828361164a565b946020939093013593505050565b600080604083850312156116a357600080fd5b50508035926020909101359150565b600080604083850312156116c557600080fd5b6116ce8361164a565b91506116dc6020840161164a565b90509250929050565b80151581146116f357600080fd5b50565b6000806040838503121561170957600080fd5b82359150602083013561171b816116e5565b809150509250929050565b6020808252601290820152714e6f7420656e6f7567682062616c616e636560701b604082015260600190565b60006020828403121561176457600080fd5b815160ff8116811461177557600080fd5b9392505050565b634e487b7160e01b600052601160045260246000fd5b600181815b808511156117cd5781600019048211156117b3576117b361177c565b808516156117c057918102915b93841c9390800290611797565b509250929050565b6000826117e45750600161045a565b816117f15750600061045a565b816001811461180757600281146118115761182d565b600191505061045a565b60ff8411156118225761182261177c565b50506001821b61045a565b5060208310610133831016604e8410600b8410161715611850575081810a61045a565b61185a8383611792565b806000190482111561186e5761186e61177c565b029392505050565b600061177560ff8416836117d5565b6000826118a257634e487b7160e01b600052601260045260246000fd5b500490565b808202811582820484141761045a5761045a61177c565b6000602082840312156118d057600080fd5b8151611775816116e5565b8082018082111561045a5761045a61177c565b600061177583836117d5565b8181038181111561045a5761045a61177c565b60006001820161191f5761191f61177c565b506001019056fea2646970667358221220257d0fcef9327e72e8c9d83d65d26bfb381402b318dc782e8f9f11345f4aabe764736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c8063925931fc1161008c578063ee36d4ab11610066578063ee36d4ab1461022c578063f3fef3a31461023f578063f7888aec14610252578063f88d20471461027d57600080fd5b8063925931fc14610207578063a4406bcd14610210578063bb110bf91461022357600080fd5b80630fc63d10146100d457806335cea2881461010457806347e7ef241461017e5780634a8393f3146101935780635f64b55b146101dd57806372fb0777146101f0575b600080fd5b6000546100e7906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61014d610112366004611631565b60026020819052600091825260409091208054600182015492820154600383015460049093015491939290916001600160a01b039091169085565b604080519586526020860194909452928401919091526001600160a01b03166060830152608082015260a0016100fb565b61019161018c366004611666565b610290565b005b61014d6101a1366004611631565b6003602081905260009182526040909120805460018201546002830154938301546004909301549193909290916001600160a01b039091169085565b6001546100e7906001600160a01b031681565b6101f960065481565b6040519081526020016100fb565b6101f960075481565b6101f961021e366004611690565b61035c565b6101f960055481565b6101f961023a366004611690565b610460565b61019161024d366004611666565b6105e3565b6101f96102603660046116b2565b600460209081526000928352604080842090915290825290205481565b61019161028b3660046116f6565b6106df565b61029861085b565b6000546001600160a01b03838116911614806102c157506001546001600160a01b038381169116145b6103025760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b60448201526064015b60405180910390fd5b600081116103435760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b60448201526064016102f9565b61034e338383610885565b6103586001600855565b5050565b600061036661085b565b600083116103a65760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064016102f9565b600082116103e75760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b60448201526064016102f9565b33600090815260046020908152604080832083546001600160a01b0316845290915290205482111561042b5760405162461bcd60e51b81526004016102f990611726565b6000610438338585610987565b9050801561044b5761044b338583610c11565b505060075461045a6001600855565b92915050565b600061046a61085b565b600083116104aa5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064016102f9565b600082116104eb5760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b60448201526064016102f9565b60008054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561053c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105609190611752565b61056b90600a611876565b6105759083611885565b61057f90846118a7565b3360009081526004602090815260408083206001546001600160a01b0316845290915290205410156105c35760405162461bcd60e51b81526004016102f990611726565b60006105d0338585610d97565b9050801561044b5761044b338583611047565b6105eb61085b565b6000546001600160a01b038381169116148061061457506001546001600160a01b038381169116145b6106505760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b60448201526064016102f9565b600081116106915760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b60448201526064016102f9565b3360009081526004602090815260408083206001600160a01b03861684529091529020548111156106d45760405162461bcd60e51b81526004016102f990611726565b61034e33838361125b565b6106e761085b565b600081610701576000838152600360205260409020610710565b60008381526002602052604090205b60038101549091506001600160a01b03166107635760405162461bcd60e51b81526020600482015260136024820152724f7264657220646f206e6f742065786973747360681b60448201526064016102f9565b60038101546001600160a01b031633146107d45760405162461bcd60e51b815260206004820152602c60248201527f4f6e6c7920746865206f726465722063726561746f722063616e2063616e636560448201526b36103a3434b99037b93232b960a11b60648201526084016102f9565b60008160020154116108345760405162461bcd60e51b8152602060048201526024808201527f4f7264657220616c72656164792063616e63656c6c6564206f722066756c66696044820152631b1b195960e21b60648201526084016102f9565b81610847576108428161134e565b610850565b61085081611476565b506103586001600855565b60026008540361087e57604051633ee5aeb560e01b815260040160405180910390fd5b6002600855565b6040516323b872dd60e01b81526001600160a01b038481166004830152306024830152604482018390528316906323b872dd906064016020604051808303816000875af11580156108da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108fe91906118be565b506001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906109369084906118db565b9091555050604080516001600160a01b038481168252602082018490528516917f5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f6291015b60405180910390a2505050565b600554600080546040805163313ce56760e01b8152905192939284926001600160a01b03169163313ce5679160048083019260209291908290030181865afa1580156109d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109fb9190611752565b60ff1690505b8115801590610a105750600084115b15610c0657600082815260026020526040902060018101548611610bfa57600085826002015410610a415785610a47565b81600201545b905086610a5584600a6118ee565b610a5f9083611885565b610a6990826118a7565b6001600160a01b03808b16600090815260046020908152604080832060015490941683529290529081208054909190610aa39084906118db565b90915550506001600160a01b03808a166000908152600460209081526040808320835490941683529290529081208054849290610ae19084906118fa565b909155505060038301546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054849290610b249084906118db565b90915550610b34905082886118fa565b965081836002016000828254610b4a91906118fa565b9091555050600183015460038401546040805185815260208101939093526001600160a01b03918216908301528a16907f83cc6330e30db670fe04b07b602bd9cba4da39e5037267131b5d60cecba29f619060600160405180910390a28260020154600003610bf35760048084015460009687526002602081905260408820888155600181018990559081018890556003810180546001600160a01b0319169055909101959095555b5050610c00565b50610c06565b50610a01565b506005555092915050565b60078054906000610c218361190d565b909155505060065460005b8115801590610c4b575060008281526003602052604090206001015484115b15610c69575060008181526003602052604090206004015490610c2c565b6040805160a08101825260075480825260208083018881528385018881526001600160a01b038b811660608701818152608088018b8152600097885260038088528a892099518a55955160018a0155935160028901555193870180546001600160a01b03191694831694909417909355905160049586015590835292815283822082549093168252919091529081208054859290610d089084906118fa565b9091555050600754604080516000815260208101929092526001600160a01b038716828201526060820186905260808201859052517f286a2d7a29403e4e0f090c310eeb9b565090f3731608308fe263443ca910e9d59181900360a00190a180600003610d7a57600754600655610d90565b6007546000828152600360205260409020600401555b5050505050565b600654600080546040805163313ce56760e01b8152905192939284926001600160a01b03169163313ce5679160048083019260209291908290030181865afa158015610de7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0b9190611752565b60ff1690505b8115801590610e205750600084115b1561103c5760008281526003602052604090206001810154861061103057600085826002015410610e515785610e57565b81600201545b6001830154909150610e6a84600a6118ee565b610e749083611885565b610e7e90826118a7565b6001600160a01b03808b16600090815260046020908152604080832060015490941683529290529081208054909190610eb89084906118fa565b90915550506001600160a01b03808a166000908152600460209081526040808320835490941683529290529081208054849290610ef69084906118db565b90915550610f07905084600a6118ee565b610f119083611885565b610f1b90826118a7565b60038401546001600160a01b03908116600090815260046020908152604080832060015490941683529290529081208054909190610f5a9084906118db565b90915550610f6a905082886118fa565b965081836002016000828254610f8091906118fa565b9091555050600183015460038401546040805185815260208101939093526001600160a01b03918216908301528a16907f83cc6330e30db670fe04b07b602bd9cba4da39e5037267131b5d60cecba29f619060600160405180910390a282600201546000036110295760048084015460009687526003602081905260408820888155600181018990556002810189905590810180546001600160a01b0319169055909101959095555b5050611036565b5061103c565b50610e11565b506006555092915050565b600780549060006110578361190d565b909155505060055460005b8115801590611081575060008281526002602052604090206001015484105b1561109f575060008181526002602052604090206004015490611062565b6040805160a08101825260075480825260208083018881528385018881526001600160a01b038b811660608701908152608087018a815260009687526002808752898820985189559451600189015592519387019390935591516003860180546001600160a01b031916918416919091179055516004948501559154845163313ce56760e01b8152945192169363313ce5679381810193918290030181865afa158015611150573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111749190611752565b61117f90600a611876565b6111899084611885565b61119390856118a7565b6001600160a01b038087166000908152600460209081526040808320600154909416835292905290812080549091906111cd9084906118fa565b9091555050600754604080516001815260208101929092526001600160a01b038716828201526060820186905260808201859052517f286a2d7a29403e4e0f090c310eeb9b565090f3731608308fe263443ca910e9d59181900360a00190a18060000361123f57600754600555610d90565b6007546000828152600260205260409020600401555050505050565b6001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906112929084906118fa565b909155505060405163a9059cbb60e01b81526001600160a01b0384811660048301526024820183905283169063a9059cbb906044016020604051808303816000875af11580156112e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061130a91906118be565b50604080516001600160a01b038481168252602082018490528516917f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb910161097a565b6006548154036113655760048101546006556113ea565b60065460009081526003602052604090205b81546004820154146113e05780600401546000036113c95760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b60448201526064016102f9565b600401546000908152600360205260409020611377565b6004808301549101555b600281015460038201546001600160a01b03908116600090815260046020908152604080832083549094168352929052908120805490919061142d9084906118db565b9091555050600060028201819055815460405191825233917fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a4906020015b60405180910390a350565b60055481540361148d576004810154600555611512565b60055460009081526002602052604090205b81546004820154146115085780600401546000036114f15760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b60448201526064016102f9565b60040154600090815260026020526040902061149f565b6004808301549101555b60008054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611563573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115879190611752565b61159290600a611876565b81600201546115a19190611885565b81600101546115b091906118a7565b60038201546001600160a01b039081166000908152600460209081526040808320600154909416835292905290812080549091906115ef9084906118db565b9091555050600060028201558054604051600181523391907fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a49060200161146b565b60006020828403121561164357600080fd5b5035919050565b80356001600160a01b038116811461166157600080fd5b919050565b6000806040838503121561167957600080fd5b6116828361164a565b946020939093013593505050565b600080604083850312156116a357600080fd5b50508035926020909101359150565b600080604083850312156116c557600080fd5b6116ce8361164a565b91506116dc6020840161164a565b90509250929050565b80151581146116f357600080fd5b50565b6000806040838503121561170957600080fd5b82359150602083013561171b816116e5565b809150509250929050565b6020808252601290820152714e6f7420656e6f7567682062616c616e636560701b604082015260600190565b60006020828403121561176457600080fd5b815160ff8116811461177557600080fd5b9392505050565b634e487b7160e01b600052601160045260246000fd5b600181815b808511156117cd5781600019048211156117b3576117b361177c565b808516156117c057918102915b93841c9390800290611797565b509250929050565b6000826117e45750600161045a565b816117f15750600061045a565b816001811461180757600281146118115761182d565b600191505061045a565b60ff8411156118225761182261177c565b50506001821b61045a565b5060208310610133831016604e8410600b8410161715611850575081810a61045a565b61185a8383611792565b806000190482111561186e5761186e61177c565b029392505050565b600061177560ff8416836117d5565b6000826118a257634e487b7160e01b600052601260045260246000fd5b500490565b808202811582820484141761045a5761045a61177c565b6000602082840312156118d057600080fd5b8151611775816116e5565b8082018082111561045a5761045a61177c565b600061177583836117d5565b8181038181111561045a5761045a61177c565b60006001820161191f5761191f61177c565b506001019056fea2646970667358221220257d0fcef9327e72e8c9d83d65d26bfb381402b318dc782e8f9f11345f4aabe764736f6c63430008180033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/data/abis/ExchangeFactory.json b/data/abis/ExchangeFactory.json new file mode 100644 index 0000000..2aae3f8 --- /dev/null +++ b/data/abis/ExchangeFactory.json @@ -0,0 +1,123 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "ExchangeFactory", + "sourceName": "contracts/orderbook/ExchangeFactory.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "exchange", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "deployer", + "type": "address" + } + ], + "name": "ExchangeDeployed", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "exchange", + "type": "address" + } + ], + "name": "availableExchanges", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + } + ], + "name": "deployExchange", + "outputs": [ + { + "internalType": "address", + "name": "exchange", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "exchangeDeployed", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "exchange", + "type": "address" + } + ], + "name": "isExchangeAvailable", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50611e52806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063443bc94a146100515780636b35ea281461009757806378c39d5f146100aa578063a397c073146100e6575b600080fd5b61007a61005f366004610321565b6001602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61007a6100a5366004610356565b610109565b6100d66100b8366004610389565b6001600160a01b031660009081526020819052604090205460ff1690565b604051901515815260200161008e565b6100d66100f4366004610389565b60006020819052908152604090205460ff1681565b6040516bffffffffffffffffffffffff1933606090811b8216602084015284811b8216603484015283901b1660488201526000908190605c0160408051601f198184030181529181528151602092830120600081815260019093529120549091506001600160a01b0316156101c45760405162461bcd60e51b815260206004820152601960248201527f45786368616e676520616c7265616479206465706c6f79656400000000000000604482015260640160405180910390fd5b6101cf818585610266565b600082815260016020818152604080842080546001600160a01b0319166001600160a01b0387811691821790925580865285845294829020805460ff1916909417909355805193845288831691840191909152908616908201523360608201529092507fa5b803b77d28e977c1af94d97267f3615377386271d1d18976e7d87309e04b0d9060800160405180910390a15092915050565b6000806040518060200161027990610314565b601f1982820381018352601f9091011660408181526001600160a01b038781166020840152861681830152805180830382018152606083019091529192506000906102ca90849084906080016103d4565b60405160208183030381529060405290506102e587826102f0565b979650505050505050565b60008082602001835185818334f592505050803b61030d57600080fd5b9392505050565b611a2b806103f283390190565b60006020828403121561033357600080fd5b5035919050565b80356001600160a01b038116811461035157600080fd5b919050565b6000806040838503121561036957600080fd5b6103728361033a565b91506103806020840161033a565b90509250929050565b60006020828403121561039b57600080fd5b61030d8261033a565b6000815160005b818110156103c557602081850181015186830152016103ab565b50600093019283525090919050565b60006103e96103e383866103a4565b846103a4565b94935050505056fe60806040523480156200001157600080fd5b5060405162001a2b38038062001a2b833981016040819052620000349162000087565b60016008819055600080546001600160a01b03199081166001600160a01b039586161790915581541691909216179055620000bf565b80516001600160a01b03811681146200008257600080fd5b919050565b600080604083850312156200009b57600080fd5b620000a6836200006a565b9150620000b6602084016200006a565b90509250929050565b61195c80620000cf6000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c8063925931fc1161008c578063ee36d4ab11610066578063ee36d4ab1461022c578063f3fef3a31461023f578063f7888aec14610252578063f88d20471461027d57600080fd5b8063925931fc14610207578063a4406bcd14610210578063bb110bf91461022357600080fd5b80630fc63d10146100d457806335cea2881461010457806347e7ef241461017e5780634a8393f3146101935780635f64b55b146101dd57806372fb0777146101f0575b600080fd5b6000546100e7906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61014d610112366004611631565b60026020819052600091825260409091208054600182015492820154600383015460049093015491939290916001600160a01b039091169085565b604080519586526020860194909452928401919091526001600160a01b03166060830152608082015260a0016100fb565b61019161018c366004611666565b610290565b005b61014d6101a1366004611631565b6003602081905260009182526040909120805460018201546002830154938301546004909301549193909290916001600160a01b039091169085565b6001546100e7906001600160a01b031681565b6101f960065481565b6040519081526020016100fb565b6101f960075481565b6101f961021e366004611690565b61035c565b6101f960055481565b6101f961023a366004611690565b610460565b61019161024d366004611666565b6105e3565b6101f96102603660046116b2565b600460209081526000928352604080842090915290825290205481565b61019161028b3660046116f6565b6106df565b61029861085b565b6000546001600160a01b03838116911614806102c157506001546001600160a01b038381169116145b6103025760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b60448201526064015b60405180910390fd5b600081116103435760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b60448201526064016102f9565b61034e338383610885565b6103586001600855565b5050565b600061036661085b565b600083116103a65760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064016102f9565b600082116103e75760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b60448201526064016102f9565b33600090815260046020908152604080832083546001600160a01b0316845290915290205482111561042b5760405162461bcd60e51b81526004016102f990611726565b6000610438338585610987565b9050801561044b5761044b338583610c11565b505060075461045a6001600855565b92915050565b600061046a61085b565b600083116104aa5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064016102f9565b600082116104eb5760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b60448201526064016102f9565b60008054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561053c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105609190611752565b61056b90600a611876565b6105759083611885565b61057f90846118a7565b3360009081526004602090815260408083206001546001600160a01b0316845290915290205410156105c35760405162461bcd60e51b81526004016102f990611726565b60006105d0338585610d97565b9050801561044b5761044b338583611047565b6105eb61085b565b6000546001600160a01b038381169116148061061457506001546001600160a01b038381169116145b6106505760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b60448201526064016102f9565b600081116106915760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b60448201526064016102f9565b3360009081526004602090815260408083206001600160a01b03861684529091529020548111156106d45760405162461bcd60e51b81526004016102f990611726565b61034e33838361125b565b6106e761085b565b600081610701576000838152600360205260409020610710565b60008381526002602052604090205b60038101549091506001600160a01b03166107635760405162461bcd60e51b81526020600482015260136024820152724f7264657220646f206e6f742065786973747360681b60448201526064016102f9565b60038101546001600160a01b031633146107d45760405162461bcd60e51b815260206004820152602c60248201527f4f6e6c7920746865206f726465722063726561746f722063616e2063616e636560448201526b36103a3434b99037b93232b960a11b60648201526084016102f9565b60008160020154116108345760405162461bcd60e51b8152602060048201526024808201527f4f7264657220616c72656164792063616e63656c6c6564206f722066756c66696044820152631b1b195960e21b60648201526084016102f9565b81610847576108428161134e565b610850565b61085081611476565b506103586001600855565b60026008540361087e57604051633ee5aeb560e01b815260040160405180910390fd5b6002600855565b6040516323b872dd60e01b81526001600160a01b038481166004830152306024830152604482018390528316906323b872dd906064016020604051808303816000875af11580156108da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108fe91906118be565b506001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906109369084906118db565b9091555050604080516001600160a01b038481168252602082018490528516917f5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f6291015b60405180910390a2505050565b600554600080546040805163313ce56760e01b8152905192939284926001600160a01b03169163313ce5679160048083019260209291908290030181865afa1580156109d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109fb9190611752565b60ff1690505b8115801590610a105750600084115b15610c0657600082815260026020526040902060018101548611610bfa57600085826002015410610a415785610a47565b81600201545b905086610a5584600a6118ee565b610a5f9083611885565b610a6990826118a7565b6001600160a01b03808b16600090815260046020908152604080832060015490941683529290529081208054909190610aa39084906118db565b90915550506001600160a01b03808a166000908152600460209081526040808320835490941683529290529081208054849290610ae19084906118fa565b909155505060038301546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054849290610b249084906118db565b90915550610b34905082886118fa565b965081836002016000828254610b4a91906118fa565b9091555050600183015460038401546040805185815260208101939093526001600160a01b03918216908301528a16907f83cc6330e30db670fe04b07b602bd9cba4da39e5037267131b5d60cecba29f619060600160405180910390a28260020154600003610bf35760048084015460009687526002602081905260408820888155600181018990559081018890556003810180546001600160a01b0319169055909101959095555b5050610c00565b50610c06565b50610a01565b506005555092915050565b60078054906000610c218361190d565b909155505060065460005b8115801590610c4b575060008281526003602052604090206001015484115b15610c69575060008181526003602052604090206004015490610c2c565b6040805160a08101825260075480825260208083018881528385018881526001600160a01b038b811660608701818152608088018b8152600097885260038088528a892099518a55955160018a0155935160028901555193870180546001600160a01b03191694831694909417909355905160049586015590835292815283822082549093168252919091529081208054859290610d089084906118fa565b9091555050600754604080516000815260208101929092526001600160a01b038716828201526060820186905260808201859052517f286a2d7a29403e4e0f090c310eeb9b565090f3731608308fe263443ca910e9d59181900360a00190a180600003610d7a57600754600655610d90565b6007546000828152600360205260409020600401555b5050505050565b600654600080546040805163313ce56760e01b8152905192939284926001600160a01b03169163313ce5679160048083019260209291908290030181865afa158015610de7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0b9190611752565b60ff1690505b8115801590610e205750600084115b1561103c5760008281526003602052604090206001810154861061103057600085826002015410610e515785610e57565b81600201545b6001830154909150610e6a84600a6118ee565b610e749083611885565b610e7e90826118a7565b6001600160a01b03808b16600090815260046020908152604080832060015490941683529290529081208054909190610eb89084906118fa565b90915550506001600160a01b03808a166000908152600460209081526040808320835490941683529290529081208054849290610ef69084906118db565b90915550610f07905084600a6118ee565b610f119083611885565b610f1b90826118a7565b60038401546001600160a01b03908116600090815260046020908152604080832060015490941683529290529081208054909190610f5a9084906118db565b90915550610f6a905082886118fa565b965081836002016000828254610f8091906118fa565b9091555050600183015460038401546040805185815260208101939093526001600160a01b03918216908301528a16907f83cc6330e30db670fe04b07b602bd9cba4da39e5037267131b5d60cecba29f619060600160405180910390a282600201546000036110295760048084015460009687526003602081905260408820888155600181018990556002810189905590810180546001600160a01b0319169055909101959095555b5050611036565b5061103c565b50610e11565b506006555092915050565b600780549060006110578361190d565b909155505060055460005b8115801590611081575060008281526002602052604090206001015484105b1561109f575060008181526002602052604090206004015490611062565b6040805160a08101825260075480825260208083018881528385018881526001600160a01b038b811660608701908152608087018a815260009687526002808752898820985189559451600189015592519387019390935591516003860180546001600160a01b031916918416919091179055516004948501559154845163313ce56760e01b8152945192169363313ce5679381810193918290030181865afa158015611150573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111749190611752565b61117f90600a611876565b6111899084611885565b61119390856118a7565b6001600160a01b038087166000908152600460209081526040808320600154909416835292905290812080549091906111cd9084906118fa565b9091555050600754604080516001815260208101929092526001600160a01b038716828201526060820186905260808201859052517f286a2d7a29403e4e0f090c310eeb9b565090f3731608308fe263443ca910e9d59181900360a00190a18060000361123f57600754600555610d90565b6007546000828152600260205260409020600401555050505050565b6001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906112929084906118fa565b909155505060405163a9059cbb60e01b81526001600160a01b0384811660048301526024820183905283169063a9059cbb906044016020604051808303816000875af11580156112e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061130a91906118be565b50604080516001600160a01b038481168252602082018490528516917f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb910161097a565b6006548154036113655760048101546006556113ea565b60065460009081526003602052604090205b81546004820154146113e05780600401546000036113c95760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b60448201526064016102f9565b600401546000908152600360205260409020611377565b6004808301549101555b600281015460038201546001600160a01b03908116600090815260046020908152604080832083549094168352929052908120805490919061142d9084906118db565b9091555050600060028201819055815460405191825233917fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a4906020015b60405180910390a350565b60055481540361148d576004810154600555611512565b60055460009081526002602052604090205b81546004820154146115085780600401546000036114f15760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b60448201526064016102f9565b60040154600090815260026020526040902061149f565b6004808301549101555b60008054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611563573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115879190611752565b61159290600a611876565b81600201546115a19190611885565b81600101546115b091906118a7565b60038201546001600160a01b039081166000908152600460209081526040808320600154909416835292905290812080549091906115ef9084906118db565b9091555050600060028201558054604051600181523391907fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a49060200161146b565b60006020828403121561164357600080fd5b5035919050565b80356001600160a01b038116811461166157600080fd5b919050565b6000806040838503121561167957600080fd5b6116828361164a565b946020939093013593505050565b600080604083850312156116a357600080fd5b50508035926020909101359150565b600080604083850312156116c557600080fd5b6116ce8361164a565b91506116dc6020840161164a565b90509250929050565b80151581146116f357600080fd5b50565b6000806040838503121561170957600080fd5b82359150602083013561171b816116e5565b809150509250929050565b6020808252601290820152714e6f7420656e6f7567682062616c616e636560701b604082015260600190565b60006020828403121561176457600080fd5b815160ff8116811461177557600080fd5b9392505050565b634e487b7160e01b600052601160045260246000fd5b600181815b808511156117cd5781600019048211156117b3576117b361177c565b808516156117c057918102915b93841c9390800290611797565b509250929050565b6000826117e45750600161045a565b816117f15750600061045a565b816001811461180757600281146118115761182d565b600191505061045a565b60ff8411156118225761182261177c565b50506001821b61045a565b5060208310610133831016604e8410600b8410161715611850575081810a61045a565b61185a8383611792565b806000190482111561186e5761186e61177c565b029392505050565b600061177560ff8416836117d5565b6000826118a257634e487b7160e01b600052601260045260246000fd5b500490565b808202811582820484141761045a5761045a61177c565b6000602082840312156118d057600080fd5b8151611775816116e5565b8082018082111561045a5761045a61177c565b600061177583836117d5565b8181038181111561045a5761045a61177c565b60006001820161191f5761191f61177c565b506001019056fea2646970667358221220257d0fcef9327e72e8c9d83d65d26bfb381402b318dc782e8f9f11345f4aabe764736f6c63430008180033a2646970667358221220817748b9071ca0de2b2e4ba4c31d8939f545702220a86007e85c4ccef8af4d0c64736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c8063443bc94a146100515780636b35ea281461009757806378c39d5f146100aa578063a397c073146100e6575b600080fd5b61007a61005f366004610321565b6001602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61007a6100a5366004610356565b610109565b6100d66100b8366004610389565b6001600160a01b031660009081526020819052604090205460ff1690565b604051901515815260200161008e565b6100d66100f4366004610389565b60006020819052908152604090205460ff1681565b6040516bffffffffffffffffffffffff1933606090811b8216602084015284811b8216603484015283901b1660488201526000908190605c0160408051601f198184030181529181528151602092830120600081815260019093529120549091506001600160a01b0316156101c45760405162461bcd60e51b815260206004820152601960248201527f45786368616e676520616c7265616479206465706c6f79656400000000000000604482015260640160405180910390fd5b6101cf818585610266565b600082815260016020818152604080842080546001600160a01b0319166001600160a01b0387811691821790925580865285845294829020805460ff1916909417909355805193845288831691840191909152908616908201523360608201529092507fa5b803b77d28e977c1af94d97267f3615377386271d1d18976e7d87309e04b0d9060800160405180910390a15092915050565b6000806040518060200161027990610314565b601f1982820381018352601f9091011660408181526001600160a01b038781166020840152861681830152805180830382018152606083019091529192506000906102ca90849084906080016103d4565b60405160208183030381529060405290506102e587826102f0565b979650505050505050565b60008082602001835185818334f592505050803b61030d57600080fd5b9392505050565b611a2b806103f283390190565b60006020828403121561033357600080fd5b5035919050565b80356001600160a01b038116811461035157600080fd5b919050565b6000806040838503121561036957600080fd5b6103728361033a565b91506103806020840161033a565b90509250929050565b60006020828403121561039b57600080fd5b61030d8261033a565b6000815160005b818110156103c557602081850181015186830152016103ab565b50600093019283525090919050565b60006103e96103e383866103a4565b846103a4565b94935050505056fe60806040523480156200001157600080fd5b5060405162001a2b38038062001a2b833981016040819052620000349162000087565b60016008819055600080546001600160a01b03199081166001600160a01b039586161790915581541691909216179055620000bf565b80516001600160a01b03811681146200008257600080fd5b919050565b600080604083850312156200009b57600080fd5b620000a6836200006a565b9150620000b6602084016200006a565b90509250929050565b61195c80620000cf6000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c8063925931fc1161008c578063ee36d4ab11610066578063ee36d4ab1461022c578063f3fef3a31461023f578063f7888aec14610252578063f88d20471461027d57600080fd5b8063925931fc14610207578063a4406bcd14610210578063bb110bf91461022357600080fd5b80630fc63d10146100d457806335cea2881461010457806347e7ef241461017e5780634a8393f3146101935780635f64b55b146101dd57806372fb0777146101f0575b600080fd5b6000546100e7906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61014d610112366004611631565b60026020819052600091825260409091208054600182015492820154600383015460049093015491939290916001600160a01b039091169085565b604080519586526020860194909452928401919091526001600160a01b03166060830152608082015260a0016100fb565b61019161018c366004611666565b610290565b005b61014d6101a1366004611631565b6003602081905260009182526040909120805460018201546002830154938301546004909301549193909290916001600160a01b039091169085565b6001546100e7906001600160a01b031681565b6101f960065481565b6040519081526020016100fb565b6101f960075481565b6101f961021e366004611690565b61035c565b6101f960055481565b6101f961023a366004611690565b610460565b61019161024d366004611666565b6105e3565b6101f96102603660046116b2565b600460209081526000928352604080842090915290825290205481565b61019161028b3660046116f6565b6106df565b61029861085b565b6000546001600160a01b03838116911614806102c157506001546001600160a01b038381169116145b6103025760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b60448201526064015b60405180910390fd5b600081116103435760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b60448201526064016102f9565b61034e338383610885565b6103586001600855565b5050565b600061036661085b565b600083116103a65760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064016102f9565b600082116103e75760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b60448201526064016102f9565b33600090815260046020908152604080832083546001600160a01b0316845290915290205482111561042b5760405162461bcd60e51b81526004016102f990611726565b6000610438338585610987565b9050801561044b5761044b338583610c11565b505060075461045a6001600855565b92915050565b600061046a61085b565b600083116104aa5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064016102f9565b600082116104eb5760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b60448201526064016102f9565b60008054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561053c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105609190611752565b61056b90600a611876565b6105759083611885565b61057f90846118a7565b3360009081526004602090815260408083206001546001600160a01b0316845290915290205410156105c35760405162461bcd60e51b81526004016102f990611726565b60006105d0338585610d97565b9050801561044b5761044b338583611047565b6105eb61085b565b6000546001600160a01b038381169116148061061457506001546001600160a01b038381169116145b6106505760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b60448201526064016102f9565b600081116106915760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b60448201526064016102f9565b3360009081526004602090815260408083206001600160a01b03861684529091529020548111156106d45760405162461bcd60e51b81526004016102f990611726565b61034e33838361125b565b6106e761085b565b600081610701576000838152600360205260409020610710565b60008381526002602052604090205b60038101549091506001600160a01b03166107635760405162461bcd60e51b81526020600482015260136024820152724f7264657220646f206e6f742065786973747360681b60448201526064016102f9565b60038101546001600160a01b031633146107d45760405162461bcd60e51b815260206004820152602c60248201527f4f6e6c7920746865206f726465722063726561746f722063616e2063616e636560448201526b36103a3434b99037b93232b960a11b60648201526084016102f9565b60008160020154116108345760405162461bcd60e51b8152602060048201526024808201527f4f7264657220616c72656164792063616e63656c6c6564206f722066756c66696044820152631b1b195960e21b60648201526084016102f9565b81610847576108428161134e565b610850565b61085081611476565b506103586001600855565b60026008540361087e57604051633ee5aeb560e01b815260040160405180910390fd5b6002600855565b6040516323b872dd60e01b81526001600160a01b038481166004830152306024830152604482018390528316906323b872dd906064016020604051808303816000875af11580156108da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108fe91906118be565b506001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906109369084906118db565b9091555050604080516001600160a01b038481168252602082018490528516917f5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f6291015b60405180910390a2505050565b600554600080546040805163313ce56760e01b8152905192939284926001600160a01b03169163313ce5679160048083019260209291908290030181865afa1580156109d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109fb9190611752565b60ff1690505b8115801590610a105750600084115b15610c0657600082815260026020526040902060018101548611610bfa57600085826002015410610a415785610a47565b81600201545b905086610a5584600a6118ee565b610a5f9083611885565b610a6990826118a7565b6001600160a01b03808b16600090815260046020908152604080832060015490941683529290529081208054909190610aa39084906118db565b90915550506001600160a01b03808a166000908152600460209081526040808320835490941683529290529081208054849290610ae19084906118fa565b909155505060038301546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054849290610b249084906118db565b90915550610b34905082886118fa565b965081836002016000828254610b4a91906118fa565b9091555050600183015460038401546040805185815260208101939093526001600160a01b03918216908301528a16907f83cc6330e30db670fe04b07b602bd9cba4da39e5037267131b5d60cecba29f619060600160405180910390a28260020154600003610bf35760048084015460009687526002602081905260408820888155600181018990559081018890556003810180546001600160a01b0319169055909101959095555b5050610c00565b50610c06565b50610a01565b506005555092915050565b60078054906000610c218361190d565b909155505060065460005b8115801590610c4b575060008281526003602052604090206001015484115b15610c69575060008181526003602052604090206004015490610c2c565b6040805160a08101825260075480825260208083018881528385018881526001600160a01b038b811660608701818152608088018b8152600097885260038088528a892099518a55955160018a0155935160028901555193870180546001600160a01b03191694831694909417909355905160049586015590835292815283822082549093168252919091529081208054859290610d089084906118fa565b9091555050600754604080516000815260208101929092526001600160a01b038716828201526060820186905260808201859052517f286a2d7a29403e4e0f090c310eeb9b565090f3731608308fe263443ca910e9d59181900360a00190a180600003610d7a57600754600655610d90565b6007546000828152600360205260409020600401555b5050505050565b600654600080546040805163313ce56760e01b8152905192939284926001600160a01b03169163313ce5679160048083019260209291908290030181865afa158015610de7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0b9190611752565b60ff1690505b8115801590610e205750600084115b1561103c5760008281526003602052604090206001810154861061103057600085826002015410610e515785610e57565b81600201545b6001830154909150610e6a84600a6118ee565b610e749083611885565b610e7e90826118a7565b6001600160a01b03808b16600090815260046020908152604080832060015490941683529290529081208054909190610eb89084906118fa565b90915550506001600160a01b03808a166000908152600460209081526040808320835490941683529290529081208054849290610ef69084906118db565b90915550610f07905084600a6118ee565b610f119083611885565b610f1b90826118a7565b60038401546001600160a01b03908116600090815260046020908152604080832060015490941683529290529081208054909190610f5a9084906118db565b90915550610f6a905082886118fa565b965081836002016000828254610f8091906118fa565b9091555050600183015460038401546040805185815260208101939093526001600160a01b03918216908301528a16907f83cc6330e30db670fe04b07b602bd9cba4da39e5037267131b5d60cecba29f619060600160405180910390a282600201546000036110295760048084015460009687526003602081905260408820888155600181018990556002810189905590810180546001600160a01b0319169055909101959095555b5050611036565b5061103c565b50610e11565b506006555092915050565b600780549060006110578361190d565b909155505060055460005b8115801590611081575060008281526002602052604090206001015484105b1561109f575060008181526002602052604090206004015490611062565b6040805160a08101825260075480825260208083018881528385018881526001600160a01b038b811660608701908152608087018a815260009687526002808752898820985189559451600189015592519387019390935591516003860180546001600160a01b031916918416919091179055516004948501559154845163313ce56760e01b8152945192169363313ce5679381810193918290030181865afa158015611150573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111749190611752565b61117f90600a611876565b6111899084611885565b61119390856118a7565b6001600160a01b038087166000908152600460209081526040808320600154909416835292905290812080549091906111cd9084906118fa565b9091555050600754604080516001815260208101929092526001600160a01b038716828201526060820186905260808201859052517f286a2d7a29403e4e0f090c310eeb9b565090f3731608308fe263443ca910e9d59181900360a00190a18060000361123f57600754600555610d90565b6007546000828152600260205260409020600401555050505050565b6001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906112929084906118fa565b909155505060405163a9059cbb60e01b81526001600160a01b0384811660048301526024820183905283169063a9059cbb906044016020604051808303816000875af11580156112e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061130a91906118be565b50604080516001600160a01b038481168252602082018490528516917f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb910161097a565b6006548154036113655760048101546006556113ea565b60065460009081526003602052604090205b81546004820154146113e05780600401546000036113c95760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b60448201526064016102f9565b600401546000908152600360205260409020611377565b6004808301549101555b600281015460038201546001600160a01b03908116600090815260046020908152604080832083549094168352929052908120805490919061142d9084906118db565b9091555050600060028201819055815460405191825233917fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a4906020015b60405180910390a350565b60055481540361148d576004810154600555611512565b60055460009081526002602052604090205b81546004820154146115085780600401546000036114f15760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b60448201526064016102f9565b60040154600090815260026020526040902061149f565b6004808301549101555b60008054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611563573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115879190611752565b61159290600a611876565b81600201546115a19190611885565b81600101546115b091906118a7565b60038201546001600160a01b039081166000908152600460209081526040808320600154909416835292905290812080549091906115ef9084906118db565b9091555050600060028201558054604051600181523391907fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a49060200161146b565b60006020828403121561164357600080fd5b5035919050565b80356001600160a01b038116811461166157600080fd5b919050565b6000806040838503121561167957600080fd5b6116828361164a565b946020939093013593505050565b600080604083850312156116a357600080fd5b50508035926020909101359150565b600080604083850312156116c557600080fd5b6116ce8361164a565b91506116dc6020840161164a565b90509250929050565b80151581146116f357600080fd5b50565b6000806040838503121561170957600080fd5b82359150602083013561171b816116e5565b809150509250929050565b6020808252601290820152714e6f7420656e6f7567682062616c616e636560701b604082015260600190565b60006020828403121561176457600080fd5b815160ff8116811461177557600080fd5b9392505050565b634e487b7160e01b600052601160045260246000fd5b600181815b808511156117cd5781600019048211156117b3576117b361177c565b808516156117c057918102915b93841c9390800290611797565b509250929050565b6000826117e45750600161045a565b816117f15750600061045a565b816001811461180757600281146118115761182d565b600191505061045a565b60ff8411156118225761182261177c565b50506001821b61045a565b5060208310610133831016604e8410600b8410161715611850575081810a61045a565b61185a8383611792565b806000190482111561186e5761186e61177c565b029392505050565b600061177560ff8416836117d5565b6000826118a257634e487b7160e01b600052601260045260246000fd5b500490565b808202811582820484141761045a5761045a61177c565b6000602082840312156118d057600080fd5b8151611775816116e5565b8082018082111561045a5761045a61177c565b600061177583836117d5565b8181038181111561045a5761045a61177c565b60006001820161191f5761191f61177c565b506001019056fea2646970667358221220257d0fcef9327e72e8c9d83d65d26bfb381402b318dc782e8f9f11345f4aabe764736f6c63430008180033a2646970667358221220817748b9071ca0de2b2e4ba4c31d8939f545702220a86007e85c4ccef8af4d0c64736f6c63430008180033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/data/abis/ExchangeFactoryHTS.json b/data/abis/ExchangeFactoryHTS.json new file mode 100644 index 0000000..707a35b --- /dev/null +++ b/data/abis/ExchangeFactoryHTS.json @@ -0,0 +1,123 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "ExchangeFactoryHTS", + "sourceName": "contracts/orderbook/hts/ExchangeFactoryHTS.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "exchange", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "deployer", + "type": "address" + } + ], + "name": "ExchangeDeployed", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "exchange", + "type": "address" + } + ], + "name": "availableExchanges", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + } + ], + "name": "deployExchange", + "outputs": [ + { + "internalType": "address", + "name": "exchange", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "exchangeDeployed", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "exchange", + "type": "address" + } + ], + "name": "isExchangeAvailable", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50612194806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063443bc94a146100515780636b35ea281461009757806378c39d5f146100aa578063a397c073146100e6575b600080fd5b61007a61005f366004610321565b6001602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61007a6100a5366004610356565b610109565b6100d66100b8366004610389565b6001600160a01b031660009081526020819052604090205460ff1690565b604051901515815260200161008e565b6100d66100f4366004610389565b60006020819052908152604090205460ff1681565b6040516bffffffffffffffffffffffff1933606090811b8216602084015284811b8216603484015283901b1660488201526000908190605c0160408051601f198184030181529181528151602092830120600081815260019093529120549091506001600160a01b0316156101c45760405162461bcd60e51b815260206004820152601960248201527f45786368616e676520616c7265616479206465706c6f79656400000000000000604482015260640160405180910390fd5b6101cf818585610266565b600082815260016020818152604080842080546001600160a01b0319166001600160a01b0387811691821790925580865285845294829020805460ff1916909417909355805193845288831691840191909152908616908201523360608201529092507fa5b803b77d28e977c1af94d97267f3615377386271d1d18976e7d87309e04b0d9060800160405180910390a15092915050565b6000806040518060200161027990610314565b601f1982820381018352601f9091011660408181526001600160a01b038781166020840152861681830152805180830382018152606083019091529192506000906102ca90849084906080016103d4565b60405160208183030381529060405290506102e587826102f0565b979650505050505050565b60008082602001835185818334f592505050803b61030d57600080fd5b9392505050565b611d6d806103f283390190565b60006020828403121561033357600080fd5b5035919050565b80356001600160a01b038116811461035157600080fd5b919050565b6000806040838503121561036957600080fd5b6103728361033a565b91506103806020840161033a565b90509250929050565b60006020828403121561039b57600080fd5b61030d8261033a565b6000815160005b818110156103c557602081850181015186830152016103ab565b50600093019283525090919050565b60006103e96103e383866103a4565b846103a4565b94935050505056fe60806040523480156200001157600080fd5b5060405162001d6d38038062001d6d8339810160408190526200003491620001e1565b60016008819055600080546001600160a01b038086166001600160a01b03199283168117909355835490851691161790915562000072903062000092565b6001546200008a906001600160a01b03163062000092565b505062000276565b604080516001600160a01b038381166024830152841660448083019190915282518083039091018152606490910182526020810180516001600160e01b031663248a35ef60e11b17905290516000918291829161016791620000f5919062000219565b6000604051808303816000865af19150503d806000811462000134576040519150601f19603f3d011682016040523d82523d6000602084013e62000139565b606091505b5091509150816200014c57601562000162565b808060200190518101906200016291906200024a565b9250600383900b601614620001bd5760405162461bcd60e51b815260206004820152601f60248201527f536166652073696e676c65206173736f63696174696f6e206661696c65642100604482015260640160405180910390fd5b5050505050565b80516001600160a01b0381168114620001dc57600080fd5b919050565b60008060408385031215620001f557600080fd5b6200020083620001c4565b91506200021060208401620001c4565b90509250929050565b6000825160005b818110156200023c576020818601810151858301520162000220565b506000920191825250919050565b6000602082840312156200025d57600080fd5b81518060030b81146200026f57600080fd5b9392505050565b611ae780620002866000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806372fb07771161008c578063bb110bf911610066578063bb110bf91461024d578063dd5813ee14610256578063f7888aec14610269578063f88d2047146102aa57600080fd5b806372fb077714610226578063925931fc1461022f57806392c029911461023857600080fd5b806303c4c2e1146100d45780630fc63d10146100fa5780632ec2add71461012557806335cea288146101385780634a8393f3146101c15780635f64b55b14610213575b600080fd5b6100e76100e236600461184d565b6102bd565b6040519081526020015b60405180910390f35b60005461010d906001600160a01b031681565b6040516001600160a01b0390911681526020016100f1565b6100e761013336600461184d565b6103f1565b61018a610146366004611880565b600260208190526000918252604090912080546001820154928201546003909201549092600781810b93600160401b909204900b916001600160a01b039091169085565b60408051958652600794850b60208701529290930b918401919091526001600160a01b03166060830152608082015260a0016100f1565b61018a6101cf366004611880565b600360208190526000918252604090912080546001820154600283015492909301549092600781810b93600160401b909204900b916001600160a01b039091169085565b60015461010d906001600160a01b031681565b6100e760065481565b6100e760075481565b61024b6102463660046118b0565b6104f3565b005b6100e760055481565b61024b6102643660046118b0565b610607565b6102976102773660046118cc565b600460209081526000928352604080842090915290825290205460070b81565b60405160079190910b81526020016100f1565b61024b6102b83660046118f6565b6106c3565b60006102c761084c565b60008360070b1361030f5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064015b60405180910390fd5b60008260070b136103535760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b6044820152606401610306565b6103616305f5e10083611941565b61036b908461198e565b3360009081526004602090815260408083206001546001600160a01b03168452909152902054600791820b910b12156103b65760405162461bcd60e51b8152600401610306906119b5565b60006103c3338585610876565b905060008160070b13156103dc576103dc338583610b96565b50506007546103eb6001600855565b92915050565b60006103fb61084c565b60008360070b1361043e5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b6044820152606401610306565b60008260070b136104825760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b6044820152606401610306565b33600090815260046020908152604080832083546001600160a01b03168452909152902054600783810b91900b12156104cd5760405162461bcd60e51b8152600401610306906119b5565b60006104da338585610da7565b905060008160070b13156103dc576103dc3385836110a1565b6104fb61084c565b6000546001600160a01b038381169116148061052457506001546001600160a01b038381169116145b6105605760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b6044820152606401610306565b60008160070b136105a45760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b6044820152606401610306565b3360009081526004602090815260408083206001600160a01b0386168452909152902054600782810b91900b12156105ee5760405162461bcd60e51b8152600401610306906119b5565b6105f9338383611287565b6106036001600855565b5050565b61060f61084c565b6000546001600160a01b038381169116148061063857506001546001600160a01b038381169116145b6106745760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b6044820152606401610306565b60008160070b136106b85760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b6044820152606401610306565b6105f933838361134b565b6106cb61084c565b6000816106e55760008381526003602052604090206106f4565b60008381526002602052604090205b60028101549091506001600160a01b03166107485760405162461bcd60e51b815260206004820152601460248201527313dc99195c88191bd95cc81b9bdd08195e1a5cdd60621b6044820152606401610306565b60028101546001600160a01b031633146107b95760405162461bcd60e51b815260206004820152602c60248201527f4f6e6c7920746865206f726465722063726561746f722063616e2063616e636560448201526b36103a3434b99037b93232b960a11b6064820152608401610306565b60018101546000600160401b90910460070b136108245760405162461bcd60e51b8152602060048201526024808201527f4f7264657220616c72656164792063616e63656c6c6564206f722066756c66696044820152631b1b195960e21b6064820152608401610306565b81156108385761083381611402565b610841565b61084181611591565b506106036001600855565b60026008540361086f57604051633ee5aeb560e01b815260040160405180910390fd5b6002600855565b600654600090600a825b8215801590610892575060008560070b135b801561089d57508181105b15610b8a5760008381526003602052604090206001810154600790810b9088900b12610b71576001810154600090600788810b600160401b909204900b126108e557866108f5565b6001820154600160401b900460070b5b600183015490915060070b61090e6305f5e10083611941565b610918908261198e565b6001600160a01b03808c1660009081526004602090815260408083206001549094168352929052908120805490919061095590849060070b6119e1565b82546001600160401b039182166101009390930a9283029190920219909116179055506001600160a01b03808b1660009081526004602090815260408083208354909416835292905290812080548492906109b490849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055506305f5e100826109eb9190611941565b6109f5908261198e565b60028401546001600160a01b03908116600090815260046020908152604080832060015490941683529290529081208054909190610a3790849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055508188610a6a91906119e1565b6001840180549199508391600890610a8d908490600160401b900460070b6119e1565b82546101009290920a6001600160401b03818102199093169190921691909102179055506001830154600284015460408051600786810b82529390930b60208401526001600160a01b038d8116848301529091166060830152517f3811c688c91a2195acbf84f4d5e7adc8d2f17ba048f1051955046e511411fd7d9181900360800190a16001830154600160401b900460070b600003610b6a5760038084015460009788526020829052604088208881556001810180546001600160801b03191690556002810180546001600160a01b0319169055909101969096555b5050610b77565b50610b8a565b81610b8181611a3f565b92505050610880565b50506006555092915050565b60078054906000610ba683611a3f565b909155505060055460005b8115801590610bd75750600082815260026020526040902060010154600785810b91900b135b15610bf5575060008181526002602052604090206003015490610bb1565b6040805160a0810182526007805480835287820b60208085019182529288900b8486019081526001600160a01b03808c1660608701908152608087018a815260009586526002968790529790942095518655915160018601805492516001600160401b03908116600160401b026001600160801b031990941692169190911791909117905590519183018054929091166001600160a01b03199092169190911790559051600390910155610cad6305f5e10084611941565b610cb7908561198e565b6001600160a01b03808716600090815260046020908152604080832060015490941683529290529081208054909190610cf490849060070b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060078054604080516001815260208101929092526001600160a01b0388169082015285820b60608201529084900b60808201527fddca83c332d5b33951e90d550eefc003b7a1efae1cdf8c4d684d4ccdf2cd8d599060a00160405180910390a180600003610d8a57600754600555610da0565b6007546000828152600260205260409020600301555b5050505050565b600554600090600a825b8215801590610dc3575060008560070b135b8015610dce57508181105b156110955760008381526002602052604090206001810154600790810b9088900b1361107c576001810154600090600788810b600160401b909204900b12610e165786610e26565b6001820154600160401b900460070b5b905087610e376305f5e10083611941565b610e41908261198e565b6001600160a01b03808c16600090815260046020908152604080832060015490941683529290529081208054909190610e7e90849060070b611a10565b82546001600160401b039182166101009390930a9283029190920219909116179055506001600160a01b03808b166000908152600460209081526040808320835490941683529290529081208054849290610edd90849060070b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060028301546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054849290610f4190849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055508188610f7491906119e1565b6001840180549199508391600890610f97908490600160401b900460070b6119e1565b82546101009290920a6001600160401b03818102199093169190921691909102179055506001830154600284015460408051600786810b82529390930b60208401526001600160a01b038d8116848301529091166060830152517f3811c688c91a2195acbf84f4d5e7adc8d2f17ba048f1051955046e511411fd7d9181900360800190a16001830154600160401b900460070b60000361107557600380840154600097885260026020819052604089208981556001810180546001600160801b031916905590810180546001600160a01b0319169055909101969096555b5050611082565b50611095565b8161108c81611a3f565b92505050610db1565b50506005555092915050565b600780549060006110b183611a3f565b909155505060065460005b81158015906110e25750600082815260036020526040902060010154600785810b91900b125b15611101575060008181526003602081905260409091200154906110bc565b6040805160a0810182526007805480835287820b602080850191825288840b8587019081526001600160a01b03808d1660608801818152608089018c8152600097885260038087528b89209a518b55965160018b01805496516001600160401b03908116600160401b026001600160801b031990981692169190911795909517909455516002890180549184166001600160a01b0319909216919091179055915196909301959095559382526004845284822082549091168252909252918120805486939192916111d49185910b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060078054604080516000815260208101929092526001600160a01b0388169082015285820b60608201529084900b60808201527fddca83c332d5b33951e90d550eefc003b7a1efae1cdf8c4d684d4ccdf2cd8d599060a00160405180910390a18060000361126a57600754600655610da0565b600754600082815260036020819052604090912001555050505050565b6001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906112c190849060070b6119e1565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055506112f4823085846116fd565b604080516001600160a01b03808616825284166020820152600783900b918101919091527fd09be44e70d8a46d3b3b8da9701595dcaec29d75930065918923c7d9fd73eb58906060015b60405180910390a1505050565b611357828430846116fd565b6001600160a01b0380841660009081526004602090815260408083209386168352929052908120805483929061139190849060070b611a10565b82546001600160401b039182166101009390930a928302919092021990911617905550604080516001600160a01b03808616825284166020820152600783900b918101919091527f64e3b56a34ae45fe7d1f33e3d3da391a671e6043606ac9c811a6c1243023841d9060600161133e565b60055481540361141957600381015460055561149e565b60055460009081526002602052604090205b815460038201541461149457806003015460000361147d5760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b6044820152606401610306565b60030154600090815260026020526040902061142b565b6003808301549101555b60018101546114bc906305f5e10090600160401b900460070b611941565b60018201546114ce919060070b61198e565b60028201546001600160a01b0390811660009081526004602090815260408083206001549094168352929052908120805490919061151090849060070b611a10565b82546001600160401b039182166101009390930a9283029190920219909116179055506001818101805467ffffffffffffffff60401b19169055815460408051928352602083019190915233908201527fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a4906060015b60405180910390a150565b6006548154036115a8576003810154600655611630565b60065460009081526003602052604090205b815460038201541461162657806003015460000361160c5760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b6044820152606401610306565b6003908101546000908152602091909152604090206115ba565b6003808301549101555b600181015460028201546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054600160401b909304600790810b93919291611683918591900b611a10565b82546001600160401b039182166101009390930a92830291909202199091161790555060018101805467ffffffffffffffff60401b1916905580546040805160008152602081019290925233908201527fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a490606001611586565b604080516001600160a01b038681166024830152858116604483015284166064820152600783900b6084808301919091528251808303909101815260a490910182526020810180516001600160e01b031663eca3691760e01b179052905160009182918291610167916117709190611a58565b6000604051808303816000865af19150503d80600081146117ad576040519150601f19603f3d011682016040523d82523d6000602084013e6117b2565b606091505b5091509150816117c35760156117d7565b808060200190518101906117d79190611a87565b9250600383900b60161461182d5760405162461bcd60e51b815260206004820152601b60248201527f5361666520746f6b656e207472616e73666572206661696c65642100000000006044820152606401610306565b50505050505050565b8035600781900b811461184857600080fd5b919050565b6000806040838503121561186057600080fd5b61186983611836565b915061187760208401611836565b90509250929050565b60006020828403121561189257600080fd5b5035919050565b80356001600160a01b038116811461184857600080fd5b600080604083850312156118c357600080fd5b61186983611899565b600080604083850312156118df57600080fd5b6118e883611899565b915061187760208401611899565b6000806040838503121561190957600080fd5b823591506020830135801515811461192057600080fd5b809150509250929050565b634e487b7160e01b600052601160045260246000fd5b60008160070b8360070b8061196657634e487b7160e01b600052601260045260246000fd5b677fffffffffffffff198214600019821416156119855761198561192b565b90059392505050565b60008260070b8260070b028060070b91508082146119ae576119ae61192b565b5092915050565b6020808252601290820152714e6f7420656e6f7567682062616c616e636560701b604082015260600190565b600782810b9082900b03677fffffffffffffff198112677fffffffffffffff821317156103eb576103eb61192b565b600781810b9083900b01677fffffffffffffff8113677fffffffffffffff19821217156103eb576103eb61192b565b600060018201611a5157611a5161192b565b5060010190565b6000825160005b81811015611a795760208186018101518583015201611a5f565b506000920191825250919050565b600060208284031215611a9957600080fd5b81518060030b8114611aaa57600080fd5b939250505056fea26469706673582212205aa48bb78bfafd55a1384ef36fe88a0c230cf3807ce8924547abd90a0159119064736f6c63430008180033a2646970667358221220ee260359a878572e79fea58191e64d978b62ba0101ca875e8e46a04f026576ea64736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c8063443bc94a146100515780636b35ea281461009757806378c39d5f146100aa578063a397c073146100e6575b600080fd5b61007a61005f366004610321565b6001602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61007a6100a5366004610356565b610109565b6100d66100b8366004610389565b6001600160a01b031660009081526020819052604090205460ff1690565b604051901515815260200161008e565b6100d66100f4366004610389565b60006020819052908152604090205460ff1681565b6040516bffffffffffffffffffffffff1933606090811b8216602084015284811b8216603484015283901b1660488201526000908190605c0160408051601f198184030181529181528151602092830120600081815260019093529120549091506001600160a01b0316156101c45760405162461bcd60e51b815260206004820152601960248201527f45786368616e676520616c7265616479206465706c6f79656400000000000000604482015260640160405180910390fd5b6101cf818585610266565b600082815260016020818152604080842080546001600160a01b0319166001600160a01b0387811691821790925580865285845294829020805460ff1916909417909355805193845288831691840191909152908616908201523360608201529092507fa5b803b77d28e977c1af94d97267f3615377386271d1d18976e7d87309e04b0d9060800160405180910390a15092915050565b6000806040518060200161027990610314565b601f1982820381018352601f9091011660408181526001600160a01b038781166020840152861681830152805180830382018152606083019091529192506000906102ca90849084906080016103d4565b60405160208183030381529060405290506102e587826102f0565b979650505050505050565b60008082602001835185818334f592505050803b61030d57600080fd5b9392505050565b611d6d806103f283390190565b60006020828403121561033357600080fd5b5035919050565b80356001600160a01b038116811461035157600080fd5b919050565b6000806040838503121561036957600080fd5b6103728361033a565b91506103806020840161033a565b90509250929050565b60006020828403121561039b57600080fd5b61030d8261033a565b6000815160005b818110156103c557602081850181015186830152016103ab565b50600093019283525090919050565b60006103e96103e383866103a4565b846103a4565b94935050505056fe60806040523480156200001157600080fd5b5060405162001d6d38038062001d6d8339810160408190526200003491620001e1565b60016008819055600080546001600160a01b038086166001600160a01b03199283168117909355835490851691161790915562000072903062000092565b6001546200008a906001600160a01b03163062000092565b505062000276565b604080516001600160a01b038381166024830152841660448083019190915282518083039091018152606490910182526020810180516001600160e01b031663248a35ef60e11b17905290516000918291829161016791620000f5919062000219565b6000604051808303816000865af19150503d806000811462000134576040519150601f19603f3d011682016040523d82523d6000602084013e62000139565b606091505b5091509150816200014c57601562000162565b808060200190518101906200016291906200024a565b9250600383900b601614620001bd5760405162461bcd60e51b815260206004820152601f60248201527f536166652073696e676c65206173736f63696174696f6e206661696c65642100604482015260640160405180910390fd5b5050505050565b80516001600160a01b0381168114620001dc57600080fd5b919050565b60008060408385031215620001f557600080fd5b6200020083620001c4565b91506200021060208401620001c4565b90509250929050565b6000825160005b818110156200023c576020818601810151858301520162000220565b506000920191825250919050565b6000602082840312156200025d57600080fd5b81518060030b81146200026f57600080fd5b9392505050565b611ae780620002866000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806372fb07771161008c578063bb110bf911610066578063bb110bf91461024d578063dd5813ee14610256578063f7888aec14610269578063f88d2047146102aa57600080fd5b806372fb077714610226578063925931fc1461022f57806392c029911461023857600080fd5b806303c4c2e1146100d45780630fc63d10146100fa5780632ec2add71461012557806335cea288146101385780634a8393f3146101c15780635f64b55b14610213575b600080fd5b6100e76100e236600461184d565b6102bd565b6040519081526020015b60405180910390f35b60005461010d906001600160a01b031681565b6040516001600160a01b0390911681526020016100f1565b6100e761013336600461184d565b6103f1565b61018a610146366004611880565b600260208190526000918252604090912080546001820154928201546003909201549092600781810b93600160401b909204900b916001600160a01b039091169085565b60408051958652600794850b60208701529290930b918401919091526001600160a01b03166060830152608082015260a0016100f1565b61018a6101cf366004611880565b600360208190526000918252604090912080546001820154600283015492909301549092600781810b93600160401b909204900b916001600160a01b039091169085565b60015461010d906001600160a01b031681565b6100e760065481565b6100e760075481565b61024b6102463660046118b0565b6104f3565b005b6100e760055481565b61024b6102643660046118b0565b610607565b6102976102773660046118cc565b600460209081526000928352604080842090915290825290205460070b81565b60405160079190910b81526020016100f1565b61024b6102b83660046118f6565b6106c3565b60006102c761084c565b60008360070b1361030f5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064015b60405180910390fd5b60008260070b136103535760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b6044820152606401610306565b6103616305f5e10083611941565b61036b908461198e565b3360009081526004602090815260408083206001546001600160a01b03168452909152902054600791820b910b12156103b65760405162461bcd60e51b8152600401610306906119b5565b60006103c3338585610876565b905060008160070b13156103dc576103dc338583610b96565b50506007546103eb6001600855565b92915050565b60006103fb61084c565b60008360070b1361043e5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b6044820152606401610306565b60008260070b136104825760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b6044820152606401610306565b33600090815260046020908152604080832083546001600160a01b03168452909152902054600783810b91900b12156104cd5760405162461bcd60e51b8152600401610306906119b5565b60006104da338585610da7565b905060008160070b13156103dc576103dc3385836110a1565b6104fb61084c565b6000546001600160a01b038381169116148061052457506001546001600160a01b038381169116145b6105605760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b6044820152606401610306565b60008160070b136105a45760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b6044820152606401610306565b3360009081526004602090815260408083206001600160a01b0386168452909152902054600782810b91900b12156105ee5760405162461bcd60e51b8152600401610306906119b5565b6105f9338383611287565b6106036001600855565b5050565b61060f61084c565b6000546001600160a01b038381169116148061063857506001546001600160a01b038381169116145b6106745760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b6044820152606401610306565b60008160070b136106b85760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b6044820152606401610306565b6105f933838361134b565b6106cb61084c565b6000816106e55760008381526003602052604090206106f4565b60008381526002602052604090205b60028101549091506001600160a01b03166107485760405162461bcd60e51b815260206004820152601460248201527313dc99195c88191bd95cc81b9bdd08195e1a5cdd60621b6044820152606401610306565b60028101546001600160a01b031633146107b95760405162461bcd60e51b815260206004820152602c60248201527f4f6e6c7920746865206f726465722063726561746f722063616e2063616e636560448201526b36103a3434b99037b93232b960a11b6064820152608401610306565b60018101546000600160401b90910460070b136108245760405162461bcd60e51b8152602060048201526024808201527f4f7264657220616c72656164792063616e63656c6c6564206f722066756c66696044820152631b1b195960e21b6064820152608401610306565b81156108385761083381611402565b610841565b61084181611591565b506106036001600855565b60026008540361086f57604051633ee5aeb560e01b815260040160405180910390fd5b6002600855565b600654600090600a825b8215801590610892575060008560070b135b801561089d57508181105b15610b8a5760008381526003602052604090206001810154600790810b9088900b12610b71576001810154600090600788810b600160401b909204900b126108e557866108f5565b6001820154600160401b900460070b5b600183015490915060070b61090e6305f5e10083611941565b610918908261198e565b6001600160a01b03808c1660009081526004602090815260408083206001549094168352929052908120805490919061095590849060070b6119e1565b82546001600160401b039182166101009390930a9283029190920219909116179055506001600160a01b03808b1660009081526004602090815260408083208354909416835292905290812080548492906109b490849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055506305f5e100826109eb9190611941565b6109f5908261198e565b60028401546001600160a01b03908116600090815260046020908152604080832060015490941683529290529081208054909190610a3790849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055508188610a6a91906119e1565b6001840180549199508391600890610a8d908490600160401b900460070b6119e1565b82546101009290920a6001600160401b03818102199093169190921691909102179055506001830154600284015460408051600786810b82529390930b60208401526001600160a01b038d8116848301529091166060830152517f3811c688c91a2195acbf84f4d5e7adc8d2f17ba048f1051955046e511411fd7d9181900360800190a16001830154600160401b900460070b600003610b6a5760038084015460009788526020829052604088208881556001810180546001600160801b03191690556002810180546001600160a01b0319169055909101969096555b5050610b77565b50610b8a565b81610b8181611a3f565b92505050610880565b50506006555092915050565b60078054906000610ba683611a3f565b909155505060055460005b8115801590610bd75750600082815260026020526040902060010154600785810b91900b135b15610bf5575060008181526002602052604090206003015490610bb1565b6040805160a0810182526007805480835287820b60208085019182529288900b8486019081526001600160a01b03808c1660608701908152608087018a815260009586526002968790529790942095518655915160018601805492516001600160401b03908116600160401b026001600160801b031990941692169190911791909117905590519183018054929091166001600160a01b03199092169190911790559051600390910155610cad6305f5e10084611941565b610cb7908561198e565b6001600160a01b03808716600090815260046020908152604080832060015490941683529290529081208054909190610cf490849060070b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060078054604080516001815260208101929092526001600160a01b0388169082015285820b60608201529084900b60808201527fddca83c332d5b33951e90d550eefc003b7a1efae1cdf8c4d684d4ccdf2cd8d599060a00160405180910390a180600003610d8a57600754600555610da0565b6007546000828152600260205260409020600301555b5050505050565b600554600090600a825b8215801590610dc3575060008560070b135b8015610dce57508181105b156110955760008381526002602052604090206001810154600790810b9088900b1361107c576001810154600090600788810b600160401b909204900b12610e165786610e26565b6001820154600160401b900460070b5b905087610e376305f5e10083611941565b610e41908261198e565b6001600160a01b03808c16600090815260046020908152604080832060015490941683529290529081208054909190610e7e90849060070b611a10565b82546001600160401b039182166101009390930a9283029190920219909116179055506001600160a01b03808b166000908152600460209081526040808320835490941683529290529081208054849290610edd90849060070b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060028301546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054849290610f4190849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055508188610f7491906119e1565b6001840180549199508391600890610f97908490600160401b900460070b6119e1565b82546101009290920a6001600160401b03818102199093169190921691909102179055506001830154600284015460408051600786810b82529390930b60208401526001600160a01b038d8116848301529091166060830152517f3811c688c91a2195acbf84f4d5e7adc8d2f17ba048f1051955046e511411fd7d9181900360800190a16001830154600160401b900460070b60000361107557600380840154600097885260026020819052604089208981556001810180546001600160801b031916905590810180546001600160a01b0319169055909101969096555b5050611082565b50611095565b8161108c81611a3f565b92505050610db1565b50506005555092915050565b600780549060006110b183611a3f565b909155505060065460005b81158015906110e25750600082815260036020526040902060010154600785810b91900b125b15611101575060008181526003602081905260409091200154906110bc565b6040805160a0810182526007805480835287820b602080850191825288840b8587019081526001600160a01b03808d1660608801818152608089018c8152600097885260038087528b89209a518b55965160018b01805496516001600160401b03908116600160401b026001600160801b031990981692169190911795909517909455516002890180549184166001600160a01b0319909216919091179055915196909301959095559382526004845284822082549091168252909252918120805486939192916111d49185910b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060078054604080516000815260208101929092526001600160a01b0388169082015285820b60608201529084900b60808201527fddca83c332d5b33951e90d550eefc003b7a1efae1cdf8c4d684d4ccdf2cd8d599060a00160405180910390a18060000361126a57600754600655610da0565b600754600082815260036020819052604090912001555050505050565b6001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906112c190849060070b6119e1565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055506112f4823085846116fd565b604080516001600160a01b03808616825284166020820152600783900b918101919091527fd09be44e70d8a46d3b3b8da9701595dcaec29d75930065918923c7d9fd73eb58906060015b60405180910390a1505050565b611357828430846116fd565b6001600160a01b0380841660009081526004602090815260408083209386168352929052908120805483929061139190849060070b611a10565b82546001600160401b039182166101009390930a928302919092021990911617905550604080516001600160a01b03808616825284166020820152600783900b918101919091527f64e3b56a34ae45fe7d1f33e3d3da391a671e6043606ac9c811a6c1243023841d9060600161133e565b60055481540361141957600381015460055561149e565b60055460009081526002602052604090205b815460038201541461149457806003015460000361147d5760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b6044820152606401610306565b60030154600090815260026020526040902061142b565b6003808301549101555b60018101546114bc906305f5e10090600160401b900460070b611941565b60018201546114ce919060070b61198e565b60028201546001600160a01b0390811660009081526004602090815260408083206001549094168352929052908120805490919061151090849060070b611a10565b82546001600160401b039182166101009390930a9283029190920219909116179055506001818101805467ffffffffffffffff60401b19169055815460408051928352602083019190915233908201527fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a4906060015b60405180910390a150565b6006548154036115a8576003810154600655611630565b60065460009081526003602052604090205b815460038201541461162657806003015460000361160c5760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b6044820152606401610306565b6003908101546000908152602091909152604090206115ba565b6003808301549101555b600181015460028201546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054600160401b909304600790810b93919291611683918591900b611a10565b82546001600160401b039182166101009390930a92830291909202199091161790555060018101805467ffffffffffffffff60401b1916905580546040805160008152602081019290925233908201527fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a490606001611586565b604080516001600160a01b038681166024830152858116604483015284166064820152600783900b6084808301919091528251808303909101815260a490910182526020810180516001600160e01b031663eca3691760e01b179052905160009182918291610167916117709190611a58565b6000604051808303816000865af19150503d80600081146117ad576040519150601f19603f3d011682016040523d82523d6000602084013e6117b2565b606091505b5091509150816117c35760156117d7565b808060200190518101906117d79190611a87565b9250600383900b60161461182d5760405162461bcd60e51b815260206004820152601b60248201527f5361666520746f6b656e207472616e73666572206661696c65642100000000006044820152606401610306565b50505050505050565b8035600781900b811461184857600080fd5b919050565b6000806040838503121561186057600080fd5b61186983611836565b915061187760208401611836565b90509250929050565b60006020828403121561189257600080fd5b5035919050565b80356001600160a01b038116811461184857600080fd5b600080604083850312156118c357600080fd5b61186983611899565b600080604083850312156118df57600080fd5b6118e883611899565b915061187760208401611899565b6000806040838503121561190957600080fd5b823591506020830135801515811461192057600080fd5b809150509250929050565b634e487b7160e01b600052601160045260246000fd5b60008160070b8360070b8061196657634e487b7160e01b600052601260045260246000fd5b677fffffffffffffff198214600019821416156119855761198561192b565b90059392505050565b60008260070b8260070b028060070b91508082146119ae576119ae61192b565b5092915050565b6020808252601290820152714e6f7420656e6f7567682062616c616e636560701b604082015260600190565b600782810b9082900b03677fffffffffffffff198112677fffffffffffffff821317156103eb576103eb61192b565b600781810b9083900b01677fffffffffffffff8113677fffffffffffffff19821217156103eb576103eb61192b565b600060018201611a5157611a5161192b565b5060010190565b6000825160005b81811015611a795760208186018101518583015201611a5f565b506000920191825250919050565b600060208284031215611a9957600080fd5b81518060030b8114611aaa57600080fd5b939250505056fea26469706673582212205aa48bb78bfafd55a1384ef36fe88a0c230cf3807ce8924547abd90a0159119064736f6c63430008180033a2646970667358221220ee260359a878572e79fea58191e64d978b62ba0101ca875e8e46a04f026576ea64736f6c63430008180033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/data/abis/ExchangeHTS.json b/data/abis/ExchangeHTS.json new file mode 100644 index 0000000..cd56238 --- /dev/null +++ b/data/abis/ExchangeHTS.json @@ -0,0 +1,444 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "ExchangeHTS", + "sourceName": "contracts/orderbook/hts/ExchangeHTS.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenB", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int64", + "name": "amount", + "type": "int64" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "isBuy", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "indexed": false, + "internalType": "int64", + "name": "volume", + "type": "int64" + } + ], + "name": "NewOrder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "isBuy", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "OrderCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int64", + "name": "tradedVolume", + "type": "int64" + }, + { + "indexed": false, + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "indexed": false, + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "seller", + "type": "address" + } + ], + "name": "Trade", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int64", + "name": "amount", + "type": "int64" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "int64", + "name": "", + "type": "int64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "buyOrders", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "int64", + "name": "volume", + "type": "int64" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "next", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isBuyOrder", + "type": "bool" + } + ], + "name": "cancelOrder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "currentOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "firstBuyOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "firstSellOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "int64", + "name": "volume", + "type": "int64" + } + ], + "name": "placeBuyOrder", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "int64", + "name": "volume", + "type": "int64" + } + ], + "name": "placeSellOrder", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sellOrders", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "int64", + "name": "volume", + "type": "int64" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "next", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenA", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x60806040523480156200001157600080fd5b5060405162001d6d38038062001d6d8339810160408190526200003491620001e1565b60016008819055600080546001600160a01b038086166001600160a01b03199283168117909355835490851691161790915562000072903062000092565b6001546200008a906001600160a01b03163062000092565b505062000276565b604080516001600160a01b038381166024830152841660448083019190915282518083039091018152606490910182526020810180516001600160e01b031663248a35ef60e11b17905290516000918291829161016791620000f5919062000219565b6000604051808303816000865af19150503d806000811462000134576040519150601f19603f3d011682016040523d82523d6000602084013e62000139565b606091505b5091509150816200014c57601562000162565b808060200190518101906200016291906200024a565b9250600383900b601614620001bd5760405162461bcd60e51b815260206004820152601f60248201527f536166652073696e676c65206173736f63696174696f6e206661696c65642100604482015260640160405180910390fd5b5050505050565b80516001600160a01b0381168114620001dc57600080fd5b919050565b60008060408385031215620001f557600080fd5b6200020083620001c4565b91506200021060208401620001c4565b90509250929050565b6000825160005b818110156200023c576020818601810151858301520162000220565b506000920191825250919050565b6000602082840312156200025d57600080fd5b81518060030b81146200026f57600080fd5b9392505050565b611ae780620002866000396000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806372fb07771161008c578063bb110bf911610066578063bb110bf91461024d578063dd5813ee14610256578063f7888aec14610269578063f88d2047146102aa57600080fd5b806372fb077714610226578063925931fc1461022f57806392c029911461023857600080fd5b806303c4c2e1146100d45780630fc63d10146100fa5780632ec2add71461012557806335cea288146101385780634a8393f3146101c15780635f64b55b14610213575b600080fd5b6100e76100e236600461184d565b6102bd565b6040519081526020015b60405180910390f35b60005461010d906001600160a01b031681565b6040516001600160a01b0390911681526020016100f1565b6100e761013336600461184d565b6103f1565b61018a610146366004611880565b600260208190526000918252604090912080546001820154928201546003909201549092600781810b93600160401b909204900b916001600160a01b039091169085565b60408051958652600794850b60208701529290930b918401919091526001600160a01b03166060830152608082015260a0016100f1565b61018a6101cf366004611880565b600360208190526000918252604090912080546001820154600283015492909301549092600781810b93600160401b909204900b916001600160a01b039091169085565b60015461010d906001600160a01b031681565b6100e760065481565b6100e760075481565b61024b6102463660046118b0565b6104f3565b005b6100e760055481565b61024b6102643660046118b0565b610607565b6102976102773660046118cc565b600460209081526000928352604080842090915290825290205460070b81565b60405160079190910b81526020016100f1565b61024b6102b83660046118f6565b6106c3565b60006102c761084c565b60008360070b1361030f5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064015b60405180910390fd5b60008260070b136103535760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b6044820152606401610306565b6103616305f5e10083611941565b61036b908461198e565b3360009081526004602090815260408083206001546001600160a01b03168452909152902054600791820b910b12156103b65760405162461bcd60e51b8152600401610306906119b5565b60006103c3338585610876565b905060008160070b13156103dc576103dc338583610b96565b50506007546103eb6001600855565b92915050565b60006103fb61084c565b60008360070b1361043e5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b6044820152606401610306565b60008260070b136104825760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b6044820152606401610306565b33600090815260046020908152604080832083546001600160a01b03168452909152902054600783810b91900b12156104cd5760405162461bcd60e51b8152600401610306906119b5565b60006104da338585610da7565b905060008160070b13156103dc576103dc3385836110a1565b6104fb61084c565b6000546001600160a01b038381169116148061052457506001546001600160a01b038381169116145b6105605760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b6044820152606401610306565b60008160070b136105a45760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b6044820152606401610306565b3360009081526004602090815260408083206001600160a01b0386168452909152902054600782810b91900b12156105ee5760405162461bcd60e51b8152600401610306906119b5565b6105f9338383611287565b6106036001600855565b5050565b61060f61084c565b6000546001600160a01b038381169116148061063857506001546001600160a01b038381169116145b6106745760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b6044820152606401610306565b60008160070b136106b85760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b6044820152606401610306565b6105f933838361134b565b6106cb61084c565b6000816106e55760008381526003602052604090206106f4565b60008381526002602052604090205b60028101549091506001600160a01b03166107485760405162461bcd60e51b815260206004820152601460248201527313dc99195c88191bd95cc81b9bdd08195e1a5cdd60621b6044820152606401610306565b60028101546001600160a01b031633146107b95760405162461bcd60e51b815260206004820152602c60248201527f4f6e6c7920746865206f726465722063726561746f722063616e2063616e636560448201526b36103a3434b99037b93232b960a11b6064820152608401610306565b60018101546000600160401b90910460070b136108245760405162461bcd60e51b8152602060048201526024808201527f4f7264657220616c72656164792063616e63656c6c6564206f722066756c66696044820152631b1b195960e21b6064820152608401610306565b81156108385761083381611402565b610841565b61084181611591565b506106036001600855565b60026008540361086f57604051633ee5aeb560e01b815260040160405180910390fd5b6002600855565b600654600090600a825b8215801590610892575060008560070b135b801561089d57508181105b15610b8a5760008381526003602052604090206001810154600790810b9088900b12610b71576001810154600090600788810b600160401b909204900b126108e557866108f5565b6001820154600160401b900460070b5b600183015490915060070b61090e6305f5e10083611941565b610918908261198e565b6001600160a01b03808c1660009081526004602090815260408083206001549094168352929052908120805490919061095590849060070b6119e1565b82546001600160401b039182166101009390930a9283029190920219909116179055506001600160a01b03808b1660009081526004602090815260408083208354909416835292905290812080548492906109b490849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055506305f5e100826109eb9190611941565b6109f5908261198e565b60028401546001600160a01b03908116600090815260046020908152604080832060015490941683529290529081208054909190610a3790849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055508188610a6a91906119e1565b6001840180549199508391600890610a8d908490600160401b900460070b6119e1565b82546101009290920a6001600160401b03818102199093169190921691909102179055506001830154600284015460408051600786810b82529390930b60208401526001600160a01b038d8116848301529091166060830152517f3811c688c91a2195acbf84f4d5e7adc8d2f17ba048f1051955046e511411fd7d9181900360800190a16001830154600160401b900460070b600003610b6a5760038084015460009788526020829052604088208881556001810180546001600160801b03191690556002810180546001600160a01b0319169055909101969096555b5050610b77565b50610b8a565b81610b8181611a3f565b92505050610880565b50506006555092915050565b60078054906000610ba683611a3f565b909155505060055460005b8115801590610bd75750600082815260026020526040902060010154600785810b91900b135b15610bf5575060008181526002602052604090206003015490610bb1565b6040805160a0810182526007805480835287820b60208085019182529288900b8486019081526001600160a01b03808c1660608701908152608087018a815260009586526002968790529790942095518655915160018601805492516001600160401b03908116600160401b026001600160801b031990941692169190911791909117905590519183018054929091166001600160a01b03199092169190911790559051600390910155610cad6305f5e10084611941565b610cb7908561198e565b6001600160a01b03808716600090815260046020908152604080832060015490941683529290529081208054909190610cf490849060070b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060078054604080516001815260208101929092526001600160a01b0388169082015285820b60608201529084900b60808201527fddca83c332d5b33951e90d550eefc003b7a1efae1cdf8c4d684d4ccdf2cd8d599060a00160405180910390a180600003610d8a57600754600555610da0565b6007546000828152600260205260409020600301555b5050505050565b600554600090600a825b8215801590610dc3575060008560070b135b8015610dce57508181105b156110955760008381526002602052604090206001810154600790810b9088900b1361107c576001810154600090600788810b600160401b909204900b12610e165786610e26565b6001820154600160401b900460070b5b905087610e376305f5e10083611941565b610e41908261198e565b6001600160a01b03808c16600090815260046020908152604080832060015490941683529290529081208054909190610e7e90849060070b611a10565b82546001600160401b039182166101009390930a9283029190920219909116179055506001600160a01b03808b166000908152600460209081526040808320835490941683529290529081208054849290610edd90849060070b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060028301546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054849290610f4190849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055508188610f7491906119e1565b6001840180549199508391600890610f97908490600160401b900460070b6119e1565b82546101009290920a6001600160401b03818102199093169190921691909102179055506001830154600284015460408051600786810b82529390930b60208401526001600160a01b038d8116848301529091166060830152517f3811c688c91a2195acbf84f4d5e7adc8d2f17ba048f1051955046e511411fd7d9181900360800190a16001830154600160401b900460070b60000361107557600380840154600097885260026020819052604089208981556001810180546001600160801b031916905590810180546001600160a01b0319169055909101969096555b5050611082565b50611095565b8161108c81611a3f565b92505050610db1565b50506005555092915050565b600780549060006110b183611a3f565b909155505060065460005b81158015906110e25750600082815260036020526040902060010154600785810b91900b125b15611101575060008181526003602081905260409091200154906110bc565b6040805160a0810182526007805480835287820b602080850191825288840b8587019081526001600160a01b03808d1660608801818152608089018c8152600097885260038087528b89209a518b55965160018b01805496516001600160401b03908116600160401b026001600160801b031990981692169190911795909517909455516002890180549184166001600160a01b0319909216919091179055915196909301959095559382526004845284822082549091168252909252918120805486939192916111d49185910b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060078054604080516000815260208101929092526001600160a01b0388169082015285820b60608201529084900b60808201527fddca83c332d5b33951e90d550eefc003b7a1efae1cdf8c4d684d4ccdf2cd8d599060a00160405180910390a18060000361126a57600754600655610da0565b600754600082815260036020819052604090912001555050505050565b6001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906112c190849060070b6119e1565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055506112f4823085846116fd565b604080516001600160a01b03808616825284166020820152600783900b918101919091527fd09be44e70d8a46d3b3b8da9701595dcaec29d75930065918923c7d9fd73eb58906060015b60405180910390a1505050565b611357828430846116fd565b6001600160a01b0380841660009081526004602090815260408083209386168352929052908120805483929061139190849060070b611a10565b82546001600160401b039182166101009390930a928302919092021990911617905550604080516001600160a01b03808616825284166020820152600783900b918101919091527f64e3b56a34ae45fe7d1f33e3d3da391a671e6043606ac9c811a6c1243023841d9060600161133e565b60055481540361141957600381015460055561149e565b60055460009081526002602052604090205b815460038201541461149457806003015460000361147d5760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b6044820152606401610306565b60030154600090815260026020526040902061142b565b6003808301549101555b60018101546114bc906305f5e10090600160401b900460070b611941565b60018201546114ce919060070b61198e565b60028201546001600160a01b0390811660009081526004602090815260408083206001549094168352929052908120805490919061151090849060070b611a10565b82546001600160401b039182166101009390930a9283029190920219909116179055506001818101805467ffffffffffffffff60401b19169055815460408051928352602083019190915233908201527fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a4906060015b60405180910390a150565b6006548154036115a8576003810154600655611630565b60065460009081526003602052604090205b815460038201541461162657806003015460000361160c5760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b6044820152606401610306565b6003908101546000908152602091909152604090206115ba565b6003808301549101555b600181015460028201546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054600160401b909304600790810b93919291611683918591900b611a10565b82546001600160401b039182166101009390930a92830291909202199091161790555060018101805467ffffffffffffffff60401b1916905580546040805160008152602081019290925233908201527fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a490606001611586565b604080516001600160a01b038681166024830152858116604483015284166064820152600783900b6084808301919091528251808303909101815260a490910182526020810180516001600160e01b031663eca3691760e01b179052905160009182918291610167916117709190611a58565b6000604051808303816000865af19150503d80600081146117ad576040519150601f19603f3d011682016040523d82523d6000602084013e6117b2565b606091505b5091509150816117c35760156117d7565b808060200190518101906117d79190611a87565b9250600383900b60161461182d5760405162461bcd60e51b815260206004820152601b60248201527f5361666520746f6b656e207472616e73666572206661696c65642100000000006044820152606401610306565b50505050505050565b8035600781900b811461184857600080fd5b919050565b6000806040838503121561186057600080fd5b61186983611836565b915061187760208401611836565b90509250929050565b60006020828403121561189257600080fd5b5035919050565b80356001600160a01b038116811461184857600080fd5b600080604083850312156118c357600080fd5b61186983611899565b600080604083850312156118df57600080fd5b6118e883611899565b915061187760208401611899565b6000806040838503121561190957600080fd5b823591506020830135801515811461192057600080fd5b809150509250929050565b634e487b7160e01b600052601160045260246000fd5b60008160070b8360070b8061196657634e487b7160e01b600052601260045260246000fd5b677fffffffffffffff198214600019821416156119855761198561192b565b90059392505050565b60008260070b8260070b028060070b91508082146119ae576119ae61192b565b5092915050565b6020808252601290820152714e6f7420656e6f7567682062616c616e636560701b604082015260600190565b600782810b9082900b03677fffffffffffffff198112677fffffffffffffff821317156103eb576103eb61192b565b600781810b9083900b01677fffffffffffffff8113677fffffffffffffff19821217156103eb576103eb61192b565b600060018201611a5157611a5161192b565b5060010190565b6000825160005b81811015611a795760208186018101518583015201611a5f565b506000920191825250919050565b600060208284031215611a9957600080fd5b81518060030b8114611aaa57600080fd5b939250505056fea26469706673582212205aa48bb78bfafd55a1384ef36fe88a0c230cf3807ce8924547abd90a0159119064736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c806372fb07771161008c578063bb110bf911610066578063bb110bf91461024d578063dd5813ee14610256578063f7888aec14610269578063f88d2047146102aa57600080fd5b806372fb077714610226578063925931fc1461022f57806392c029911461023857600080fd5b806303c4c2e1146100d45780630fc63d10146100fa5780632ec2add71461012557806335cea288146101385780634a8393f3146101c15780635f64b55b14610213575b600080fd5b6100e76100e236600461184d565b6102bd565b6040519081526020015b60405180910390f35b60005461010d906001600160a01b031681565b6040516001600160a01b0390911681526020016100f1565b6100e761013336600461184d565b6103f1565b61018a610146366004611880565b600260208190526000918252604090912080546001820154928201546003909201549092600781810b93600160401b909204900b916001600160a01b039091169085565b60408051958652600794850b60208701529290930b918401919091526001600160a01b03166060830152608082015260a0016100f1565b61018a6101cf366004611880565b600360208190526000918252604090912080546001820154600283015492909301549092600781810b93600160401b909204900b916001600160a01b039091169085565b60015461010d906001600160a01b031681565b6100e760065481565b6100e760075481565b61024b6102463660046118b0565b6104f3565b005b6100e760055481565b61024b6102643660046118b0565b610607565b6102976102773660046118cc565b600460209081526000928352604080842090915290825290205460070b81565b60405160079190910b81526020016100f1565b61024b6102b83660046118f6565b6106c3565b60006102c761084c565b60008360070b1361030f5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b60448201526064015b60405180910390fd5b60008260070b136103535760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b6044820152606401610306565b6103616305f5e10083611941565b61036b908461198e565b3360009081526004602090815260408083206001546001600160a01b03168452909152902054600791820b910b12156103b65760405162461bcd60e51b8152600401610306906119b5565b60006103c3338585610876565b905060008160070b13156103dc576103dc338583610b96565b50506007546103eb6001600855565b92915050565b60006103fb61084c565b60008360070b1361043e5760405162461bcd60e51b815260206004820152600d60248201526c496e76616c696420507269636560981b6044820152606401610306565b60008260070b136104825760405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420566f6c756d6560901b6044820152606401610306565b33600090815260046020908152604080832083546001600160a01b03168452909152902054600783810b91900b12156104cd5760405162461bcd60e51b8152600401610306906119b5565b60006104da338585610da7565b905060008160070b13156103dc576103dc3385836110a1565b6104fb61084c565b6000546001600160a01b038381169116148061052457506001546001600160a01b038381169116145b6105605760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b6044820152606401610306565b60008160070b136105a45760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b6044820152606401610306565b3360009081526004602090815260408083206001600160a01b0386168452909152902054600782810b91900b12156105ee5760405162461bcd60e51b8152600401610306906119b5565b6105f9338383611287565b6106036001600855565b5050565b61060f61084c565b6000546001600160a01b038381169116148061063857506001546001600160a01b038381169116145b6106745760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103a37b5b2b760991b6044820152606401610306565b60008160070b136106b85760405162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a5908185b5bdd5b9d60921b6044820152606401610306565b6105f933838361134b565b6106cb61084c565b6000816106e55760008381526003602052604090206106f4565b60008381526002602052604090205b60028101549091506001600160a01b03166107485760405162461bcd60e51b815260206004820152601460248201527313dc99195c88191bd95cc81b9bdd08195e1a5cdd60621b6044820152606401610306565b60028101546001600160a01b031633146107b95760405162461bcd60e51b815260206004820152602c60248201527f4f6e6c7920746865206f726465722063726561746f722063616e2063616e636560448201526b36103a3434b99037b93232b960a11b6064820152608401610306565b60018101546000600160401b90910460070b136108245760405162461bcd60e51b8152602060048201526024808201527f4f7264657220616c72656164792063616e63656c6c6564206f722066756c66696044820152631b1b195960e21b6064820152608401610306565b81156108385761083381611402565b610841565b61084181611591565b506106036001600855565b60026008540361086f57604051633ee5aeb560e01b815260040160405180910390fd5b6002600855565b600654600090600a825b8215801590610892575060008560070b135b801561089d57508181105b15610b8a5760008381526003602052604090206001810154600790810b9088900b12610b71576001810154600090600788810b600160401b909204900b126108e557866108f5565b6001820154600160401b900460070b5b600183015490915060070b61090e6305f5e10083611941565b610918908261198e565b6001600160a01b03808c1660009081526004602090815260408083206001549094168352929052908120805490919061095590849060070b6119e1565b82546001600160401b039182166101009390930a9283029190920219909116179055506001600160a01b03808b1660009081526004602090815260408083208354909416835292905290812080548492906109b490849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055506305f5e100826109eb9190611941565b6109f5908261198e565b60028401546001600160a01b03908116600090815260046020908152604080832060015490941683529290529081208054909190610a3790849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055508188610a6a91906119e1565b6001840180549199508391600890610a8d908490600160401b900460070b6119e1565b82546101009290920a6001600160401b03818102199093169190921691909102179055506001830154600284015460408051600786810b82529390930b60208401526001600160a01b038d8116848301529091166060830152517f3811c688c91a2195acbf84f4d5e7adc8d2f17ba048f1051955046e511411fd7d9181900360800190a16001830154600160401b900460070b600003610b6a5760038084015460009788526020829052604088208881556001810180546001600160801b03191690556002810180546001600160a01b0319169055909101969096555b5050610b77565b50610b8a565b81610b8181611a3f565b92505050610880565b50506006555092915050565b60078054906000610ba683611a3f565b909155505060055460005b8115801590610bd75750600082815260026020526040902060010154600785810b91900b135b15610bf5575060008181526002602052604090206003015490610bb1565b6040805160a0810182526007805480835287820b60208085019182529288900b8486019081526001600160a01b03808c1660608701908152608087018a815260009586526002968790529790942095518655915160018601805492516001600160401b03908116600160401b026001600160801b031990941692169190911791909117905590519183018054929091166001600160a01b03199092169190911790559051600390910155610cad6305f5e10084611941565b610cb7908561198e565b6001600160a01b03808716600090815260046020908152604080832060015490941683529290529081208054909190610cf490849060070b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060078054604080516001815260208101929092526001600160a01b0388169082015285820b60608201529084900b60808201527fddca83c332d5b33951e90d550eefc003b7a1efae1cdf8c4d684d4ccdf2cd8d599060a00160405180910390a180600003610d8a57600754600555610da0565b6007546000828152600260205260409020600301555b5050505050565b600554600090600a825b8215801590610dc3575060008560070b135b8015610dce57508181105b156110955760008381526002602052604090206001810154600790810b9088900b1361107c576001810154600090600788810b600160401b909204900b12610e165786610e26565b6001820154600160401b900460070b5b905087610e376305f5e10083611941565b610e41908261198e565b6001600160a01b03808c16600090815260046020908152604080832060015490941683529290529081208054909190610e7e90849060070b611a10565b82546001600160401b039182166101009390930a9283029190920219909116179055506001600160a01b03808b166000908152600460209081526040808320835490941683529290529081208054849290610edd90849060070b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060028301546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054849290610f4190849060070b611a10565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055508188610f7491906119e1565b6001840180549199508391600890610f97908490600160401b900460070b6119e1565b82546101009290920a6001600160401b03818102199093169190921691909102179055506001830154600284015460408051600786810b82529390930b60208401526001600160a01b038d8116848301529091166060830152517f3811c688c91a2195acbf84f4d5e7adc8d2f17ba048f1051955046e511411fd7d9181900360800190a16001830154600160401b900460070b60000361107557600380840154600097885260026020819052604089208981556001810180546001600160801b031916905590810180546001600160a01b0319169055909101969096555b5050611082565b50611095565b8161108c81611a3f565b92505050610db1565b50506005555092915050565b600780549060006110b183611a3f565b909155505060065460005b81158015906110e25750600082815260036020526040902060010154600785810b91900b125b15611101575060008181526003602081905260409091200154906110bc565b6040805160a0810182526007805480835287820b602080850191825288840b8587019081526001600160a01b03808d1660608801818152608089018c8152600097885260038087528b89209a518b55965160018b01805496516001600160401b03908116600160401b026001600160801b031990981692169190911795909517909455516002890180549184166001600160a01b0319909216919091179055915196909301959095559382526004845284822082549091168252909252918120805486939192916111d49185910b6119e1565b82546001600160401b039182166101009390930a92830291909202199091161790555060078054604080516000815260208101929092526001600160a01b0388169082015285820b60608201529084900b60808201527fddca83c332d5b33951e90d550eefc003b7a1efae1cdf8c4d684d4ccdf2cd8d599060a00160405180910390a18060000361126a57600754600655610da0565b600754600082815260036020819052604090912001555050505050565b6001600160a01b038084166000908152600460209081526040808320938616835292905290812080548392906112c190849060070b6119e1565b92506101000a8154816001600160401b03021916908360070b6001600160401b031602179055506112f4823085846116fd565b604080516001600160a01b03808616825284166020820152600783900b918101919091527fd09be44e70d8a46d3b3b8da9701595dcaec29d75930065918923c7d9fd73eb58906060015b60405180910390a1505050565b611357828430846116fd565b6001600160a01b0380841660009081526004602090815260408083209386168352929052908120805483929061139190849060070b611a10565b82546001600160401b039182166101009390930a928302919092021990911617905550604080516001600160a01b03808616825284166020820152600783900b918101919091527f64e3b56a34ae45fe7d1f33e3d3da391a671e6043606ac9c811a6c1243023841d9060600161133e565b60055481540361141957600381015460055561149e565b60055460009081526002602052604090205b815460038201541461149457806003015460000361147d5760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b6044820152606401610306565b60030154600090815260026020526040902061142b565b6003808301549101555b60018101546114bc906305f5e10090600160401b900460070b611941565b60018201546114ce919060070b61198e565b60028201546001600160a01b0390811660009081526004602090815260408083206001549094168352929052908120805490919061151090849060070b611a10565b82546001600160401b039182166101009390930a9283029190920219909116179055506001818101805467ffffffffffffffff60401b19169055815460408051928352602083019190915233908201527fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a4906060015b60405180910390a150565b6006548154036115a8576003810154600655611630565b60065460009081526003602052604090205b815460038201541461162657806003015460000361160c5760405162461bcd60e51b815260206004820152600f60248201526e13dc99195c881b9bdd08199bdd5b99608a1b6044820152606401610306565b6003908101546000908152602091909152604090206115ba565b6003808301549101555b600181015460028201546001600160a01b039081166000908152600460209081526040808320835490941683529290529081208054600160401b909304600790810b93919291611683918591900b611a10565b82546001600160401b039182166101009390930a92830291909202199091161790555060018101805467ffffffffffffffff60401b1916905580546040805160008152602081019290925233908201527fcfa919375acf04933787aa7f7b25d71adf2e92839529dad777b47ff2cd0827a490606001611586565b604080516001600160a01b038681166024830152858116604483015284166064820152600783900b6084808301919091528251808303909101815260a490910182526020810180516001600160e01b031663eca3691760e01b179052905160009182918291610167916117709190611a58565b6000604051808303816000865af19150503d80600081146117ad576040519150601f19603f3d011682016040523d82523d6000602084013e6117b2565b606091505b5091509150816117c35760156117d7565b808060200190518101906117d79190611a87565b9250600383900b60161461182d5760405162461bcd60e51b815260206004820152601b60248201527f5361666520746f6b656e207472616e73666572206661696c65642100000000006044820152606401610306565b50505050505050565b8035600781900b811461184857600080fd5b919050565b6000806040838503121561186057600080fd5b61186983611836565b915061187760208401611836565b90509250929050565b60006020828403121561189257600080fd5b5035919050565b80356001600160a01b038116811461184857600080fd5b600080604083850312156118c357600080fd5b61186983611899565b600080604083850312156118df57600080fd5b6118e883611899565b915061187760208401611899565b6000806040838503121561190957600080fd5b823591506020830135801515811461192057600080fd5b809150509250929050565b634e487b7160e01b600052601160045260246000fd5b60008160070b8360070b8061196657634e487b7160e01b600052601260045260246000fd5b677fffffffffffffff198214600019821416156119855761198561192b565b90059392505050565b60008260070b8260070b028060070b91508082146119ae576119ae61192b565b5092915050565b6020808252601290820152714e6f7420656e6f7567682062616c616e636560701b604082015260600190565b600782810b9082900b03677fffffffffffffff198112677fffffffffffffff821317156103eb576103eb61192b565b600781810b9083900b01677fffffffffffffff8113677fffffffffffffff19821217156103eb576103eb61192b565b600060018201611a5157611a5161192b565b5060010190565b6000825160005b81811015611a795760208186018101518583015201611a5f565b506000920191825250919050565b600060208284031215611a9957600080fd5b81518060030b8114611aaa57600080fd5b939250505056fea26469706673582212205aa48bb78bfafd55a1384ef36fe88a0c230cf3807ce8924547abd90a0159119064736f6c63430008180033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/data/abis/OrderBook.json b/data/abis/OrderBook.json new file mode 100644 index 0000000..de9a9b8 --- /dev/null +++ b/data/abis/OrderBook.json @@ -0,0 +1,321 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "OrderBook", + "sourceName": "contracts/orderbook/Orderbook.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "isBuy", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "volume", + "type": "uint256" + } + ], + "name": "NewOrder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "isBuy", + "type": "bool" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "OrderCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "tradedVolume", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "seller", + "type": "address" + } + ], + "name": "Trade", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "buyOrders", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "next", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "firstBuyOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "firstSellOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sellOrders", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "next", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenA", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x", + "deployedBytecode": "0x", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/data/abis/OrderBookHTS.json b/data/abis/OrderBookHTS.json new file mode 100644 index 0000000..df8ec2a --- /dev/null +++ b/data/abis/OrderBookHTS.json @@ -0,0 +1,321 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "OrderBookHTS", + "sourceName": "contracts/orderbook/hts/OrderBookHTS.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int64", + "name": "amount", + "type": "int64" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "isBuy", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "indexed": false, + "internalType": "int64", + "name": "volume", + "type": "int64" + } + ], + "name": "NewOrder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "isBuy", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "trader", + "type": "address" + } + ], + "name": "OrderCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int64", + "name": "tradedVolume", + "type": "int64" + }, + { + "indexed": false, + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "indexed": false, + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "seller", + "type": "address" + } + ], + "name": "Trade", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int64", + "name": "amount", + "type": "int64" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "int64", + "name": "", + "type": "int64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "buyOrders", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "int64", + "name": "volume", + "type": "int64" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "next", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "firstBuyOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "firstSellOrderId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sellOrders", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "int64", + "name": "volume", + "type": "int64" + }, + { + "internalType": "address", + "name": "trader", + "type": "address" + }, + { + "internalType": "uint256", + "name": "next", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenA", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x", + "deployedBytecode": "0x", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/package.json b/package.json index 8bb394c..c4e8419 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,12 @@ "@typechain/ethers-v6": "^0.5.0", "@typechain/hardhat": "^9.0.0", "@types/chai": "^4.2.0", + "@types/elliptic": "^6.4.18", "@types/mocha": "^9.1.0", "@types/node": "^18.0.0", "chai": "^4.2.0", "dotenv": "^16.4.5", + "elliptic": "^6.5.5", "eslint": "^8.57.0", "eslint-config-standard-with-typescript": "^43.0.1", "eslint-plugin-import": "^2.29.1", diff --git a/test/orderbook/exchange-factory-hts.test.ts b/test/orderbook/exchange-factory-hts.test.ts new file mode 100644 index 0000000..56cce12 --- /dev/null +++ b/test/orderbook/exchange-factory-hts.test.ts @@ -0,0 +1,74 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { PrivateKey, Client, AccountId } from "@hashgraph/sdk"; + +// Tests +describe("ExchangeFactory", function () { + async function deployFixture() { + const [owner] = await ethers.getSigners(); + + let client = Client.forTestnet(); + + const operatorPrKey = PrivateKey.fromStringECDSA(process.env.PRIVATE_KEY || ''); + const operatorAccountId = AccountId.fromString(process.env.ACCOUNT_ID || ''); + + client.setOperator( + operatorAccountId, + operatorPrKey + ); + + const exchangeFactoryFactory = await ethers.getContractFactory("ExchangeFactoryHTS", owner); + const exchangeFactory = await exchangeFactoryFactory.deploy(); + await exchangeFactory.waitForDeployment(); + + return { + exchangeFactory, + client, + owner, + }; + } + + describe("deployExchange", function () { + describe("when there is no exchange created for the pair", () => { + it("should deploy exchange", async function () { + const { exchangeFactory, owner } = await deployFixture(); + const exchangeDetails = { + tokenA: "0x000000000000000000000000000000000042cf0f", + tokenB: "0x000000000000000000000000000000000042cf11", + } + + const tx = await exchangeFactory.deployExchange( + exchangeDetails.tokenA, + exchangeDetails.tokenB, + { from: owner.address, gasLimit: 3000000 } + ); + + await expect(tx).to.emit(exchangeFactory, "ExchangeDeployed"); + }); + }); + + describe("when there is already an exchange created", () => { + it("should revert", async function () { + // @notice: revertedWith feature is not working with hedera + + // const { exchangeFactory, owner } = await deployFixture(); + // const exchangeDetails = { + // tokenA: "0x000000000000000000000000000000000042cf0f", + // tokenB: "0x000000000000000000000000000000000042cf11", + // } + + // await exchangeFactory.deployExchange( + // exchangeDetails.tokenA, + // exchangeDetails.tokenB, + // { from: owner.address, gasLimit: 3000000 } + // ); + + // await expect(exchangeFactory.deployExchange( + // exchangeDetails.tokenA, + // exchangeDetails.tokenB, + // { from: owner.address, gasLimit: 3000000 })).to.be.revertedWith('Exchange already deployed'); + // }); + }); + }); + }); +}); diff --git a/test/orderbook/exchange-factory.test.ts b/test/orderbook/exchange-factory.test.ts new file mode 100644 index 0000000..f121378 --- /dev/null +++ b/test/orderbook/exchange-factory.test.ts @@ -0,0 +1,72 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { PrivateKey, Client, AccountId } from "@hashgraph/sdk"; + +// Tests +describe("ExchangeFactory", function () { + async function deployFixture() { + const [owner] = await ethers.getSigners(); + + let client = Client.forTestnet(); + + const operatorPrKey = PrivateKey.fromStringECDSA(process.env.PRIVATE_KEY || ''); + const operatorAccountId = AccountId.fromString(process.env.ACCOUNT_ID || ''); + + client.setOperator( + operatorAccountId, + operatorPrKey + ); + + const exchangeFactoryFactory = await ethers.getContractFactory("ExchangeFactory", owner); + const exchangeFactory = await exchangeFactoryFactory.deploy(); + await exchangeFactory.waitForDeployment(); + + return { + exchangeFactory, + client, + owner, + }; + } + + describe("deployExchange", function () { + describe("when there is no exchange created for the pair", () => { + it("should deploy exchange", async function () { + const { exchangeFactory, owner } = await deployFixture(); + const exchangeDetails = { + tokenA: "0x000000000000000000000000000000000042cf0f", + tokenB: "0x000000000000000000000000000000000042cf11", + } + + const tx = await exchangeFactory.deployExchange( + exchangeDetails.tokenA, + exchangeDetails.tokenB, + { from: owner.address, gasLimit: 3000000 } + ); + + await expect(tx).to.emit(exchangeFactory, "ExchangeDeployed"); + }); + }); + + describe("when there is already an exchange created", () => { + it("should revert", async function () { + const { exchangeFactory, owner } = await deployFixture(); + const exchangeDetails = { + tokenA: "0x000000000000000000000000000000000042cf0f", + tokenB: "0x000000000000000000000000000000000042cf11", + } + + await exchangeFactory.deployExchange( + exchangeDetails.tokenA, + exchangeDetails.tokenB, + { from: owner.address, gasLimit: 3000000 } + ) + + await expect(exchangeFactory.deployExchange( + exchangeDetails.tokenA, + exchangeDetails.tokenB, + { from: owner.address, gasLimit: 3000000 } + )).to.be.revertedWith('Exchange already deployed'); + }); + }); + }); +}); diff --git a/test/orderbook/exchange-hts.test.ts b/test/orderbook/exchange-hts.test.ts new file mode 100644 index 0000000..1746f25 --- /dev/null +++ b/test/orderbook/exchange-hts.test.ts @@ -0,0 +1,651 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import Utils, { Operator } from '../utils'; +import { AccountId, PrivateKey, TokenId, ContractId, Client, ContractFunctionParameters, TransactionRecord } from '@hashgraph/sdk'; +import { AbiCoder } from 'ethers'; +import { bytecode as ExchangeHTSbytecode } from '../../data/abis/ExchangeHTS.json'; + +const cancelOrder = async (client: Client, operator: Operator, contract: ContractId, orderId: number, isBuy: boolean) => { + client.setOperator( + operator.accountId, + operator.key + ); + + const functionName = "cancelOrder"; + const params = new ContractFunctionParameters() + .addUint256(orderId.toString()) + .addBool(isBuy) + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params); + + if (receipt.status._code != 22) { + throw new Error('Error depositing token status code' + receipt.status._code); + } + + return {receipt, record}; +} + +const depositToken = async (client: Client, operator: Operator, contract: ContractId, tokenId: TokenId | string, amount: number) => { + client.setOperator( + operator.accountId, + operator.key + ); + + if (typeof tokenId != 'string') { + tokenId = `0x${tokenId.toSolidityAddress()}`; + } + + const functionName = "deposit"; + const params = new ContractFunctionParameters() + .addAddress(tokenId) + .addInt64(amount.toString()); + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params); + + if (receipt.status._code != 22) { + throw new Error('Error depositing token status code' + receipt.status._code); + } + + return {receipt, record}; +} + +const withdrawToken = async (client: Client, operator: Operator, contract: ContractId, tokenAddress: string, amount: number) => { + client.setOperator( + operator.accountId, + operator.key + ); + const functionName = "withdraw"; + const params = new ContractFunctionParameters() + .addAddress(tokenAddress) + .addInt64(amount.toString()); + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params); + + if (receipt.status._code != 22) { + throw new Error('Error withdrawing token status code' + receipt.status._code); + } + + return { receipt, record }; +} + +const placeBuyOrder = async (client: Client, operator: Operator, contract: ContractId, price: number, volume: number) => { + client.setOperator( + operator.accountId, + operator.key + ); + const functionName = "placeBuyOrder"; + const params = new ContractFunctionParameters() + .addInt64(price.toString()) + .addInt64(volume.toString()); + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params); + + if (receipt.status._code != 22) { + throw new Error('Error depositing token status code' + receipt.status._code); + } + + return { receipt, record }; +} + +const queryBalanceOfExchange = async (client: Client, contract: ContractId, userAddress: string, tokenAddress: string) => { + const functionName = "balanceOf"; + const params = new ContractFunctionParameters() + .addAddress(userAddress) + .addAddress(tokenAddress); + + // Decode the result + const result = await Utils.queryContract(client, contract, functionName, params); + const balance = result.getInt64(0); // Adjust based on your function's return type + return balance; +} + +const queryCurrentOrderId = async (client: Client, contract: ContractId) => { + const functionName = "currentOrderId"; + const params = new ContractFunctionParameters(); + + // Decode the result + const result = await Utils.queryContract(client, contract, functionName, params); + const balance = result.getUint256(0); // Adjust based on your function's return type + return balance; +} + +const queryBuyOrder = async (client: Client, contract: ContractId, orderId: number) => { + const functionName = "buyOrders"; + const params = new ContractFunctionParameters() + .addUint256(orderId); + + // Decode the result + const result = await Utils.queryContract(client, contract, functionName, params); + + const id = result.getUint256(0); + const price = result.getInt64(1); + const volume = result.getInt64(2); + const trader = `0x${result.getAddress(3)}`; + const next = result.getInt64(4); + + return { id, price, volume, trader, next }; +} + +const placeSellOrder = async (client: Client, operator: Operator, contract: ContractId, price: number, volume: number) => { + client.setOperator( + operator.accountId, + operator.key + ); + const functionName = "placeSellOrder"; + const params = new ContractFunctionParameters() + .addInt64(price.toString()) + .addInt64(volume.toString()); + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params); + + if (receipt.status._code != 22) { + throw new Error('Error depositing token status code' + receipt.status._code); + } + + return { receipt, record }; +} + +const eventsEmited = (record: TransactionRecord, eventTopic: string) => { + return record.contractFunctionResult?.logs.reduce((acc: Uint8Array[], log) => { + for (const topic of log.topics) { + const pTopic = `0x${Buffer.from(topic).toString('hex')}`; + if (pTopic === eventTopic) { + acc.push(log.data); + } + } + return acc; + }, []) +} + +async function deployExchangeFixture() { + const client = Client.forTestnet(); + + const operatorAccountId = AccountId.fromString(process.env.ACCOUNT_ID as string); + const operatorPrivateKey = PrivateKey.fromStringECDSA(process.env.PRIVATE_KEY as string); + + const aliceAccountId = AccountId.fromString(process.env.ALICE_ACCOUNT_ID as string); + const alicePrivateKey = PrivateKey.fromStringED25519(process.env.ALICE_KEY as string); + + const bobAccountId = AccountId.fromString(process.env.BOB_ACCOUNT_ID as string); + const bobPrivateKey = PrivateKey.fromStringECDSA(process.env.BOB_KEY as string); + + const charlieAccountId = AccountId.fromString(process.env.CHARLIE_ACCOUNT_ID as string); + const charliePrivateKey = PrivateKey.fromStringECDSA(process.env.CHARLIE_KEY as string); + + const davidAccountId = AccountId.fromString(process.env.DAVID_ACCOUNT_ID as string); + const davidPrivateKey = PrivateKey.fromStringED25519(process.env.DAVID_KEY as string); + + const operator = { + accountId: operatorAccountId, + key: operatorPrivateKey, + address: '0x' + operatorAccountId.toSolidityAddress(), + } + + const alice = { + accountId: aliceAccountId, + key: alicePrivateKey, + address: '0x' + aliceAccountId.toSolidityAddress(), + } + + const bob = { + accountId: bobAccountId, + key: bobPrivateKey, + address: '0x' + bobAccountId.toSolidityAddress() + } + + const charlie = { + accountId: charlieAccountId, + key: charliePrivateKey, + address: '0x' + charlieAccountId.toSolidityAddress() + } + + const david = { + accountId: davidAccountId, + key: davidPrivateKey, + address: '0x' + davidAccountId.toSolidityAddress() + } + + client.setOperator( + operator.accountId, + operator.key + ); + + // const idTokenA = TokenId.fromString('0.0.4378383'); + // const idTokenB = TokenId.fromString('0.0.4378385'); + // const exchangeId = ContractId.fromString("0.0.4397364"); + const idTokenA = await Utils.createFungibleToken(client, operator.accountId, operator.key, 'Test Token A', 'TOKEN_A'); + const idTokenB = await Utils.createFungibleToken(client, operator.accountId, operator.key, 'Test Token B', 'TOKEN_B'); + const exchangeId = await Utils.deployContract( + client, + operator, + ExchangeHTSbytecode, + ['0x' + idTokenA.toSolidityAddress(), '0x' + idTokenB.toSolidityAddress()] + ); + + console.log(`deployed exchange at ${exchangeId} or ${exchangeId.toSolidityAddress()}`) + + const toTokenA = (_number: number) => { + return _number * 10 ** 8; + } + + const toTokenB = (_number: number) => { + return _number * 10 ** 8; + } + + const minting = [ + Utils.tokenMint(client, operator, idTokenA, toTokenA(1_000_000 * 4)), + Utils.tokenMint(client, operator, idTokenB, toTokenB(1_000_000 * 4)), + ]; + + await Promise.all(minting); + + const associations = [ + // token A + Utils.associateTokenToAccount(client, idTokenA, alice), + Utils.associateTokenToAccount(client, idTokenA, bob), + Utils.associateTokenToAccount(client, idTokenA, charlie), + Utils.associateTokenToAccount(client, idTokenA, david), + // token B + Utils.associateTokenToAccount(client, idTokenB, alice), + Utils.associateTokenToAccount(client, idTokenB, bob), + Utils.associateTokenToAccount(client, idTokenB, charlie), + Utils.associateTokenToAccount(client, idTokenB, david), + ]; + + await Promise.all(associations); + + const transfers = [ + // token A + Utils.transferToken(client, idTokenA, operator, alice.accountId, toTokenA(1_000_000)), + Utils.transferToken(client, idTokenA, operator, bob.accountId, toTokenA(1_000_000)), + Utils.transferToken(client, idTokenA, operator, charlie.accountId, toTokenA(1_000_000)), + Utils.transferToken(client, idTokenA, operator, david.accountId, toTokenA(1_000_000)), + // // token B + Utils.transferToken(client, idTokenB, operator, alice.accountId, toTokenB(1_000_000)), + Utils.transferToken(client, idTokenB, operator, bob.accountId, toTokenB(1_000_000)), + Utils.transferToken(client, idTokenB, operator, charlie.accountId, toTokenB(1_000_000)), + Utils.transferToken(client, idTokenB, operator, david.accountId, toTokenB(1_000_000)), + // token A + Utils.approveToken(client, idTokenA, alice, exchangeId, toTokenA(1_000_000)), + Utils.approveToken(client, idTokenA, bob, exchangeId, toTokenA(1_000_000)), + Utils.approveToken(client, idTokenA, charlie, exchangeId, toTokenA(1_000_000)), + Utils.approveToken(client, idTokenA, david, exchangeId, toTokenA(1_000_000)), + // token B + Utils.approveToken(client, idTokenB, alice, exchangeId, toTokenB(1_000_000)), + Utils.approveToken(client, idTokenB, bob, exchangeId, toTokenB(1_000_000)), + Utils.approveToken(client, idTokenB, charlie, exchangeId, toTokenB(1_000_000)), + Utils.approveToken(client, idTokenB, david, exchangeId, toTokenB(1_000_000)), + ]; + + await Promise.all(transfers); + + return { + client, + operator, + alice, + bob, + charlie, + david, + exchangeId, + idTokenA, + idTokenB, + toTokenA, + toTokenB, + } +} + +describe('Exchange HTS', () => { + describe('Exchange', () => { + describe('.lastOrderId', () => { + describe('when buy or sell order is placed', () => { + it("should increment by 1", async function() { + const { client, alice, exchangeId, idTokenB, toTokenA, toTokenB } = await deployExchangeFixture(); + await depositToken(client, alice, exchangeId, idTokenB, toTokenB(400)); + const currentId = await queryCurrentOrderId(client, exchangeId); + + const orders = [ + placeBuyOrder(client, alice, exchangeId, toTokenB(100), toTokenA(1)), + placeBuyOrder(client, alice, exchangeId, toTokenB(100), toTokenA(1)), + placeBuyOrder(client, alice, exchangeId, toTokenB(100), toTokenA(1)), + placeBuyOrder(client, alice, exchangeId, toTokenB(100), toTokenA(1)), + ] + + await Promise.all(orders); + + expect(await queryCurrentOrderId(client, exchangeId)).to.equal(currentId.plus(4)); + }); + }); + }); + + describe('.placeBuyOrder', () => { + describe('when there is not matching sell order', () => { + it('should place buy order', async () => { + const { client, alice, exchangeId, idTokenB, toTokenB, toTokenA } = await deployExchangeFixture(); + await depositToken(client, alice, exchangeId, idTokenB, toTokenB(1000)); + await placeBuyOrder(client, alice, exchangeId, toTokenB(200), toTokenA(5)); + const currentId = await queryCurrentOrderId(client, exchangeId); + const order = await queryBuyOrder(client, exchangeId, currentId.toNumber()); + + expect(order.id).to.equal(currentId.toNumber()); + expect(order.price).to.equal(toTokenB(200)); + expect(order.volume).to.equal(toTokenA(5)); + expect(order.trader).to.equal(alice.address); + }); + }); + + describe('when there is matching sell order', () => { + describe('when is whole fulfillment', () => { + it('should trade one to one', async () => { + const { client, alice, bob, exchangeId, idTokenB, idTokenA, toTokenA, toTokenB } = await deployExchangeFixture(); + + await Promise.all([ + depositToken(client, alice, exchangeId, idTokenA, toTokenA(1)), + depositToken(client, bob, exchangeId, idTokenB, toTokenB(1000)), + ]); + + await placeSellOrder(client, alice, exchangeId, toTokenB(765), toTokenA(1)); + const { record } = await placeBuyOrder(client, bob, exchangeId, toTokenB(765), toTokenB(1)); + + const tradeEvent = eventsEmited(record, ethers.id("Trade(int64,int64,address,address)")) as Uint8Array []; + + expect(tradeEvent.length).to.be.equal(1); + + const parsedLog = AbiCoder.defaultAbiCoder().decode( + ["int64", "int64", "address"], + tradeEvent[0] + ); + + expect(parsedLog.length).to.equal(3); + expect(parsedLog[0]).to.equal(BigInt(toTokenA(1))); + expect(parsedLog[1]).to.equal(BigInt(toTokenB(765))); + // expect(parsedLog[2].toLowerCase()).to.equal(alice.address); + + }); + + it('should trade many to one', async () => { + const { client, alice, bob, charlie, david, exchangeId, idTokenB, idTokenA, toTokenA, toTokenB } = await deployExchangeFixture(); + + await Promise.all([ + depositToken(client, alice, exchangeId, idTokenA, toTokenA(1)), + depositToken(client, bob, exchangeId, idTokenA, toTokenA(1)), + depositToken(client, charlie, exchangeId, idTokenA, toTokenA(1)), + depositToken(client, david, exchangeId, idTokenB, toTokenB(2295)), + ]); + + + await placeSellOrder(client, alice, exchangeId, toTokenB(765), toTokenA(1)); + await placeSellOrder(client, bob, exchangeId, toTokenB(765), toTokenA(1)); + await placeSellOrder(client, charlie, exchangeId, toTokenB(765), toTokenA(1)); + + const { record } = await placeBuyOrder(client, david, exchangeId, toTokenB(765), toTokenA(3)); + + const tradeEvent = eventsEmited(record, ethers.id("Trade(int64,int64,address,address)")) as Uint8Array []; + + expect(tradeEvent.length).to.be.equal(3); + + const parsedLog1 = AbiCoder.defaultAbiCoder().decode( + ["int64", "int64", "address"], + tradeEvent[0] + ); + + expect(parsedLog1.length).to.equal(3); + expect(parsedLog1[0]).to.equal(BigInt(toTokenA(1))); + expect(parsedLog1[1]).to.equal(BigInt(toTokenB(765))); + // expect(parsedLog1[2].toLowerCase()).to.equal(alice.address); + + const parsedLog2 = AbiCoder.defaultAbiCoder().decode( + ["int64", "int64", "address"], + tradeEvent[1] + ); + + expect(parsedLog2.length).to.equal(3); + expect(parsedLog2[0]).to.equal(BigInt(toTokenA(1))); + expect(parsedLog2[1]).to.equal(BigInt(toTokenB(765))); + // expect(parsedLog2[2].toLowerCase()).to.equal(bob.address); + + const parsedLog3 = AbiCoder.defaultAbiCoder().decode( + ["int64", "int64", "address"], + tradeEvent[2] + ); + + expect(parsedLog3.length).to.equal(3); + expect(parsedLog3[0]).to.equal(BigInt(toTokenA(1))); + expect(parsedLog3[1]).to.equal(BigInt(toTokenB(765))); + // expect(parsedLog3[2].toLowerCase()).to.equal(charlie.address); + + }); + }); + + describe('when is partial fulfillment', () => { + it('should trade and insert remainder volume', async () => { + const { client, alice, bob, exchangeId, idTokenB, idTokenA, toTokenA, toTokenB } = await deployExchangeFixture(); + + await Promise.all([ + depositToken(client, alice, exchangeId, idTokenA, toTokenA(2)), + depositToken(client, bob, exchangeId, idTokenB, toTokenB(1590)), + ]); + + await placeSellOrder(client, alice, exchangeId, toTokenB(795), toTokenA(1)); // sell only one + const { record } = await placeBuyOrder(client, bob, exchangeId, toTokenB(795), toTokenA(2)); + + const tradeEvent = eventsEmited(record, ethers.id("Trade(int64,int64,address,address)")) as Uint8Array []; + const newOrderEvent = eventsEmited(record, ethers.id("NewOrder(bool,uint256,address,int64,int64)")) as Uint8Array []; + + expect(tradeEvent.length).to.be.equal(1, "trade lengh error"); + expect(newOrderEvent.length).to.be.equal(1, "new order lengh error"); + + const parsedLogTrade = AbiCoder.defaultAbiCoder().decode( + ["int64", "int64", "address"], + tradeEvent[0] + ); + + expect(parsedLogTrade.length).to.equal(3); + expect(parsedLogTrade[0]).to.equal(BigInt(toTokenA(1))); + expect(parsedLogTrade[1]).to.equal(BigInt(toTokenB(795))); + // expect(parsedLogTrade[2].toLowerCase()).to.equal(alice.address); + + const parsedLogNewOrder = AbiCoder.defaultAbiCoder().decode( + ["bool","uint256","address","int64","int64"], + newOrderEvent[0] + ); + + expect(parsedLogNewOrder.length).to.equal(5); + expect(parsedLogNewOrder[0]).to.equal(true); + // expect(parsedLogNewOrder[1]).to.equal(765n); + // expect(parsedLogNewOrder[2]).to.equal(765n); + expect(parsedLogNewOrder[3]).to.equal(BigInt(toTokenB(795))); + expect(parsedLogNewOrder[4]).to.equal(BigInt(toTokenB(1))); + + }); + }); + }); + }); + + describe('.withdraw', () => { + describe('when token is invalid', () => { + it('should revert', async () => { + const { client, exchangeId, alice } = await deployExchangeFixture(); + try { + await withdrawToken(client, alice, exchangeId, ethers.ZeroAddress, 100) + expect.fail("withdraw should have reverted but it did not."); + } catch (error: any) { + expect(error.message).to.exist; + } + }); + }); + + describe('when amount is invalid', () => { + it('should revert', async () => { + const { exchangeId, client, alice, idTokenA, toTokenA } = await deployExchangeFixture(); + + await depositToken(client, alice, exchangeId, idTokenA, toTokenA(100)); + try { + await withdrawToken(client, alice, exchangeId, alice.address, 0); + expect.fail("withdraw should have reverted but it did not."); + } catch (error: any) { + expect(error.message).to.exist; + } + }); + }); + + describe('when trader has not enough balance', () => { + it('should revert', async () => { + const { exchangeId, client, alice, idTokenA, toTokenA } = await deployExchangeFixture(); + + await depositToken(client, alice, exchangeId, idTokenA, toTokenA(100)); + try { + await withdrawToken(client, alice, exchangeId, alice.address, toTokenA(101)); + expect.fail("withdraw should have reverted but it did not."); + } catch (error: any) { + expect(error.message).to.exist; + } + }); + }); + + describe('when trader does not cancel a buy order', () => { + it('it should revert', async () => { + const { exchangeId, client, alice, idTokenB, toTokenB, toTokenA } = await deployExchangeFixture(); + + await depositToken(client, alice, exchangeId, idTokenB, toTokenB(100)); + await placeBuyOrder(client, alice, exchangeId, toTokenB(100), toTokenA(1)); + + try { + await withdrawToken(client, alice, exchangeId, alice.address, toTokenB(100)); + expect.fail("withdraw should have reverted but it did not."); + } catch (error: any) { + expect(error.message).to.exist; + } + }); + }); + + describe('when trader has balance', () => { + it('it should withdraw', async () => { + const { exchangeId, client, alice, idTokenB, toTokenB } = await deployExchangeFixture(); + const addressTokenB = '0x' + idTokenB.toSolidityAddress(); + await depositToken(client, alice, exchangeId, idTokenB, toTokenB(100)); + + const { record } = await withdrawToken(client, alice, exchangeId, addressTokenB, toTokenB(100)); + + const withdrawEvent = eventsEmited(record, ethers.id("Withdraw(address,address,int64)")) as Uint8Array []; + + expect(withdrawEvent.length).to.be.equal(1, "withdraw lengh error"); + + const parsedLogWithdraw = AbiCoder.defaultAbiCoder().decode( + ["address", "address", "int64"], + withdrawEvent[0] + ); + + expect(parsedLogWithdraw.length).to.be.equal(3); + expect(parsedLogWithdraw[0].toLowerCase()).to.be.equal(alice.address); + expect(parsedLogWithdraw[1].toLowerCase()).to.be.equal(addressTokenB); + expect(parsedLogWithdraw[2]).to.be.equal(toTokenB(100)); + }); + }); + }); + + describe('.deposit', () => { + describe('when amount is invalid', () => { + it('should revert', async () => { + const { client, exchangeId, alice, idTokenA } = await deployExchangeFixture(); + try { + await depositToken(client, alice, exchangeId, idTokenA, 0) + expect.fail("withdraw should have reverted but it did not."); + } catch (error: any) { + expect(error.message).to.exist; + } + }); + }); + + describe('when token is invalid', () => { + it('should revert', async () => { + it('should revert', async () => { + const { client, exchangeId, alice, idTokenA, toTokenA } = await deployExchangeFixture(); + try { + await depositToken(client, alice, exchangeId, ethers.ZeroAddress, toTokenA(100)) + expect.fail("withdraw should have reverted but it did not."); + } catch (error: any) { + expect(error.message).to.exist; + } + }); + }); + }); + }); + + describe('.cancelOrder', () => { + describe('when order dont exists', () => { + it('should revert', async () => { + const { client, exchangeId, alice, idTokenA } = await deployExchangeFixture(); + + const orderId = 0; + + try { + await cancelOrder(client, alice, exchangeId, orderId, true); + expect.fail("withdraw should have reverted but it did not."); + } catch (error: any) { + expect(error.message).to.exist; + } + }); + }); + + describe('when order exists', () => { + describe('when is Order', () => { + describe('when caller is not the order owner', () => { + it('should revert', async () => { + const { client, exchangeId, alice, bob, idTokenA, toTokenA, toTokenB } = await deployExchangeFixture(); + + await depositToken(client, alice, exchangeId, idTokenA, toTokenA(1)); + await placeSellOrder(client, alice, exchangeId, toTokenB(1), toTokenA(1)); + const orderId = await queryCurrentOrderId(client, exchangeId); + + try { + await cancelOrder(client, bob, exchangeId, orderId.toNumber(), false); + expect.fail("withdraw should have reverted but it did not."); + } catch (error: any) { + expect(error.message).to.exist; + } + }); + }); + describe('when order is fulfilled', () => { + it('should revert', async () => { + const { client, exchangeId, alice, bob, idTokenA, idTokenB, toTokenA, toTokenB } = await deployExchangeFixture(); + + await depositToken(client, alice, exchangeId, idTokenA, toTokenA(1)); + await depositToken(client, bob, exchangeId, idTokenB, toTokenA(10)); + await placeSellOrder(client, alice, exchangeId, toTokenB(10), toTokenA(1)); + const orderId = await queryCurrentOrderId(client, exchangeId); + await placeBuyOrder(client, bob, exchangeId, toTokenB(10), toTokenA(1)); + + try { + await cancelOrder(client, bob, exchangeId, orderId.toNumber(), false); + expect.fail("withdraw should have reverted but it did not."); + } catch (error: any) { + expect(error.message).to.exist; + } + }); + }); + + describe('when caller is the orders owner', () => { + it('should cancel order and refund balance', async () => { + const { client, exchangeId, alice, idTokenA, toTokenA, toTokenB } = await deployExchangeFixture(); + + const addressTokenA = `0x${idTokenA.toSolidityAddress()}`; + await depositToken(client, alice, exchangeId, idTokenA, toTokenA(207)); + const balance1 = await queryBalanceOfExchange(client, exchangeId, alice.address, addressTokenA); + await placeSellOrder(client, alice, exchangeId, toTokenB(10), toTokenA(207)); + const balance2 = await queryBalanceOfExchange(client, exchangeId, alice.address, addressTokenA); + const orderId = await queryCurrentOrderId(client, exchangeId); + + await cancelOrder(client, alice, exchangeId, orderId.toNumber(), false); + const balance3 = await queryBalanceOfExchange(client, exchangeId, alice.address, addressTokenA); + + expect(balance2).to.be.equal(balance1.minus(toTokenA(207))); + expect(balance3).to.be.equal(balance1); + + }); + }); + }); + }); + }); + }); +}); + diff --git a/test/orderbook/exchange.test.ts b/test/orderbook/exchange.test.ts new file mode 100644 index 0000000..ef9c699 --- /dev/null +++ b/test/orderbook/exchange.test.ts @@ -0,0 +1,576 @@ +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { BigNumberish } from 'ethers'; +import { ethers } from 'hardhat'; + +async function deployExchangeFixture() { + const [owner, alice, bob, charlie, david] = await ethers.getSigners(); + + // Deploy Mock ERC20 tokens for tokenA and tokenB + const Token = await ethers.getContractFactory("TestERC20"); + const tokenB = await Token.deploy("Token B", "TKB"); + const tokenA = await Token.deploy("Token A", "TKA"); + + const addressTokenA = await tokenA.getAddress(); + const addressTokenB = await tokenB.getAddress(); + + const exchange = await ethers.deployContract('Exchange', [tokenA, tokenB], owner); + await exchange.waitForDeployment(); + + const decimalsTokenA = await tokenA.decimals(); + const decimalsTokenB = await tokenB.decimals(); + + const toTokenA = (_number: bigint) => { + return _number * 10n ** decimalsTokenA; + } + + const toTokenB = (_number: bigint) => { + return _number * 10n ** decimalsTokenB; + } + + // Token A config + await tokenA.mint(alice.address, toTokenA(10000000000n) ); + await tokenA.mint(bob.address, toTokenA(10000000000n)); + await tokenA.mint(charlie.address, toTokenA(10000000000n)); + await tokenA.mint(david.address, toTokenA(10000000000n)); + + await tokenA.connect(alice).approve(await exchange.getAddress(), toTokenA(10000000000n)); + await tokenA.connect(bob).approve(await exchange.getAddress(), toTokenA(10000000000n)); + await tokenA.connect(charlie).approve(await exchange.getAddress(), toTokenA(10000000000n)); + await tokenA.connect(david).approve(await exchange.getAddress(), toTokenA(10000000000n)); + + // Token B config + await tokenB.mint(alice.address, toTokenB(10000000000n)); + await tokenB.mint(bob.address, toTokenB(10000000000n)); + await tokenB.mint(charlie.address, toTokenB(10000000000n)); + await tokenB.mint(david.address, toTokenB(10000000000n)); + + await tokenB.connect(alice).approve(await exchange.getAddress(), toTokenB(10000000000n)); + await tokenB.connect(bob).approve(await exchange.getAddress(), toTokenB(10000000000n)); + await tokenB.connect(charlie).approve(await exchange.getAddress(), toTokenB(10000000000n)); + await tokenB.connect(david).approve(await exchange.getAddress(), toTokenB(10000000000n)); + + return { + owner, + alice, + bob, + charlie, + david, + exchange, + tokenA, + addressTokenA, + toTokenA, + tokenB, + addressTokenB, + toTokenB, + } +} + +describe('Exchange', () => { + + describe('.lastOrderId', () => { + describe('when buy or sell order is placed', () => { + it("should increment by 1", async function() { + const { exchange, alice, bob, charlie, david, tokenB, tokenA, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(tokenB, toTokenB(750n)); + await exchange.connect(alice).placeBuyOrder(toTokenB(150n), toTokenA(5n)); + expect(await exchange.currentOrderId()).to.equal(1); + + await exchange.connect(bob).deposit(tokenB, toTokenB(750n)); + await exchange.connect(bob).placeBuyOrder(toTokenB(150n), toTokenA(5n)); + expect(await exchange.currentOrderId()).to.equal(2); + + await exchange.connect(charlie).deposit(tokenB, toTokenB(750n)); + await exchange.connect(charlie).placeBuyOrder(toTokenB(150n), toTokenA(5n)); + expect(await exchange.currentOrderId()).to.equal(3); + + await exchange.connect(david).deposit(tokenA, toTokenA(5n)); + await exchange.connect(david).placeSellOrder(toTokenB(150n), toTokenA(5n)); + expect(await exchange.currentOrderId()).to.equal(3); // matched orders does not increment id + + await exchange.connect(david).deposit(tokenA, toTokenA(750n)); + await exchange.connect(david).placeBuyOrder(toTokenB(150n), toTokenA(5n)); + expect(await exchange.currentOrderId()).to.equal(4); // matched orders does not increment id + + }); + }); + }); + + describe('.placeBuyOrder', () => { + describe('when there is not matching sell order', () => { + it('should place buy order', async () => { + const { exchange, alice, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenB, toTokenB(1000n)); // price, volume + await exchange.connect(alice).placeBuyOrder(toTokenB(100n), toTokenA(10n)); // price, volume + const order = await exchange.buyOrders(1); // Accessing the first order + + expect(order.price).to.equal(toTokenB(100n)); + expect(order.volume).to.equal(toTokenA(10n)); + expect(order.trader).to.equal(alice.address); + expect(order.next).to.equal(0); // No next order + }); + + it("should place buy orders sorted by highest price", async function () { + const { exchange, alice, bob, charlie, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenB, toTokenB(600n)); + await exchange.connect(bob).deposit(addressTokenB, toTokenB(750n)); + await exchange.connect(charlie).deposit(addressTokenB, toTokenB(4600n)); + + await exchange.connect(alice).placeBuyOrder(toTokenB(200n), toTokenA(3n)); + await exchange.connect(bob).placeBuyOrder(toTokenB(150n), toTokenA(5n)); + await exchange.connect(charlie).placeBuyOrder(toTokenB(110n), toTokenA(10n)); + await exchange.connect(charlie).placeBuyOrder(toTokenB(100n), toTokenA(10n)); + + const firstOrder = await exchange.buyOrders(1); // The order with the highest price + const secondOrder = await exchange.buyOrders(2); + const thirdOrder = await exchange.buyOrders(3); + const fourthOrder = await exchange.buyOrders(4); + + expect(firstOrder.price).to.equal(toTokenB(200n)); + expect(secondOrder.price).to.equal(toTokenB(150n)); + expect(thirdOrder.price).to.equal(toTokenB(110n)); + expect(fourthOrder.price).to.equal(toTokenB(100n)); + + expect(firstOrder.next).to.equal(2); // Points to second highest price + expect(secondOrder.next).to.equal(3); + expect(thirdOrder.next).to.equal(4); + expect(fourthOrder.next).to.equal(0); + + // check if firstBuyOrderId is set correctly to the bid with the hihgest price + expect(await exchange.firstBuyOrderId()).to.equal(1); + + await exchange.connect(charlie).placeBuyOrder(toTokenB(250n), toTokenA(10n)); + const fifthOrder_2 = await exchange.buyOrders(5); + + expect(await exchange.firstBuyOrderId()).to.equal(5); + expect(fifthOrder_2.next).to.equal(1); + }); + }); + + describe('when there is matching sell order', () => { + describe('when is whole fulfillment', () => { + it('should trade one to one', async () => { + const { exchange, alice, bob, addressTokenB, addressTokenA, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(10n)); + await exchange.connect(bob).deposit(addressTokenB, toTokenB(1000n)); + + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(10n)); // price, volume + const trade = await exchange.connect(bob).placeBuyOrder(toTokenB(100n), toTokenA(10n)); // price, volume + + await expect(trade).to.emit(exchange, 'Trade').withArgs(toTokenA(10n), toTokenB(100n), bob.address, alice.address); + + }); + + it('should trade many to one', async () => { + const { exchange, alice, bob, charlie, david, addressTokenA, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(3n)); + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(3n)); // price, volume + + await exchange.connect(bob).deposit(addressTokenA, toTokenA(6n)); + await exchange.connect(bob).placeSellOrder(toTokenB(100n), toTokenA(6n)); // price, volume + + await exchange.connect(charlie).deposit(addressTokenA, toTokenA(1n)); + await exchange.connect(charlie).placeSellOrder(toTokenB(100n), toTokenA(1n)); // price, volume + const firstBuyOrderIdBefore = await exchange.firstBuyOrderId(); + + await exchange.connect(david).deposit(addressTokenB, toTokenB(1000n)); + const trade = await exchange.connect(david).placeBuyOrder(toTokenB(100n), toTokenA(10n)); // price, volume + await trade.wait(); + + const firstBuyOrderIdAfter = await exchange.firstBuyOrderId(); + expect(firstBuyOrderIdBefore).to.equal(firstBuyOrderIdAfter); // should not insert buy order to storage + + const events = (await exchange.queryFilter(exchange.filters.Trade, -1)); + expect(events.length).to.equal(3); + + await expect(trade) + .to.emit(exchange, 'Trade').withArgs(toTokenA(3n), toTokenB(100n), david.address, alice.address) + .to.emit(exchange, 'Trade').withArgs(toTokenA(6n), toTokenB(100n), david.address, bob.address) + .to.emit(exchange, 'Trade').withArgs(toTokenA(1n), toTokenB(100n), david.address, charlie.address) + + }); + }); + + describe('when is partial fulfillment', () => { + it('should trade and insert remainder volume', async () => { + const { exchange, alice, bob, addressTokenA, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(10n)); + const tx = await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(10n)); // price, volume + await tx.wait(); + + await exchange.connect(bob).deposit(addressTokenB, toTokenB(1500n)); + const trade = await exchange.connect(bob).placeBuyOrder(toTokenB(100n), toTokenA(15n)); // price, volume + await trade.wait(); + + const events1 = (await exchange.queryFilter(exchange.filters.Trade, -1)); + expect(events1.length).to.equal(1); + + const events2 = (await exchange.queryFilter(exchange.filters.NewOrder, -2)); + expect(events2.length).to.equal(2); + + await expect(trade) + .to.emit(exchange, 'Trade').withArgs(toTokenA(10n), toTokenB(100n), bob.address, alice.address) + .to.emit(exchange, 'NewOrder').withArgs(true, 2, bob.address, toTokenB(100n), toTokenA(5n)); + + }); + }); + }); + }); + + describe('.placeSellOrder', () => { + describe('when trying to place sell order', () => { + it('should succeed', async () => { + const { exchange, alice, addressTokenA, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(10n)); // price, volume + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(10n)); // price, volume + const order = await exchange.sellOrders(1); // Accessing the first order + + expect(order.price).to.equal(toTokenB(100n)); + expect(order.volume).to.equal(toTokenA(10n)); + expect(order.trader).to.equal(alice.address); + expect(order.next).to.equal(0); // No next order + }); + + it("should insert sell orders sorted by lowest price", async function () { + const { exchange, alice, bob, charlie, addressTokenA, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(5n)); + await exchange.connect(alice).placeSellOrder(toTokenB(150n), toTokenA(5n)); + + await exchange.connect(bob).deposit(addressTokenA, toTokenA(3n)); + await exchange.connect(bob).placeSellOrder(toTokenB(200n), toTokenA(3n)); + + await exchange.connect(charlie).deposit(addressTokenA, toTokenA(20n)); + await exchange.connect(charlie).placeSellOrder(toTokenB(100n), toTokenA(10n)); + + const firstOrder = await exchange.sellOrders(3); // The order with the highest price + const secondOrder = await exchange.sellOrders(1); + const thirdOrder = await exchange.sellOrders(2); + + expect(firstOrder.price).to.equal(toTokenB(100n)); + expect(secondOrder.price).to.equal(toTokenB(150n)); + expect(thirdOrder.price).to.equal(toTokenB(200n)); + + expect(firstOrder.next).to.equal(1); // Points to second highest price + expect(secondOrder.next).to.equal(2); // Points to lowest price + expect(thirdOrder.next).to.equal(0); // No next order + + // check if firstBuyOrderId is set correctly to the bid with the hihgest price + expect(await exchange.firstSellOrderId()).to.equal(3); + + await exchange.connect(charlie).placeSellOrder(toTokenB(90n), toTokenA(10n)); + + expect(await exchange.firstSellOrderId()).to.equal(4); + }); + }); + }); + + describe('.balanceOf', () => { + describe('when a buy order is placed', () => { + it('should not increase balanceOf token B', async () => { + const { exchange, alice, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenB, toTokenB(13200n)); // price, volume + await exchange.connect(alice).placeBuyOrder(toTokenB(100n), toTokenA(132n)); // price, volume + + expect(await exchange.balanceOf(alice.address, await exchange.tokenB())).to.be.equal(0); + }); + + it('should increase only remainder balance', async () => { + const { exchange, alice, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenB, toTokenB(1000n)); + await exchange.connect(alice).placeBuyOrder(toTokenB(100n), toTokenA(5n)); // price, volume + + expect(await exchange.balanceOf(alice.address, await exchange.tokenB())).to.be.equal(toTokenB(500n)); + }); + }); + + describe('when a sell order is placed', () => { + it('should not increase balanceOf token A', async () => { + const { exchange, alice, addressTokenA, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(132n)); + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(132n)); // price, volume + + expect(await exchange.balanceOf(alice.address, await exchange.tokenA())).to.be.equal(0); + }); + + it('should only increade remainder balance', async () => { + const { exchange, alice, addressTokenA, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(100n)); + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(50n)); // price, volume + + expect(await exchange.balanceOf(alice.address, await exchange.tokenA())).to.be.equal(toTokenA(50n)); + }); + + }); + + describe('when a trade happens', () => { + describe('when the price is the same', () => { + it('should trade', async () => { + const { exchange, alice, bob, charlie, david, addressTokenA, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(100n)); + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(100n)); // price, volume + + await exchange.connect(bob).deposit(addressTokenA, toTokenA(30n)); + await exchange.connect(bob).placeSellOrder(toTokenB(100n), toTokenA(30n)); // price, volume + + await exchange.connect(charlie).deposit(addressTokenA, toTokenA(20n)); + await exchange.connect(charlie).placeSellOrder(toTokenB(100n), toTokenA(20n)); // price, volume + + await exchange.connect(david).deposit(addressTokenB, toTokenB(20000n)); + await exchange.connect(david).placeBuyOrder(toTokenB(100n), toTokenA(200n)); // price, volume + + expect(await exchange.balanceOf(alice.address, await exchange.tokenA())).to.be.equal(0); + expect(await exchange.balanceOf(bob.address, await exchange.tokenA())).to.be.equal(0); + expect(await exchange.balanceOf(charlie.address, await exchange.tokenA())).to.be.equal(0); + expect(await exchange.balanceOf(david.address, await exchange.tokenA())).to.be.equal(toTokenA(150n)); + expect(await exchange.balanceOf(david.address, await exchange.tokenB())).to.be.equal(0); + }); + }); + describe('when the price is different', () => { + it('should trade with Market Price Priority', async () => { + const { exchange, alice, bob, charlie, david, addressTokenA, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(100n)); + await exchange.connect(alice).placeSellOrder(toTokenB(99n), toTokenA(100n)); // price, volume + + await exchange.connect(bob).deposit(addressTokenA, toTokenA(30n)); + await exchange.connect(bob).placeSellOrder(toTokenB(100n), toTokenA(30n)); // price, volume + + await exchange.connect(charlie).deposit(addressTokenA, toTokenA(20n)); + await exchange.connect(charlie).placeSellOrder(toTokenB(100n), toTokenA(20n)); // price, volume + + await exchange.connect(david).deposit(addressTokenB, toTokenB(20000n)); + await exchange.connect(david).placeBuyOrder(toTokenB(100n), toTokenA(200n)); // price, volume + + expect(await exchange.balanceOf(alice.address, await exchange.tokenA())).to.be.equal(0); + expect(await exchange.balanceOf(bob.address, await exchange.tokenA())).to.be.equal(0); + expect(await exchange.balanceOf(charlie.address, await exchange.tokenA())).to.be.equal(0); + expect(await exchange.balanceOf(david.address, await exchange.tokenA())).to.be.equal(toTokenA(150n)); + + const deposited = toTokenB(20000n); // tokenB + const traded = (toTokenB(100n * 20n)) + (toTokenB(100n *30n)) + (toTokenB(99n * 100n)); // tokenB + const remainderCreated = toTokenB(100n * 50n); // tokenB + + expect(await exchange.balanceOf(david.address, await exchange.tokenB())).to.be.equal(deposited - traded - remainderCreated); + + }); + }); + }); + }); + + describe('.withdraw', () => { + describe('when token is invalid', () => { + it('should revert', async () => { + const { exchange, alice } = await loadFixture(deployExchangeFixture); + + await expect(exchange.connect(alice).withdraw(ethers.ZeroAddress, 100)) + .to.be.revertedWith('Invalid token'); + }); + }); + + describe('when amount is invalid', () => { + it('should revert', async () => { + const { exchange, alice, addressTokenA, toTokenA } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(1000n)); + await expect(exchange.connect(alice).withdraw(addressTokenA, 0)) + .to.be.revertedWith('Invalid amount'); + }); + }); + + describe('when trader has not enough balance', () => { + it('should revert', async () => { + const { exchange, alice, addressTokenA, toTokenA } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(999n)); + await expect(exchange.connect(alice).withdraw(addressTokenA, toTokenA(1000n))) + .to.be.revertedWith('Not enough balance'); + }); + }); + + describe('when trader does not cancel a buy order', () => { + it('it should revert', async () => { + const { exchange, alice, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenB, toTokenB(1000n)); + await exchange.connect(alice).placeBuyOrder(toTokenB(100n), toTokenA(10n)); + + await expect(exchange.connect(alice).withdraw(addressTokenB, toTokenB(10n))) + .to.be.revertedWith('Not enough balance'); + }); + }); + + describe('when trader does not cancel a sell order', () => { + it('it should revert', async () => { + const { exchange, alice, addressTokenA, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(10n)); + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(10n)); + await expect(exchange.connect(alice).withdraw(addressTokenA, toTokenA(10n))) + .to.be.revertedWith('Not enough balance'); + }); + }); + + describe('when trader does not cancel the order', () => { + it('it should revert', async () => { + const { exchange, alice, addressTokenA, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(10n)); + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(10n)); + await expect(exchange.connect(alice).withdraw(addressTokenA, toTokenA(10n))) + .to.be.revertedWith('Not enough balance'); + }); + }); + + describe('when trader has balance', () => { + it('it should withdraw', async () => { + const { exchange, alice, addressTokenA, toTokenA } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(10n)); + expect(await exchange.balanceOf(alice.address, addressTokenA)).to.equal(toTokenA(10n)); + + const withdraw = await exchange.connect(alice).withdraw(addressTokenA, toTokenA(10n)); + expect(await exchange.balanceOf(alice.address, addressTokenA)).to.equal(0); + + expect(withdraw).to.emit(exchange, "Withdraw").withArgs(alice.address, addressTokenA, toTokenA(10n)); + + }); + }); + }); + + describe('.deposit', () => { + describe('when amount is invalid', () => { + it('should revert', async () => { + const { exchange, alice, addressTokenA, toTokenA } = await loadFixture(deployExchangeFixture); + + await expect(exchange.connect(alice).deposit(addressTokenA, toTokenA(0n))) + .to.be.revertedWith('Invalid amount'); + }); + }); + + describe('when token is invalid', () => { + it('should revert', async () => { + const { exchange, alice } = await loadFixture(deployExchangeFixture); + + await expect(exchange.connect(alice).deposit(ethers.ZeroAddress, 100)) + .to.be.revertedWith('Invalid token'); + }); + }); + + describe('when deposit is valid', () => { + it('should deposit', async () => { + const { exchange, alice, addressTokenA, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + + await exchange.connect(alice).deposit(addressTokenA, toTokenA(100n)); + await exchange.connect(alice).deposit(addressTokenB, toTokenB(1000n)); + + expect(await exchange.balanceOf(alice.address, addressTokenA)).to.equal(toTokenA(100n)) + expect(await exchange.balanceOf(alice.address, addressTokenB)).to.equal(toTokenB(1000n)) + }); + }); + }); + + describe('.cancelOrder', () => { + describe('when order dont exists', () => { + it('should revert', async () => { + const { exchange, alice } = await loadFixture(deployExchangeFixture); + + await expect(exchange.connect(alice).cancelOrder(1, true)).to.be.revertedWith('Order do not exists'); + }); + }); + + describe('when order exists', () => { + describe('when is buy Order', () => { + describe('when caller is not the order owner', () => { + it('should revert', async () => { + const { exchange, alice, bob, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + await exchange.connect(alice).deposit(addressTokenB, toTokenB(1000n)); + await exchange.connect(alice).placeBuyOrder(toTokenB(100n), toTokenA(10n)); + await expect(exchange.connect(bob).cancelOrder(1, true)).to.be.revertedWith('Only the order creator can cancel this order'); + }); + }); + describe('when order is fulfilled', () => { + it('should revert', async () => { + const { exchange, alice, bob, addressTokenA, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + await exchange.connect(alice).deposit(addressTokenB, toTokenB(1000n)); + await exchange.connect(alice).placeBuyOrder(toTokenB(100n), toTokenA(10n)); + await exchange.connect(bob).deposit(addressTokenA, toTokenA(10n)); + await exchange.connect(bob).placeSellOrder(toTokenB(100n), toTokenA(10n)); + + await expect(exchange.connect(alice).cancelOrder(1, true)).to.be.revertedWith('Order do not exists'); + }); + }); + + describe('when caller is the orders owner', () => { + it('should cancel order and refund balance', async () => { + const { exchange, alice, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + await exchange.connect(alice).deposit(addressTokenB, toTokenB(1000n)); + await exchange.connect(alice).placeBuyOrder(toTokenB(100n), toTokenA(10n)); + + expect(await exchange.balanceOf(alice.address, addressTokenB)).to.be.equal(0); + + const cancelation = await exchange.connect(alice).cancelOrder(1, true); + + expect(await exchange.balanceOf(alice.address, addressTokenB)).to.be.equal(toTokenB(1000n)); + + await expect(cancelation).to.emit(exchange, 'OrderCanceled').withArgs(true, 1, alice.address); + + }); + }); + }); + + describe('when is sell Order', () => { + describe('when caller is not the order owner', () => { + it('should revert', async () => { + const { exchange, alice, bob, addressTokenA, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + await exchange.connect(alice).deposit(addressTokenA, toTokenA(10n)); + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(10n)); + await expect(exchange.connect(bob).cancelOrder(1, false)).to.be.revertedWith('Only the order creator can cancel this order'); + }); + }); + + describe('when order is fulfilled', () => { + it('should revert', async () => { + const { exchange, alice, bob, addressTokenA, addressTokenB, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + await exchange.connect(alice).deposit(addressTokenA, toTokenA(10n)); + await exchange.connect(bob).deposit(addressTokenB, toTokenB(1000n)); + + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(10n)); + await exchange.connect(bob).placeBuyOrder(toTokenB(100n), toTokenA(10n)); + + await expect(exchange.connect(alice).cancelOrder(1, false)).to.be.revertedWith('Order do not exists'); + }); + }); + + describe('when caller is the orders owner', () => { + it('should cancel order and refund balance', async () => { + const { exchange, alice, addressTokenA, toTokenA, toTokenB } = await loadFixture(deployExchangeFixture); + await exchange.connect(alice).deposit(addressTokenA, toTokenA(10n)); + await exchange.connect(alice).placeSellOrder(toTokenB(100n), toTokenA(10n)); + + expect(await exchange.balanceOf(alice.address, addressTokenA)).to.be.equal(0); + + const cancelation = await exchange.connect(alice).cancelOrder(1, false); + + expect(await exchange.balanceOf(alice.address, addressTokenA)).to.be.equal(toTokenA(10n)); + + await expect(cancelation).to.emit(exchange, 'OrderCanceled').withArgs(false, 1, alice.address); + + }); + }); + }); + }); + }); +}); diff --git a/test/orderbook/load/exchange.load-2.test.ts b/test/orderbook/load/exchange.load-2.test.ts new file mode 100644 index 0000000..d052082 --- /dev/null +++ b/test/orderbook/load/exchange.load-2.test.ts @@ -0,0 +1,348 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import Utils, { Operator } from '../../utils'; +import { AccountId, PrivateKey, TokenId, ContractId, Client, ContractFunctionParameters, TransactionRecord, AccountCreateTransaction, Hbar, StatusError } from '@hashgraph/sdk'; +import { AbiCoder } from 'ethers'; +import { bytecode as ExchangeHTSbytecode } from '../../../data/abis/ExchangeHTS.json'; +import elliptic from 'elliptic'; + +const depositToken = async (client: Client, operator: Operator, contract: ContractId, tokenId: TokenId | string, amount: number) => { + client.setOperator( + operator.accountId, + operator.key + ); + + if (typeof tokenId != 'string') { + tokenId = `0x${tokenId.toSolidityAddress()}`; + } + + const functionName = "deposit"; + const params = new ContractFunctionParameters() + .addAddress(tokenId) + .addInt64(amount.toString()); + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params); + + if (receipt.status._code != 22) { + throw new Error('Error depositing token status code' + receipt.status._code); + } + + return {receipt, record}; +} + +const placeBuyOrder = async (client: Client, operator: Operator, contract: ContractId, price: number, volume: number) => { + client.setOperator( + operator.accountId, + operator.key + ); + const functionName = "placeBuyOrder"; + const params = new ContractFunctionParameters() + .addInt64(price.toString()) + .addInt64(volume.toString()); + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params, { gas : 600_000}); + + if (receipt.status._code != 22) { + throw new Error('Error depositing token status code' + receipt.status._code); + } + + return { receipt, record }; +} + +const queryBalanceOfExchange = async (client: Client, contract: ContractId, userAddress: string, tokenAddress: string) => { + const functionName = "balanceOf"; + const params = new ContractFunctionParameters() + .addAddress(userAddress) + .addAddress(tokenAddress); + + // Decode the result + const result = await Utils.queryContract(client, contract, functionName, params); + const balance = result.getInt64(0); // Adjust based on your function's return type + return balance; +} + +const placeSellOrder = async (client: Client, operator: Operator, contract: ContractId, price: number, volume: number) => { + client.setOperator( + operator.accountId, + operator.key + ); + const functionName = "placeSellOrder"; + const params = new ContractFunctionParameters() + .addInt64(price.toString()) + .addInt64(volume.toString()); + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params, { gas : 600_000}); + + if (receipt.status._code != 22) { + throw new Error('Error depositing token status code' + receipt.status._code); + } + + return { receipt, record }; +} + +const eventsEmited = (record: TransactionRecord, eventTopic: string) => { + return record.contractFunctionResult?.logs.reduce((acc: Uint8Array[], log) => { + for (const topic of log.topics) { + const pTopic = `0x${Buffer.from(topic).toString('hex')}`; + if (pTopic === eventTopic) { + acc.push(log.data); + } + } + return acc; + }, []) +} + +function getRandomInt(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function returnOneOf(list: T[], except?: T) { + if (list.length === 0) { + throw new Error('returnOneOf: empty list'); + } + + let filteredList = except !== undefined ? list.filter(item => item !== except) : list; + + if (filteredList.length === 0) { + return list[0]; // Handle case where all items are excluded + } + + const randomIndex = Math.floor(Math.random() * filteredList.length); + return filteredList[randomIndex]; +} + +const createECDSAKeyPair = () => { + const EC = elliptic.ec; + const ec = new EC('secp256k1'); + const keyPair = ec.genKeyPair(); + const privateKey = keyPair.getPrivate('hex'); + const publicKey = keyPair.getPublic('hex'); + + return { privateKey, publicKey } +} + +const createAccount = async (client: Client, operator: Operator): Promise => { + + client.setOperator(operator.accountId, operator.key); + + const key = PrivateKey.fromStringECDSA(createECDSAKeyPair().privateKey); + const accountCreate = new AccountCreateTransaction() + .setKey(key) + .setInitialBalance(new Hbar(3)) + .setTransactionValidDuration(180) + .freezeWith(client); + + const tx = await accountCreate.execute(client); + const receipt = await tx.getReceipt(client); + + console.log(`- account created with id ${receipt.accountId}`) + + return { accountId: receipt.accountId as AccountId, key, address: `0x${receipt.accountId?.toSolidityAddress()}` }; +} + +const retryThat = async (action: string, maxRetries: number, retryDelay: number, callback: Function) => { + let attempt = 0; + while (attempt < maxRetries) { + try{ + const result = await callback(); + + return result; + } catch (error) { + //@ts-ignore + console.error(`---- Error ${action}: ${error?.message} ----`); + + if (error instanceof StatusError && error.status.toString() === 'TRANSACTION_EXPIRED') { + attempt++; + console.warn(`Transaction expired. Retry attempt ${attempt}/${maxRetries}`); + if (attempt < maxRetries) { + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } + } else { + throw error; + } + } + } +} + +async function deployExchangeFixture() { + const client = Client.forTestnet().setMaxAttempts(10000) + //Create your local client + // const node = {"127.0.0.1:50211": new AccountId(3)}; + // const client = Client.forNetwork(node).setMirrorNetwork("127.0.0.1:5600") + // .setMaxAttempts(100_000_000) + // .setMaxBackoff(100_000_000); + + const operatorAccountId = AccountId.fromString(process.env.ACCOUNT_ID as string); + const operatorPrivateKey = PrivateKey.fromStringECDSA(process.env.PRIVATE_KEY as string); + + + const operator = { + accountId: operatorAccountId, + key: operatorPrivateKey, + address: '0x' + (operatorAccountId.toSolidityAddress().toLowerCase()) + } + + client.setOperator( + operator.accountId, + operator.key + ); + + // client.setOperator(AccountId.fromString("0.0.2"),PrivateKey.fromStringED25519("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137")); + + const idTokenA = TokenId.fromSolidityAddress("0x000000000000000000000000000000000043cca1"); + const idTokenB = TokenId.fromSolidityAddress("0x000000000000000000000000000000000043cca2"); + const exchangeId = ContractId.fromString("0.0.4443300"); + // const idTokenA = await Utils.createFungibleToken(client, operator.accountId, operator.key, 'Test Token A', 'TOKEN_A'); + // const idTokenB = await Utils.createFungibleToken(client, operator.accountId, operator.key, 'Test Token B', 'TOKEN_B'); + + const addressTokenA = '0x' + idTokenA.toSolidityAddress(); + const addressTokenB = '0x' + idTokenB.toSolidityAddress(); + + // const exchangeId = await Utils.deployContract( + // client, + // operator, + // ExchangeHTSbytecode, + // [addressTokenA, addressTokenB] + // ); + + // console.log(`deployed exchange at ${exchangeId} or ${exchangeId.toSolidityAddress()}`) + + const toTokenA = (_number: number) => { + return _number * 10 ** 8; + } + + const toTokenB = (_number: number) => { + return _number * 10 ** 8; + } + + const fromTokenA = (_number: number) => { + return _number / 10 ** 8; + } + + const fromTokenB = (_number: number) => { + return _number / 10 ** 8; + } + + const minting = [ + Utils.tokenMint(client, operator, idTokenA, toTokenA(100_000_000)), + Utils.tokenMint(client, operator, idTokenB, toTokenB(100_000_000)), + ]; + + await Promise.all(minting); + + return { + client, + operator, + exchangeId, + idTokenA, + idTokenB, + toTokenA, + toTokenB, + addressTokenA, + addressTokenB, + fromTokenA, + fromTokenB + } +} + +describe('Load Test Exchange HTS', () => { + describe('Exchange', () => { + it('should load test', async () => { + const { client, operator, exchangeId, idTokenB, idTokenA, toTokenB, toTokenA, addressTokenA, addressTokenB, fromTokenA, fromTokenB } = await deployExchangeFixture(); + + function hasTradeEvent (record: TransactionRecord) { + const tradeEvent = eventsEmited(record, ethers.id("Trade(int64,int64,address,address)")) as Uint8Array []; + if (tradeEvent.length){ + for( const trade of tradeEvent) { + const parsedLogTrade = AbiCoder.defaultAbiCoder().decode( + ["uint256", "uint256", "address"], + trade + ); + console.log(`-- Trade happend with ${parsedLogTrade.toString()}`); + } + } + + return tradeEvent.length; + } + + const list = new Array(1300).fill(0); + const userListSell = (await Promise.allSettled(list.slice(0, list.length / 2).map(async (_, index) => { + const price = toTokenB(getRandomInt(1000, 1020)) + const amount = toTokenA(getRandomInt(1, 5)); + + return retryThat('Creating Sell User', 5, 200, async () => { + const newAccount = await createAccount(client, operator); + await Utils.associateTokenToAccount(client, idTokenA, newAccount); + await Utils.transferToken(client, idTokenA, operator, newAccount.accountId, amount); + await Utils.approveToken(client, idTokenA, newAccount, exchangeId, (amount)); + await depositToken(client, newAccount, exchangeId, idTokenA, amount); + return { account: newAccount, price: price, amount: amount } + }); + + }))) + .filter(p => p.status === 'fulfilled') + // @ts-ignore + .map((p) => p.value); + + const userListBuy = (await Promise.allSettled(list.slice(list.length / 2).map(async (_, index) => { + const price = getRandomInt(1000, 1020); + const amount = getRandomInt(1, 5); + + return retryThat('Creating Buy User', 5, 200, async () => { + const newAccount = await createAccount(client, operator); + await Utils.associateTokenToAccount(client, idTokenB, newAccount); + await Utils.transferToken(client, idTokenB, operator, newAccount.accountId, toTokenB(price * amount)); + await Utils.approveToken(client, idTokenB, newAccount, exchangeId, toTokenB(price * amount)); + await depositToken(client, newAccount, exchangeId, idTokenB, toTokenB(price * amount)); + + return { account: newAccount, price: toTokenB(price), amount: toTokenA(amount) } + }); + + }))) + .filter(p => p.status === 'fulfilled') + // @ts-ignore + .map((p) => p.value); + + const buyOrders = userListBuy.map(async (trader, index) => { + await new Promise((res) => { setTimeout(() => {res(true)}, 200 * index)}); + const { record: recordBuy} = await placeBuyOrder(client, trader.account, exchangeId, trader.price, trader.amount); + hasTradeEvent(recordBuy); + return trader; + }); + + const sellOrders = userListSell.map(async (trader, index) => { + await new Promise((res) => { setTimeout(() => {res(true)}, 200 * (index + 1))}); + const { record: recordSell } = await placeSellOrder(client, trader.account, exchangeId, trader.price, trader.amount); + hasTradeEvent(recordSell); + return trader; + }) + + try { + const results = await Promise.allSettled([...buyOrders, ...sellOrders]); + const rejected = results.filter(r => r.status === 'rejected'); + const fulfilled = results.filter(r => r.status === 'fulfilled'); + + + console.log({ + status: 'rejected', + len : rejected.length, + // @ts-ignore + ids: rejected.map(rej => rej?.reason?.transactionId) + }) + + console.log({ + fulfilled: { + len: fulfilled.length, + } + }); + + } catch (error) { + console.error("Error placing orders"); + console.error(error); + expect.fail('Error placing orders'); + } + + }); + }); +}); + diff --git a/test/orderbook/load/exchange.load.test.ts b/test/orderbook/load/exchange.load.test.ts new file mode 100644 index 0000000..9de8452 --- /dev/null +++ b/test/orderbook/load/exchange.load.test.ts @@ -0,0 +1,413 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import Utils, { Operator } from '../../utils'; +import { AccountId, PrivateKey, TokenId, ContractId, Client, ContractFunctionParameters, TransactionRecord, AccountCreateTransaction, Hbar } from '@hashgraph/sdk'; +import { AbiCoder } from 'ethers'; +import { bytecode as ExchangeHTSbytecode } from '../../../data/abis/ExchangeHTS.json'; +import elliptic from 'elliptic'; + +const depositToken = async (client: Client, operator: Operator, contract: ContractId, tokenId: TokenId | string, amount: number) => { + client.setOperator( + operator.accountId, + operator.key + ); + + if (typeof tokenId != 'string') { + tokenId = `0x${tokenId.toSolidityAddress()}`; + } + + const functionName = "deposit"; + const params = new ContractFunctionParameters() + .addAddress(tokenId) + .addInt64(amount.toString()); + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params); + + if (receipt.status._code != 22) { + throw new Error('Error depositing token status code' + receipt.status._code); + } + + return {receipt, record}; +} + +const placeBuyOrder = async (client: Client, operator: Operator, contract: ContractId, price: number, volume: number) => { + client.setOperator( + operator.accountId, + operator.key + ); + const functionName = "placeBuyOrder"; + const params = new ContractFunctionParameters() + .addInt64(price.toString()) + .addInt64(volume.toString()); + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params, { gas : 600_000}); + + if (receipt.status._code != 22) { + throw new Error('Error depositing token status code' + receipt.status._code); + } + + return { receipt, record }; +} + +const queryBalanceOfExchange = async (client: Client, contract: ContractId, userAddress: string, tokenAddress: string) => { + const functionName = "balanceOf"; + const params = new ContractFunctionParameters() + .addAddress(userAddress) + .addAddress(tokenAddress); + + // Decode the result + const result = await Utils.queryContract(client, contract, functionName, params); + const balance = result.getInt64(0); // Adjust based on your function's return type + return balance; +} + +const placeSellOrder = async (client: Client, operator: Operator, contract: ContractId, price: number, volume: number) => { + client.setOperator( + operator.accountId, + operator.key + ); + const functionName = "placeSellOrder"; + const params = new ContractFunctionParameters() + .addInt64(price.toString()) + .addInt64(volume.toString()); + + const { receipt, record } = await Utils.executeContract(client, operator, contract, functionName, params, { gas : 600_000}); + + if (receipt.status._code != 22) { + throw new Error('Error depositing token status code' + receipt.status._code); + } + + return { receipt, record }; +} + +const eventsEmited = (record: TransactionRecord, eventTopic: string) => { + return record.contractFunctionResult?.logs.reduce((acc: Uint8Array[], log) => { + for (const topic of log.topics) { + const pTopic = `0x${Buffer.from(topic).toString('hex')}`; + if (pTopic === eventTopic) { + acc.push(log.data); + } + } + return acc; + }, []) +} + +function getRandomInt(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function returnOneOf(list: T[], except?: T) { + if (list.length === 0) { + throw new Error('returnOneOf: empty list'); + } + + let filteredList = except !== undefined ? list.filter(item => item !== except) : list; + + if (filteredList.length === 0) { + return list[0]; // Handle case where all items are excluded + } + + const randomIndex = Math.floor(Math.random() * filteredList.length); + return filteredList[randomIndex]; +} + + +const createECDSAKeyPair = () => { + const EC = elliptic.ec; + const ec = new EC('secp256k1'); + const keyPair = ec.genKeyPair(); + const privateKey = keyPair.getPrivate('hex'); + const publicKey = keyPair.getPublic('hex'); + + return { privateKey, publicKey } +} + +const createAccount = async (client: Client, operator: Operator): Promise => { + + client.setOperator(operator.accountId, operator.key); + + const key = PrivateKey.fromStringECDSA(createECDSAKeyPair().privateKey); + const accountCreate = new AccountCreateTransaction() + .setKey(key) + .setInitialBalance(new Hbar(1000)) + .freezeWith(client); + + const tx = await accountCreate.execute(client); + const receipt = await tx.getReceipt(client); + + console.log(`- account created with id ${receipt.accountId}`) + + return { accountId: receipt.accountId as AccountId, key, address: `0x${receipt.accountId?.toSolidityAddress()}` }; +} + +async function deployExchangeFixture() { + const node = {"127.0.0.1:50211": new AccountId(3)}; + const client = Client.forNetwork(node).setMirrorNetwork("127.0.0.1:5600").setMaxAttempts(100_000_000).setMaxBackoff(100_000_000); + + const operatorAccountId = AccountId.fromString(process.env.ACCOUNT_ID as string); + const operatorPrivateKey = PrivateKey.fromStringED25519(process.env.PRIVATE_KEY as string); + + const aliceAccountId = AccountId.fromString(process.env.ALICE_ACCOUNT_ID as string); + const alicePrivateKey = PrivateKey.fromStringED25519(process.env.ALICE_KEY as string); + + const bobAccountId = AccountId.fromString(process.env.BOB_ACCOUNT_ID as string); + const bobPrivateKey = PrivateKey.fromStringECDSA(process.env.BOB_KEY as string); + + const charlieAccountId = AccountId.fromString(process.env.CHARLIE_ACCOUNT_ID as string); + const charliePrivateKey = PrivateKey.fromStringECDSA(process.env.CHARLIE_KEY as string); + + const davidAccountId = AccountId.fromString(process.env.DAVID_ACCOUNT_ID as string); + const davidPrivateKey = PrivateKey.fromStringED25519(process.env.DAVID_KEY as string); + + const operator = { + accountId: operatorAccountId, + key: operatorPrivateKey, + address: '0x' + (operatorAccountId.toSolidityAddress().toLowerCase()) + } + + + // { + // accountId: aliceAccountId, + // key: alicePrivateKey, + // address: '0x' + (aliceAccountId.toSolidityAddress().toLowerCase()) + // } + + // const bob = { + // accountId: bobAccountId, + // key: bobPrivateKey, + // address: '0x' + (bobAccountId.toSolidityAddress().toLowerCase()) + // } + + // const charlie = { + // accountId: charlieAccountId, + // key: charliePrivateKey, + // address: '0x' + (charlieAccountId.toSolidityAddress().toLowerCase()) + // } + + // const david = { + // accountId: davidAccountId, + // key: davidPrivateKey, + // address: '0x' + (davidAccountId.toSolidityAddress().toLowerCase()) + // } + + // client.setOperator( + // operator.accountId, + // operator.key + // ); + + client.setOperator(AccountId.fromString("0.0.2"),PrivateKey.fromStringED25519("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137")); + + const alice = await createAccount(client, operator) + const bob = await createAccount(client, operator) + const charlie = await createAccount(client, operator) + const david = await createAccount(client, operator) + + // const idTokenA = TokenId.fromString('0.0.4421954'); + // const idTokenB = TokenId.fromString('0.0.4421955'); + // const exchangeId = ContractId.fromString("0.0.4422160"); + const idTokenA = await Utils.createFungibleToken(client, operator.accountId, operator.key, 'Test Token A', 'TOKEN_A'); + const idTokenB = await Utils.createFungibleToken(client, operator.accountId, operator.key, 'Test Token B', 'TOKEN_B'); + + const addressTokenA = '0x' + idTokenA.toSolidityAddress(); + const addressTokenB = '0x' + idTokenB.toSolidityAddress(); + + const exchangeId = await Utils.deployContract( + client, + operator, + ExchangeHTSbytecode, + [addressTokenA, addressTokenB] + ); + + console.log(`deployed exchange at ${exchangeId} or ${exchangeId.toSolidityAddress()}`) + + const toTokenA = (_number: number) => { + return _number * 10 ** 8; + } + + const toTokenB = (_number: number) => { + return _number * 10 ** 8; + } + + const fromTokenA = (_number: number) => { + return _number / 10 ** 8; + } + + const fromTokenB = (_number: number) => { + return _number / 10 ** 8; + } + + const minting = [ + Utils.tokenMint(client, operator, idTokenA, toTokenA(1_000_000 * 4)), + Utils.tokenMint(client, operator, idTokenB, toTokenB(1_000_000 * 4)), + ]; + + await Promise.all(minting); + + const associations = [ + // token A + Utils.associateTokenToAccount(client, idTokenA, alice), + Utils.associateTokenToAccount(client, idTokenA, bob), + Utils.associateTokenToAccount(client, idTokenA, charlie), + Utils.associateTokenToAccount(client, idTokenA, david), + // token B + Utils.associateTokenToAccount(client, idTokenB, alice), + Utils.associateTokenToAccount(client, idTokenB, bob), + Utils.associateTokenToAccount(client, idTokenB, charlie), + Utils.associateTokenToAccount(client, idTokenB, david), + ]; + + await Promise.all(associations); + + const transfers = [ + // token A + Utils.transferToken(client, idTokenA, operator, alice.accountId, toTokenA(1_000_000)), + Utils.transferToken(client, idTokenA, operator, bob.accountId, toTokenA(1_000_000)), + Utils.transferToken(client, idTokenA, operator, charlie.accountId, toTokenA(1_000_000)), + Utils.transferToken(client, idTokenA, operator, david.accountId, toTokenA(1_000_000)), + // // token B + Utils.transferToken(client, idTokenB, operator, alice.accountId, toTokenB(1_000_000)), + Utils.transferToken(client, idTokenB, operator, bob.accountId, toTokenB(1_000_000)), + Utils.transferToken(client, idTokenB, operator, charlie.accountId, toTokenB(1_000_000)), + Utils.transferToken(client, idTokenB, operator, david.accountId, toTokenB(1_000_000)), + // token A + Utils.approveToken(client, idTokenA, alice, exchangeId, toTokenA(1_000_000)), + Utils.approveToken(client, idTokenA, bob, exchangeId, toTokenA(1_000_000)), + Utils.approveToken(client, idTokenA, charlie, exchangeId, toTokenA(1_000_000)), + Utils.approveToken(client, idTokenA, david, exchangeId, toTokenA(1_000_000)), + // token B + Utils.approveToken(client, idTokenB, alice, exchangeId, toTokenB(1_000_000)), + Utils.approveToken(client, idTokenB, bob, exchangeId, toTokenB(1_000_000)), + Utils.approveToken(client, idTokenB, charlie, exchangeId, toTokenB(1_000_000)), + Utils.approveToken(client, idTokenB, david, exchangeId, toTokenB(1_000_000)), + ]; + + await Promise.all(transfers); + + return { + client, + operator, + alice, + bob, + charlie, + david, + exchangeId, + idTokenA, + idTokenB, + toTokenA, + toTokenB, + addressTokenA, + addressTokenB, + fromTokenA, + fromTokenB + } +} + +describe('Load Test Exchange HTS', () => { + describe('Exchange', () => { + it('should load test', async () => { + const { client, alice, bob, charlie, david, exchangeId, idTokenB, idTokenA, toTokenB, toTokenA, addressTokenA, addressTokenB, fromTokenA, fromTokenB } = await deployExchangeFixture(); + + // deposits + await Promise.all([ + depositToken(client, alice, exchangeId, idTokenA, toTokenA(1_000_000)), + depositToken(client, bob, exchangeId, idTokenA, toTokenA(1_000_000)), + depositToken(client, charlie, exchangeId, idTokenA, toTokenA(1_000_000)), + depositToken(client, david, exchangeId, idTokenA, toTokenA(1_000_000)), + + depositToken(client, alice, exchangeId, idTokenB, toTokenB(1_000_000)), + depositToken(client, bob, exchangeId, idTokenB, toTokenB(1_000_000)), + depositToken(client, charlie, exchangeId, idTokenB, toTokenB(1_000_000)), + depositToken(client, david, exchangeId, idTokenB, toTokenB(1_000_000)), + ]); + + function hasTradeEvent (record: TransactionRecord) { + const tradeEvent = eventsEmited(record, ethers.id("Trade(int64,int64,address,address)")) as Uint8Array []; + if (tradeEvent.length){ + for( const trade of tradeEvent) { + const parsedLogTrade = AbiCoder.defaultAbiCoder().decode( + ["uint256", "uint256", "address"], + trade + ); + console.log(`-- Trade happend with ${parsedLogTrade.toString()}`); + } + } + + return tradeEvent.length; + } + + const list = new Array(100).fill(0); + + const buyOrders = list.map(async (order, index) => { + await new Promise((res) => { setTimeout(() => {res(true)}, 300 * index)}); + const buyer = returnOneOf([alice, bob, charlie, david]); + const { record: recordBuy} = await placeBuyOrder(client, buyer, exchangeId, toTokenB(getRandomInt(1000, 1020)), toTokenA(getRandomInt(1, 5))); + hasTradeEvent(recordBuy); + return order; + }); + + const sellOrders = list.map(async (order, index) => { + await new Promise((res) => { setTimeout(() => {res(true)}, 200 * (index + 1))}); + const seller = returnOneOf([alice, bob, charlie, david]); + const { record: recordSell } = await placeSellOrder(client, seller, exchangeId, toTokenB(getRandomInt(1000, 1020)), toTokenA(getRandomInt(1, 15))); + hasTradeEvent(recordSell); + return order; + }) + + try { + const results = await Promise.allSettled([...buyOrders, ...sellOrders]); + const rejected = results.filter(r => r.status === 'rejected'); + const fulfilled = results.filter(r => r.status === 'fulfilled'); + + + console.log({ + status: 'rejected', + len : rejected.length, + // @ts-ignore + ids: rejected.map(rej => rej.reason.transactionId.toString()) + }) + + console.log({ + fulfilled: { + len: fulfilled.length, + } + }); + + + } catch (error) { + console.error("Error placing orders"); + console.error(error); + expect.fail('Error placing orders'); + } + + + + const aliceTokenABalance = (await queryBalanceOfExchange(client, exchangeId, alice.address, addressTokenA)).toNumber(); + const aliceTokenBBalance = (await queryBalanceOfExchange(client, exchangeId, alice.address, addressTokenB)).toNumber(); + + const bobTokenABalance = (await queryBalanceOfExchange(client, exchangeId, "0xed9a4895a19483001d7228354d714e7ea4523d4b", addressTokenA)).toNumber(); + const bobTokenBBalance = (await queryBalanceOfExchange(client, exchangeId, "0xed9a4895a19483001d7228354d714e7ea4523d4b", addressTokenB)).toNumber(); + + const charlieTokenABalance = (await queryBalanceOfExchange(client, exchangeId, "0xb8c8d838121d5bf0a7b022737b34b13eb9fa5bea", addressTokenA)).toNumber(); + const charlieTokenBBalance = (await queryBalanceOfExchange(client, exchangeId, "0xb8c8d838121d5bf0a7b022737b34b13eb9fa5bea", addressTokenB)).toNumber(); + + const davidTokenABalance = (await queryBalanceOfExchange(client, exchangeId, david.address, addressTokenA)).toNumber(); + const davidTokenBBalance = (await queryBalanceOfExchange(client, exchangeId, david.address, addressTokenB)).toNumber(); + + + console.log({ + aliceTokenABalance: fromTokenA(aliceTokenABalance), + bobTokenABalance: fromTokenA(bobTokenABalance), + charlieTokenABalance: fromTokenA(charlieTokenABalance), + davidTokenABalance: fromTokenA(davidTokenABalance), + aliceTokenBBalance: fromTokenB(aliceTokenBBalance), + bobTokenBBalance: fromTokenB(bobTokenBBalance), + charlieTokenBBalance: fromTokenB(charlieTokenBBalance), + davidTokenBBalance: fromTokenB(davidTokenBBalance), + }) + + + + }); + }); +}); + diff --git a/test/orderbook/load/transfer.test.ts b/test/orderbook/load/transfer.test.ts new file mode 100644 index 0000000..b3a8ef6 --- /dev/null +++ b/test/orderbook/load/transfer.test.ts @@ -0,0 +1,34 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import Utils from '../../utils'; +import { AccountId, PrivateKey, Client } from '@hashgraph/sdk'; + +async function fixture() { + const client = Client.forTestnet(); + + const operatorAccountId = AccountId.fromString(''); + const operatorPrivateKey = PrivateKey.fromStringED25519(""); + + const operator = { + accountId: operatorAccountId, + key: operatorPrivateKey, + address: '0x' + (operatorAccountId.toSolidityAddress().toLowerCase()) + } + + return { + client, + operator, + } +} + +describe('Load Test Exchange HTS', () => { + describe('Exchange', () => { + it('should load test', async () => { + const { client, operator } = await fixture(); + + await Utils.transferHbar(client, operator, AccountId.fromString('0.0.3640088'), 999); + + }); + }); +}); + diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..c8b0f7e --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,317 @@ +import { + AccountId, + Client, + ContractId, + PrivateKey, + TokenId, + ContractExecuteTransaction, + AccountAllowanceApproveTransaction, + TransferTransaction, + TokenAssociateTransaction, + FileAppendTransaction, + ContractFunctionParameters, + ContractCreateTransaction, + FileCreateTransaction, + TokenCreateTransaction, + AccountCreateTransaction, + TokenMintTransaction, + TokenType, + TokenSupplyType, + TransactionReceipt, + TokenUpdateTransaction, + Hbar, + Key, + FileId, + ContractCallQuery, + AccountBalanceQuery, + TransactionRecord, +} from '@hashgraph/sdk'; +import axios from 'axios'; + +export interface Operator { + accountId: AccountId; + key: PrivateKey; + address: string; +} + +class Utils { + async transferHbar(client: Client, from: Operator, to: string | AccountId, amount: number) { + client.setOperator( + from.accountId, + from.key + ); + + const transferTx = new TransferTransaction() + .addHbarTransfer(from.accountId, -amount) + .addHbarTransfer(to, amount) + .freezeWith(client); + + const transferSign = await transferTx.sign(from.key); + const tx = await transferSign.execute(client); + const rx = await tx.getReceipt(client); + + if (rx.status._code != 22) { + throw new Error('Error transfer token status code' + rx.status._code); + } + + console.log(`- transferHbar success with id: ${tx.transactionId}`) + } + + async createAccount(client: Client): Promise { + const key = PrivateKey.fromStringECDSA(process.env.PRIVATE_KEY as string); + const adminAccountTx = await new AccountCreateTransaction() + .setKey(key.publicKey) + .setInitialBalance(new Hbar(10)) + .execute(client); + const adminAccountReceipt = await adminAccountTx.getReceipt(client); + + return { accountId: adminAccountReceipt.accountId as AccountId, key, address: '0x' + adminAccountReceipt.accountId?.toSolidityAddress() } ; + } + + async createFungibleToken(client: Client, tresury: AccountId, adminKey: PrivateKey, name: string, symbol: string): Promise { + client.setOperator( + tresury, + adminKey + ); + + const tokenCreateTx = await new TokenCreateTransaction() + .setTokenName(name) + .setTokenSymbol(symbol) + .setTokenType(TokenType.FungibleCommon) + .setSupplyType(TokenSupplyType.Infinite) + .setInitialSupply(1000000) + .setTreasuryAccountId(tresury) + .setSupplyKey(adminKey) + .setAdminKey(adminKey) + .freezeWith(client); + + const tokenCreateTxSign = await tokenCreateTx.sign(adminKey); + const tokenCreateRx = await tokenCreateTxSign.execute(client); + const tokenCreateReceipt = await tokenCreateRx.getReceipt(client); + return tokenCreateReceipt.tokenId as TokenId; + } + + async updateFungibleTokenSuply(client: Client, tokenId: TokenId, adminKey: PrivateKey, supplyKey: Key | ContractId): Promise { + const tokenCreateTx = new TokenUpdateTransaction() + .setTokenId(tokenId) + .setSupplyKey(supplyKey) + .freezeWith(client); + + const tokenCreateTxSign = await tokenCreateTx.sign(adminKey); + const tokenCreateRx = await tokenCreateTxSign.execute(client); + const tokenCreateReceipt = await tokenCreateRx.getReceipt(client); + return tokenCreateReceipt.tokenId as TokenId; + } + + async deployContract(client: Client, operator: Operator, bytecode: string, params: any[]): Promise { + // Create a file on Hedera and store the bytecode + const fileCreateTx = new FileCreateTransaction() + .setKeys([operator.key.publicKey]) + .setMaxTransactionFee(new Hbar(2)) // Set appropriate max transaction fee + .freezeWith(client); + + const fileCreateSign = await fileCreateTx.sign(operator.key); + const fileCreateSubmit = await fileCreateSign.execute(client); + const fileCreateReceipt = await fileCreateSubmit.getReceipt(client); + const bytecodeFileId = fileCreateReceipt.fileId as FileId; + + console.log(" - The smart contract bytecode file ID is:", bytecodeFileId.toString()); + + console.log(" - Upload bytecode in chucks"); + // Append the bytecode in chunks + const chunkSize = 4096; // 4 KB + for (let i = 0; i < bytecode.length; i += chunkSize) { + const chunk = bytecode.slice(i, i + chunkSize); + const fileAppendTx = new FileAppendTransaction() + .setFileId(bytecodeFileId) + .setContents(chunk) + .setMaxTransactionFee(new Hbar(2)) // Set appropriate max transaction fee + .freezeWith(client); + const fileAppendSign = await fileAppendTx.sign(operator.key); + await fileAppendSign.execute(client); + } + + console.log(" - Bytecode file upload completed."); + + console.log(params[0], params[1]) + + // Create the smart contract + const contractTx = new ContractCreateTransaction() + .setBytecodeFileId(bytecodeFileId) + .setConstructorParameters( + new ContractFunctionParameters() + .addAddress(params[0]) + .addAddress(params[1]) + ) + .setGas(10000000) // Adjust gas as needed + .setAdminKey(operator.key) // Optional: Set an admin key to manage the contract + .setMaxTransactionFee(new Hbar(16)); // Set appropriate max transaction fee + + const contractResponse = await contractTx.execute(client); + const contractReceipt = await contractResponse.getReceipt(client); + + return contractReceipt.contractId as ContractId; + } + + async tokenMint(client: Client, operator: Operator, tokenId: TokenId | string, amount: number) { + client.setOperator( + operator.accountId, + operator.key + ); + + const tokenMintTx = new TokenMintTransaction() + .setTokenId(tokenId) + .setAmount(amount) + .freezeWith(client); + + const tokenMintSign = await tokenMintTx.sign(operator.key); + const tx = await tokenMintSign.execute(client); + const rx = await tx.getReceipt(client); + + if (rx.status._code != 22) { + throw new Error('Error transfer token mint code' + rx.status._code); + } + + console.log(`- tokenMint success with id: ${tx.transactionId}`) + } + + async associateTokenToAccount(client: Client, tokenId: TokenId | string, account: Operator) { + client.setOperator( + account.accountId, + account.key + ); + // Associate the recipient account with the token + const associateTx = new TokenAssociateTransaction() + .setAccountId(account.accountId) + .setTokenIds([tokenId]) + .freezeWith(client); + + const associateSign = await associateTx.sign(account.key); // Use the recipient's private key + const result = await associateSign.execute(client); + const receipt = await result.getReceipt(client); + + if (receipt.status._code != 22) { + throw new Error('Error Associating token code' + receipt.status._code); + } + + console.log(`- Associate token transaciton executed ${result.transactionId}`); + } + + async transferToken(client: Client, tokenId: TokenId | string, from: Operator, to: AccountId, amount: number) { + client.setOperator( + from.accountId, + from.key + ); + + const transferTx = new TransferTransaction() + .addTokenTransfer(tokenId, from.accountId, amount * -1) + .addTokenTransfer(tokenId, to, amount * 1) + .freezeWith(client); + + const transferSign = await transferTx.sign(from.key); + const tx = await transferSign.execute(client); + const rx = await tx.getReceipt(client); + + if (rx.status._code != 22) { + throw new Error('Error transfer token status code' + rx.status._code); + } + + console.log(`- transferToken success with id: ${tx.transactionId}`) + + } + + async approveToken(client: Client, tokenId: TokenId | string, from: Operator, spender: AccountId | ContractId | string, amount: number) { + // Set the operator for the client + client.setOperator(from.accountId, from.key); + + // Create the AccountAllowanceApproveTransaction + const approveTx = new AccountAllowanceApproveTransaction() + .approveTokenAllowance(tokenId, from.accountId, spender, amount) + .freezeWith(client); + + // Sign the transaction with the owner's key + const approveSign = await approveTx.sign(from.key); + + // Execute the transaction + const tx = await approveSign.execute(client); + + // Get the receipt to ensure the transaction was successful + const receipt = await tx.getReceipt(client); + + // Check the status code + if (receipt.status._code !== 22) { + throw new Error('Error approving token status code ' + receipt.status._code); + } + + // Log the success message + console.log(`- AccountAllowanceApproveTransaction success with id: ${tx.transactionId}`) + } + + async executeContract(client: Client, operator: Operator, contract: ContractId | string, functionName: string, params: ContractFunctionParameters, config?: any): Promise<{ receipt: TransactionReceipt, record : TransactionRecord }> { + const contractExecuteTx = new ContractExecuteTransaction() + .setContractId(contract) + .setGas(config?.gas || 1000000) // Adjust based on the complexity of your function + .setFunction(functionName, params) + .setMaxTransactionFee(new Hbar(2)) // Set an appropriate max transaction fee + .freezeWith(client) + + // Sign and execute the transaction + const contractExecuteSign = await contractExecuteTx.sign(operator.key); + const contractExecuteSubmit = await contractExecuteSign.execute(client); + + console.log(` - Contract Execute ${functionName} with transaction ${contractExecuteSubmit.transactionId.toString()}`); + + return { + receipt: await contractExecuteSubmit.getReceipt(client), + record : await contractExecuteSubmit.getRecord(client), + } + + } + + async queryContract(client: Client, contractId: ContractId, functionName: string, params: ContractFunctionParameters) { + const query = new ContractCallQuery() + .setContractId(contractId) + .setGas(100000) // Adjust gas limit as needed + .setFunction(functionName, params); + + return await query.execute(client); + } + + async queryAccountBalance(client: Client, accountId: AccountId) { + const balanceQuery = new AccountBalanceQuery() + .setAccountId(accountId); + + const accountBalance = await balanceQuery.execute(client); + return accountBalance; + } + + async queryTokenBalance(client: Client, accountId: AccountId, tokenId: TokenId) { + const accountBalance = await this.queryAccountBalance(client, accountId); + const tokenBalance = accountBalance?.tokens?.get(tokenId); + return tokenBalance; + } + + async queryAccountInfo(ownerAccountId: AccountId | ContractId) { + const mirrorNodeUrl = process.env.MIRROR_NODE_URL || "https://testnet.mirrornode.hedera.com"; // Use appropriate mirror node URL + + try { + const response = await axios.get(`${mirrorNodeUrl}/api/v1/accounts/${ownerAccountId.toString()}`); + return response.data; + } catch (error) { + console.error(`Error querying allowance: ${error}`); + } + } + + async queryTokenInfo(tokenId: TokenId | string) { + const mirrorNodeUrl = process.env.MIRROR_NODE_URL || "https://testnet.mirrornode.hedera.com"; // Use appropriate mirror node URL + + try { + const response = await axios.get(`${mirrorNodeUrl}/api/v1/tokens/${tokenId.toString()}`); + return response.data; + } catch (error) { + console.error(`Error querying allowance: ${error}`); + } + } +} + +export default new Utils(); diff --git a/yarn.lock b/yarn.lock index 920ee42..e924b16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1189,6 +1189,13 @@ dependencies: fs-extra "^9.1.0" +"@types/bn.js@*", "@types/bn.js@^5.1.0": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" + integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== + dependencies: + "@types/node" "*" + "@types/bn.js@^4.11.3": version "4.11.6" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" @@ -1196,13 +1203,6 @@ dependencies: "@types/node" "*" -"@types/bn.js@^5.1.0": - version "5.1.5" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" - integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== - dependencies: - "@types/node" "*" - "@types/chai-as-promised@^7.1.3": version "7.1.8" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz#f2b3d82d53c59626b5d6bbc087667ccb4b677fe9" @@ -1227,6 +1227,13 @@ dependencies: "@types/node" "*" +"@types/elliptic@^6.4.18": + version "6.4.18" + resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.18.tgz#bc96e26e1ccccbabe8b6f0e409c85898635482e1" + integrity sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw== + dependencies: + "@types/bn.js" "*" + "@types/form-data@0.0.33": version "0.0.33" resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-0.0.33.tgz#c9ac85b2a5fd18435b8c85d9ecb50e6d6c893ff8" @@ -2607,7 +2614,7 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.5.2, elliptic@^6.5.4: +elliptic@^6.5.2, elliptic@^6.5.4, elliptic@^6.5.5: version "6.5.5" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==