Skip to content

Commit

Permalink
Merge pull request #8 from icanvardar/feat/#3-arithmetic
Browse files Browse the repository at this point in the history
Fix Bugs and Update Functions in SignedInt and UnsignedInt, Update Tests
  • Loading branch information
icanvardar authored Jul 28, 2024
2 parents afeee0a + 7efe616 commit 4ac8e04
Show file tree
Hide file tree
Showing 7 changed files with 578 additions and 34 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

<!-- generated by git-cliff -->
52 changes: 43 additions & 9 deletions src/libraries/ArithmeticLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
55 changes: 44 additions & 11 deletions src/types/SignedInt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
30 changes: 17 additions & 13 deletions src/types/UnsignedInt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@ 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)
}
}
}

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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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) } {
Expand Down
99 changes: 98 additions & 1 deletion test/ArithmeticLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading

0 comments on commit 4ac8e04

Please sign in to comment.