Skip to content

Commit a206a50

Browse files
committed
wip
1 parent da1dfe6 commit a206a50

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

contracts/utils/Compression.sol

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
/**
6+
* @dev Library for compressing and decompressing buffers. Supported compression algorithm:
7+
* * FastLZ (level1): WIP
8+
* * Calldata optimized: todo
9+
* * ZIP: todo
10+
*/
11+
library Compression {
12+
/**
13+
* @dev FastLZ level 1 decompression.
14+
*
15+
* Based on the reference implementation available here:
16+
* https://github.com/ariya/FastLZ?tab=readme-ov-file#decompressor-reference-implementation
17+
*/
18+
function flzDecompress(bytes memory input) internal pure returns (bytes memory output) {
19+
assembly ("memory-safe") {
20+
// Use new memory allocate at the FMP
21+
output := mload(0x40)
22+
23+
// Decrypted data location
24+
let ptr := add(output, 0x20)
25+
26+
// end of the input data (input.length after the beginning of the data)
27+
let end := add(add(input, 0x20), mload(input))
28+
29+
for {
30+
let data := add(input, 0x20)
31+
} lt(data, end) {} {
32+
let chunk := mload(data)
33+
let first := byte(0, chunk)
34+
let type_ := shr(5, first)
35+
36+
switch type_
37+
case 0 {
38+
mstore(ptr, mload(add(data, 1)))
39+
data := add(data, add(2, first))
40+
ptr := add(ptr, add(1, first))
41+
}
42+
case 7 {
43+
let ofs := add(shl(8, and(first, 31)), byte(2, chunk))
44+
let len := add(9, byte(1, chunk))
45+
let ref := sub(sub(ptr, ofs), 1)
46+
let step := sub(0x20, mul(lt(ofs, 0x20), sub(0x1f, ofs))) // min(ofs+1, 0x20)
47+
for {
48+
let i := 0
49+
} lt(i, len) {
50+
i := add(i, step)
51+
} {
52+
mstore(add(ptr, i), mload(add(ref, i)))
53+
}
54+
data := add(data, 3)
55+
ptr := add(ptr, len)
56+
}
57+
default {
58+
let ofs := add(shl(8, and(first, 31)), byte(1, chunk))
59+
let len := add(2, type_)
60+
let ref := sub(sub(ptr, ofs), 1)
61+
let step := sub(0x20, mul(lt(ofs, 0x20), sub(0x1f, ofs))) // min(ofs+1, 0x20)
62+
for {
63+
let i := 0
64+
} lt(i, len) {
65+
i := add(i, step)
66+
} {
67+
mstore(add(ptr, i), mload(add(ref, i)))
68+
}
69+
data := add(data, 2)
70+
ptr := add(ptr, len)
71+
}
72+
}
73+
mstore(output, sub(ptr, add(output, 0x20)))
74+
mstore(0x40, ptr)
75+
}
76+
}
77+
}

test/utils/Compression.t.sol

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {Test} from "forge-std/Test.sol";
6+
import {Compression} from "@openzeppelin/contracts/utils/Compression.sol";
7+
8+
contract CompressionTest is Test {
9+
using Compression for bytes;
10+
11+
function testEncodeDecode(bytes memory input) external pure {
12+
assertEq(_flzCompress(input).flzDecompress(), input);
13+
}
14+
15+
/// Copied from solady
16+
function _flzCompress(bytes memory input) private pure returns (bytes memory output) {
17+
assembly ("memory-safe") {
18+
// store 8 bytes (value) at ptr, and return updated ptr
19+
function ms8(ptr, value) -> ret {
20+
mstore8(ptr, value)
21+
ret := add(ptr, 1)
22+
}
23+
// load 24 bytes from a given location in memory, right aligned and in reverse order
24+
function u24(ptr) -> value {
25+
value := mload(ptr)
26+
value := or(shl(16, byte(2, value)), or(shl(8, byte(1, value)), byte(0, value)))
27+
}
28+
function cmp(p_, q_, e_) -> _l {
29+
for {
30+
e_ := sub(e_, q_)
31+
} lt(_l, e_) {
32+
_l := add(_l, 1)
33+
} {
34+
e_ := mul(iszero(byte(0, xor(mload(add(p_, _l)), mload(add(q_, _l))))), e_)
35+
}
36+
}
37+
function literals(runs_, src_, dest_) -> _o {
38+
for {
39+
_o := dest_
40+
} iszero(lt(runs_, 0x20)) {
41+
runs_ := sub(runs_, 0x20)
42+
} {
43+
mstore(ms8(_o, 31), mload(src_))
44+
_o := add(_o, 0x21)
45+
src_ := add(src_, 0x20)
46+
}
47+
if iszero(runs_) {
48+
leave
49+
}
50+
mstore(ms8(_o, sub(runs_, 1)), mload(src_))
51+
_o := add(1, add(_o, runs_))
52+
}
53+
function mt(l_, d_, o_) -> _o {
54+
for {
55+
d_ := sub(d_, 1)
56+
} iszero(lt(l_, 263)) {
57+
l_ := sub(l_, 262)
58+
} {
59+
o_ := ms8(ms8(ms8(o_, add(224, shr(8, d_))), 253), and(0xff, d_))
60+
}
61+
if iszero(lt(l_, 7)) {
62+
_o := ms8(ms8(ms8(o_, add(224, shr(8, d_))), sub(l_, 7)), and(0xff, d_))
63+
leave
64+
}
65+
_o := ms8(ms8(o_, add(shl(5, l_), shr(8, d_))), and(0xff, d_))
66+
}
67+
function setHash(i_, v_) {
68+
let p_ := add(mload(0x40), shl(2, i_))
69+
mstore(p_, xor(mload(p_), shl(224, xor(shr(224, mload(p_)), v_))))
70+
}
71+
function getHash(i_) -> _h {
72+
_h := shr(224, mload(add(mload(0x40), shl(2, i_))))
73+
}
74+
function hash(v_) -> _r {
75+
_r := and(shr(19, mul(2654435769, v_)), 0x1fff)
76+
}
77+
function setNextHash(ip_, ipStart_) -> _ip {
78+
setHash(hash(u24(ip_)), sub(ip_, ipStart_))
79+
_ip := add(ip_, 1)
80+
}
81+
82+
output := mload(0x40)
83+
84+
calldatacopy(output, calldatasize(), 0x8000) // Zeroize the hashmap.
85+
let op := add(output, 0x8000)
86+
87+
let a := add(input, 0x20)
88+
89+
let ipStart := a
90+
let ipLimit := sub(add(ipStart, mload(input)), 13)
91+
for {
92+
let ip := add(2, a)
93+
} lt(ip, ipLimit) {} {
94+
let r := 0
95+
let d := 0
96+
for {} 1 {} {
97+
let s := u24(ip)
98+
let h := hash(s)
99+
r := add(ipStart, getHash(h))
100+
setHash(h, sub(ip, ipStart))
101+
d := sub(ip, r)
102+
if iszero(lt(ip, ipLimit)) {
103+
break
104+
}
105+
ip := add(ip, 1)
106+
if iszero(gt(d, 0x1fff)) {
107+
if eq(s, u24(r)) {
108+
break
109+
}
110+
}
111+
}
112+
if iszero(lt(ip, ipLimit)) {
113+
break
114+
}
115+
ip := sub(ip, 1)
116+
if gt(ip, a) {
117+
op := literals(sub(ip, a), a, op)
118+
}
119+
let l := cmp(add(r, 3), add(ip, 3), add(ipLimit, 9))
120+
op := mt(l, d, op)
121+
ip := setNextHash(setNextHash(add(ip, l), ipStart), ipStart)
122+
a := ip
123+
}
124+
// Copy the result to compact the memory, overwriting the hashmap.
125+
let end := sub(literals(sub(add(ipStart, mload(input)), a), a, op), 0x7fe0)
126+
let o := add(output, 0x20)
127+
mstore(output, sub(end, o)) // Store the length.
128+
for {} iszero(gt(o, end)) {
129+
o := add(o, 0x20)
130+
} {
131+
mstore(o, mload(add(o, 0x7fe0)))
132+
}
133+
134+
mstore(0x40, end)
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)