├── .gitmodules ├── src ├── types │ ├── Error.sol │ ├── BytesCalldata.sol │ ├── protocols │ │ ├── Dyn.sol │ │ ├── WETH.sol │ │ ├── ERC721.sol │ │ ├── UniV2Pair.sol │ │ ├── ERC6909.sol │ │ ├── ERC20.sol │ │ └── UniV3Pool.sol │ ├── Action.sol │ └── PayloadPointer.sol ├── LotusRouter.sol └── util │ ├── BBCEncoder.sol │ └── BBCDecoder.sol ├── .gitignore ├── test ├── interfaces │ ├── IUniV3FlashCallback.sol │ ├── IUniV2Callee.sol │ └── IUniV3SwapCallback.sol ├── mock │ ├── ERC721Mock.sol │ ├── DynTargetMock.sol │ ├── UniV2PairMock.sol │ ├── ERC6909Mock.sol │ ├── ERC20Mock.sol │ ├── UniV3PoolMock.sol │ ├── WETHMock.sol │ └── BBCDecoderMock.sol └── BBCDecoder.t.sol ├── foundry.toml ├── .github └── workflows │ └── test.yml ├── .gas-snapshot ├── README.md └── LICENSE /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /src/types/Error.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | library Error { 5 | error UnexpectedEntryPoint(); 6 | error CallFailure(); 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /test/interfaces/IUniV3FlashCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | interface IUniV3FlashCallback { 5 | function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external; 6 | } 7 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | optimizer_runs = 0xffffffff 7 | via_ir = true 8 | bytecode_hash = "none" 9 | 10 | [fmt] 11 | multiline_func_header = "params_first" 12 | sort_imports = true 13 | line_length = 100 14 | bracket_spacing = true 15 | -------------------------------------------------------------------------------- /test/interfaces/IUniV2Callee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | interface IUniV2Callee { 5 | function uniswapV2Call( 6 | address sender, 7 | uint256 amount0, 8 | uint256 amount1, 9 | bytes calldata data 10 | ) external; 11 | } 12 | -------------------------------------------------------------------------------- /test/interfaces/IUniV3SwapCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | interface IUniV3SwapCallback { 5 | function uniswapV3SwapCallback( 6 | int256 amount0Delta, 7 | int256 amount1Delta, 8 | bytes calldata data 9 | ) external; 10 | } 11 | -------------------------------------------------------------------------------- /src/types/BytesCalldata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | // Why? Because the compiler doesn't like unconventional usage of the standard 5 | // calldata bytes pointer `bytes calldata`. As such, we occupy 32 bits to 6 | // indicate its start, however, its encoding is dependent on the schemas defined 7 | // in the [`BBCDecoder`](src/types/BBCDecoder.sol) library. 8 | type BytesCalldata is uint32; 9 | -------------------------------------------------------------------------------- /src/types/protocols/Dyn.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { BytesCalldata } from "src/types/BytesCalldata.sol"; 5 | 6 | function dynCall(address target, uint256 value, BytesCalldata data) returns (bool success) { 7 | assembly ("memory-safe") { 8 | let fmp := mload(0x40) 9 | 10 | let dataLen := shr(0xe0, calldataload(data)) 11 | 12 | data := add(data, 0x04) 13 | 14 | calldatacopy(fmp, data, dataLen) 15 | 16 | success := call(gas(), target, value, fmp, dataLen, 0x00, 0x00) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/mock/ERC721Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | contract ERC721Mock { 5 | event Transfer(address indexed sender, address indexed receiver, uint256 indexed tokenId); 6 | 7 | bool internal _shouldThrow = false; 8 | 9 | function setShouldThrow( 10 | bool shouldThrow 11 | ) public { 12 | _shouldThrow = shouldThrow; 13 | } 14 | 15 | function transferFrom(address sender, address receiver, uint256 tokenId) public { 16 | require(!_shouldThrow); 17 | 18 | emit Transfer(sender, receiver, tokenId); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/types/Action.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | // ## Action Enumeration 5 | // 6 | // This represents the opcodes for the Lotus Router. 7 | // 8 | // Each option maps to an integer (index zero), and we dispatch based on which 9 | // action is encoded. This allows for tighter packing of calldata. 10 | enum Action { 11 | Halt, 12 | SwapUniV2, 13 | SwapUniV3, 14 | FlashUniV3, 15 | TransferERC20, 16 | TransferFromERC20, 17 | TransferFromERC721, 18 | TransferERC6909, 19 | TransferFromERC6909, 20 | DepositWETH, 21 | WithdrawWETH, 22 | DynCall 23 | } 24 | -------------------------------------------------------------------------------- /test/mock/DynTargetMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | contract DynTargetMock { 5 | event Called(address caller, uint256 value, bytes data); 6 | 7 | bool _shouldThrow = false; 8 | 9 | function setShouldThrow(bool shouldThrow) public { 10 | _shouldThrow = shouldThrow; 11 | } 12 | 13 | fallback() external payable { 14 | require(!_shouldThrow); 15 | 16 | emit Called(msg.sender, msg.value, msg.data); 17 | } 18 | 19 | receive() external payable { 20 | require(!_shouldThrow); 21 | 22 | emit Called(msg.sender, msg.value, new bytes(0)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | strategy: 14 | fail-fast: true 15 | 16 | name: Foundry project 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install Foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | 26 | - name: Show Forge version 27 | run: | 28 | forge --version 29 | 30 | - name: Run Forge fmt 31 | run: | 32 | forge fmt --check 33 | id: fmt 34 | 35 | - name: Run Forge build 36 | run: | 37 | forge build --sizes 38 | id: build 39 | 40 | - name: Run Forge tests 41 | run: | 42 | forge test -vvv 43 | id: test 44 | -------------------------------------------------------------------------------- /test/mock/UniV2PairMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { IUniV2Callee } from "test/interfaces/IUniV2Callee.sol"; 5 | 6 | contract UniV2PairMock { 7 | event Swap(uint256 amount0Out, uint256 amount1Out, address to, bytes data); 8 | 9 | bool internal _shouldDoCallback = false; 10 | bool internal _shouldThrow = false; 11 | 12 | function setDoCallback( 13 | bool shouldDoCallback 14 | ) public { 15 | _shouldDoCallback = shouldDoCallback; 16 | } 17 | 18 | function setShouldThrow( 19 | bool shouldThrow 20 | ) public { 21 | _shouldThrow = shouldThrow; 22 | } 23 | 24 | function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) public { 25 | if (_shouldThrow) revert(); 26 | 27 | emit Swap(amount0Out, amount1Out, to, data); 28 | 29 | if (_shouldDoCallback && data.length > 0) { 30 | IUniV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/mock/ERC6909Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | contract ERC6909Mock { 5 | event Transfer( 6 | address caller, address sender, address receiver, uint256 tokenId, uint256 amount 7 | ); 8 | 9 | bool internal _shouldThrow = false; 10 | bool internal _result = true; 11 | 12 | function setShouldThrow( 13 | bool shouldThrow 14 | ) public { 15 | _shouldThrow = shouldThrow; 16 | } 17 | 18 | function setResult( 19 | bool result 20 | ) public { 21 | _result = result; 22 | } 23 | 24 | function transfer(address receiver, uint256 tokenId, uint256 amount) public returns (bool) { 25 | require(!_shouldThrow); 26 | 27 | emit Transfer(msg.sender, msg.sender, receiver, tokenId, amount); 28 | 29 | return _result; 30 | } 31 | 32 | function transferFrom( 33 | address sender, 34 | address receiver, 35 | uint256 tokenId, 36 | uint256 amount 37 | ) public returns (bool) { 38 | require(!_shouldThrow); 39 | 40 | emit Transfer(msg.sender, sender, receiver, tokenId, amount); 41 | 42 | return _result; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/mock/ERC20Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | contract ERC20Mock { 5 | event Transfer(address indexed sender, address indexed receiver, uint256 amount); 6 | 7 | bool internal _shouldThrow = false; 8 | bool internal _shouldReturnAnything = true; 9 | bool internal _result = true; 10 | 11 | function setShouldThrow( 12 | bool shouldThrow 13 | ) public { 14 | _shouldThrow = shouldThrow; 15 | } 16 | 17 | function setShouldReturnAnything( 18 | bool shouldReturnAnything 19 | ) public { 20 | _shouldReturnAnything = shouldReturnAnything; 21 | } 22 | 23 | function setResult( 24 | bool result 25 | ) public { 26 | _result = result; 27 | } 28 | 29 | function transfer(address receiver, uint256 amount) public returns (bool) { 30 | require(!_shouldThrow); 31 | 32 | emit Transfer(msg.sender, receiver, amount); 33 | 34 | if (_shouldReturnAnything) return _result; 35 | 36 | assembly { 37 | return(0x00, 0x00) 38 | } 39 | } 40 | 41 | function transferFrom(address sender, address receiver, uint256 amount) public returns (bool) { 42 | require(!_shouldThrow); 43 | 44 | emit Transfer(sender, receiver, amount); 45 | 46 | if (_shouldReturnAnything) return _result; 47 | 48 | assembly { 49 | return(0x00, 0x00) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/types/protocols/WETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | type WETH is address; 5 | 6 | using { deposit, withdraw } for WETH global; 7 | 8 | uint256 constant withdrawSelector = 9 | 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000; 10 | 11 | // ## Execute WETH deposit 12 | // 13 | // ### Parameters 14 | // 15 | // - value: The deposit amount. 16 | // 17 | // ### Returns 18 | // 19 | // - success: returns True if the deposit succeeded 20 | // 21 | // ### Notes 22 | // 23 | // Hard-coding the WETH address would be profitable for single-chain 24 | // implementations, though we're focusing on multi-chain deployability, so our 25 | // implementation will require it to be passed in as calldata. 26 | // 27 | // Also, using the fallback function with no calldata is marginally cheaper than 28 | // using the `deposit()` function, as Solidity short circuits the selector 29 | // dispatcher in WETH if the `calldatasize` is zero. 30 | // 31 | // ### Procedures 32 | // 33 | // 01. Call the `weth` contract, returning the boolean. 34 | function deposit(WETH weth, uint256 value) returns (bool success) { 35 | assembly ("memory-safe") { 36 | success := call(gas(), weth, value, 0x00, 0x00, 0x00, 0x00) 37 | } 38 | } 39 | 40 | // ## Execute WETH withdraw 41 | // 42 | // ### Parameters 43 | // 44 | // - value: The withdraw amount. 45 | // 46 | // ### Returns 47 | // 48 | // - success: returns True if the withdraw succeeded 49 | // 50 | // ### Notes 51 | // 52 | // Hard-coding the WETH address would be profitable for single-chain 53 | // implementations, though we're focusing on multi-chain deployability, so our 54 | // implementation will require it to be passed in as calldata. 55 | // 56 | // ### Procedures 57 | // 58 | // 01. Store the `withdrawSelector`. 59 | // 02. Store the `value`. 60 | // 03. Call the `weth` contract, returning the boolean. 61 | function withdraw(WETH weth, uint256 value) returns (bool success) { 62 | assembly ("memory-safe") { 63 | mstore(0x00, withdrawSelector) 64 | 65 | mstore(0x04, value) 66 | 67 | success := call(gas(), weth, 0x00, 0x00, 0x24, 0x00, 0x00) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/types/protocols/ERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | type ERC721 is address; 5 | 6 | using { transferFrom } for ERC721 global; 7 | 8 | uint256 constant transferFromSelector = 9 | 0x23b872dd00000000000000000000000000000000000000000000000000000000; 10 | 11 | // ## Execute ERC721 transfer 12 | // 13 | // ### Parameters 14 | // 15 | // - token: The ERC721 address. 16 | // - sender: The transfer sender address. 17 | // - receiver: The transfer receiver address. 18 | // - tokenId: The token ID to transfer. 19 | // 20 | // ### Returns 21 | // 22 | // - success: returns True if the transfer succeeded 23 | // 24 | // ### Notes 25 | // 26 | // Since the calldata of this is small, we allocate the memory for it in the 27 | // scratch space normally used by Solidity for keccak hashing. This reduces 28 | // overall memory allocations. However, it's worth noting that doing this 29 | // occupies the first 100 bytes, which overwrites the free memory pointer and 30 | // the first four bytes of the zero slot `0x60`. So we overwrite these values 31 | // at the end to ensure the free memory pointer and zero slot are correct. 32 | // 33 | // ### Procedures 34 | // 35 | // 01. Load the free memory pointer. 36 | // 02. Store the `transferFromSelector`. 37 | // 03. Store the `sender`. 38 | // 04. Store the `receiver`. 39 | // 05. Store the `tokenId`. 40 | // 06. Call the `token` contract, caching the `success` boolean. 41 | // 07. Check that either the `returndatasize` is zero or the returned value is. 42 | // non-zero. 43 | // 08. Logical AND the success conditions. 44 | // 09. Restore the free memory pointer. 45 | // 10. Restore the zero slot. 46 | function transferFrom( 47 | ERC721 token, 48 | address sender, 49 | address receiver, 50 | uint256 tokenId 51 | ) returns (bool success) { 52 | assembly ("memory-safe") { 53 | let fmp := mload(0x40) 54 | 55 | mstore(0x00, transferFromSelector) 56 | 57 | mstore(0x04, sender) 58 | 59 | mstore(0x24, receiver) 60 | 61 | mstore(0x44, tokenId) 62 | 63 | success := call(gas(), token, 0x00, 0x00, 0x64, 0x00, 0x00) 64 | 65 | mstore(0x40, fmp) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/types/PayloadPointer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { Action } from "src/types/Action.sol"; 5 | import { Error } from "src/types/Error.sol"; 6 | 7 | type Ptr is uint256; 8 | 9 | using { nextAction } for Ptr global; 10 | 11 | uint256 constant takeAction = 0x19ff8034; 12 | uint256 constant uniswapV2Call = 0x10d1e85c; 13 | uint256 constant uniswapV3SwapCallback = 0xfa461e33; 14 | uint256 constant uniswapV3FlashCallback = 0xe9cbafb0; 15 | 16 | // ## Finds the Payload Pointer 17 | // 18 | // ### Returns 19 | // 20 | // - ptr: A pointer to the payload in calldata 21 | // 22 | // ### Reverts 23 | // 24 | // - If the selector does not match any expected ones. 25 | // 26 | // ### Notes 27 | // 28 | // For `callLotusRouter()`, the payload immediately follows the selector at 29 | // index four (`0x04`). 30 | // 31 | // For `uniswapV2Call(address,uint256,uint256,bytes)`, the payload is in the 32 | // fourth parameter, `bytes calldata data`, where the offset is at index 100, 33 | // the length is at index 132, and the data itself is at index 164 (`0xa4`). 34 | function findPtr() pure returns (Ptr) { 35 | uint256 selector = uint256(uint32(msg.sig)); 36 | 37 | if (selector == takeAction) { 38 | return Ptr.wrap(0x04); 39 | } else if (selector == uniswapV2Call) { 40 | return Ptr.wrap(0xa4); 41 | } else if (selector == uniswapV3SwapCallback) { 42 | return Ptr.wrap(0x84); 43 | } else if (selector == uniswapV3FlashCallback) { 44 | return Ptr.wrap(0x84); 45 | } else { 46 | revert Error.UnexpectedEntryPoint(); 47 | } 48 | } 49 | 50 | // ## Loads the Next Action from Calldata 51 | // 52 | // ## Parameters 53 | // 54 | // - ptr: The payload pointer 55 | // 56 | // ## Returns 57 | // 58 | // - ptr: The incremented payload pointer 59 | // - action: The loaded action 60 | // 61 | // ## Notes 62 | // 63 | // The `ptr` parameter is incremented in place to allow continuous parsing. 64 | function nextAction( 65 | Ptr ptr 66 | ) pure returns (Ptr, Action action) { 67 | assembly { 68 | action := shr(0xf8, calldataload(ptr)) 69 | 70 | ptr := add(ptr, 0x01) 71 | } 72 | 73 | return (ptr, action); 74 | } 75 | -------------------------------------------------------------------------------- /test/mock/UniV3PoolMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { IUniV3FlashCallback } from "test/interfaces/IUniV3FlashCallback.sol"; 5 | import { IUniV3SwapCallback } from "test/interfaces/IUniV3SwapCallback.sol"; 6 | 7 | contract UniV3PoolMock { 8 | event Swap( 9 | address sender, 10 | address recipient, 11 | bool zeroForOne, 12 | int256 amountSpecified, 13 | uint160 sqrtPriceX96, 14 | bytes data 15 | ); 16 | 17 | event Flash(address recipient, uint256 amount0, uint256 amount1, bytes data); 18 | 19 | bool internal _shouldDoCallback = false; 20 | bool internal _shouldThrow = false; 21 | int256 internal _amount0Delta = 0x01; 22 | int256 internal _amount1Delta = 0x02; 23 | 24 | function setDoCallback( 25 | bool shouldDoCallback 26 | ) public { 27 | _shouldDoCallback = shouldDoCallback; 28 | } 29 | 30 | function setShouldThrow( 31 | bool shouldThrow 32 | ) public { 33 | _shouldThrow = shouldThrow; 34 | } 35 | 36 | function setDeltas(int256 amount0Delta, int256 amount1Delta) public { 37 | _amount0Delta = amount0Delta; 38 | _amount1Delta = amount1Delta; 39 | } 40 | 41 | function swap( 42 | address recipient, 43 | bool zeroForOne, 44 | int256 amountSpecified, 45 | uint160 sqrtPriceLimitX96, 46 | bytes calldata data 47 | ) public { 48 | require(!_shouldThrow); 49 | 50 | emit Swap(msg.sender, recipient, zeroForOne, amountSpecified, sqrtPriceLimitX96, data); 51 | 52 | if (_shouldDoCallback) { 53 | IUniV3SwapCallback(msg.sender).uniswapV3SwapCallback(_amount0Delta, _amount1Delta, data); 54 | } 55 | } 56 | 57 | function flash( 58 | address recipient, 59 | uint256 amount0, 60 | uint256 amount1, 61 | bytes calldata data 62 | ) public { 63 | require(!_shouldThrow); 64 | 65 | emit Flash(recipient, amount0, amount1, data); 66 | 67 | if (_shouldDoCallback) { 68 | IUniV3FlashCallback(msg.sender).uniswapV3FlashCallback(amount0, amount1, data); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/types/protocols/UniV2Pair.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { BytesCalldata } from "src/types/BytesCalldata.sol"; 5 | 6 | type UniV2Pair is address; 7 | 8 | using { swap } for UniV2Pair global; 9 | 10 | uint256 constant swapSelector = 0x022c0d9f00000000000000000000000000000000000000000000000000000000; 11 | 12 | // ## Execute Uniswap V2 Swap 13 | // 14 | // ### Parameters 15 | // 16 | // - pair: The Uniswap V2 pair address. 17 | // - amount0Out: The expected output amount for token 0. 18 | // - amount1Out: The expected output amount for token 1. 19 | // - to: The receiver of the swap output. 20 | // - data: The arbitrary calldata for UniV2 callbacks, if any. 21 | // 22 | // ### Returns 23 | // 24 | // - success: returns True if the swap succeeded. 25 | // 26 | // ### Notes 27 | // 28 | // If swapping across multiple pairs, `to` will be the next pair in the chain. 29 | // 30 | // ### Procedures 31 | // 32 | // 01. Load the free memory pointer. 33 | // 02. Load the `data` length as a 32 bit integer. 34 | // 03. Increment the `data` pointer to the beginning of the bytes. 35 | // 04. Store the `swapSelector`. 36 | // 05. Store the `amount0Out` argument. 37 | // 06. Store the `amount1Out` argument. 38 | // 07. Store the `to` argument. 39 | // 08. Store the `data` offset, relative to the slot after the selector. 40 | // 09. Store the `dataLen`. 41 | // 10. Copy the data from calldata to memory. 42 | // 11. Call the `pair` contract, returning `success` to the caller of this 43 | // function. 44 | function swap( 45 | UniV2Pair pair, 46 | uint256 amount0Out, 47 | uint256 amount1Out, 48 | address to, 49 | BytesCalldata data 50 | ) returns (bool success) { 51 | assembly ("memory-safe") { 52 | let fmp := mload(0x40) 53 | 54 | let dataLen := shr(0xe0, calldataload(data)) 55 | 56 | data := add(data, 0x04) 57 | 58 | mstore(add(fmp, 0x00), swapSelector) 59 | 60 | mstore(add(fmp, 0x04), amount0Out) 61 | 62 | mstore(add(fmp, 0x24), amount1Out) 63 | 64 | mstore(add(fmp, 0x44), to) 65 | 66 | mstore(add(fmp, 0x64), 0x80) 67 | 68 | mstore(add(fmp, 0x84), dataLen) 69 | 70 | calldatacopy(add(fmp, 0xa4), data, dataLen) 71 | 72 | success := call(gas(), pair, 0x00, fmp, add(dataLen, 0xc4), 0x00, 0x00) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/types/protocols/ERC6909.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | type ERC6909 is address; 5 | 6 | using { transfer, transferFrom } for ERC6909 global; 7 | 8 | uint256 constant transferSelector = 9 | 0x095bcdb600000000000000000000000000000000000000000000000000000000; 10 | uint256 constant transferFromSelector = 11 | 0xfe99049a00000000000000000000000000000000000000000000000000000000; 12 | 13 | // ## Execute ERC6909 transfer 14 | // 15 | // ### Parameters 16 | // 17 | // - token: The ERC6909 address. 18 | // - receiver: The transfer receiver address. 19 | // - tokenId: The ID of the token to transfer. 20 | // - amount: The transfer amount. 21 | // 22 | // ### Returns 23 | // 24 | // - success: returns True if the transfer succeeded 25 | // 26 | // ### Notes 27 | // 28 | // Since the calldata of this is small, we allocate the memory for it in the 29 | // scratch space normally used by Solidity for keccak hashing. This reduces 30 | // overall memory allocations. However, it's worth noting that doing this 31 | // occupies the first 100 bytes, which overwrites the free memory pointer and 32 | // the first four bytes of the zero slot `0x60`. So we overwrite these upper 33 | // bytes at the end to ensure the free memory pointer and zero slot are correct. 34 | // 35 | // ### Procedures 36 | // 37 | // 01. Load the free memory pointer. 38 | // 02. Store the `transferSelector`. 39 | // 03. Store the `receiver`. 40 | // 04. Store the `tokenId`. 41 | // 05. Store the `amount`. 42 | // 06. Call the `token` contract, caching the `success` boolean. 43 | // 07. Check that the return value is true (0x01). 44 | // 08. Restore the free memory pointer. 45 | // 09. Restore the zero slot. 46 | function transfer( 47 | ERC6909 multitoken, 48 | address receiver, 49 | uint256 tokenId, 50 | uint256 amount 51 | ) returns (bool success) { 52 | assembly ("memory-safe") { 53 | let fmp := mload(0x40) 54 | 55 | mstore(0x00, transferSelector) 56 | 57 | mstore(0x04, receiver) 58 | 59 | mstore(0x24, tokenId) 60 | 61 | mstore(0x44, amount) 62 | 63 | success := call(gas(), multitoken, 0x00, 0x00, 0x64, 0x00, 0x20) 64 | 65 | success := and(success, eq(0x01, mload(0x00))) 66 | 67 | mstore(0x40, fmp) 68 | 69 | mstore(0x60, 0x00) 70 | } 71 | } 72 | 73 | // ## Execute ERC6909 transfer 74 | // 75 | // ### Parameters 76 | // 77 | // - token: The ERC6909 address. 78 | // - receiver: The transfer receiver address. 79 | // - tokenId: The ID of the token to transfer. 80 | // - amount: The transfer amount. 81 | // 82 | // ### Returns 83 | // 84 | // - success: returns True if the transfer succeeded 85 | // 86 | // ### Notes 87 | // 88 | // We store data at the end of allocated memory. Generally, memory is not 89 | // allocated (as in the free memory pointer is updated), but in the interest of 90 | // keeping this straight forward for modifications, we use the free memory 91 | // pointer to start allocating memory. 92 | // 93 | // Note that we do not update the free memory pointer, as this allows solidity 94 | // to overwrite this memory, saving space. Again, this is likely of little to 95 | // no consequence in the Lotus Router, but modifications may. 96 | // 97 | // ### Procedures 98 | // 99 | // 01. Load the free memory pointer. 100 | // 02. Store the `transferFrom`. 101 | // 03. Store the `sender`. 102 | // 04. Store the `receiver`. 103 | // 05. Store the `tokenId`. 104 | // 06. Store the `amount`. 105 | // 07. Call the `token` contract, caching the `success` boolean. 106 | // 08. Check that the return value is true (0x01). 107 | function transferFrom( 108 | ERC6909 multitoken, 109 | address sender, 110 | address receiver, 111 | uint256 tokenId, 112 | uint256 amount 113 | ) returns (bool success) { 114 | assembly ("memory-safe") { 115 | let fmp := mload(0x40) 116 | 117 | mstore(add(fmp, 0x00), transferFromSelector) 118 | 119 | mstore(add(fmp, 0x04), sender) 120 | 121 | mstore(add(fmp, 0x24), receiver) 122 | 123 | mstore(add(fmp, 0x44), tokenId) 124 | 125 | mstore(add(fmp, 0x64), amount) 126 | 127 | success := call(gas(), multitoken, 0x00, fmp, 0x84, fmp, 0x20) 128 | 129 | success := and(success, eq(0x01, mload(fmp))) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/types/protocols/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | type ERC20 is address; 5 | 6 | using { transfer, transferFrom } for ERC20 global; 7 | 8 | uint256 constant transferSelector = 9 | 0xa9059cbb00000000000000000000000000000000000000000000000000000000; 10 | uint256 constant transferFromSelector = 11 | 0x23b872dd00000000000000000000000000000000000000000000000000000000; 12 | 13 | // ## Execute ERC20 transfer 14 | // 15 | // ### Parameters 16 | // 17 | // - token: The ERC20 address. 18 | // - receiver: The transfer receiver address. 19 | // - amount: The transfer amount. 20 | // 21 | // ### Returns 22 | // 23 | // - success: returns True if the transfer succeeded 24 | // 25 | // ### Notes 26 | // 27 | // ERC20 conformity is a debacle. Some never return anything, some revert on 28 | // failure, some return false on failure. So we check that the execution context 29 | // did not revert and that either nothing was returned, or if something was 30 | // returned, it is a nonzero value (not false). 31 | // 32 | // Since the calldata of this is small, we allocate the memory for it in the 33 | // scratch space normally used by Solidity for keccak hashing. This reduces 34 | // overall memory allocations. However, it's worth noting that doing this 35 | // occupies the first 68 bytes, which overwrites the first four bytes of memory 36 | // slot `0x40`, which contains the free memory pointer. So we overwrite these 37 | // upper bytes at the end to ensure the free memory pointer is correct. 38 | // 39 | // ### Procedures 40 | // 41 | // 01. Store the `transferSelector`. 42 | // 02. Store the `receiver`. 43 | // 03. Store the `amount`. 44 | // 04. Call the `token` contract, caching the `success` boolean. 45 | // 05. Check that either the `returndatasize` is zero or the returned value is. 46 | // non-zero. 47 | // 06. Logical AND the success conditions. 48 | // 07. Store zero to restore the upper bytes of the free memory pointer to zero. 49 | function transfer(ERC20 token, address receiver, uint256 amount) returns (bool success) { 50 | assembly ("memory-safe") { 51 | mstore(0x00, transferSelector) 52 | 53 | mstore(0x04, receiver) 54 | 55 | mstore(0x24, amount) 56 | 57 | success := call(gas(), token, 0x00, 0x00, 0x44, 0x00, 0x20) 58 | 59 | let successERC20 := or(iszero(returndatasize()), eq(0x01, mload(0x00))) 60 | 61 | success := and(success, successERC20) 62 | 63 | mstore(0x24, 0x00) 64 | } 65 | } 66 | 67 | // ## Execute ERC20 transfer 68 | // 69 | // ### Parameters 70 | // 71 | // - token: The ERC20 address. 72 | // - sender: The transfer sender address. 73 | // - receiver: The transfer receiver address. 74 | // - amount: The transfer amount. 75 | // 76 | // ### Returns 77 | // 78 | // - success: returns True if the transfer succeeded 79 | // 80 | // ### Notes 81 | // 82 | // ERC20 conformity is a debacle. Some never return anything, some revert on 83 | // failure, some return false on failure. So we check that the execution context 84 | // did not revert and that either nothing was returned, or if something was 85 | // returned, it is a nonzero value (not false). 86 | // 87 | // Since the calldata of this is small, we allocate the memory for it in the 88 | // scratch space normally used by Solidity for keccak hashing. This reduces 89 | // overall memory allocations. However, it's worth noting that doing this 90 | // occupies the first 100 bytes, which overwrites the free memory pointer and 91 | // the first four bytes of the zero slot `0x60`. So we overwrite these values 92 | // at the end to ensure the free memory pointer and zero slot are correct. 93 | // 94 | // ### Procedures 95 | // 96 | // 01. Load the free memory pointer. 97 | // 02. Store the `transferFromSelector`. 98 | // 03. Store the `sender`. 99 | // 04. Store the `receiver`. 100 | // 05. Store the `amount`. 101 | // 06. Call the `token` contract, caching the `success` boolean. 102 | // 07. Check that either the `returndatasize` is zero or the returned value is 103 | // true (0x01). 104 | // 08. Logical AND the success conditions. 105 | // 09. Restore the free memory pointer. 106 | // 10. Restore the zero slot. 107 | function transferFrom( 108 | ERC20 token, 109 | address sender, 110 | address receiver, 111 | uint256 amount 112 | ) returns (bool success) { 113 | assembly ("memory-safe") { 114 | let fmp := mload(0x40) 115 | 116 | mstore(0x00, transferFromSelector) 117 | 118 | mstore(0x04, sender) 119 | 120 | mstore(0x24, receiver) 121 | 122 | mstore(0x44, amount) 123 | 124 | success := call(gas(), token, 0x00, 0x00, 0x64, 0x00, 0x20) 125 | 126 | let successERC20 := or(iszero(returndatasize()), eq(0x01, mload(0x00))) 127 | 128 | success := and(success, successERC20) 129 | 130 | mstore(0x40, fmp) 131 | 132 | mstore(0x60, 0x00) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/types/protocols/UniV3Pool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { BytesCalldata } from "src/types/BytesCalldata.sol"; 5 | 6 | type UniV3Pool is address; 7 | 8 | using { swap, flash } for UniV3Pool global; 9 | 10 | uint256 constant swapSelector = 0x128acb0800000000000000000000000000000000000000000000000000000000; 11 | uint256 constant flashSelector = 0x490e6cbc00000000000000000000000000000000000000000000000000000000; 12 | 13 | // ## Execute Uniswap V3 Swap 14 | // 15 | // ### Parameters 16 | // 17 | // - pool: The Uniswap V3 pool address. 18 | // - recipient: The receiver of the swap output. 19 | // - zeroForOne: Direction of the trade; "true": zero for one, "false": one for zero. 20 | // - amountSpecified: The "exact" portion of the trade amount (More in Notes). 21 | // - sqrtPriceLimitX96: The Q64.96 representation of the price limit. 22 | // - data: The arbitrary calldata for UniV3 callbacks, if any. 23 | // 24 | // ### Returns 25 | // 26 | // - success: returns True if the flash succeeded. 27 | // 28 | // ### Notes 29 | // 30 | // The `amountSpecified` parameter is positive if the input amount is the 31 | // "exact" amount parameter, but if it is negative, the output amount is the 32 | // "exact" amount parameter. 33 | // 34 | // ### Procedures 35 | // 36 | // 01. Load the free memory pointer. 37 | // 02. Load the `data` length as a 32 bit integer. 38 | // 03. Increment the `data` pointer to the beginning of the bytes. 39 | // 04. Store the `swapSelector`. 40 | // 05. Store the `recipient`. 41 | // 06. Store the `zeroForOne`. 42 | // 07. Store the `amountSpecified`. 43 | // 08. Store the `sqrtPriceLimitX96`. 44 | // 09. Store the `data` offset, relative to the slot after the selector. 45 | // 10. Store the `dataLen`. 46 | // 11. Copy the data from calldata to memory. 47 | // 12. Call the `pool` contract, returning `success` to the caller of this 48 | // function. 49 | function swap( 50 | UniV3Pool pool, 51 | address recipient, 52 | bool zeroForOne, 53 | int256 amountSpecified, 54 | uint160 sqrtPriceLimitX96, 55 | BytesCalldata data 56 | ) returns (bool success) { 57 | assembly ("memory-safe") { 58 | let fmp := mload(0x40) 59 | 60 | let dataLen := shr(0xe0, calldataload(data)) 61 | 62 | data := add(data, 0x04) 63 | 64 | mstore(add(fmp, 0x00), swapSelector) 65 | 66 | mstore(add(fmp, 0x04), recipient) 67 | 68 | mstore(add(fmp, 0x24), zeroForOne) 69 | 70 | mstore(add(fmp, 0x44), amountSpecified) 71 | 72 | mstore(add(fmp, 0x64), sqrtPriceLimitX96) 73 | 74 | mstore(add(fmp, 0x84), 0xa0) 75 | 76 | mstore(add(fmp, 0xa4), dataLen) 77 | 78 | calldatacopy(add(fmp, 0xc4), data, dataLen) 79 | 80 | success := call(gas(), pool, 0x00, fmp, add(dataLen, 0xe4), 0x00, 0x00) 81 | } 82 | } 83 | 84 | // ## Execute Uniswap V3 Flash Loan 85 | // 86 | // ### Parameters 87 | // 88 | // - pool: The Uniswap V3 pool address. 89 | // - recipient: The receiver of the flash output. 90 | // - amount0: The amount of Token 0 to flash. 91 | // - amount1: The amount of Token 1 to flash. 92 | // - data: The arbitrary calldata for UniV3 callbacks, if any. 93 | // 94 | // ### Returns 95 | // 96 | // - success: returns True if the flash succeeded. 97 | // 98 | // ### Notes 99 | // 100 | // The `amountSpecified` parameter is positive if the input amount is the 101 | // "exact" amount parameter, but if it is negative, the output amount is the 102 | // "exact" amount parameter. 103 | // 104 | // ### Procedures 105 | // 106 | // 01. Load the free memory pointer. 107 | // 02. Load the `data` length as a 32 bit integer. 108 | // 03. Increment the `data` pointer to the beginning of the bytes. 109 | // 04. Store the `flashSelector`. 110 | // 05. Store the `recipient`. 111 | // 06. Store the `amount0`. 112 | // 07. Store the `amount0`. 113 | // 08. Store the `data` offset, relative to the slot after the selector. 114 | // 09. Store the `dataLen`. 115 | // 10. Copy the data from calldata to memory. 116 | // 11. Call the `pool` contract, returning `success` to the caller of this 117 | // function. 118 | function flash( 119 | UniV3Pool pool, 120 | address recipient, 121 | uint256 amount0, 122 | uint256 amount1, 123 | BytesCalldata data 124 | ) returns (bool success) { 125 | assembly ("memory-safe") { 126 | let fmp := mload(0x40) 127 | 128 | let dataLen := shr(0xe0, calldataload(data)) 129 | 130 | data := add(data, 0x04) 131 | 132 | mstore(add(fmp, 0x00), flashSelector) 133 | 134 | mstore(add(fmp, 0x04), recipient) 135 | 136 | mstore(add(fmp, 0x24), amount0) 137 | 138 | mstore(add(fmp, 0x44), amount1) 139 | 140 | mstore(add(fmp, 0x64), 0x80) 141 | 142 | mstore(add(fmp, 0x84), dataLen) 143 | 144 | calldatacopy(add(fmp, 0xa4), data, dataLen) 145 | 146 | success := call(gas(), pool, 0x00, fmp, add(dataLen, 0xc4), 0x00, 0x00) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /test/mock/WETHMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | // weth9 (`0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2`) 5 | // bytecode on ethereum, compiled w `v0.4.19+commit.c4cbbb05` 6 | function wethBytecode() pure returns (bytes memory) { 7 | return 8 | hex"6060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029"; 9 | } 10 | 11 | contract WETHMock { 12 | event Deposit(address indexed account, uint256 value); 13 | event Withdrawal(address indexed account, uint256 value); 14 | 15 | bool internal _shouldThrow = false; 16 | 17 | function setShouldThrow( 18 | bool shouldThrow 19 | ) public { 20 | _shouldThrow = shouldThrow; 21 | } 22 | 23 | function withdraw( 24 | uint256 value 25 | ) public { 26 | require(!_shouldThrow); 27 | 28 | emit Withdrawal(msg.sender, value); 29 | } 30 | 31 | receive() external payable { 32 | require(!_shouldThrow); 33 | 34 | emit Deposit(msg.sender, msg.value); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.gas-snapshot: -------------------------------------------------------------------------------- 1 | BBCDecoderTest:testDecodWithdrawWETH() (gas: 23582) 2 | BBCDecoderTest:testDecodeDepositWETH() (gas: 23978) 3 | BBCDecoderTest:testDecodeFlashUniV3() (gas: 39945) 4 | BBCDecoderTest:testDecodeSwapUniV2() (gas: 39486) 5 | BBCDecoderTest:testDecodeSwapUniV3() (gas: 48282) 6 | BBCDecoderTest:testDecodeSwapUniV3Negative() (gas: 48759) 7 | BBCDecoderTest:testDecodeTransferERC20() (gas: 28910) 8 | BBCDecoderTest:testDecodeTransferERC6909() (gas: 38135) 9 | BBCDecoderTest:testDecodeTransferFromERC20() (gas: 33885) 10 | BBCDecoderTest:testDecodeTransferFromERC6909() (gas: 42760) 11 | BBCDecoderTest:testDecodeTransferFromERC721() (gas: 34369) 12 | BBCDecoderTest:testFuzzDecodeDepositWETH(bool,address,uint8) (runs: 264, μ: 21365, ~: 19638) 13 | BBCDecoderTest:testFuzzDecodeFlashUniV3(bool,address,address,uint256,uint256,bytes) (runs: 264, μ: 32923, ~: 31096) 14 | BBCDecoderTest:testFuzzDecodeSwapUniV3(bool,address,address,bool,int256,uint160,bytes) (runs: 263, μ: 37681, ~: 36174) 15 | BBCDecoderTest:testFuzzDecodeSwapUniv2(bool,address,uint8,uint8,address,bytes) (runs: 264, μ: 35699, ~: 32022) 16 | BBCDecoderTest:testFuzzDecodeTransferERC6909(bool,address,address,uint256,uint256) (runs: 264, μ: 27284, ~: 24572) 17 | BBCDecoderTest:testFuzzDecodeTransferFromERC20(bool,address,address,address,uint8) (runs: 264, μ: 27094, ~: 21956) 18 | BBCDecoderTest:testFuzzDecodeTransferFromERC6909(bool,address,address,address,uint256,uint256) (runs: 264, μ: 31458, ~: 27400) 19 | BBCDecoderTest:testFuzzDecodeTransferFromERC721(bool,address,address,address,uint8) (runs: 264, μ: 27402, ~: 22264) 20 | BBCDecoderTest:testFuzzDecodeWithdrawWETH(bool,address,uint8) (runs: 264, μ: 21519, ~: 19792) 21 | LotusRouterTest:testDepositWETHFromBalance() (gas: 33257) 22 | LotusRouterTest:testDepositWETHFromCaller() (gas: 40737) 23 | LotusRouterTest:testDepositWETHNothing() (gas: 28048) 24 | LotusRouterTest:testDepositWETHThrows() (gas: 46395) 25 | LotusRouterTest:testFlashUniV3Recurse() (gas: 99333) 26 | LotusRouterTest:testFlashUniV3RecurseThrows() (gas: 86760) 27 | LotusRouterTest:testFlashUniV3Single() (gas: 42794) 28 | LotusRouterTest:testFlashUniV3SingleThrows() (gas: 63014) 29 | LotusRouterTest:testFuzzDepositWETH(bool,bool,bool,uint256) (runs: 264, μ: 46560, ~: 45928) 30 | LotusRouterTest:testFuzzFlashUniV3Recurse(bool,bool,address,uint256,uint256,bytes) (runs: 264, μ: 79880, ~: 79679) 31 | LotusRouterTest:testFuzzFlashUniV3Single(bool,bool,address,uint256,uint256,bytes) (runs: 264, μ: 46351, ~: 45319) 32 | LotusRouterTest:testFuzzSwapUniV2Chain(uint256,uint256,bytes,uint256,uint256,bytes) (runs: 264, μ: 64188, ~: 64086) 33 | LotusRouterTest:testFuzzSwapUniV2Single(bool,uint256,uint256,address,bytes) (runs: 264, μ: 36168, ~: 35814) 34 | LotusRouterTest:testFuzzSwapUniV3Recurse(bool,bool,address,bool,int256,uint160,bytes) (runs: 263, μ: 95625, ~: 96840) 35 | LotusRouterTest:testFuzzSwapUniV3Single(bool,bool,address,bool,int256,uint160,bytes) (runs: 263, μ: 53943, ~: 54184) 36 | LotusRouterTest:testFuzzTransferERC20Chain(bool,bool,bool,bool,address,uint256) (runs: 264, μ: 52118, ~: 51599) 37 | LotusRouterTest:testFuzzTransferERC20Single(bool,bool,bool,bool,address,uint256) (runs: 264, μ: 32777, ~: 32694) 38 | LotusRouterTest:testFuzzTransferERC6909Chain(bool,bool,bool,address,uint256,uint256,address,uint256,uint256) (runs: 264, μ: 63458, ~: 59011) 39 | LotusRouterTest:testFuzzTransferERC6909Single(bool,bool,bool,address,uint256,uint256) (runs: 264, μ: 36494, ~: 36069) 40 | LotusRouterTest:testFuzzTransferFromERC20Chain(bool,bool,bool,bool,address,address,uint256) (runs: 264, μ: 59324, ~: 56872) 41 | LotusRouterTest:testFuzzTransferFromERC20Single(bool,bool,bool,bool,address,address,uint256) (runs: 264, μ: 36242, ~: 34404) 42 | LotusRouterTest:testFuzzTransferFromERC6909Chain(bool,bool,bool,address,address,uint256,uint256,address,address,uint256,uint256) (runs: 264, μ: 67528, ~: 61789) 43 | LotusRouterTest:testFuzzTransferFromERC6909Single(bool,bool,bool,address,address,uint256,uint256) (runs: 264, μ: 39626, ~: 37664) 44 | LotusRouterTest:testFuzzTransferFromERC721Chain(bool,bool,address,address,uint256) (runs: 264, μ: 59539, ~: 56564) 45 | LotusRouterTest:testFuzzTransferFromERC721Single(bool,bool,address,address,uint256) (runs: 264, μ: 40899, ~: 40992) 46 | LotusRouterTest:testFuzzWithdrawWETH(bool,bool,uint256) (runs: 264, μ: 36109, ~: 30015) 47 | LotusRouterTest:testSwapUniV2Chain() (gas: 77975) 48 | LotusRouterTest:testSwapUniV2ChainThrows() (gas: 88274) 49 | LotusRouterTest:testSwapUniV2Recurse() (gas: 60462) 50 | LotusRouterTest:testSwapUniV2Single() (gas: 44824) 51 | LotusRouterTest:testSwapUniV2SingleThrows() (gas: 62183) 52 | LotusRouterTest:testSwapUniV3NegativeSingle() (gas: 52585) 53 | LotusRouterTest:testSwapUniV3Recurse() (gas: 121327) 54 | LotusRouterTest:testSwapUniV3RecurseFirstThrows() (gas: 103243) 55 | LotusRouterTest:testSwapUniV3RecurseSecondThrows() (gas: 136595) 56 | LotusRouterTest:testSwapUniV3Single() (gas: 53219) 57 | LotusRouterTest:testSwapUniV3ThrowsSingle() (gas: 68865) 58 | LotusRouterTest:testTransferERC20Chain() (gas: 58734) 59 | LotusRouterTest:testTransferERC20ChainFirstThrows() (gas: 51175) 60 | LotusRouterTest:testTransferERC20ChainReturnsNothing() (gas: 66112) 61 | LotusRouterTest:testTransferERC20ChainSecondThrows() (gas: 58932) 62 | LotusRouterTest:testTransferERC20Single() (gas: 34028) 63 | LotusRouterTest:testTransferERC20SingleReturnsFalse() (gas: 36244) 64 | LotusRouterTest:testTransferERC20SingleReturnsNothing() (gas: 37376) 65 | LotusRouterTest:testTransferERC20SingleThrows() (gas: 35459) 66 | LotusRouterTest:testTransferERC6909Chain() (gas: 75728) 67 | LotusRouterTest:testTransferERC6909ChainReturnsFalse() (gas: 64454) 68 | LotusRouterTest:testTransferERC6909ChainThrows() (gas: 67565) 69 | LotusRouterTest:testTransferERC6909Single() (gas: 42704) 70 | LotusRouterTest:testTransferERC6909SingleReturnsFalse() (gas: 41012) 71 | LotusRouterTest:testTransferERC6909SingleThrows() (gas: 43441) 72 | LotusRouterTest:testTransferFromERC20Chain() (gas: 68228) 73 | LotusRouterTest:testTransferFromERC20ChainFirstThrows() (gas: 60955) 74 | LotusRouterTest:testTransferFromERC20ChainReturnsNothing() (gas: 76046) 75 | LotusRouterTest:testTransferFromERC20ChainSecondThrows() (gas: 67940) 76 | LotusRouterTest:testTransferFromERC20Single() (gas: 38112) 77 | LotusRouterTest:testTransferFromERC20SingleReturnsFalse() (gas: 41693) 78 | LotusRouterTest:testTransferFromERC20SingleReturnsNothing() (gas: 42340) 79 | LotusRouterTest:testTransferFromERC20SingleThrows() (gas: 39496) 80 | LotusRouterTest:testTransferFromERC6909Chain() (gas: 85396) 81 | LotusRouterTest:testTransferFromERC6909ChainReturnsFalse() (gas: 73633) 82 | LotusRouterTest:testTransferFromERC6909ChainThrows() (gas: 77884) 83 | LotusRouterTest:testTransferFromERC6909Single() (gas: 47883) 84 | LotusRouterTest:testTransferFromERC6909SingleReturnsFalse() (gas: 47049) 85 | LotusRouterTest:testTransferFromERC6909SingleThrows() (gas: 48242) 86 | LotusRouterTest:testTransferFromERC721Chain() (gas: 66578) 87 | LotusRouterTest:testTransferFromERC721ChainFirstThrows() (gas: 78701) 88 | LotusRouterTest:testTransferFromERC721ChainSecondThrows() (gas: 85587) 89 | LotusRouterTest:testTransferFromERC721Single() (gas: 37331) 90 | LotusRouterTest:testTransferFromERC721SingleThrows() (gas: 56208) 91 | LotusRouterTest:testWETHSendIsCheaperThanDeposit() (gas: 75426) 92 | LotusRouterTest:testWithdrawWETH() (gas: 27356) 93 | LotusRouterTest:testWithdrawWETHTHrows() (gas: 47439) -------------------------------------------------------------------------------- /test/mock/BBCDecoderMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { BytesCalldata } from "src/types/BytesCalldata.sol"; 5 | import { Ptr } from "src/types/PayloadPointer.sol"; 6 | import { ERC20 } from "src/types/protocols/ERC20.sol"; 7 | import { ERC6909 } from "src/types/protocols/ERC6909.sol"; 8 | import { ERC721 } from "src/types/protocols/ERC721.sol"; 9 | import { UniV2Pair } from "src/types/protocols/UniV2Pair.sol"; 10 | import { UniV3Pool } from "src/types/protocols/UniV3Pool.sol"; 11 | import { WETH } from "src/types/protocols/WETH.sol"; 12 | import { BBCDecoder } from "src/util/BBCDecoder.sol"; 13 | 14 | contract BBCDecoderMock { 15 | using BBCDecoder for Ptr; 16 | 17 | function decodeSwapUniV2( 18 | bytes calldata encoded 19 | ) 20 | public 21 | pure 22 | returns ( 23 | bool canFail, 24 | UniV2Pair pair, 25 | uint256 amount0Out, 26 | uint256 amount1Out, 27 | address to, 28 | bytes memory data 29 | ) 30 | { 31 | Ptr ptr; 32 | BytesCalldata packedData; 33 | 34 | // add 0x01 bc the first byte is the `Action` opcode, it's not decoded 35 | assembly { 36 | ptr := add(0x01, encoded.offset) 37 | } 38 | 39 | (, canFail, pair, amount0Out, amount1Out, to, packedData) = ptr.decodeSwapUniV2(); 40 | 41 | assembly { 42 | let fmp := mload(0x40) 43 | 44 | data := fmp 45 | 46 | let len := shr(0xe0, calldataload(packedData)) 47 | 48 | mstore(fmp, len) 49 | 50 | fmp := add(fmp, 0x20) 51 | 52 | calldatacopy(fmp, add(packedData, 0x04), len) 53 | 54 | fmp := add(fmp, len) 55 | 56 | mstore(0x40, fmp) 57 | } 58 | } 59 | 60 | function decodeSwapUniV3( 61 | bytes calldata encoded 62 | ) 63 | public 64 | pure 65 | returns ( 66 | bool canFail, 67 | UniV3Pool pool, 68 | address recipient, 69 | bool zeroForOne, 70 | int256 amountSpecified, 71 | uint160 sqrtPriceLimitX96, 72 | bytes memory data 73 | ) 74 | { 75 | Ptr ptr; 76 | BytesCalldata packedData; 77 | 78 | // add 0x01 bc the first byte is the `Action` opcode, it's not decoded 79 | assembly { 80 | ptr := add(0x01, encoded.offset) 81 | } 82 | 83 | (, canFail, pool, recipient, zeroForOne, amountSpecified, sqrtPriceLimitX96, packedData) = 84 | ptr.decodeSwapUniV3(); 85 | 86 | assembly { 87 | let fmp := mload(0x40) 88 | 89 | data := fmp 90 | 91 | let len := shr(0xe0, calldataload(packedData)) 92 | 93 | mstore(fmp, len) 94 | 95 | fmp := add(fmp, 0x20) 96 | 97 | calldatacopy(fmp, add(packedData, 0x04), len) 98 | 99 | fmp := add(fmp, len) 100 | 101 | mstore(0x40, fmp) 102 | } 103 | } 104 | 105 | function decodeFlashUniV3( 106 | bytes calldata encoded 107 | ) 108 | public 109 | pure 110 | returns ( 111 | bool canFail, 112 | UniV3Pool pool, 113 | address recipient, 114 | uint256 amount0, 115 | uint256 amount1, 116 | bytes memory data 117 | ) 118 | { 119 | Ptr ptr; 120 | BytesCalldata packedData; 121 | 122 | // add 0x01 bc the first byte is the `Action` opcode, it's not decoded 123 | assembly { 124 | ptr := add(0x01, encoded.offset) 125 | } 126 | 127 | (, canFail, pool, recipient, amount0, amount1, packedData) = ptr.decodeFlashUniV3(); 128 | 129 | assembly { 130 | let fmp := mload(0x40) 131 | 132 | data := fmp 133 | 134 | let len := shr(0xe0, calldataload(packedData)) 135 | 136 | mstore(fmp, len) 137 | 138 | fmp := add(fmp, 0x20) 139 | 140 | calldatacopy(fmp, add(packedData, 0x04), len) 141 | 142 | fmp := add(fmp, len) 143 | 144 | mstore(0x40, fmp) 145 | } 146 | } 147 | 148 | function decodeTransferERC20( 149 | bytes calldata encoded 150 | ) public pure returns (bool canFail, ERC20 token, address receiver, uint256 amount) { 151 | Ptr ptr; 152 | 153 | // add 0x01 bc the first byte is the `Action` opcode, it's not decoded 154 | assembly { 155 | ptr := add(0x01, encoded.offset) 156 | } 157 | 158 | (, canFail, token, receiver, amount) = ptr.decodeTransferERC20(); 159 | } 160 | 161 | function decodeTransferFromERC20( 162 | bytes calldata encoded 163 | ) 164 | public 165 | pure 166 | returns (bool canFail, ERC20 token, address sender, address receiver, uint256 amount) 167 | { 168 | Ptr ptr; 169 | 170 | // add 0x01 bc the first byte is the `Action` opcode, it's not decoded 171 | assembly { 172 | ptr := add(0x01, encoded.offset) 173 | } 174 | 175 | (, canFail, token, sender, receiver, amount) = ptr.decodeTransferFromERC20(); 176 | } 177 | 178 | function decodeTransferFromERC721( 179 | bytes calldata encoded 180 | ) 181 | public 182 | pure 183 | returns (bool canFail, ERC721 token, address sender, address receiver, uint256 tokenId) 184 | { 185 | Ptr ptr; 186 | 187 | // add 0x01 bc the first byte is the `Action` opcode, it's not decoded 188 | assembly { 189 | ptr := add(0x01, encoded.offset) 190 | } 191 | 192 | (, canFail, token, sender, receiver, tokenId) = ptr.decodeTransferFromERC721(); 193 | } 194 | 195 | function decodeTransferERC6909( 196 | bytes calldata encoded 197 | ) 198 | public 199 | pure 200 | returns ( 201 | bool canFail, 202 | ERC6909 multitoken, 203 | address receiver, 204 | uint256 tokenId, 205 | uint256 amount 206 | ) 207 | { 208 | Ptr ptr; 209 | 210 | // add 0x01 bc the first byte is the `Action` opcode, it's not decoded 211 | assembly { 212 | ptr := add(0x01, encoded.offset) 213 | } 214 | 215 | (, canFail, multitoken, receiver, tokenId, amount) = ptr.decodeTransferERC6909(); 216 | } 217 | 218 | function decodeTransferFromERC6909( 219 | bytes calldata encoded 220 | ) 221 | public 222 | pure 223 | returns ( 224 | bool canFail, 225 | ERC6909 multitoken, 226 | address sender, 227 | address receiver, 228 | uint256 tokenId, 229 | uint256 amount 230 | ) 231 | { 232 | Ptr ptr; 233 | 234 | // add 0x01 bc the first byte is the `Action` opcode, it's not decoded 235 | assembly { 236 | ptr := add(0x01, encoded.offset) 237 | } 238 | 239 | (, canFail, multitoken, sender, receiver, tokenId, amount) = ptr.decodeTransferFromERC6909(); 240 | } 241 | 242 | function decodeDepositWETH( 243 | bytes calldata encoded 244 | ) public pure returns (bool canFail, WETH weth, uint256 value) { 245 | Ptr ptr; 246 | 247 | // add 0x01 bc the first byte is the `Action` opcode, it's not decoded 248 | assembly { 249 | ptr := add(0x01, encoded.offset) 250 | } 251 | 252 | (, canFail, weth, value) = ptr.decodeDepositWETH(); 253 | } 254 | 255 | function decodeWithdrawWETH( 256 | bytes calldata encoded 257 | ) public pure returns (bool canFail, WETH weth, uint256 value) { 258 | Ptr ptr; 259 | 260 | // add 0x01 bc the first byte is the `Action` opcode, it's not decoded 261 | assembly { 262 | ptr := add(0x01, encoded.offset) 263 | } 264 | 265 | (, canFail, weth, value) = ptr.decodeWithdrawWETH(); 266 | } 267 | 268 | function decodeDynCall(bytes calldata encoded) 269 | public pure returns (bool canFail, address target, uint256 value, bytes memory data) { 270 | Ptr ptr; 271 | BytesCalldata packedData; 272 | 273 | assembly { 274 | ptr := add(0x01, encoded.offset) 275 | } 276 | 277 | (, canFail, target, value, packedData) = ptr.decodeDynCall(); 278 | 279 | assembly { 280 | let fmp := mload(0x40) 281 | 282 | data := fmp 283 | 284 | let len := shr(0xe0, calldataload(packedData)) 285 | 286 | mstore(fmp, len) 287 | 288 | fmp := add(fmp, 0x20) 289 | 290 | calldatacopy(fmp, add(packedData, 0x04), len) 291 | 292 | fmp := add(fmp, len) 293 | 294 | mstore(0x40, fmp) 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/LotusRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { Action } from "src/types/Action.sol"; 5 | import { BytesCalldata } from "src/types/BytesCalldata.sol"; 6 | 7 | import { Error } from "src/types/Error.sol"; 8 | import { Ptr, findPtr } from "src/types/PayloadPointer.sol"; 9 | import { ERC20 } from "src/types/protocols/ERC20.sol"; 10 | import { ERC6909 } from "src/types/protocols/ERC6909.sol"; 11 | import { ERC721 } from "src/types/protocols/ERC721.sol"; 12 | import { UniV2Pair } from "src/types/protocols/UniV2Pair.sol"; 13 | import { UniV3Pool } from "src/types/protocols/UniV3Pool.sol"; 14 | import { WETH } from "src/types/protocols/WETH.sol"; 15 | import { dynCall } from "src/types/protocols/Dyn.sol"; 16 | import { BBCDecoder } from "src/util/BBCDecoder.sol"; 17 | 18 | // +---------------------------------------------------------------------------+ 19 | // | ## The Lotus Router Manifesto | 20 | // | | 21 | // | I am the Lotus Router. | 22 | // | | 23 | // | I exist for the individual. | 24 | // | I exist for the collective. | 25 | // | I exist for the developers. | 26 | // | I exist for the users. | 27 | // | | 28 | // | I exist, above all else, to empower. | 29 | // | | 30 | // | I do not to extract value. | 31 | // | I do not to capture rent. | 32 | // | I am a political statement, as all software is. | 33 | // | | 34 | // | I subscribe to no -ism. | 35 | // | I wave no banner. | 36 | // | I am an act of defiance against hoarders of technology and capital. | 37 | // | | 38 | // | I bear the license of free, as in cost AND freedom, software. | 39 | // | I am free for distribution. | 40 | // | I am free for study. | 41 | // | I am free for modification. | 42 | // | I am free for redistribution. | 43 | // | | 44 | // | I ask only that redistributions of me bear the same license. | 45 | // | | 46 | // | ___ | 47 | // | ___ / \ ___ | 48 | // | / \/ | \/ \ | 49 | // | / / \ ___ / \ \ | 50 | // | \ \ / \ / / | 51 | // | ,-----,/ \,-----, | 52 | // | \ \ | / / | 53 | // | \ \ \ | / / / | 54 | // | __-\_\____\ | /____/_/-__ | 55 | // | '--___ '-' ___--' | 56 | // | '----_____----' | 57 | // +---------------------------------------------------------------------------+ 58 | 59 | /// @title Lotus Router 60 | /// @author Nameless Researchers and Developers of Ethereum 61 | contract LotusRouter { 62 | // ## Fallback Function 63 | // 64 | // This contains all of the Lotus Router's execution logic. 65 | // 66 | // We use the fallback function to eschew Solidity's ABI encoding scheme. 67 | // Documentation is be provided for interfacing with this safely. 68 | fallback() external payable { 69 | Ptr ptr = findPtr(); 70 | Action action; 71 | bool success = true; 72 | 73 | while (success) { 74 | (ptr, action) = ptr.nextAction(); 75 | 76 | if (action == Action.Halt) { 77 | assembly { 78 | stop() 79 | } 80 | } else if (action == Action.SwapUniV2) { 81 | bool canFail; 82 | UniV2Pair pair; 83 | uint256 amount0Out; 84 | uint256 amount1Out; 85 | address to; 86 | BytesCalldata data; 87 | 88 | (ptr, canFail, pair, amount0Out, amount1Out, to, data) = 89 | BBCDecoder.decodeSwapUniV2(ptr); 90 | 91 | success = pair.swap(amount0Out, amount1Out, to, data) || canFail; 92 | } else if (action == Action.SwapUniV3) { 93 | bool canFail; 94 | UniV3Pool pool; 95 | address recipient; 96 | bool zeroForOne; 97 | int256 amountSpecified; 98 | uint160 sqrtPriceLimitX96; 99 | BytesCalldata data; 100 | 101 | ( 102 | ptr, 103 | canFail, 104 | pool, 105 | recipient, 106 | zeroForOne, 107 | amountSpecified, 108 | sqrtPriceLimitX96, 109 | data 110 | ) = BBCDecoder.decodeSwapUniV3(ptr); 111 | 112 | success = pool.swap(recipient, zeroForOne, amountSpecified, sqrtPriceLimitX96, data) 113 | || canFail; 114 | } else if (action == Action.FlashUniV3) { 115 | bool canFail; 116 | UniV3Pool pool; 117 | address recipient; 118 | uint256 amount0; 119 | uint256 amount1; 120 | BytesCalldata data; 121 | 122 | (ptr, canFail, pool, recipient, amount0, amount1, data) = 123 | BBCDecoder.decodeFlashUniV3(ptr); 124 | 125 | success = pool.flash(recipient, amount0, amount1, data) || canFail; 126 | } else if (action == Action.TransferERC20) { 127 | bool canFail; 128 | ERC20 token; 129 | address receiver; 130 | uint256 amount; 131 | 132 | (ptr, canFail, token, receiver, amount) = BBCDecoder.decodeTransferERC20(ptr); 133 | 134 | success = token.transfer(receiver, amount) || canFail; 135 | } else if (action == Action.TransferFromERC20) { 136 | bool canFail; 137 | ERC20 token; 138 | address sender; 139 | address receiver; 140 | uint256 amount; 141 | 142 | (ptr, canFail, token, sender, receiver, amount) = 143 | BBCDecoder.decodeTransferFromERC20(ptr); 144 | 145 | success = token.transferFrom(sender, receiver, amount) || canFail; 146 | } else if (action == Action.TransferFromERC721) { 147 | bool canFail; 148 | ERC721 token; 149 | address sender; 150 | address receiver; 151 | uint256 amount; 152 | 153 | (ptr, canFail, token, sender, receiver, amount) = 154 | BBCDecoder.decodeTransferFromERC721(ptr); 155 | 156 | success = token.transferFrom(sender, receiver, amount) || canFail; 157 | } else if (action == Action.TransferERC6909) { 158 | bool canFail; 159 | ERC6909 multitoken; 160 | address receiver; 161 | uint256 tokenId; 162 | uint256 amount; 163 | 164 | (ptr, canFail, multitoken, receiver, tokenId, amount) = 165 | BBCDecoder.decodeTransferERC6909(ptr); 166 | 167 | success = multitoken.transfer(receiver, tokenId, amount) || canFail; 168 | } else if (action == Action.TransferFromERC6909) { 169 | bool canFail; 170 | ERC6909 multitoken; 171 | address sender; 172 | address receiver; 173 | uint256 tokenId; 174 | uint256 amount; 175 | 176 | (ptr, canFail, multitoken, sender, receiver, tokenId, amount) = 177 | BBCDecoder.decodeTransferFromERC6909(ptr); 178 | 179 | success = multitoken.transferFrom(sender, receiver, tokenId, amount) || canFail; 180 | } else if (action == Action.DepositWETH) { 181 | bool canFail; 182 | WETH weth; 183 | uint256 value; 184 | 185 | (ptr, canFail, weth, value) = BBCDecoder.decodeDepositWETH(ptr); 186 | 187 | success = weth.deposit(value) || canFail; 188 | } else if (action == Action.WithdrawWETH) { 189 | bool canFail; 190 | WETH weth; 191 | uint256 value; 192 | 193 | (ptr, canFail, weth, value) = BBCDecoder.decodeWithdrawWETH(ptr); 194 | 195 | success = weth.withdraw(value) || canFail; 196 | } else if (action == Action.DynCall) { 197 | bool canFail; 198 | address target; 199 | uint256 value; 200 | BytesCalldata data; 201 | 202 | (ptr, canFail, target, value, data) = BBCDecoder.decodeDynCall(ptr); 203 | 204 | success = dynCall(target, value, data) || canFail; 205 | } else { 206 | success = false; 207 | } 208 | } 209 | 210 | revert Error.CallFailure(); 211 | } 212 | 213 | // ## Receiver Function 214 | // 215 | // This triggers when this contract is called with no calldata. It takes no 216 | // action, it only returns gracefully. 217 | receive() external payable { } 218 | } 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lotus Router 2 | 3 | The Lotus Router is an embedded virtual machine which treats instructions as 4 | DeFi protocol interactions, primarily automated market marker protocols. It does 5 | not take fees, it does not extract rent, it is not upgradeable, it is 6 | permissionless, it is free and open source software. It bears the AGPL-3.0 7 | copy-left license. 8 | 9 | Built with experience from the frontier, with solidarity for developers of 10 | sovereignity, and with a love for democratization of knowledge and software. 11 | 12 | > Work In Progress, Do Not Use Yet 13 | 14 | ## Why? 15 | 16 | Searchers and Solvers alike employ people like us to repeatedly build the state 17 | of the art in router technology. 18 | 19 | Searchers and Solvers alike justify secrecy with "alpha decay" and other pseudo- 20 | academic terminology in order to hoard the cutting edge and the capital which 21 | comes with it. 22 | 23 |
24 | 25 | We grow tired of building the same software again and again. 26 | 27 | We grow tired of signing NDA after NDA. 28 | 29 | We grow tired of repeating ourselves. 30 | 31 |
32 | 33 | So we the Researchers and Developers write this software with the intent to 34 | democratize the cutting edge of router technology. 35 | 36 | So we the Researchers and Developers write this software with the intent to 37 | liberate the secrets of a parasitic industry. 38 | 39 | So we the Researchers and Developers write this software with the intent to 40 | expose the elegant simplicity which hides behind bytecode obfuscators and the 41 | mysticism of our local elites. 42 | 43 | ## Disclaimer 44 | 45 | Multiple organizations may contend that this technology was stolen, or that it 46 | is the subject of trade secrets. 47 | 48 | However, we the Researchers and Developers formally declare this software is 49 | developed explicitly on our own time, on our own hardware, with our own 50 | software, and with our own knowledge accumulated from both educational resources 51 | and through our understanding and interpretation of the bytecode which exists on 52 | the public blockchains. 53 | 54 | ## Implementation Details 55 | 56 | Batchable actions: 57 | 58 | - [x] Uniswap V2 Swap 59 | - [x] Uniswap V3 Swap 60 | - [x] Uniswap V3 Flash 61 | - [x] ERC20 Transfer 62 | - [x] ERC20 TransferFrom 63 | - [x] ERC721 TransferFrom 64 | - [x] ERC6909 Transfer 65 | - [x] ERC6909 TransferFrom 66 | - [x] Wrap WETH 67 | - [x] Unwrap WETH 68 | - [x] Dynamic Contract Call 69 | 70 | Other features: 71 | 72 | - [x] Unconventional Encoder/Decoder (inspired by bigbrainchad.eth) 73 | - [ ] Transient storage call stack constraints (inspired by bigbrainchad.eth) 74 | - [x] Virtual Machine Style Architecture (inspired by, yes, bigbrainchad.eth) 75 | 76 | ### Call Diagrams 77 | 78 | #### Uniswap V2 Chaining 79 | 80 | Chaining Uniswap V2 markets entails iteratively calling pairs, forwarding the 81 | output of one swap into the next pair. 82 | 83 | - `Lotus` transfers `TokenA` to `MarketAB` 84 | - `Lotus` calls `swap` on `MarketAB` 85 | - `MarketAB` swaps and transfers `TokenB` to `MarketBC` 86 | - `Lotus` calls `swap` on `MarketBC` 87 | - `MarketBC` swaps and transfers `TokenC` to `Lotus` 88 | 89 | ```mermaid 90 | sequenceDiagram 91 | Lotus-->>MarketAB: transfer A 92 | Lotus->>+MarketAB: swap(A, B) 93 | MarketAB-->>MarketBC: transfer B 94 | MarketAB->>-Lotus: return 95 | Lotus->>+MarketBC: swap(B, C) 96 | MarketBC-->>Lotus: transfer C 97 | MarketBC->>-Lotus: return 98 | ``` 99 | 100 | #### Uniswap V3 Chaining 101 | 102 | Chaining Uniswap V3 markets entails recursively calling pools, settling each 103 | market in its respective callback to the router. 104 | 105 | While it is possible to simplify encoding control flow by calling iteratively, 106 | recursion saves `O(n)` calls. 107 | 108 | - `Lotus` calls `swap` on `MarketBC` 109 | - `MarketBC` transfers `TokenC` to `Lotus` 110 | - `MarketBC` calls back into `Lotus` with `uniswapV3Callback` 111 | - `Lotus` calls `swap` on `MarketAB` 112 | - `MarketAB` transfers `TokenB` to `Lotus` 113 | - `MarketAB` calls back into `Lotus` with `uniswapV3Callback` 114 | - `Lotus` transfers `TokenA` to `MarketAB`, settling the balances 115 | - `Lotus` transfers `TokenB` to `MarketBC`, settling the balances 116 | 117 | ```mermaid 118 | sequenceDiagram 119 | Lotus->>+MarketBC: swap(B, C) 120 | MarketBC-->>Lotus: transfer C 121 | MarketBC->>+Lotus: uniswapV3SwapCallback 122 | Lotus->>+MarketAB: swap(A, B) 123 | MarketAB-->>Lotus: transfer B 124 | MarketAB->>+Lotus: uniswapV3SwapCallback 125 | Lotus-->>MarketAB: transfer A 126 | Lotus-->>MarketBC: transfer B 127 | Lotus->>-MarketAB: return 128 | MarketAB->>-Lotus: return 129 | Lotus->>-MarketBC: return 130 | MarketBC->>-Lotus: return 131 | ``` 132 | 133 | A broken out, more intuitive diagram breaks the `Lotus` router out into its 134 | three independent call contexts. 135 | 136 | ```mermaid 137 | sequenceDiagram 138 | Lotus->>+MarketBC: swap(B, C) 139 | MarketBC-->>Lotus(1): transfer C 140 | MarketBC->>+Lotus(1): uniswapV3SwapCallback 141 | Lotus(1)->>+MarketAB: swap(A, B) 142 | MarketAB-->>Lotus(2): transfer B 143 | MarketAB->>+Lotus(2): uniswapV3SwapCallback 144 | Lotus(2)-->>MarketAB: transfer A 145 | Lotus(2)-->>MarketBC: transfer B 146 | Lotus(2)->>-MarketAB: return 147 | MarketAB->>-Lotus(1): return 148 | Lotus(1)->>-MarketBC: return 149 | MarketBC->>-Lotus: return 150 | ``` 151 | 152 | #### Uniswap V3 Flash 153 | 154 | - `Lotus` calls `flash` on `MarketAB` 155 | - `MarketAB` transfers `TokenA` to `Lotus`, if any was requested 156 | - `MarketAB` transfers `TokenB` to `Lotus`, if any was requested 157 | - `MarketAB` back into `Lotus` with `uniswapV3FlashCallback` 158 | - `Lotus` transfers `TokenA` to `MarketAB`, if any was taken 159 | - `Lotus` transfers `TokenB` to `MarketAB`, if any was taken 160 | 161 | ```mermaid 162 | sequenceDiagram 163 | Lotus->>+MarketAB: flash 164 | MarketAB-->>Lotus: transfer A 165 | MarketAB-->>Lotus: transfer B 166 | MarketAB->>+Lotus: uniswapV3FlashCallback 167 | Lotus-->>MarketAB: transfer A 168 | Lotus-->>MarketAB: transfer B 169 | Lotus->>-MarketAB: return 170 | MarketAB->>-Lotus: return 171 | ``` 172 | 173 | A broken out, more intuitive diagram breaks the `Lotus` router out into its 174 | two independent call contexts. 175 | 176 | ```mermaid 177 | sequenceDiagram 178 | Lotus->>+MarketAB: flash 179 | MarketAB-->>Lotus(1): transfer A 180 | MarketAB-->>Lotus(1): transfer B 181 | MarketAB->>+Lotus(1): uniswapV3FlashCallback 182 | Lotus(1)-->>MarketAB: transfer A 183 | Lotus(1)-->>MarketAB: transfer B 184 | Lotus(1)->>-MarketAB: return 185 | MarketAB->>-Lotus: return 186 | ``` 187 | 188 | ### Encoding Scheme 189 | 190 | The encoding scheme reduces all statically sized values to their smallest size, 191 | in bytes, and prefixes them with an 8 bit integer indicating the truncated byte 192 | length. All dynamically sized values are prefixed with only a 32 bit length of 193 | the data. There are no offsets in this encoding scheme, so objects are parsed 194 | in order with a pointer incrementing continuously while parsing. 195 | 196 | ```ebnf 197 | ::= 198 | | ("0x00") 199 | | ("0x01" . ) 200 | | ("0x02" . ) 201 | | ("0x03" . ) 202 | | ("0x04" . ) 203 | | ("0x05" . ) 204 | | ("0x06" . ) 205 | | ("0x07" . ) 206 | | ("0x08" . ) 207 | | ("0x09" . ) 208 | | ("0x0a" . ) 209 | | ("0x0b" . ) ; 210 | 211 | ::= 212 | . 213 | . 214 | . 215 | . 216 | . 217 | . 218 | . 219 | . 220 | . 221 | . 222 | . ; 223 | 224 | ::= 225 | . 226 | . 227 | . 228 | . 229 | . 230 | . 231 | . 232 | . 233 | . 234 | . 235 | . 236 | . ; 237 | 238 | ::= 239 | . 240 | . 241 | . 242 | . 243 | . 244 | . 245 | . 246 | . 247 | . 248 | . 249 | . 250 | . ; 251 | 252 | ::= 253 | . 254 | . 255 | . 256 | . 257 | . 258 | . 259 | . 260 | . ; 261 | 262 | ::= 263 | . 264 | . 265 | . 266 | . 267 | . 268 | . 269 | . 270 | . 271 | . 272 | . ; 273 | 274 | ::= 275 | . 276 | . 277 | . 278 | . 279 | . 280 | . 281 | . 282 | . 283 | . 284 | . ; 285 | 286 | ::= 287 | . 288 | . 289 | . 290 | . 291 | . 292 | . 293 | . 294 | . 295 | . 296 | . ; 297 | 298 | ::= 299 | . 300 | . 301 | . 302 | . 303 | . 304 | . 305 | . 306 | . 307 | . 308 | . 309 | . 310 | . ; 311 | 312 | ::= 313 | . 314 | . 315 | . 316 | . 317 | . ; 318 | 319 | ::= 320 | . 321 | . 322 | . 323 | . 324 | . ; 325 | 326 | ::= 327 | . 328 | . 329 | . 330 | . 331 | . 332 | . 333 | . ; 334 | ``` 335 | 336 | As an example, the following is a representation of making a Uniswap V2 Swap 337 | call with some arbitrary data appended to the end. 338 | 339 | ```solidity 340 | bool canFail = false; 341 | address pair = address(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc); 342 | uint256 amount0Out = 1 ether; 343 | uint256 amount1Out = 0; 344 | address to = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); 345 | bytes memory data = "deadbeef"; 346 | 347 | // Solidity representation 348 | abi.encode( 349 | UniV2Pair.swap.selector, 350 | canFail, 351 | pair, 352 | amount0Out, 353 | amount1Out, 354 | to, 355 | data 356 | ) 357 | 358 | /* 359 | // Solidity ABI 360 | // length: 288 bytes 361 | 362 | 022c0d9f00000000000000000000000000000000000000000000000000000000 // UniV2Pair.swap.selector 363 | 0000000000000000000000000000000000000000000000000000000000000000 // canFail 364 | 000000000000000000000000b4e16d0168e52d35cacd2c6185b44281ec28c9dc // pair 365 | 0000000000000000000000000000000000000000000000000de0b6b3a7640000 // amount0Out 366 | 0000000000000000000000000000000000000000000000000000000000000000 // amount1Out 367 | 000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266 // to 368 | 00000000000000000000000000000000000000000000000000000000000000e0 // data.offset 369 | 0000000000000000000000000000000000000000000000000000000000000008 // data.length 370 | 6465616462656566000000000000000000000000000000000000000000000000 // data 371 | */ 372 | 373 | BBCEncoder.encodeSwapUniV2(canFail, pair, amount0Out, amount1Out, to, data); 374 | 375 | /* 376 | // BBC ABI 377 | // length: 66 bytes 378 | 379 | 01 // Action.SwapUniV2 380 | 00 // canFail 381 | 14 // pair byte length 382 | b4e16d0168e52d35cacd2c6185b44281ec28c9dc // pair bytes 383 | 08 // amount0Out byte length 384 | 0de0b6b3a7640000 // amount0Out bytes 385 | 00 // amount1Out byte length 386 | 14 // to byte length 387 | f39fd6e51aad88f6f4ce6ab8827279cfffb92266 // to bytes 388 | 00000008 // data byte length 389 | 6465616462656566 // data bytes 390 | */ 391 | ``` 392 | -------------------------------------------------------------------------------- /src/util/BBCEncoder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { Action } from "src/types/Action.sol"; 5 | 6 | // ## Decoder 7 | // 8 | // Inspired by the calldata schema of BigBrainChad.eth 9 | // 10 | // ### Encoding Overview 11 | // 12 | // Statically sized calldata arguments of 8 bits or less are encoded in place. 13 | // 14 | // Statically sized calldata arguments of 9 to 256 bits are prefixed with their 15 | // byte length (as an 8 bit integer) followed by the argument, compacted to its 16 | // byte length. This is to handle the common case of the majority of bits being 17 | // unoccupied. 18 | // 19 | // Dynamically sized calldata arguments are prefixed with a 32 bit integer 20 | // indicating its byte length, followed by the bytes themselves. This is worth 21 | // exploring in the future as to whether or not the upper bits of the byte 22 | // length are unoccupied enough to justify an encoding as mentioned in the 23 | // statically sized calldata arguments above. 24 | // 25 | // ### Notes 26 | // 27 | // This encoder, while in the source directory of the Lotus Router and its 28 | // libraries, is not rigorously optimized, as the encoding scheme is meant to 29 | // reduce the cost of the smart contract entry point, given the unusually high 30 | // cost per byte of calldata. This is largely in service of testing libraries 31 | // and more offchain periphery will be developed in the future to ensure users 32 | // may interface with the Lotus Router in a reasonably safe way. 33 | // 34 | // Also, the encoder largely uses assembly nonetheless, as Solidity does not 35 | // support fully dependent types, which would allow for run-time 36 | // parameterization of value byte lengths. 37 | library BBCEncoder { 38 | function encodeSwapUniV2( 39 | bool canFail, 40 | address pair, 41 | uint256 amount0Out, 42 | uint256 amount1Out, 43 | address to, 44 | bytes memory data 45 | ) internal view returns (bytes memory) { 46 | Action action = Action.SwapUniV2; 47 | uint8 pairByteLen = byteLen(pair); 48 | uint8 amount0OutByteLen = byteLen(amount0Out); 49 | uint8 amount1OutByteLen = byteLen(amount1Out); 50 | uint8 toByteLen = byteLen(to); 51 | uint256 dataByteLen = data.length; 52 | 53 | bytes memory encoded = new bytes( 54 | 10 + pairByteLen + amount0OutByteLen + amount1OutByteLen + toByteLen + dataByteLen 55 | ); 56 | 57 | assembly ("memory-safe") { 58 | let ptr := add(encoded, 0x20) 59 | 60 | mstore(ptr, shl(0xf8, action)) 61 | ptr := add(ptr, 0x01) 62 | 63 | mstore(ptr, shl(0xf8, canFail)) 64 | ptr := add(ptr, 0x01) 65 | 66 | mstore(ptr, shl(0xf8, pairByteLen)) 67 | ptr := add(ptr, 0x01) 68 | 69 | mstore(ptr, shl(sub(0x0100, mul(0x08, pairByteLen)), pair)) 70 | ptr := add(ptr, pairByteLen) 71 | 72 | mstore(ptr, shl(0xf8, amount0OutByteLen)) 73 | ptr := add(ptr, 0x01) 74 | 75 | mstore(ptr, shl(sub(0x0100, mul(0x08, amount0OutByteLen)), amount0Out)) 76 | ptr := add(ptr, amount0OutByteLen) 77 | 78 | mstore(ptr, shl(0xf8, amount1OutByteLen)) 79 | ptr := add(ptr, 0x01) 80 | 81 | mstore(ptr, shl(sub(0x0100, mul(0x08, amount1OutByteLen)), amount1Out)) 82 | ptr := add(ptr, amount1OutByteLen) 83 | 84 | mstore(ptr, shl(0xf8, toByteLen)) 85 | ptr := add(ptr, 0x01) 86 | 87 | mstore(ptr, shl(sub(0x0100, mul(0x08, toByteLen)), to)) 88 | ptr := add(ptr, toByteLen) 89 | 90 | mstore(ptr, shl(0xe0, dataByteLen)) 91 | ptr := add(ptr, 0x04) 92 | 93 | pop(staticcall(gas(), 0x04, add(data, 0x20), dataByteLen, ptr, dataByteLen)) 94 | } 95 | 96 | return encoded; 97 | } 98 | 99 | function encodeSwapUniV3( 100 | bool canFail, 101 | address pool, 102 | address recipient, 103 | bool zeroForOne, 104 | int256 amountSpecified, 105 | uint160 sqrtPriceLimitX96, 106 | bytes memory data 107 | ) internal view returns (bytes memory) { 108 | Action action = Action.SwapUniV3; 109 | uint8 poolByteLen = byteLen(pool); 110 | uint8 recipientByteLen = byteLen(recipient); 111 | uint8 amountSpecifiedByteLen = byteLen(amountSpecified); 112 | uint8 sqrtPriceLimitX96ByteLen = byteLen(sqrtPriceLimitX96); 113 | uint256 dataByteLen = data.length; 114 | 115 | bytes memory encoded = new bytes( 116 | 11 + poolByteLen + recipientByteLen + amountSpecifiedByteLen + sqrtPriceLimitX96ByteLen 117 | + dataByteLen 118 | ); 119 | 120 | assembly ("memory-safe") { 121 | let ptr := add(encoded, 0x20) 122 | 123 | mstore(ptr, shl(0xf8, action)) 124 | ptr := add(ptr, 0x01) 125 | 126 | mstore(ptr, shl(0xf8, canFail)) 127 | ptr := add(ptr, 0x01) 128 | 129 | mstore(ptr, shl(0xf8, poolByteLen)) 130 | ptr := add(ptr, 0x01) 131 | 132 | mstore(ptr, shl(sub(0x0100, mul(poolByteLen, 0x08)), pool)) 133 | ptr := add(ptr, poolByteLen) 134 | 135 | mstore(ptr, shl(0xf8, recipientByteLen)) 136 | ptr := add(ptr, 0x01) 137 | 138 | mstore(ptr, shl(sub(0x0100, mul(recipientByteLen, 0x08)), recipient)) 139 | ptr := add(ptr, recipientByteLen) 140 | 141 | mstore(ptr, shl(0xf8, zeroForOne)) 142 | ptr := add(ptr, 0x01) 143 | 144 | mstore(ptr, shl(0xf8, amountSpecifiedByteLen)) 145 | ptr := add(ptr, 0x01) 146 | 147 | mstore(ptr, shl(sub(0x0100, mul(amountSpecifiedByteLen, 0x08)), amountSpecified)) 148 | ptr := add(ptr, amountSpecifiedByteLen) 149 | 150 | mstore(ptr, shl(0xf8, sqrtPriceLimitX96ByteLen)) 151 | ptr := add(ptr, 0x01) 152 | 153 | mstore(ptr, shl(sub(0x0100, mul(sqrtPriceLimitX96ByteLen, 0x08)), sqrtPriceLimitX96)) 154 | ptr := add(ptr, sqrtPriceLimitX96ByteLen) 155 | 156 | mstore(ptr, shl(0xe0, dataByteLen)) 157 | ptr := add(ptr, 0x04) 158 | 159 | pop(staticcall(gas(), 0x04, add(data, 0x20), dataByteLen, ptr, dataByteLen)) 160 | } 161 | 162 | return encoded; 163 | } 164 | 165 | function encodeFlashUniV3( 166 | bool canFail, 167 | address pool, 168 | address recipient, 169 | uint256 amount0, 170 | uint256 amount1, 171 | bytes memory data 172 | ) internal view returns (bytes memory) { 173 | Action action = Action.FlashUniV3; 174 | uint8 poolByteLen = byteLen(pool); 175 | uint8 recipientByteLen = byteLen(recipient); 176 | uint8 amount0ByteLen = byteLen(amount0); 177 | uint8 amount1ByteLen = byteLen(amount1); 178 | uint256 dataByteLen = data.length; 179 | 180 | bytes memory encoded = new bytes( 181 | 10 + poolByteLen + recipientByteLen + amount0ByteLen + amount1ByteLen + dataByteLen 182 | ); 183 | 184 | assembly ("memory-safe") { 185 | let ptr := add(encoded, 0x20) 186 | 187 | mstore(ptr, shl(0xf8, action)) 188 | ptr := add(ptr, 0x01) 189 | 190 | mstore(ptr, shl(0xf8, canFail)) 191 | ptr := add(ptr, 0x01) 192 | 193 | mstore(ptr, shl(0xf8, poolByteLen)) 194 | ptr := add(ptr, 0x01) 195 | 196 | mstore(ptr, shl(sub(0x0100, mul(poolByteLen, 0x08)), pool)) 197 | ptr := add(ptr, poolByteLen) 198 | 199 | mstore(ptr, shl(0xf8, recipientByteLen)) 200 | ptr := add(ptr, 0x01) 201 | 202 | mstore(ptr, shl(sub(0x0100, mul(recipientByteLen, 0x08)), recipient)) 203 | ptr := add(ptr, recipientByteLen) 204 | 205 | mstore(ptr, shl(0xf8, amount0ByteLen)) 206 | ptr := add(ptr, 0x01) 207 | 208 | mstore(ptr, shl(sub(0x0100, mul(amount0ByteLen, 0x08)), amount0)) 209 | ptr := add(ptr, amount0ByteLen) 210 | 211 | mstore(ptr, shl(0xf8, amount1ByteLen)) 212 | ptr := add(ptr, 0x01) 213 | 214 | mstore(ptr, shl(sub(0x0100, mul(amount1ByteLen, 0x08)), amount1)) 215 | ptr := add(ptr, amount1ByteLen) 216 | 217 | mstore(ptr, shl(0xe0, dataByteLen)) 218 | ptr := add(ptr, 0x04) 219 | 220 | pop(staticcall(gas(), 0x04, add(data, 0x20), dataByteLen, ptr, dataByteLen)) 221 | } 222 | 223 | return encoded; 224 | } 225 | 226 | function encodeTransferERC20( 227 | bool canFail, 228 | address token, 229 | address receiver, 230 | uint256 amount 231 | ) internal pure returns (bytes memory) { 232 | Action action = Action.TransferERC20; 233 | uint8 tokenByteLen = byteLen(token); 234 | uint8 receiverByteLen = byteLen(receiver); 235 | uint8 amountByteLen = byteLen(amount); 236 | 237 | bytes memory encoded = new bytes(5 + tokenByteLen + receiverByteLen + amountByteLen); 238 | 239 | assembly ("memory-safe") { 240 | let ptr := add(encoded, 0x20) 241 | 242 | mstore(ptr, shl(0xf8, action)) 243 | 244 | ptr := add(ptr, 0x01) 245 | 246 | mstore(ptr, shl(0xf8, canFail)) 247 | 248 | ptr := add(ptr, 0x01) 249 | 250 | mstore(ptr, shl(0xf8, tokenByteLen)) 251 | 252 | ptr := add(ptr, 0x01) 253 | 254 | mstore(ptr, shl(sub(0x0100, mul(0x08, tokenByteLen)), token)) 255 | 256 | ptr := add(ptr, tokenByteLen) 257 | 258 | mstore(ptr, shl(0xf8, receiverByteLen)) 259 | 260 | ptr := add(ptr, 0x01) 261 | 262 | mstore(ptr, shl(sub(0x0100, mul(0x08, receiverByteLen)), receiver)) 263 | 264 | ptr := add(ptr, receiverByteLen) 265 | 266 | mstore(ptr, shl(0xf8, amountByteLen)) 267 | 268 | ptr := add(ptr, 0x01) 269 | 270 | mstore(ptr, shl(sub(0x0100, mul(0x08, amountByteLen)), amount)) 271 | } 272 | 273 | return encoded; 274 | } 275 | 276 | function encodeTransferFromERC20( 277 | bool canFail, 278 | address token, 279 | address sender, 280 | address receiver, 281 | uint256 amount 282 | ) internal pure returns (bytes memory) { 283 | Action action = Action.TransferFromERC20; 284 | uint8 tokenByteLen = byteLen(token); 285 | uint8 senderByteLen = byteLen(sender); 286 | uint8 receiverByteLen = byteLen(receiver); 287 | uint8 amountByteLen = byteLen(amount); 288 | 289 | bytes memory encoded = 290 | new bytes(6 + tokenByteLen + senderByteLen + receiverByteLen + amountByteLen); 291 | 292 | assembly ("memory-safe") { 293 | let ptr := add(encoded, 0x20) 294 | 295 | mstore(ptr, shl(0xf8, action)) 296 | 297 | ptr := add(ptr, 0x01) 298 | 299 | mstore(ptr, shl(0xf8, canFail)) 300 | 301 | ptr := add(ptr, 0x01) 302 | 303 | mstore(ptr, shl(0xf8, tokenByteLen)) 304 | 305 | ptr := add(ptr, 0x01) 306 | 307 | mstore(ptr, shl(sub(0x0100, mul(0x08, tokenByteLen)), token)) 308 | 309 | ptr := add(ptr, tokenByteLen) 310 | 311 | mstore(ptr, shl(0xf8, senderByteLen)) 312 | 313 | ptr := add(ptr, 0x01) 314 | 315 | mstore(ptr, shl(sub(0x0100, mul(0x08, senderByteLen)), sender)) 316 | 317 | ptr := add(ptr, senderByteLen) 318 | 319 | mstore(ptr, shl(0xf8, receiverByteLen)) 320 | 321 | ptr := add(ptr, 0x01) 322 | 323 | mstore(ptr, shl(sub(0x0100, mul(0x08, receiverByteLen)), receiver)) 324 | 325 | ptr := add(ptr, receiverByteLen) 326 | 327 | mstore(ptr, shl(0xf8, amountByteLen)) 328 | 329 | ptr := add(ptr, 0x01) 330 | 331 | mstore(ptr, shl(sub(0x0100, mul(0x08, amountByteLen)), amount)) 332 | } 333 | 334 | return encoded; 335 | } 336 | 337 | function encodeTransferFromERC721( 338 | bool canFail, 339 | address token, 340 | address sender, 341 | address receiver, 342 | uint256 tokenId 343 | ) internal pure returns (bytes memory) { 344 | Action action = Action.TransferFromERC20; 345 | uint8 tokenByteLen = byteLen(token); 346 | uint8 senderByteLen = byteLen(sender); 347 | uint8 receiverByteLen = byteLen(receiver); 348 | uint8 tokenIdByteLen = byteLen(tokenId); 349 | 350 | bytes memory encoded = 351 | new bytes(6 + tokenByteLen + senderByteLen + receiverByteLen + tokenIdByteLen); 352 | 353 | assembly ("memory-safe") { 354 | let ptr := add(encoded, 0x20) 355 | 356 | mstore(ptr, shl(0xf8, action)) 357 | 358 | ptr := add(ptr, 0x01) 359 | 360 | mstore(ptr, shl(0xf8, canFail)) 361 | 362 | ptr := add(ptr, 0x01) 363 | 364 | mstore(ptr, shl(0xf8, tokenByteLen)) 365 | 366 | ptr := add(ptr, 0x01) 367 | 368 | mstore(ptr, shl(sub(0x0100, mul(0x08, tokenByteLen)), token)) 369 | 370 | ptr := add(ptr, tokenByteLen) 371 | 372 | mstore(ptr, shl(0xf8, senderByteLen)) 373 | 374 | ptr := add(ptr, 0x01) 375 | 376 | mstore(ptr, shl(sub(0x0100, mul(0x08, senderByteLen)), sender)) 377 | 378 | ptr := add(ptr, senderByteLen) 379 | 380 | mstore(ptr, shl(0xf8, receiverByteLen)) 381 | 382 | ptr := add(ptr, 0x01) 383 | 384 | mstore(ptr, shl(sub(0x0100, mul(0x08, receiverByteLen)), receiver)) 385 | 386 | ptr := add(ptr, receiverByteLen) 387 | 388 | mstore(ptr, shl(0xf8, tokenIdByteLen)) 389 | 390 | ptr := add(ptr, 0x01) 391 | 392 | mstore(ptr, shl(sub(0x0100, mul(0x08, tokenIdByteLen)), tokenId)) 393 | } 394 | 395 | return encoded; 396 | } 397 | 398 | function encodeTransferERC6909( 399 | bool canFail, 400 | address multitoken, 401 | address receiver, 402 | uint256 tokenId, 403 | uint256 amount 404 | ) internal pure returns (bytes memory) { 405 | Action action = Action.TransferERC6909; 406 | uint8 multitokenByteLen = byteLen(multitoken); 407 | uint8 receiverByteLen = byteLen(receiver); 408 | uint8 tokenIdByteLen = byteLen(tokenId); 409 | uint8 amountByteLen = byteLen(amount); 410 | 411 | bytes memory encoded = 412 | new bytes(6 + multitokenByteLen + receiverByteLen + tokenIdByteLen + amountByteLen); 413 | 414 | assembly ("memory-safe") { 415 | let ptr := add(encoded, 0x20) 416 | 417 | mstore(ptr, shl(0xf8, action)) 418 | 419 | ptr := add(ptr, 0x01) 420 | 421 | mstore(ptr, shl(0xf8, canFail)) 422 | 423 | ptr := add(ptr, 0x01) 424 | 425 | mstore(ptr, shl(0xf8, multitokenByteLen)) 426 | 427 | ptr := add(ptr, 0x01) 428 | 429 | mstore(ptr, shl(sub(0x0100, mul(0x08, multitokenByteLen)), multitoken)) 430 | 431 | ptr := add(ptr, multitokenByteLen) 432 | 433 | mstore(ptr, shl(0xf8, receiverByteLen)) 434 | 435 | ptr := add(ptr, 0x01) 436 | 437 | mstore(ptr, shl(sub(0x0100, mul(0x08, receiverByteLen)), receiver)) 438 | 439 | ptr := add(ptr, receiverByteLen) 440 | 441 | mstore(ptr, shl(0xf8, tokenIdByteLen)) 442 | 443 | ptr := add(ptr, 0x01) 444 | 445 | mstore(ptr, shl(sub(0x0100, mul(0x08, tokenIdByteLen)), tokenId)) 446 | 447 | ptr := add(ptr, tokenIdByteLen) 448 | 449 | mstore(ptr, shl(0xf8, amountByteLen)) 450 | 451 | ptr := add(ptr, 0x01) 452 | 453 | mstore(ptr, shl(sub(0x0100, mul(0x08, amountByteLen)), amount)) 454 | } 455 | 456 | return encoded; 457 | } 458 | 459 | function encodeTransferFromERC6909( 460 | bool canFail, 461 | address multitoken, 462 | address sender, 463 | address receiver, 464 | uint256 tokenId, 465 | uint256 amount 466 | ) internal pure returns (bytes memory) { 467 | Action action = Action.TransferFromERC6909; 468 | uint8 multitokenByteLen = byteLen(multitoken); 469 | uint8 senderByteLen = byteLen(sender); 470 | uint8 receiverByteLen = byteLen(receiver); 471 | uint8 tokenIdByteLen = byteLen(tokenId); 472 | uint8 amountByteLen = byteLen(amount); 473 | 474 | bytes memory encoded = new bytes( 475 | 7 + multitokenByteLen + senderByteLen + receiverByteLen + tokenIdByteLen + amountByteLen 476 | ); 477 | 478 | assembly ("memory-safe") { 479 | let ptr := add(encoded, 0x20) 480 | 481 | mstore(ptr, shl(0xf8, action)) 482 | 483 | ptr := add(ptr, 0x01) 484 | 485 | mstore(ptr, shl(0xf8, canFail)) 486 | 487 | ptr := add(ptr, 0x01) 488 | 489 | mstore(ptr, shl(0xf8, multitokenByteLen)) 490 | 491 | ptr := add(ptr, 0x01) 492 | 493 | mstore(ptr, shl(sub(0x0100, mul(0x08, multitokenByteLen)), multitoken)) 494 | 495 | ptr := add(ptr, multitokenByteLen) 496 | 497 | mstore(ptr, shl(0xf8, senderByteLen)) 498 | 499 | ptr := add(ptr, 0x01) 500 | 501 | mstore(ptr, shl(sub(0x0100, mul(0x08, senderByteLen)), sender)) 502 | 503 | ptr := add(ptr, senderByteLen) 504 | 505 | mstore(ptr, shl(0xf8, receiverByteLen)) 506 | 507 | ptr := add(ptr, 0x01) 508 | 509 | mstore(ptr, shl(sub(0x0100, mul(0x08, receiverByteLen)), receiver)) 510 | 511 | ptr := add(ptr, receiverByteLen) 512 | 513 | mstore(ptr, shl(0xf8, tokenIdByteLen)) 514 | 515 | ptr := add(ptr, 0x01) 516 | 517 | mstore(ptr, shl(sub(0x0100, mul(0x08, tokenIdByteLen)), tokenId)) 518 | 519 | ptr := add(ptr, tokenIdByteLen) 520 | 521 | mstore(ptr, shl(0xf8, amountByteLen)) 522 | 523 | ptr := add(ptr, 0x01) 524 | 525 | mstore(ptr, shl(sub(0x0100, mul(0x08, amountByteLen)), amount)) 526 | } 527 | 528 | return encoded; 529 | } 530 | 531 | function encodeDepositWETH( 532 | bool canFail, 533 | address weth, 534 | uint256 value 535 | ) internal pure returns (bytes memory) { 536 | Action action = Action.DepositWETH; 537 | uint8 wethByteLen = byteLen(weth); 538 | uint8 valueByteLen = byteLen(value); 539 | 540 | bytes memory encoded = new bytes(4 + wethByteLen + valueByteLen); 541 | 542 | assembly { 543 | let ptr := add(encoded, 0x20) 544 | 545 | mstore(ptr, shl(0xf8, action)) 546 | 547 | ptr := add(ptr, 0x01) 548 | 549 | mstore(ptr, shl(0xf8, canFail)) 550 | 551 | ptr := add(ptr, 0x01) 552 | 553 | mstore(ptr, shl(0xf8, wethByteLen)) 554 | 555 | ptr := add(ptr, 0x01) 556 | 557 | mstore(ptr, shl(sub(0x0100, mul(0x08, wethByteLen)), weth)) 558 | 559 | ptr := add(ptr, wethByteLen) 560 | 561 | mstore(ptr, shl(0xf8, valueByteLen)) 562 | 563 | ptr := add(ptr, 0x01) 564 | 565 | mstore(ptr, shl(sub(0x0100, mul(0x08, valueByteLen)), value)) 566 | } 567 | 568 | return encoded; 569 | } 570 | 571 | function encodeWithdrawWETH( 572 | bool canFail, 573 | address weth, 574 | uint256 value 575 | ) internal pure returns (bytes memory) { 576 | Action action = Action.WithdrawWETH; 577 | uint8 wethByteLen = byteLen(weth); 578 | uint8 valueByteLen = byteLen(value); 579 | 580 | bytes memory encoded = new bytes(4 + wethByteLen + valueByteLen); 581 | 582 | assembly { 583 | let ptr := add(encoded, 0x20) 584 | 585 | mstore(ptr, shl(0xf8, action)) 586 | 587 | ptr := add(ptr, 0x01) 588 | 589 | mstore(ptr, shl(0xf8, canFail)) 590 | 591 | ptr := add(ptr, 0x01) 592 | 593 | mstore(ptr, shl(0xf8, wethByteLen)) 594 | 595 | ptr := add(ptr, 0x01) 596 | 597 | mstore(ptr, shl(sub(0x0100, mul(0x08, wethByteLen)), weth)) 598 | 599 | ptr := add(ptr, wethByteLen) 600 | 601 | mstore(ptr, shl(0xf8, valueByteLen)) 602 | 603 | ptr := add(ptr, 0x01) 604 | 605 | mstore(ptr, shl(sub(0x0100, mul(0x08, valueByteLen)), value)) 606 | } 607 | 608 | return encoded; 609 | } 610 | 611 | function encodeDynCall( 612 | bool canFail, 613 | address target, 614 | uint256 value, 615 | bytes memory data 616 | ) internal view returns (bytes memory) { 617 | Action action = Action.DynCall; 618 | uint8 targetByteLen = byteLen(target); 619 | uint8 valueByteLen = byteLen(value); 620 | uint256 dataByteLen = data.length; 621 | 622 | bytes memory encoded = new bytes( 623 | 8 + targetByteLen + valueByteLen + dataByteLen 624 | ); 625 | 626 | assembly ("memory-safe") { 627 | let ptr := add(encoded, 0x20) 628 | 629 | mstore(ptr, shl(0xf8, action)) 630 | ptr := add(ptr, 0x01) 631 | 632 | mstore(ptr, shl(0xf8, canFail)) 633 | ptr := add(ptr, 0x01) 634 | 635 | mstore(ptr, shl(0xf8, targetByteLen)) 636 | ptr := add(ptr, 0x01) 637 | 638 | mstore(ptr, shl(sub(0x0100, mul(0x08, targetByteLen)), target)) 639 | ptr := add(ptr, targetByteLen) 640 | 641 | mstore(ptr, shl(0xf8, valueByteLen)) 642 | ptr := add(ptr, 0x01) 643 | 644 | mstore(ptr, shl(sub(0x0100, mul(0x08, valueByteLen)), value)) 645 | ptr := add(ptr, valueByteLen) 646 | 647 | mstore(ptr, shl(0xe0, dataByteLen)) 648 | ptr := add(ptr, 0x04) 649 | 650 | pop(staticcall(gas(), 0x04, add(data, 0x20), dataByteLen, ptr, dataByteLen)) 651 | } 652 | 653 | return encoded; 654 | } 655 | 656 | function byteLen( 657 | uint256 word 658 | ) internal pure returns (uint8) { 659 | for (uint8 i = 32; i > 0; i--) { 660 | if (word >> ((i - 1) * 8) != 0) return i; 661 | } 662 | 663 | return 0; 664 | } 665 | 666 | function byteLen( 667 | address addr 668 | ) internal pure returns (uint8) { 669 | uint160 word = uint160(addr); 670 | 671 | for (uint8 i = 20; i > 0; i--) { 672 | if (word >> ((i - 1) * 8) != 0) return i; 673 | } 674 | 675 | return 0; 676 | } 677 | 678 | function byteLen( 679 | int256 word 680 | ) internal pure returns (uint8) { 681 | uint256 adjusted; 682 | 683 | if (word < 0) { 684 | adjusted = uint256(-word); 685 | } else { 686 | adjusted = uint256(word); 687 | } 688 | 689 | if (byteLen(adjusted) == 32) return 32; 690 | else return byteLen(adjusted << 1); 691 | } 692 | } 693 | -------------------------------------------------------------------------------- /test/BBCDecoder.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { Test } from "lib/forge-std/src/Test.sol"; 5 | import { BBCDecoderMock } from "test/mock/BBCDecoderMock.sol"; 6 | 7 | import { Action } from "src/types/Action.sol"; 8 | import { BytesCalldata } from "src/types/BytesCalldata.sol"; 9 | import { Ptr } from "src/types/PayloadPointer.sol"; 10 | import { ERC20 } from "src/types/protocols/ERC20.sol"; 11 | import { ERC6909 } from "src/types/protocols/ERC6909.sol"; 12 | import { ERC721 } from "src/types/protocols/ERC721.sol"; 13 | import { UniV2Pair } from "src/types/protocols/UniV2Pair.sol"; 14 | import { UniV3Pool } from "src/types/protocols/UniV3Pool.sol"; 15 | import { WETH } from "src/types/protocols/WETH.sol"; 16 | import { BBCDecoder } from "src/util/BBCDecoder.sol"; 17 | import { BBCEncoder } from "src/util/BBCEncoder.sol"; 18 | 19 | contract BBCDecoderTest is Test { 20 | BBCDecoderMock decoder; 21 | 22 | function setUp() public { 23 | decoder = new BBCDecoderMock(); 24 | } 25 | 26 | function testDecodeSwapUniV2() public view { 27 | bool expectedCanFail = true; 28 | address expectedPair = address(0xaabbccdd); 29 | uint8 expectedAmount0Out = 0x45; 30 | uint8 expectedAmount1Out = 0x46; 31 | address expectedTo = address(0xeeffaabb); 32 | bytes memory expectedData = hex"deadbeef"; 33 | 34 | bytes memory encoded = BBCEncoder.encodeSwapUniV2( 35 | expectedCanFail, 36 | expectedPair, 37 | expectedAmount0Out, 38 | expectedAmount1Out, 39 | expectedTo, 40 | expectedData 41 | ); 42 | 43 | ( 44 | bool canFail, 45 | UniV2Pair pair, 46 | uint256 amount0Out, 47 | uint256 amount1Out, 48 | address to, 49 | bytes memory data 50 | ) = decoder.decodeSwapUniV2(encoded); 51 | 52 | assertEq(canFail, expectedCanFail); 53 | assertEq(UniV2Pair.unwrap(pair), expectedPair); 54 | assertEq(amount0Out, expectedAmount0Out); 55 | assertEq(amount1Out, expectedAmount1Out); 56 | assertEq(to, expectedTo); 57 | assertEq(keccak256(data), keccak256(expectedData)); 58 | } 59 | 60 | function testFuzzDecodeSwapUniv2( 61 | bool expectedCanFail, 62 | address expectedPair, 63 | uint8 expectedAmount0Out, 64 | uint8 expectedAmount1Out, 65 | address expectedTo, 66 | bytes memory expectedData 67 | ) public view { 68 | bytes memory encoded = BBCEncoder.encodeSwapUniV2( 69 | expectedCanFail, 70 | expectedPair, 71 | expectedAmount0Out, 72 | expectedAmount1Out, 73 | expectedTo, 74 | expectedData 75 | ); 76 | 77 | ( 78 | bool canFail, 79 | UniV2Pair pair, 80 | uint256 amount0Out, 81 | uint256 amount1Out, 82 | address to, 83 | bytes memory data 84 | ) = decoder.decodeSwapUniV2(encoded); 85 | 86 | assertEq(canFail, expectedCanFail); 87 | assertEq(UniV2Pair.unwrap(pair), expectedPair); 88 | assertEq(amount0Out, expectedAmount0Out); 89 | assertEq(amount1Out, expectedAmount1Out); 90 | assertEq(to, expectedTo); 91 | assertEq(keccak256(data), keccak256(expectedData)); 92 | } 93 | 94 | function testDecodeSwapUniV3() public view { 95 | bool expectedCanFail = true; 96 | address expectedPool = address(0xaabbccdd); 97 | address expectedRecipient = address(0xeeffaabb); 98 | bool expectedZeroForOne = true; 99 | int256 expectedAmountSpecified = 0x02; 100 | uint160 expectedSqrtPriceLimitX96 = 0x03; 101 | bytes memory expectedData = hex"deadbeef"; 102 | 103 | bytes memory encoded = BBCEncoder.encodeSwapUniV3( 104 | expectedCanFail, 105 | expectedPool, 106 | expectedRecipient, 107 | expectedZeroForOne, 108 | expectedAmountSpecified, 109 | expectedSqrtPriceLimitX96, 110 | expectedData 111 | ); 112 | 113 | ( 114 | bool canFail, 115 | UniV3Pool pool, 116 | address recipient, 117 | bool zeroForOne, 118 | int256 amountSpecified, 119 | uint160 sqrtPriceLimitX96, 120 | bytes memory data 121 | ) = decoder.decodeSwapUniV3(encoded); 122 | 123 | assertEq(canFail, expectedCanFail); 124 | assertEq(UniV3Pool.unwrap(pool), expectedPool); 125 | assertEq(recipient, expectedRecipient); 126 | assertEq(zeroForOne, expectedZeroForOne); 127 | assertEq(amountSpecified, expectedAmountSpecified); 128 | assertEq(sqrtPriceLimitX96, expectedSqrtPriceLimitX96); 129 | assertEq(keccak256(data), keccak256(expectedData)); 130 | } 131 | 132 | function testDecodeSwapUniV3Negative() public view { 133 | bool expectedCanFail = true; 134 | address expectedPool = address(0xaabbccdd); 135 | address expectedRecipient = address(0xeeffaabb); 136 | bool expectedZeroForOne = true; 137 | int256 expectedAmountSpecified = -0x02; 138 | uint160 expectedSqrtPriceLimitX96 = 0x03; 139 | bytes memory expectedData = hex"deadbeef"; 140 | 141 | bytes memory encoded = BBCEncoder.encodeSwapUniV3( 142 | expectedCanFail, 143 | expectedPool, 144 | expectedRecipient, 145 | expectedZeroForOne, 146 | expectedAmountSpecified, 147 | expectedSqrtPriceLimitX96, 148 | expectedData 149 | ); 150 | 151 | ( 152 | bool canFail, 153 | UniV3Pool pool, 154 | address recipient, 155 | bool zeroForOne, 156 | int256 amountSpecified, 157 | uint160 sqrtPriceLimitX96, 158 | bytes memory data 159 | ) = decoder.decodeSwapUniV3(encoded); 160 | 161 | assertEq(canFail, expectedCanFail); 162 | assertEq(UniV3Pool.unwrap(pool), expectedPool); 163 | assertEq(recipient, expectedRecipient); 164 | assertEq(zeroForOne, expectedZeroForOne); 165 | assertEq(amountSpecified, expectedAmountSpecified); 166 | assertEq(sqrtPriceLimitX96, expectedSqrtPriceLimitX96); 167 | assertEq(keccak256(data), keccak256(expectedData)); 168 | } 169 | 170 | function testFuzzDecodeSwapUniV3( 171 | bool expectedCanFail, 172 | address expectedPool, 173 | address expectedRecipient, 174 | bool expectedZeroForOne, 175 | int256 expectedAmountSpecified, 176 | uint160 expectedSqrtPriceLimitX96, 177 | bytes memory expectedData 178 | ) public view { 179 | // why? bc `-expectedAmountSpecified` in this exact case overflows :( 180 | vm.assume( 181 | expectedAmountSpecified 182 | != -57896044618658097711785492504343953926634992332820282019728792003956564819968 183 | ); 184 | 185 | bytes memory encoded = BBCEncoder.encodeSwapUniV3( 186 | expectedCanFail, 187 | expectedPool, 188 | expectedRecipient, 189 | expectedZeroForOne, 190 | expectedAmountSpecified, 191 | expectedSqrtPriceLimitX96, 192 | expectedData 193 | ); 194 | 195 | ( 196 | bool canFail, 197 | UniV3Pool pool, 198 | address recipient, 199 | bool zeroForOne, 200 | int256 amountSpecified, 201 | uint160 sqrtPriceLimitX96, 202 | bytes memory data 203 | ) = decoder.decodeSwapUniV3(encoded); 204 | 205 | assertEq(canFail, expectedCanFail); 206 | assertEq(UniV3Pool.unwrap(pool), expectedPool); 207 | assertEq(recipient, expectedRecipient); 208 | assertEq(zeroForOne, expectedZeroForOne); 209 | assertEq(amountSpecified, expectedAmountSpecified); 210 | assertEq(sqrtPriceLimitX96, expectedSqrtPriceLimitX96); 211 | assertEq(keccak256(data), keccak256(expectedData)); 212 | } 213 | 214 | function testDecodeFlashUniV3() public view { 215 | bool expectedCanFail = true; 216 | address expectedPool = address(0xaabbccdd); 217 | address expectedRecipient = address(0xeeffaabb); 218 | uint256 expectedAmount0 = 0x45; 219 | uint256 expectedAmount1 = 0x46; 220 | bytes memory expectedData = hex"deadbeef"; 221 | 222 | bytes memory encoded = BBCEncoder.encodeFlashUniV3( 223 | expectedCanFail, 224 | expectedPool, 225 | expectedRecipient, 226 | expectedAmount0, 227 | expectedAmount1, 228 | expectedData 229 | ); 230 | 231 | ( 232 | bool canFail, 233 | UniV3Pool pool, 234 | address recipient, 235 | uint256 amount0, 236 | uint256 amount1, 237 | bytes memory data 238 | ) = decoder.decodeFlashUniV3(encoded); 239 | 240 | assertEq(canFail, expectedCanFail); 241 | assertEq(UniV3Pool.unwrap(pool), expectedPool); 242 | assertEq(recipient, expectedRecipient); 243 | assertEq(amount0, expectedAmount0); 244 | assertEq(amount1, expectedAmount1); 245 | assertEq(keccak256(data), keccak256(expectedData)); 246 | } 247 | 248 | function testFuzzDecodeFlashUniV3( 249 | bool expectedCanFail, 250 | address expectedPool, 251 | address expectedRecipient, 252 | uint256 expectedAmount0, 253 | uint256 expectedAmount1, 254 | bytes memory expectedData 255 | ) public { 256 | bytes memory encoded = BBCEncoder.encodeFlashUniV3( 257 | expectedCanFail, 258 | expectedPool, 259 | expectedRecipient, 260 | expectedAmount0, 261 | expectedAmount1, 262 | expectedData 263 | ); 264 | 265 | emit log_bytes(encoded); 266 | 267 | ( 268 | bool canFail, 269 | UniV3Pool pool, 270 | address recipient, 271 | uint256 amount0, 272 | uint256 amount1, 273 | bytes memory data 274 | ) = decoder.decodeFlashUniV3(encoded); 275 | 276 | assertEq(canFail, expectedCanFail); 277 | assertEq(UniV3Pool.unwrap(pool), expectedPool); 278 | assertEq(recipient, expectedRecipient); 279 | assertEq(amount0, expectedAmount0); 280 | assertEq(amount1, expectedAmount1); 281 | assertEq(keccak256(data), keccak256(expectedData)); 282 | } 283 | 284 | function testDecodeTransferERC20() public view { 285 | bool expectedCanFail = true; 286 | address expectedToken = address(0xaabbccdd); 287 | address expectedReceiver = address(0xeeffaabb); 288 | uint8 expectedAmount = 0x45; 289 | 290 | bytes memory encoded = BBCEncoder.encodeTransferERC20( 291 | expectedCanFail, expectedToken, expectedReceiver, expectedAmount 292 | ); 293 | 294 | (bool canFail, ERC20 token, address receiver, uint256 amount) = 295 | decoder.decodeTransferERC20(encoded); 296 | 297 | assertEq(canFail, expectedCanFail); 298 | assertEq(ERC20.unwrap(token), expectedToken); 299 | assertEq(receiver, expectedReceiver); 300 | assertEq(amount, expectedAmount); 301 | } 302 | 303 | function tesFuzzDecodeTransferERC20( 304 | bool expectedCanFail, 305 | address expectedToken, 306 | address expectedReceiver, 307 | uint8 expectedAmount 308 | ) public view { 309 | bytes memory encoded = BBCEncoder.encodeTransferERC20( 310 | expectedCanFail, expectedToken, expectedReceiver, expectedAmount 311 | ); 312 | 313 | (bool canFail, ERC20 token, address receiver, uint256 amount) = 314 | decoder.decodeTransferERC20(encoded); 315 | 316 | assertEq(canFail, expectedCanFail); 317 | assertEq(ERC20.unwrap(token), expectedToken); 318 | assertEq(receiver, expectedReceiver); 319 | assertEq(amount, expectedAmount); 320 | } 321 | 322 | function testDecodeTransferFromERC20() public view { 323 | bool expectedCanFail = true; 324 | address expectedToken = address(0xaabbccdd); 325 | address expectedSender = address(0xeeffaabb); 326 | address expectedReceiver = address(0xccddeeff); 327 | uint8 expectedAmount = 0x45; 328 | 329 | bytes memory encoded = BBCEncoder.encodeTransferFromERC20( 330 | expectedCanFail, expectedToken, expectedSender, expectedReceiver, expectedAmount 331 | ); 332 | 333 | (bool canFail, ERC20 token, address sender, address receiver, uint256 amount) = 334 | decoder.decodeTransferFromERC20(encoded); 335 | 336 | assertEq(canFail, expectedCanFail); 337 | assertEq(ERC20.unwrap(token), expectedToken); 338 | assertEq(sender, expectedSender); 339 | assertEq(receiver, expectedReceiver); 340 | assertEq(amount, expectedAmount); 341 | } 342 | 343 | function testFuzzDecodeTransferFromERC20( 344 | bool expectedCanFail, 345 | address expectedToken, 346 | address expectedSender, 347 | address expectedReceiver, 348 | uint8 expectedAmount 349 | ) public view { 350 | bytes memory encoded = BBCEncoder.encodeTransferFromERC20( 351 | expectedCanFail, expectedToken, expectedSender, expectedReceiver, expectedAmount 352 | ); 353 | 354 | (bool canFail, ERC20 token, address sender, address receiver, uint256 amount) = 355 | decoder.decodeTransferFromERC20(encoded); 356 | 357 | assertEq(canFail, expectedCanFail); 358 | assertEq(ERC20.unwrap(token), expectedToken); 359 | assertEq(sender, expectedSender); 360 | assertEq(receiver, expectedReceiver); 361 | assertEq(amount, expectedAmount); 362 | } 363 | 364 | function testDecodeTransferFromERC721() public view { 365 | bool expectedCanFail = true; 366 | address expectedToken = address(0xaabbccdd); 367 | address expectedSender = address(0xeeffaabb); 368 | address expectedReceiver = address(0xccddeeff); 369 | uint8 expectedTokenId = 0x45; 370 | 371 | bytes memory encoded = BBCEncoder.encodeTransferFromERC721( 372 | expectedCanFail, expectedToken, expectedSender, expectedReceiver, expectedTokenId 373 | ); 374 | 375 | (bool canFail, ERC721 token, address sender, address receiver, uint256 tokenId) = 376 | decoder.decodeTransferFromERC721(encoded); 377 | 378 | assertEq(canFail, expectedCanFail); 379 | assertEq(ERC721.unwrap(token), expectedToken); 380 | assertEq(sender, expectedSender); 381 | assertEq(receiver, expectedReceiver); 382 | assertEq(tokenId, expectedTokenId); 383 | } 384 | 385 | function testFuzzDecodeTransferFromERC721( 386 | bool expectedCanFail, 387 | address expectedToken, 388 | address expectedSender, 389 | address expectedReceiver, 390 | uint8 expectedTokenId 391 | ) public view { 392 | bytes memory encoded = BBCEncoder.encodeTransferFromERC721( 393 | expectedCanFail, expectedToken, expectedSender, expectedReceiver, expectedTokenId 394 | ); 395 | 396 | (bool canFail, ERC721 token, address sender, address receiver, uint256 tokenId) = 397 | decoder.decodeTransferFromERC721(encoded); 398 | 399 | assertEq(canFail, expectedCanFail); 400 | assertEq(ERC721.unwrap(token), expectedToken); 401 | assertEq(sender, expectedSender); 402 | assertEq(receiver, expectedReceiver); 403 | assertEq(tokenId, expectedTokenId); 404 | } 405 | 406 | function testDecodeTransferERC6909() public view { 407 | bool expectedCanFail = false; 408 | address expectedMultitoken = address(0xaabbccdd); 409 | address expectedReceiver = address(0xeeffaabb); 410 | uint256 expectedTokenId = 0x45; 411 | uint256 expectedAmount = 0x46; 412 | 413 | bytes memory encoded = BBCEncoder.encodeTransferERC6909( 414 | expectedCanFail, expectedMultitoken, expectedReceiver, expectedTokenId, expectedAmount 415 | ); 416 | 417 | (bool canFail, ERC6909 multitoken, address receiver, uint256 tokenId, uint256 amount) = 418 | decoder.decodeTransferERC6909(encoded); 419 | 420 | assertEq(canFail, expectedCanFail); 421 | assertEq(ERC6909.unwrap(multitoken), expectedMultitoken); 422 | assertEq(receiver, expectedReceiver); 423 | assertEq(tokenId, expectedTokenId); 424 | assertEq(amount, expectedAmount); 425 | } 426 | 427 | function testFuzzDecodeTransferERC6909( 428 | bool expectedCanFail, 429 | address expectedMultitoken, 430 | address expectedReceiver, 431 | uint256 expectedTokenId, 432 | uint256 expectedAmount 433 | ) public view { 434 | bytes memory encoded = BBCEncoder.encodeTransferERC6909( 435 | expectedCanFail, expectedMultitoken, expectedReceiver, expectedTokenId, expectedAmount 436 | ); 437 | 438 | (bool canFail, ERC6909 multitoken, address receiver, uint256 tokenId, uint256 amount) = 439 | decoder.decodeTransferERC6909(encoded); 440 | 441 | assertEq(canFail, expectedCanFail); 442 | assertEq(ERC6909.unwrap(multitoken), expectedMultitoken); 443 | assertEq(receiver, expectedReceiver); 444 | assertEq(tokenId, expectedTokenId); 445 | assertEq(amount, expectedAmount); 446 | } 447 | 448 | function testDecodeTransferFromERC6909() public view { 449 | bool expectedCanFail = false; 450 | address expectedMultitoken = address(0xaabbccdd); 451 | address expectedSender = address(0xeeffaabb); 452 | address expectedReceiver = address(0xccddeeff); 453 | uint256 expectedTokenId = 0x45; 454 | uint256 expectedAmount = 0x46; 455 | 456 | bytes memory encoded = BBCEncoder.encodeTransferFromERC6909( 457 | expectedCanFail, 458 | expectedMultitoken, 459 | expectedSender, 460 | expectedReceiver, 461 | expectedTokenId, 462 | expectedAmount 463 | ); 464 | 465 | ( 466 | bool canFail, 467 | ERC6909 multitoken, 468 | address sender, 469 | address receiver, 470 | uint256 tokenId, 471 | uint256 amount 472 | ) = decoder.decodeTransferFromERC6909(encoded); 473 | 474 | assertEq(canFail, expectedCanFail); 475 | assertEq(ERC6909.unwrap(multitoken), expectedMultitoken); 476 | assertEq(sender, expectedSender); 477 | assertEq(receiver, expectedReceiver); 478 | assertEq(tokenId, expectedTokenId); 479 | assertEq(amount, expectedAmount); 480 | } 481 | 482 | function testFuzzDecodeTransferFromERC6909( 483 | bool expectedCanFail, 484 | address expectedMultitoken, 485 | address expectedSender, 486 | address expectedReceiver, 487 | uint256 expectedTokenId, 488 | uint256 expectedAmount 489 | ) public view { 490 | bytes memory encoded = BBCEncoder.encodeTransferFromERC6909( 491 | expectedCanFail, 492 | expectedMultitoken, 493 | expectedSender, 494 | expectedReceiver, 495 | expectedTokenId, 496 | expectedAmount 497 | ); 498 | 499 | ( 500 | bool canFail, 501 | ERC6909 multitoken, 502 | address sender, 503 | address receiver, 504 | uint256 tokenId, 505 | uint256 amount 506 | ) = decoder.decodeTransferFromERC6909(encoded); 507 | 508 | assertEq(canFail, expectedCanFail); 509 | assertEq(ERC6909.unwrap(multitoken), expectedMultitoken); 510 | assertEq(sender, expectedSender); 511 | assertEq(receiver, expectedReceiver); 512 | assertEq(tokenId, expectedTokenId); 513 | assertEq(amount, expectedAmount); 514 | } 515 | 516 | function testDecodeDepositWETH() public view { 517 | bool expectedCanFail = false; 518 | address expectedWeth = address(0xaabbccdd); 519 | uint256 expectedValue = 0x45; 520 | 521 | bytes memory encoded = 522 | BBCEncoder.encodeDepositWETH(expectedCanFail, expectedWeth, expectedValue); 523 | 524 | (bool canFail, WETH weth, uint256 value) = decoder.decodeDepositWETH(encoded); 525 | 526 | assertEq(canFail, expectedCanFail); 527 | assertEq(WETH.unwrap(weth), expectedWeth); 528 | assertEq(value, expectedValue); 529 | } 530 | 531 | function testFuzzDecodeDepositWETH( 532 | bool expectedCanFail, 533 | address expectedWeth, 534 | uint8 expectedValue 535 | ) public view { 536 | bytes memory encoded = 537 | BBCEncoder.encodeDepositWETH(expectedCanFail, expectedWeth, expectedValue); 538 | 539 | (bool canFail, WETH weth, uint256 value) = decoder.decodeDepositWETH(encoded); 540 | 541 | assertEq(canFail, expectedCanFail); 542 | assertEq(WETH.unwrap(weth), expectedWeth); 543 | assertEq(value, expectedValue); 544 | } 545 | 546 | function testDecodWithdrawWETH() public view { 547 | bool expectedCanFail = false; 548 | address expectedWeth = address(0xaabbccdd); 549 | uint256 expectedValue = 0x45; 550 | 551 | bytes memory encoded = 552 | BBCEncoder.encodeWithdrawWETH(expectedCanFail, expectedWeth, expectedValue); 553 | 554 | (bool canFail, WETH weth, uint256 value) = decoder.decodeWithdrawWETH(encoded); 555 | 556 | assertEq(canFail, expectedCanFail); 557 | assertEq(WETH.unwrap(weth), expectedWeth); 558 | assertEq(value, expectedValue); 559 | } 560 | 561 | function testFuzzDecodeWithdrawWETH( 562 | bool expectedCanFail, 563 | address expectedWeth, 564 | uint8 expectedValue 565 | ) public view { 566 | bytes memory encoded = 567 | BBCEncoder.encodeWithdrawWETH(expectedCanFail, expectedWeth, expectedValue); 568 | 569 | (bool canFail, WETH weth, uint256 value) = decoder.decodeWithdrawWETH(encoded); 570 | 571 | assertEq(canFail, expectedCanFail); 572 | assertEq(WETH.unwrap(weth), expectedWeth); 573 | assertEq(value, expectedValue); 574 | } 575 | 576 | function testDecodeDynCall() public view { 577 | bool expectedCanFail = false; 578 | address expectedTarget = address(0xaabbccdd); 579 | uint256 expectedValue = 0x45; 580 | bytes memory expectedData = hex"deadbeef"; 581 | 582 | bytes memory encoded = BBCEncoder.encodeDynCall( 583 | expectedCanFail, 584 | expectedTarget, 585 | expectedValue, 586 | expectedData 587 | ); 588 | 589 | (bool canFail, address target, uint256 value, bytes memory data) = decoder.decodeDynCall(encoded); 590 | 591 | assertEq(canFail, expectedCanFail); 592 | assertEq(target, expectedTarget); 593 | assertEq(value, expectedValue); 594 | assertEq(keccak256(data), keccak256(expectedData)); 595 | } 596 | 597 | function testFuzzDecodeDynCall( 598 | bool expectedCanFail, 599 | address expectedTarget, 600 | uint256 expectedValue, 601 | bytes memory expectedData 602 | ) public view { 603 | bytes memory encoded = BBCEncoder.encodeDynCall( 604 | expectedCanFail, 605 | expectedTarget, 606 | expectedValue, 607 | expectedData 608 | ); 609 | 610 | (bool canFail, address target, uint256 value, bytes memory data) = decoder.decodeDynCall(encoded); 611 | 612 | assertEq(canFail, expectedCanFail); 613 | assertEq(target, expectedTarget); 614 | assertEq(value, expectedValue); 615 | assertEq(keccak256(data), keccak256(expectedData)); 616 | } 617 | } 618 | -------------------------------------------------------------------------------- /src/util/BBCDecoder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.28; 3 | 4 | import { BytesCalldata } from "src/types/BytesCalldata.sol"; 5 | import { Ptr } from "src/types/PayloadPointer.sol"; 6 | import { ERC20 } from "src/types/protocols/ERC20.sol"; 7 | import { ERC6909 } from "src/types/protocols/ERC6909.sol"; 8 | import { ERC721 } from "src/types/protocols/ERC721.sol"; 9 | import { UniV2Pair } from "src/types/protocols/UniV2Pair.sol"; 10 | import { UniV3Pool } from "src/types/protocols/UniV3Pool.sol"; 11 | import { WETH } from "src/types/protocols/WETH.sol"; 12 | 13 | // ## Decoder 14 | // 15 | // Inspired by the calldata schema of BigBrainChad.eth 16 | // 17 | // ### Encoding Overview 18 | // 19 | // Statically sized calldata arguments of 8 bits or less are encoded in place. 20 | // 21 | // Statically sized calldata arguments of 9 to 256 bits are prefixed with their 22 | // byte length (as an 8 bit integer) followed by the argument, compacted to its 23 | // byte length. This is to handle the common case of the majority of bits being 24 | // unoccupied. 25 | // 26 | // Dynamically sized calldata arguments are prefixed with a 32 bit integer 27 | // indicating its byte length, followed by the bytes themselves. This is worth 28 | // exploring in the future as to whether or not the upper bits of the byte 29 | // length are unoccupied enough to justify an encoding as mentioned in the 30 | // statically sized calldata arguments above. 31 | // 32 | // We maintain a running pointer `Ptr`, which is incremented as parameters are 33 | // parsed from calldata. This is because the encoding scheme is tightly packed 34 | // such that the exact position of subsequent parameters is unknown at compile 35 | // time. We do this to ensure tight calldata encoding, as callata is quite 36 | // expensive. 37 | library BBCDecoder { 38 | uint256 internal constant u8Shr = 0xf8; 39 | uint256 internal constant u32Shr = 0xe0; 40 | 41 | // ## Decode Uniswap V2 Swap 42 | // 43 | // ### Parameters 44 | // 45 | // - ptr: The running pointer. 46 | // 47 | // ### Returns 48 | // 49 | // - nextPtr: The updated pointer. 50 | // - canFail: Boolean indicating whether the call can fail. 51 | // - pair: The Uniswap V2 pair address. 52 | // - amount0Out: The expected output amount for token 0. 53 | // - amount1Out: The expected output amount for token 1. 54 | // - to: The receiver of the swap output. 55 | // - data: The arbitrary calldata for UniV2 callbacks, if any. 56 | function decodeSwapUniV2( 57 | Ptr ptr 58 | ) 59 | internal 60 | pure 61 | returns ( 62 | Ptr nextPtr, 63 | bool canFail, 64 | UniV2Pair pair, 65 | uint256 amount0Out, 66 | uint256 amount1Out, 67 | address to, 68 | BytesCalldata data 69 | ) 70 | { 71 | assembly { 72 | let nextByteLen, nextBitShift 73 | nextPtr := ptr 74 | 75 | canFail := shr(u8Shr, calldataload(nextPtr)) 76 | 77 | nextPtr := add(nextPtr, 0x01) 78 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 79 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 80 | nextPtr := add(nextPtr, 0x01) 81 | 82 | pair := shr(nextBitShift, calldataload(nextPtr)) 83 | 84 | nextPtr := add(nextPtr, nextByteLen) 85 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 86 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 87 | nextPtr := add(nextPtr, 0x01) 88 | 89 | amount0Out := shr(nextBitShift, calldataload(nextPtr)) 90 | 91 | nextPtr := add(nextPtr, nextByteLen) 92 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 93 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 94 | nextPtr := add(nextPtr, 0x01) 95 | 96 | amount1Out := shr(nextBitShift, calldataload(nextPtr)) 97 | 98 | nextPtr := add(nextPtr, nextByteLen) 99 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 100 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 101 | nextPtr := add(nextPtr, 0x01) 102 | 103 | to := shr(nextBitShift, calldataload(nextPtr)) 104 | 105 | nextPtr := add(nextPtr, nextByteLen) 106 | nextByteLen := shr(u32Shr, calldataload(nextPtr)) 107 | 108 | data := nextPtr 109 | 110 | nextPtr := add(nextPtr, 0x04) 111 | 112 | nextPtr := add(nextPtr, nextByteLen) 113 | } 114 | } 115 | 116 | // ## Decode Uniswap V3 Swap 117 | // 118 | // ### Parameters 119 | // 120 | // - ptr: The running pointer. 121 | // 122 | // ### Returns 123 | // 124 | // - nextPtr: The updated pointer. 125 | // - canFail: Boolean indicating whether the call can fail. 126 | // - pool: The Uniswap V3 pool address. 127 | // - recipient: The receiver of the swap output. 128 | // - zeroForOne: Direction of the trade; "true": zero for one, "false": one for zero. 129 | // - amountSpecified: The "exact" portion of the trade amount (More in Notes). 130 | // - sqrtPriceLimitX96: The Q64.96 representation of the price limit. 131 | // - data: The arbitrary calldata for UniV3 callbacks, if any. 132 | function decodeSwapUniV3( 133 | Ptr ptr 134 | ) 135 | internal 136 | pure 137 | returns ( 138 | Ptr nextPtr, 139 | bool canFail, 140 | UniV3Pool pool, 141 | address recipient, 142 | bool zeroForOne, 143 | int256 amountSpecified, 144 | uint160 sqrtPriceLimitX96, 145 | BytesCalldata data 146 | ) 147 | { 148 | assembly { 149 | let nextByteLen, nextBitShift 150 | nextPtr := ptr 151 | 152 | canFail := shr(u8Shr, calldataload(nextPtr)) 153 | 154 | nextPtr := add(nextPtr, 0x01) 155 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 156 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 157 | nextPtr := add(nextPtr, 0x01) 158 | 159 | pool := shr(nextBitShift, calldataload(nextPtr)) 160 | 161 | nextPtr := add(nextPtr, nextByteLen) 162 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 163 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 164 | nextPtr := add(nextPtr, 0x01) 165 | 166 | recipient := shr(nextBitShift, calldataload(nextPtr)) 167 | 168 | nextPtr := add(nextPtr, nextByteLen) 169 | 170 | zeroForOne := shr(u8Shr, calldataload(nextPtr)) 171 | 172 | nextPtr := add(nextPtr, 0x01) 173 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 174 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 175 | nextPtr := add(nextPtr, 0x01) 176 | 177 | amountSpecified := shr(nextBitShift, calldataload(nextPtr)) 178 | amountSpecified := signextend(sub(nextByteLen, 0x01), amountSpecified) 179 | 180 | nextPtr := add(nextPtr, nextByteLen) 181 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 182 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 183 | nextPtr := add(nextPtr, 0x01) 184 | 185 | sqrtPriceLimitX96 := shr(nextBitShift, calldataload(nextPtr)) 186 | 187 | nextPtr := add(nextPtr, nextByteLen) 188 | nextByteLen := shr(u32Shr, calldataload(nextPtr)) 189 | 190 | data := nextPtr 191 | 192 | nextPtr := add(nextPtr, 0x04) 193 | 194 | nextPtr := add(nextPtr, nextByteLen) 195 | } 196 | } 197 | 198 | // ## Decode Uniswap V3 Flash Loan 199 | // 200 | // ### Parameters 201 | // 202 | // - ptr: The running pointer. 203 | // 204 | // ### Returns 205 | // 206 | // - nextPtr: The updated pointer. 207 | // - canFail: Boolean indicating whether the call can fail. 208 | // - pool: The Uniswap V3 pool address. 209 | // - recipient: The receiver of the flash output. 210 | // - amount0: The amount of Token 0 to flash. 211 | // - amount1: The amount of Token 1 to flash. 212 | // - data: The arbitrary calldata for UniV3 callbacks, if any. 213 | function decodeFlashUniV3( 214 | Ptr ptr 215 | ) 216 | internal 217 | pure 218 | returns ( 219 | Ptr nextPtr, 220 | bool canFail, 221 | UniV3Pool pool, 222 | address recipient, 223 | uint256 amount0, 224 | uint256 amount1, 225 | BytesCalldata data 226 | ) 227 | { 228 | assembly { 229 | let nextByteLen, nextBitShift 230 | nextPtr := ptr 231 | 232 | canFail := shr(u8Shr, calldataload(nextPtr)) 233 | 234 | nextPtr := add(nextPtr, 0x01) 235 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 236 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 237 | nextPtr := add(nextPtr, 0x01) 238 | 239 | pool := shr(nextBitShift, calldataload(nextPtr)) 240 | 241 | nextPtr := add(nextPtr, nextByteLen) 242 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 243 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 244 | nextPtr := add(nextPtr, 0x01) 245 | 246 | recipient := shr(nextBitShift, calldataload(nextPtr)) 247 | 248 | nextPtr := add(nextPtr, nextByteLen) 249 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 250 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 251 | nextPtr := add(nextPtr, 0x01) 252 | 253 | amount0 := shr(nextBitShift, calldataload(nextPtr)) 254 | 255 | nextPtr := add(nextPtr, nextByteLen) 256 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 257 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 258 | nextPtr := add(nextPtr, 0x01) 259 | 260 | amount1 := shr(nextBitShift, calldataload(nextPtr)) 261 | 262 | nextPtr := add(nextPtr, nextByteLen) 263 | nextByteLen := shr(u32Shr, calldataload(nextPtr)) 264 | 265 | data := nextPtr 266 | 267 | nextPtr := add(nextPtr, 0x04) 268 | 269 | nextPtr := add(nextPtr, nextByteLen) 270 | } 271 | } 272 | 273 | // ## Decode ERC20 Transfer 274 | // 275 | // ### Parameters 276 | // 277 | // - ptr: The running pointer. 278 | // 279 | // ### Returns 280 | // 281 | // - nextPtr: The updated pointer. 282 | // - canFail: Boolean indicating whether the call can fail. 283 | // - token: The ERC20 address. 284 | // - receiver: The transfer receiver address. 285 | // - amount: The transfer amount. 286 | function decodeTransferERC20( 287 | Ptr ptr 288 | ) 289 | internal 290 | pure 291 | returns (Ptr nextPtr, bool canFail, ERC20 token, address receiver, uint256 amount) 292 | { 293 | assembly { 294 | let nextByteLen, nextBitShift 295 | nextPtr := ptr 296 | 297 | canFail := shr(u8Shr, calldataload(nextPtr)) 298 | 299 | nextPtr := add(nextPtr, 0x01) 300 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 301 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 302 | nextPtr := add(nextPtr, 0x01) 303 | 304 | token := shr(nextBitShift, calldataload(nextPtr)) 305 | 306 | nextPtr := add(nextPtr, nextByteLen) 307 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 308 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 309 | nextPtr := add(nextPtr, 0x01) 310 | 311 | receiver := shr(nextBitShift, calldataload(nextPtr)) 312 | 313 | nextPtr := add(nextPtr, nextByteLen) 314 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 315 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 316 | nextPtr := add(nextPtr, 0x01) 317 | 318 | amount := shr(nextBitShift, calldataload(nextPtr)) 319 | 320 | nextPtr := add(nextPtr, nextByteLen) 321 | } 322 | } 323 | 324 | // ## Decode ERC20 TransferFrom 325 | // 326 | // ### Parameters 327 | // 328 | // - ptr: The running pointer. 329 | // 330 | // ### Returns 331 | // 332 | // - nextPtr: The updated pointer. 333 | // - canFail: Boolean indicating whether the call can fail. 334 | // - token: The ERC20 address. 335 | // - sender: The transfer sender address. 336 | // - receiver: The transfer receiver address. 337 | // - amount: The transfer amount. 338 | function decodeTransferFromERC20( 339 | Ptr ptr 340 | ) 341 | internal 342 | pure 343 | returns ( 344 | Ptr nextPtr, 345 | bool canFail, 346 | ERC20 token, 347 | address sender, 348 | address receiver, 349 | uint256 amount 350 | ) 351 | { 352 | assembly { 353 | let nextByteLen, nextBitShift 354 | nextPtr := ptr 355 | 356 | canFail := shr(u8Shr, calldataload(nextPtr)) 357 | 358 | nextPtr := add(nextPtr, 0x01) 359 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 360 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 361 | nextPtr := add(nextPtr, 0x01) 362 | 363 | token := shr(nextBitShift, calldataload(nextPtr)) 364 | 365 | nextPtr := add(nextPtr, nextByteLen) 366 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 367 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 368 | nextPtr := add(nextPtr, 0x01) 369 | 370 | sender := shr(nextBitShift, calldataload(nextPtr)) 371 | 372 | nextPtr := add(nextPtr, nextByteLen) 373 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 374 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 375 | nextPtr := add(nextPtr, 0x01) 376 | 377 | receiver := shr(nextBitShift, calldataload(nextPtr)) 378 | 379 | nextPtr := add(nextPtr, nextByteLen) 380 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 381 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 382 | nextPtr := add(nextPtr, 0x01) 383 | 384 | amount := shr(nextBitShift, calldataload(nextPtr)) 385 | 386 | nextPtr := add(nextPtr, nextByteLen) 387 | } 388 | } 389 | 390 | // ## Decode ERC721 TransferFrom 391 | // 392 | // ### Parameters 393 | // 394 | // - ptr: The running pointer. 395 | // 396 | // ### Returns 397 | // 398 | // - nextPtr: The updated pointer. 399 | // - canFail: Boolean indicating whether the call can fail. 400 | // - token: The ERC721 address. 401 | // - sender: The transfer sender address. 402 | // - receiver: The transfer receiver address. 403 | // - tokenId: The token ID to transfer. 404 | function decodeTransferFromERC721( 405 | Ptr ptr 406 | ) 407 | internal 408 | pure 409 | returns ( 410 | Ptr nextPtr, 411 | bool canFail, 412 | ERC721 token, 413 | address sender, 414 | address receiver, 415 | uint256 tokenId 416 | ) 417 | { 418 | assembly { 419 | let nextByteLen, nextBitShift 420 | nextPtr := ptr 421 | 422 | canFail := shr(u8Shr, calldataload(nextPtr)) 423 | 424 | nextPtr := add(nextPtr, 0x01) 425 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 426 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 427 | nextPtr := add(nextPtr, 0x01) 428 | 429 | token := shr(nextBitShift, calldataload(nextPtr)) 430 | 431 | nextPtr := add(nextPtr, nextByteLen) 432 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 433 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 434 | nextPtr := add(nextPtr, 0x01) 435 | 436 | sender := shr(nextBitShift, calldataload(nextPtr)) 437 | 438 | nextPtr := add(nextPtr, nextByteLen) 439 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 440 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 441 | nextPtr := add(nextPtr, 0x01) 442 | 443 | receiver := shr(nextBitShift, calldataload(nextPtr)) 444 | 445 | nextPtr := add(nextPtr, nextByteLen) 446 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 447 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 448 | nextPtr := add(nextPtr, 0x01) 449 | 450 | tokenId := shr(nextBitShift, calldataload(nextPtr)) 451 | 452 | nextPtr := add(nextPtr, nextByteLen) 453 | } 454 | } 455 | 456 | // ## Decode ERC6909 Transfer 457 | // 458 | // ### Parameters 459 | // 460 | // - ptr: The running pointer. 461 | // 462 | // ### Returns 463 | // 464 | // - nextPtr: The updated pointer. 465 | // - canFail: Boolean indicating whether the call can fail. 466 | // - multitoken: The ERC6909 address. 467 | // - receiver: The transfer receiver address. 468 | // - amount: The amount to transfer. 469 | // - tokenId: The token ID to transfer. 470 | function decodeTransferERC6909( 471 | Ptr ptr 472 | ) 473 | internal 474 | pure 475 | returns ( 476 | Ptr nextPtr, 477 | bool canFail, 478 | ERC6909 multitoken, 479 | address receiver, 480 | uint256 tokenId, 481 | uint256 amount 482 | ) 483 | { 484 | assembly { 485 | let nextByteLen, nextBitShift 486 | nextPtr := ptr 487 | 488 | canFail := shr(u8Shr, calldataload(nextPtr)) 489 | 490 | nextPtr := add(nextPtr, 0x01) 491 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 492 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 493 | nextPtr := add(nextPtr, 0x01) 494 | 495 | multitoken := shr(nextBitShift, calldataload(nextPtr)) 496 | 497 | nextPtr := add(nextPtr, nextByteLen) 498 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 499 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 500 | nextPtr := add(nextPtr, 0x01) 501 | 502 | receiver := shr(nextBitShift, calldataload(nextPtr)) 503 | 504 | nextPtr := add(nextPtr, nextByteLen) 505 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 506 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 507 | nextPtr := add(nextPtr, 0x01) 508 | 509 | tokenId := shr(nextBitShift, calldataload(nextPtr)) 510 | 511 | nextPtr := add(nextPtr, nextByteLen) 512 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 513 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 514 | nextPtr := add(nextPtr, 0x01) 515 | 516 | amount := shr(nextBitShift, calldataload(nextPtr)) 517 | 518 | nextPtr := add(nextPtr, nextByteLen) 519 | } 520 | } 521 | 522 | // ## Decode ERC6909 TransferFrom 523 | // 524 | // ### Parameters 525 | // 526 | // - ptr: The running pointer. 527 | // 528 | // ### Returns 529 | // 530 | // - nextPtr: The updated pointer. 531 | // - canFail: Boolean indicating whether the call can fail. 532 | // - multitoken: The ERC6909 address. 533 | // - sender: The transfer sender address. 534 | // - receiver: The transfer receiver address. 535 | // - amount: The amount to transfer. 536 | // - tokenId: The token ID to transfer. 537 | function decodeTransferFromERC6909( 538 | Ptr ptr 539 | ) 540 | internal 541 | pure 542 | returns ( 543 | Ptr nextPtr, 544 | bool canFail, 545 | ERC6909 multitoken, 546 | address sender, 547 | address receiver, 548 | uint256 tokenId, 549 | uint256 amount 550 | ) 551 | { 552 | assembly { 553 | let nextByteLen, nextBitShift 554 | nextPtr := ptr 555 | 556 | canFail := shr(u8Shr, calldataload(nextPtr)) 557 | 558 | nextPtr := add(nextPtr, 0x01) 559 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 560 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 561 | nextPtr := add(nextPtr, 0x01) 562 | 563 | multitoken := shr(nextBitShift, calldataload(nextPtr)) 564 | 565 | nextPtr := add(nextPtr, nextByteLen) 566 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 567 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 568 | nextPtr := add(nextPtr, 0x01) 569 | 570 | sender := shr(nextBitShift, calldataload(nextPtr)) 571 | 572 | nextPtr := add(nextPtr, nextByteLen) 573 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 574 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 575 | nextPtr := add(nextPtr, 0x01) 576 | 577 | receiver := shr(nextBitShift, calldataload(nextPtr)) 578 | 579 | nextPtr := add(nextPtr, nextByteLen) 580 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 581 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 582 | nextPtr := add(nextPtr, 0x01) 583 | 584 | tokenId := shr(nextBitShift, calldataload(nextPtr)) 585 | 586 | nextPtr := add(nextPtr, nextByteLen) 587 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 588 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 589 | nextPtr := add(nextPtr, 0x01) 590 | 591 | amount := shr(nextBitShift, calldataload(nextPtr)) 592 | 593 | nextPtr := add(nextPtr, nextByteLen) 594 | } 595 | } 596 | 597 | // ## Decode WETH Deposit 598 | // 599 | // ### Parameters 600 | // 601 | // - ptr: The running pointer. 602 | // 603 | // ### Returns 604 | // 605 | // - nextPtr: The updated pointer. 606 | // - canFail: Boolean indicating whether the call can fail. 607 | // - weth: The WETH address. 608 | // - value: The amount to deposit. 609 | function decodeDepositWETH( 610 | Ptr ptr 611 | ) internal pure returns (Ptr nextPtr, bool canFail, WETH weth, uint256 value) { 612 | assembly { 613 | let nextByteLen, nextBitShift 614 | nextPtr := ptr 615 | 616 | canFail := shr(u8Shr, calldataload(nextPtr)) 617 | 618 | nextPtr := add(nextPtr, 0x01) 619 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 620 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 621 | nextPtr := add(nextPtr, 0x01) 622 | 623 | weth := shr(nextBitShift, calldataload(nextPtr)) 624 | 625 | nextPtr := add(nextPtr, nextByteLen) 626 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 627 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 628 | nextPtr := add(nextPtr, 0x01) 629 | 630 | value := shr(nextBitShift, calldataload(nextPtr)) 631 | 632 | nextPtr := add(nextPtr, nextByteLen) 633 | } 634 | } 635 | 636 | // ## Decode WETH Withdrawal 637 | // 638 | // ### Parameters 639 | // 640 | // - ptr: The running pointer. 641 | // 642 | // ### Returns 643 | // 644 | // - nextPtr: The updated pointer. 645 | // - canFail: Boolean indicating whether the call can fail. 646 | // - weth: The WETH address. 647 | // - value: The amount to withdraw. 648 | function decodeWithdrawWETH( 649 | Ptr ptr 650 | ) internal pure returns (Ptr nextPtr, bool canFail, WETH weth, uint256 value) { 651 | assembly { 652 | let nextByteLen, nextBitShift 653 | nextPtr := ptr 654 | 655 | canFail := shr(u8Shr, calldataload(nextPtr)) 656 | 657 | nextPtr := add(nextPtr, 0x01) 658 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 659 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 660 | nextPtr := add(nextPtr, 0x01) 661 | 662 | weth := shr(nextBitShift, calldataload(nextPtr)) 663 | 664 | nextPtr := add(nextPtr, nextByteLen) 665 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 666 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 667 | nextPtr := add(nextPtr, 0x01) 668 | 669 | value := shr(nextBitShift, calldataload(nextPtr)) 670 | 671 | nextPtr := add(nextPtr, nextByteLen) 672 | } 673 | } 674 | 675 | // ## Decode Dynamic Contract Call 676 | // 677 | // ### Parameters 678 | // 679 | // - ptr: The running pointer. 680 | // 681 | // ### Returns 682 | // 683 | // - nextPtr: The updated pointer. 684 | // - canFail: Boolean indicating whether the call can fail. 685 | // - target: The call target address. 686 | // - value: The call value. 687 | // - data: The call payload. 688 | function decodeDynCall( 689 | Ptr ptr 690 | ) 691 | internal 692 | pure 693 | returns (Ptr nextPtr, bool canFail, address target, uint256 value, BytesCalldata data) 694 | { 695 | assembly { 696 | let nextByteLen, nextBitShift 697 | nextPtr := ptr 698 | 699 | canFail := shr(u8Shr, calldataload(nextPtr)) 700 | 701 | nextPtr := add(nextPtr, 0x01) 702 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 703 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 704 | nextPtr := add(nextPtr, 0x01) 705 | 706 | target := shr(nextBitShift, calldataload(nextPtr)) 707 | 708 | nextPtr := add(nextPtr, nextByteLen) 709 | nextByteLen := shr(u8Shr, calldataload(nextPtr)) 710 | nextBitShift := sub(0x0100, mul(0x08, nextByteLen)) 711 | nextPtr := add(nextPtr, 0x01) 712 | 713 | value := shr(nextBitShift, calldataload(nextPtr)) 714 | 715 | nextPtr := add(nextPtr, nextByteLen) 716 | nextByteLen := shr(u32Shr, calldataload(nextPtr)) 717 | 718 | data := nextPtr 719 | 720 | nextPtr := add(nextPtr, 0x04) 721 | 722 | nextPtr := add(nextPtr, nextByteLen) 723 | } 724 | } 725 | } 726 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | --------------------------------------------------------------------------------