Arrays.test.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { randomArray, generators } = require('../helpers/random');
  5. // See https://en.cppreference.com/w/cpp/algorithm/lower_bound
  6. const lowerBound = (array, value) => {
  7. const i = array.findIndex(element => value <= element);
  8. return i == -1 ? array.length : i;
  9. };
  10. // See https://en.cppreference.com/w/cpp/algorithm/upper_bound
  11. const upperBound = (array, value) => {
  12. const i = array.findIndex(element => value < element);
  13. return i == -1 ? array.length : i;
  14. };
  15. const hasDuplicates = array => array.some((v, i) => array.indexOf(v) != i);
  16. describe('Arrays', function () {
  17. describe('search', function () {
  18. for (const [title, { array, tests }] of Object.entries({
  19. 'Even number of elements': {
  20. array: [11n, 12n, 13n, 14n, 15n, 16n, 17n, 18n, 19n, 20n],
  21. tests: {
  22. 'basic case': 16n,
  23. 'first element': 11n,
  24. 'last element': 20n,
  25. 'searched value is over the upper boundary': 32n,
  26. 'searched value is under the lower boundary': 2n,
  27. },
  28. },
  29. 'Odd number of elements': {
  30. array: [11n, 12n, 13n, 14n, 15n, 16n, 17n, 18n, 19n, 20n, 21n],
  31. tests: {
  32. 'basic case': 16n,
  33. 'first element': 11n,
  34. 'last element': 21n,
  35. 'searched value is over the upper boundary': 32n,
  36. 'searched value is under the lower boundary': 2n,
  37. },
  38. },
  39. 'Array with gap': {
  40. array: [11n, 12n, 13n, 14n, 15n, 20n, 21n, 22n, 23n, 24n],
  41. tests: {
  42. 'search value in gap': 17n,
  43. },
  44. },
  45. 'Array with duplicated elements': {
  46. array: [0n, 10n, 10n, 10n, 10n, 10n, 10n, 10n, 20n],
  47. tests: {
  48. 'search value is duplicated': 10n,
  49. },
  50. },
  51. 'Array with duplicated first element': {
  52. array: [10n, 10n, 10n, 10n, 10n, 10n, 10n, 20n],
  53. tests: {
  54. 'search value is duplicated first element': 10n,
  55. },
  56. },
  57. 'Array with duplicated last element': {
  58. array: [0n, 10n, 10n, 10n, 10n, 10n, 10n, 10n],
  59. tests: {
  60. 'search value is duplicated last element': 10n,
  61. },
  62. },
  63. 'Empty array': {
  64. array: [],
  65. tests: {
  66. 'always returns 0 for empty array': 10n,
  67. },
  68. },
  69. })) {
  70. describe(title, function () {
  71. const fixture = async () => {
  72. return { mock: await ethers.deployContract('Uint256ArraysMock', [array]) };
  73. };
  74. beforeEach(async function () {
  75. Object.assign(this, await loadFixture(fixture));
  76. });
  77. for (const [name, input] of Object.entries(tests)) {
  78. describe(name, function () {
  79. it('[deprecated] findUpperBound', async function () {
  80. // findUpperBound does not support duplicated
  81. if (hasDuplicates(array)) {
  82. expect(await this.mock.findUpperBound(input)).to.be.equal(upperBound(array, input) - 1);
  83. } else {
  84. expect(await this.mock.findUpperBound(input)).to.be.equal(lowerBound(array, input));
  85. }
  86. });
  87. it('lowerBound', async function () {
  88. expect(await this.mock.lowerBound(input)).to.be.equal(lowerBound(array, input));
  89. expect(await this.mock.lowerBoundMemory(array, input)).to.be.equal(lowerBound(array, input));
  90. });
  91. it('upperBound', async function () {
  92. expect(await this.mock.upperBound(input)).to.be.equal(upperBound(array, input));
  93. expect(await this.mock.upperBoundMemory(array, input)).to.be.equal(upperBound(array, input));
  94. });
  95. });
  96. }
  97. });
  98. }
  99. });
  100. describe('unsafeAccess', function () {
  101. for (const [title, { artifact, elements }] of Object.entries({
  102. address: { artifact: 'AddressArraysMock', elements: randomArray(generators.address, 10) },
  103. bytes32: { artifact: 'Bytes32ArraysMock', elements: randomArray(generators.bytes32, 10) },
  104. uint256: { artifact: 'Uint256ArraysMock', elements: randomArray(generators.uint256, 10) },
  105. })) {
  106. describe(title, function () {
  107. const fixture = async () => {
  108. return { mock: await ethers.deployContract(artifact, [elements]) };
  109. };
  110. beforeEach(async function () {
  111. Object.assign(this, await loadFixture(fixture));
  112. });
  113. for (const i in elements) {
  114. it(`unsafeAccess within bounds #${i}`, async function () {
  115. expect(await this.mock.unsafeAccess(i)).to.equal(elements[i]);
  116. });
  117. }
  118. it('unsafeAccess outside bounds', async function () {
  119. await expect(this.mock.unsafeAccess(elements.length)).to.not.be.rejected;
  120. });
  121. });
  122. }
  123. });
  124. });