-
Notifications
You must be signed in to change notification settings - Fork 0
/
EtheriaExchangeXL-1pt2_0x111.sol
291 lines (227 loc) · 12.6 KB
/
EtheriaExchangeXL-1pt2_0x111.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// 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));
}
}