Entropy.t.sol 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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 "../contracts/entropy/Entropy.sol";
  6. // TODO
  7. // - what's the impact of # of in-flight requests on gas usage? More requests => more hashes to
  8. // verify the provider's value.
  9. // - fuzz test?
  10. contract EntropyTest is Test {
  11. Entropy public random;
  12. uint pythFeeInWei = 7;
  13. address public provider1 = address(1);
  14. bytes32[] provider1Proofs;
  15. uint provider1FeeInWei = 8;
  16. uint64 provider1ChainLength = 100;
  17. address public provider2 = address(2);
  18. bytes32[] provider2Proofs;
  19. uint provider2FeeInWei = 20;
  20. address public user1 = address(3);
  21. address public user2 = address(4);
  22. address public unregisteredProvider = address(7);
  23. uint256 MAX_UINT256 = 2 ** 256 - 1;
  24. bytes32 ALL_ZEROS = bytes32(uint256(0));
  25. function setUp() public {
  26. random = new Entropy(pythFeeInWei);
  27. bytes32[] memory hashChain1 = generateHashChain(
  28. provider1,
  29. 0,
  30. provider1ChainLength
  31. );
  32. provider1Proofs = hashChain1;
  33. vm.prank(provider1);
  34. random.register(
  35. provider1FeeInWei,
  36. provider1Proofs[0],
  37. bytes32(keccak256(abi.encodePacked(uint256(0x0100)))),
  38. provider1ChainLength
  39. );
  40. bytes32[] memory hashChain2 = generateHashChain(provider2, 0, 100);
  41. provider2Proofs = hashChain2;
  42. vm.prank(provider2);
  43. random.register(
  44. provider2FeeInWei,
  45. provider2Proofs[0],
  46. bytes32(keccak256(abi.encodePacked(uint256(0x0200)))),
  47. 100
  48. );
  49. }
  50. function generateHashChain(
  51. address provider,
  52. uint64 startSequenceNumber,
  53. uint64 size
  54. ) public pure returns (bytes32[] memory hashChain) {
  55. bytes32 initialValue = keccak256(abi.encodePacked(startSequenceNumber));
  56. hashChain = new bytes32[](size);
  57. for (uint64 i = 0; i < size; i++) {
  58. hashChain[size - (i + 1)] = initialValue;
  59. initialValue = keccak256(bytes.concat(initialValue));
  60. }
  61. }
  62. // Test helper method for requesting a random value as user from provider.
  63. function request(
  64. address user,
  65. address provider,
  66. uint randomNumber,
  67. bool useBlockhash
  68. ) public returns (uint64 sequenceNumber) {
  69. sequenceNumber = requestWithFee(
  70. user,
  71. random.getFee(provider),
  72. provider,
  73. randomNumber,
  74. useBlockhash
  75. );
  76. }
  77. function requestWithFee(
  78. address user,
  79. uint fee,
  80. address provider,
  81. uint randomNumber,
  82. bool useBlockhash
  83. ) public returns (uint64 sequenceNumber) {
  84. vm.deal(user, fee);
  85. vm.prank(user);
  86. sequenceNumber = random.request{value: fee}(
  87. provider,
  88. random.constructUserCommitment(bytes32(randomNumber)),
  89. useBlockhash
  90. );
  91. }
  92. function assertRequestReverts(
  93. uint fee,
  94. address provider,
  95. uint randomNumber,
  96. bool useBlockhash
  97. ) public {
  98. // Note: for some reason vm.expectRevert() won't catch errors from the request function (?!),
  99. // even though they definitely revert. Use a try/catch instead for the moment, though the try/catch
  100. // doesn't let you simulate the msg.sender. However, it's fine if the msg.sender is the test contract.
  101. bool requestSucceeds = false;
  102. try
  103. random.request{value: fee}(
  104. provider,
  105. random.constructUserCommitment(bytes32(uint256(randomNumber))),
  106. useBlockhash
  107. )
  108. {
  109. requestSucceeds = true;
  110. } catch {
  111. requestSucceeds = false;
  112. }
  113. assert(!requestSucceeds);
  114. }
  115. function assertRevealSucceeds(
  116. address provider,
  117. uint64 sequenceNumber,
  118. uint userRandom,
  119. bytes32 providerRevelation,
  120. bytes32 hash
  121. ) public {
  122. bytes32 randomNumber = random.reveal(
  123. provider,
  124. sequenceNumber,
  125. bytes32(userRandom),
  126. providerRevelation
  127. );
  128. assertEq(
  129. randomNumber,
  130. random.combineRandomValues(
  131. bytes32(userRandom),
  132. providerRevelation,
  133. hash
  134. )
  135. );
  136. }
  137. function assertRevealReverts(
  138. address provider,
  139. uint64 sequenceNumber,
  140. uint userRandom,
  141. bytes32 providerRevelation
  142. ) public {
  143. vm.expectRevert();
  144. random.reveal(
  145. provider,
  146. sequenceNumber,
  147. bytes32(uint256(userRandom)),
  148. providerRevelation
  149. );
  150. }
  151. function assertInvariants() public {
  152. uint expectedBalance = random
  153. .getProviderInfo(provider1)
  154. .accruedFeesInWei +
  155. random.getProviderInfo(provider2).accruedFeesInWei +
  156. random.getAccruedPythFees();
  157. assertEq(address(random).balance, expectedBalance);
  158. EntropyStructs.ProviderInfo memory info1 = random.getProviderInfo(
  159. provider1
  160. );
  161. assert(
  162. info1.originalCommitmentSequenceNumber <=
  163. info1.currentCommitmentSequenceNumber
  164. );
  165. assert(info1.currentCommitmentSequenceNumber < info1.sequenceNumber);
  166. assert(info1.sequenceNumber <= info1.endSequenceNumber);
  167. EntropyStructs.ProviderInfo memory info2 = random.getProviderInfo(
  168. provider2
  169. );
  170. assert(
  171. info2.originalCommitmentSequenceNumber <=
  172. info2.currentCommitmentSequenceNumber
  173. );
  174. assert(info2.sequenceNumber > info2.currentCommitmentSequenceNumber);
  175. assert(info2.sequenceNumber <= info2.endSequenceNumber);
  176. }
  177. function testBasicFlow() public {
  178. uint64 sequenceNumber = request(user2, provider1, 42, false);
  179. assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 0);
  180. assertRevealSucceeds(
  181. provider1,
  182. sequenceNumber,
  183. 42,
  184. provider1Proofs[sequenceNumber],
  185. ALL_ZEROS
  186. );
  187. // You can only reveal the random number once. This isn't a feature of the contract per se, but it is
  188. // the expected behavior.
  189. assertRevealReverts(
  190. provider1,
  191. sequenceNumber,
  192. 42,
  193. provider1Proofs[sequenceNumber]
  194. );
  195. }
  196. function testNoSuchProvider() public {
  197. assertRequestReverts(10000000, unregisteredProvider, 42, false);
  198. }
  199. function testAdversarialReveal() public {
  200. uint64 sequenceNumber = request(user2, provider1, 42, false);
  201. // test revealing with the wrong hashes in the same chain
  202. for (uint256 i = 0; i < 10; i++) {
  203. if (i != sequenceNumber) {
  204. assertRevealReverts(
  205. provider1,
  206. sequenceNumber,
  207. 42,
  208. provider1Proofs[i]
  209. );
  210. }
  211. }
  212. // test revealing with the wrong user revealed value.
  213. for (uint256 i = 0; i < 42; i++) {
  214. assertRevealReverts(
  215. provider1,
  216. sequenceNumber,
  217. i,
  218. provider1Proofs[sequenceNumber]
  219. );
  220. }
  221. // test revealing sequence numbers that haven't been requested yet.
  222. for (uint64 i = sequenceNumber + 1; i < sequenceNumber + 3; i++) {
  223. assertRevealReverts(
  224. provider1,
  225. i,
  226. 42,
  227. provider1Proofs[sequenceNumber]
  228. );
  229. assertRevealReverts(provider1, i, 42, provider1Proofs[i]);
  230. }
  231. }
  232. function testConcurrentRequests() public {
  233. uint64 s1 = request(user1, provider1, 1, false);
  234. uint64 s2 = request(user2, provider1, 2, false);
  235. uint64 s3 = request(user1, provider1, 3, false);
  236. uint64 s4 = request(user1, provider1, 4, false);
  237. assertRevealSucceeds(provider1, s3, 3, provider1Proofs[s3], ALL_ZEROS);
  238. assertInvariants();
  239. uint64 s5 = request(user1, provider1, 5, false);
  240. assertRevealSucceeds(provider1, s4, 4, provider1Proofs[s4], ALL_ZEROS);
  241. assertInvariants();
  242. assertRevealSucceeds(provider1, s1, 1, provider1Proofs[s1], ALL_ZEROS);
  243. assertInvariants();
  244. assertRevealSucceeds(provider1, s2, 2, provider1Proofs[s2], ALL_ZEROS);
  245. assertInvariants();
  246. assertRevealSucceeds(provider1, s5, 5, provider1Proofs[s5], ALL_ZEROS);
  247. assertInvariants();
  248. }
  249. function testBlockhash() public {
  250. vm.roll(1234);
  251. uint64 sequenceNumber = request(user2, provider1, 42, true);
  252. assertEq(
  253. random.getRequest(provider1, sequenceNumber).blockNumber,
  254. 1234
  255. );
  256. assertRevealSucceeds(
  257. provider1,
  258. sequenceNumber,
  259. 42,
  260. provider1Proofs[sequenceNumber],
  261. blockhash(1234)
  262. );
  263. // You can only reveal the random number once. This isn't a feature of the contract per se, but it is
  264. // the expected behavior.
  265. assertRevealReverts(
  266. provider1,
  267. sequenceNumber,
  268. 42,
  269. provider1Proofs[sequenceNumber]
  270. );
  271. }
  272. function testProviderCommitmentRotation() public {
  273. uint userRandom = 42;
  274. uint64 sequenceNumber1 = request(user2, provider1, userRandom, false);
  275. uint64 sequenceNumber2 = request(user2, provider1, userRandom, false);
  276. assertInvariants();
  277. uint64 newHashChainOffset = sequenceNumber2 + 1;
  278. bytes32[] memory newHashChain = generateHashChain(
  279. provider1,
  280. newHashChainOffset,
  281. 10
  282. );
  283. vm.prank(provider1);
  284. random.register(
  285. provider1FeeInWei,
  286. newHashChain[0],
  287. bytes32(keccak256(abi.encodePacked(uint256(0x0100)))),
  288. 10
  289. );
  290. assertInvariants();
  291. EntropyStructs.ProviderInfo memory info1 = random.getProviderInfo(
  292. provider1
  293. );
  294. assertEq(info1.endSequenceNumber, newHashChainOffset + 10);
  295. uint64 sequenceNumber3 = request(user2, provider1, 42, false);
  296. // Rotating the provider key uses a sequence number
  297. assertEq(sequenceNumber3, sequenceNumber2 + 2);
  298. // Requests that were in-flight at the time of rotation use the commitment from the time of request
  299. for (uint256 i = 0; i < 10; i++) {
  300. assertRevealReverts(
  301. provider1,
  302. sequenceNumber1,
  303. userRandom,
  304. newHashChain[i]
  305. );
  306. }
  307. assertRevealSucceeds(
  308. provider1,
  309. sequenceNumber1,
  310. userRandom,
  311. provider1Proofs[sequenceNumber1],
  312. ALL_ZEROS
  313. );
  314. assertInvariants();
  315. // Requests after the rotation use the new commitment
  316. assertRevealReverts(
  317. provider1,
  318. sequenceNumber3,
  319. userRandom,
  320. provider1Proofs[sequenceNumber3]
  321. );
  322. assertRevealSucceeds(
  323. provider1,
  324. sequenceNumber3,
  325. userRandom,
  326. newHashChain[sequenceNumber3 - newHashChainOffset],
  327. ALL_ZEROS
  328. );
  329. assertInvariants();
  330. }
  331. function testOutOfRandomness() public {
  332. // Should be able to request chainLength - 1 random numbers successfully.
  333. for (uint64 i = 0; i < provider1ChainLength - 1; i++) {
  334. request(user1, provider1, i, false);
  335. }
  336. assertRequestReverts(
  337. random.getFee(provider1),
  338. provider1,
  339. provider1ChainLength - 1,
  340. false
  341. );
  342. }
  343. function testGetFee() public {
  344. assertEq(random.getFee(provider1), pythFeeInWei + provider1FeeInWei);
  345. assertEq(random.getFee(provider2), pythFeeInWei + provider2FeeInWei);
  346. // Requesting the fee for a nonexistent provider returns pythFeeInWei. This isn't necessarily desirable behavior,
  347. // but it's unlikely to cause a problem.
  348. assertEq(random.getFee(unregisteredProvider), pythFeeInWei);
  349. // Check that overflowing the fee arithmetic causes the transaction to revert.
  350. vm.prank(provider1);
  351. random.register(
  352. MAX_UINT256,
  353. provider1Proofs[0],
  354. bytes32(keccak256(abi.encodePacked(uint256(0x0100)))),
  355. 100
  356. );
  357. vm.expectRevert();
  358. random.getFee(provider1);
  359. }
  360. function testFees() public {
  361. // Insufficient fees causes a revert
  362. assertRequestReverts(0, provider1, 42, false);
  363. assertRequestReverts(
  364. pythFeeInWei + provider1FeeInWei - 1,
  365. provider1,
  366. 42,
  367. false
  368. );
  369. assertRequestReverts(0, provider2, 42, false);
  370. assertRequestReverts(
  371. pythFeeInWei + provider2FeeInWei - 1,
  372. provider2,
  373. 42,
  374. false
  375. );
  376. // Accrue some fees for both providers
  377. for (uint i = 0; i < 3; i++) {
  378. request(user2, provider1, 42, false);
  379. }
  380. request(user2, provider2, 42, false);
  381. // this call overpays for the random number
  382. requestWithFee(
  383. user2,
  384. pythFeeInWei + provider2FeeInWei + 10000,
  385. provider2,
  386. 42,
  387. false
  388. );
  389. assertEq(
  390. random.getProviderInfo(provider1).accruedFeesInWei,
  391. provider1FeeInWei * 3
  392. );
  393. assertEq(
  394. random.getProviderInfo(provider2).accruedFeesInWei,
  395. provider2FeeInWei * 2
  396. );
  397. assertEq(random.getAccruedPythFees(), pythFeeInWei * 5 + 10000);
  398. assertInvariants();
  399. // Reregistering updates the required fees
  400. vm.prank(provider1);
  401. random.register(
  402. 12345,
  403. provider1Proofs[0],
  404. bytes32(keccak256(abi.encodePacked(uint256(0x0100)))),
  405. 100
  406. );
  407. assertRequestReverts(pythFeeInWei + 12345 - 1, provider1, 42, false);
  408. requestWithFee(user2, pythFeeInWei + 12345, provider1, 42, false);
  409. uint providerOneBalance = provider1FeeInWei * 3 + 12345;
  410. assertEq(
  411. random.getProviderInfo(provider1).accruedFeesInWei,
  412. providerOneBalance
  413. );
  414. assertInvariants();
  415. vm.prank(unregisteredProvider);
  416. vm.expectRevert();
  417. random.withdraw(1000);
  418. vm.prank(provider1);
  419. random.withdraw(1000);
  420. assertEq(
  421. random.getProviderInfo(provider1).accruedFeesInWei,
  422. providerOneBalance - 1000
  423. );
  424. assertInvariants();
  425. vm.prank(provider1);
  426. vm.expectRevert();
  427. random.withdraw(providerOneBalance);
  428. }
  429. }