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 diff --git a/src/libraries/ArithmeticLib.sol b/src/libraries/ArithmeticLib.sol index 1215b7c..123ad00 100644 --- a/src/libraries/ArithmeticLib.sol +++ b/src/libraries/ArithmeticLib.sol @@ -2,28 +2,62 @@ 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, uint16 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) } + 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 - sizeInBytes has to be multiple of eight - if gt(mod(sizeInBytes, 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) + } } - uint8 sizeCap = cap(sizeInBits); + uint16 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 lt(desiredBits, sizeCap) { + /// @dev bytes4(keccak256("MismatchedSizes(uint256,uint256)")) => 0x01e8c4a5 + mstore(0x80, 0x01e8c4a5) + mstore(0xa0, desiredBits) + mstore(0xc0, sizeCap) + revert(0x9c, 0x44) + } to := b } } /// 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 { + 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) if or(gt(tmp, bitSize), eq(tmp, bitSize)) { diff --git a/src/types/SignedInt.sol b/src/types/SignedInt.sol index 49c52f8..334a039 100644 --- a/src/types/SignedInt.sol +++ b/src/types/SignedInt.sol @@ -29,46 +29,79 @@ 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) } } } 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) } } } 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) } } } 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) } } } 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 } } - 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 1a8305a..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) @@ -39,16 +41,17 @@ 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) } } } 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) @@ -75,15 +79,15 @@ 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 } } - 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) } { 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); + } +} diff --git a/test/SignedIntLib.t.sol b/test/SignedIntLib.t.sol new file mode 100644 index 0000000..4fc2a32 --- /dev/null +++ b/test/SignedIntLib.t.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: UNLICENSED +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 { + 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_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); + + vm.expectRevert(DivisionByZero.selector); + s_1 / s_2; + } + + 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); + + vm.expectRevert(DivisionByZero.selector); + s_1 % s_2; + } + + 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); + + vm.expectRevert(NegativeExponent.selector); + s_1 ^ s_2; + } + + function toBytes(int256 integer) public pure returns (bytes32 result) { + assembly { + result := integer + } + } +} diff --git a/test/UnsignedIntLib.t.sol b/test/UnsignedIntLib.t.sol new file mode 100644 index 0000000..01cba73 --- /dev/null +++ b/test/UnsignedIntLib.t.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: UNLICENSED +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 { + 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 { + UnsignedInt u = UnsignedInt.wrap(0x0); + + vm.expectRevert(); + u.convertWithSize(0); + } + + function test_RevertWhen_DesiredBitsAreBiggerThan256Bits_ConvertWithSize() public { + UnsignedInt u = UnsignedInt.wrap(0x0); + + vm.expectRevert(); + u.convertWithSize(257); + } + + function test_RevertWhen_DesiredBitsAreNotMultipleOfEight_ConvertWithSize() public { + UnsignedInt u = UnsignedInt.wrap(0x0); + + 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)); + + vm.expectRevert(Overflow.selector); + u_1 + u_2; + } + + 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)); + + vm.expectRevert(Underflow.selector); + u_1 - u_2; + } + + 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)); + + vm.expectRevert(DivisionByZero.selector); + u_1 / u_2; + } + + 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)); + + vm.expectRevert(DivisionByZero.selector); + u_1 % u_2; + } +}