-
Notifications
You must be signed in to change notification settings - Fork 669
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-Authored-By: Vectorized <[email protected]>
- Loading branch information
1 parent
10fc959
commit 1c48742
Showing
8 changed files
with
533 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
} | ||
} | ||
} |
Oops, something went wrong.