Skip to content

Commit

Permalink
Merge pull request #36 from CamposBruno/feat/orderbook
Browse files Browse the repository at this point in the history
Feat/orderbook
  • Loading branch information
CamposBruno authored Jun 17, 2024
2 parents 2c472b7 + 1c5d071 commit f35cc32
Show file tree
Hide file tree
Showing 22 changed files with 5,119 additions and 8 deletions.
106 changes: 106 additions & 0 deletions contracts/orderbook/Exchange.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
90 changes: 90 additions & 0 deletions contracts/orderbook/ExchangeFactory.sol
Original file line number Diff line number Diff line change
@@ -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];
}
}
Loading

0 comments on commit f35cc32

Please sign in to comment.