Entropy.t.sol 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. // SPDX-License-Identifier: Apache 2
  2. pragma solidity ^0.8.0;
  3. import "forge-std/Test.sol";
  4. import "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol";
  5. import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
  6. import "./utils/EntropyTestUtils.t.sol";
  7. import "../contracts/entropy/EntropyUpgradable.sol";
  8. // TODO
  9. // - fuzz test?
  10. contract EntropyTest is Test, EntropyTestUtils {
  11. ERC1967Proxy public proxy;
  12. EntropyUpgradable public random;
  13. uint128 pythFeeInWei = 7;
  14. address public provider1 = address(1);
  15. bytes32[] provider1Proofs;
  16. uint128 provider1FeeInWei = 8;
  17. uint64 provider1ChainLength = 100;
  18. bytes provider1Uri = bytes("https://foo.com");
  19. bytes provider1CommitmentMetadata = hex"0100";
  20. address public provider2 = address(2);
  21. bytes32[] provider2Proofs;
  22. uint128 provider2FeeInWei = 20;
  23. bytes provider2Uri = bytes("https://bar.com");
  24. address public user1 = address(3);
  25. address public user2 = address(4);
  26. address public unregisteredProvider = address(7);
  27. uint128 MAX_UINT128 = 2 ** 128 - 1;
  28. bytes32 ALL_ZEROS = bytes32(uint256(0));
  29. address public owner = address(8);
  30. address public admin = address(9);
  31. address public admin2 = address(10);
  32. function setUp() public {
  33. EntropyUpgradable _random = new EntropyUpgradable();
  34. // deploy proxy contract and point it to implementation
  35. proxy = new ERC1967Proxy(address(_random), "");
  36. // wrap in ABI to support easier calls
  37. random = EntropyUpgradable(address(proxy));
  38. random.initialize(owner, admin, pythFeeInWei, provider1, false);
  39. bytes32[] memory hashChain1 = generateHashChain(
  40. provider1,
  41. 0,
  42. provider1ChainLength
  43. );
  44. provider1Proofs = hashChain1;
  45. vm.prank(provider1);
  46. random.register(
  47. provider1FeeInWei,
  48. provider1Proofs[0],
  49. provider1CommitmentMetadata,
  50. provider1ChainLength,
  51. provider1Uri
  52. );
  53. bytes32[] memory hashChain2 = generateHashChain(provider2, 0, 100);
  54. provider2Proofs = hashChain2;
  55. vm.prank(provider2);
  56. random.register(
  57. provider2FeeInWei,
  58. provider2Proofs[0],
  59. hex"0200",
  60. 100,
  61. provider2Uri
  62. );
  63. }
  64. // Test helper method for requesting a random value as user from provider.
  65. function request(
  66. address user,
  67. address provider,
  68. uint randomNumber,
  69. bool useBlockhash
  70. ) public returns (uint64 sequenceNumber) {
  71. sequenceNumber = requestWithFee(
  72. user,
  73. random.getFee(provider),
  74. provider,
  75. randomNumber,
  76. useBlockhash
  77. );
  78. }
  79. function requestWithFee(
  80. address user,
  81. uint fee,
  82. address provider,
  83. uint randomNumber,
  84. bool useBlockhash
  85. ) public returns (uint64 sequenceNumber) {
  86. vm.deal(user, fee);
  87. vm.startPrank(user);
  88. sequenceNumber = random.request{value: fee}(
  89. provider,
  90. random.constructUserCommitment(bytes32(randomNumber)),
  91. useBlockhash
  92. );
  93. vm.stopPrank();
  94. }
  95. function assertRequestReverts(
  96. uint fee,
  97. address provider,
  98. uint randomNumber,
  99. bool useBlockhash
  100. ) public {
  101. // Note: for some reason vm.expectRevert() won't catch errors from the request function (?!),
  102. // even though they definitely revert. Use a try/catch instead for the moment, though the try/catch
  103. // doesn't let you simulate the msg.sender. However, it's fine if the msg.sender is the test contract.
  104. bool requestSucceeds = false;
  105. try
  106. random.request{value: fee}(
  107. provider,
  108. random.constructUserCommitment(bytes32(uint256(randomNumber))),
  109. useBlockhash
  110. )
  111. {
  112. requestSucceeds = true;
  113. } catch {
  114. requestSucceeds = false;
  115. }
  116. assert(!requestSucceeds);
  117. }
  118. function assertRevealSucceeds(
  119. address user,
  120. address provider,
  121. uint64 sequenceNumber,
  122. uint userRandom,
  123. bytes32 providerRevelation,
  124. bytes32 hash
  125. ) public {
  126. vm.prank(user);
  127. bytes32 randomNumber = random.reveal(
  128. provider,
  129. sequenceNumber,
  130. bytes32(userRandom),
  131. providerRevelation
  132. );
  133. assertEq(
  134. randomNumber,
  135. random.combineRandomValues(
  136. bytes32(userRandom),
  137. providerRevelation,
  138. hash
  139. )
  140. );
  141. }
  142. function assertRevealReverts(
  143. address user,
  144. address provider,
  145. uint64 sequenceNumber,
  146. uint userRandom,
  147. bytes32 providerRevelation
  148. ) public {
  149. vm.startPrank(user);
  150. vm.expectRevert();
  151. random.reveal(
  152. provider,
  153. sequenceNumber,
  154. bytes32(uint256(userRandom)),
  155. providerRevelation
  156. );
  157. vm.stopPrank();
  158. }
  159. function assertInvariants() public {
  160. uint expectedBalance = random
  161. .getProviderInfo(provider1)
  162. .accruedFeesInWei +
  163. random.getProviderInfo(provider2).accruedFeesInWei +
  164. random.getAccruedPythFees();
  165. assertEq(address(random).balance, expectedBalance);
  166. EntropyStructs.ProviderInfo memory info1 = random.getProviderInfo(
  167. provider1
  168. );
  169. assert(
  170. info1.originalCommitmentSequenceNumber <=
  171. info1.currentCommitmentSequenceNumber
  172. );
  173. assert(info1.currentCommitmentSequenceNumber < info1.sequenceNumber);
  174. assert(info1.sequenceNumber <= info1.endSequenceNumber);
  175. EntropyStructs.ProviderInfo memory info2 = random.getProviderInfo(
  176. provider2
  177. );
  178. assert(
  179. info2.originalCommitmentSequenceNumber <=
  180. info2.currentCommitmentSequenceNumber
  181. );
  182. assert(info2.sequenceNumber > info2.currentCommitmentSequenceNumber);
  183. assert(info2.sequenceNumber <= info2.endSequenceNumber);
  184. }
  185. function testBasicFlow() public {
  186. vm.roll(17);
  187. uint64 sequenceNumber = request(user2, provider1, 42, false);
  188. assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 17);
  189. assertEq(
  190. random.getRequest(provider1, sequenceNumber).useBlockhash,
  191. false
  192. );
  193. assertRevealSucceeds(
  194. user2,
  195. provider1,
  196. sequenceNumber,
  197. 42,
  198. provider1Proofs[sequenceNumber],
  199. ALL_ZEROS
  200. );
  201. // You can only reveal the random number once. This isn't a feature of the contract per se, but it is
  202. // the expected behavior.
  203. assertRevealReverts(
  204. user2,
  205. provider1,
  206. sequenceNumber,
  207. 42,
  208. provider1Proofs[sequenceNumber]
  209. );
  210. }
  211. function testDefaultProvider() public {
  212. vm.roll(20);
  213. uint64 sequenceNumber = request(
  214. user2,
  215. random.getDefaultProvider(),
  216. 42,
  217. false
  218. );
  219. assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 20);
  220. assertEq(
  221. random.getRequest(provider1, sequenceNumber).useBlockhash,
  222. false
  223. );
  224. assertRevealReverts(
  225. user2,
  226. random.getDefaultProvider(),
  227. sequenceNumber,
  228. 42,
  229. provider2Proofs[sequenceNumber]
  230. );
  231. assertRevealSucceeds(
  232. user2,
  233. random.getDefaultProvider(),
  234. sequenceNumber,
  235. 42,
  236. provider1Proofs[sequenceNumber],
  237. ALL_ZEROS
  238. );
  239. }
  240. function testNoSuchProvider() public {
  241. assertRequestReverts(10000000, unregisteredProvider, 42, false);
  242. }
  243. function testAuthorization() public {
  244. uint64 sequenceNumber = request(user2, provider1, 42, false);
  245. assertEq(random.getRequest(provider1, sequenceNumber).requester, user2);
  246. // user1 not authorized, must be user2.
  247. assertRevealReverts(
  248. user1,
  249. provider1,
  250. sequenceNumber,
  251. 42,
  252. provider1Proofs[sequenceNumber]
  253. );
  254. assertRevealSucceeds(
  255. user2,
  256. provider1,
  257. sequenceNumber,
  258. 42,
  259. provider1Proofs[sequenceNumber],
  260. ALL_ZEROS
  261. );
  262. }
  263. function testAdversarialReveal() public {
  264. uint64 sequenceNumber = request(user2, provider1, 42, false);
  265. // test revealing with the wrong hashes in the same chain
  266. for (uint256 i = 0; i < 10; i++) {
  267. if (i != sequenceNumber) {
  268. assertRevealReverts(
  269. user2,
  270. provider1,
  271. sequenceNumber,
  272. 42,
  273. provider1Proofs[i]
  274. );
  275. }
  276. }
  277. // test revealing with the wrong user revealed value.
  278. for (uint256 i = 0; i < 42; i++) {
  279. assertRevealReverts(
  280. user2,
  281. provider1,
  282. sequenceNumber,
  283. i,
  284. provider1Proofs[sequenceNumber]
  285. );
  286. }
  287. // test revealing sequence numbers that haven't been requested yet.
  288. for (uint64 i = sequenceNumber + 1; i < sequenceNumber + 3; i++) {
  289. assertRevealReverts(
  290. user2,
  291. provider1,
  292. i,
  293. 42,
  294. provider1Proofs[sequenceNumber]
  295. );
  296. assertRevealReverts(user2, provider1, i, 42, provider1Proofs[i]);
  297. }
  298. }
  299. function testConcurrentRequests() public {
  300. uint64 s1 = request(user1, provider1, 1, false);
  301. uint64 s2 = request(user2, provider1, 2, false);
  302. uint64 s3 = request(user1, provider1, 3, false);
  303. uint64 s4 = request(user1, provider1, 4, false);
  304. assertRevealSucceeds(
  305. user1,
  306. provider1,
  307. s3,
  308. 3,
  309. provider1Proofs[s3],
  310. ALL_ZEROS
  311. );
  312. assertInvariants();
  313. uint64 s5 = request(user1, provider1, 5, false);
  314. assertRevealSucceeds(
  315. user1,
  316. provider1,
  317. s4,
  318. 4,
  319. provider1Proofs[s4],
  320. ALL_ZEROS
  321. );
  322. assertInvariants();
  323. assertRevealSucceeds(
  324. user1,
  325. provider1,
  326. s1,
  327. 1,
  328. provider1Proofs[s1],
  329. ALL_ZEROS
  330. );
  331. assertInvariants();
  332. assertRevealSucceeds(
  333. user2,
  334. provider1,
  335. s2,
  336. 2,
  337. provider1Proofs[s2],
  338. ALL_ZEROS
  339. );
  340. assertInvariants();
  341. assertRevealSucceeds(
  342. user1,
  343. provider1,
  344. s5,
  345. 5,
  346. provider1Proofs[s5],
  347. ALL_ZEROS
  348. );
  349. assertInvariants();
  350. }
  351. function testBlockhash() public {
  352. vm.roll(1234);
  353. uint64 sequenceNumber = request(user2, provider1, 42, true);
  354. assertEq(
  355. random.getRequest(provider1, sequenceNumber).blockNumber,
  356. 1234
  357. );
  358. assertEq(
  359. random.getRequest(provider1, sequenceNumber).useBlockhash,
  360. true
  361. );
  362. assertRevealSucceeds(
  363. user2,
  364. provider1,
  365. sequenceNumber,
  366. 42,
  367. provider1Proofs[sequenceNumber],
  368. blockhash(1234)
  369. );
  370. }
  371. function testProviderCommitmentRotation() public {
  372. uint userRandom = 42;
  373. uint64 sequenceNumber1 = request(user2, provider1, userRandom, false);
  374. uint64 sequenceNumber2 = request(user2, provider1, userRandom, false);
  375. assertInvariants();
  376. uint64 newHashChainOffset = sequenceNumber2 + 1;
  377. bytes32[] memory newHashChain = generateHashChain(
  378. provider1,
  379. newHashChainOffset,
  380. 10
  381. );
  382. vm.prank(provider1);
  383. random.register(
  384. provider1FeeInWei,
  385. newHashChain[0],
  386. hex"0100",
  387. 10,
  388. provider1Uri
  389. );
  390. assertInvariants();
  391. EntropyStructs.ProviderInfo memory info1 = random.getProviderInfo(
  392. provider1
  393. );
  394. assertEq(info1.endSequenceNumber, newHashChainOffset + 10);
  395. uint64 sequenceNumber3 = request(user2, provider1, 42, false);
  396. // Rotating the provider key uses a sequence number
  397. assertEq(sequenceNumber3, sequenceNumber2 + 2);
  398. // Requests that were in-flight at the time of rotation use the commitment from the time of request
  399. for (uint256 i = 0; i < 10; i++) {
  400. assertRevealReverts(
  401. user2,
  402. provider1,
  403. sequenceNumber1,
  404. userRandom,
  405. newHashChain[i]
  406. );
  407. }
  408. assertRevealSucceeds(
  409. user2,
  410. provider1,
  411. sequenceNumber1,
  412. userRandom,
  413. provider1Proofs[sequenceNumber1],
  414. ALL_ZEROS
  415. );
  416. assertInvariants();
  417. // Requests after the rotation use the new commitment
  418. assertRevealReverts(
  419. user2,
  420. provider1,
  421. sequenceNumber3,
  422. userRandom,
  423. provider1Proofs[sequenceNumber3]
  424. );
  425. assertRevealSucceeds(
  426. user2,
  427. provider1,
  428. sequenceNumber3,
  429. userRandom,
  430. newHashChain[sequenceNumber3 - newHashChainOffset],
  431. ALL_ZEROS
  432. );
  433. assertInvariants();
  434. }
  435. function testOutOfRandomness() public {
  436. // Should be able to request chainLength - 1 random numbers successfully.
  437. for (uint64 i = 0; i < provider1ChainLength - 1; i++) {
  438. request(user1, provider1, i, false);
  439. }
  440. assertRequestReverts(
  441. random.getFee(provider1),
  442. provider1,
  443. provider1ChainLength - 1,
  444. false
  445. );
  446. }
  447. function testGetFee() public {
  448. assertEq(random.getFee(provider1), pythFeeInWei + provider1FeeInWei);
  449. assertEq(random.getFee(provider2), pythFeeInWei + provider2FeeInWei);
  450. // Requesting the fee for a nonexistent provider returns pythFeeInWei. This isn't necessarily desirable behavior,
  451. // but it's unlikely to cause a problem.
  452. assertEq(random.getFee(unregisteredProvider), pythFeeInWei);
  453. // Check that overflowing the fee arithmetic causes the transaction to revert.
  454. vm.prank(provider1);
  455. random.register(
  456. MAX_UINT128,
  457. provider1Proofs[0],
  458. hex"0100",
  459. 100,
  460. provider1Uri
  461. );
  462. vm.expectRevert();
  463. random.getFee(provider1);
  464. }
  465. function testOverflow() public {
  466. // msg.value overflows the uint128 fee variable
  467. assertRequestReverts(2 ** 128, provider1, 42, false);
  468. // block number is too large
  469. vm.roll(2 ** 96);
  470. assertRequestReverts(
  471. pythFeeInWei + provider1FeeInWei,
  472. provider1,
  473. 42,
  474. true
  475. );
  476. }
  477. function testFees() public {
  478. // Insufficient fees causes a revert
  479. assertRequestReverts(0, provider1, 42, false);
  480. assertRequestReverts(
  481. pythFeeInWei + provider1FeeInWei - 1,
  482. provider1,
  483. 42,
  484. false
  485. );
  486. assertRequestReverts(0, provider2, 42, false);
  487. assertRequestReverts(
  488. pythFeeInWei + provider2FeeInWei - 1,
  489. provider2,
  490. 42,
  491. false
  492. );
  493. // Accrue some fees for both providers
  494. for (uint i = 0; i < 3; i++) {
  495. request(user2, provider1, 42, false);
  496. }
  497. request(user2, provider2, 42, false);
  498. // this call overpays for the random number
  499. requestWithFee(
  500. user2,
  501. pythFeeInWei + provider2FeeInWei + 10000,
  502. provider2,
  503. 42,
  504. false
  505. );
  506. assertEq(
  507. random.getProviderInfo(provider1).accruedFeesInWei,
  508. provider1FeeInWei * 3
  509. );
  510. assertEq(
  511. random.getProviderInfo(provider2).accruedFeesInWei,
  512. provider2FeeInWei * 2
  513. );
  514. assertEq(random.getAccruedPythFees(), pythFeeInWei * 5 + 10000);
  515. assertInvariants();
  516. // Reregistering updates the required fees
  517. vm.prank(provider1);
  518. random.register(
  519. 12345,
  520. provider1Proofs[0],
  521. hex"0100",
  522. 100,
  523. provider1Uri
  524. );
  525. assertRequestReverts(pythFeeInWei + 12345 - 1, provider1, 42, false);
  526. requestWithFee(user2, pythFeeInWei + 12345, provider1, 42, false);
  527. uint128 providerOneBalance = provider1FeeInWei * 3 + 12345;
  528. assertEq(
  529. random.getProviderInfo(provider1).accruedFeesInWei,
  530. providerOneBalance
  531. );
  532. assertInvariants();
  533. vm.prank(unregisteredProvider);
  534. vm.expectRevert();
  535. random.withdraw(1000);
  536. vm.prank(provider1);
  537. random.withdraw(1000);
  538. assertEq(
  539. random.getProviderInfo(provider1).accruedFeesInWei,
  540. providerOneBalance - 1000
  541. );
  542. assertInvariants();
  543. vm.prank(provider1);
  544. vm.expectRevert();
  545. random.withdraw(providerOneBalance);
  546. }
  547. function testGetProviderInfo() public {
  548. EntropyStructs.ProviderInfo memory providerInfo1 = random
  549. .getProviderInfo(provider1);
  550. // These two fields aren't used by the Entropy contract itself -- they're just convenient info to store
  551. // on-chain -- so they aren't tested in the other tests.
  552. assertEq(providerInfo1.uri, provider1Uri);
  553. assertEq(providerInfo1.commitmentMetadata, provider1CommitmentMetadata);
  554. }
  555. }