Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ tokens
├─ ERC721 — "Simple ERC721 implementation with storage hitchhiking"
├─ WETH — "Simple Wrapped Ether implementation"
utils
├─ Base58 — "Library for Base58 encoding and decoding"
├─ Base64 — "Library for Base64 encoding and decoding"
├─ CallContextChecker — "Call context checker mixin"
├─ CREATE3 — "Deterministic deployments agnostic to the initialization code"
Expand Down
44 changes: 44 additions & 0 deletions docs/utils/base58.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Base58

Library to encode strings in Base58.






<!-- customintro:start --><!-- customintro:end -->

## Custom Errors

### Base58DecodingError()

```solidity
error Base58DecodingError()
```

An unrecognized character was encountered during decoding.

## Encoding / Decoding

### encode(bytes)

```solidity
function encode(bytes memory data)
internal
pure
returns (string memory result)
```

Encodes `data` into a Base58 string.

### decode(string)

```solidity
function decode(string memory encoded)
internal
pure
returns (bytes memory result)
```

Decodes `encoded`, a Base58 string, into the original bytes.
1 change: 1 addition & 0 deletions src/Milady.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import "./tokens/ERC4626.sol";
import "./tokens/ERC6909.sol";
import "./tokens/ERC721.sol";
import "./tokens/WETH.sol";
import "./utils/Base58.sol";
import "./utils/Base64.sol";
import "./utils/CREATE3.sol";
import "./utils/CallContextChecker.sol";
Expand Down
147 changes: 147 additions & 0 deletions src/utils/Base58.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Library to encode strings in Base58.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base58.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Base58.sol)
library Base58 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev An unrecognized character was encountered during decoding.
error Base58DecodingError();

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ENCODING / DECODING */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Encodes `data` into a Base58 string.
function encode(bytes memory data) internal pure returns (string memory result) {
uint256 l = data.length;
if (l == uint256(0)) return result;
/// @solidity memory-safe-assembly
assembly {
let b := add(data, 0x20) // Start of `data` bytes.
let z := 0 // Number of leading zero bytes in `data`.
// Count leading zero bytes.
for {} lt(byte(0, mload(add(b, z))), lt(z, l)) {} { z := add(1, z) }

// Start the output offset by an over-estimate of the length.
let o := add(add(mload(0x40), 0x20), add(z, add(1, div(mul(sub(l, z), 8351), 6115))))
let e := o

let limbs := o
let limbsEnd := limbs
// Populate the uint248 limbs.
for {
let i := mod(l, 31)
if i {
mstore(limbsEnd, shr(shl(3, add(1, sub(31, i))), mload(b)))
limbsEnd := add(limbsEnd, 0x20)
}
} lt(i, l) { i := add(i, 31) } {
mstore(limbsEnd, shr(8, mload(add(b, i))))
limbsEnd := add(limbsEnd, 0x20)
}

// Use the extended scratch space for the lookup. We'll restore 0x40 later.
mstore(0x1f, "123456789ABCDEFGHJKLMNPQRSTUVWXY")
mstore(0x3f, "Zabcdefghijkmnopqrstuvwxyz")

let w := not(0) // -1.
for {} 1 {} {
let i := limbs
for {} 1 {} {
if mload(i) { break }
i := add(i, 0x20)
if eq(i, limbsEnd) { break }
}
if eq(i, limbsEnd) { break }

let carry := 0
for { i := limbs } 1 {} {
let acc := add(shl(248, carry), mload(i))
mstore(i, div(acc, 58))
carry := mod(acc, 58)
i := add(i, 0x20)
if eq(i, limbsEnd) { break }
}
o := add(o, w)
mstore8(o, mload(carry))
}
// We probably can optimize this more by writing 32 bytes at a time.
for {} z { z := add(z, w) } {
o := add(o, w)
mstore8(o, 49) // '1' in ASCII.
}

let n := sub(e, o) // Compute the final length.
result := sub(o, 0x20) // Move back one word for the length.
mstore(result, n) // Store the length.
mstore(add(add(result, 0x20), n), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(result, 0x40), n)) // Allocate memory.
}
}

/// @dev Decodes `encoded`, a Base58 string, into the original bytes.
function decode(string memory encoded) internal pure returns (bytes memory result) {
uint256 n = bytes(encoded).length;
if (n == uint256(0)) return result;
/// @solidity memory-safe-assembly
assembly {
let s := add(encoded, 0x20)
let z := 0 // Number of leading '1' in `data`.
// Count leading '1'.
for {} and(eq(49, byte(0, mload(add(s, z)))), lt(z, n)) {} { z := add(1, z) }

// Start the output offset by an over-estimate of the length.
let o := add(add(mload(0x40), 0x20), add(z, add(1, div(mul(sub(n, z), 7736), 10000))))
let e := o
let limbs := o
let limbsEnd := limbs
let limbMask := shr(8, not(0))
// Use the extended scratch space for the lookup. We'll restore 0x40 later.
mstore(0x2a, 0x30313233343536373839)
mstore(0x20, 0x1718191a1b1c1d1e1f20ffffffffffff2122232425262728292a2bff2c2d2e2f)
mstore(0x00, 0x000102030405060708ffffffffffffff090a0b0c0d0e0f10ff1112131415ff16)

for { let j := 0 } 1 {} {
let c := sub(byte(0, mload(add(s, j))), 49)
// Check if the input character is valid.
if iszero(and(shl(c, 1), 0x3fff7ff03ffbeff01ff)) {
mstore(0x00, 0xe8fad793) // `Base58DecodingError()`.
revert(0x1c, 0x04)
}
let carry := byte(0, mload(c))
for { let i := limbs } iszero(eq(i, limbsEnd)) { i := add(i, 0x20) } {
let acc := add(carry, mul(58, mload(i)))
mstore(i, and(limbMask, acc))
carry := shr(248, acc)
}
// Carry will always be < 58.
if carry {
mstore(limbsEnd, carry)
limbsEnd := add(limbsEnd, 0x20)
}
j := add(j, 1)
if eq(j, n) { break }
}
// Copy and compact the uint248 limbs.
for { let i := limbs } iszero(eq(i, limbsEnd)) { i := add(i, 0x20) } {
o := sub(o, 31)
mstore(sub(o, 1), mload(i))
}
// Strip any leading zeros from the limbs.
for {} lt(byte(0, mload(o)), lt(o, e)) {} { o := add(o, 1) }
o := sub(o, z) // Move back for the leading zero bytes.
calldatacopy(o, calldatasize(), z) // Fill the leading zero bytes.

let l := sub(e, o) // Compute the final length.
result := sub(o, 0x20) // Move back one word for the length.
mstore(result, l) // Store the length.
mstore(add(add(result, 0x20), l), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(result, 0x40), l)) // Allocate memory.
}
}
}
Loading