Skip to content

Commit a2dfd37

Browse files
committed
Squash
1 parent 56e7ebb commit a2dfd37

File tree

5 files changed

+329
-36
lines changed

5 files changed

+329
-36
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ tokens
6060
├─ ERC721 — "Simple ERC721 implementation with storage hitchhiking"
6161
├─ WETH — "Simple Wrapped Ether implementation"
6262
utils
63+
├─ Base58 — "Library for Base58 encoding and decoding"
6364
├─ Base64 — "Library for Base64 encoding and decoding"
6465
├─ CallContextChecker — "Call context checker mixin"
6566
├─ CREATE3 — "Deterministic deployments agnostic to the initialization code"

docs/utils/base58.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Base58
2+
3+
Library to encode strings in Base58.
4+
5+
6+
7+
8+
9+
10+
<!-- customintro:start --><!-- customintro:end -->
11+
12+
## Custom Errors
13+
14+
### Base58DecodingError()
15+
16+
```solidity
17+
error Base58DecodingError()
18+
```
19+
20+
An unrecognized character was encountered or the carry has overflowed.
21+
22+
## Encoding / Decoding
23+
24+
### encode(bytes)
25+
26+
```solidity
27+
function encode(bytes memory data)
28+
internal
29+
pure
30+
returns (string memory result)
31+
```
32+
33+
Encodes `data` into a Base58 string.
34+
35+
### decode(string)
36+
37+
```solidity
38+
function decode(string memory encoded)
39+
internal
40+
pure
41+
returns (bytes memory result)
42+
```
43+
44+
Decodes `encoded`, a Base58 string, into the original bytes.

src/Milady.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import "./tokens/ERC4626.sol";
2323
import "./tokens/ERC6909.sol";
2424
import "./tokens/ERC721.sol";
2525
import "./tokens/WETH.sol";
26+
import "./utils/Base58.sol";
2627
import "./utils/Base64.sol";
2728
import "./utils/CREATE3.sol";
2829
import "./utils/CallContextChecker.sol";

src/utils/Base58.sol

Lines changed: 105 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,35 @@ pragma solidity ^0.8.4;
55
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base58.sol)
66
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Base58.sol)
77
library Base58 {
8-
/// @dev Encodes `data` into a base58 string.
8+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9+
/* CUSTOM ERRORS */
10+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11+
12+
/// @dev An unrecognized character was encountered or the carry has overflowed.
13+
error Base58DecodingError();
14+
15+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
16+
/* ENCODING / DECODING */
17+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
18+
19+
/// @dev Encodes `data` into a Base58 string.
920
function encode(bytes memory data) internal pure returns (string memory result) {
21+
uint256 l = data.length;
22+
if (l == uint256(0)) return result;
1023
/// @solidity memory-safe-assembly
1124
assembly {
12-
let l := mload(data) // `data.length`.
1325
let b := add(data, 0x20) // Start of `data` bytes.
1426
let z := 0 // Number of leading zero bytes in `data`.
27+
// Count leading zero bytes.
1528
for {} lt(byte(0, mload(add(b, z))), lt(z, l)) {} { z := add(1, z) }
1629

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

2134
let limbs := o
2235
let limbsEnd := limbs
23-
36+
// Populate the uint248 limbs.
2437
for {
2538
let i := mod(l, 31)
2639
if i {
@@ -32,44 +45,106 @@ library Base58 {
3245
limbsEnd := add(limbsEnd, 0x20)
3346
}
3447

35-
// Use the scratch space for the lookup. We'll restore 0x40 later.
48+
// Use the extended scratch space for the lookup. We'll restore 0x40 later.
3649
mstore(0x1f, "123456789ABCDEFGHJKLMNPQRSTUVWXY")
3750
mstore(0x3f, "Zabcdefghijkmnopqrstuvwxyz")
3851

39-
if iszero(eq(limbs, limbsEnd)) {
40-
for {} 1 {} {
41-
let anyNonZero := 0
42-
for { let i := limbs } 1 {} {
43-
if mload(i) {
44-
anyNonZero := 1
45-
break
46-
}
47-
i := add(i, 0x20)
48-
if eq(i, limbsEnd) { break }
52+
for {} 1 {} {
53+
let anyNonZero := 0
54+
for { let i := limbs } 1 {} {
55+
if mload(i) {
56+
anyNonZero := 1
57+
break
4958
}
50-
if iszero(anyNonZero) { break }
51-
52-
let carry := 0
53-
for { let i := limbs } 1 {} {
54-
let acc := add(shl(248, carry), mload(i))
55-
mstore(i, div(acc, 58))
56-
carry := mod(acc, 58)
57-
i := add(i, 0x20)
58-
if eq(i, limbsEnd) { break }
59-
}
60-
o := sub(o, 1)
61-
mstore8(o, mload(carry))
59+
i := add(i, 0x20)
60+
if eq(i, limbsEnd) { break }
6261
}
63-
for { let i := 0 } iszero(eq(i, z)) { i := add(i, 1) } {
64-
o := sub(o, 1)
65-
mstore8(o, 49) // '1' in ASCII.
62+
if iszero(anyNonZero) { break }
63+
64+
let carry := 0
65+
for { let i := limbs } 1 {} {
66+
let acc := add(shl(248, carry), mload(i))
67+
mstore(i, div(acc, 58))
68+
carry := mod(acc, 58)
69+
i := add(i, 0x20)
70+
if eq(i, limbsEnd) { break }
6671
}
72+
o := sub(o, 1)
73+
mstore8(o, mload(carry))
74+
}
75+
// We probably can optimize this more by writing 32 bytes at a time.
76+
for { let i := 0 } iszero(eq(i, z)) { i := add(i, 1) } {
77+
o := sub(o, 1)
78+
mstore8(o, 49) // '1' in ASCII.
6779
}
80+
6881
let n := sub(e, o) // Compute the final length.
6982
result := sub(o, 0x20) // Move back one word for the length.
7083
mstore(result, n) // Store the length.
7184
mstore(add(add(result, 0x20), n), 0) // Zeroize the slot after the bytes.
7285
mstore(0x40, add(add(result, 0x40), n)) // Allocate memory.
7386
}
7487
}
88+
89+
/// @dev Decodes `encoded`, a Base58 string, into the original bytes.
90+
function decode(string memory encoded) internal pure returns (bytes memory result) {
91+
uint256 n = bytes(encoded).length;
92+
if (n == uint256(0)) return result;
93+
/// @solidity memory-safe-assembly
94+
assembly {
95+
let s := add(encoded, 0x20)
96+
let z := 0 // Number of leading '1' in `data`.
97+
// Count leading '1'.
98+
for {} and(eq(49, byte(0, mload(add(s, z)))), lt(z, n)) {} { z := add(1, z) }
99+
100+
// Start the output offset by an over-estimate of the length.
101+
let o := add(add(mload(0x40), 0x20), add(z, add(1, div(mul(sub(n, z), 7736), 10000))))
102+
let e := o
103+
let limbs := o
104+
let limbsEnd := limbs
105+
// Use the extended scratch space for the lookup. We'll restore 0x40 later.
106+
mstore(0x2a, 0x30313233343536373839)
107+
mstore(0x20, 0x1718191a1b1c1d1e1f20ffffffffffff2122232425262728292a2bff2c2d2e2f)
108+
mstore(0x00, 0x000102030405060708ffffffffffffff090a0b0c0d0e0f10ff1112131415ff16)
109+
110+
for { let j := 0 } 1 {} {
111+
let c := sub(byte(0, mload(add(s, j))), 49)
112+
if iszero(and(shl(c, 1), 0x3fff7ff03ffbeff01ff)) {
113+
mstore(0x00, 0xe8fad793) // `Base58DecodingError()`.
114+
revert(0x1c, 0x04)
115+
}
116+
let carry := byte(0, mload(c))
117+
for { let i := limbs } iszero(eq(i, limbsEnd)) { i := add(i, 0x20) } {
118+
let acc := add(carry, mul(58, mload(i)))
119+
mstore(i, shr(8, shl(8, acc)))
120+
carry := shr(248, acc)
121+
}
122+
if carry {
123+
if iszero(lt(carry, 58)) {
124+
mstore(0x00, 0xe8fad793) // `Base58DecodingError()`.
125+
revert(0x1c, 0x04)
126+
}
127+
mstore(limbsEnd, carry)
128+
limbsEnd := add(limbsEnd, 0x20)
129+
}
130+
j := add(j, 1)
131+
if eq(j, n) { break }
132+
}
133+
// Copy and compact the uint248 limbs.
134+
for { let i := limbs } iszero(eq(i, limbsEnd)) { i := add(i, 0x20) } {
135+
o := sub(o, 31)
136+
mstore(sub(o, 1), mload(i))
137+
}
138+
// Strip any leading zeros from the limbs.
139+
for {} lt(byte(0, mload(o)), lt(o, e)) {} { o := add(o, 1) }
140+
o := sub(o, z) // Move back for the leading zero bytes.
141+
calldatacopy(o, calldatasize(), z) // Fill the leading zero bytes.
142+
143+
let l := sub(e, o) // Compute the final length.
144+
result := sub(o, 0x20) // Move back one word for the length.
145+
mstore(result, l) // Store the length.
146+
mstore(add(add(result, 0x20), l), 0) // Zeroize the slot after the bytes.
147+
mstore(0x40, add(add(result, 0x40), l)) // Allocate memory.
148+
}
149+
}
75150
}

0 commit comments

Comments
 (0)