Math.test.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers');
  2. const { expect } = require('chai');
  3. const { MAX_UINT256 } = constants;
  4. const { Rounding } = require('../../helpers/enums.js');
  5. const { expectRevertCustomError } = require('../../helpers/customError.js');
  6. const Math = artifacts.require('$Math');
  7. function expectStruct(value, expected) {
  8. for (const key in expected) {
  9. if (BN.isBN(value[key])) {
  10. expect(value[key]).to.be.bignumber.equal(expected[key]);
  11. } else {
  12. expect(value[key]).to.be.equal(expected[key]);
  13. }
  14. }
  15. }
  16. async function testCommutativeIterable(fn, lhs, rhs, expected, ...extra) {
  17. expectStruct(await fn(lhs, rhs, ...extra), expected);
  18. expectStruct(await fn(rhs, lhs, ...extra), expected);
  19. }
  20. contract('Math', function () {
  21. const min = new BN('1234');
  22. const max = new BN('5678');
  23. const MAX_UINT256_SUB1 = MAX_UINT256.sub(new BN('1'));
  24. const MAX_UINT256_SUB2 = MAX_UINT256.sub(new BN('2'));
  25. beforeEach(async function () {
  26. this.math = await Math.new();
  27. });
  28. describe('tryAdd', function () {
  29. it('adds correctly', async function () {
  30. const a = new BN('5678');
  31. const b = new BN('1234');
  32. await testCommutativeIterable(this.math.$tryAdd, a, b, [true, a.add(b)]);
  33. });
  34. it('reverts on addition overflow', async function () {
  35. const a = MAX_UINT256;
  36. const b = new BN('1');
  37. await testCommutativeIterable(this.math.$tryAdd, a, b, [false, '0']);
  38. });
  39. });
  40. describe('trySub', function () {
  41. it('subtracts correctly', async function () {
  42. const a = new BN('5678');
  43. const b = new BN('1234');
  44. expectStruct(await this.math.$trySub(a, b), [true, a.sub(b)]);
  45. });
  46. it('reverts if subtraction result would be negative', async function () {
  47. const a = new BN('1234');
  48. const b = new BN('5678');
  49. expectStruct(await this.math.$trySub(a, b), [false, '0']);
  50. });
  51. });
  52. describe('tryMul', function () {
  53. it('multiplies correctly', async function () {
  54. const a = new BN('1234');
  55. const b = new BN('5678');
  56. await testCommutativeIterable(this.math.$tryMul, a, b, [true, a.mul(b)]);
  57. });
  58. it('multiplies by zero correctly', async function () {
  59. const a = new BN('0');
  60. const b = new BN('5678');
  61. await testCommutativeIterable(this.math.$tryMul, a, b, [true, a.mul(b)]);
  62. });
  63. it('reverts on multiplication overflow', async function () {
  64. const a = MAX_UINT256;
  65. const b = new BN('2');
  66. await testCommutativeIterable(this.math.$tryMul, a, b, [false, '0']);
  67. });
  68. });
  69. describe('tryDiv', function () {
  70. it('divides correctly', async function () {
  71. const a = new BN('5678');
  72. const b = new BN('5678');
  73. expectStruct(await this.math.$tryDiv(a, b), [true, a.div(b)]);
  74. });
  75. it('divides zero correctly', async function () {
  76. const a = new BN('0');
  77. const b = new BN('5678');
  78. expectStruct(await this.math.$tryDiv(a, b), [true, a.div(b)]);
  79. });
  80. it('returns complete number result on non-even division', async function () {
  81. const a = new BN('7000');
  82. const b = new BN('5678');
  83. expectStruct(await this.math.$tryDiv(a, b), [true, a.div(b)]);
  84. });
  85. it('reverts on division by zero', async function () {
  86. const a = new BN('5678');
  87. const b = new BN('0');
  88. expectStruct(await this.math.$tryDiv(a, b), [false, '0']);
  89. });
  90. });
  91. describe('tryMod', function () {
  92. describe('modulos correctly', async function () {
  93. it('when the dividend is smaller than the divisor', async function () {
  94. const a = new BN('284');
  95. const b = new BN('5678');
  96. expectStruct(await this.math.$tryMod(a, b), [true, a.mod(b)]);
  97. });
  98. it('when the dividend is equal to the divisor', async function () {
  99. const a = new BN('5678');
  100. const b = new BN('5678');
  101. expectStruct(await this.math.$tryMod(a, b), [true, a.mod(b)]);
  102. });
  103. it('when the dividend is larger than the divisor', async function () {
  104. const a = new BN('7000');
  105. const b = new BN('5678');
  106. expectStruct(await this.math.$tryMod(a, b), [true, a.mod(b)]);
  107. });
  108. it('when the dividend is a multiple of the divisor', async function () {
  109. const a = new BN('17034'); // 17034 == 5678 * 3
  110. const b = new BN('5678');
  111. expectStruct(await this.math.$tryMod(a, b), [true, a.mod(b)]);
  112. });
  113. });
  114. it('reverts with a 0 divisor', async function () {
  115. const a = new BN('5678');
  116. const b = new BN('0');
  117. expectStruct(await this.math.$tryMod(a, b), [false, '0']);
  118. });
  119. });
  120. describe('max', function () {
  121. it('is correctly detected in first argument position', async function () {
  122. expect(await this.math.$max(max, min)).to.be.bignumber.equal(max);
  123. });
  124. it('is correctly detected in second argument position', async function () {
  125. expect(await this.math.$max(min, max)).to.be.bignumber.equal(max);
  126. });
  127. });
  128. describe('min', function () {
  129. it('is correctly detected in first argument position', async function () {
  130. expect(await this.math.$min(min, max)).to.be.bignumber.equal(min);
  131. });
  132. it('is correctly detected in second argument position', async function () {
  133. expect(await this.math.$min(max, min)).to.be.bignumber.equal(min);
  134. });
  135. });
  136. describe('average', function () {
  137. function bnAverage(a, b) {
  138. return a.add(b).divn(2);
  139. }
  140. it('is correctly calculated with two odd numbers', async function () {
  141. const a = new BN('57417');
  142. const b = new BN('95431');
  143. expect(await this.math.$average(a, b)).to.be.bignumber.equal(bnAverage(a, b));
  144. });
  145. it('is correctly calculated with two even numbers', async function () {
  146. const a = new BN('42304');
  147. const b = new BN('84346');
  148. expect(await this.math.$average(a, b)).to.be.bignumber.equal(bnAverage(a, b));
  149. });
  150. it('is correctly calculated with one even and one odd number', async function () {
  151. const a = new BN('57417');
  152. const b = new BN('84346');
  153. expect(await this.math.$average(a, b)).to.be.bignumber.equal(bnAverage(a, b));
  154. });
  155. it('is correctly calculated with two max uint256 numbers', async function () {
  156. const a = MAX_UINT256;
  157. expect(await this.math.$average(a, a)).to.be.bignumber.equal(bnAverage(a, a));
  158. });
  159. });
  160. describe('ceilDiv', function () {
  161. it('reverts on zero division', async function () {
  162. const a = new BN('2');
  163. const b = new BN('0');
  164. // It's unspecified because it's a low level 0 division error
  165. await expectRevert.unspecified(this.math.$ceilDiv(a, b));
  166. });
  167. it('does not round up a zero result', async function () {
  168. const a = new BN('0');
  169. const b = new BN('2');
  170. expect(await this.math.$ceilDiv(a, b)).to.be.bignumber.equal('0');
  171. });
  172. it('does not round up on exact division', async function () {
  173. const a = new BN('10');
  174. const b = new BN('5');
  175. expect(await this.math.$ceilDiv(a, b)).to.be.bignumber.equal('2');
  176. });
  177. it('rounds up on division with remainders', async function () {
  178. const a = new BN('42');
  179. const b = new BN('13');
  180. expect(await this.math.$ceilDiv(a, b)).to.be.bignumber.equal('4');
  181. });
  182. it('does not overflow', async function () {
  183. const b = new BN('2');
  184. const result = new BN('1').shln(255);
  185. expect(await this.math.$ceilDiv(MAX_UINT256, b)).to.be.bignumber.equal(result);
  186. });
  187. it('correctly computes max uint256 divided by 1', async function () {
  188. const b = new BN('1');
  189. expect(await this.math.$ceilDiv(MAX_UINT256, b)).to.be.bignumber.equal(MAX_UINT256);
  190. });
  191. });
  192. describe('muldiv', function () {
  193. it('divide by 0', async function () {
  194. await expectRevert.unspecified(this.math.$mulDiv(1, 1, 0, Rounding.Down));
  195. });
  196. it('reverts with result higher than 2 ^ 256', async function () {
  197. await expectRevertCustomError(this.math.$mulDiv(5, MAX_UINT256, 2, Rounding.Down), 'MathOverflowedMulDiv', []);
  198. });
  199. describe('does round down', async function () {
  200. it('small values', async function () {
  201. expect(await this.math.$mulDiv('3', '4', '5', Rounding.Down)).to.be.bignumber.equal('2');
  202. expect(await this.math.$mulDiv('3', '5', '5', Rounding.Down)).to.be.bignumber.equal('3');
  203. });
  204. it('large values', async function () {
  205. expect(
  206. await this.math.$mulDiv(new BN('42'), MAX_UINT256_SUB1, MAX_UINT256, Rounding.Down),
  207. ).to.be.bignumber.equal(new BN('41'));
  208. expect(await this.math.$mulDiv(new BN('17'), MAX_UINT256, MAX_UINT256, Rounding.Down)).to.be.bignumber.equal(
  209. new BN('17'),
  210. );
  211. expect(
  212. await this.math.$mulDiv(MAX_UINT256_SUB1, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Down),
  213. ).to.be.bignumber.equal(MAX_UINT256_SUB2);
  214. expect(
  215. await this.math.$mulDiv(MAX_UINT256, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Down),
  216. ).to.be.bignumber.equal(MAX_UINT256_SUB1);
  217. expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256, MAX_UINT256, Rounding.Down)).to.be.bignumber.equal(
  218. MAX_UINT256,
  219. );
  220. });
  221. });
  222. describe('does round up', async function () {
  223. it('small values', async function () {
  224. expect(await this.math.$mulDiv('3', '4', '5', Rounding.Up)).to.be.bignumber.equal('3');
  225. expect(await this.math.$mulDiv('3', '5', '5', Rounding.Up)).to.be.bignumber.equal('3');
  226. });
  227. it('large values', async function () {
  228. expect(await this.math.$mulDiv(new BN('42'), MAX_UINT256_SUB1, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal(
  229. new BN('42'),
  230. );
  231. expect(await this.math.$mulDiv(new BN('17'), MAX_UINT256, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal(
  232. new BN('17'),
  233. );
  234. expect(
  235. await this.math.$mulDiv(MAX_UINT256_SUB1, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Up),
  236. ).to.be.bignumber.equal(MAX_UINT256_SUB1);
  237. expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal(
  238. MAX_UINT256_SUB1,
  239. );
  240. expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal(
  241. MAX_UINT256,
  242. );
  243. });
  244. });
  245. });
  246. describe('sqrt', function () {
  247. it('rounds down', async function () {
  248. expect(await this.math.$sqrt('0', Rounding.Down)).to.be.bignumber.equal('0');
  249. expect(await this.math.$sqrt('1', Rounding.Down)).to.be.bignumber.equal('1');
  250. expect(await this.math.$sqrt('2', Rounding.Down)).to.be.bignumber.equal('1');
  251. expect(await this.math.$sqrt('3', Rounding.Down)).to.be.bignumber.equal('1');
  252. expect(await this.math.$sqrt('4', Rounding.Down)).to.be.bignumber.equal('2');
  253. expect(await this.math.$sqrt('144', Rounding.Down)).to.be.bignumber.equal('12');
  254. expect(await this.math.$sqrt('999999', Rounding.Down)).to.be.bignumber.equal('999');
  255. expect(await this.math.$sqrt('1000000', Rounding.Down)).to.be.bignumber.equal('1000');
  256. expect(await this.math.$sqrt('1000001', Rounding.Down)).to.be.bignumber.equal('1000');
  257. expect(await this.math.$sqrt('1002000', Rounding.Down)).to.be.bignumber.equal('1000');
  258. expect(await this.math.$sqrt('1002001', Rounding.Down)).to.be.bignumber.equal('1001');
  259. expect(await this.math.$sqrt(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal(
  260. '340282366920938463463374607431768211455',
  261. );
  262. });
  263. it('rounds up', async function () {
  264. expect(await this.math.$sqrt('0', Rounding.Up)).to.be.bignumber.equal('0');
  265. expect(await this.math.$sqrt('1', Rounding.Up)).to.be.bignumber.equal('1');
  266. expect(await this.math.$sqrt('2', Rounding.Up)).to.be.bignumber.equal('2');
  267. expect(await this.math.$sqrt('3', Rounding.Up)).to.be.bignumber.equal('2');
  268. expect(await this.math.$sqrt('4', Rounding.Up)).to.be.bignumber.equal('2');
  269. expect(await this.math.$sqrt('144', Rounding.Up)).to.be.bignumber.equal('12');
  270. expect(await this.math.$sqrt('999999', Rounding.Up)).to.be.bignumber.equal('1000');
  271. expect(await this.math.$sqrt('1000000', Rounding.Up)).to.be.bignumber.equal('1000');
  272. expect(await this.math.$sqrt('1000001', Rounding.Up)).to.be.bignumber.equal('1001');
  273. expect(await this.math.$sqrt('1002000', Rounding.Up)).to.be.bignumber.equal('1001');
  274. expect(await this.math.$sqrt('1002001', Rounding.Up)).to.be.bignumber.equal('1001');
  275. expect(await this.math.$sqrt(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal(
  276. '340282366920938463463374607431768211456',
  277. );
  278. });
  279. });
  280. describe('log', function () {
  281. describe('log2', function () {
  282. it('rounds down', async function () {
  283. // For some reason calling .$log2() directly fails
  284. expect(await this.math.methods['$log2(uint256,uint8)']('0', Rounding.Down)).to.be.bignumber.equal('0');
  285. expect(await this.math.methods['$log2(uint256,uint8)']('1', Rounding.Down)).to.be.bignumber.equal('0');
  286. expect(await this.math.methods['$log2(uint256,uint8)']('2', Rounding.Down)).to.be.bignumber.equal('1');
  287. expect(await this.math.methods['$log2(uint256,uint8)']('3', Rounding.Down)).to.be.bignumber.equal('1');
  288. expect(await this.math.methods['$log2(uint256,uint8)']('4', Rounding.Down)).to.be.bignumber.equal('2');
  289. expect(await this.math.methods['$log2(uint256,uint8)']('5', Rounding.Down)).to.be.bignumber.equal('2');
  290. expect(await this.math.methods['$log2(uint256,uint8)']('6', Rounding.Down)).to.be.bignumber.equal('2');
  291. expect(await this.math.methods['$log2(uint256,uint8)']('7', Rounding.Down)).to.be.bignumber.equal('2');
  292. expect(await this.math.methods['$log2(uint256,uint8)']('8', Rounding.Down)).to.be.bignumber.equal('3');
  293. expect(await this.math.methods['$log2(uint256,uint8)']('9', Rounding.Down)).to.be.bignumber.equal('3');
  294. expect(await this.math.methods['$log2(uint256,uint8)'](MAX_UINT256, Rounding.Down)).to.be.bignumber.equal(
  295. '255',
  296. );
  297. });
  298. it('rounds up', async function () {
  299. // For some reason calling .$log2() directly fails
  300. expect(await this.math.methods['$log2(uint256,uint8)']('0', Rounding.Up)).to.be.bignumber.equal('0');
  301. expect(await this.math.methods['$log2(uint256,uint8)']('1', Rounding.Up)).to.be.bignumber.equal('0');
  302. expect(await this.math.methods['$log2(uint256,uint8)']('2', Rounding.Up)).to.be.bignumber.equal('1');
  303. expect(await this.math.methods['$log2(uint256,uint8)']('3', Rounding.Up)).to.be.bignumber.equal('2');
  304. expect(await this.math.methods['$log2(uint256,uint8)']('4', Rounding.Up)).to.be.bignumber.equal('2');
  305. expect(await this.math.methods['$log2(uint256,uint8)']('5', Rounding.Up)).to.be.bignumber.equal('3');
  306. expect(await this.math.methods['$log2(uint256,uint8)']('6', Rounding.Up)).to.be.bignumber.equal('3');
  307. expect(await this.math.methods['$log2(uint256,uint8)']('7', Rounding.Up)).to.be.bignumber.equal('3');
  308. expect(await this.math.methods['$log2(uint256,uint8)']('8', Rounding.Up)).to.be.bignumber.equal('3');
  309. expect(await this.math.methods['$log2(uint256,uint8)']('9', Rounding.Up)).to.be.bignumber.equal('4');
  310. expect(await this.math.methods['$log2(uint256,uint8)'](MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('256');
  311. });
  312. });
  313. describe('log10', function () {
  314. it('rounds down', async function () {
  315. expect(await this.math.$log10('0', Rounding.Down)).to.be.bignumber.equal('0');
  316. expect(await this.math.$log10('1', Rounding.Down)).to.be.bignumber.equal('0');
  317. expect(await this.math.$log10('2', Rounding.Down)).to.be.bignumber.equal('0');
  318. expect(await this.math.$log10('9', Rounding.Down)).to.be.bignumber.equal('0');
  319. expect(await this.math.$log10('10', Rounding.Down)).to.be.bignumber.equal('1');
  320. expect(await this.math.$log10('11', Rounding.Down)).to.be.bignumber.equal('1');
  321. expect(await this.math.$log10('99', Rounding.Down)).to.be.bignumber.equal('1');
  322. expect(await this.math.$log10('100', Rounding.Down)).to.be.bignumber.equal('2');
  323. expect(await this.math.$log10('101', Rounding.Down)).to.be.bignumber.equal('2');
  324. expect(await this.math.$log10('999', Rounding.Down)).to.be.bignumber.equal('2');
  325. expect(await this.math.$log10('1000', Rounding.Down)).to.be.bignumber.equal('3');
  326. expect(await this.math.$log10('1001', Rounding.Down)).to.be.bignumber.equal('3');
  327. expect(await this.math.$log10(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal('77');
  328. });
  329. it('rounds up', async function () {
  330. expect(await this.math.$log10('0', Rounding.Up)).to.be.bignumber.equal('0');
  331. expect(await this.math.$log10('1', Rounding.Up)).to.be.bignumber.equal('0');
  332. expect(await this.math.$log10('2', Rounding.Up)).to.be.bignumber.equal('1');
  333. expect(await this.math.$log10('9', Rounding.Up)).to.be.bignumber.equal('1');
  334. expect(await this.math.$log10('10', Rounding.Up)).to.be.bignumber.equal('1');
  335. expect(await this.math.$log10('11', Rounding.Up)).to.be.bignumber.equal('2');
  336. expect(await this.math.$log10('99', Rounding.Up)).to.be.bignumber.equal('2');
  337. expect(await this.math.$log10('100', Rounding.Up)).to.be.bignumber.equal('2');
  338. expect(await this.math.$log10('101', Rounding.Up)).to.be.bignumber.equal('3');
  339. expect(await this.math.$log10('999', Rounding.Up)).to.be.bignumber.equal('3');
  340. expect(await this.math.$log10('1000', Rounding.Up)).to.be.bignumber.equal('3');
  341. expect(await this.math.$log10('1001', Rounding.Up)).to.be.bignumber.equal('4');
  342. expect(await this.math.$log10(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('78');
  343. });
  344. });
  345. describe('log256', function () {
  346. it('rounds down', async function () {
  347. expect(await this.math.$log256('0', Rounding.Down)).to.be.bignumber.equal('0');
  348. expect(await this.math.$log256('1', Rounding.Down)).to.be.bignumber.equal('0');
  349. expect(await this.math.$log256('2', Rounding.Down)).to.be.bignumber.equal('0');
  350. expect(await this.math.$log256('255', Rounding.Down)).to.be.bignumber.equal('0');
  351. expect(await this.math.$log256('256', Rounding.Down)).to.be.bignumber.equal('1');
  352. expect(await this.math.$log256('257', Rounding.Down)).to.be.bignumber.equal('1');
  353. expect(await this.math.$log256('65535', Rounding.Down)).to.be.bignumber.equal('1');
  354. expect(await this.math.$log256('65536', Rounding.Down)).to.be.bignumber.equal('2');
  355. expect(await this.math.$log256('65537', Rounding.Down)).to.be.bignumber.equal('2');
  356. expect(await this.math.$log256(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal('31');
  357. });
  358. it('rounds up', async function () {
  359. expect(await this.math.$log256('0', Rounding.Up)).to.be.bignumber.equal('0');
  360. expect(await this.math.$log256('1', Rounding.Up)).to.be.bignumber.equal('0');
  361. expect(await this.math.$log256('2', Rounding.Up)).to.be.bignumber.equal('1');
  362. expect(await this.math.$log256('255', Rounding.Up)).to.be.bignumber.equal('1');
  363. expect(await this.math.$log256('256', Rounding.Up)).to.be.bignumber.equal('1');
  364. expect(await this.math.$log256('257', Rounding.Up)).to.be.bignumber.equal('2');
  365. expect(await this.math.$log256('65535', Rounding.Up)).to.be.bignumber.equal('2');
  366. expect(await this.math.$log256('65536', Rounding.Up)).to.be.bignumber.equal('2');
  367. expect(await this.math.$log256('65537', Rounding.Up)).to.be.bignumber.equal('3');
  368. expect(await this.math.$log256(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('32');
  369. });
  370. });
  371. });
  372. });