Math.test.js 20 KB

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