ETH Price: $2,420.91 (+7.70%)

Transaction Decoder

Block:
12051386 at Mar-16-2021 06:51:44 PM +UTC
Transaction Fee:
0.007316334 ETH $17.71
Gas Used:
41,103 Gas / 178 Gwei

Emitted Events:

172 TweetMarket.Offer( tweetID=1364560733472579591, bidder=[Sender] 0xbd1c30fc75c38c40e2d839c69385c475ea5cee13, outbid=0x0379c1fddc7ac17de2c0fbf1db86063c1c5c3ad9, amount=390676960000000000, timestamp=1615920704 )

Account State Difference:

  Address   Before After State Difference Code
0x0379c1fd...c1c5c3Ad9 1.142286912 Eth1.482733972 Eth0.34044706
(Spark Pool)
159.765955541894946695 Eth159.773271875894946695 Eth0.007316334
0xBD1C30Fc...5EA5CEe13
0.446799344561717 Eth
Nonce: 10
0.048806050561717 Eth
Nonce: 11
0.397993294
0xE14ab3Ee...D2dF22585 2,568.46918217 Eth2,568.51941207 Eth0.0502299

Execution Trace

ETH 0.39067696 TweetMarket.offer( tweetID=1364560733472579591 )
  • ETH 0.34044706 0x0379c1fddc7ac17de2c0fbf1db86063c1c5c3ad9.CALL( )
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.6.6;
    
    contract TweetMarket {
    	struct Bid {
    		uint256 amount;
    		address bidder;
    		uint256 lockup;
    	}
    
    	uint256 private constant UINT_160_MAX = (2 ** 160) - 1;
    	uint256 private constant UINT_64_MAX = (2 ** 64) - 1;
    	uint256 private constant UINT_32_MAX = (2 ** 32) - 1;
    	uint256 private constant SATOSHI = 10000000000;
    
    	address private constant NULL_ADDRESS = address(0);
    
    	uint256 private BID_MINIMUM = SATOSHI;
    	uint256 private BID_MIN_INCREASE_PCT = 5;
    	uint256 private BID_LOCKUP_PERIOD = 24 hours;
    	uint256 private CLOSE_FEE_PCT = 5;
    
    	mapping(uint256 => uint256) private highBids;
    	mapping(address => uint256) private balances;
    
    	address private admin;
    	address[] private delegateHistory;
    	mapping(address => bool) private delegates;
    	bool private halted;
    
    	event Offer (
    		uint256 indexed tweetID,
    		address indexed bidder,
    		address indexed outbid,
    		uint256 amount,
    		uint256 timestamp
    	);
    
    	event Cancel (
    		uint256 indexed tweetID,
    		address indexed bidder,
    		uint256 amount,
    		uint256 timestamp
    	);
    
    	event Close (
    		uint256 indexed tweetID,
    		address indexed seller,
    		address indexed buyer,
    		uint256 amount,
    		uint256 timestamp
    	);
    
    	event Withdraw (
    		address indexed account,
    		uint256 amount,
    		uint256 timestamp
    	);
    
    	modifier haltable {
    		require(halted == false, "Halted");
    		_;
    	}
    
    	modifier onlyAdmin {
    		require(msg.sender == admin, "Only Admin");
    		_;
    	}
    
    	modifier onlyDelegates {
    		require(delegates[msg.sender] || msg.sender == admin, "Only Delegates");
    		_;
    	}
    
    	constructor() public {
    		admin = msg.sender;
    	}
    
    	/**
    	 * USER APIs
    	 */
    	function offer(uint256 tweetID) public payable haltable {
    		address bidder = msg.sender;
    		uint256 amount = round(msg.value);
    
    		Bid memory bid = loadBid(tweetID);
    
    		uint256 oldAmount = bid.amount;
    		uint256 minAmount = oldAmount == 0 ? BID_MINIMUM : round(oldAmount + (oldAmount * BID_MIN_INCREASE_PCT / 100));
    		require(amount >= minAmount, "Offer too low");
    
    		address oldBidder = bid.bidder;
    		if (oldBidder != NULL_ADDRESS) {
    			_payOrStore(oldBidder, oldAmount);
    		}
    
    		bid.amount = amount;
    		bid.bidder = bidder;
    		bid.lockup = now + BID_LOCKUP_PERIOD;
    
    		saveBid(tweetID, bid);
    
    		emit Offer(
    			tweetID,
    			bidder,
    			oldBidder,
    			amount,
    			now
    		);
    	}
    
    	function cancel(uint256 tweetID) public haltable {
    		address bidder = msg.sender;
    
    		Bid memory bid = loadBid(tweetID);
    
    		require(bid.bidder == bidder, "Current high offer uses a different address");
    		require(bid.lockup <= now, "Minimum offer period not met");
    
    		uint256 bidAmount = bid.amount;
    
    		bid.amount = 0;
    		bid.bidder = NULL_ADDRESS;
    		bid.lockup = 0;
    
    		saveBid(tweetID, bid);
    
    		_payOrFail(bidder, bidAmount);
    
    		emit Cancel(
    			tweetID,
    			bidder,
    			bidAmount,
    			now
    		);
    	}
    
    	function withdraw() public haltable {
    		address account = msg.sender;
    		uint256 balance = balances[account] = 0;
    		balances[account] = 0;
    
    		_payOrFail(account, balance);
    
    		emit Withdraw(account, balance, now);
    	}
    
    	/**
    	 * HELPERS
    	 */
    	function _payOrStore(address recipient, uint256 amount) internal returns (bool) {
    		(bool success, ) = recipient.call{value: amount, gas: 0}("");
    		if (!success) {
    			balances[recipient] += amount;
    		}
    		return success;
    	}
    
    	function _payOrFail(address recipient, uint256 amount) internal {
    		(bool success, ) = recipient.call{value: amount}("");
    		require(success, "Transfer failed");
    	}
    
    	function _close(uint256 tweetID, address seller, uint256 minPrice, uint256 percentFee) internal returns (uint256) {
    		Bid memory bid = loadBid(tweetID);
    
    		address buyer = bid.bidder;
    		uint256 price = bid.amount;
    		require(price >= minPrice, "Below expected price");
    		uint256 fee = price * percentFee / 100;
    		uint256 net = price - fee;
    
    		bid.amount = 0;
    		bid.bidder = NULL_ADDRESS;
    		bid.lockup = 0;
    
    		saveBid(tweetID, bid);
    
    		_payOrStore(seller, net);
    
    		emit Close(
    			tweetID,
    			seller,
    			buyer,
    			price,
    			now
    		);
    
    		return fee;
    	}
    
    	function loadBid(uint256 tweetID) internal view returns (Bid memory bid) {
    		uint256 data = highBids[tweetID];
    
    		bid.amount = (data & UINT_64_MAX) * SATOSHI;
    		data = data >> 64;
    		bid.bidder = address(data & UINT_160_MAX);
    		data = data >> 160;
    		bid.lockup = data & UINT_32_MAX;
    
    		return bid;
    	}
    
    	function saveBid(uint256 tweetID, Bid memory bid) internal {
    		uint256 data = 0;
    
    		data |= bid.lockup;
    		data = data << 160;
    		data |= uint256(bid.bidder);
    		data = data << 64;
    		data |= bid.amount / SATOSHI;
    
    		highBids[tweetID] = data;
    	}
    
    	function round(uint256 amount) internal pure returns (uint256) {
    		return amount - (amount % SATOSHI);
    	}
    
    	/**
    	 * DELEGATE APIs
    	 */
    	function close(uint256 tweetID, address seller, uint256 price) public onlyDelegates haltable {
    		uint256 fees = _close(tweetID, seller, price, CLOSE_FEE_PCT);
    		if (fees > 0) {
    			_payOrFail(admin, fees);
    		}
    	}
    
    	function closeMany(uint256[] memory tweetIDs, address[] memory sellers, uint256[] memory prices) public onlyDelegates haltable {
    		uint256 fees = 0;
    		uint256 percentFee = CLOSE_FEE_PCT;
    		uint256 numClosing = tweetIDs.length;
    		require(numClosing == sellers.length && numClosing == prices.length, "Bad params");
    		for (uint i = 0; i < numClosing; i++) {
    			fees += _close(tweetIDs[i], sellers[i], prices[i], percentFee);
    		}
    		if (fees > 0) {
    			_payOrFail(admin, fees);
    		}
    	}
    
    	/**
    	 * ADMIN APIs
    	 */
    	function setDelegate(address delegate, bool privileged) public onlyAdmin {
    		delegates[delegate] = privileged;
    		if (privileged) {
    			delegateHistory.push(delegate);
    		}
    	}
    
    	function changeAdmin(address newAdmin) public onlyAdmin {
    		admin = newAdmin;
    	}
    
    	function setBidMinimum(uint256 minimum) public onlyAdmin {
    		require(minimum >= SATOSHI, "Invalid minimum");
    		BID_MINIMUM = minimum;
    	}
    
    	function setBidMinimumIncrease(uint256 percent) public onlyAdmin {
    		BID_MIN_INCREASE_PCT = percent;
    	}
    
    	function setBidLockupTime(uint256 duration) public onlyAdmin {
    	    require(duration <= 30 days, "Invalid duration");
    		BID_LOCKUP_PERIOD = duration;
    	}
    
    	function setCloseFee(uint256 percent) public onlyAdmin {
    	    require(percent <= 100, "Invalid percent");
    		CLOSE_FEE_PCT = percent;
    	}
    
    	function halt() public onlyAdmin {
    		halted = true;
    		_payOrFail(msg.sender, address(this).balance);
    	}
    
    	/**
    	 * READ APIs
    	 */
    	function getBid(uint256 tweetID) public view returns (uint256, address, uint256) {
    		Bid memory bid = loadBid(tweetID);
    		return (
    			bid.lockup,
    			bid.bidder,
    			bid.amount
    		);
    	}
    
    	function getBidMinimum() public view returns (uint256) {
    		return BID_MINIMUM;
    	}
    
    	function getBidLockupTime() public view returns (uint256) {
    		return BID_LOCKUP_PERIOD;
    	}
    
    	function getDelegateHistory() public view returns (address[] memory) {
    		return delegateHistory;
    	}
    
    	function getCloseFeePercent() public view returns (uint256) {
    		return CLOSE_FEE_PCT;
    	}
    
    	function isDelegate(address delegate) public view returns (bool) {
    		return delegates[delegate];
    	}
    
    	function getBalance(address owner) public view returns (uint256) {
    		return balances[owner];
    	}
    
    	function getAdmin() public view returns (address) {
    		return admin;
    	}
    }