Bytes.test.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { MAX_UINT128, MAX_UINT64, MAX_UINT32, MAX_UINT16 } = require('../helpers/constants');
  5. const { generators } = require('../helpers/random');
  6. // Helper functions for fixed bytes types
  7. const bytes32 = value => ethers.toBeHex(value, 32);
  8. const bytes16 = value => ethers.toBeHex(value, 16);
  9. const bytes8 = value => ethers.toBeHex(value, 8);
  10. const bytes4 = value => ethers.toBeHex(value, 4);
  11. const bytes2 = value => ethers.toBeHex(value, 2);
  12. async function fixture() {
  13. const mock = await ethers.deployContract('$Bytes');
  14. return { mock };
  15. }
  16. const lorem = ethers.toUtf8Bytes(
  17. 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
  18. );
  19. const present = lorem.at(1);
  20. const absent = 255;
  21. describe('Bytes', function () {
  22. before(async function () {
  23. Object.assign(this, await loadFixture(fixture));
  24. });
  25. describe('indexOf', function () {
  26. it('first', async function () {
  27. await expect(this.mock.$indexOf(lorem, ethers.toBeHex(present))).to.eventually.equal(lorem.indexOf(present));
  28. });
  29. it('from index', async function () {
  30. for (const start in Array(lorem.length + 10).fill()) {
  31. const index = lorem.indexOf(present, start);
  32. const result = index === -1 ? ethers.MaxUint256 : index;
  33. await expect(
  34. this.mock.$indexOf(lorem, ethers.toBeHex(present), ethers.Typed.uint256(start)),
  35. ).to.eventually.equal(result);
  36. }
  37. });
  38. it('absent', async function () {
  39. await expect(this.mock.$indexOf(lorem, ethers.toBeHex(absent))).to.eventually.equal(ethers.MaxUint256);
  40. });
  41. it('empty buffer', async function () {
  42. await expect(this.mock.$indexOf('0x', '0x00')).to.eventually.equal(ethers.MaxUint256);
  43. await expect(this.mock.$indexOf('0x', '0x00', ethers.Typed.uint256(17))).to.eventually.equal(ethers.MaxUint256);
  44. });
  45. });
  46. describe('lastIndexOf', function () {
  47. it('first', async function () {
  48. await expect(this.mock.$lastIndexOf(lorem, ethers.toBeHex(present))).to.eventually.equal(
  49. lorem.lastIndexOf(present),
  50. );
  51. });
  52. it('from index', async function () {
  53. for (const start in Array(lorem.length + 10).fill()) {
  54. const index = lorem.lastIndexOf(present, start);
  55. const result = index === -1 ? ethers.MaxUint256 : index;
  56. await expect(
  57. this.mock.$lastIndexOf(lorem, ethers.toBeHex(present), ethers.Typed.uint256(start)),
  58. ).to.eventually.equal(result);
  59. }
  60. });
  61. it('absent', async function () {
  62. await expect(this.mock.$lastIndexOf(lorem, ethers.toBeHex(absent))).to.eventually.equal(ethers.MaxUint256);
  63. });
  64. it('empty buffer', async function () {
  65. await expect(this.mock.$lastIndexOf('0x', '0x00')).to.eventually.equal(ethers.MaxUint256);
  66. await expect(this.mock.$lastIndexOf('0x', '0x00', ethers.Typed.uint256(17))).to.eventually.equal(
  67. ethers.MaxUint256,
  68. );
  69. });
  70. });
  71. describe('slice & splice', function () {
  72. describe('slice(bytes, uint256) & splice(bytes, uint256)', function () {
  73. for (const [descr, start] of Object.entries({
  74. 'start = 0': 0,
  75. 'start within bound': 10,
  76. 'start out of bound': 1000,
  77. })) {
  78. it(descr, async function () {
  79. const result = ethers.hexlify(lorem.slice(start));
  80. await expect(this.mock.$slice(lorem, start)).to.eventually.equal(result);
  81. await expect(this.mock.$splice(lorem, start)).to.eventually.equal(result);
  82. });
  83. }
  84. });
  85. describe('slice(bytes, uint256, uint256) & splice(bytes, uint256, uint256)', function () {
  86. for (const [descr, [start, end]] of Object.entries({
  87. 'start = 0': [0, 42],
  88. 'start and end within bound': [17, 42],
  89. 'end out of bound': [42, 1000],
  90. 'start = end': [17, 17],
  91. 'start > end': [42, 17],
  92. })) {
  93. it(descr, async function () {
  94. const result = ethers.hexlify(lorem.slice(start, end));
  95. await expect(this.mock.$slice(lorem, start, ethers.Typed.uint256(end))).to.eventually.equal(result);
  96. await expect(this.mock.$splice(lorem, start, ethers.Typed.uint256(end))).to.eventually.equal(result);
  97. });
  98. }
  99. });
  100. });
  101. describe('concat', function () {
  102. it('empty list', async function () {
  103. await expect(this.mock.$concat([])).to.eventually.equal(generators.bytes.zero);
  104. });
  105. it('single item', async function () {
  106. const item = generators.bytes();
  107. await expect(this.mock.$concat([item])).to.eventually.equal(item);
  108. });
  109. it('multiple (non-empty) items', async function () {
  110. const items = Array.from({ length: 17 }, generators.bytes);
  111. await expect(this.mock.$concat(items)).to.eventually.equal(ethers.concat(items));
  112. });
  113. it('multiple (empty) items', async function () {
  114. const items = Array.from({ length: 17 }).fill(generators.bytes.zero);
  115. await expect(this.mock.$concat(items)).to.eventually.equal(ethers.concat(items));
  116. });
  117. it('multiple (variable length) items', async function () {
  118. const items = [
  119. generators.bytes.zero,
  120. generators.bytes(17),
  121. generators.bytes.zero,
  122. generators.bytes(42),
  123. generators.bytes(1),
  124. generators.bytes(256),
  125. generators.bytes(1024),
  126. generators.bytes.zero,
  127. generators.bytes(7),
  128. generators.bytes(15),
  129. generators.bytes(63),
  130. generators.bytes.zero,
  131. generators.bytes.zero,
  132. ];
  133. await expect(this.mock.$concat(items)).to.eventually.equal(ethers.concat(items));
  134. });
  135. });
  136. describe('clz bytes', function () {
  137. it('empty buffer', async function () {
  138. await expect(this.mock.$clz('0x')).to.eventually.equal(0);
  139. });
  140. it('single zero byte', async function () {
  141. await expect(this.mock.$clz('0x00')).to.eventually.equal(8);
  142. });
  143. it('single non-zero byte', async function () {
  144. await expect(this.mock.$clz('0x01')).to.eventually.equal(7);
  145. await expect(this.mock.$clz('0xff')).to.eventually.equal(0);
  146. });
  147. it('multiple leading zeros', async function () {
  148. await expect(this.mock.$clz('0x0000000001')).to.eventually.equal(39);
  149. await expect(
  150. this.mock.$clz('0x0000000000000000000000000000000000000000000000000000000000000001'),
  151. ).to.eventually.equal(255);
  152. });
  153. it('all zeros of various lengths', async function () {
  154. await expect(this.mock.$clz('0x00000000')).to.eventually.equal(32);
  155. await expect(
  156. this.mock.$clz('0x0000000000000000000000000000000000000000000000000000000000000000'),
  157. ).to.eventually.equal(256);
  158. // Complete chunks
  159. await expect(this.mock.$clz('0x' + '00'.repeat(32) + '01')).to.eventually.equal(263); // 32*8+7
  160. await expect(this.mock.$clz('0x' + '00'.repeat(64) + '01')).to.eventually.equal(519); // 64*8+7
  161. // Partial last chunk
  162. await expect(this.mock.$clz('0x' + '00'.repeat(33) + '01')).to.eventually.equal(271); // 33*8+7
  163. await expect(this.mock.$clz('0x' + '00'.repeat(34) + '01')).to.eventually.equal(279); // 34*8+7
  164. await expect(this.mock.$clz('0x' + '00'.repeat(40) + '01' + '00'.repeat(9))).to.eventually.equal(327); // 40*8+7
  165. await expect(this.mock.$clz('0x' + '00'.repeat(50))).to.eventually.equal(400); // 50*8
  166. // First byte of each chunk non-zero
  167. await expect(this.mock.$clz('0x80' + '00'.repeat(31))).to.eventually.equal(0);
  168. await expect(this.mock.$clz('0x01' + '00'.repeat(31))).to.eventually.equal(7);
  169. await expect(this.mock.$clz('0x' + '00'.repeat(32) + '80' + '00'.repeat(31))).to.eventually.equal(256); // 32*8
  170. await expect(this.mock.$clz('0x' + '00'.repeat(32) + '01' + '00'.repeat(31))).to.eventually.equal(263); // 32*8+7
  171. // Last byte of each chunk non-zero
  172. await expect(this.mock.$clz('0x' + '00'.repeat(31) + '01')).to.eventually.equal(255); // 31*8+7
  173. await expect(this.mock.$clz('0x' + '00'.repeat(63) + '01')).to.eventually.equal(511); // 63*8+7
  174. // Middle byte of each chunk non-zero
  175. await expect(this.mock.$clz('0x' + '00'.repeat(16) + '01' + '00'.repeat(15))).to.eventually.equal(135); // 16*8+7
  176. await expect(this.mock.$clz('0x' + '00'.repeat(32) + '01' + '00'.repeat(31))).to.eventually.equal(263); // 32*8+7
  177. await expect(this.mock.$clz('0x' + '00'.repeat(48) + '01' + '00'.repeat(47))).to.eventually.equal(391); // 48*8+7
  178. await expect(this.mock.$clz('0x' + '00'.repeat(64) + '01' + '00'.repeat(63))).to.eventually.equal(519); // 64*8+7
  179. });
  180. });
  181. describe('equal', function () {
  182. it('identical buffers', async function () {
  183. await expect(this.mock.$equal(lorem, lorem)).to.eventually.be.true;
  184. });
  185. it('same content', async function () {
  186. const copy = new Uint8Array(lorem);
  187. await expect(this.mock.$equal(lorem, copy)).to.eventually.be.true;
  188. });
  189. it('different content', async function () {
  190. const different = ethers.toUtf8Bytes('Different content');
  191. await expect(this.mock.$equal(lorem, different)).to.eventually.be.false;
  192. });
  193. it('different lengths', async function () {
  194. const shorter = lorem.slice(0, 10);
  195. await expect(this.mock.$equal(lorem, shorter)).to.eventually.be.false;
  196. });
  197. it('empty buffers', async function () {
  198. const empty1 = new Uint8Array(0);
  199. const empty2 = new Uint8Array(0);
  200. await expect(this.mock.$equal(empty1, empty2)).to.eventually.be.true;
  201. });
  202. it('one empty one not', async function () {
  203. const empty = new Uint8Array(0);
  204. await expect(this.mock.$equal(lorem, empty)).to.eventually.be.false;
  205. });
  206. });
  207. describe('reverseBits', function () {
  208. describe('reverseBytes32', function () {
  209. it('reverses bytes correctly', async function () {
  210. await expect(this.mock.$reverseBytes32(bytes32(0))).to.eventually.equal(bytes32(0));
  211. await expect(this.mock.$reverseBytes32(bytes32(ethers.MaxUint256))).to.eventually.equal(
  212. bytes32(ethers.MaxUint256),
  213. );
  214. // Test complex pattern that clearly shows byte reversal
  215. await expect(
  216. this.mock.$reverseBytes32('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'),
  217. ).to.eventually.equal('0xefcdab8967452301efcdab8967452301efcdab8967452301efcdab8967452301');
  218. });
  219. it('double reverse returns original', async function () {
  220. const values = [0n, 1n, 0x12345678n, ethers.MaxUint256];
  221. for (const value of values) {
  222. const reversed = await this.mock.$reverseBytes32(bytes32(value));
  223. await expect(this.mock.$reverseBytes32(reversed)).to.eventually.equal(bytes32(value));
  224. }
  225. });
  226. });
  227. describe('reverseBytes16', function () {
  228. it('reverses bytes correctly', async function () {
  229. await expect(this.mock.$reverseBytes16(bytes16(0))).to.eventually.equal(bytes16(0));
  230. await expect(this.mock.$reverseBytes16(bytes16(MAX_UINT128))).to.eventually.equal(bytes16(MAX_UINT128));
  231. // Test complex pattern that clearly shows byte reversal
  232. await expect(this.mock.$reverseBytes16('0x0123456789abcdef0123456789abcdef')).to.eventually.equal(
  233. '0xefcdab8967452301efcdab8967452301',
  234. );
  235. });
  236. it('double reverse returns original', async function () {
  237. const values = [0n, 1n, 0x12345678n, MAX_UINT128];
  238. for (const value of values) {
  239. const reversed = await this.mock.$reverseBytes16(bytes16(value));
  240. // Cast back to uint128 for comparison since function returns uint256
  241. await expect(this.mock.$reverseBytes16(reversed)).to.eventually.equal(bytes16(value & MAX_UINT128));
  242. }
  243. });
  244. });
  245. describe('reverseBytes8', function () {
  246. it('reverses bytes correctly', async function () {
  247. await expect(this.mock.$reverseBytes8(bytes8(0))).to.eventually.equal(bytes8(0));
  248. await expect(this.mock.$reverseBytes8(bytes8(MAX_UINT64))).to.eventually.equal(bytes8(MAX_UINT64));
  249. // Test known pattern: 0x123456789ABCDEF0 -> 0xF0DEBC9A78563412
  250. await expect(this.mock.$reverseBytes8('0x123456789abcdef0')).to.eventually.equal('0xf0debc9a78563412');
  251. });
  252. it('double reverse returns original', async function () {
  253. const values = [0n, 1n, 0x12345678n, MAX_UINT64];
  254. for (const value of values) {
  255. const reversed = await this.mock.$reverseBytes8(bytes8(value));
  256. // Cast back to uint64 for comparison since function returns uint256
  257. await expect(this.mock.$reverseBytes8(reversed)).to.eventually.equal(bytes8(value & MAX_UINT64));
  258. }
  259. });
  260. });
  261. describe('reverseBytes4', function () {
  262. it('reverses bytes correctly', async function () {
  263. await expect(this.mock.$reverseBytes4(bytes4(0))).to.eventually.equal(bytes4(0));
  264. await expect(this.mock.$reverseBytes4(bytes4(MAX_UINT32))).to.eventually.equal(bytes4(MAX_UINT32));
  265. // Test known pattern: 0x12345678 -> 0x78563412
  266. await expect(this.mock.$reverseBytes4(bytes4(0x12345678))).to.eventually.equal(bytes4(0x78563412));
  267. });
  268. it('double reverse returns original', async function () {
  269. const values = [0n, 1n, 0x12345678n, MAX_UINT32];
  270. for (const value of values) {
  271. const reversed = await this.mock.$reverseBytes4(bytes4(value));
  272. // Cast back to uint32 for comparison since function returns uint256
  273. await expect(this.mock.$reverseBytes4(reversed)).to.eventually.equal(bytes4(value & MAX_UINT32));
  274. }
  275. });
  276. });
  277. describe('reverseBytes2', function () {
  278. it('reverses bytes correctly', async function () {
  279. await expect(this.mock.$reverseBytes2(bytes2(0))).to.eventually.equal(bytes2(0));
  280. await expect(this.mock.$reverseBytes2(bytes2(MAX_UINT16))).to.eventually.equal(bytes2(MAX_UINT16));
  281. // Test known pattern: 0x1234 -> 0x3412
  282. await expect(this.mock.$reverseBytes2(bytes2(0x1234))).to.eventually.equal(bytes2(0x3412));
  283. });
  284. it('double reverse returns original', async function () {
  285. const values = [0n, 1n, 0x1234n, MAX_UINT16];
  286. for (const value of values) {
  287. const reversed = await this.mock.$reverseBytes2(bytes2(value));
  288. // Cast back to uint16 for comparison since function returns uint256
  289. await expect(this.mock.$reverseBytes2(reversed)).to.eventually.equal(bytes2(value & MAX_UINT16));
  290. }
  291. });
  292. });
  293. describe('edge cases', function () {
  294. it('handles single byte values', async function () {
  295. await expect(this.mock.$reverseBytes2(bytes2(0x00ff))).to.eventually.equal(bytes2(0xff00));
  296. await expect(this.mock.$reverseBytes4(bytes4(0x000000ff))).to.eventually.equal(bytes4(0xff000000));
  297. });
  298. it('handles alternating patterns', async function () {
  299. await expect(this.mock.$reverseBytes2(bytes2(0xaaaa))).to.eventually.equal(bytes2(0xaaaa));
  300. await expect(this.mock.$reverseBytes2(bytes2(0x5555))).to.eventually.equal(bytes2(0x5555));
  301. await expect(this.mock.$reverseBytes4(bytes4(0xaaaaaaaa))).to.eventually.equal(bytes4(0xaaaaaaaa));
  302. await expect(this.mock.$reverseBytes4(bytes4(0x55555555))).to.eventually.equal(bytes4(0x55555555));
  303. });
  304. });
  305. });
  306. });