From cadbb1992736cfa40b40a2d1ef9d5737a55a58bf Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 01:16:44 +0300 Subject: [PATCH 01/16] refactor: change `sizeInBytes` into `desiredBits` --- src/libraries/ArithmeticLib.sol | 12 ++++++------ src/types/SignedInt.sol | 4 ++-- src/types/UnsignedInt.sol | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/libraries/ArithmeticLib.sol b/src/libraries/ArithmeticLib.sol index 1215b7c..f5b3e99 100644 --- a/src/libraries/ArithmeticLib.sol +++ b/src/libraries/ArithmeticLib.sol @@ -2,20 +2,20 @@ pragma solidity 0.8.26; library ArithmeticLib { - function convertWithSize(bytes32 b, uint16 sizeInBytes, uint8 sizeInBits) public pure returns (bytes32 to) { + function convertWithSize(bytes32 b, uint16 desiredBits, uint8 sizeInBits) public pure returns (bytes32 to) { assembly { - /// TODO: add custom error here - sizeInBytes cannot be zero/cannot be bigger than 256 bits - if or(eq(sizeInBytes, 0), gt(sizeInBytes, 0x100)) { revert(0x00, 0x00) } + /// TODO: add custom error here - desiredBits cannot be zero/cannot be bigger than 256 bits + if or(eq(desiredBits, 0), gt(desiredBits, 0x100)) { revert(0x00, 0x00) } - /// TODO: add custom error here - sizeInBytes has to be multiple of eight - if gt(mod(sizeInBytes, 0x8), 0) { revert(0x00, 0x00) } + /// TODO: add custom error here - desiredBits has to be multiple of eight + if gt(mod(desiredBits, 0x8), 0) { revert(0x00, 0x00) } } uint8 sizeCap = cap(sizeInBits); assembly { /// TODO: add custom error here - size cap cannot be bigger than given size in bytes - if gt(sizeCap, sizeInBytes) { revert(0x00, 0x00) } + if gt(sizeCap, desiredBits) { revert(0x00, 0x00) } to := b } diff --git a/src/types/SignedInt.sol b/src/types/SignedInt.sol index 49c52f8..edf2407 100644 --- a/src/types/SignedInt.sol +++ b/src/types/SignedInt.sol @@ -60,8 +60,8 @@ function expSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt r using SignedIntLib for SignedInt global; library SignedIntLib { - function convertWithSize(SignedInt u, uint16 sizeInBytes) public pure returns (int256 to) { - bytes32 result = ArithmeticLib.convertWithSize(SignedInt.unwrap(u), sizeInBytes, sizeInBits(u)); + function convertWithSize(SignedInt u, uint16 desiredBits) public pure returns (int256 to) { + bytes32 result = ArithmeticLib.convertWithSize(SignedInt.unwrap(u), desiredBits, sizeInBits(u)); assembly { to := result diff --git a/src/types/UnsignedInt.sol b/src/types/UnsignedInt.sol index 1a8305a..ef65a4f 100644 --- a/src/types/UnsignedInt.sol +++ b/src/types/UnsignedInt.sol @@ -75,8 +75,8 @@ function expUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (Unsig using UnsignedIntLib for UnsignedInt global; library UnsignedIntLib { - function convertWithSize(UnsignedInt u, uint16 sizeInBytes) public pure returns (uint256 to) { - bytes32 result = ArithmeticLib.convertWithSize(UnsignedInt.unwrap(u), sizeInBytes, sizeInBits(u)); + function convertWithSize(UnsignedInt u, uint16 desiredBits) public pure returns (uint256 to) { + bytes32 result = ArithmeticLib.convertWithSize(UnsignedInt.unwrap(u), desiredBits, sizeInBits(u)); assembly { to := result From 69494d2649f3d6d69a3841694a965a46533ba0c9 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 01:23:36 +0300 Subject: [PATCH 02/16] fix: change `bitSize` parameter type as uint16 --- src/libraries/ArithmeticLib.sol | 9 ++++++--- src/types/SignedInt.sol | 2 +- src/types/UnsignedInt.sol | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libraries/ArithmeticLib.sol b/src/libraries/ArithmeticLib.sol index f5b3e99..04fca9d 100644 --- a/src/libraries/ArithmeticLib.sol +++ b/src/libraries/ArithmeticLib.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.26; library ArithmeticLib { - function convertWithSize(bytes32 b, uint16 desiredBits, uint8 sizeInBits) public pure returns (bytes32 to) { + function convertWithSize(bytes32 b, uint16 desiredBits, uint16 sizeInBits) public pure returns (bytes32 to) { assembly { /// TODO: add custom error here - desiredBits cannot be zero/cannot be bigger than 256 bits if or(eq(desiredBits, 0), gt(desiredBits, 0x100)) { revert(0x00, 0x00) } @@ -11,7 +11,7 @@ library ArithmeticLib { if gt(mod(desiredBits, 0x8), 0) { revert(0x00, 0x00) } } - uint8 sizeCap = cap(sizeInBits); + uint16 sizeCap = cap(sizeInBits); assembly { /// TODO: add custom error here - size cap cannot be bigger than given size in bytes @@ -22,8 +22,11 @@ library ArithmeticLib { } /// NOTE: finds the nearest bit cap in multiple of eight - function cap(uint8 bitSize) public pure returns (uint8 res) { + function cap(uint16 bitSize) public pure returns (uint16 res) { assembly { + /// TODO: add custom error here - bitSize cannot be zero/cannot be bigger than 256 bits + if or(eq(bitSize, 0), gt(bitSize, 0x100)) { revert(0x00, 0x00) } + for { let i := 0x0 } lt(i, 0x21) { i := add(i, 1) } { let tmp := mul(add(i, 1), 8) if or(gt(tmp, bitSize), eq(tmp, bitSize)) { diff --git a/src/types/SignedInt.sol b/src/types/SignedInt.sol index edf2407..962b95c 100644 --- a/src/types/SignedInt.sol +++ b/src/types/SignedInt.sol @@ -68,7 +68,7 @@ library SignedIntLib { } } - function sizeInBits(SignedInt s) public pure returns (uint8 size) { + function sizeInBits(SignedInt s) public pure returns (uint16 size) { assembly { let tmp let isNegative := sgt(0, s) diff --git a/src/types/UnsignedInt.sol b/src/types/UnsignedInt.sol index ef65a4f..f2c3269 100644 --- a/src/types/UnsignedInt.sol +++ b/src/types/UnsignedInt.sol @@ -83,7 +83,7 @@ library UnsignedIntLib { } } - function sizeInBits(UnsignedInt u) public pure returns (uint8 size) { + function sizeInBits(UnsignedInt u) public pure returns (uint16 size) { assembly { let tmp for { let i := 0x1f } gt(i, 0x0) { i := sub(i, 1) } { From 71850ea4d69cbfaa920d79b3490cbe4689625ae3 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 04:12:53 +0300 Subject: [PATCH 03/16] refactor(ArithmeticLib): add missing custom errors --- src/libraries/ArithmeticLib.sol | 47 +++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/libraries/ArithmeticLib.sol b/src/libraries/ArithmeticLib.sol index 04fca9d..123ad00 100644 --- a/src/libraries/ArithmeticLib.sol +++ b/src/libraries/ArithmeticLib.sol @@ -4,18 +4,38 @@ pragma solidity 0.8.26; library ArithmeticLib { function convertWithSize(bytes32 b, uint16 desiredBits, uint16 sizeInBits) public pure returns (bytes32 to) { assembly { - /// TODO: add custom error here - desiredBits cannot be zero/cannot be bigger than 256 bits - if or(eq(desiredBits, 0), gt(desiredBits, 0x100)) { revert(0x00, 0x00) } + if eq(desiredBits, 0) { + /// @dev bytes4(keccak256("CannotBeZero(uint256)")) => 0xfef3c17b + mstore(0x80, 0xfef3c17b) + mstore(0xa0, desiredBits) + revert(0x9c, 0x24) + } + + if gt(desiredBits, 0x100) { + /// @dev bytes4(keccak256("ExceedsTheBound(uint256)")) => 0x95646250 + mstore(0x80, 0x95646250) + mstore(0xa0, desiredBits) + revert(0x9c, 0x24) + } - /// TODO: add custom error here - desiredBits has to be multiple of eight - if gt(mod(desiredBits, 0x8), 0) { revert(0x00, 0x00) } + if gt(mod(desiredBits, 0x8), 0) { + /// @dev bytes4(keccak256("MustBeAMultipleOfEight(uint256)")) => 0x36780089 + mstore(0x80, 0x36780089) + mstore(0xa0, desiredBits) + revert(0x9c, 0x24) + } } uint16 sizeCap = cap(sizeInBits); assembly { - /// TODO: add custom error here - size cap cannot be bigger than given size in bytes - if gt(sizeCap, desiredBits) { revert(0x00, 0x00) } + if lt(desiredBits, sizeCap) { + /// @dev bytes4(keccak256("MismatchedSizes(uint256,uint256)")) => 0x01e8c4a5 + mstore(0x80, 0x01e8c4a5) + mstore(0xa0, desiredBits) + mstore(0xc0, sizeCap) + revert(0x9c, 0x44) + } to := b } @@ -24,8 +44,19 @@ library ArithmeticLib { /// NOTE: finds the nearest bit cap in multiple of eight function cap(uint16 bitSize) public pure returns (uint16 res) { assembly { - /// TODO: add custom error here - bitSize cannot be zero/cannot be bigger than 256 bits - if or(eq(bitSize, 0), gt(bitSize, 0x100)) { revert(0x00, 0x00) } + if eq(bitSize, 0) { + /// @dev bytes4(keccak256("CannotBeZero(uint256)")) => 0xfef3c17b + mstore(0x80, 0xfef3c17b) + mstore(0xa0, bitSize) + revert(0x9c, 0x24) + } + + if gt(bitSize, 0x100) { + /// @dev bytes4(keccak256("ExceedsTheBound(uint256)")) => 0x95646250 + mstore(0x80, 0x95646250) + mstore(0xa0, bitSize) + revert(0x9c, 0x24) + } for { let i := 0x0 } lt(i, 0x21) { i := add(i, 1) } { let tmp := mul(add(i, 1), 8) From 793a3e19ded4c6e728e5a09e404164159e5c25b3 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 04:13:11 +0300 Subject: [PATCH 04/16] test(ArithmeticLib): update test cases --- test/ArithmeticLib.t.sol | 99 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/test/ArithmeticLib.t.sol b/test/ArithmeticLib.t.sol index b7d9021..76c371d 100644 --- a/test/ArithmeticLib.t.sol +++ b/test/ArithmeticLib.t.sol @@ -4,4 +4,101 @@ pragma solidity 0.8.26; import { ArithmeticLib } from "../src/libraries/ArithmeticLib.sol"; import { Test, console } from "forge-std/Test.sol"; -contract ArithmeticLibTest is Test { } +import { SignedInt, SignedIntLib } from "../src/types/SignedInt.sol"; +import { UnsignedInt, UnsignedIntLib } from "../src/types/UnsignedInt.sol"; + +contract ArithmeticLibTest is Test { + error CannotBeZero(uint256); + error ExceedsTheBound(uint256); + error MustBeAMultipleOfEight(uint256); + error MismatchedSizes(uint256, uint256); + + function test_ConvertWithSize_Uint() public pure { + uint256[3][3] memory cases = [ + [uint256(12), uint256(64), uint256(8)], + [uint256(257), uint256(32), uint256(16)], + [uint256(UINT256_MAX), uint256(256), uint256(256)] + ]; + + uint8 i; + for (i; i < cases.length; i++) { + uint256 v = cases[i][0]; + assertEq(ArithmeticLib.convertWithSize(bytes32(v), uint16(cases[i][1]), uint16(cases[i][2])), bytes32(v)); + } + } + + function test_ConvertWithSize_Int() public pure { + int256[4] memory values = [int256(-1), 256, 0, int256(-257)]; + + uint256[2][4] memory cases = [ + [uint256(64), uint256(8)], + [uint256(32), uint256(16)], + [uint256(256), uint256(8)], + [uint256(128), uint256(16)] + ]; + + uint8 i; + for (i; i < cases.length; i++) { + int256 v = values[i]; + bytes32 tmp; + + assembly { + tmp := v + } + + assertEq(ArithmeticLib.convertWithSize(tmp, uint16(cases[i][0]), uint16(cases[i][1])), tmp); + } + } + + function test_Cap() public pure { + uint16[2][12] memory cases = [ + [uint16(256), uint16(256)], + [uint16(242), uint16(248)], + [uint16(186), uint16(192)], + [uint16(146), uint16(152)], + [uint16(140), uint16(144)], + [uint16(102), uint16(104)], + [uint16(92), uint16(96)], + [uint16(64), uint16(64)], + [uint16(48), uint16(48)], + [uint16(28), uint16(32)], + [uint16(16), uint16(16)], + [uint16(4), uint16(8)] + ]; + + uint8 i; + for (i; i < cases.length; i++) { + assertEq(ArithmeticLib.cap(cases[i][0]), ArithmeticLib.cap(cases[i][1])); + } + } + + function test_RevertIf_DesiredBitsIsEqualToZero_ConvertWithSize() public { + vm.expectRevert(abi.encodeWithSelector(CannotBeZero.selector, 0)); + ArithmeticLib.convertWithSize(bytes32(uint256(1)), 0, 8); + } + + function test_RevertIf_DesiredBitsIsBiggerThan256Bits_ConvertWithSize() public { + vm.expectRevert(abi.encodeWithSelector(ExceedsTheBound.selector, 257)); + ArithmeticLib.convertWithSize(bytes32(uint256(1)), 257, 8); + } + + function test_RevertIf_DesiredBitsIsNotDivisibleToEight_ConvertWithSize() public { + vm.expectRevert(abi.encodeWithSelector(MustBeAMultipleOfEight.selector, 107)); + ArithmeticLib.convertWithSize(bytes32(uint256(1)), 107, 8); + } + + function test_RevertWhen_DesiredBitsIsBiggerThanSizeCap_ConvertWithSize() public { + vm.expectRevert(abi.encodeWithSelector(MismatchedSizes.selector, 32, 64)); + ArithmeticLib.convertWithSize(bytes32(uint256(1)), 32, 64); + } + + function test_RevertIf_BitSizeIsEqualToZero_Cap() public { + vm.expectRevert(abi.encodeWithSelector(CannotBeZero.selector, 0)); + ArithmeticLib.cap(0); + } + + function test_RevertIf_BitSizeIsBiggerThan256Bits_Cap() public { + vm.expectRevert(abi.encodeWithSelector(ExceedsTheBound.selector, 257)); + ArithmeticLib.cap(257); + } +} From 51ab7b5b0bcc0819d56351c243cb30cc9db10ae6 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 04:23:15 +0300 Subject: [PATCH 05/16] test(SignedIntLibTest): create test suite --- test/SignedIntLib.t.sol | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/SignedIntLib.t.sol diff --git a/test/SignedIntLib.t.sol b/test/SignedIntLib.t.sol new file mode 100644 index 0000000..6ef5cb1 --- /dev/null +++ b/test/SignedIntLib.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { ArithmeticLib } from "../src/libraries/ArithmeticLib.sol"; +import { Test, console } from "forge-std/Test.sol"; + +contract SignedIntLibTest is Test { + function test_ConvertWithSize() public view { } + + function test_SizeInBits() public view { } + + function test_AddSignedInt() public view { } + + function test_SubSignedInt() public view { } + + function test_MulSignedInt() public view { } + + function test_DivSignedInt() public view { } + + function test_ModSignedInt() public view { } + + function test_ExpSignedInt() public view { } +} From 62055c2b447dbd4d9df345d0751e6d7245a20e28 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 04:23:23 +0300 Subject: [PATCH 06/16] test(UnsignedIntLibTest): create test suite --- test/UnsignedIntLib.t.sol | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/UnsignedIntLib.t.sol diff --git a/test/UnsignedIntLib.t.sol b/test/UnsignedIntLib.t.sol new file mode 100644 index 0000000..ab5fb56 --- /dev/null +++ b/test/UnsignedIntLib.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { ArithmeticLib } from "../src/libraries/ArithmeticLib.sol"; +import { Test, console } from "forge-std/Test.sol"; + +contract UnsignedIntLibTest is Test { + function test_ConvertWithSize() public view { } + + function test_SizeInBits() public view { } + + function test_AddUnsignedInt() public view { } + + function test_SubUnsignedInt() public view { } + + function test_MulUnsignedInt() public view { } + + function test_DivUnsignedInt() public view { } + + function test_ModUnsignedInt() public view { } + + function test_ExpUnsignedInt() public view { } +} From c55b1791beeced290c1bd2999969b329bed9a892 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 18:52:56 +0300 Subject: [PATCH 07/16] fix(UnsignedInt): fix wrong conditions `mulUnsignedInt` function is updated, case 1 would make result zero and default one would be multiplication. --- src/types/UnsignedInt.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/UnsignedInt.sol b/src/types/UnsignedInt.sol index f2c3269..871028e 100644 --- a/src/types/UnsignedInt.sol +++ b/src/types/UnsignedInt.sol @@ -39,8 +39,8 @@ function subUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (Unsig function mulUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (UnsignedInt res) { assembly { switch or(iszero(left), iszero(right)) - case 0 { res := 0 } - case 1 { res := mul(left, right) } + case 1 { res := 0 } + default { res := mul(left, right) } } } From 765047776e2358c0ebd2e4bb9429a74b73b041e3 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 18:53:35 +0300 Subject: [PATCH 08/16] fix(SignedInt): fix wrong conditions `mulSignedInt` function is updated, case 1 would make result zero and default one would be multiplication. --- src/types/SignedInt.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/SignedInt.sol b/src/types/SignedInt.sol index 962b95c..02f471d 100644 --- a/src/types/SignedInt.sol +++ b/src/types/SignedInt.sol @@ -29,8 +29,8 @@ function subSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt r function mulSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt res) { assembly { switch or(iszero(left), iszero(right)) - case 0 { res := 0 } - case 1 { res := mul(left, right) } + case 1 { res := 0 } + default { res := mul(left, right) } } } From e482fac1c6b0ae4ee2bb0e46e3728ecc47de2817 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 19:27:51 +0300 Subject: [PATCH 09/16] refactor(UnsignedInt): add custom errors --- src/types/UnsignedInt.sol | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/types/UnsignedInt.sol b/src/types/UnsignedInt.sol index 871028e..9b6e612 100644 --- a/src/types/UnsignedInt.sol +++ b/src/types/UnsignedInt.sol @@ -19,8 +19,9 @@ function addUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (Unsig res := add(left, right) if lt(res, left) { - /// TODO: add custom error here - overflow check - revert(0x00, 0x00) + /// @dev bytes4(keccak256("Overflow()")) => 0x35278d12 + mstore(0x80, 0x35278d12) + revert(0x9c, 0x04) } } } @@ -28,8 +29,9 @@ function addUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (Unsig function subUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (UnsignedInt res) { assembly { if gt(right, left) { - /// TODO: add custom error here - underflow check - revert(0x00, 0x00) + /// @dev bytes4(keccak256("Underflow()")) => 0xcaccb6d9 + mstore(0x80, 0xcaccb6d9) + revert(0x9c, 0x04) } res := sub(left, right) @@ -47,8 +49,9 @@ function mulUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (Unsig function divUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (UnsignedInt res) { assembly { if eq(right, 0) { - /// TODO: add custom error here - division by zero check - revert(0x00, 0x00) + /// @dev bytes4(keccak256("DivisionByZero()")) => 0x23d359a3 + mstore(0x80, 0x23d359a3) + revert(0x9c, 0x04) } res := div(left, right) @@ -58,8 +61,9 @@ function divUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (Unsig function modUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (UnsignedInt res) { assembly { if iszero(right) { - /// TODO: add custom error here - modulo by zero check - revert(0x00, 0x00) + /// @dev bytes4(keccak256("DivisionByZero()")) => 0x23d359a3 + mstore(0x80, 0x23d359a3) + revert(0x9c, 0x04) } res := mod(left, right) From 2a19472d8ceb4516313b4220a8adbb42b9302c4d Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 19:28:05 +0300 Subject: [PATCH 10/16] test(UnsignedIntLib): update test cases --- test/UnsignedIntLib.t.sol | 160 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 152 insertions(+), 8 deletions(-) diff --git a/test/UnsignedIntLib.t.sol b/test/UnsignedIntLib.t.sol index ab5fb56..4fbe6a8 100644 --- a/test/UnsignedIntLib.t.sol +++ b/test/UnsignedIntLib.t.sol @@ -2,22 +2,166 @@ pragma solidity 0.8.26; import { ArithmeticLib } from "../src/libraries/ArithmeticLib.sol"; + +import { UnsignedInt, UnsignedIntLib } from "../src/types/UnsignedInt.sol"; import { Test, console } from "forge-std/Test.sol"; contract UnsignedIntLibTest is Test { - function test_ConvertWithSize() public view { } + error Overflow(); + error Underflow(); + error DivisionByZero(); + + function test_ConvertWithSize() public pure { + uint256 u_int = 1024; + UnsignedInt u = UnsignedInt.wrap(bytes32(u_int)); + + assertEq(uint32(u.convertWithSize(16)), uint32(u_int)); + } + + function test_SizeInBits() public pure { + /// NOTE: The number 1024 is 0x400 in bytes form, + /// and its size is 12 bits also its upper bound is 16 bits. + uint256 u_int = 1024; + UnsignedInt u = UnsignedInt.wrap(bytes32(u_int)); + + assertEq(u.sizeInBits(), 12); + } + + function test_AddUnsignedInt() public pure { + uint256 u_int_1 = 1024; + uint256 u_int_2 = 2048; + UnsignedInt expected = UnsignedInt.wrap(bytes32(uint256(3072))); + UnsignedInt u_1 = UnsignedInt.wrap(bytes32(u_int_1)); + UnsignedInt u_2 = UnsignedInt.wrap(bytes32(u_int_2)); + UnsignedInt result = u_1 + u_2; + + assertEq(UnsignedInt.unwrap(expected), UnsignedInt.unwrap(result)); + } + + function test_SubUnsignedInt() public pure { + uint256 u_int_1 = 2048; + uint256 u_int_2 = 1024; + UnsignedInt expected = UnsignedInt.wrap(bytes32(uint256(1024))); + UnsignedInt u_1 = UnsignedInt.wrap(bytes32(u_int_1)); + UnsignedInt u_2 = UnsignedInt.wrap(bytes32(u_int_2)); + UnsignedInt result = u_1 - u_2; + + assertEq(UnsignedInt.unwrap(expected), UnsignedInt.unwrap(result)); + } + + function test_MulUnsignedInt() public pure { + uint256 u_int_1 = 2048; + uint256 u_int_2 = 2; + UnsignedInt expected = UnsignedInt.wrap(bytes32(uint256(4096))); + UnsignedInt u_1 = UnsignedInt.wrap(bytes32(u_int_1)); + UnsignedInt u_2 = UnsignedInt.wrap(bytes32(u_int_2)); + UnsignedInt result = u_1 * u_2; + + assertEq(UnsignedInt.unwrap(expected), UnsignedInt.unwrap(result)); + } + + function test_DivUnsignedInt() public pure { + uint256 u_int_1 = 2048; + uint256 u_int_2 = 1024; + UnsignedInt expected = UnsignedInt.wrap(bytes32(uint256(2))); + UnsignedInt u_1 = UnsignedInt.wrap(bytes32(u_int_1)); + UnsignedInt u_2 = UnsignedInt.wrap(bytes32(u_int_2)); + UnsignedInt result = u_1 / u_2; + + assertEq(UnsignedInt.unwrap(expected), UnsignedInt.unwrap(result)); + } + + function test_ModUnsignedInt() public pure { + uint256 u_int_1 = 2048; + uint256 u_int_2 = 8; + UnsignedInt expected = UnsignedInt.wrap(0x00); + UnsignedInt u_1 = UnsignedInt.wrap(bytes32(u_int_1)); + UnsignedInt u_2 = UnsignedInt.wrap(bytes32(u_int_2)); + UnsignedInt result = u_1 % u_2; + + assertEq(UnsignedInt.unwrap(expected), UnsignedInt.unwrap(result)); + } + + function test_ExpUnsignedInt() public pure { + uint256 u_int_1 = 4; + uint256 u_int_2 = 2; + UnsignedInt expected = UnsignedInt.wrap(bytes32(uint256(16))); + UnsignedInt u_1 = UnsignedInt.wrap(bytes32(u_int_1)); + UnsignedInt u_2 = UnsignedInt.wrap(bytes32(u_int_2)); + UnsignedInt result = u_1 ^ u_2; + + assertEq(UnsignedInt.unwrap(expected), UnsignedInt.unwrap(result)); + } + + function test_RevertWhen_DesiredBitsAreZero_ConvertWithSize() public { + uint256 u_int = 1024; + UnsignedInt u = UnsignedInt.wrap(bytes32(u_int)); + + vm.expectRevert(); + u.convertWithSize(0); + } + + function test_RevertWhen_DesiredBitsAreBiggerThan256Bits_ConvertWithSize() public { + uint256 u_int = 1024; + UnsignedInt u = UnsignedInt.wrap(bytes32(u_int)); + + vm.expectRevert(); + u.convertWithSize(257); + } + + function test_RevertWhen_DesiredBitsAreNotMultipleOfEight_ConvertWithSize() public { + uint256 u_int = 1024; + UnsignedInt u = UnsignedInt.wrap(bytes32(u_int)); + + vm.expectRevert(); + u.convertWithSize(33); + } + + function test_RevertWhen_DesiredBitsAreLessThanDataBitSize_ConvertWithSize() public { + uint256 u_int = 1024; + UnsignedInt u = UnsignedInt.wrap(bytes32(u_int)); + + vm.expectRevert(); + u.convertWithSize(8); + } + + function test_RevertWhen_ResultOverflows_AddUnsignedInt() public { + uint256 u_int_1 = UINT256_MAX; + uint256 u_int_2 = 1; + UnsignedInt u_1 = UnsignedInt.wrap(bytes32(u_int_1)); + UnsignedInt u_2 = UnsignedInt.wrap(bytes32(u_int_2)); - function test_SizeInBits() public view { } + vm.expectRevert(Overflow.selector); + u_1 + u_2; + } - function test_AddUnsignedInt() public view { } + function test_RevertWhen_ResultUnderflows_SubUnsignedInt() public { + uint256 u_int_1 = 1; + uint256 u_int_2 = 2; + UnsignedInt u_1 = UnsignedInt.wrap(bytes32(u_int_1)); + UnsignedInt u_2 = UnsignedInt.wrap(bytes32(u_int_2)); - function test_SubUnsignedInt() public view { } + vm.expectRevert(Underflow.selector); + u_1 - u_2; + } - function test_MulUnsignedInt() public view { } + function test_RevertIf_RightValueIsZero_DivUnsignedInt() public { + uint256 u_int_1 = 1; + uint256 u_int_2 = 0; + UnsignedInt u_1 = UnsignedInt.wrap(bytes32(u_int_1)); + UnsignedInt u_2 = UnsignedInt.wrap(bytes32(u_int_2)); - function test_DivUnsignedInt() public view { } + vm.expectRevert(DivisionByZero.selector); + u_1 / u_2; + } - function test_ModUnsignedInt() public view { } + function test_RevertIf_RightValueIsZero_ModUnsignedInt() public { + uint256 u_int_1 = 1; + uint256 u_int_2 = 0; + UnsignedInt u_1 = UnsignedInt.wrap(bytes32(u_int_1)); + UnsignedInt u_2 = UnsignedInt.wrap(bytes32(u_int_2)); - function test_ExpUnsignedInt() public view { } + vm.expectRevert(DivisionByZero.selector); + u_1 % u_2; + } } From efb089bf45ee92409e20d6dc28e5306533e24309 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 21:26:13 +0300 Subject: [PATCH 11/16] fix(SignedInt): update `divSignedInt` function Division of left number by right number can differ sometimes due to the different sign of numbers. The solution of checking number signs is implemented. --- src/types/SignedInt.sol | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/types/SignedInt.sol b/src/types/SignedInt.sol index 02f471d..bb96faa 100644 --- a/src/types/SignedInt.sol +++ b/src/types/SignedInt.sol @@ -36,7 +36,22 @@ function mulSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt r function divSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt res) { assembly { - res := div(left, right) + if eq(right, 0) { + /// @dev bytes4(keccak256("DivisionByZero()")) => 0x23d359a3 + mstore(0x80, 0x23d359a3) + revert(0x9c, 0x04) + } + + let leftNeg := slt(left, 0) + let rightNeg := slt(right, 0) + let sign := xor(leftNeg, rightNeg) + + if leftNeg { left := sub(0, left) } + if rightNeg { right := sub(0, right) } + + let absRes := div(left, right) + res := absRes + if sign { res := sub(0, absRes) } } } From a3a92b3d75d576dccec2a6d12ea441c126df04a4 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 21:31:25 +0300 Subject: [PATCH 12/16] fix(SignedInt): update `modSignedInt` function The sign of number control is implemented. Also, the custom error `DivisionByZero` is implemented. --- src/types/SignedInt.sol | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/types/SignedInt.sol b/src/types/SignedInt.sol index bb96faa..bfe4edb 100644 --- a/src/types/SignedInt.sol +++ b/src/types/SignedInt.sol @@ -57,12 +57,19 @@ function divSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt r function modSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt res) { assembly { - if iszero(right) { - /// TODO: add custom error here - modulo by zero check - revert(0x00, 0x00) + if eq(right, 0) { + /// @dev bytes4(keccak256("DivisionByZero()")) => 0x23d359a3 + mstore(0x80, 0x23d359a3) + revert(0x9c, 0x04) } - res := mod(left, right) + let leftNeg := slt(left, 0) + if leftNeg { left := sub(0, left) } + if slt(right, 0) { right := sub(0, right) } + + let absRes := mod(left, right) + res := absRes + if leftNeg { res := sub(0, absRes) } } } From 25c88b1d586904f102fde6ac6f9ef9e79462187b Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 21:32:27 +0300 Subject: [PATCH 13/16] fix(SignedInt): update `expSignedInt` function The sign of number control is implemented for exponentiation. Also, the custom error `NegativeExponent` is implemented. --- src/types/SignedInt.sol | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/types/SignedInt.sol b/src/types/SignedInt.sol index bfe4edb..334a039 100644 --- a/src/types/SignedInt.sol +++ b/src/types/SignedInt.sol @@ -75,7 +75,18 @@ function modSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt r function expSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt res) { assembly { - res := exp(left, right) + if slt(right, 0) { + /// bytes4(keccak256("NegativeExponent()")) + mstore(0x80, 0xe782e44b) + revert(0x9c, 0x04) + } + + let leftNeg := slt(left, 0) + let absLeft := left + + res := exp(absLeft, right) + + if leftNeg { res := sub(0, res) } } } From 3c048db57ab527af8980e42466d875e838912e8c Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 21:40:00 +0300 Subject: [PATCH 14/16] test(UnsignedIntLib): update test suite --- test/UnsignedIntLib.t.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/UnsignedIntLib.t.sol b/test/UnsignedIntLib.t.sol index 4fbe6a8..01cba73 100644 --- a/test/UnsignedIntLib.t.sol +++ b/test/UnsignedIntLib.t.sol @@ -94,24 +94,21 @@ contract UnsignedIntLibTest is Test { } function test_RevertWhen_DesiredBitsAreZero_ConvertWithSize() public { - uint256 u_int = 1024; - UnsignedInt u = UnsignedInt.wrap(bytes32(u_int)); + UnsignedInt u = UnsignedInt.wrap(0x0); vm.expectRevert(); u.convertWithSize(0); } function test_RevertWhen_DesiredBitsAreBiggerThan256Bits_ConvertWithSize() public { - uint256 u_int = 1024; - UnsignedInt u = UnsignedInt.wrap(bytes32(u_int)); + UnsignedInt u = UnsignedInt.wrap(0x0); vm.expectRevert(); u.convertWithSize(257); } function test_RevertWhen_DesiredBitsAreNotMultipleOfEight_ConvertWithSize() public { - uint256 u_int = 1024; - UnsignedInt u = UnsignedInt.wrap(bytes32(u_int)); + UnsignedInt u = UnsignedInt.wrap(0x0); vm.expectRevert(); u.convertWithSize(33); From 555f7a230145a9f3a32c21f13aaa283f73d0f346 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 21:40:32 +0300 Subject: [PATCH 15/16] test(SignedIntLib): update test cases --- test/SignedIntLib.t.sol | 188 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 180 insertions(+), 8 deletions(-) diff --git a/test/SignedIntLib.t.sol b/test/SignedIntLib.t.sol index 6ef5cb1..4fc2a32 100644 --- a/test/SignedIntLib.t.sol +++ b/test/SignedIntLib.t.sol @@ -2,22 +2,194 @@ pragma solidity 0.8.26; import { ArithmeticLib } from "../src/libraries/ArithmeticLib.sol"; + +import { SignedInt, SignedIntLib } from "../src/types/SignedInt.sol"; import { Test, console } from "forge-std/Test.sol"; contract SignedIntLibTest is Test { - function test_ConvertWithSize() public view { } + error Overflow(); + error Underflow(); + error DivisionByZero(); + error NegativeExponent(); + + function test_ConvertWithSize() public pure { + int256 s_int = -1024; + bytes32 s_int_bytes = toBytes(s_int); + + SignedInt s = SignedInt.wrap(s_int_bytes); + + assertEq(int32(s.convertWithSize(16)), int32(s_int)); + } + + function test_SizeInBits() public pure { + /// NOTE: The negative number -1024 is 0xfff..c00 in bytes form, + /// and its size is 12 bits also its upper bound is 16 bits. + int256 s_int = -1024; + bytes32 s_int_bytes = toBytes(s_int); + SignedInt s = SignedInt.wrap(s_int_bytes); + + assertEq(s.sizeInBits(), 12); + } + + function test_AddUnsignedInt() public pure { + int256 s_int_1 = -1024; + bytes32 s_int_1_bytes = toBytes(s_int_1); + int256 s_int_2 = -2048; + bytes32 s_int_2_bytes = toBytes(s_int_2); + bytes32 expected_in_bytes = toBytes(-3072); + + SignedInt expected = SignedInt.wrap(expected_in_bytes); + SignedInt u_1 = SignedInt.wrap(s_int_1_bytes); + SignedInt u_2 = SignedInt.wrap(s_int_2_bytes); + SignedInt result = u_1 + u_2; + + assertEq(SignedInt.unwrap(expected), SignedInt.unwrap(result)); + } + + function test_SubUnsignedInt() public pure { + int256 s_int_1 = -1024; + bytes32 s_int_1_bytes = toBytes(s_int_1); + int256 s_int_2 = -2048; + bytes32 s_int_2_bytes = toBytes(s_int_2); + bytes32 expected_in_bytes = toBytes(1024); + + SignedInt expected = SignedInt.wrap(expected_in_bytes); + SignedInt u_1 = SignedInt.wrap(s_int_1_bytes); + SignedInt u_2 = SignedInt.wrap(s_int_2_bytes); + SignedInt result = u_1 - u_2; + + assertEq(SignedInt.unwrap(expected), SignedInt.unwrap(result)); + } + + function test_MulUnsignedInt() public pure { + int256 s_int_1 = -2048; + bytes32 s_int_1_bytes = toBytes(s_int_1); + int256 s_int_2 = -2; + bytes32 s_int_2_bytes = toBytes(s_int_2); + bytes32 expected_in_bytes = toBytes(4096); + + SignedInt expected = SignedInt.wrap(expected_in_bytes); + SignedInt u_1 = SignedInt.wrap(s_int_1_bytes); + SignedInt u_2 = SignedInt.wrap(s_int_2_bytes); + SignedInt result = u_1 * u_2; + + assertEq(SignedInt.unwrap(expected), SignedInt.unwrap(result)); + } + + function test_DivUnsignedInt() public pure { + int256 s_int_1 = -2048; + bytes32 s_int_1_bytes = toBytes(s_int_1); + int256 s_int_2 = 2; + bytes32 s_int_2_bytes = toBytes(s_int_2); + bytes32 expected_in_bytes = toBytes(-1024); + + SignedInt expected = SignedInt.wrap(expected_in_bytes); + SignedInt u_1 = SignedInt.wrap(s_int_1_bytes); + SignedInt u_2 = SignedInt.wrap(s_int_2_bytes); + SignedInt result = u_1 / u_2; + + assertEq(SignedInt.unwrap(expected), SignedInt.unwrap(result)); + } + + function test_ModUnsignedInt() public pure { + int256 s_int_1 = -2048; + bytes32 s_int_1_bytes = toBytes(s_int_1); + int256 s_int_2 = 2; + bytes32 s_int_2_bytes = toBytes(s_int_2); + bytes32 expected_in_bytes = toBytes(-1024); + + SignedInt expected = SignedInt.wrap(expected_in_bytes); + SignedInt u_1 = SignedInt.wrap(s_int_1_bytes); + SignedInt u_2 = SignedInt.wrap(s_int_2_bytes); + SignedInt result = u_1 / u_2; + + assertEq(SignedInt.unwrap(expected), SignedInt.unwrap(result)); + } + + function test_ExpUnsignedInt() public pure { + int256 s_int_1 = -2; + bytes32 s_int_1_bytes = toBytes(s_int_1); + int256 s_int_2 = 8; + bytes32 s_int_2_bytes = toBytes(s_int_2); + bytes32 expected_in_bytes = toBytes(-256); + + SignedInt expected = SignedInt.wrap(expected_in_bytes); + SignedInt u_1 = SignedInt.wrap(s_int_1_bytes); + SignedInt u_2 = SignedInt.wrap(s_int_2_bytes); + SignedInt result = u_1 ^ u_2; + + assertEq(SignedInt.unwrap(expected), SignedInt.unwrap(result)); + } + + function test_RevertWhen_DesiredBitsAreZero_ConvertWithSize() public { + SignedInt s = SignedInt.wrap(0x0); + + vm.expectRevert(); + s.convertWithSize(0); + } + + function test_RevertWhen_DesiredBitsAreBiggerThan256Bits_ConvertWithSize() public { + SignedInt s = SignedInt.wrap(0x0); + + vm.expectRevert(); + s.convertWithSize(257); + } + + function test_RevertWhen_DesiredBitsAreNotMultipleOfEight_ConvertWithSize() public { + SignedInt s = SignedInt.wrap(0x0); + + vm.expectRevert(); + s.convertWithSize(33); + } + + function test_RevertWhen_DesiredBitsAreLessThanDataBitSize_ConvertWithSize() public { + int256 s_int = -1024; + bytes32 s_int_bytes = toBytes(s_int); + SignedInt s = SignedInt.wrap(s_int_bytes); + + vm.expectRevert(); + s.convertWithSize(8); + } - function test_SizeInBits() public view { } + function test_RevertIf_RightValueIsZero_DivUnsignedInt() public { + int256 s_int_1 = 1; + int256 s_int_2 = 0; + bytes32 s_int_1_bytes = toBytes(s_int_1); + bytes32 s_int_2_bytes = toBytes(s_int_2); + SignedInt s_1 = SignedInt.wrap(s_int_1_bytes); + SignedInt s_2 = SignedInt.wrap(s_int_2_bytes); - function test_AddSignedInt() public view { } + vm.expectRevert(DivisionByZero.selector); + s_1 / s_2; + } - function test_SubSignedInt() public view { } + function test_RevertIf_RightValueIsZero_ModUnsignedInt() public { + int256 s_int_1 = 1; + int256 s_int_2 = 0; + bytes32 s_int_1_bytes = toBytes(s_int_1); + bytes32 s_int_2_bytes = toBytes(s_int_2); + SignedInt s_1 = SignedInt.wrap(s_int_1_bytes); + SignedInt s_2 = SignedInt.wrap(s_int_2_bytes); - function test_MulSignedInt() public view { } + vm.expectRevert(DivisionByZero.selector); + s_1 % s_2; + } - function test_DivSignedInt() public view { } + function test_RevertIf_RightValueIsNegative_ExpSignedInt() public { + int256 s_int_1 = 8; + int256 s_int_2 = -2; + bytes32 s_int_1_bytes = toBytes(s_int_1); + bytes32 s_int_2_bytes = toBytes(s_int_2); + SignedInt s_1 = SignedInt.wrap(s_int_1_bytes); + SignedInt s_2 = SignedInt.wrap(s_int_2_bytes); - function test_ModSignedInt() public view { } + vm.expectRevert(NegativeExponent.selector); + s_1 ^ s_2; + } - function test_ExpSignedInt() public view { } + function toBytes(int256 integer) public pure returns (bytes32 result) { + assembly { + result := integer + } + } } From 7efe6167c40d3b2f00fe727758ebbcd9941ad112 Mon Sep 17 00:00:00 2001 From: Can Vardar Date: Sun, 28 Jul 2024 21:42:32 +0300 Subject: [PATCH 16/16] chore: update CHANGELOG.md --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac20cf1..3d2108e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,12 @@ All notable changes to this project will be documented in this file. ### 🐛 Bug Fixes - *(ci)* Update ci-all-via-ir.yml +- Change `bitSize` parameter type as uint16 +- *(UnsignedInt)* Fix wrong conditions +- *(SignedInt)* Fix wrong conditions +- *(SignedInt)* Update `divSignedInt` function +- *(SignedInt)* Update `modSignedInt` function +- *(SignedInt)* Update `expSignedInt` function ### 🚜 Refactor @@ -34,6 +40,10 @@ All notable changes to this project will be documented in this file. - *(BitmaskLib)* Update `build` function params - *(BitmaskLib)* Remove `getLength` function - *(UnsignedInt)* Update functions +- Update Gasgnome.sol +- Change `sizeInBytes` into `desiredBits` +- *(ArithmeticLib)* Add missing custom errors +- *(UnsignedInt)* Add custom errors ### 📚 Documentation @@ -54,6 +64,12 @@ All notable changes to this project will be documented in this file. - *(Storage)* Add test suite - *(Bitmask)* Add test suite - *(BitmaskLib)* Update test suite +- *(ArithmeticLib)* Update test cases +- *(SignedIntLibTest)* Create test suite +- *(UnsignedIntLibTest)* Create test suite +- *(UnsignedIntLib)* Update test cases +- *(UnsignedIntLib)* Update test suite +- *(SignedIntLib)* Update test cases ### ⚙️ Miscellaneous Tasks @@ -66,5 +82,6 @@ All notable changes to this project will be documented in this file. - Add CHANGELOG.md - Update CHANGELOG.md - Update Gasgnome.sol +- Update CHANGELOG.md