1 /*
2 This file is part of solidity.
3
4 solidity is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 solidity is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with solidity. If not, see <http://www.gnu.org/licenses/>.
16 */
17 // SPDX-License-Identifier: GPL-3.0
18 /**
19 * @author Christian <c@ethdev.com>
20 * @date 2015
21 * Tests for a fixed fee registrar contract.
22 */
23
24 #include <test/libsolidity/SolidityExecutionFramework.h>
25 #include <test/contracts/ContractInterface.h>
26 #include <test/EVMHost.h>
27
28 #include <libsolutil/LazyInit.h>
29
30 #include <boost/test/unit_test.hpp>
31
32 #include <string>
33 #include <optional>
34
35 using namespace std;
36 using namespace solidity;
37 using namespace solidity::util;
38 using namespace solidity::test;
39
40 namespace solidity::frontend::test
41 {
42
43 namespace
44 {
45 static char const* registrarCode = R"DELIMITER(
46 pragma solidity >=0.7.0 <0.9.0;
47
48 abstract contract NameRegister {
49 function addr(string memory _name) public virtual view returns (address o_owner);
50 function name(address _owner) public view virtual returns (string memory o_name);
51 }
52
53 abstract contract Registrar is NameRegister {
54 event Changed(string indexed name);
55 event PrimaryChanged(string indexed name, address indexed addr);
56
57 function owner(string memory _name) public view virtual returns (address o_owner);
58 function addr(string memory _name) public virtual override view returns (address o_address);
59 function subRegistrar(string memory _name) public virtual view returns (address o_subRegistrar);
60 function content(string memory _name) public virtual view returns (bytes32 o_content);
61
62 function name(address _owner) public virtual override view returns (string memory o_name);
63 }
64
65 abstract contract AuctionSystem {
66 event AuctionEnded(string indexed _name, address _winner);
67 event NewBid(string indexed _name, address _bidder, uint _value);
68
69 /// Function that is called once an auction ends.
70 function onAuctionEnd(string memory _name) internal virtual;
71
72 function bid(string memory _name, address payable _bidder, uint _value) internal {
73 Auction storage auction = m_auctions[_name];
74 if (auction.endDate > 0 && block.timestamp > auction.endDate)
75 {
76 emit AuctionEnded(_name, auction.highestBidder);
77 onAuctionEnd(_name);
78 delete m_auctions[_name];
79 return;
80 }
81 if (msg.value > auction.highestBid)
82 {
83 // new bid on auction
84 auction.secondHighestBid = auction.highestBid;
85 auction.sumOfBids += _value;
86 auction.highestBid = _value;
87 auction.highestBidder = _bidder;
88 auction.endDate = block.timestamp + c_biddingTime;
89
90 emit NewBid(_name, _bidder, _value);
91 }
92 }
93
94 uint constant c_biddingTime = 7 days;
95
96 struct Auction {
97 address payable highestBidder;
98 uint highestBid;
99 uint secondHighestBid;
100 uint sumOfBids;
101 uint endDate;
102 }
103 mapping(string => Auction) m_auctions;
104 }
105
106 contract GlobalRegistrar is Registrar, AuctionSystem {
107 struct Record {
108 address payable owner;
109 address primary;
110 address subRegistrar;
111 bytes32 content;
112 uint renewalDate;
113 }
114
115 uint constant c_renewalInterval = 365 days;
116 uint constant c_freeBytes = 12;
117
118 constructor() {
119 // TODO: Populate with hall-of-fame.
120 }
121
122 function onAuctionEnd(string memory _name) internal override {
123 Auction storage auction = m_auctions[_name];
124 Record storage record = m_toRecord[_name];
125 address previousOwner = record.owner;
126 record.renewalDate = block.timestamp + c_renewalInterval;
127 record.owner = auction.highestBidder;
128 emit Changed(_name);
129 if (previousOwner != 0x0000000000000000000000000000000000000000) {
130 if (!record.owner.send(auction.sumOfBids - auction.highestBid / 100))
131 revert();
132 } else {
133 if (!auction.highestBidder.send(auction.highestBid - auction.secondHighestBid))
134 revert();
135 }
136 }
137
138 function reserve(string calldata _name) external payable {
139 if (bytes(_name).length == 0)
140 revert();
141 bool needAuction = requiresAuction(_name);
142 if (needAuction)
143 {
144 if (block.timestamp < m_toRecord[_name].renewalDate)
145 revert();
146 bid(_name, payable(msg.sender), msg.value);
147 } else {
148 Record storage record = m_toRecord[_name];
149 if (record.owner != 0x0000000000000000000000000000000000000000)
150 revert();
151 m_toRecord[_name].owner = payable(msg.sender);
152 emit Changed(_name);
153 }
154 }
155
156 function requiresAuction(string memory _name) internal returns (bool) {
157 return bytes(_name).length < c_freeBytes;
158 }
159
160 modifier onlyrecordowner(string memory _name) { if (m_toRecord[_name].owner == msg.sender) _; }
161
162 function transfer(string memory _name, address payable _newOwner) onlyrecordowner(_name) public {
163 m_toRecord[_name].owner = _newOwner;
164 emit Changed(_name);
165 }
166
167 function disown(string memory _name) onlyrecordowner(_name) public {
168 if (stringsEqual(m_toName[m_toRecord[_name].primary], _name))
169 {
170 emit PrimaryChanged(_name, m_toRecord[_name].primary);
171 m_toName[m_toRecord[_name].primary] = "";
172 }
173 delete m_toRecord[_name];
174 emit Changed(_name);
175 }
176
177 function setAddress(string memory _name, address _a, bool _primary) onlyrecordowner(_name) public {
178 m_toRecord[_name].primary = _a;
179 if (_primary)
180 {
181 emit PrimaryChanged(_name, _a);
182 m_toName[_a] = _name;
183 }
184 emit Changed(_name);
185 }
186 function setSubRegistrar(string memory _name, address _registrar) onlyrecordowner(_name) public {
187 m_toRecord[_name].subRegistrar = _registrar;
188 emit Changed(_name);
189 }
190 function setContent(string memory _name, bytes32 _content) onlyrecordowner(_name) public {
191 m_toRecord[_name].content = _content;
192 emit Changed(_name);
193 }
194
195 function stringsEqual(string storage _a, string memory _b) internal returns (bool) {
196 bytes storage a = bytes(_a);
197 bytes memory b = bytes(_b);
198 if (a.length != b.length)
199 return false;
200 // @todo unroll this loop
201 for (uint i = 0; i < a.length; i ++)
202 if (a[i] != b[i])
203 return false;
204 return true;
205 }
206
207 function owner(string memory _name) public override view returns (address) { return m_toRecord[_name].owner; }
208 function addr(string memory _name) public override view returns (address) { return m_toRecord[_name].primary; }
209 function subRegistrar(string memory _name) public override view returns (address) { return m_toRecord[_name].subRegistrar; }
210 function content(string memory _name) public override view returns (bytes32) { return m_toRecord[_name].content; }
211 function name(address _addr) public override view returns (string memory o_name) { return m_toName[_addr]; }
212
213 mapping (address => string) m_toName;
214 mapping (string => Record) m_toRecord;
215 }
216 )DELIMITER";
217
218 static LazyInit<bytes> s_compiledRegistrar;
219
220 class AuctionRegistrarTestFramework: public SolidityExecutionFramework
221 {
222 protected:
deployRegistrar()223 void deployRegistrar()
224 {
225 bytes const& compiled = s_compiledRegistrar.init([&]{
226 return compileContract(registrarCode, "GlobalRegistrar");
227 });
228
229 sendMessage(compiled, true);
230 BOOST_REQUIRE(m_transactionSuccessful);
231 BOOST_REQUIRE(!m_output.empty());
232 }
233
234 class RegistrarInterface: public ContractInterface
235 {
236 public:
RegistrarInterface(SolidityExecutionFramework & _framework)237 RegistrarInterface(SolidityExecutionFramework& _framework): ContractInterface(_framework) {}
reserve(string const & _name)238 void reserve(string const& _name)
239 {
240 callString("reserve", _name);
241 }
owner(string const & _name)242 h160 owner(string const& _name)
243 {
244 return callStringReturnsAddress("owner", _name);
245 }
setAddress(string const & _name,h160 const & _address,bool _primary)246 void setAddress(string const& _name, h160 const& _address, bool _primary)
247 {
248 callStringAddressBool("setAddress", _name, _address, _primary);
249 }
addr(string const & _name)250 h160 addr(string const& _name)
251 {
252 return callStringReturnsAddress("addr", _name);
253 }
name(h160 const & _addr)254 string name(h160 const& _addr)
255 {
256 return callAddressReturnsString("name", _addr);
257 }
setSubRegistrar(string const & _name,h160 const & _address)258 void setSubRegistrar(string const& _name, h160 const& _address)
259 {
260 callStringAddress("setSubRegistrar", _name, _address);
261 }
subRegistrar(string const & _name)262 h160 subRegistrar(string const& _name)
263 {
264 return callStringReturnsAddress("subRegistrar", _name);
265 }
setContent(string const & _name,h256 const & _content)266 void setContent(string const& _name, h256 const& _content)
267 {
268 callStringBytes32("setContent", _name, _content);
269 }
content(string const & _name)270 h256 content(string const& _name)
271 {
272 return callStringReturnsBytes32("content", _name);
273 }
transfer(string const & _name,h160 const & _target)274 void transfer(string const& _name, h160 const& _target)
275 {
276 return callStringAddress("transfer", _name, _target);
277 }
disown(string const & _name)278 void disown(string const& _name)
279 {
280 return callString("disown", _name);
281 }
282 };
283
284 int64_t const m_biddingTime = 7 * 24 * 3600;
285 };
286
287 }
288
289 /// This is a test suite that tests optimised code!
BOOST_FIXTURE_TEST_SUITE(SolidityAuctionRegistrar,AuctionRegistrarTestFramework)290 BOOST_FIXTURE_TEST_SUITE(SolidityAuctionRegistrar, AuctionRegistrarTestFramework)
291
292 BOOST_AUTO_TEST_CASE(creation)
293 {
294 deployRegistrar();
295 }
296
BOOST_AUTO_TEST_CASE(reserve)297 BOOST_AUTO_TEST_CASE(reserve)
298 {
299 // Test that reserving works for long strings
300 deployRegistrar();
301 vector<string> names{"abcabcabcabcabc", "defdefdefdefdef", "ghighighighighighighighighighighighighighighi"};
302
303 RegistrarInterface registrar(*this);
304
305 // should not work
306 registrar.reserve("");
307 BOOST_CHECK_EQUAL(registrar.owner(""), h160{});
308
309 for (auto const& name: names)
310 {
311 registrar.reserve(name);
312 BOOST_CHECK_EQUAL(registrar.owner(name), m_sender);
313 }
314 }
315
BOOST_AUTO_TEST_CASE(double_reserve_long)316 BOOST_AUTO_TEST_CASE(double_reserve_long)
317 {
318 // Test that it is not possible to re-reserve from a different address.
319 deployRegistrar();
320 string name = "abcabcabcabcabcabcabcabcabcabca";
321 RegistrarInterface registrar(*this);
322 registrar.reserve(name);
323 BOOST_CHECK_EQUAL(registrar.owner(name), m_sender);
324
325 sendEther(account(1), u256(10) * ether);
326 m_sender = account(1);
327 registrar.reserve(name);
328 BOOST_CHECK_EQUAL(registrar.owner(name), account(0));
329 }
330
BOOST_AUTO_TEST_CASE(properties)331 BOOST_AUTO_TEST_CASE(properties)
332 {
333 // Test setting and retrieving the various properties works.
334 deployRegistrar();
335 RegistrarInterface registrar(*this);
336 string names[] = {"abcaeouoeuaoeuaoeu", "defncboagufra,fui", "ghagpyajfbcuajouhaeoi"};
337 size_t addr = 0x9872543;
338 size_t count = 1;
339 for (string const& name: names)
340 {
341 m_sender = account(0);
342 sendEther(account(count), u256(20) * ether);
343 m_sender = account(count);
344 auto sender = m_sender;
345 addr += count;
346 // setting by sender works
347 registrar.reserve(name);
348 BOOST_CHECK_EQUAL(registrar.owner(name), sender);
349 registrar.setAddress(name, h160(addr), true);
350 BOOST_CHECK_EQUAL(registrar.addr(name), h160(addr));
351 registrar.setSubRegistrar(name, h160(addr + 20));
352 BOOST_CHECK_EQUAL(registrar.subRegistrar(name), h160(addr + 20));
353 registrar.setContent(name, h256(u256(addr + 90)));
354 BOOST_CHECK_EQUAL(registrar.content(name), h256(u256(addr + 90)));
355
356 // but not by someone else
357 m_sender = account(count - 1);
358 BOOST_CHECK_EQUAL(registrar.owner(name), sender);
359 registrar.setAddress(name, h160(addr + 1), true);
360 BOOST_CHECK_EQUAL(registrar.addr(name), h160(addr));
361 registrar.setSubRegistrar(name, h160(addr + 20 + 1));
362 BOOST_CHECK_EQUAL(registrar.subRegistrar(name), h160(addr + 20));
363 registrar.setContent(name, h256(u256(addr + 90 + 1)));
364 BOOST_CHECK_EQUAL(registrar.content(name), h256(u256(addr + 90)));
365 count++;
366 }
367 }
368
BOOST_AUTO_TEST_CASE(transfer)369 BOOST_AUTO_TEST_CASE(transfer)
370 {
371 deployRegistrar();
372 string name = "abcaoeguaoucaeoduceo";
373 RegistrarInterface registrar(*this);
374 registrar.reserve(name);
375 registrar.setContent(name, h256(u256(123)));
376 registrar.transfer(name, h160(555));
377 BOOST_CHECK_EQUAL(registrar.owner(name), h160(555));
378 BOOST_CHECK_EQUAL(registrar.content(name), h256(u256(123)));
379 }
380
BOOST_AUTO_TEST_CASE(disown)381 BOOST_AUTO_TEST_CASE(disown)
382 {
383 deployRegistrar();
384 string name = "abcaoeguaoucaeoduceo";
385
386 RegistrarInterface registrar(*this);
387 registrar.reserve(name);
388 registrar.setContent(name, h256(u256(123)));
389 registrar.setAddress(name, h160(124), true);
390 registrar.setSubRegistrar(name, h160(125));
391 BOOST_CHECK_EQUAL(registrar.name(h160(124)), name);
392
393 // someone else tries disowning
394 sendEther(account(1), u256(10) * ether);
395 m_sender = account(1);
396 registrar.disown(name);
397 BOOST_CHECK_EQUAL(registrar.owner(name), account(0));
398
399 m_sender = account(0);
400 registrar.disown(name);
401 BOOST_CHECK_EQUAL(registrar.owner(name), h160());
402 BOOST_CHECK_EQUAL(registrar.addr(name), h160());
403 BOOST_CHECK_EQUAL(registrar.subRegistrar(name), h160());
404 BOOST_CHECK_EQUAL(registrar.content(name), h256());
405 BOOST_CHECK_EQUAL(registrar.name(h160(124)), "");
406 }
407
BOOST_AUTO_TEST_CASE(auction_simple)408 BOOST_AUTO_TEST_CASE(auction_simple)
409 {
410 deployRegistrar();
411 string name = "x";
412
413 RegistrarInterface registrar(*this);
414 // initiate auction
415 registrar.setNextValue(8);
416 registrar.reserve(name);
417 BOOST_CHECK_EQUAL(registrar.owner(name), h160());
418 // "wait" until auction end
419
420 m_evmcHost->tx_context.block_timestamp += m_biddingTime + 10;
421 // trigger auction again
422 registrar.reserve(name);
423 BOOST_CHECK_EQUAL(registrar.owner(name), m_sender);
424 }
425
BOOST_AUTO_TEST_CASE(auction_bidding)426 BOOST_AUTO_TEST_CASE(auction_bidding)
427 {
428 deployRegistrar();
429 string name = "x";
430
431 unsigned startTime = 0x776347e2;
432 m_evmcHost->tx_context.block_timestamp = startTime;
433
434 RegistrarInterface registrar(*this);
435 // initiate auction
436 registrar.setNextValue(8);
437 registrar.reserve(name);
438 BOOST_CHECK_EQUAL(registrar.owner(name), h160());
439 // overbid self
440 m_evmcHost->tx_context.block_timestamp = startTime + m_biddingTime - 10;
441 registrar.setNextValue(12);
442 registrar.reserve(name);
443 // another bid by someone else
444 sendEther(account(1), 10 * ether);
445 m_sender = account(1);
446 m_evmcHost->tx_context.block_timestamp = startTime + 2 * m_biddingTime - 50;
447 registrar.setNextValue(13);
448 registrar.reserve(name);
449 BOOST_CHECK_EQUAL(registrar.owner(name), h160());
450 // end auction by first bidder (which is not highest) trying to overbid again (too late)
451 m_sender = account(0);
452 m_evmcHost->tx_context.block_timestamp = startTime + 4 * m_biddingTime;
453 registrar.setNextValue(20);
454 registrar.reserve(name);
455 BOOST_CHECK_EQUAL(registrar.owner(name), account(1));
456 }
457
458 BOOST_AUTO_TEST_SUITE_END()
459
460 } // end namespaces
461