pragma solidity ^0.8.0;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
contract OrderBook is Ownable {
using SafeERC20 for IERC20;
using Strings for uint256;
struct Order {
uint256 id;
address seller;
address tokenToSell;
uint256 amountToSell;
uint256 priceInUSDC;
uint256 deadlineTimestamp;
bool isActive;
}
struct PendingAmendment {
uint256 newAmountToSell;
uint256 newPriceInUSDC;
uint256 newDeadlineTimestamp;
uint256 requestTimestamp;
}
mapping(uint256 => PendingAmendment) public pendingAmendments;
mapping(uint256 => uint256) public pendingCancellations;
uint256 public constant TIME_LOCK_DELAY = 60;
uint256 public constant MAX_DEADLINE_DURATION = 3 days;
uint256 public constant FEE = 3;
uint256 public constant PRECISION = 100;
IERC20 public immutable iWETH;
IERC20 public immutable iWBTC;
IERC20 public immutable iWSOL;
IERC20 public immutable iUSDC;
mapping(address => bool) public allowedSellToken;
mapping(uint256 => Order) public orders;
uint256 private _nextOrderId;
uint256 public totalFees;
event OrderCreated(uint256 indexed orderId, address indexed seller, address indexed tokenToSell, uint256 amountToSell, uint256 priceInUSDC, uint256 deadlineTimestamp);
event OrderAmended(uint256 indexed orderId, uint256 newAmountToSell, uint256 newPriceInUSDC, uint256 newDeadlineTimestamp);
event OrderCancelled(uint256 indexed orderId, address indexed seller);
event OrderFilled(uint256 indexed orderId, address indexed buyer, address indexed seller);
event TokenAllowed(address indexed token, bool indexed status);
event EmergencyWithdrawal(address indexed token, uint256 indexed amount, address indexed receiver);
event FeesWithdrawn(address indexed receiver);
event AmendmentRequested(uint256 indexed orderId, uint256 newAmountToSell, uint256 newPriceInUSDC, uint256 newDeadlineTimestamp, uint256 requestTimestamp);
event CancellationRequested(uint256 indexed orderId, uint256 requestTimestamp);
error OrderNotFound();
error NotOrderSeller();
error OrderNotActive();
error OrderExpired();
error OrderAlreadyInactive();
error InvalidToken();
error InvalidAmount();
error InvalidPrice();
error InvalidDeadline();
error InvalidAddress();
error TimeLockNotElapsed();
constructor(address _weth, address _wbtc, address _wsol, address _usdc, address _owner) Ownable(_owner) {
if (_weth == address(0) || _wbtc == address(0) || _wsol == address(0) || _usdc == address(0)) revert InvalidToken();
if (_owner == address(0)) revert InvalidAddress();
iWETH = IERC20(_weth);
allowedSellToken[_weth] = true;
iWBTC = IERC20(_wbtc);
allowedSellToken[_wbtc] = true;
iWSOL = IERC20(_wsol);
allowedSellToken[_wsol] = true;
iUSDC = IERC20(_usdc);
_nextOrderId = 1;
}
function requestAmendSellOrder(
uint256 _orderId,
uint256 _newAmountToSell,
uint256 _newPriceInUSDC,
uint256 _newDeadlineDuration
) external {
Order storage order = orders[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
if (order.seller != msg.sender) revert NotOrderSeller();
if (!order.isActive) revert OrderAlreadyInactive();
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
if (_newAmountToSell == 0) revert InvalidAmount();
if (_newPriceInUSDC == 0) revert InvalidPrice();
if (_newDeadlineDuration == 0 || _newDeadlineDuration > MAX_DEADLINE_DURATION) revert InvalidDeadline();
uint256 newDeadlineTimestamp = block.timestamp + _newDeadlineDuration;
pendingAmendments[_orderId] = PendingAmendment({
newAmountToSell: _newAmountToSell,
newPriceInUSDC: _newPriceInUSDC,
newDeadlineTimestamp: newDeadlineTimestamp,
requestTimestamp: block.timestamp
});
emit AmendmentRequested(_orderId, _newAmountToSell, _newPriceInUSDC, newDeadlineTimestamp, block.timestamp);
}
function confirmAmendSellOrder(uint256 _orderId) external {
Order storage order = orders[_orderId];
PendingAmendment memory amendment = pendingAmendments[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
if (order.seller != msg.sender) revert NotOrderSeller();
if (amendment.requestTimestamp == 0) revert("No pending amendment");
if (block.timestamp < amendment.requestTimestamp + TIME_LOCK_DELAY) revert TimeLockNotElapsed();
if (!order.isActive) revert OrderAlreadyInactive();
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
IERC20 token = IERC20(order.tokenToSell);
if (amendment.newAmountToSell > order.amountToSell) {
uint256 diff = amendment.newAmountToSell - order.amountToSell;
token.safeTransferFrom(msg.sender, address(this), diff);
} else if (amendment.newAmountToSell < order.amountToSell) {
uint256 diff = order.amountToSell - amendment.newAmountToSell;
token.safeTransfer(order.seller, diff);
}
order.amountToSell = amendment.newAmountToSell;
order.priceInUSDC = amendment.newPriceInUSDC;
order.deadlineTimestamp = amendment.newDeadlineTimestamp;
delete pendingAmendments[_orderId];
emit OrderAmended(_orderId, amendment.newAmountToSell, amendment.newPriceInUSDC, amendment.newDeadlineTimestamp);
}
function requestCancelSellOrder(uint256 _orderId) external {
Order storage order = orders[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
if (order.seller != msg.sender) revert NotOrderSeller();
if (!order.isActive) revert OrderAlreadyInactive();
pendingCancellations[_orderId] = block.timestamp;
emit CancellationRequested(_orderId, block.timestamp);
}
function confirmCancelSellOrder(uint256 _orderId) external {
Order storage order = orders[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
if (order.seller != msg.sender) revert NotOrderSeller();
if (pendingCancellations[_orderId] == 0) revert("No pending cancellation");
if (block.timestamp < pendingCancellations[_orderId] + TIME_LOCK_DELAY) revert TimeLockNotElapsed();
if (!order.isActive) revert OrderAlreadyInactive();
order.isActive = false;
IERC20(order.tokenToSell).safeTransfer(order.seller, order.amountToSell);
delete pendingCancellations[_orderId];
emit OrderCancelled(_orderId, order.seller);
}
function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
if (order.seller == address(0)) revert OrderNotFound();
if (!order.isActive) revert OrderNotActive();
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
if (pendingAmendments[_orderId].requestTimestamp != 0 || pendingCancellations[_orderId] != 0) {
revert("Order has pending amendment or cancellation");
}
order.isActive = false;
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
uint256 sellerReceives = order.priceInUSDC - protocolFee;
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
totalFees += protocolFee;
emit OrderFilled(_orderId, msg.sender, order.seller);
}
}