Entropy.t.sol 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  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. uint64 sequenceNumber = request(user2, provider1, 42, false);
  187. assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 0);
  188. assertRevealSucceeds(
  189. user2,
  190. provider1,
  191. sequenceNumber,
  192. 42,
  193. provider1Proofs[sequenceNumber],
  194. ALL_ZEROS
  195. );
  196. // You can only reveal the random number once. This isn't a feature of the contract per se, but it is
  197. // the expected behavior.
  198. assertRevealReverts(
  199. user2,
  200. provider1,
  201. sequenceNumber,
  202. 42,
  203. provider1Proofs[sequenceNumber]
  204. );
  205. }
  206. function testDefaultProvider() public {
  207. uint64 sequenceNumber = request(
  208. user2,
  209. random.getDefaultProvider(),
  210. 42,
  211. false
  212. );
  213. assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 0);
  214. assertRevealReverts(
  215. user2,
  216. random.getDefaultProvider(),
  217. sequenceNumber,
  218. 42,
  219. provider2Proofs[sequenceNumber]
  220. );
  221. assertRevealSucceeds(
  222. user2,
  223. random.getDefaultProvider(),
  224. sequenceNumber,
  225. 42,
  226. provider1Proofs[sequenceNumber],
  227. ALL_ZEROS
  228. );
  229. }
  230. function testNoSuchProvider() public {
  231. assertRequestReverts(10000000, unregisteredProvider, 42, false);
  232. }
  233. function testAuthorization() public {
  234. uint64 sequenceNumber = request(user2, provider1, 42, false);
  235. assertEq(random.getRequest(provider1, sequenceNumber).requester, user2);
  236. // user1 not authorized, must be user2.
  237. assertRevealReverts(
  238. user1,
  239. provider1,
  240. sequenceNumber,
  241. 42,
  242. provider1Proofs[sequenceNumber]
  243. );
  244. assertRevealSucceeds(
  245. user2,
  246. provider1,
  247. sequenceNumber,
  248. 42,
  249. provider1Proofs[sequenceNumber],
  250. ALL_ZEROS
  251. );
  252. }
  253. function testAdversarialReveal() public {
  254. uint64 sequenceNumber = request(user2, provider1, 42, false);
  255. // test revealing with the wrong hashes in the same chain
  256. for (uint256 i = 0; i < 10; i++) {
  257. if (i != sequenceNumber) {
  258. assertRevealReverts(
  259. user2,
  260. provider1,
  261. sequenceNumber,
  262. 42,
  263. provider1Proofs[i]
  264. );
  265. }
  266. }
  267. // test revealing with the wrong user revealed value.
  268. for (uint256 i = 0; i < 42; i++) {
  269. assertRevealReverts(
  270. user2,
  271. provider1,
  272. sequenceNumber,
  273. i,
  274. provider1Proofs[sequenceNumber]
  275. );
  276. }
  277. // test revealing sequence numbers that haven't been requested yet.
  278. for (uint64 i = sequenceNumber + 1; i < sequenceNumber + 3; i++) {
  279. assertRevealReverts(
  280. user2,
  281. provider1,
  282. i,
  283. 42,
  284. provider1Proofs[sequenceNumber]
  285. );
  286. assertRevealReverts(user2, provider1, i, 42, provider1Proofs[i]);
  287. }
  288. }
  289. function testConcurrentRequests() public {
  290. uint64 s1 = request(user1, provider1, 1, false);
  291. uint64 s2 = request(user2, provider1, 2, false);
  292. uint64 s3 = request(user1, provider1, 3, false);
  293. uint64 s4 = request(user1, provider1, 4, false);
  294. assertRevealSucceeds(
  295. user1,
  296. provider1,
  297. s3,
  298. 3,
  299. provider1Proofs[s3],
  300. ALL_ZEROS
  301. );
  302. assertInvariants();
  303. uint64 s5 = request(user1, provider1, 5, false);
  304. assertRevealSucceeds(
  305. user1,
  306. provider1,
  307. s4,
  308. 4,
  309. provider1Proofs[s4],
  310. ALL_ZEROS
  311. );
  312. assertInvariants();
  313. assertRevealSucceeds(
  314. user1,
  315. provider1,
  316. s1,
  317. 1,
  318. provider1Proofs[s1],
  319. ALL_ZEROS
  320. );
  321. assertInvariants();
  322. assertRevealSucceeds(
  323. user2,
  324. provider1,
  325. s2,
  326. 2,
  327. provider1Proofs[s2],
  328. ALL_ZEROS
  329. );
  330. assertInvariants();
  331. assertRevealSucceeds(
  332. user1,
  333. provider1,
  334. s5,
  335. 5,
  336. provider1Proofs[s5],
  337. ALL_ZEROS
  338. );
  339. assertInvariants();
  340. }
  341. function testBlockhash() public {
  342. vm.roll(1234);
  343. uint64 sequenceNumber = request(user2, provider1, 42, true);
  344. assertEq(
  345. random.getRequest(provider1, sequenceNumber).blockNumber,
  346. 1234
  347. );
  348. assertRevealSucceeds(
  349. user2,
  350. provider1,
  351. sequenceNumber,
  352. 42,
  353. provider1Proofs[sequenceNumber],
  354. blockhash(1234)
  355. );
  356. }
  357. function testProviderCommitmentRotation() public {
  358. uint userRandom = 42;
  359. uint64 sequenceNumber1 = request(user2, provider1, userRandom, false);
  360. uint64 sequenceNumber2 = request(user2, provider1, userRandom, false);
  361. assertInvariants();
  362. uint64 newHashChainOffset = sequenceNumber2 + 1;
  363. bytes32[] memory newHashChain = generateHashChain(
  364. provider1,
  365. newHashChainOffset,
  366. 10
  367. );
  368. vm.prank(provider1);
  369. random.register(
  370. provider1FeeInWei,
  371. newHashChain[0],
  372. hex"0100",
  373. 10,
  374. provider1Uri
  375. );
  376. assertInvariants();
  377. EntropyStructs.ProviderInfo memory info1 = random.getProviderInfo(
  378. provider1
  379. );
  380. assertEq(info1.endSequenceNumber, newHashChainOffset + 10);
  381. uint64 sequenceNumber3 = request(user2, provider1, 42, false);
  382. // Rotating the provider key uses a sequence number
  383. assertEq(sequenceNumber3, sequenceNumber2 + 2);
  384. // Requests that were in-flight at the time of rotation use the commitment from the time of request
  385. for (uint256 i = 0; i < 10; i++) {
  386. assertRevealReverts(
  387. user2,
  388. provider1,
  389. sequenceNumber1,
  390. userRandom,
  391. newHashChain[i]
  392. );
  393. }
  394. assertRevealSucceeds(
  395. user2,
  396. provider1,
  397. sequenceNumber1,
  398. userRandom,
  399. provider1Proofs[sequenceNumber1],
  400. ALL_ZEROS
  401. );
  402. assertInvariants();
  403. // Requests after the rotation use the new commitment
  404. assertRevealReverts(
  405. user2,
  406. provider1,
  407. sequenceNumber3,
  408. userRandom,
  409. provider1Proofs[sequenceNumber3]
  410. );
  411. assertRevealSucceeds(
  412. user2,
  413. provider1,
  414. sequenceNumber3,
  415. userRandom,
  416. newHashChain[sequenceNumber3 - newHashChainOffset],
  417. ALL_ZEROS
  418. );
  419. assertInvariants();
  420. }
  421. function testOutOfRandomness() public {
  422. // Should be able to request chainLength - 1 random numbers successfully.
  423. for (uint64 i = 0; i < provider1ChainLength - 1; i++) {
  424. request(user1, provider1, i, false);
  425. }
  426. assertRequestReverts(
  427. random.getFee(provider1),
  428. provider1,
  429. provider1ChainLength - 1,
  430. false
  431. );
  432. }
  433. function testGetFee() public {
  434. assertEq(random.getFee(provider1), pythFeeInWei + provider1FeeInWei);
  435. assertEq(random.getFee(provider2), pythFeeInWei + provider2FeeInWei);
  436. // Requesting the fee for a nonexistent provider returns pythFeeInWei. This isn't necessarily desirable behavior,
  437. // but it's unlikely to cause a problem.
  438. assertEq(random.getFee(unregisteredProvider), pythFeeInWei);
  439. // Check that overflowing the fee arithmetic causes the transaction to revert.
  440. vm.prank(provider1);
  441. random.register(
  442. MAX_UINT128,
  443. provider1Proofs[0],
  444. hex"0100",
  445. 100,
  446. provider1Uri
  447. );
  448. vm.expectRevert();
  449. random.getFee(provider1);
  450. }
  451. function testOverflow() public {
  452. // msg.value overflows the uint128 fee variable
  453. assertRequestReverts(2 ** 128, provider1, 42, false);
  454. // block number is too large
  455. vm.roll(2 ** 96);
  456. assertRequestReverts(
  457. pythFeeInWei + provider1FeeInWei,
  458. provider1,
  459. 42,
  460. true
  461. );
  462. }
  463. function testFees() public {
  464. // Insufficient fees causes a revert
  465. assertRequestReverts(0, provider1, 42, false);
  466. assertRequestReverts(
  467. pythFeeInWei + provider1FeeInWei - 1,
  468. provider1,
  469. 42,
  470. false
  471. );
  472. assertRequestReverts(0, provider2, 42, false);
  473. assertRequestReverts(
  474. pythFeeInWei + provider2FeeInWei - 1,
  475. provider2,
  476. 42,
  477. false
  478. );
  479. // Accrue some fees for both providers
  480. for (uint i = 0; i < 3; i++) {
  481. request(user2, provider1, 42, false);
  482. }
  483. request(user2, provider2, 42, false);
  484. // this call overpays for the random number
  485. requestWithFee(
  486. user2,
  487. pythFeeInWei + provider2FeeInWei + 10000,
  488. provider2,
  489. 42,
  490. false
  491. );
  492. assertEq(
  493. random.getProviderInfo(provider1).accruedFeesInWei,
  494. provider1FeeInWei * 3
  495. );
  496. assertEq(
  497. random.getProviderInfo(provider2).accruedFeesInWei,
  498. provider2FeeInWei * 2
  499. );
  500. assertEq(random.getAccruedPythFees(), pythFeeInWei * 5 + 10000);
  501. assertInvariants();
  502. // Reregistering updates the required fees
  503. vm.prank(provider1);
  504. random.register(
  505. 12345,
  506. provider1Proofs[0],
  507. hex"0100",
  508. 100,
  509. provider1Uri
  510. );
  511. assertRequestReverts(pythFeeInWei + 12345 - 1, provider1, 42, false);
  512. requestWithFee(user2, pythFeeInWei + 12345, provider1, 42, false);
  513. uint128 providerOneBalance = provider1FeeInWei * 3 + 12345;
  514. assertEq(
  515. random.getProviderInfo(provider1).accruedFeesInWei,
  516. providerOneBalance
  517. );
  518. assertInvariants();
  519. vm.prank(unregisteredProvider);
  520. vm.expectRevert();
  521. random.withdraw(1000);
  522. vm.prank(provider1);
  523. random.withdraw(1000);
  524. assertEq(
  525. random.getProviderInfo(provider1).accruedFeesInWei,
  526. providerOneBalance - 1000
  527. );
  528. assertInvariants();
  529. vm.prank(provider1);
  530. vm.expectRevert();
  531. random.withdraw(providerOneBalance);
  532. }
  533. function testGetProviderInfo() public {
  534. EntropyStructs.ProviderInfo memory providerInfo1 = random
  535. .getProviderInfo(provider1);
  536. // These two fields aren't used by the Entropy contract itself -- they're just convenient info to store
  537. // on-chain -- so they aren't tested in the other tests.
  538. assertEq(providerInfo1.uri, provider1Uri);
  539. assertEq(providerInfo1.commitmentMetadata, provider1CommitmentMetadata);
  540. }
  541. }