Last active
November 1, 2024 14:51
-
-
Save recmo/8adf1b518e17bb1d11b9cf565058714f to your computer and use it in GitHub Desktop.
This file contains hidden or 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
object "Claim" { | |
code { | |
datacopy(0, dataoffset("Runtime"), datasize("Runtime")) | |
return(0, datasize("Runtime")) | |
} | |
object "Runtime" { | |
code { | |
// Memory layout | |
// 0..192: ModExp precompile calldata | |
// 96..128: base | |
// 128..160: exponent (defaults to (p+1)/4, i.e. sqrt) | |
// 160..192: modulus (set to p) | |
// 192..580: Claim calldata | |
// 196..228: grantId (fixed for batch) | |
// 228..260: receiver | |
// 260..292: root (fixed for batch) | |
// 292..324: nullifierHash | |
// 324..580: proof | |
// Set modexp calldata and claim bytes4 | |
datacopy(0, dataoffset("Template"), datasize("Template")) | |
// Shared per batch | |
// 1 byte: grantId | |
// 32 bytes: root | |
// mstore(196, add(2560, shr(248, calldataload(0)))) | |
mstore(196, shr(248, calldataload(0))) | |
mstore(260, calldataload(1)) | |
// Loop through batch | |
let offset := 33 | |
for { } lt(offset, calldatasize()) { offset := add(offset, 180) } { | |
// 20 bytes: receiver address | |
// 32 bytes: nullifierHash | |
// 32 bytes: A x | |
// 32 bytes: B x 0 | |
// 32 bytes: B x 1 | |
// 32 bytes: C x | |
// Total 180 bytes. | |
// Set recipient address. | |
mstore(228, shr(96, calldataload(offset))) | |
// Set nullifierHash | |
mstore(292, calldataload(add(offset, 20))) | |
// Proof | |
{ // A | |
let x, y := decompress_g1(calldataload(add(offset, 52))) | |
mstore(324, x) | |
mstore(356, y) | |
} | |
{ // C | |
let x, y := decompress_g1(calldataload(add(offset, 148))) | |
mstore(516, x) | |
mstore(548, y) | |
} | |
{ // B | |
let x0, x1, y0, y1 := decompress_g2( | |
calldataload(add(offset, 84)), | |
calldataload(add(offset, 116)) | |
) | |
mstore(388, x1) | |
mstore(420, x0) | |
mstore(452, y1) | |
mstore(484, y0) | |
} | |
pop(call(gas(), 0xe773335550b63eed23a6e60dcc4709106a1f653c, 0, 192, 388, 0, 0)) | |
// pop(call(gas(), 0xC84337376696EfdefE38CD6112b4ad70E1EFCA95, 0, 192, 388, 0, 0)) | |
// Continue if call failed. We don't want to revert the whole batch. | |
} | |
function decompress_g1(n) -> x, y { | |
let P := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 | |
x := shr(1, n) | |
// Compute x³ + 3 which equals y² | |
// Note: Sqrt in the next step takes care of final reduction. | |
mstore(96, add(3, mulmod(mulmod(x, x, P), x, P))) | |
// y = sqrt y² | |
// Note: If the input does not have a quadratic residue it instead | |
// returns the quadratic residue of the negated input. | |
pop(staticcall(gas(), 5, 0, 192, 96, 32)) | |
y := mload(96) | |
// Negate y if bit is set | |
// Note: This does not reduce correctly for 0, but no point on the | |
// curve has a zero y-coordinate since (x³ + 3) is irreducible. | |
if and(n, 1) { | |
y := sub(P, y) | |
} | |
} | |
function decompress_g2(n0, n1) -> x0, x1, y0, y1 { | |
let P := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 | |
x0 := shr(2, n0) | |
x1 := n1 | |
// -3⋅x₀⋅x₁ | |
let n3ab := mulmod(mulmod(x0, x1, P), | |
0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd44 | |
, P) | |
// a₀ = 27/82 + x₀³ - 3⋅x₀⋅x₁² | |
let a0 := addmod( | |
0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5, | |
addmod( | |
mulmod(mulmod(x0, x0, P), x0, P), | |
mulmod(n3ab, x1, P), P), P) | |
// a₁ = -(3/82 + x₁³ - 3 ⋅ x₀²⋅x₁) | |
let a1 := sub(P, addmod( | |
0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775, | |
addmod( | |
mulmod(mulmod(x1, x1, P), x1, P), | |
mulmod(n3ab, x0, P), P), P)) | |
// d = ± sqrt(a₁² + a₁²) | |
mstore(96, addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)) | |
pop(staticcall(gas(), 5, 0, 192, 96, 32)) | |
let d := mload(96) | |
// Note: The next addmod takes care of the reduction if d is zero. | |
if and(n0, 1) { | |
d := sub(P, d) | |
} | |
// y₀ = sqrt((a₀ + d) / 2) | |
mstore(96, mulmod( | |
0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4, | |
addmod(a0, d, P), P)) | |
pop(staticcall(gas(), 5, 0, 192, 96, 32)) | |
y0 := mload(96) | |
// y₁ = a₁ / (2⋅y₀) | |
// Note: we temporarily set the exponent to p-2 to compute the inverse. | |
// OPT: Use a batch inversion for the whole claims batch. | |
mstore(96, add(y0, y0)) // modexp will reduce | |
mstore(128, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd45) | |
pop(staticcall(gas(), 5, 0, 192, 96, 32)) | |
mstore(128, 0x0c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52) | |
y1 := mulmod(a1, mload(96), P) | |
// Note: Points with y0 = 0 or y1 = 0 should not set the negate bit. | |
if and(n0, 2) { | |
y0 := sub(P, y0) | |
y1 := sub(P, y1) | |
} | |
} | |
} | |
data "Template" hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f5230644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47a41e6ceb00000000000000000000000000000000000000000000000000000000" | |
} | |
} |
This file contains hidden or 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
/* Example uncompressed transaction | |
https://optimistic.etherscan.io/tx/0x2ec43c5d9c1a6a9d887534c33e5eb7a174eb0a0f6c7b6fc4c0fff3ba2096c676 | |
execTransaction( | |
address to, | |
uint256 value, | |
bytes calldata data, | |
Enum.Operation operation, | |
uint256 safeTxGas, | |
uint256 baseGas, | |
uint256 gasPrice, | |
address gasToken, | |
address payable refundReceiver, | |
bytes memory signatures | |
) | |
000 6a761202 selector | |
004 000000000000000000000000b5fbfeba9848664fd1a49dc2a250d9b5d1294f2a to (ApprovalSwapRouter) | |
036 0000000000000000000000000000000000000000000000000000000000000000 value | |
068 0000000000000000000000000000000000000000000000000000000000000140 data offset | |
100 0000000000000000000000000000000000000000000000000000000000000001 operation | |
132 0000000000000000000000000000000000000000000000000000000000030d40 safeTxGas | |
164 0000000000000000000000000000000000000000000000000000000000000000 baseGas | |
196 0000000000000000000000000000000000000000000000000000000000000000 gasPrice | |
228 0000000000000000000000000000000000000000000000000000000000000000 gasToken | |
260 0000000000000000000000000000000000000000000000000000000000000000 refundReceiver | |
292 0000000000000000000000000000000000000000000000000000000000000280 signatures offset | |
// data | |
exactInputSingle(( | |
address tokenIn, | |
address tokenOut, | |
uint24 fee, | |
address recipient, | |
uint256 deadline, | |
uint256 amountIn, | |
uint256 amountOutMinimum, | |
uint160 sqrtPriceLimitX96 | |
)) | |
324 0000000000000000000000000000000000000000000000000000000000000104 length (260 bytes) | |
356 414bf389 selector | |
360 0000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c31607 tokenIn | |
392 00000000000000000000000068f180fcce6836688e9084f035309e29bf0a2095 tokenOut | |
424 00000000000000000000000000000000000000000000000000000000000001f4 fee | |
456 0000000000000000000000000bb48311c5e339d50c3bd6a2a8aa4eea964b2ebb recipient (== self) | |
488 0000000000000000000000000000000000000000000000000000000064c4e78a deadline | |
520 000000000000000000000000000000000000000000000000000000000007a120 amountIn | |
552 000000000000000000000000000000000000000000000000000000000000069a amountOutMinimum | |
584 0000000000000000000000000000000000000000000000000000000000000000 sqrtPriceLimitX96 | |
// padding (28 bytes) | |
616 00000000000000000000000000000000000000000000000000000000 | |
// signatures | |
644 0000000000000000000000000000000000000000000000000000000000000041 length (65 bytes) | |
676 4001b35fcfd1fe5770e2fe0aa35bf0edf8ae0d8b2bbff331d05fbc2bebb3f401 r | |
708 0f1dbac698c6b1528df3c01e2741f5f189feb275063971e987b4b12ca717666c s | |
740 1b v | |
// padding (31 bytes) | |
741 00000000000000000000000000000000000000000000000000000000000000 | |
total 772 bytes | |
*/ | |
object "SafeSwap" { | |
code { | |
datacopy(0, dataoffset("Runtime"), datasize("Runtime")) | |
return(0, datasize("Runtime")) | |
} | |
object "Runtime" { | |
code { | |
// Load template. | |
// Datacopy Costs 3 gas per word. Template is 22 words excluding | |
// trailing zeros. Total 66 gas. | |
// Alternatively, 7 pokes would suffice at 9 gas each. Total 63 gas. | |
// Hybrid approach: 12 word copy + 1 poke: 45 gas. | |
datacopy(0, dataoffset("Template"), datasize("Template")) | |
mstore(644, 0x41) | |
// Fixed values per batch | |
// 3 bytes: Deadline offset in 16 second increments starting from 1693937440. | |
// This allows dates until 2032-03-08. | |
let deadlineoffset := add(1693937440, shl(4, shr(232, calldataload(0)))) | |
// Loop through batch | |
let offset := 3 | |
for { } lt(offset, calldatasize()) { offset := add(offset, 117) } { | |
// 20 bytes: wallet address | |
// 1 byte : pool index (tokenIn, tokenOut, fee) | |
// 1 bytes: deadline - offset. | |
// 12 bytes: amountIn. | |
// 12 bytes: amountOutMinimum. | |
// 32 bytes: signature r | |
// 32 bytes: signature s & v | |
// Total 110 bytes. | |
// Set token pair and fee | |
datacopy(16, add(dataoffset("Tokens"), mul(shr(248, calldataload(add(offset, 21)), 192)), 192) | |
// Set recipient address to wallet address. | |
mstore(360, shr(96, calldataload(offset))) | |
// Set deadline | |
// 8 bits allows a batch to have deadlines spanning 4 minutes. | |
mstore(392, add(deadlineoffset, shr(248, calldataload(add(offset, 21))))) | |
// Set amountIn. | |
mstore(424, shr(160, calldataload(add(offset, 41)))) | |
// Set amountOutMinimum. | |
mstore(456, shr(160, calldataload(add(offset, 41)))) | |
// Set r. | |
mstore(484, calldataload(add(offset, 53))) | |
// Set v and s. | |
{ | |
let s := calldataload(add(offset, 85)) | |
mstore8(548, add(27, and(s, 1))) | |
mstore(516, shr(1, s)) | |
} | |
let _ := call(gas(), shr(96, calldataload(offset)), 0, 0, 580, 0, 0) | |
// Continue if call failed. We don't want to revert the whole batch. | |
} | |
} | |
data "Template" hex"6a761202000000000000000000000000b5fbfeba9848664fd1a49dc2a250d9b5d1294f2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000104414bf389" | |
// Tuples of (tokenIn, tokenOut, poolFee) (3×32 bytes) | |
data "Pools" hex"000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f10000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c31607000000000000000000000000000000000000000000000000000000000000271000000000000000000000000068f180fcce6836688e9084f035309e29bf0a20950000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c3160700000000000000000000000000000000000000000000000000000000000001f40000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c31607000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f100000000000000000000000000000000000000000000000000000000000027100000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c3160700000000000000000000000068f180fcce6836688e9084f035309e29bf0a209500000000000000000000000000000000000000000000000000000000000001f400000000000000000000000042000000000000000000000000000000000000060000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c3160700000000000000000000000000000000000000000000000000000000000001f40000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c31607000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000001f40000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c31607000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f10000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f10000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c316070000000000000000000000000000000000000000000000000000000000000bb8" | |
} | |
} |
This file contains hidden or 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
// TODO: Instead of address we could send the creation salt and tx | |
// nonce. Use ECRECOVER to get the owner and then use that to | |
// reconstruct the Safe deployment and create2 address. | |
// This costs at least 3k gas and saves ~14 bytes when using 3-byte salt/nonce. | |
object "SafeTransfer" { | |
code { | |
datacopy(0, dataoffset("Runtime"), datasize("Runtime")) | |
return(0, datasize("Runtime")) | |
} | |
object "Runtime" { | |
code { | |
// Load template. Costs 3 gas per word. Template is 16 words excluding | |
// trailing zeros. Total 48 gas. | |
// Alternatively, since it's mostly zeros, six pokes would suffice at | |
// 9 gas each. Total 54 gas. | |
datacopy(0, dataoffset("Template"), datasize("Template")) | |
// Loop through batch | |
let offset := 0 | |
for { } lt(offset, calldatasize()) { offset := add(offset, 117) } { | |
// 20 bytes: wallet address | |
// 1 byte : token index. | |
// 20 bytes: to address | |
// 12 bytes: amount | |
// 32 bytes: signature r | |
// 32 bytes: signature s & v | |
// Total 117 bytes. | |
// Set token address. | |
datacopy(16, add(dataoffset("Tokens"), mul( | |
shr(248, calldataload(add(offset, 20))) | |
, 20)), 20) | |
// Set to address. | |
mstore(360, shr(96, calldataload(add(offset, 21)))) | |
// Set amount. | |
mstore(392, shr(160, calldataload(add(offset, 41)))) | |
// Set r. | |
mstore(484, calldataload(add(offset, 53))) | |
// Set v and s. | |
{ | |
let s := calldataload(add(offset, 85)) | |
mstore8(548, add(27, and(s, 1))) | |
mstore(516, shr(1, s)) | |
} | |
let _ := call(gas(), shr(96, calldataload(offset)), 0, 0, 580, 0, 0) | |
// Continue if call failed. We don't want to revert the whole batch. | |
} | |
} | |
data "Template" hex"6a76120200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041" | |
// TODO: We can have up to 256 tokens at no extra cost. Add all the popular ones! | |
data "Tokens" hex"dC6fF44d5d932Cbd77B52E5612Ba0529DC6226F142000000000000000000000000000000000000067F5c764cBc14f9669B88837ca1490cCa17c3160768f180fcCe6836688e9084f035309E29Bf0A2095" | |
// data "Tokens" hex"6FCc5ff6CD784b11D55e3bEBDD05376f7c17E6F64200000000000000000000000000000000000006f132e6112e358a36fCCF4660d082C9dAe3DA7411b21Fa419e6C82b6915Ab759507283b7b0F2Cb59E" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment