Sitelet https://github.com/Vectorized/solady/pull/1443/files
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
24 changes: 23 additions & 1 deletion docs/utils/base58.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ function encode(bytes memory data)

Encodes `data` into a Base58 string.

### encodeWord(bytes32)

```solidity
function encodeWord(bytes32 data)
internal
pure
returns (string memory result)
```

Encodes the `data` word into a Base58 string.

### decode(string)

```solidity
Expand All @@ -41,4 +52,15 @@ function decode(string memory encoded)
returns (bytes memory result)
```

Decodes `encoded`, a Base58 string, into the original bytes.
Decodes `encoded`, a Base58 string, into the original bytes.

### decodeWord(string)

```solidity
function decodeWord(string memory encoded)
internal
pure
returns (bytes32 result)
```

Decodes `encoded`, a Base58 string, into the original word.
46 changes: 46 additions & 0 deletions src/utils/Base58.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,37 @@ library Base58 {
}
}

/// @dev Encodes the `data` word into a Base58 string.
function encodeWord(bytes32 data) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
let o := add(mload(0x40), 0x4c) // 32 for word, 44 for maximum possible length.
let e := o

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

let w := not(0) // -1.
let z := shl(5, iszero(data)) // Number of leading zeroes in `data`.
if iszero(z) {
for { let v := data } v { v := div(v, 58) } {
o := add(o, w)
mstore8(o, mload(mod(v, 58)))
}
for {} iszero(byte(z, data)) { z := add(z, 1) } {} // Just loop, `z` is often tiny.
}
if z { mstore(sub(o, 0x20), mul(div(w, 0xff), 49)) } // '1111...1111' in ASCII.
o := sub(o, z)

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;
Expand Down Expand Up @@ -140,4 +171,19 @@ library Base58 {
mstore(0x40, add(add(result, 0x40), l)) // Allocate memory.
}
}

/// @dev Decodes `encoded`, a Base58 string, into the original word.
function decodeWord(string memory encoded) internal pure returns (bytes32 result) {
// Specializing and optimizing this for bytes32 is left as an exercise to the reader.
bytes memory t = decode(encoded);
/// @solidity memory-safe-assembly
assembly {
let n := mload(t)
if iszero(lt(n, 0x21)) {
mstore(0x00, 0xe8fad793) // `Base58DecodingError()`.
revert(0x1c, 0x04)
}
result := mload(add(t, n))
}
}
}
32 changes: 32 additions & 0 deletions test/Base58.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ import {Base58} from "../src/utils/Base58.sol";
import {LibString} from "../src/utils/LibString.sol";

contract Base58Test is SoladyTest {
function testBase58DecodeRevertsIfInvalidCharacter(bytes1 c) public {
if (isValidBase58Character(c)) {
this.base58DecodeRevertsIfInvalidCharacter(c);
} else {
vm.expectRevert(Base58.Base58DecodingError.selector);
this.base58DecodeRevertsIfInvalidCharacter(c);
}
}

function isValidBase58Character(bytes1 c) internal pure returns (bool) {
bytes memory allowed = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
for (uint256 i; i < allowed.length; ++i) {
if (allowed[i] == c) return true;
}
return false;
}

function base58DecodeRevertsIfInvalidCharacter(bytes1 c) public {
emit LogBytes(Base58.decode(string(abi.encodePacked(c))));
}

function testBase58EncodeDecode(bytes memory data, uint256 r) public {
if (r & 0x00f == 0) {
_brutalizeMemory();
Expand Down Expand Up @@ -196,4 +217,15 @@ contract Base58Test is SoladyTest {
function check_CarryBoundsTrick(uint248 limb, uint8 carry) public pure {
testCarryBoundsTrick(limb, carry);
}

function testEncodeWordDifferential(bytes32 word) public {
string memory expected = Base58.encode(abi.encodePacked(word));
string memory computed = Base58.encodeWord(word);
assertEq(computed, expected);
}

function testEncodeDecodeWord(bytes32 word) public {
string memory encoded = Base58.encodeWord(word);
assertEq(Base58.decodeWord(encoded), word);
}
}