Created
October 11, 2019 14:43
-
-
Save crypto-perry/dd377c58b6d984b607b9bf8001db616b to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.5.11+commit.c082d0b4.js&optimize=false&gist=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.5.0; | |
import "./ierc20.sol"; | |
// In fact this only implements the IERC20 transfer and trasnferFrom functions | |
// Except for that it allows users to withdraw money | |
contract ERC20Controlled is IERC20 { | |
string public constant name = "Controlled Token"; | |
string public constant symbol = "CTRL"; | |
uint256 private constant MASK_BUTLER = 1 << 0; | |
uint256 private constant MASK_BOT = 1 << 1; | |
event SendTokens(address indexed to, uint tokens); // Event to be emited when the bots need to complete a trade to some user. | |
address public owner; // The owner can give roles to other users. | |
modifier onlyOwner { require(owner == msg.sender); _; } | |
mapping(address => uint256) public roles; // Permissions for special accounts such as bots and butlers | |
modifier onlyBot { require(roles[msg.sender] & MASK_BOT == MASK_BOT); _; } | |
modifier onlyButler { require(roles[msg.sender] & MASK_BUTLER == MASK_BUTLER); _; } | |
mapping(address => uint256) public balances; // Balances of users. | |
mapping(uint256 => bool) private deposits; // Deposits recorded in order to make sure no duplicates are sent | |
constructor() public { | |
owner = msg.sender; | |
} | |
// Give permissions to a special account | |
function setPermissions(address account, uint256 permissions) public onlyOwner { | |
roles[account] = permissions; | |
} | |
// Our bots call this function to record deposits from users | |
function recordDeposit(address user, uint256 numTokens, uint256 tokenTxHash) public onlyBot { | |
require(!deposits[tokenTxHash]); | |
uint256 res = balances[user] + numTokens; | |
assert(res >= numTokens); // Protect against overflow. | |
balances[user] = res; | |
deposits[tokenTxHash] = true; | |
} | |
// Any user can call this function to withdraw tokens that are not locked in a certain trade. | |
function withdraw(uint256 numTokens) public { | |
require(balances[msg.sender] >= numTokens); | |
balances[msg.sender] -= numTokens; | |
emit SendTokens(msg.sender, numTokens); | |
} | |
// Below are ERC20 functions that will be called by our butler smart contracts... | |
// ****************************************************************************************** | |
// This always returns true and instructs our bots to perform the transfer. | |
// This should never be called illegally GIVEN our butler smart contracts are correct. | |
function transfer(address to, uint numTokens) public onlyButler returns (bool) { | |
emit SendTokens(to, numTokens); | |
return true; | |
} | |
// Checks that the user has enough balance to enter the trade. | |
// If the transfer is meant for another user it instructs our bots to complete the transfer. | |
function transferFrom(address from, address to, uint numTokens) public onlyButler returns (bool) { | |
require(numTokens <= balances[from]); | |
balances[from] -= numTokens; | |
if (roles[to] & MASK_BUTLER != MASK_BUTLER) { | |
emit SendTokens(to, numTokens); | |
} | |
return true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.5.0; | |
import "./ierc20.sol"; | |
import './safemathlib.sol'; | |
contract ERC20OpenBuy { | |
using SafeMath for uint256; | |
event UpdateBuy(address maker, address token, uint256 weiPerToken, uint256 expiration, uint256 weiRemaining); | |
address public owner; | |
uint256 public feesGathered; | |
mapping (uint256 => uint256) public buyOrders; | |
modifier onlyOwner { | |
require(msg.sender == owner); | |
_; | |
} | |
constructor() public { | |
owner = msg.sender; | |
} | |
function addToBuyOffer(address token, uint256 weiPerToken, uint256 expiration, uint256 tokenAmount) public payable { | |
require(weiPerToken % 1000 == 0, "Price must be multiple of 1000!"); // We require this to guarantee fees will add up. | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(msg.sender, token, weiPerToken, expiration))); | |
uint256 weiPayment = tokenAmount.mul(weiPerToken); | |
uint256 fee = computeFee(weiPayment); | |
require(msg.value == weiPayment.add(fee), "Not enough ether sent!"); // Add fee and check eth sent | |
buyOrders[tradeId] = buyOrders[tradeId].add(weiPayment); // Update trade state | |
emit UpdateBuy(msg.sender, token, weiPerToken, expiration, buyOrders[tradeId]); // Log open BUY | |
} | |
function partialFillBuy(address token, uint256 weiPerToken, uint256 expiration, uint256 tokenAmount, address payable other) public { | |
require(expiration > now, "Trade expired!"); | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(other, token, weiPerToken, expiration))); | |
uint256 weiPayment = tokenAmount.mul(weiPerToken); | |
require(buyOrders[tradeId] >= weiPayment, "Not enough ether in trade!"); // Make sure there are enough tokens | |
buyOrders[tradeId] = buyOrders[tradeId] - weiPayment; | |
require(IERC20(token).transferFrom(msg.sender, other, tokenAmount), "Token transfer failed!"); // Take tokens from seller and give to buyer | |
uint256 fee = computeFee(weiPayment); | |
msg.sender.transfer(weiPayment - fee); | |
feesGathered = feesGathered.add(fee).add(fee); | |
emit UpdateBuy(other, token, weiPerToken, expiration, buyOrders[tradeId]); // Log open BUY | |
} | |
function cancelBuyOffer(address token, uint256 weiPerToken, uint256 expiration) public { | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(msg.sender, token, weiPerToken, expiration))); | |
uint256 ethAmount = buyOrders[tradeId]; | |
buyOrders[tradeId] = 0; | |
msg.sender.transfer(ethAmount); // Refund eth to buyer | |
emit UpdateBuy(msg.sender, token, weiPerToken, expiration, 0); // Log open BUY | |
} | |
function _withdrawFees() public onlyOwner { | |
uint256 amount = feesGathered; | |
feesGathered = 0; | |
msg.sender.transfer(amount); | |
} | |
function computeFee(uint256 value) private pure returns (uint256) { | |
return value.mul(5) / 1000; // this is the fee we take on each side (0.5%) | |
} | |
function() external payable { | |
revert(); | |
} | |
} | |
contract ERC20OpenSell { | |
using SafeMath for uint256; | |
event UpdateSell(address maker, address token, uint256 weiPerToken, uint256 expiration, uint256 tokensRemaining); | |
address public owner; | |
address public allowedSpender; | |
uint256 public feesGathered; | |
mapping (uint256 => uint256) public sellOrders; | |
modifier onlyOwner { | |
require(msg.sender == owner); | |
_; | |
} | |
constructor() public { | |
owner = msg.sender; | |
} | |
function addToSellOffer(address token, uint256 weiPerToken, uint256 expiration, uint256 tokenAmount) public { | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(msg.sender, token, weiPerToken, expiration))); | |
require(IERC20(token).transferFrom(msg.sender, address(this), tokenAmount)); // Take tokens from seller | |
sellOrders[tradeId] = sellOrders[tradeId].add(tokenAmount); // Update state | |
emit UpdateSell(msg.sender, token, weiPerToken, expiration, sellOrders[tradeId]); // Log open SELL | |
} | |
function partialFillSell(address token, uint256 weiPerToken, uint256 expiration, address payable other) public payable { | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(other, token, weiPerToken, expiration))); | |
uint256 ethPayment = msg.value.mul(1000) / 1005; // Without fee | |
uint256 tokenAmount = ethPayment / weiPerToken; | |
require(sellOrders[tradeId] >= tokenAmount); // Make sure there are enough tokens to pay | |
sellOrders[tradeId] = sellOrders[tradeId] - tokenAmount; // Update state | |
require(IERC20(token).transfer(msg.sender, tokenAmount)); // Give tokens to buyer | |
uint256 fee = computeFee(ethPayment); | |
other.transfer(ethPayment - fee); // Pay the seller | |
feesGathered = feesGathered.add(fee).add(fee); | |
emit UpdateSell(other, token, weiPerToken, expiration, sellOrders[tradeId]); // Log open BUY | |
} | |
function cancelSellOffer(address token, uint256 weiPerToken, uint256 expiration) public { | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(msg.sender, token, weiPerToken, expiration))); | |
uint256 tokenAmount = sellOrders[tradeId]; | |
sellOrders[tradeId] = 0; | |
IERC20(token).transfer(msg.sender, tokenAmount); // Refund seller | |
emit UpdateSell(msg.sender, token, weiPerToken, expiration, 0); // Log open SELL | |
} | |
function _withdrawFees() public onlyOwner { | |
uint256 amount = feesGathered; | |
feesGathered = 0; | |
msg.sender.transfer(amount); | |
} | |
function computeFee(uint256 value) private pure returns (uint256) { | |
return value.mul(5) / 1000; // this is the fee we take on each side (0.5%) | |
} | |
function() external payable { | |
revert(); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.5.0; | |
import "./ierc20.sol"; | |
import './safemathlib.sol'; | |
contract ERC20OptionTrade { | |
using SafeMath for uint256; | |
enum TradeState {None, SellPaid, BuyPaid, Canceled, Matched, Closed, Expired} | |
struct Trade { | |
address payable buyer; | |
address payable seller; | |
string symbol; | |
uint256 payment; | |
uint256 amountOfTokens; | |
uint256 deposit; | |
uint256 expiration; | |
TradeState state; | |
} | |
event OpenTrade(uint256 tradeId, address indexed buyer, address indexed seller, string symbol, uint256 pricePerToken, uint256 amountOfTokens, uint256 depositPercentage, uint256 expiration, TradeState state); | |
event UpdateTrade(uint256 tradeId, address indexed buyer, address indexed seller, TradeState state); | |
address private owner; | |
uint256 public feesGathered; | |
mapping (uint256 => Trade) public trades; | |
mapping (bytes32 => IERC20) private tokens; | |
modifier onlyOwner { | |
require(msg.sender == owner); | |
_; | |
} | |
constructor() public { | |
owner = msg.sender; | |
} | |
function() external payable { | |
revert(); | |
} | |
function A_trade(bool wantToBuy, string memory symbol, uint256 amountOfTokens, uint256 pricePerToken, uint256 depositPercentage, uint256 expiration, address payable other) public payable { | |
require(tokens[convert(symbol)] != IERC20(0x0)); | |
require(pricePerToken >= 1000); // min price so that divisions with 1000 never give remainder | |
Trade memory t; | |
(t.symbol, t.payment, t.amountOfTokens, t.deposit, t.expiration) | |
= (symbol, pricePerToken.mul(amountOfTokens), amountOfTokens, pricePerToken.mul(amountOfTokens).mul(depositPercentage) / 100, expiration); | |
uint256 paymentRequired; | |
(t.buyer, t.seller, t.state, paymentRequired) = wantToBuy | |
? (msg.sender, other, TradeState.BuyPaid, t.payment.add(computeFee(t.payment))) | |
: (other, msg.sender, TradeState.SellPaid, t.deposit.add(computeFee(t.payment))); | |
require(msg.value >= paymentRequired); | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(t.buyer, t.seller, t.symbol, t.amountOfTokens, pricePerToken, depositPercentage, t.expiration))); | |
Trade storage existingTrade = trades[tradeId]; | |
if (existingTrade.state == TradeState.None) { | |
emit OpenTrade(tradeId, t.buyer, t.seller, t.symbol, pricePerToken, t.amountOfTokens, depositPercentage, t.expiration, t.state); | |
trades[tradeId] = t; | |
} else if (t.state == TradeState.BuyPaid && existingTrade.state == TradeState.SellPaid | |
|| t.state == TradeState.SellPaid && existingTrade.state == TradeState.BuyPaid) { | |
existingTrade.state = TradeState.Matched; | |
emit UpdateTrade(tradeId, t.buyer, t.seller, existingTrade.state); | |
} else { | |
revert(); | |
} | |
msg.sender.transfer(msg.value - paymentRequired); | |
} | |
function B_matchTrade(uint256 tradeId) public payable { | |
Trade storage t = trades[tradeId]; | |
uint256 paymentRequired; | |
if(t.state == TradeState.SellPaid) { | |
if (t.buyer == address(0x0)) { | |
t.buyer = msg.sender; | |
} else { | |
require(msg.sender == t.buyer); | |
} | |
paymentRequired = t.payment.add(computeFee(t.payment)); | |
} else if(t.state == TradeState.BuyPaid) { | |
if (t.seller == address(0x0)) { | |
t.seller = msg.sender; | |
} else { | |
require(msg.sender == t.seller); | |
} | |
paymentRequired = t.deposit.add(computeFee(t.payment)); | |
} else { | |
revert(); | |
} | |
require(msg.value >= paymentRequired); | |
t.state = TradeState.Matched; | |
emit UpdateTrade(tradeId, t.buyer, t.seller, t.state); | |
msg.sender.transfer(msg.value - paymentRequired); | |
} | |
function B_cancelOpenTrade(uint256 tradeId) public { | |
Trade storage t = trades[tradeId]; | |
require(t.state == TradeState.SellPaid || t.state == TradeState.BuyPaid); | |
require(msg.sender == t.seller || msg.sender == t.buyer); | |
address payable actor; uint256 refund; | |
(actor, refund) = (t.state == TradeState.SellPaid) | |
? (t.seller, t.deposit.add(computeFee(t.payment))) | |
: (t.buyer, t.payment.add(computeFee(t.payment))); | |
t.state = TradeState.Canceled; | |
emit UpdateTrade(tradeId, t.buyer, t.seller, t.state); | |
actor.transfer(refund); | |
} | |
function C_completeTrade(uint256 tradeId) public { | |
Trade storage t = trades[tradeId]; | |
require(t.state == TradeState.Matched); | |
t.state = TradeState.Closed; | |
feesGathered += computeFee(t.payment).mul(2); | |
require(tokens[convert(t.symbol)].transferFrom(t.seller, t.buyer, t.amountOfTokens)); | |
t.seller.transfer(t.payment + t.deposit); | |
emit UpdateTrade(tradeId, t.buyer, t.seller, t.state); | |
} | |
function C_claimExpiredTrade(uint256 tradeId) public { | |
Trade storage t = trades[tradeId]; | |
require(t.state == TradeState.Matched && msg.sender == t.buyer && t.expiration < now); | |
t.state = TradeState.Expired; | |
feesGathered += computeFee(t.payment).mul(2); | |
t.buyer.transfer(t.payment + t.deposit); | |
emit UpdateTrade(tradeId, t.buyer, t.seller, t.state); | |
} | |
function _withdrawFees(uint256 amount) public onlyOwner { | |
require(feesGathered >= amount); | |
feesGathered -= amount; | |
msg.sender.transfer(amount); | |
} | |
function computeFee(uint256 value) private pure returns (uint256) { | |
return value.mul(5) / 1000; // This is the fee we take on each side (0.5%) | |
} | |
function convert(string memory key) private pure returns (bytes32 ret) { | |
require(bytes(key).length <= 32); | |
assembly { | |
ret := mload(add(key, 32)) | |
} | |
} | |
function getExpirationAfter(uint256 amountOfHours) public view returns (uint256) { | |
return now.add(amountOfHours.mul(1 hours)); | |
} | |
function tradeInfo(bool wantToBuy, string memory symbol, uint256 amountOfTokens, | |
uint256 priceOfOneToken, uint256 depositPercentage, uint256 expiration, address payable other) public view | |
returns (uint256 _tradeId, uint256 _buySideTotal, uint256 _sellSideTotal, TradeState _state) { | |
_buySideTotal = amountOfTokens.mul(priceOfOneToken); | |
_sellSideTotal = depositPercentage.mul(_buySideTotal) / 100; | |
_sellSideTotal = _sellSideTotal.add(computeFee(_buySideTotal)); | |
_buySideTotal = _buySideTotal.add(computeFee(_buySideTotal)); | |
address payable buyer; address payable seller; (buyer, seller) = wantToBuy ? (msg.sender, other) : (other, msg.sender); | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(buyer, seller, symbol, amountOfTokens, priceOfOneToken, depositPercentage, expiration))); | |
return (tradeId, _buySideTotal, _sellSideTotal, trades[tradeId].state); | |
} | |
function _setTokenAddress(string memory symbol, address token) public onlyOwner { | |
tokens[convert(symbol)] = IERC20(token); | |
} | |
function getTokenAddress(string memory symbol) public view returns (IERC20) { | |
return tokens[convert(symbol)]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.5.0; | |
import "./ierc20.sol"; | |
import './safemathlib.sol'; | |
contract ERC20Test is IERC20 { | |
event Transfer(address indexed from, address indexed to, uint256 value); | |
event Approval(address indexed owner, address indexed spender, uint256 value); | |
using SafeMath for uint256; | |
string public constant name = "ERC20TestToken"; | |
string public constant symbol = "TEST"; | |
mapping(address => uint256) balances; | |
mapping(address => mapping (address => uint256)) allowed; | |
uint256 totalSupply_; | |
constructor(uint256 total) public { | |
totalSupply_ = total; | |
balances[msg.sender] = totalSupply_; | |
} | |
function totalSupply() public view returns (uint256) { | |
return totalSupply_; | |
} | |
function balanceOf(address tokenOwner) public view returns (uint) { | |
return balances[tokenOwner]; | |
} | |
function transfer(address receiver, uint numTokens) public returns (bool) { | |
require(numTokens <= balances[msg.sender]); | |
balances[msg.sender] -= numTokens; | |
balances[receiver] = balances[receiver].add(numTokens); | |
emit Transfer(msg.sender, receiver, numTokens); | |
return true; | |
} | |
function approve(address delegate, uint numTokens) public returns (bool) { | |
allowed[msg.sender][delegate] = numTokens; | |
emit Approval(msg.sender, delegate, numTokens); | |
return true; | |
} | |
function allowance(address owner, address delegate) public view returns (uint) { | |
return allowed[owner][delegate]; | |
} | |
function transferFrom(address owner, address buyer, uint numTokens) public returns (bool) { | |
require(numTokens <= balances[owner]); | |
require(numTokens <= allowed[owner][msg.sender]); | |
balances[owner] -= numTokens; | |
allowed[owner][msg.sender] -= numTokens; | |
balances[buyer] = balances[buyer].add(numTokens); | |
emit Transfer(owner, buyer, numTokens); | |
return true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.5.0; | |
import './ierc20.sol'; | |
import './safemathlib.sol'; | |
contract OTCButlerERC20Trade { | |
using SafeMath for uint256; | |
uint256 constant NonePaid = 0; | |
uint256 constant BuyPaid = 1; | |
uint256 constant SellPaid = 2; | |
event UpdateTrade(address indexed buyer, address indexed seller, address token, uint256 tokenAmount, uint256 weiAmount, uint256 expiration, uint256 tradeState); | |
event UpdateBuy(address maker, address token, uint256 weiPerToken, uint256 expiration, uint256 tokensWanted); | |
event UpdateSell(address maker, address token, uint256 weiPerToken, uint256 expiration, uint256 tokensOffered); | |
uint256 public feesGathered; | |
event ECFAnnouncement(uint256 newEarlyCancellationFee, uint256 enforcementDate); | |
uint256 public ecf = 1000; // Early cancellation fee in wei (ECF) | |
uint256 public announcedecf; // This is the announced early cancellation fee (AECF), which can be enforced only after 1 day from announcement! | |
uint256 public changeEcfWindowStart; // Time after which the ECF can be set to AECF. Can be set at most 3 hours after this time. | |
function announceNewECF(uint256 newECF) public { | |
require(msg.sender == owner, "Only owner is allowed to call this!"); | |
announcedecf = newECF; | |
changeEcfWindowStart = now + 1 days; | |
emit ECFAnnouncement(newECF, changeEcfWindowStart); | |
} | |
function enforceAnnouncedECF() public { | |
require(changeEcfWindowStart < now && now < changeEcfWindowStart + 3 hours, "Not within allowed window!"); | |
ecf = announcedecf; | |
changeEcfWindowStart = 0; | |
} | |
mapping(uint256 => uint256) public trades; | |
address payable public owner; | |
constructor() public { | |
owner = msg.sender; | |
} | |
// Function to create/supplement a buy offer. | |
// The msg.sender pays ether and requests an amount of tokens at a certain price. | |
function addToBuyOffer(address token, uint256 weiPerToken, uint256 expiration, uint256 tokenAmount) public payable { | |
require(weiPerToken > 0 && weiPerToken % 1000 == 0, "Price must be multiple of 1000!"); // We require this to guarantee fees will never give remainder. | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(msg.sender, address(0), token, weiPerToken, expiration))); | |
uint256 weiPayment = tokenAmount.mul(weiPerToken); | |
uint256 fee = computeFee(weiPayment); | |
require(msg.value == weiPayment.add(fee), "Not enough ether sent!"); // Add fee and check eth sent | |
trades[tradeId] = trades[tradeId].add(tokenAmount); // Update trade state | |
emit UpdateBuy(msg.sender, token, weiPerToken, expiration, trades[tradeId]); // Log active BUY | |
} | |
// Function to (partially) fill a buy offer. | |
// The msg.sender will give an amount of tokens at the specified price. He must have allowed our contract to spend on his behalf. | |
function partialFillBuy(address token, uint256 weiPerToken, uint256 expiration, uint256 tokenAmount, address payable other) public { | |
require(expiration > now, "Trade expired!"); | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(other, address(0), token, weiPerToken, expiration))); | |
require(trades[tradeId] >= tokenAmount, "Not enough tokens in offer!"); | |
trades[tradeId] -= tokenAmount; | |
require(IERC20(token).transferFrom(msg.sender, other, tokenAmount), "Token transfer failed!"); // Take tokens from seller and give to buyer | |
uint256 weiPayment = tokenAmount.mul(weiPerToken); | |
uint256 fee = computeFee(weiPayment); | |
msg.sender.transfer(weiPayment - fee); | |
feesGathered = feesGathered.add(fee).add(fee); | |
emit UpdateBuy(other, token, weiPerToken, expiration, trades[tradeId]); // Log active/completed BUY | |
} | |
// Function to cancel a buy offer by its maker. Only a trade maker can close a trade. He will pay a fee for early cancellation! | |
function cancelBuyOffer(address token, uint256 weiPerToken, uint256 expiration) public { | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(msg.sender, address(0), token, weiPerToken, expiration))); | |
require(trades[tradeId] > 0, "Trade doesn't exist!"); | |
uint256 weiPayment = trades[tradeId].mul(weiPerToken); | |
weiPayment = weiPayment.add(computeFee(weiPayment)); | |
trades[tradeId] = 0; | |
if (expiration > now) { // If trade did not expire yet | |
if (weiPayment > ecf) { // If enough eth to cover the early cancellation fee | |
msg.sender.transfer(weiPayment - ecf); // Refund buyer | |
feesGathered = feesGathered.add(ecf); // but take early cancellation fee | |
} else { // If not enough ether | |
feesGathered = feesGathered.add(weiPayment); // Take max possible fee and don't refund | |
} | |
} else { // If trade expired | |
msg.sender.transfer(weiPayment); // Refund buyer without taking fee. | |
} | |
emit UpdateBuy(msg.sender, token, weiPerToken, expiration, 0); // Log canceled BUY | |
} | |
// Function to create/supplement a sell offer. | |
// The msg.sender will give an amount of tokens at the specified price. He must have allowed our contract to spend on his behalf. | |
function addToSellOffer(address token, uint256 weiPerToken, uint256 expiration, uint256 tokenAmount) public { | |
require(weiPerToken > 0 && weiPerToken % 1000 == 0, "Price must be multiple of 1000!"); // We require this to guarantee fees will never give remainder. | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(address(0), msg.sender, token, weiPerToken, expiration))); | |
require(IERC20(token).transferFrom(msg.sender, address(this), tokenAmount), "Token transfer failed!"); // Take tokens from seller and keep in escrow | |
trades[tradeId] = trades[tradeId].add(tokenAmount); // Update state | |
emit UpdateSell(msg.sender, token, weiPerToken, expiration, trades[tradeId]); // Log open SELL | |
} | |
// Function to (partially) fill a sell offer. | |
// The msg.sender pays ether in exchange for an amount of tokens at the specified price. | |
function partialFillSell(address token, uint256 weiPerToken, uint256 expiration, uint256 tokenAmount, address payable other) public payable { | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(address(0), other, token, weiPerToken, expiration))); | |
require(trades[tradeId] >= tokenAmount, "Not enough tokens in offer!"); | |
uint256 weiPayment = tokenAmount.mul(weiPerToken); | |
uint256 fee = computeFee(weiPayment); | |
require(msg.value == weiPayment.add(fee), "Not enough ether sent!"); // Add fee and check eth sent | |
trades[tradeId] = trades[tradeId] - tokenAmount; // Update state | |
require(IERC20(token).transfer(msg.sender, tokenAmount), "Token transfer failed!"); // Give tokens to buyer | |
other.transfer(weiPayment - fee); // Pay the seller | |
feesGathered = feesGathered.add(fee).add(fee); | |
emit UpdateSell(other, token, weiPerToken, expiration, trades[tradeId]); // Log open BUY | |
} | |
// Function to cancel a sell offer by its maker. Only a trade maker can close a trade. He will pay a fee for early cancellation! | |
function cancelSellOffer(address token, uint256 weiPerToken, uint256 expiration) public payable { | |
uint256 tradeId = uint256(keccak256(abi.encodePacked(address(0), msg.sender, token, weiPerToken, expiration))); | |
require(trades[tradeId] > 0, "Trade doesn't exist!"); | |
uint256 tokenAmount = trades[tradeId]; | |
if (expiration > now) { // If trade did not expire yet | |
require(msg.value == ecf, "Early cancellation fee not paid!"); // Take a fee for early cancellation | |
feesGathered = feesGathered.add(ecf); | |
} | |
trades[tradeId] = 0; | |
IERC20(token).transfer(msg.sender, tokenAmount); // Refund seller | |
emit UpdateSell(msg.sender, token, weiPerToken, expiration, 0); // Log open SELL | |
} | |
// Function to create a direct trade. User must specify all details exactly or he will open a trade instead of closing one. | |
function directTrade(bool wantToBuy, address payable other, address token, uint256 tokenAmount, uint256 weiAmount, uint256 expiration) public payable { | |
uint256 tradeId = wantToBuy | |
? uint256(keccak256(abi.encodePacked(msg.sender, other, token, tokenAmount, weiAmount, expiration))) | |
: uint256(keccak256(abi.encodePacked(other, msg.sender, token, tokenAmount, weiAmount, expiration))); | |
uint256 state = trades[tradeId]; | |
if (state == NonePaid) { // If the trade doesn't exist | |
require(weiAmount > 0 && weiAmount % 1000 == 0, "Price must be multiple of 1000!"); // We require this to guarantee fees will never give remainder. | |
if (wantToBuy) { // If trying to buy | |
require(msg.value == weiAmount.add(computeFee(weiAmount)), "Not enough ether sent!"); // Take eth and fee from buyer | |
trades[tradeId] = BuyPaid; // Update state | |
emit UpdateTrade(msg.sender, other, token, tokenAmount, weiAmount, expiration, BuyPaid); // Log open BUY | |
} else { // If trying to sell | |
require(IERC20(token).transferFrom(msg.sender, address(this), tokenAmount), "Token transfer failed!"); // Take tokens from seller | |
trades[tradeId] = SellPaid; // Update state | |
emit UpdateTrade(other, msg.sender, token, tokenAmount, weiAmount, expiration, SellPaid); // Log open SELL | |
} | |
} else if (wantToBuy && state == SellPaid) { // If buyer closes the trade | |
require(expiration > now, "Trade expired!"); | |
uint256 fee = computeFee(weiAmount); | |
require(msg.value == weiAmount.add(fee), "Not enough ether sent!"); // Take eth and fee from buyer | |
trades[tradeId] = NonePaid; // Close trade first to protect against reentrancy | |
require(IERC20(token).transfer(msg.sender, tokenAmount), "Token transfer failed!"); // Send tokens to buyer (from this contract as seller already paid us) | |
other.transfer(weiAmount - fee); // Send eth - fee to seller | |
feesGathered = feesGathered.add(fee).add(fee); | |
emit UpdateTrade(msg.sender, other, token, tokenAmount, weiAmount, expiration, NonePaid); // Log closed trade | |
} else if (!wantToBuy && state == BuyPaid) { // If seller closes the trade | |
require(expiration > now, "Trade expired!"); | |
trades[tradeId] = NonePaid; // Close trade first to protect against reentrancy | |
require(IERC20(token).transferFrom(msg.sender, other, tokenAmount), "Token transfer failed!"); // Send tokens to buyer (directly from seller) | |
uint256 fee = computeFee(weiAmount); | |
msg.sender.transfer(weiAmount - fee); // Send eth - fee to seller | |
feesGathered = feesGathered.add(fee).add(fee); | |
emit UpdateTrade(other, msg.sender, token, tokenAmount, weiAmount, expiration, NonePaid); // Log closed trade | |
} else { | |
revert(); | |
} | |
} | |
// Function that allows the trade maker to cancel a direct trade. We will take a fee for cancellations before the expiry time! | |
function cancelDirectTrade(bool wantToBuy, address payable other, address token, uint256 tokenAmount, uint256 weiAmount, uint256 expiration) public payable { | |
uint256 tradeId = wantToBuy | |
? uint256(keccak256(abi.encodePacked(msg.sender, other, token, tokenAmount, weiAmount, expiration))) | |
: uint256(keccak256(abi.encodePacked(other, msg.sender, token, tokenAmount, weiAmount, expiration))); | |
uint256 state = trades[tradeId]; | |
if (wantToBuy && state == BuyPaid) { // If buyer cancels the trade | |
trades[tradeId] = NonePaid; // Close trade first to protect against reentrancy | |
uint256 weiPayment = weiAmount.add(computeFee(weiAmount)); | |
if (expiration > now) { // If trade did not expire yet | |
if (weiPayment > ecf) { // If enough eth to cover the early cancellation fee | |
msg.sender.transfer(weiPayment - ecf); // Refund buyer | |
feesGathered = feesGathered.add(ecf); // but take early cancellation fee | |
} else { // If not enough ether | |
feesGathered = feesGathered.add(weiPayment); // Take max possible fee and don't refund | |
} | |
} else { // If trade expired | |
msg.sender.transfer(weiPayment); // Refund buyer without taking fee. | |
} | |
emit UpdateTrade(msg.sender, other, token, tokenAmount, weiAmount, expiration, NonePaid); // Log closed trade | |
} else if (!wantToBuy && state == SellPaid) { // If seller cancels the trade | |
if (expiration > now) { // If trade did not expire yet | |
require(msg.value == ecf, "Not enough eth to cover fee!"); // Take a fee for early cancellation | |
feesGathered = feesGathered.add(ecf); | |
} | |
trades[tradeId] = NonePaid; // Close trade first to protect against reentrancy | |
require(IERC20(token).transfer(msg.sender, tokenAmount), "Token transfer failed!"); // Refund tokens to seller | |
emit UpdateTrade(other, msg.sender, token, tokenAmount, weiAmount, expiration, NonePaid); // Log closed trade | |
} else { | |
revert("Cannot interact with trade!"); | |
} | |
} | |
function _withdrawFees() public { | |
uint256 amount = feesGathered; | |
feesGathered = 0; | |
owner.transfer(amount); | |
} | |
function computeFee(uint256 value) private pure returns (uint256) { | |
return value.mul(5) / 1000; // this is the fee we take on each side (0.5%) | |
} | |
function() external payable { | |
revert(); | |
} | |
event SelfDestructAnnouncement(uint256 selfDestructTime); | |
uint256 public selfDestructAfter = 2**256 - 1; | |
function announceSelfDestruct() public { | |
require(owner == msg.sender); | |
selfDestructAfter = now + 30 days; | |
emit SelfDestructAnnouncement(selfDestructAfter); | |
} | |
function selfDestruct() public { | |
require(selfDestructAfter < now); | |
selfdestruct(owner); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.5.0; | |
interface IERC20 { | |
function transfer(address to, uint256 value) external returns (bool); | |
function approve(address spender, uint256 value) external returns (bool); | |
function transferFrom(address from, address to, uint256 value) external returns (bool); | |
function totalSupply() external view returns (uint256); | |
function balanceOf(address who) external view returns (uint256); | |
function allowance(address owner, address spender) external view returns (uint256); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity >=0.5.0; | |
library SafeMath { | |
function add(uint256 a, uint256 b) internal pure returns (uint256 c) { | |
c = a + b; | |
require(c >= a); | |
return c; | |
} | |
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { | |
if (a == 0) { | |
return 0; | |
} | |
c = a * b; | |
require(c / a == b); | |
return c; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment