Skip to content

Instantly share code, notes, and snippets.

@pradyuman-verma
Last active April 13, 2023 17:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pradyuman-verma/166be5dc97a6d3942a9e76aab0561eb7 to your computer and use it in GitHub Desktop.
Save pradyuman-verma/166be5dc97a6d3942a9e76aab0561eb7 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: WTFPL
pragma solidity ^0.8.0;
// Simple order-based exchange for selling ERC20 tokens in exchange for ETH. Orders are stored off-chain and signed by the sellers.
// Buyers submit the order to execute to the contract, along with the seller's signature and the ETH to purchase the tokens.
// Orders can be EXACT or PARTIAL. Exact orders need to be fulfilled in a single operation, whereas partial orders can be fulfilled in multiple purchases from multiple buyers.
// Orders can also optionally define a referrer address, who will receive 1% of the value in ETH of the purchase. Can be used to reward the app that stores the orders off-chain.
// Does not allocate a fee if set to the zero address.
// ==========================
// I can open your eyes
// Take you wonder by wonder
// Over, sideways and under
// On a magic carpet ride...
// ==========================
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
contract Exchange {
uint256 constant RATE_DENOMINATOR = 2 ** 64;
uint256 constant REFERRAL_FEE = 100;
uint256 constant REFERRAL_FEE_DENOMINATOR = 10000;
// Whether the order needs to be executed for the exact total amount of tokens offered, or can be partially consumed
uint8 constant EXACT_ORDER = 0;
uint8 constant PARTIAL_ORDER = 1;
// Off-chain order representing a token sale (seller is the signer of the order)
struct Order {
address referrer; // optional referrer field that takes a 1% of the amount paid in ETH
address token; // token being offered in this order
uint128 rate; // amount of tokens per eth sold (times RATE_DENOMINATOR)
uint24 nonce; // nonce for differentiating two otherwise identical orders
uint256 amount; // amount of tokens offered in this order
uint8 orderType; // type of order (see constants above)
}
// How much of the total amount of tokens of an order have been purchased so far (indexed by order hash)
mapping(bytes32 => uint256) public amountExecutedPerOrder;
// Executes an order purchasing however many tokens correspond to the msg.value sent based on the order rate
function executeOrder(Order calldata order, uint8 v, bytes32 r, bytes32 s) external payable {
uint256 tokensPurchased = msg.value * order.rate / RATE_DENOMINATOR;
uint256 fee = order.referrer != address(0) ? (msg.value * REFERRAL_FEE / REFERRAL_FEE_DENOMINATOR) : 0;
bytes32 orderHash = getOrderHash(order);
address seller = ecrecover(orderHash, v, r, s);
require(msg.value > 0, "Payment required");
require(seller != address(0), "Wrong signature");
require(order.orderType == EXACT_ORDER ? tokensPurchased == order.amount : true, "Cannot take partial amount");
require(tokensPurchased <= order.amount + amountExecutedPerOrder[orderHash], "Amount purchased exceeds order");
amountExecutedPerOrder[orderHash] += tokensPurchased;
IERC20(order.token).transferFrom(seller, msg.sender, tokensPurchased);
if (fee > 0) payable(order.referrer).transfer(fee);
payable(seller).transfer(msg.value - fee);
}
// Gets the hash of an order (used as its identifier)
function getOrderHash(Order memory order) public view returns (bytes32) {
return keccak256(abi.encodePacked(
order.referrer,
order.token,
order.rate,
order.nonce,
address(this), // include address of this contract to prevent replay attacks
order.amount,
order.orderType
));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment