Transaction Hash:
Block:
9780157 at Mar-31-2020 02:50:49 PM +UTC
Transaction Fee:
0.000197012 ETH
$0.40
Gas Used:
49,253 Gas / 4 Gwei
Emitted Events:
| 28 |
Exchange.OrderCancelled( id=15262, time=1585666249 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
|
0x433022C4...cA782174D
Miner
| (K1POOL.COM) | 0.788638490612872873 Eth | 0.788835502612872873 Eth | 0.000197012 | |
| 0x8D03635c...f88cAdD3c |
0.034993211 Eth
Nonce: 16
|
0.054796199 Eth
Nonce: 17
| 0.019802988 | ||
| 0xaA5bBD5A...444Dbd586 | (Saturn Network 2) | 35.966444195229676859 Eth | 35.946444195229676859 Eth | 0.02 |
Execution Trace
Exchange.cancelOrder( orderId=15262 )
- ETH 0.02
0x8d03635c0adf3501ccfc69e5cefd8ecf88cadd3c.CALL( )
cancelOrder[Exchange (ln:426)]
sub[Exchange (ln:432)]sendTokensTo[Exchange (ln:433)]transfer[Exchange (ln:592)]transfer[Exchange (ln:595)]
OrderCancelled[Exchange (ln:438)]
// Saturn Protocol
// File: contracts/SafeMath.sol
pragma solidity ^0.4.24;
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
// File: contracts/BytesLib.sol
// from
// https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol
library BytesLib {
function toAddress(bytes _bytes, uint _start) internal pure returns (address) {
require(_bytes.length >= (_start + 20));
address tempAddress;
assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}
return tempAddress;
}
function toUint(bytes _bytes, uint _start) internal pure returns (uint256) {
require(_bytes.length >= (_start + 32));
uint256 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x20), _start))
}
return tempUint;
}
}
// File: contracts/ERC223.sol
contract ERC223 {
uint public totalSupply;
function balanceOf(address who) constant public returns (uint);
function name() constant public returns (string _name);
function symbol() constant public returns (string _symbol);
function decimals() constant public returns (uint8 _decimals);
function totalSupply() constant public returns (uint256 _supply);
function transfer(address to, uint value) public returns (bool ok);
function transfer(address to, uint value, bytes data) public returns (bool ok);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event ERC223Transfer(address indexed _from, address indexed _to, uint256 _value, bytes _data);
}
contract ContractReceiver {
function tokenFallback(address _from, uint _value, bytes _data) public;
}
contract ERC223I is ERC223 {
using SafeMath for uint;
mapping(address => uint) balances;
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
function name() constant public returns (string _name) {
return name;
}
function symbol() constant public returns (string _symbol) {
return symbol;
}
function decimals() constant public returns (uint8 _decimals) {
return decimals;
}
function totalSupply() constant public returns (uint256 _totalSupply) {
return totalSupply;
}
function transfer(address _to, uint _value, bytes _data) public returns (bool success) {
if (isContract(_to)) {
return transferToContract(_to, _value, _data);
} else {
return transferToAddress(_to, _value, _data);
}
}
function transfer(address _to, uint _value) public returns (bool success) {
bytes memory empty;
if (isContract(_to)) {
return transferToContract(_to, _value, empty);
} else {
return transferToAddress(_to, _value, empty);
}
}
function isContract(address _addr) private view returns (bool is_contract) {
uint length;
assembly {
length := extcodesize(_addr)
}
return (length > 0);
}
function transferToAddress(address _to, uint _value, bytes _data) private returns (bool success) {
if (balanceOf(msg.sender) < _value) revert();
balances[msg.sender] = balanceOf(msg.sender).sub(_value);
balances[_to] = balanceOf(_to).add(_value);
Transfer(msg.sender, _to, _value);
ERC223Transfer(msg.sender, _to, _value, _data);
return true;
}
function transferToContract(address _to, uint _value, bytes _data) private returns (bool success) {
if (balanceOf(msg.sender) < _value) revert();
balances[msg.sender] = balanceOf(msg.sender).sub(_value);
balances[_to] = balanceOf(_to).add(_value);
ContractReceiver reciever = ContractReceiver(_to);
reciever.tokenFallback(msg.sender, _value, _data);
Transfer(msg.sender, _to, _value);
ERC223Transfer(msg.sender, _to, _value, _data);
return true;
}
function balanceOf(address _owner) constant public returns (uint balance) {
return balances[_owner];
}
}
// File: contracts/Exchange.sol
// Saturn Protocol
contract ERC20 {
function totalSupply() public view returns (uint);
function balanceOf(address holder) public view returns (uint);
function allowance(address holder, address other) public view returns (uint);
function approve(address other, uint amount) public returns (bool);
function transfer(address to, uint amount) public returns (bool);
function transferFrom(
address from, address to, uint amount
) public returns (bool);
}
contract Exchange is ContractReceiver {
using SafeMath for uint256;
using BytesLib for bytes;
bool private rentrancy_lock = false;
modifier nonReentrant() {
require(!rentrancy_lock);
rentrancy_lock = true;
_;
rentrancy_lock = false;
}
struct Order {
address owner;
bool active;
address sellToken;
address buyToken;
address ring;
uint256 amount;
uint256 priceMul;
uint256 priceDiv;
}
// person => token => balance
mapping(address => mapping(address => uint256)) private balances;
mapping(uint256 => Order) private orderBook;
uint256 public orderCount;
address private etherAddress = 0x0;
address private saturnToken;
address private admin;
uint256 public tradeMiningBalance;
address public treasury;
uint256 public feeMul;
uint256 public feeDiv;
uint256 public tradeMiningMul;
uint256 public tradeMiningDiv;
event NewOrder(
uint256 id,
address owner,
address sellToken,
address buyToken,
address ring,
uint256 amount,
uint256 priceMul,
uint256 priceDiv,
uint256 time
);
event OrderCancelled(
uint256 id,
uint256 time
);
event OrderFulfilled(
uint256 id,
uint256 time
);
event Trade(
address from,
address to,
uint256 orderId,
uint256 soldTokens,
uint256 boughtTokens,
uint256 feePaid,
uint256 time
);
event Mined(
address trader,
uint256 amount,
uint256 time
);
function Exchange(
address _saturnToken,
address _treasury,
uint256 _feeMul,
uint256 _feeDiv,
uint256 _tradeMiningMul,
uint256 _tradeMiningDiv
) public {
saturnToken = _saturnToken;
treasury = _treasury;
feeMul = _feeMul;
feeDiv = _feeDiv;
tradeMiningMul = _tradeMiningMul;
tradeMiningDiv = _tradeMiningDiv;
admin = msg.sender;
}
function() payable public { revert(); }
//////////////////
// public views //
//////////////////
// add views for prices too
// and for order owner too
function getBalance(address token, address user) view public returns(uint256) {
return balances[user][token];
}
function isOrderActive(uint256 orderId) view public returns(bool) {
return orderBook[orderId].active;
}
function remainingAmount(uint256 orderId) view public returns(uint256) {
return orderBook[orderId].amount;
}
function getBuyTokenAmount(uint256 desiredSellTokenAmount, uint256 orderId) public view returns(uint256 amount) {
require(desiredSellTokenAmount > 0);
Order storage order = orderBook[orderId];
if (order.sellToken == etherAddress || order.buyToken == etherAddress) {
uint256 feediff = feeDiv.sub(feeMul);
amount = desiredSellTokenAmount.mul(order.priceDiv).mul(feeDiv).div(order.priceMul).div(feediff);
} else {
amount = desiredSellTokenAmount.mul(order.priceDiv).div(order.priceMul);
}
require(amount > 0);
}
function calcFees(uint256 amount, uint256 orderId) public view returns(uint256 fees) {
Order storage order = orderBook[orderId];
if (order.sellToken == etherAddress) {
uint256 sellTokenAmount = amount.mul(order.priceMul).div(order.priceDiv);
fees = sellTokenAmount.mul(feeMul).div(feeDiv);
} else if (order.buyToken == etherAddress) {
fees = amount.mul(feeMul).div(feeDiv);
} else {
fees = 0;
}
return fees;
}
function tradeMiningAmount(uint256 fees, uint256 orderId) public view returns(uint256) {
if (fees == 0) { return 0; }
Order storage order = orderBook[orderId];
if (!order.active) { return 0; }
uint256 tokenAmount = fees.mul(tradeMiningMul).div(tradeMiningDiv);
if (tradeMiningBalance < tokenAmount) {
return tradeMiningBalance;
} else {
return tokenAmount;
}
}
////////////////////
// public methods //
////////////////////
function withdrawTradeMining() public {
if (msg.sender != admin) { revert(); }
require(tradeMiningBalance > 0);
uint toSend = tradeMiningBalance;
tradeMiningBalance = 0;
require(sendTokensTo(admin, toSend, saturnToken));
}
function changeTradeMiningPrice(uint256 newMul, uint256 newDiv) public {
if (msg.sender != admin) { revert(); }
require(newDiv != 0);
tradeMiningMul = newMul;
tradeMiningDiv = newDiv;
}
// handle incoming ERC223 tokens
function tokenFallback(address from, uint value, bytes data) public {
// depending on length of data
// this should be either an order creating transaction
// or an order taking transaction
// or a transaction allocating tokens for trade mining
if (data.length == 0 && msg.sender == saturnToken) {
_topUpTradeMining(value);
} else if (data.length == 84) {
_newOrder(from, msg.sender, data.toAddress(64), value, data.toUint(0), data.toUint(32), etherAddress);
} else if (data.length == 104) {
_newOrder(from, msg.sender, data.toAddress(64), value, data.toUint(0), data.toUint(32), data.toAddress(84));
} else if (data.length == 32) {
_executeOrder(from, data.toUint(0), msg.sender, value);
} else {
// unknown payload!
revert();
}
}
function sellEther(
address buyToken,
uint256 priceMul,
uint256 priceDiv
) public payable returns(uint256 orderId) {
require(msg.value > 0);
return _newOrder(msg.sender, etherAddress, buyToken, msg.value, priceMul, priceDiv, etherAddress);
}
function sellEtherWithRing(
address buyToken,
uint256 priceMul,
uint256 priceDiv,
address ring
) public payable returns(uint256 orderId) {
require(msg.value > 0);
return _newOrder(msg.sender, etherAddress, buyToken, msg.value, priceMul, priceDiv, ring);
}
function buyOrderWithEth(uint256 orderId) public payable {
require(msg.value > 0);
_executeOrder(msg.sender, orderId, etherAddress, msg.value);
}
function sellERC20Token(
address sellToken,
address buyToken,
uint256 amount,
uint256 priceMul,
uint256 priceDiv
) public returns(uint256 orderId) {
require(amount > 0);
uint256 pulledAmount = pullTokens(sellToken, amount);
return _newOrder(msg.sender, sellToken, buyToken, pulledAmount, priceMul, priceDiv, etherAddress);
}
function sellERC20TokenWithRing(
address sellToken,
address buyToken,
uint256 amount,
uint256 priceMul,
uint256 priceDiv,
address ring
) public returns(uint256 orderId) {
require(amount > 0);
uint256 pulledAmount = pullTokens(sellToken, amount);
return _newOrder(msg.sender, sellToken, buyToken, pulledAmount, priceMul, priceDiv, ring);
}
function buyOrderWithERC20Token(
uint256 orderId,
address token,
uint256 amount
) public {
require(amount > 0);
require(pullTokens(token, amount) > 0);
_executeOrder(msg.sender, orderId, token, amount);
}
function cancelOrder(uint256 orderId) public nonReentrant {
Order storage order = orderBook[orderId];
require(order.amount > 0);
require(order.active);
require(msg.sender == order.owner);
balances[msg.sender][order.sellToken] = balances[msg.sender][order.sellToken].sub(order.amount);
require(sendTokensTo(order.owner, order.amount, order.sellToken));
// deleting the order refunds the caller some gas
// this also sets order.active to false
delete orderBook[orderId];
emit OrderCancelled(orderId, now);
}
/////////////////////
// private methods //
/////////////////////
function _newOrder(
address owner,
address sellToken,
address buyToken,
uint256 amount,
uint256 priceMul,
uint256 priceDiv,
address ring
) private nonReentrant returns(uint256 orderId) {
/////////////////////////
// step 1. validations //
/////////////////////////
require(amount > 0);
require(priceMul > 0);
require(priceDiv > 0);
require(sellToken != buyToken);
///////////////////////////////
// step 2. Update order book //
///////////////////////////////
orderId = orderCount++;
orderBook[orderId] = Order(owner, true, sellToken, buyToken, ring, amount, priceMul, priceDiv);
balances[owner][sellToken] = balances[owner][sellToken].add(amount);
emit NewOrder(orderId, owner, sellToken, buyToken, ring, amount, priceMul, priceDiv, now);
}
function _executeBuyOrder(address trader, uint256 orderId, uint256 buyTokenAmount) private returns(uint256) {
// buytoken: tkn
// selltoken: ether
Order storage order = orderBook[orderId];
uint256 sellTokenAmount = buyTokenAmount.mul(order.priceMul).div(order.priceDiv);
uint256 fees = sellTokenAmount.mul(feeMul).div(feeDiv);
require(sellTokenAmount > 0);
require(sellTokenAmount <= order.amount);
order.amount = order.amount.sub(sellTokenAmount);
// send tokens to order owner
require(sendTokensTo(order.owner, buyTokenAmount, order.buyToken));
// send ether to trader
require(sendTokensTo(trader, sellTokenAmount.sub(fees), order.sellToken));
emit Trade(trader, order.owner, orderId, sellTokenAmount.sub(fees), buyTokenAmount, fees, now);
return fees;
}
function _executeSellOrder(address trader, uint256 orderId, uint256 buyTokenAmount) private returns(uint256) {
// buytoken: ether
// selltoken: tkn
Order storage order = orderBook[orderId];
uint256 fees = buyTokenAmount.mul(feeMul).div(feeDiv);
uint256 sellTokenAmount = buyTokenAmount.sub(fees).mul(order.priceMul).div(order.priceDiv);
require(sellTokenAmount > 0);
require(sellTokenAmount <= order.amount);
order.amount = order.amount.sub(sellTokenAmount);
// send ether to order owner
require(sendTokensTo(order.owner, buyTokenAmount.sub(fees), order.buyToken));
// send token to trader
require(sendTokensTo(trader, sellTokenAmount, order.sellToken));
emit Trade(trader, order.owner, orderId, sellTokenAmount, buyTokenAmount.sub(fees), fees, now);
return fees;
}
function _executeTokenSwap(address trader, uint256 orderId, uint256 buyTokenAmount) private returns(uint256) {
// no ether was exchanged
Order storage order = orderBook[orderId];
uint256 sellTokenAmount = buyTokenAmount.mul(order.priceMul).div(order.priceDiv);
require(sellTokenAmount > 0);
require(sellTokenAmount <= order.amount);
order.amount = order.amount.sub(sellTokenAmount);
require(sendTokensTo(order.owner, buyTokenAmount, order.buyToken));
require(order.active);
require(sendTokensTo(trader, sellTokenAmount, order.sellToken));
emit Trade(trader, order.owner, orderId, sellTokenAmount, buyTokenAmount, 0, now);
return 0;
}
function _executeOrder(address trader, uint256 orderId, address buyToken, uint256 buyTokenAmount) private nonReentrant {
/////////////////////////
// step 0. validations //
/////////////////////////
require(orderId < orderCount);
require(buyTokenAmount > 0);
Order storage order = orderBook[orderId];
require(order.active);
require(trader != order.owner);
require(buyToken == order.buyToken);
// enforce exclusivity
if (order.ring != etherAddress) { require(order.ring == tx.origin); }
////////////////////////////
// step 1. token exchange //
////////////////////////////
uint256 fees;
if (order.sellToken == etherAddress) {
// buy order: taker sends ether, gets tokens
fees = _executeBuyOrder(trader, orderId, buyTokenAmount);
} else if (order.buyToken == etherAddress) {
// sell order: taker sends tokens, gets ether
fees = _executeSellOrder(trader, orderId, buyTokenAmount);
} else {
fees = _executeTokenSwap(trader, orderId, buyTokenAmount);
}
////////////////////////////
// step 2. fees & wrap up //
////////////////////////////
// collect fees and issue trade mining
require(_tradeMiningAndFees(fees, trader));
// deleting the order refunds the caller some gas
if (orderBook[orderId].amount == 0) {
delete orderBook[orderId];
emit OrderFulfilled(orderId, now);
}
}
function _tradeMiningAndFees(uint256 fees, address trader) private returns(bool) {
if (fees == 0) { return true; }
// step one: send fees to the treasury
require(sendTokensTo(treasury, fees, etherAddress));
if (tradeMiningBalance == 0) { return true; }
// step two: calculate reward
uint256 tokenAmount = fees.mul(tradeMiningMul).div(tradeMiningDiv);
if (tokenAmount == 0) { return true; }
if (tokenAmount > tradeMiningBalance) { tokenAmount = tradeMiningBalance; }
// account for sent tokens
tradeMiningBalance = tradeMiningBalance.sub(tokenAmount);
// step three: send the reward to the trader
require(sendTokensTo(trader, tokenAmount, saturnToken));
emit Mined(trader, tokenAmount, now);
return true;
}
function sendTokensTo(
address destination,
uint256 amount,
address tkn
) private returns(bool) {
if (tkn == etherAddress) {
destination.transfer(amount);
} else {
// works with both ERC223 and ERC20
ERC20(tkn).transfer(destination, amount);
}
return true;
}
// ERC20 fixture
function pullTokens(address token, uint256 amount) private nonReentrant returns(uint256) {
ERC20 tkn = ERC20(token);
// need to do this balance dance in order to account for deflationary tokens
uint256 balanceBefore = tkn.balanceOf(address(this));
tkn.transferFrom(msg.sender, address(this), amount);
uint256 balanceAfter = tkn.balanceOf(address(this));
return balanceAfter.sub(balanceBefore);
}
function _topUpTradeMining(uint256 amount) private returns(bool) {
tradeMiningBalance = tradeMiningBalance.add(amount);
return true;
}
}