Skip to content

Commit

Permalink
✨ New utils
Browse files Browse the repository at this point in the history
Co-Authored-By: Vectorized <[email protected]>
  • Loading branch information
transmissions11 and Vectorized committed Sep 8, 2022
1 parent 10fc959 commit 1c48742
Show file tree
Hide file tree
Showing 8 changed files with 533 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ tokens
utils
├─ SSTORE2 — "Library for cheaper reads and writes to persistent storage"
├─ CREATE3 — "Deploy to deterministic addresses without an initcode factor"
├─ LibString — "Library for creating string representations of uint values"
├─ SafeCastLib — "Safe unsigned integer casting lib that reverts on overflow"
├─ SignedWadMath — "Signed integer 18 decimal fixed point arithmetic library"
├─ MerkleProofLib — "Efficient merkle tree inclusion proof verification library"
├─ ReentrancyGuard — "Gas optimized reentrancy protection for smart contracts"
├─ FixedPointMathLib — "Arithmetic library with operations for fixed-point numbers"
├─ Bytes32AddressLib — "Library for converting between addresses and bytes32 values"
Expand Down
107 changes: 107 additions & 0 deletions src/test/LibString.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {DSTestPlus} from "./utils/DSTestPlus.sol";

import {LibString} from "../utils/LibString.sol";

contract LibStringTest is DSTestPlus {
function testToString() public {
assertEq(LibString.toString(0), "0");
assertEq(LibString.toString(1), "1");
assertEq(LibString.toString(17), "17");
assertEq(LibString.toString(99999999), "99999999");
assertEq(LibString.toString(99999999999), "99999999999");
assertEq(LibString.toString(2342343923423), "2342343923423");
assertEq(LibString.toString(98765685434567), "98765685434567");
}

function testDifferentiallyFuzzToString(uint256 value, bytes calldata brutalizeWith)
public
brutalizeMemory(brutalizeWith)
{
string memory libString = LibString.toString(value);
string memory oz = toStringOZ(value);

assertEq(bytes(libString).length, bytes(oz).length);
assertEq(libString, oz);
}

function testToStringOverwrite() public {
string memory str = LibString.toString(1);

bytes32 data;
bytes32 expected;

assembly {
// Imagine a high level allocation writing something to the current free memory.
// Should have sufficient higher order bits for this to be visible
mstore(mload(0x40), not(0))
// Correctly allocate 32 more bytes, to avoid more interference
mstore(0x40, add(mload(0x40), 32))
data := mload(add(str, 32))

// the expected value should be the uft-8 encoding of 1 (49),
// followed by clean bits. We achieve this by taking the value and
// shifting left to the end of the 32 byte word
expected := shl(248, 49)
}

assertEq(data, expected);
}

function testToStringDirty() public {
uint256 freememptr;
// Make the next 4 bytes of the free memory dirty
assembly {
let dirty := not(0)
freememptr := mload(0x40)
mstore(freememptr, dirty)
mstore(add(freememptr, 32), dirty)
mstore(add(freememptr, 64), dirty)
mstore(add(freememptr, 96), dirty)
mstore(add(freememptr, 128), dirty)
}
string memory str = LibString.toString(1);
uint256 len;
bytes32 data;
bytes32 expected;
assembly {
freememptr := str
len := mload(str)
data := mload(add(str, 32))
// the expected value should be the uft-8 encoding of 1 (49),
// followed by clean bits. We achieve this by taking the value and
// shifting left to the end of the 32 byte word
expected := shl(248, 49)
}
emit log_named_uint("str: ", freememptr);
emit log_named_uint("len: ", len);
emit log_named_bytes32("data: ", data);
assembly {
freememptr := mload(0x40)
}
emit log_named_uint("memptr: ", freememptr);

assertEq(data, expected);
}
}

function toStringOZ(uint256 value) pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
50 changes: 50 additions & 0 deletions src/test/MerkleProofLib.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {DSTestPlus} from "./utils/DSTestPlus.sol";

import {MerkleProofLib} from "../utils/MerkleProofLib.sol";

contract MerkleProofLibTest is DSTestPlus {
function testVerifyEmptyMerkleProofSuppliedLeafAndRootSame() public {
bytes32[] memory proof;
assertBoolEq(this.verify(proof, 0x00, 0x00), true);
}

function testVerifyEmptyMerkleProofSuppliedLeafAndRootDifferent() public {
bytes32[] memory proof;
bytes32 leaf = "a";
assertBoolEq(this.verify(proof, 0x00, leaf), false);
}

function testValidProofSupplied() public {
// Merkle tree created from leaves ['a', 'b', 'c'].
// Leaf is 'a'.
bytes32[] memory proof = new bytes32[](2);
proof[0] = 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510;
proof[1] = 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2;
bytes32 root = 0x5842148bc6ebeb52af882a317c765fccd3ae80589b21a9b8cbf21abb630e46a7;
bytes32 leaf = 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb;
assertBoolEq(this.verify(proof, root, leaf), true);
}

function testVerifyInvalidProofSupplied() public {
// Merkle tree created from leaves ['a', 'b', 'c'].
// Leaf is 'a'.
// Proof is same as testValidProofSupplied but last byte of first element is modified.
bytes32[] memory proof = new bytes32[](2);
proof[0] = 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5511;
proof[1] = 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2;
bytes32 root = 0x5842148bc6ebeb52af882a317c765fccd3ae80589b21a9b8cbf21abb630e46a7;
bytes32 leaf = 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb;
assertBoolEq(this.verify(proof, root, leaf), false);
}

function verify(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) external pure returns (bool) {
return MerkleProofLib.verify(proof, root, leaf);
}
}
60 changes: 60 additions & 0 deletions src/test/SignedWadMath.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {DSTestPlus} from "./utils/DSTestPlus.sol";

import {wadMul, wadDiv} from "../utils/SignedWadMath.sol";

contract SignedWadMathTest is DSTestPlus {
function testWadMul(
uint256 x,
uint256 y,
bool negX,
bool negY
) public {
x = bound(x, 0, 99999999999999e18);
y = bound(x, 0, 99999999999999e18);

int256 xPrime = negX ? -int256(x) : int256(x);
int256 yPrime = negY ? -int256(y) : int256(y);

assertEq(wadMul(xPrime, yPrime), (xPrime * yPrime) / 1e18);
}

function testFailWadMulOverflow(int256 x, int256 y) public pure {
// Ignore cases where x * y does not overflow.
unchecked {
if ((x * y) / x == y) revert();
}

wadMul(x, y);
}

function testWadDiv(
uint256 x,
uint256 y,
bool negX,
bool negY
) public {
x = bound(x, 0, 99999999e18);
y = bound(x, 1, 99999999e18);

int256 xPrime = negX ? -int256(x) : int256(x);
int256 yPrime = negY ? -int256(y) : int256(y);

assertEq(wadDiv(xPrime, yPrime), (xPrime * 1e18) / yPrime);
}

function testFailWadDivOverflow(int256 x, int256 y) public pure {
// Ignore cases where x * WAD does not overflow or y is 0.
unchecked {
if (y == 0 || (x * 1e18) / 1e18 == x) revert();
}

wadDiv(x, y);
}

function testFailWadDivZeroDenominator(int256 x) public pure {
wadDiv(x, 0);
}
}
16 changes: 16 additions & 0 deletions src/utils/FixedPointMathLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,20 @@ library FixedPointMathLib {
}
}
}

function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}

function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
54 changes: 54 additions & 0 deletions src/utils/LibString.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @notice Efficient library for creating string representations of integers.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/LibString.sol)
library LibString {
function toString(uint256 value) internal pure returns (string memory str) {
assembly {
// The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes
// to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the
// trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes.
let newFreeMemoryPointer := add(mload(0x40), 160)

// Update the free memory pointer to avoid overriding our string.
mstore(0x40, newFreeMemoryPointer)

// Assign str to the end of the zone of newly allocated memory.
str := sub(newFreeMemoryPointer, 32)

// Clean the last word of memory it may not be overwritten.
mstore(str, 0)

// Cache the end of the memory to calculate the length later.
let end := str

// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
// prettier-ignore
for { let temp := value } 1 {} {
// Move the pointer 1 byte to the left.
str := sub(str, 1)

// Write the character to the pointer.
// The ASCII index of the '0' character is 48.
mstore8(str, add(48, mod(temp, 10)))

// Keep dividing temp until zero.
temp := div(temp, 10)

// prettier-ignore
if iszero(temp) { break }
}

// Compute and cache the final total length of the string.
let length := sub(end, str)

// Move the pointer 32 bytes leftwards to make room for the length.
str := sub(str, 32)

// Store the string's length at the start of memory allocated for our string.
mstore(str, length)
}
}
}
46 changes: 46 additions & 0 deletions src/utils/MerkleProofLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @notice Gas optimized merkle proof verification library.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/MerkleProofLib.sol)
library MerkleProofLib {
function verify(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool isValid) {
assembly {
if proof.length {
// Left shifting by 5 is like multiplying by 32.
let end := add(proof.offset, shl(5, proof.length))

// Initialize offset to the offset of the proof in calldata.
let offset := proof.offset

// Iterate over proof elements to compute root hash.
// prettier-ignore
for {} 1 {} {
// Slot where the leaf should be put in scratch space. If
// leaf > calldataload(offset): slot 32, otherwise: slot 0.
let leafSlot := shl(5, gt(leaf, calldataload(offset)))

// Store elements to hash contiguously in scratch space.
// The xor puts calldataload(offset) in whichever slot leaf
// is not occupying, so 0 if leafSlot is 32, and 32 otherwise.
mstore(leafSlot, leaf)
mstore(xor(leafSlot, 32), calldataload(offset))

// Reuse leaf to store the hash to reduce stack operations.
leaf := keccak256(0, 64) // Hash both slots of scratch space.

offset := add(offset, 32) // Shift 1 word per cycle.

// prettier-ignore
if iszero(lt(offset, end)) { break }
}
}

isValid := eq(leaf, root) // The proof is valid if the roots match.
}
}
}
Loading

0 comments on commit 1c48742

Please sign in to comment.