Transaction Hash:
Block:
18414709 at Oct-23-2023 06:18:59 PM +UTC
Transaction Fee:
0.000970219796943302 ETH
$1.75
Gas Used:
30,586 Gas / 31.721042207 Gwei
Emitted Events:
206 |
EtheriaExchangeXL.WithdrawalProcessed( account=[Sender] 0x9b7890355a43dba89219db58096e09bdf42df2aa, destination=[Sender] 0x9b7890355a43dba89219db58096e09bdf42df2aa, amount=2925000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x111B76DB...a781D15db | 11.725 Eth | 8.8 Eth | 2.925 | ||
0x1f9090aa...8e676c326
Miner
| 1.872577818221208983 Eth | 1.872580876821208983 Eth | 0.0000030586 | ||
0x9B789035...DF42Df2aA |
0.196160601522745112 Eth
Nonce: 11
|
3.12019038172580181 Eth
Nonce: 12
| 2.924029780203056698 |
Execution Trace
EtheriaExchangeXL.CALL( )

- ETH 2.925
0x9b7890355a43dba89219db58096e09bdf42df2aa.CALL( )
// SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; interface Etheria { function getOwner(uint8 col, uint8 row) external view returns(address); function setOwner(uint8 col, uint8 row, address newOwner) external; } interface MapElevationRetriever { function getElevation(uint8 col, uint8 row) external view returns (uint8); } contract EtheriaExchangeXL { address public owner; address public pendingOwner; string public name = "EtheriaExchangeXL"; Etheria public constant etheria = Etheria(address(0xB21f8684f23Dbb1008508B4DE91a0aaEDEbdB7E4)); MapElevationRetriever public constant mapElevationRetriever = MapElevationRetriever(address(0x68549D7Dbb7A956f955Ec1263F55494f05972A6b)); uint128 public minBid = uint128(1 ether); // setting this to 10 finney throws compilation error for some reason uint256 public feeRate = uint256(100); // in basis points (100 is 1%) uint256 public collectedFees; struct Bid { uint128 amount; uint8 minCol; // shortened all of these for readability uint8 maxCol; uint8 minRow; uint8 maxRow; uint8 minEle; uint8 maxEle; uint8 minWat; uint8 maxWat; uint64 biddersIndex; // renamed from bidderIndex because it's the Index of the bidders array } address[] public bidders; mapping (address => Bid) public bidOf; // renamed these three to be ultra-descriptive mapping (address => uint256) public pendingWithdrawalOf; mapping (uint16 => uint128) public askFor; event OwnershipTransferInitiated(address indexed owner, address indexed pendingOwner); // renamed some of these to conform to past tense verbs event OwnershipTransferAccepted(address indexed oldOwner, address indexed newOwner); event BidCreated(address indexed bidder, uint128 indexed amount, uint8 minCol, uint8 maxCol, uint8 minRow, uint8 maxRow, uint8 minEle, uint8 maxEle, uint8 minWat, uint8 maxWat); event BidAccepted(address indexed seller, address indexed bidder, uint16 indexed index, uint128 amount, uint8 minCol, uint8 maxCol, uint8 minRow, uint8 maxRow, uint8 minEle, uint8 maxEle, uint8 minWat, uint8 maxWat); event BidCancelled(address indexed bidder, uint128 indexed amount, uint8 minCol, uint8 maxCol, uint8 minRow, uint8 maxRow, uint8 minEle, uint8 maxEle, uint8 minWat, uint8 maxWat); event AskCreated(address indexed owner, uint256 indexed price, uint16 indexed index); event AskRemoved(address indexed owner, uint256 indexed price, uint16 indexed index); event WithdrawalProcessed(address indexed account, address indexed destination, uint256 indexed amount); constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "EEXL: Not owner"); _; } function transferOwnership(address newOwner) external onlyOwner { pendingOwner = newOwner; emit OwnershipTransferInitiated(msg.sender, newOwner); } function acceptOwnership() external { require(msg.sender == pendingOwner, "EEXL: Not pending owner"); emit OwnershipTransferAccepted(owner, msg.sender); owner = msg.sender; pendingOwner = address(0); } function _safeTransferETH(address recipient, uint256 amount) internal { // Secure transfer of ETH that is much less likely to be broken by future gas-schedule EIPs (bool success, ) = recipient.call{ value: amount }(""); // syntax: (bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(encoded function and data) require(success, "EEXL: ETH transfer failed"); } function collectFees() external onlyOwner { uint256 amount = collectedFees; collectedFees = uint256(0); _safeTransferETH(msg.sender, amount); } function setFeeRate(uint256 newFeeRate) external onlyOwner { // Set the feeRate to newFeeRate, then validate it require((feeRate = newFeeRate) <= uint256(500), "EEXL: Invalid feeRate"); // feeRate will revert if req fails } function setMinBid(uint128 newMinBid) external onlyOwner { minBid = newMinBid; // doubly beneficial because I could effectively kill new bids with a huge minBid } // in the event of an exchange upgrade or unforseen problem function _getIndex(uint8 col, uint8 row) internal pure returns (uint16) { require(_isValidColOrRow(col) && _isValidColOrRow(row), "EEXL: Invalid col and/or row"); return (uint16(col) * uint16(33)) + uint16(row); } function _isValidColOrRow(uint8 value) internal pure returns (bool) { return (value >= uint8(0)) && (value <= uint8(32)); // while nobody should be checking, eg, getAsk when row/col=0/32, we do want to respond non-erroneously } function _isValidElevation(uint8 value) internal pure returns (bool) { return (value >= uint8(125)) && (value <= uint8(216)); } function _isWater(uint8 col, uint8 row) internal view returns (bool) { return mapElevationRetriever.getElevation(col, row) < uint8(125); } function _boolToUint8(bool value) internal pure returns (uint8) { return value ? uint8(1) : uint8(0); } function _getSurroundingWaterCount(uint8 col, uint8 row) internal view returns (uint8 waterTiles) { require((col >= uint8(1)) && (col <= uint8(31)), "EEXL: Water counting requres col 1-31"); require((row >= uint8(1)) && (row <= uint8(31)), "EEXL: Water counting requres col 1-31"); if (row % uint8(2) == uint8(1)) { waterTiles += _boolToUint8(_isWater(col + uint8(1), row + uint8(1))); // northeast_hex waterTiles += _boolToUint8(_isWater(col + uint8(1), row - uint8(1))); // southeast_hex } else { waterTiles += _boolToUint8(_isWater(col - uint8(1), row - uint8(1))); // southwest_hex waterTiles += _boolToUint8(_isWater(col - uint8(1), row + uint8(1))); // northwest_hex } waterTiles += _boolToUint8(_isWater(col, row - uint8(1))); // southwest_hex or southeast_hex waterTiles += _boolToUint8(_isWater(col, row + uint8(1))); // northwest_hex or northeast_hex waterTiles += _boolToUint8(_isWater(col + uint8(1), row)); // east_hex waterTiles += _boolToUint8(_isWater(col - uint8(1), row)); // west_hex } function getBidders() public view returns (address[] memory) { return bidders; } function getAsk(uint8 col, uint8 row) public view returns (uint128) { return askFor[_getIndex(col, row)]; } // we provide only the land tileIndices to minimize gas usage // should we have this function at all? // function getAsks(uint16[] calldata tileIndices) external view returns (uint128[] memory asks) { // uint256 length = tileIndices.length; // asks = new uint128[](length); // for (uint256 i; i < length; ++i) { // asks[i] = askAt(tileIndices[i]); // } // } function setAsk(uint8 col, uint8 row, uint128 price) external { require(price > 0, "EEXL: removeAsk instead"); require(etheria.getOwner(col, row) == msg.sender, "EEXL: Not tile owner"); uint16 thisIndex = _getIndex(col, row); emit AskCreated(msg.sender, askFor[thisIndex] = price, thisIndex); } function removeAsk(uint8 col, uint8 row) external { require(etheria.getOwner(col, row) == msg.sender, "EEXL: Not tile owner"); uint16 thisIndex = _getIndex(col, row); uint128 price = askFor[thisIndex]; askFor[thisIndex] = 0; emit AskRemoved(msg.sender, price, thisIndex); // price before it was zeroed } function makeBid(uint8 minCol, uint8 maxCol, uint8 minRow, uint8 maxRow, uint8 minEle, uint8 maxEle, uint8 minWat, uint8 maxWat) external payable { require(msg.sender == tx.origin, "EEXL: not EOA"); // (EOA = Externally owned account) // Etheria doesn't allow tile ownership by contracts, this check prevents black-holing require(msg.value <= type(uint128).max, "EEXL: value too high"); require(msg.value >= minBid, "EEXL: req bid amt >= minBid"); require(msg.value >= 0, "EEXL: req bid amt >= 0"); require(bidOf[msg.sender].amount == uint128(0), "EEXL: bid exists, cancel first"); require(_isValidColOrRow(minCol), "EEXL: minCol OOB"); require(_isValidColOrRow(maxCol), "EEXL: maxCol OOB"); require(minCol <= maxCol, "EEXL: req minCol <= maxCol"); require(_isValidColOrRow(minRow), "EEXL: minRow OOB"); require(_isValidColOrRow(maxRow), "EEXL: maxRow OOB"); require(minRow <= maxRow, "EEXL: req minRow <= maxRow"); require(_isValidElevation(minEle), "EEXL: minEle OOB"); // these ele checks prevent water bidding, regardless of row/col require(_isValidElevation(maxEle), "EEXL: maxEle OOB"); require(minEle <= maxEle, "EEXL: req minEle <= maxEle"); require(minWat <= uint8(6), "EEXL: minWat OOB"); require(maxWat <= uint8(6), "EEXL: maxWat OOB"); require(minWat <= maxWat, "EEXL: req minWat <= maxWat"); uint256 biddersArrayLength = bidders.length; require(biddersArrayLength < type(uint64).max, "EEXL: too many bids"); bidOf[msg.sender] = Bid({ amount: uint128(msg.value), minCol: minCol, maxCol: maxCol, minRow: minRow, maxRow: maxRow, minEle: minEle, maxEle: maxEle, minWat: minWat, maxWat: maxWat, biddersIndex: uint64(biddersArrayLength) }); bidders.push(msg.sender); emit BidCreated(msg.sender, uint128(msg.value), minCol, maxCol, minRow, maxRow, minEle, maxEle, minWat, maxWat); } function _deleteBid(address bidder, uint64 biddersIndex) internal { // used by cancelBid and acceptBid address lastBidder = bidders[bidders.length - uint256(1)]; // If bidder not last bidder, overwrite with last bidder if (bidder != lastBidder) { bidders[biddersIndex] = lastBidder; // Overwrite the bidder at the index with the last bidder bidOf[lastBidder].biddersIndex = biddersIndex; // Update the bidder index of the bid of the previously last bidder } delete bidOf[bidder]; bidders.pop(); } function cancelBid() external { // Cancels the bid, getting the bid's amount, which is then added account's pending withdrawal Bid storage bid = bidOf[msg.sender]; uint128 amount = bid.amount; require(amount != uint128(0), "EEXL: No existing bid"); emit BidCancelled(msg.sender, amount, bid.minCol, bid.maxCol, bid.minRow, bid.maxRow, bid.minEle, bid.maxEle, bid.minWat, bid.maxWat); _deleteBid(msg.sender, bid.biddersIndex); pendingWithdrawalOf[msg.sender] += uint256(amount); } function acceptBid(uint8 col, uint8 row, address bidder, uint256 minAmount) external { require(etheria.getOwner(col, row) == msg.sender, "EEXL: Not owner"); // etheria.setOwner will fail below if not owner, making this check unnecessary, but I want this here anyway Bid storage bid = bidOf[bidder]; uint128 amount = bid.amount; require( (amount >= minAmount) && (col >= bid.minCol) && (col <= bid.maxCol) && (row >= bid.minRow) && (row <= bid.maxRow) && (mapElevationRetriever.getElevation(col, row) >= bid.minEle) && (mapElevationRetriever.getElevation(col, row) <= bid.maxEle) && (_getSurroundingWaterCount(col, row) >= bid.minWat) && (_getSurroundingWaterCount(col, row) <= bid.maxWat), "EEXL: tile doesn't meet bid reqs" ); emit BidAccepted(msg.sender, bidder, _getIndex(col, row), amount, bid.minCol, bid.maxCol, bid.minRow, bid.maxRow, bid.minEle, bid.maxEle, bid.minWat, bid.maxWat); _deleteBid(bidder, bid.biddersIndex); etheria.setOwner(col, row, bidder); require(etheria.getOwner(col, row) == bidder, "EEXL: failed setting tile owner"); // ok for require after event emission. Events are technically state changes and atomic as well. uint256 fee = (uint256(amount) * feeRate) / uint256(10_000); collectedFees += fee; pendingWithdrawalOf[msg.sender] += (uint256(amount) - fee); delete askFor[_getIndex(col, row)]; // don't emit AskRemoved here. It's not really a removal } function _withdraw(address account, address payable destination) internal { uint256 amount = pendingWithdrawalOf[account]; require(amount > uint256(0), "EEXL: nothing pending"); pendingWithdrawalOf[account] = uint256(0); _safeTransferETH(destination, amount); emit WithdrawalProcessed(account, destination, amount); } function withdraw(address payable destination) external { _withdraw(msg.sender, destination); } function withdraw() external { _withdraw(msg.sender, payable(msg.sender)); } }