function createSellOrder(
address _tokenToSell,
uint256 _amountToSell,
uint256 _priceInUSDC,
uint256 _deadlineDuration
) public returns (uint256) {
@> IERC20(_tokenToSell).safeTransferFrom(msg.sender, address(this), _amountToSell);
orders[orderId] = Order({
id: orderId,
seller: msg.sender,
tokenToSell: _tokenToSell,
@> amountToSell: _amountToSell,
priceInUSDC: _priceInUSDC,
deadlineTimestamp: deadlineTimestamp,
isActive: true
});
}
```solidity
function buyOrder(uint256 _orderId) public {
@> IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
}
pragma solidity 0.8.26;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract TransferFeeToken is ERC20 {
uint256 immutable fee;
uint8 tokenDecimals;
constructor(uint256 _totalSupply, uint256 _fee, uint8 _tokenDecimals) ERC20("TransferFeeToken", "TFT") {
fee = _fee;
tokenDecimals = _tokenDecimals;
_mint(msg.sender, _totalSupply);
}
function transfer(address dst, uint256 amount) public override returns (bool) {
return transferFrom(msg.sender, dst, amount);
}
function transferFrom(address src, address dst, uint256 amount) public override returns (bool) {
require(balanceOf(src) >= amount, "insufficient-balance");
if (src != msg.sender) {
uint256 currentAllowance = allowance(src, msg.sender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "insufficient-allowance");
_approve(src, msg.sender, currentAllowance - amount);
}
}
require(amount >= fee, "transfer amount less than fee");
uint256 netAmount = amount - fee;
_transfer(src, dst, netAmount);
if (fee > 0) {
_burn(src, fee);
}
return true;
}
function mint(address to, uint256 value) public {
uint256 updateDecimals = uint256(tokenDecimals);
_mint(to, (value * 10 ** updateDecimals));
}
}
====================================================
pragma solidity ^0.8.0;
import {Test, console2} from "forge-std/Test.sol";
import {OrderBook} from "../src/OrderBook.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {MockUSDC} from "./mocks/MockUSDC.sol";
import {MockWBTC} from "./mocks/MockWBTC.sol";
import {MockWETH} from "./mocks/MockWETH.sol";
import {MockWSOL} from "./mocks/MockWSOL.sol";
import {TransferFeeToken} from "./mocks/TransferFeeToken.sol";
contract TestOrderBook is Test {
OrderBook book;
MockUSDC usdc;
MockWBTC wbtc;
MockWETH weth;
MockWSOL wsol;
TransferFeeToken transferFeeToken;
uint256 constant TRANSFER_FEE = 10 * 10 ** 18;
uint256 constant SELL_AMOUNT = 1000 * 10 ** 18;
uint256 constant PRICE_IN_USDC = 100 * 10 ** 6;
address owner;
address alice;
address bob;
address clara;
address ana;
address dan;
uint256 mdd;
function setUp() public {
owner = makeAddr("protocol_owner");
alice = makeAddr("will_sell_wbtc_order");
bob = makeAddr("will_sell_weth_order");
clara = makeAddr("will_sell_wsol_order");
dan = makeAddr("will_buy_orders");
ana = makeAddr("will_sell_transfer_fee_token_order");
usdc = new MockUSDC(6);
wbtc = new MockWBTC(8);
weth = new MockWETH(18);
wsol = new MockWSOL(18);
transferFeeToken = new TransferFeeToken(1000000 * 10 ** 18, TRANSFER_FEE, 18);
vm.prank(owner);
book = new OrderBook(address(weth), address(wbtc), address(wsol), address(usdc), owner);
usdc.mint(dan, 200_000);
wbtc.mint(alice, 2);
weth.mint(bob, 2);
wsol.mint(clara, 2);
transferFeeToken.mint(ana, SELL_AMOUNT);
mdd = book.MAX_DEADLINE_DURATION();
}
function testFeeToken() public {
vm.prank(owner);
book.setAllowedSellToken(address(transferFeeToken), true);
vm.prank(ana);
transferFeeToken.approve(address(book), SELL_AMOUNT);
vm.prank(dan);
usdc.approve(address(book), 200_000e6);
vm.prank(ana);
uint256 anaId = book.createSellOrder(address(transferFeeToken), SELL_AMOUNT, PRICE_IN_USDC, 1 hours);
vm.prank(dan);
vm.expectRevert();
book.buyOrder(anaId);
}
}
function createSellOrder(
address _tokenToSell,
uint256 _amountToSell,
uint256 _priceInUSDC,
uint256 _deadlineDuration
) public returns (uint256) {
// ... existing validation ...
uint256 orderId = _nextOrderId++;
+ // Check balance before and after transfer to handle fee-on-transfer tokens
+ uint256 balanceBefore = IERC20(_tokenToSell).balanceOf(address(this));
IERC20(_tokenToSell).safeTransferFrom(msg.sender, address(this), _amountToSell);
+ uint256 balanceAfter = IERC20(_tokenToSell).balanceOf(address(this));
+ uint256 actualAmountReceived = balanceAfter - balanceBefore;
// Store the order with actual received amount
orders[orderId] = Order({
id: orderId,
seller: msg.sender,
tokenToSell: _tokenToSell,
- amountToSell: _amountToSell,
+ amountToSell: actualAmountReceived,
priceInUSDC: _priceInUSDC,
deadlineTimestamp: deadlineTimestamp,
isActive: true
});
- emit OrderCreated(orderId, msg.sender, _tokenToSell, _amountToSell, _priceInUSDC, deadlineTimestamp);
+ emit OrderCreated(orderId, msg.sender, _tokenToSell, actualAmountReceived, _priceInUSDC, deadlineTimestamp);
return orderId;
}