Closed Thowab closed 1 month ago
Thank you for your contribution to the Solidity compiler! A team member will follow up shortly.
If you haven't read our contributing guidelines and our review checklist before, please do it now, this makes the reviewing process and accepting your contribution smoother.
If you have any questions or need our help, feel free to post them in the PR or talk to us directly on the #solidity-dev channel on Matrix.
// SPDX-License-Identifier: MIT pragma solidity 0.8.16;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./OrderLib.sol"; import "./IExchange.sol"; import "./IWETH.sol";
/**
*/ contract TWAP is ReentrancyGuard { using SafeERC20 for ERC20; using Address for address; using OrderLib for OrderLib.Order;
uint8 public constant VERSION = 4;
event OrderCreated(uint64 indexed id, address indexed maker, address indexed exchange, OrderLib.Ask ask); event OrderBid( uint64 indexed id, address indexed maker, address indexed exchange, uint32 slippagePercent, OrderLib.Bid bid ); event OrderFilled( uint64 indexed id, address indexed maker, address indexed exchange, address taker, uint256 srcAmountIn, uint256 dstAmountOut, uint256 dstFee, uint256 srcFilledAmount ); event OrderCompleted(uint64 indexed id, address indexed maker, address indexed exchange, address taker); event OrderCanceled(uint64 indexed id, address indexed maker, address sender);
uint32 public constant PERCENT_BASE = 100_000; uint32 public constant MIN_OUTBID_PERCENT = 101_000; uint32 public constant STALE_BID_SECONDS = 60 * 10; uint32 public constant MIN_BID_DELAY_SECONDS = 30;
uint32 public constant STATUS_CANCELED = 1; uint32 public constant STATUS_COMPLETED = 2;
OrderLib.Order[] public book; uint32[] public status; // STATUS or deadline timestamp by order id, used for gas efficient order filtering mapping(address => uint64[]) public makerOrders;
address public immutable iweth;
constructor(address _iweth) { iweth = _iweth; }
// -------- views --------
/**
/**
function orderIdsByMaker(address maker) external view returns (uint64[] memory) { return makerOrders[maker]; }
// -------- actions --------
/**
returns order id, emits OrderCreated */ function ask(OrderLib.Ask calldata _ask) external nonReentrant returns (uint64 id) { require( _ask.srcToken != address(0) && _ask.srcToken != _ask.dstToken && (_ask.srcToken != iweth || _ask.dstToken != address(0)) && _ask.srcAmount > 0 && _ask.srcBidAmount > 0 && _ask.srcBidAmount <= _ask.srcAmount && _ask.dstMinAmount > 0 && _ask.deadline > block.timestamp && _ask.bidDelay >= MIN_BID_DELAY_SECONDS, "params" );
OrderLib.Order memory o = OrderLib.newOrder(length(), _ask); verifyMakerBalance(o);
book.push(o); status.push(o.status); makerOrders[msg.sender].push(o.id); emit OrderCreated(o.id, o.maker, o.ask.exchange, o.ask); return o.id; }
/**
/**
if order is fully filled emits OrderCompleted and status is updated */ function fill(uint64 id) external nonReentrant { OrderLib.Order memory o = order(id);
(address exchange, uint256 srcAmountIn, uint256 dstAmountOut, uint256 dstFee) = performFill(o); o.filled(srcAmountIn);
emit OrderFilled(id, o.maker, exchange, msg.sender, srcAmountIn, dstAmountOut, dstFee, o.srcFilledAmount);
if (o.srcBidAmountNext() == 0) { status[id] = STATUS_COMPLETED; o.status = STATUS_COMPLETED; emit OrderCompleted(o.id, o.maker, exchange, msg.sender); } book[id] = o; }
/**
/**
/**
/**
returns dstAmountOut after taker dstFee, which must be higher than any previous bid, unless previous bid is stale */ function verifyBid( OrderLib.Order memory o, address exchange, uint256 dstFee, uint32 slippagePercent, bytes calldata data ) private view returns (uint256 dstAmountOut) { require(block.timestamp < o.status, "status"); // deadline, canceled or completed require(block.timestamp > o.filledTime + o.ask.fillDelay, "fill delay"); require(o.ask.exchange == address(0) || o.ask.exchange == exchange, "exchange");
dstAmountOut = IExchange(exchange).getAmountOut( o.ask.srcToken, _dstToken(o), o.srcBidAmountNext(), o.ask.data, data ); dstAmountOut -= (dstAmountOut * slippagePercent) / PERCENT_BASE; dstAmountOut -= dstFee;
require( dstAmountOut > (o.bid.dstAmount * MIN_OUTBID_PERCENT) / PERCENT_BASE || // outbid by more than MIN_OUTBID_PERCENT block.timestamp > o.bid.time + STALE_BID_SECONDS, // or stale bid "low bid" ); require(dstAmountOut >= o.dstMinAmountNext(), "min out"); verifyMakerBalance(o); }
/**
transfers all other dstToken amount to maker */ function performFill( OrderLib.Order memory o ) private returns (address exchange, uint256 srcAmountIn, uint256 dstAmountOut, uint256 dstFee) { require(msg.sender == o.bid.taker, "taker"); require(block.timestamp < o.status, "status"); // deadline, canceled or completed require(block.timestamp > o.bid.time + o.ask.bidDelay, "bid delay");
exchange = o.bid.exchange; dstFee = o.bid.dstFee; srcAmountIn = o.srcBidAmountNext(); uint256 minOut = o.dstExpectedOutNext();
ERC20(o.ask.srcToken).safeTransferFrom(o.maker, address(this), srcAmountIn); srcAmountIn = ERC20(o.ask.srcToken).balanceOf(address(this)); // support FoT tokens ERC20(o.ask.srcToken).safeIncreaseAllowance(exchange, srcAmountIn);
IExchange(exchange).swap(o.ask.srcToken, _dstToken(o), srcAmountIn, minOut + dstFee, o.ask.data, o.bid.data);
dstAmountOut = ERC20(_dstToken(o)).balanceOf(address(this)); // support FoT tokens dstAmountOut -= dstFee; require(dstAmountOut >= minOut, "min out");
if (o.ask.dstToken == address(0)) { IWETH(iweth).withdraw(ERC20(iweth).balanceOf(address(this))); Address.sendValue(payable(o.bid.taker), dstFee); Address.sendValue(payable(o.maker), dstAmountOut); } else { ERC20(_dstToken(o)).safeTransfer(o.bid.taker, dstFee); ERC20(_dstToken(o)).safeTransfer(o.maker, dstAmountOut); } }
/**
function _dstToken(OrderLib.Order memory o) private view returns (address) { return o.ask.dstToken == address(0) ? iweth : o.ask.dstToken; }
receive() external payable {} // solhint-disable-line no-empty-blocks }