diff --git a/CHANGELOG.md b/CHANGELOG.md index 018db67..ac20cf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ All notable changes to this project will be documented in this file. - Add BitmaskLib.sol - *(Mask)* Add Mask custom type and its library - *(BitmaskLib)* Implement updateDataWith +- Add ArithmeticLib library +- *(SignedInt)* Add custom type +- *(UnsignedInt)* Add custom type +- *(UnsignedInt)* Add UnsignedLib +- *(ArithmeticLib)* Add functions +- *(SignedInt)* Add functions ### 🐛 Bug Fixes @@ -27,6 +33,7 @@ All notable changes to this project will be documented in this file. - *(BitmaskLib)* Update Mask custom type - *(BitmaskLib)* Update `build` function params - *(BitmaskLib)* Remove `getLength` function +- *(UnsignedInt)* Update functions ### 📚 Documentation @@ -35,6 +42,7 @@ All notable changes to this project will be documented in this file. ### 🎨 Styling +- *(SlotLib)* Update setUp function - Move Mask to BitmaskLib - *(BitmaskLib)* Rename updateDataWith as updateLeftPadded - Format BitmaskLib.t.sol @@ -56,5 +64,7 @@ All notable changes to this project will be documented in this file. - Remove redundant contracts - Update Gasgnome.sol - Add CHANGELOG.md +- Update CHANGELOG.md +- Update Gasgnome.sol diff --git a/src/Gasgnome.sol b/src/Gasgnome.sol index 2fe941a..932e554 100644 --- a/src/Gasgnome.sol +++ b/src/Gasgnome.sol @@ -1,8 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.26; -import "./libraries/StorageLib.sol"; +/// @notice Libraries +import "./libraries/ArithmeticLib.sol"; import "./libraries/BitmaskLib.sol"; +import "./libraries/StorageLib.sol"; + +/// @notice Types +import "./types/SignedInt.sol"; +import "./types/Slot.sol"; +import "./types/UnsignedInt.sol"; library Gasgnome { string internal constant AUTHOR = "https://github.com/icanvardar"; diff --git a/src/libraries/ArithmeticLib.sol b/src/libraries/ArithmeticLib.sol new file mode 100644 index 0000000..1215b7c --- /dev/null +++ b/src/libraries/ArithmeticLib.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +library ArithmeticLib { + function convertWithSize(bytes32 b, uint16 sizeInBytes, 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 - sizeInBytes has to be multiple of eight + if gt(mod(sizeInBytes, 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) } + + to := b + } + } + + /// NOTE: finds the nearest bit cap in multiple of eight + function cap(uint8 bitSize) public pure returns (uint8 res) { + assembly { + 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)) { + res := tmp + break + } + } + } + } +} diff --git a/src/types/SignedInt.sol b/src/types/SignedInt.sol new file mode 100644 index 0000000..49c52f8 --- /dev/null +++ b/src/types/SignedInt.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { ArithmeticLib } from "../libraries/ArithmeticLib.sol"; + +type SignedInt is bytes32; + +using { + addSignedInt as +, + subSignedInt as -, + mulSignedInt as *, + divSignedInt as /, + modSignedInt as %, + expSignedInt as ^ +} for SignedInt global; + +function addSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt res) { + assembly { + res := add(left, right) + } +} + +function subSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt res) { + assembly { + res := sub(left, right) + } +} + +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) } + } +} + +function divSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt res) { + assembly { + res := div(left, right) + } +} + +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) + } + + res := mod(left, right) + } +} + +function expSignedInt(SignedInt left, SignedInt right) pure returns (SignedInt res) { + assembly { + res := exp(left, right) + } +} + +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)); + + assembly { + to := result + } + } + + function sizeInBits(SignedInt s) public pure returns (uint8 size) { + assembly { + let tmp + let isNegative := sgt(0, s) + let absValue := s + if isNegative { absValue := sub(0, s) } + + for { let i := 0x1f } gt(i, 0x0) { i := sub(i, 1) } { + let b := byte(i, absValue) + + /// Check existency of first nibble + let x := and(b, 0x0f) + if gt(x, 0) { + size := add(size, add(tmp, 0x1)) + tmp := 0x0 + } + if eq(x, 0) { tmp := add(tmp, 0x1) } + + /// Check existency of second nibble + let y := and(b, 0xf0) + if gt(y, 0) { size := add(size, 0x1) } + if eq(y, 0) { tmp := add(tmp, 0x1) } + } + + size := mul(size, 0x4) + } + } +} diff --git a/src/types/UnsignedInt.sol b/src/types/UnsignedInt.sol new file mode 100644 index 0000000..1a8305a --- /dev/null +++ b/src/types/UnsignedInt.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import { ArithmeticLib } from "../libraries/ArithmeticLib.sol"; + +type UnsignedInt is bytes32; + +using { + addUnsignedInt as +, + subUnsignedInt as -, + mulUnsignedInt as *, + divUnsignedInt as /, + modUnsignedInt as %, + expUnsignedInt as ^ +} for UnsignedInt global; + +function addUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (UnsignedInt res) { + assembly { + res := add(left, right) + + if lt(res, left) { + /// TODO: add custom error here - overflow check + revert(0x00, 0x00) + } + } +} + +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) + } + + res := sub(left, right) + } +} + +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) } + } +} + +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) + } + + res := div(left, right) + } +} + +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) + } + + res := mod(left, right) + } +} + +function expUnsignedInt(UnsignedInt left, UnsignedInt right) pure returns (UnsignedInt res) { + assembly { + res := exp(left, right) + } +} + +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)); + + assembly { + to := result + } + } + + function sizeInBits(UnsignedInt u) public pure returns (uint8 size) { + assembly { + let tmp + for { let i := 0x1f } gt(i, 0x0) { i := sub(i, 1) } { + let b := byte(i, u) + + /// NOTE: check existency of first nibble + let x := and(b, 0x0f) + if gt(x, 0) { + size := add(size, add(tmp, 0x1)) + tmp := 0x0 + } + if eq(x, 0) { tmp := add(tmp, 0x1) } + + /// NOTE: check existency of second nibble + let y := and(b, 0xf0) + if gt(y, 0) { size := add(size, 0x1) } + if eq(y, 0) { tmp := add(tmp, 0x1) } + } + + size := mul(size, 0x4) + } + } +} diff --git a/test/ArithmeticLib.t.sol b/test/ArithmeticLib.t.sol new file mode 100644 index 0000000..b7d9021 --- /dev/null +++ b/test/ArithmeticLib.t.sol @@ -0,0 +1,7 @@ +// 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 ArithmeticLibTest is Test { }