├── .gitmodules ├── README.md ├── contracts ├── .github │ └── workflows │ │ └── test.yml ├── .gitignore ├── foundry.toml ├── remappings.txt ├── script │ └── Counter.s.sol ├── src │ ├── OptimismBlockCache.sol │ ├── SunflowerSafePlugin.sol │ └── utils │ │ ├── CheckSignatures.sol │ │ └── ProofParser.sol └── test │ ├── ProofParser.t.sol │ └── SunflowerSafePlugin.t.sol ├── frontend ├── .gitignore ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── ethers.ts │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ └── setupTests.ts ├── tsconfig.json └── yarn.lock └── server ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── configs ├── mainnet_1.json ├── mainnet_10.json ├── mainnet_10_evm.json ├── mainnet_1_evm.json ├── mainnet_2.json ├── mainnet_2_evm.json ├── mainnet_3.json ├── mainnet_3_evm.json ├── mainnet_4.json ├── mainnet_4_evm.json ├── mainnet_5.json ├── mainnet_5_evm.json ├── mainnet_6.json └── mainnet_6_evm.json ├── rust-toolchain └── src ├── main.rs ├── prover.rs └── slots.rs /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "contracts/lib/forge-std"] 2 | path = contracts/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "contracts/lib/safe-core-protocol"] 5 | path = contracts/lib/safe-core-protocol 6 | url = https://github.com/5afe/safe-core-protocol 7 | [submodule "contracts/lib/safe-contracts"] 8 | path = contracts/lib/safe-contracts 9 | url = https://github.com/safe-global/safe-contracts 10 | [submodule "contracts/lib/safe-core-protocol-demo"] 11 | path = contracts/lib/safe-core-protocol-demo 12 | url = https://github.com/5afe/safe-core-protocol-demo 13 | [submodule "contracts/lib/openzeppelin-contracts"] 14 | path = contracts/lib/openzeppelin-contracts 15 | url = https://github.com/openzeppelin/openzeppelin-contracts 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Trustless-3 copy](https://github.com/zemse/sunflower/assets/22412996/ae804c27-8be9-4d2b-bf05-203e9d3a5fc8) 2 | 3 | # sunflower 4 | 5 | this project is a gnosis safe plugin that is used by a multi-sig on L2 to inherit ownership of a multi-sig on L1. 6 | 7 | ethglobal submission link: https://ethglobal.com/showcase/sunflower-ogbj9 8 | 9 | the optimism L2 has a precompile which gives access to L1's block hash. this allows access to the execution details of L1. however, doing MPT proofs + RLP can be costly in terms of computation and call data size. hence, in this hack, i am using open-source code developed by the axiom team, along with some modifications to provide a zk proof of storage slots given a block hash. 10 | 11 | ## server 12 | 13 | a rocket-rs backend server which accepts REST API requests for the endpoint `/gen_proof?address=` that generates proofs given an address of L1 gnosis multi-sig. from the multisig address, the storage slot keys are calculated based on the storage layout of gnosis multisig (which uses a mapping) and then these are used with axiom circuits to generate a proof of its value. 14 | 15 | to run the backend server: 16 | 17 | ``` 18 | cd server 19 | 20 | PROVER_PRIVATE_KEY= DEBUG=true OPTIMISM_RPC_URL= JSON_RPC_URL= cargo run --release 21 | ``` 22 | 23 | ## contracts 24 | 25 | this is solidity code that parses the proofs and the plugin business logic. there is a `OptimismBlockCache` contract which is called everytime prover is generating proof to pin the L1 block hash on optimism L2 network so it is accessable when user is verifying the proof. the `SunflowerSafePlugin` uses the plonk verifier generated through axiom circuits to check if the proof is valid and then parses the public instances to get owner list and threshold. following that the ordinary signature verification code from gnosis safe contracts is used. 26 | 27 | to run the test cases: 28 | 29 | ``` 30 | cd contracts 31 | 32 | forge test 33 | ``` 34 | 35 | for deploying contracts i've just used `forge flatten ` + remix. 36 | 37 | ## running the frontend 38 | 39 | demo UI to interact with the plugin on an optimism L2 multi-sig. frontend depends rust backend for generating a proof. cors checks are required to be disabled in the browser bcz i didn't have time to deal with fixing cors during hackathon. 40 | 41 | ``` 42 | cd frontend 43 | 44 | yarn start 45 | ``` 46 | 47 | ## license 48 | 49 | MIT 50 | -------------------------------------------------------------------------------- /contracts/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /contracts/.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 | -------------------------------------------------------------------------------- /contracts/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | solc = "0.8.19" 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 7 | -------------------------------------------------------------------------------- /contracts/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ 3 | forge-std/=lib/forge-std/src/ 4 | openzeppelin-contracts/=lib/openzeppelin-contracts/ 5 | safe-contracts/=lib/safe-contracts/ 6 | safe-core-protocol-demo/=lib/safe-core-protocol-demo/ 7 | safe-core-protocol/=lib/safe-core-protocol/contracts/ 8 | lib/forge-std:ds-test/=lib/forge-std/lib/ds-test/src/ 9 | lib/openzeppelin-contracts:ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/ 10 | lib/openzeppelin-contracts:erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ 11 | lib/openzeppelin-contracts:forge-std/=lib/openzeppelin-contracts/lib/forge-std/src/ 12 | lib/openzeppelin-contracts:openzeppelin/=lib/openzeppelin-contracts/contracts/ 13 | @safe-global/safe-contracts/=lib/safe-contracts/contracts/ 14 | @safe-global/safe-core-protocol/=lib/safe-core-protocol/ 15 | @5afe/safe-core-protocol-demo/=lib/safe-core-protocol-demo/contracts/ 16 | @openzeppelin/contracts=lib/openzeppelin-contracts/contracts/ -------------------------------------------------------------------------------- /contracts/script/Counter.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | contract CounterScript is Script { 7 | function setUp() public {} 8 | 9 | function run() public { 10 | vm.broadcast(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/src/OptimismBlockCache.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | /// @title OptimismBlockCache 6 | /// @notice This is required because proof gen takes couple mins, and block hash returned by the 7 | /// the precompile is updated in the meantime. Hence we need to cache the block hash on which 8 | /// the prover will use for generating the zk proof. 9 | contract OptimismBlockCache { 10 | L1BlockPrecompile precompileL1Block = 11 | L1BlockPrecompile(0x4200000000000000000000000000000000000015); 12 | 13 | mapping(bytes32 blockHash => uint64 timestamp) public getTimestamp; 14 | 15 | event Block(uint64 number, uint64 timestamp, bytes32 hash); 16 | 17 | /// @notice Anyone can call this function to cache the block hash on Optimism 18 | /// @dev This is a temporary solution and in this PoC the prover is calling this function, 19 | /// it will be best if L2s can somehow make recent L1 block hashes available. 20 | function hit() external { 21 | uint64 number = precompileL1Block.number(); 22 | uint64 timestamp = precompileL1Block.timestamp(); 23 | bytes32 hash = precompileL1Block.hash(); 24 | 25 | getTimestamp[hash] = timestamp; 26 | 27 | emit Block(number, timestamp, hash); 28 | } 29 | } 30 | 31 | // https://community.optimism.io/docs/protocol/protocol-2.0/#l1block 32 | interface L1BlockPrecompile { 33 | function number() external view returns (uint64); 34 | 35 | function timestamp() external view returns (uint64); 36 | 37 | function hash() external view returns (bytes32); 38 | } 39 | -------------------------------------------------------------------------------- /contracts/src/SunflowerSafePlugin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import {ISafe} from "@safe-global/safe-core-protocol/contracts/interfaces/Accounts.sol"; 6 | import {ISafeProtocolManager} from "@safe-global/safe-core-protocol/contracts/interfaces/Manager.sol"; 7 | import {ISafeProtocolPlugin} from "@safe-global/safe-core-protocol/contracts/interfaces/Integrations.sol"; 8 | import {SafeProtocolAction, SafeTransaction, SafeRootAccess} from "@safe-global/safe-core-protocol/contracts/DataTypes.sol"; 9 | 10 | import {BasePluginWithEventMetadata, PluginMetadata} from "@5afe/safe-core-protocol-demo/contracts/Base.sol"; 11 | 12 | import {CheckSignatures} from "./utils/CheckSignatures.sol"; 13 | import {ProofParser} from "./utils/ProofParser.sol"; 14 | import {OptimismBlockCache} from "./OptimismBlockCache.sol"; 15 | 16 | /// @title The Sunflower plugin for Gnosis Safe 17 | /// @notice This plugin is to be used for safes on L2 to use owners on L1 using zk proofs. 18 | contract SunflowerSafePlugin is 19 | ISafeProtocolPlugin, 20 | BasePluginWithEventMetadata, 21 | CheckSignatures, 22 | ProofParser 23 | { 24 | // Cache is used to prevent using zk proofs to prove the list of owners for every action. 25 | // After a long time, zk proof will be used to read owners from L1, however to reduce costs 26 | // some expiry like 6 hours can be used so that the Safe users can perform back to back 27 | // actions without using zk proofs every time. 28 | struct L1SafeOwnersCache { 29 | uint128 ttl; 30 | uint128 l1BlockTimestamp; 31 | address[] owners; 32 | } 33 | mapping(ISafe => L1SafeOwnersCache) public ownersCache; 34 | 35 | // Contains valid block data 36 | OptimismBlockCache blockCache; 37 | 38 | uint public pluginNonce; 39 | 40 | constructor( 41 | OptimismBlockCache blockCache_, 42 | address plonkVerifier 43 | ) 44 | BasePluginWithEventMetadata( 45 | PluginMetadata({ 46 | name: "Sunflower Plugin", 47 | version: "0.0.1", 48 | requiresRootAccess: true, 49 | iconUrl: "", 50 | appUrl: "https://5afe.github.io/safe-core-protocol-demo/#/relay/${plugin}" 51 | }) 52 | ) 53 | ProofParser(plonkVerifier) 54 | { 55 | blockCache = blockCache_; 56 | } 57 | 58 | // inputs transaction & claimed owners & prooof & owner signatures 59 | function executeTransaction( 60 | ISafeProtocolManager manager, 61 | ISafe safe, 62 | SafeProtocolAction calldata action, 63 | uint8 operation, 64 | bytes[] calldata zkProof, 65 | bytes calldata l1OwnerSignatures 66 | ) external { 67 | address[] memory owners; 68 | uint threshold; 69 | 70 | if (zkProof.length != 0) { 71 | bytes32 blockHash; 72 | address account; 73 | 74 | // verifies zk proof and parses the instances 75 | (blockHash, , account, , threshold, owners) = parse(zkProof); 76 | 77 | // check block hash 78 | uint64 l1BlockTimestamp = blockCache.getTimestamp(blockHash); 79 | require(l1BlockTimestamp != 0, "block not cached"); 80 | require( 81 | l1BlockTimestamp + 2 hours > block.timestamp, 82 | "too old block" 83 | ); 84 | 85 | // save to cache 86 | require( 87 | ownersCache[safe].l1BlockTimestamp == 0 || 88 | l1BlockTimestamp > ownersCache[safe].l1BlockTimestamp, 89 | "more recent block already in cache" 90 | ); 91 | } else { 92 | require( 93 | _l2CurrentTimestamp() < 94 | ownersCache[safe].l1BlockTimestamp + ownersCache[safe].ttl 95 | ); 96 | owners = ownersCache[safe].owners; 97 | } 98 | 99 | { 100 | uint currentNonce = pluginNonce++; 101 | 102 | bytes memory encodedTx = encodeTx({ 103 | to: action.to, 104 | value: action.value, 105 | data: action.data, 106 | operation: 0, 107 | chainId: getChainId(), 108 | nonce: currentNonce 109 | }); 110 | 111 | bytes32 dataHash = keccak256(encodedTx); 112 | 113 | // check if the signatures are from the L1 owners 114 | checkSignatures( 115 | threshold, 116 | dataHash, 117 | encodedTx, 118 | l1OwnerSignatures, 119 | owners 120 | ); 121 | } 122 | 123 | // execute transaction 124 | if (address(manager) != address(0)) { 125 | // go through the manager as a mediator plugin. 126 | // requires this plugin to be whitelisted in the manager plugin. 127 | uint256 safeNonce = uint256( 128 | keccak256(abi.encode(this, manager, safe, action)) 129 | ); 130 | if (operation == 0) { 131 | SafeProtocolAction[] memory actions = new SafeProtocolAction[]( 132 | 1 133 | ); 134 | actions[0] = action; 135 | manager.executeTransaction( 136 | safe, 137 | SafeTransaction(actions, safeNonce, bytes32(0)) 138 | ); 139 | } else if (operation == 1) { 140 | manager.executeRootAccess( 141 | safe, 142 | SafeRootAccess(action, safeNonce, bytes32(0)) 143 | ); 144 | } else { 145 | revert("invalid opr"); 146 | } 147 | } else { 148 | // directly approach the gnosis safe. 149 | // requires this plugin to be whitelisted in the gnosis safe. 150 | safe.execTransactionFromModule( 151 | action.to, 152 | action.value, 153 | action.data, 154 | operation 155 | ); 156 | } 157 | } 158 | 159 | // keccak256("SunflowerSafePluginEthParis") 160 | bytes32 constant domainSeparator = 161 | 0x6c426e77058bfdeef67364197b83db6647aa1e9fad867312eae40d220d014ba4; 162 | 163 | function encodeTx( 164 | address to, 165 | uint256 value, 166 | bytes calldata data, 167 | uint8 operation, 168 | uint256 chainId, 169 | uint256 nonce 170 | ) public pure returns (bytes memory) { 171 | bytes32 safeTxHash = keccak256( 172 | abi.encode(to, value, keccak256(data), operation, nonce) 173 | ); 174 | return 175 | abi.encodePacked( 176 | bytes1(0x19), 177 | bytes1(0x01), 178 | domainSeparator, 179 | chainId, 180 | safeTxHash 181 | ); 182 | } 183 | 184 | /// @notice Allows overriding this function to use different L2s requirements. 185 | function _l2CurrentTimestamp() public view virtual returns (uint) { 186 | return block.timestamp; 187 | } 188 | 189 | function getChainId() public view returns (uint id) { 190 | assembly { 191 | id := chainid() 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /contracts/src/utils/CheckSignatures.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import {ISignatureValidator, ISignatureValidatorConstants} from "@safe-global/safe-contracts/interfaces/ISignatureValidator.sol"; 6 | import {SignatureDecoder} from "@safe-global/safe-contracts/common/SignatureDecoder.sol"; 7 | import {SafeMath} from "@safe-global/safe-contracts/external/SafeMath.sol"; 8 | 9 | contract CheckSignatures is SignatureDecoder, ISignatureValidatorConstants { 10 | using SafeMath for uint256; 11 | 12 | address internal constant SENTINEL_OWNERS = address(0x1); 13 | 14 | /** 15 | * @notice Checks whether the signature provided is valid for the provided data and hash. Reverts otherwise. 16 | * @param threshold The threshold value of signatures 17 | * @param dataHash Hash of the data (could be either a message hash or transaction hash) 18 | * @param data That should be signed (this is passed to an external validator contract) 19 | * @param signatures Signature data that should be verified. 20 | * Can be packed ECDSA signature ({bytes32 r}{bytes32 s}{uint8 v}), contract signature (EIP-1271) or approved hash. 21 | * @param owners List of the owners 22 | */ 23 | function checkSignatures( 24 | uint256 threshold, 25 | bytes32 dataHash, 26 | bytes memory data, 27 | bytes memory signatures, 28 | address[] memory owners 29 | ) public view { 30 | // Check that a threshold is set 31 | require(threshold > 0, "GS001"); 32 | checkNSignatures(dataHash, data, signatures, owners, threshold); 33 | } 34 | 35 | /** 36 | * @notice Checks whether the signature provided is valid for the provided data and hash. Reverts otherwise. 37 | * @dev Since the EIP-1271 does an external call, be mindful of reentrancy attacks. 38 | * @param dataHash Hash of the data (could be either a message hash or transaction hash) 39 | * @param data That should be signed (this is passed to an external validator contract) 40 | * @param signatures Signature data that should be verified. 41 | * Can be packed ECDSA signature ({bytes32 r}{bytes32 s}{uint8 v}), contract signature (EIP-1271) or approved hash. 42 | * @param owners List of the owners 43 | * @param requiredSignatures Amount of required valid signatures. 44 | */ 45 | function checkNSignatures( 46 | bytes32 dataHash, 47 | bytes memory data, 48 | bytes memory signatures, 49 | address[] memory owners, 50 | uint256 requiredSignatures 51 | ) public view { 52 | // Check that the provided signature data is not too short 53 | require(signatures.length >= requiredSignatures.mul(65), "GS020"); 54 | // There cannot be an owner with address 0. 55 | address lastOwner = address(0); 56 | address currentOwner; 57 | uint8 v; 58 | bytes32 r; 59 | bytes32 s; 60 | uint256 i; 61 | uint256 o; 62 | for (i = 0; i < requiredSignatures; i++) { 63 | (v, r, s) = signatureSplit(signatures, i); 64 | if (v == 0) { 65 | require(keccak256(data) == dataHash, "GS027"); 66 | // If v is 0 then it is a contract signature 67 | // When handling contract signatures the address of the contract is encoded into r 68 | currentOwner = address(uint160(uint256(r))); 69 | 70 | // Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes 71 | // This check is not completely accurate, since it is possible that more signatures than the threshold are send. 72 | // Here we only check that the pointer is not pointing inside the part that is being processed 73 | require(uint256(s) >= requiredSignatures.mul(65), "GS021"); 74 | 75 | // Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes) 76 | require(uint256(s).add(32) <= signatures.length, "GS022"); 77 | 78 | // Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length 79 | uint256 contractSignatureLen; 80 | // solhint-disable-next-line no-inline-assembly 81 | assembly { 82 | contractSignatureLen := mload(add(add(signatures, s), 0x20)) 83 | } 84 | require( 85 | uint256(s).add(32).add(contractSignatureLen) <= 86 | signatures.length, 87 | "GS023" 88 | ); 89 | 90 | // Check signature 91 | bytes memory contractSignature; 92 | // solhint-disable-next-line no-inline-assembly 93 | assembly { 94 | // The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s 95 | contractSignature := add(add(signatures, s), 0x20) 96 | } 97 | require( 98 | ISignatureValidator(currentOwner).isValidSignature( 99 | data, 100 | contractSignature 101 | ) == EIP1271_MAGIC_VALUE, 102 | "GS024" 103 | ); 104 | } else if (v == 1) { 105 | revert("approved hash not supported currently"); 106 | // // If v is 1 then it is an approved hash 107 | // // When handling approved hashes the address of the approver is encoded into r 108 | // currentOwner = address(uint160(uint256(r))); 109 | // // Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction 110 | // require( 111 | // msg.sender == currentOwner || 112 | // approvedHashes[currentOwner][dataHash] != 0, 113 | // "GS025" 114 | // ); 115 | } else if (v > 30) { 116 | // If v > 30 then default va (27,28) has been adjusted for eth_sign flow 117 | // To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover 118 | currentOwner = ecrecover( 119 | keccak256( 120 | abi.encodePacked( 121 | "\x19Ethereum Signed Message:\n32", 122 | dataHash 123 | ) 124 | ), 125 | v - 4, 126 | r, 127 | s 128 | ); 129 | } else { 130 | // Default is the ecrecover flow with the provided data hash 131 | // Use ecrecover with the messageHash for EOA signatures 132 | currentOwner = ecrecover(dataHash, v, r, s); 133 | } 134 | 135 | while (owners[o] != currentOwner) { 136 | o++; 137 | } 138 | 139 | require( 140 | currentOwner > lastOwner && 141 | owners[o] == currentOwner && 142 | currentOwner != SENTINEL_OWNERS, 143 | "GS026" 144 | ); 145 | lastOwner = currentOwner; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /contracts/src/utils/ProofParser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | uint constant SLOTS_PER_PROOF = 10; 6 | 7 | uint constant SENTINEL_OWNERS = 0x1; 8 | uint constant PLACEHOLDER_SLOT = 0; 9 | uint constant OWNERS_MAPPING_SLOT = 2; 10 | uint constant OWNERS_COUNT_SLOT = 3; 11 | uint constant THRESHOLD_SLOT = 4; 12 | 13 | import "forge-std/console.sol"; 14 | 15 | contract ProofParser { 16 | address public immutable verifier; 17 | 18 | constructor(address _verifier) { 19 | verifier = _verifier; 20 | } 21 | 22 | function parse( 23 | bytes[] calldata proofs 24 | ) 25 | public 26 | view 27 | returns ( 28 | bytes32 blockHash, 29 | uint blockNumber, 30 | address account, 31 | uint ownersCount, 32 | uint threshold, 33 | address[] memory owners 34 | ) 35 | { 36 | uint[] memory slots; 37 | uint[] memory values; 38 | (blockHash, blockNumber, account, slots, values) = parseMultipleProofs( 39 | proofs 40 | ); 41 | (ownersCount, threshold, owners) = parseState(slots, values); 42 | } 43 | 44 | function parseSingleProof( 45 | bytes calldata proof, 46 | uint[] memory slots, 47 | uint[] memory values, 48 | uint startLocation 49 | ) 50 | public 51 | view 52 | returns (bytes32 blockHash, uint blockNumber, address account) 53 | { 54 | (bool success, ) = verifier.staticcall(proof); 55 | require(success, "Proof verification failed"); 56 | 57 | blockHash = bytes32( 58 | (uint256(bytes32(proof[384:384 + 32])) << 128) | 59 | uint128(bytes16(proof[384 + 48:384 + 64])) 60 | ); 61 | blockNumber = uint256(bytes32(proof[384 + 64:384 + 96])); 62 | account = address(bytes20(proof[384 + 108:384 + 128])); 63 | 64 | for (uint16 i = 0; i < SLOTS_PER_PROOF; i++) { 65 | uint256 slot = (uint256( 66 | bytes32(proof[384 + 128 + 128 * i:384 + 160 + 128 * i]) 67 | ) << 128) | 68 | uint128( 69 | bytes16(proof[384 + 176 + 128 * i:384 + 192 + 128 * i]) 70 | ); 71 | uint256 value = (uint256( 72 | bytes32(proof[384 + 192 + 128 * i:384 + 224 + 128 * i]) 73 | ) << 128) | 74 | uint128( 75 | bytes16(proof[384 + 240 + 128 * i:384 + 256 + 128 * i]) 76 | ); 77 | 78 | slots[startLocation + i] = slot; 79 | values[startLocation + i] = value; 80 | } 81 | } 82 | 83 | function parseMultipleProofs( 84 | bytes[] calldata proofs 85 | ) 86 | public 87 | view 88 | returns ( 89 | bytes32 blockHash, 90 | uint blockNumber, 91 | address account, 92 | uint[] memory slots, 93 | uint[] memory values 94 | ) 95 | { 96 | uint totalSlots = proofs.length * SLOTS_PER_PROOF; 97 | 98 | slots = new uint[](totalSlots); 99 | values = new uint[](totalSlots); 100 | 101 | (blockHash, blockNumber, account) = parseSingleProof( 102 | proofs[0], 103 | slots, 104 | values, 105 | 0 106 | ); 107 | 108 | for (uint i = 1; i < proofs.length; i++) { 109 | ( 110 | bytes32 _blockHash, 111 | uint _blockNumber, 112 | address _account 113 | ) = parseSingleProof(proofs[i], slots, values, i * SLOTS_PER_PROOF); 114 | require(blockHash == _blockHash); 115 | require(blockNumber == _blockNumber); 116 | require(account == _account); 117 | } 118 | } 119 | 120 | function parseState( 121 | uint[] memory slots, 122 | uint[] memory values 123 | ) 124 | public 125 | pure 126 | returns (uint ownersCount, uint threshold, address[] memory owners) 127 | { 128 | require(slots.length == values.length, "same length"); 129 | require(slots.length >= 3, "atleast 3"); 130 | 131 | // ensure correct owners count 132 | require(slots[0] == OWNERS_COUNT_SLOT, "owners count slot"); 133 | require(values[0] >= 1, "owners count value"); 134 | ownersCount = values[0]; 135 | 136 | // ensure correct threshold 137 | require(slots[1] == THRESHOLD_SLOT, "threshold slot"); 138 | require(values[1] <= values[0], "threshold value"); 139 | threshold = values[1]; 140 | 141 | // creating a owners array to convert the linked list owners into mapping 142 | owners = new address[](ownersCount); 143 | 144 | // ensure that the correct mapping reads and parse results into 145 | uint key = SENTINEL_OWNERS; 146 | uint value; 147 | for (uint i = 0; i < ownersCount; i++) { 148 | uint expectedSlot = hashTwo(key, OWNERS_MAPPING_SLOT); 149 | require(slots[i + 2] == expectedSlot, "slot mismatch"); 150 | 151 | // assuming value is correct, it should be a owner 152 | value = values[i + 2]; 153 | require(value < (1 << 160), "value is out of range"); 154 | 155 | owners[i] = address(uint160(value)); 156 | 157 | // next owner present in mapping at key as current owner 158 | // https://github.com/safe-global/safe-contracts/blob/fca63a0fe0395a885032deacbdf02f26e7ff06a0/contracts/base/OwnerManager.sol#L44 159 | key = value; 160 | } 161 | 162 | require( 163 | values[ownersCount + 2] == SENTINEL_OWNERS, 164 | "last value should be 1" 165 | ); 166 | } 167 | 168 | function hashTwo(uint a, uint b) private pure returns (uint c) { 169 | assembly { 170 | mstore(0, a) 171 | mstore(0x20, b) 172 | c := keccak256(0, 0x40) 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /contracts/test/ProofParser.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import {ProofParser, SLOTS_PER_PROOF} from "../src/utils/ProofParser.sol"; 8 | 9 | bytes constant verifierBytecode = hex"62000025565b60006040519050600081036200001a57606090505b818101604052919050565b614e89620000338162000005565b816200003f82398181f3fe60017f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd477f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000161013b565b60007f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4782107f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478410808216925050507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478384097f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478384097f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478482097f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47600382088381148086169550505050505092915050565b8060003506602052806020350660405280604035066060528060603506608052806080350660a0528060a0350660c0528060c0350660e0528060e0350661010052806101003506610120528061012035066101405280610140350661016052806101603506610180528061018035066101a052806101a035066101c052806101c035066101e052806101e0350661020052806102003506610220528061022035066102405280610240350661026052806102603506610280528061028035066102a052806102a035066102c052806102c035066102e052806102e0350661030052806103003506610320528061032035066103405280610340350661036052806103603506610380528061038035066103a052806103a035066103c052806103c035066103e052806103e0350661040052806104003506610420528061042035066104405280610440350661046052806104603506610480528061048035066104a052806104a035066104c052806104c035066104e052806104e03506610500527f2a6d0df8013246c68a47c055dff3ed4327ea7ed8090a519824a01871cdbb54b0600052610500358061052052610520358061054052846102fd8284610048565b16945050506105403580610560526105603580610580528461031f8284610048565b169450505061058035806105a0526105a035806105c052846103418284610048565b16945050506105e06000206105e0526105e051818106610600528061062052506105c03580610640526105e03580610660528461037e8284610048565b169450505061060035806106805261062035806106a052846103a08284610048565b169450505060a0610620206106c0526106c0518181066106e05280610700525060016107205360216107002061072052610720518181066107405280610760525061064035806107805261066035806107a052846103fe8284610048565b169450505061068035806107c0526106a035806107e052846104208284610048565b16945050506106c03580610800526106e0358061082052846104428284610048565b1694505050610700358061084052610720358061086052846104648284610048565b169450505061074035806108805261076035806108a052846104868284610048565b1694505050610160610760206108c0526108c0518181066108e0528061090052506107803580610920526107a0358061094052846104c48284610048565b16945050506107c03580610960526107e0358061098052846104e68284610048565b169450505061080035806109a05261082035806109c052846105088284610048565b169450505060e0610900206109e0526109e051818106610a005280610a205250806108403506610a4052806108603506610a6052806108803506610a8052806108a03506610aa052806108c03506610ac052806108e03506610ae052806109003506610b0052806109203506610b2052806109403506610b4052806109603506610b6052806109803506610b8052806109a03506610ba052806109c03506610bc052806109e03506610be05280610a003506610c005280610a203506610c205280610a403506610c405280610a603506610c605280610a803506610c805280610aa03506610ca05280610ac03506610cc05280610ae03506610ce05280610b003506610d005280610b203506610d205280610b403506610d405280610b603506610d605280610b803506610d805280610ba03506610da05280610bc03506610dc05280610be03506610de05280610c003506610e005280610c203506610e2052610420610a2020610e4052610e4051818106610e605280610e8052506001610ea0536021610e8020610ea052610ea051818106610ec05280610ee05250610c403580610f0052610c603580610f2052846106c28284610048565b16945050506060610ee020610f4052610f4051818106610f605280610f805250610c803580610fa052610ca03580610fc052846106ff8284610048565b169450505060205160405160581b8101905060605160b01b8101905080610fe05260805160a05160581b8101905060c05160b01b810190508061100052846107478284610048565b169450505060e0516101005160581b810190506101205160b01b810190508061102052610140516101605160581b810190506101805160b01b810190508061104052846107948284610048565b169450505080610a0051610a005109611060528061106051611060510961108052806110805161108051096110a052806110a0516110a051096110c052806110c0516110c051096110e052806110e0516110e05109611100528061110051611100510961112052806111205161112051096111405280611140516111405109611160528061116051611160510961118052806111805161118051096111a052806111a0516111a051096111c052806111c0516111c051096111e052806111e0516111e05109611200528061120051611200510961122052806112205161122051096112405280611240516112405109611260528061126051611260510961128052806112805161128051096112a052806112a0516112a051096112c052806112c0516112c051096112e052806112e0516112e051096113005280611300516113005109611320528061132051611320510961134052807f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000611340510861136052807f30644e427ce32d4886b01bfe313ba1dba6db8b2045d128178a7164500e0a6c11611360510961138052807f1c95a1061a42011f8f413d12ae0ccc948afa22e16f3eec65d3ffbe867a6437af61138051096113a052807f13cead6cc6ef9f0a290f08a3d3748bc89d39c5670a7a842b6fe2370d759bc852610a0051086113c052807f20669a4af88d28096b76c1e00c7eb94022b5eeb63400055aa63bd19ae7ede71461138051096113e052807f0ffdb427e8a478204cd983d675029f1d057df99245b96b369da623f9081218ed610a00510861140052807f1b048c831967630725ea9f285d46b2ebebe150d864dbc8741ee203563196aeee611380510961142052807f155fc1efc7ca3d229265a68e243aa5713c52977014dda81d24fff23dbe695113610a00510861144052807f134f571fe34eb8c7b1685e875b324820e199bd70157493377cd65b204d1a3964611380510961146052807f1d14f752fde2e76206e7e72f264f103c469a2ad86444dd59c70b9a73a2e5c69d610a00510861148052807f10450706ff540b5847e6f65ed91542ea8f8cd084a5671feef0bfcfc2f6a588af61138051096114a052807f201f476be1dd94d170694f57a86c157298a717c3d45250a2532225d0f95a7752610a0051086114c052807f1589862c1cf3f8b59954774980cc9361c568bcabd9cb7d0858de685794d4772b61138051096114e052807f1adac846c43da7741efbce6d00b4c4fb62cb2b9c9fedf388eb038d3c5b2b88d6610a00510861150052807f26501ebfe559ea5826f023d3e76e4b66f170cd940408eb5590a4075c80b498d6611380510961152052807f0a142fb2fbd7b5d1916021e29a130cf636c31ab475b0853bb33dee376f4b672b610a00510861154052806001611380510961156052807f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000610a00510861158052807f1951441010b2b95a6e47a6075066a50a036f5ba978c050f2821df86636c0facb61138051096115a052807f17130a62d07ee6cf4a089faf311ab35324c48c9f00f91f9ec1c3fd2db93f0536610a0051086115c052807f1283ba6f4b7b1a76ba2008fe823128bea4adb9269cbfd7c41c223be65bc6086361138051096115e052807f1de0940395b685b2fe303cb7ff502f9e83862f21dcf998cd27bfb9ad9439f79e610a00510861160052807f2f835d9f4207df4efa4ffa0b2bbf9a4f54221c57cc506b7a5f8dae90bd2e3d0a611380510961162052807ee0f0d39f29c0dabe004bab55c1be0dd411cbf0ad690516e454470332d1c2f7610a00510861164052807f18c95f1ae6514e11a1b30fd7923947c5ffcec5347f16e91b4dd654168326bede611380510961166052807f179aef57fae05218169d35deef48109728652313faa28775f60ba17d6cd94123610a00510861168052807f29aa84e8187de51daa6de67f44fe365fbb789d1e5f97bc95844628487ebd3a0d61138051096116a052807f06b9c98ac8b3bb0c0de25f373c8321fd6cbb4b2a1a21b3fbbf9bcd4b7142c5f4610a0051086116c052807f07fe49da5568a43070d955e0d212d956e1ff8abd41a763c737e292bee047669961138051096116e052807f286604988bc8fbf94776efd5af6e7f0646345d8b38120cca0bff62d50fb89968610a00510861170052807f2f6d5dbc65d9e42565b5259fa7b05b83e83bd240f447c0c743a36e342cbbd6ed611380510961172052807ef6f0b67b57bc04529b2016d9d0fcd93ff816078571afca003e875fc3442914610a00510861174052807f032750f8f3c2493d0828c7285d0258e1bdcaa463f4442a52747b5c96639659bb611380510961176052807f2d3cfd79ed6f56ecb0277e8e247eff7b6a6943e48575463ecf6698fd8c69a646610a00510861178052807f1ee9b22299f8d2c2a4a89b7bc09e0b44dbb47bdaea5a81b9a6754c34c1431fb061138051096117a052807f117a9c504738cd6713a7aa3ac0e34d184c7f6c6d8f5eeed79d6ca95f2ebce051610a0051086117c052807e7a4dc361069950d7cc013db4be2feeca18869368a658bafd1f9e32682bfa0b61138051096117e052807f2fea00af802b06d8e0844478ccc3286e5e1b61b5111317d646c2576187d405f6610a00510861180052807f151631bec5913741072b5d1b14089006e6a12b626e1a491aa6f2f6833c1c2b52611380510961182052807f1b4e1cb41ba068e8b124e89b6d78c8564192bce60b9f27769ceeff10b3e3d4af610a00510861184052807f1be6734ccb6588d60d469def6bf0b0f39af67bb568a3b63e3773e3dd306ba070611380510961186052807f147ddb2615cc1753ab09a7c71590a7698d3d6c931115ba530c6e11b6bf945f91610a00510861188052807f08ba2c8963ca3bf2d315ab853d66ed93430bac65f97fbf36f1fc3d5ece01ced661138051096118a052807f27aa21e97d676436e53a9a31441a6ac9e5283be28039b15a51e5b83521fe312b610a0051086118c052807f2924ff629bd1ed10993f0b98cac916ff80a3033064b97eb0f57166b5faa44e2861138051096118e052807f073f4f10455fb3191f113a1db6b8415da790e51814fff1e04e708eddf55bb1d9610a00510861190052807f036716ff170edcb8cf7ed9c000474b669234cdc02f3576a6b1467a3ea71e1a96611380510961192052807f2cfd3773ca22c370e8d16bf6813a0cf695ff1a884a83f9ea929b7b5548e1e56b610a00510861194052807f2a14464f1ff42de3856402b62520e670745e39fada049d5b2f0e1e3182673378611380510961196052807f06500823c13d724632ec43005c6071ecb3d5ae4d9fb4d33614d3d7626d98cc89610a00510861198052807f098b915260335414d254e44d3045f9c9c8bf008c9e221cdd673ccd9a702cbd3b61138051096119a052807f26d8bd2080fe4c14e5fb6169513b5e935f74e7bbdb9753b3dca527f97fd342c6610a0051086119c052807f20e37a1882697d188376fa91662bf38ce3659cb1ff9e2bfa7151dbcd1f77644a61138051096119e052807f0f80d45a5ec8231134d94b251b5564d044ce4b967a1b4496d29019c6d0889bb7610a005108611a0052807f053b1f314513079f215137df80c4e8ad17859e1ded2081bd1b77e9a66051c8a26113805109611a2052807f2b292f419c1e988a96ff0dd700bc6fb010ae4a2a8c98eed4286a0bed8fae375f610a005108611a4052807f0205f4c81bd8bfadd87e3771dc61e489caf1a2f61f6fe94419612b1039f806ba6113805109611a6052807f2e5e59aac558e07bdfd20e44a51f73d35d4245525a49874d2a80ca83b607f947610a005108611a8052807f1fd14cd8b30f8d1dc40ef8c228d7fe8d899c6cbfe9438043810693836c9f199f6113805109611aa052807f1093019a2e22130bf4414cf458a959cf9e977b889075f04dc2db62108360e662610a005108611ac052807f0c356ec22ff48e20ab09aec6d75ed2e9b3ac726818d8b8997909ade2b59f16f56113805109611ae052807f242edfb0b13d12090d4696efaa228573748775e060e0b7f7cad847b13a60e90c610a005108611b0052807f165f97bba5ad8dc4151310822d5a76b52ca4c66aecc3ea6deb0e01f62b2579426113805109611b2052807f1a04b6b73b841265a33d35345426e1a7fb8f21dd8cf5862358d3f39dc4da86bf610a005108611b4052807f085386d3273d6903e6ca9388a9b29e81643977d523db6f6db1fdc5acf965b8096113805109611b6052807f2810c79fb9f43725d185b22dd7ceb9dbc3fa707355de012391e42fe6f69a47f8610a005108611b8052807f04996bbc03a42e7fea1ac968f3b79c1e0e2106b55d5546689f22c1c89f8b8ee76113805109611ba052807f2bcae2b6dd8d71a9ce357c4d8dc9bc3f1a12e1931c642a28a4bf33cb5074711a610a005108611bc052807f141ee3b6428451f8d5e998d9bb2dfdea75b18a4f04b50812600662f34e98c3b86113805109611be052807f1c456abc9ead4e30e266acdcc6535a72b2825df97504687ee3db92a0a1673c49610a005108611c0052807f18a3f7cc3ec43e6d46579fc3561c927b87ecd8b2010c036418945dade90f4b246113805109611c2052807f17c056a6a26d61bc71f8a5f32b64c5e1a0470f9678ad6d2d2b4d97e606f0b4dd610a005108611c4052807f0962d59f2fea9851331073aa7b9a65add88e25cdcf9a7908665722e5c57e1c256113805109611c6052807f270178d3b14707d8853fd20c05e6f2af4fa5c27aaa1ef788dd8ad2ae2a81e3dc610a005108611c8052807f2a82f379f266659cdf0f56c75701fd008128739ff74007103b727432fc0822496113805109611ca052807f05e15af8eecb3a8cd940eeef2a7f5b5ca70b74a882796981086f8160f3f7ddb8610a005108611cc052807f0d8ef66ebe1552f63389590ed580b54642c2ab9a9d5b9b7caa9608d2354ffb7d6113805109611ce052807f22d55804231c4d3384c6eca7ac00a316e5713caddc5dd514994becc1bab00484610a005108611d0052807f16516d5e6cf23777960d343d43bf01bb53449709df1cb4d2d600f589fe43186b6113805109611d2052807f1a12e114743f68b2224311793dc256a1d4ef513e9a9cbbbe6de10009f1bce796610a005108611d4052807f0cf1526aaafac6bacbb67d11a4077806b123f767e4b0883d14cc0193568fc0826113805109611d6052807f2372fc083636d96eec99c8a4dd79e056770ff0e09508e8542f15f40099703f7f610a005108611d8052807f2f3185ed3ab186ae7548e0d7c1a290d739970857d48e2a61f3857ddf826dd77d6113805109611da052807f0132c885a680197b430764debfdec785ee9cdff0a52b462f505c77b46d922884610a005108611dc052807f012390465fc645541d7c803b6cd1a1477ee28c7c908d8c987faad845c66f4b456113805109611de052807f2f40be2c816b5ad59ad3c57b14afb715a9515bcbe92be3f8c4371d4e2990b4bc610a005108611e0052807f1e7bde7663beaa47e583f20c69c76f03507de98178f2e182af485cbfc55120206113805109611e2052807f11e86ffc7d72f5e1d2cc53aa17b9e959d7b5fec700c68f0e949998d42aaedfe1610a005108611e4052807f0d38d63833e51358021fc5aeb30483105d8b48022cd28fc4ccc55739e57166f46113805109611e6052807f232b783aad4c8cd1b6308007ce7cd54ccaa8a0464ce6e0cc771c9e5a0a8e990d610a005108611e8052807f12edbf9359beda5d618e20b06aba41bb313ab46a8e139f451f74eb6d4198d3de6113805109611ea052807f1d768edf8772c5cc56c2250616c716a1f6f933ddeba5d14c246d0a26ae672c23610a005108611ec052807f0b8c8800ba5f126afb12cedb50c35bac1a37423899adee47fad78844fcd72d926113805109611ee052807f24d7c67226d28dbebd3d76db30bdfcb10dfca60fe00b8249490a6d4ef328d26f610a005108611f0052807f074d04820e651b5d20ac4c2ca1616a4eb00a815acb11d44657fd9d72878c9a6d6113805109611f2052807f291749f0d2cc84cc97a3f989e01fee0e782966edaea79c4aebe4582168736594610a005108611f40526113c05181816114005109905080611f605281816114405109905080611f805281816114805109905080611fa05281816114c05109905080611fc05281816115005109905080611fe0528181611540510990508061200052818161158051099050806120205281816115c051099050806120405281816116005109905080612060528181611640510990508061208052818161168051099050806120a05281816116c051099050806120c052818161170051099050806120e0528181611740510990508061210052818161178051099050806121205281816117c051099050806121405281816118005109905080612160528181611840510990508061218052818161188051099050806121a05281816118c051099050806121c052818161190051099050806121e0528181611940510990508061220052818161198051099050806122205281816119c05109905080612240528181611a005109905080612260528181611a405109905080612280528181611a8051099050806122a0528181611ac051099050806122c0528181611b0051099050806122e0528181611b405109905080612300528181611b805109905080612320528181611bc05109905080612340528181611c005109905080612360528181611c405109905080612380528181611c8051099050806123a0528181611cc051099050806123c0528181611d0051099050806123e0528181611d405109905080612400528181611d805109905080612420528181611dc05109905080612440528181611e005109905080612460528181611e405109905080612480528181611e8051099050806124a0528181611ec051099050806124c0528181611f0051099050806124e0528181611f4051099050806125005281816113605109905080612520525060206125605260206125805260206125a052612520516125c0527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff6125e0527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000161260052826001602061254060c061256060055afa141692506125405160006113605190508282612500510961136052828282099150611f4051905082826124e05109611f4052828282099150611f0051905082826124c05109611f0052828282099150611ec051905082826124a05109611ec052828282099150611e8051905082826124805109611e8052828282099150611e4051905082826124605109611e4052828282099150611e0051905082826124405109611e0052828282099150611dc051905082826124205109611dc052828282099150611d8051905082826124005109611d8052828282099150611d4051905082826123e05109611d4052828282099150611d0051905082826123c05109611d0052828282099150611cc051905082826123a05109611cc052828282099150611c8051905082826123805109611c8052828282099150611c4051905082826123605109611c4052828282099150611c0051905082826123405109611c0052828282099150611bc051905082826123205109611bc052828282099150611b8051905082826123005109611b8052828282099150611b4051905082826122e05109611b4052828282099150611b0051905082826122c05109611b0052828282099150611ac051905082826122a05109611ac052828282099150611a8051905082826122805109611a8052828282099150611a4051905082826122605109611a4052828282099150611a0051905082826122405109611a00528282820991506119c0519050828261222051096119c052828282099150611980519050828261220051096119805282828209915061194051905082826121e051096119405282828209915061190051905082826121c05109611900528282820991506118c051905082826121a051096118c0528282820991506118805190508282612180510961188052828282099150611840519050828261216051096118405282828209915061180051905082826121405109611800528282820991506117c0519050828261212051096117c052828282099150611780519050828261210051096117805282828209915061174051905082826120e051096117405282828209915061170051905082826120c05109611700528282820991506116c051905082826120a051096116c0528282820991506116805190508282612080510961168052828282099150611640519050828261206051096116405282828209915061160051905082826120405109611600528282820991506115c0519050828261202051096115c05282828209915061158051905082826120005109611580528282820991506115405190508282611fe05109611540528282820991506115005190508282611fc05109611500528282820991506114c05190508282611fa051096114c0528282820991506114805190508282611f805109611480528282820991506114405190508282611f6051096114405282828209915061140051905082826113c0510961140052828282099150816113c0525050806113c0516113a051096126205280611400516113e051096126405280611440516114205109612660528061148051611460510961268052806114c0516114a051096126a05280611500516114e051096126c052806115405161152051096126e0528061158051611560510961270052806115c0516115a051096127205280611600516115e051096127405280611640516116205109612760528061168051611660510961278052806116c0516116a051096127a05280611700516116e051096127c052806117405161172051096127e0528061178051611760510961280052806117c0516117a051096128205280611800516117e051096128405280611840516118205109612860528061188051611860510961288052806118c0516118a051096128a05280611900516118e051096128c052806119405161192051096128e0528061198051611960510961290052806119c0516119a051096129205280611a00516119e051096129405280611a4051611a2051096129605280611a8051611a6051096129805280611ac051611aa051096129a05280611b0051611ae051096129c05280611b4051611b2051096129e05280611b8051611b605109612a005280611bc051611ba05109612a205280611c0051611be05109612a405280611c4051611c205109612a605280611c8051611c605109612a805280611cc051611ca05109612aa05280611d0051611ce05109612ac05280611d4051611d205109612ae05280611d8051611d605109612b005280611dc051611da05109612b205280611e0051611de05109612b405280611e4051611e205109612b605280611e8051611e605109612b805280611ec051611ea05109612ba05280611f0051611ee05109612bc05280611f4051611f205109612be05280602051612700510981818360405161272051090890508181836060516127405109089050818183608051612760510908905081818360a051612780510908905081818360c0516127a0510908905081818360e0516127c05109089050818183610100516127e051090890508181836101205161280051090890508181836101405161282051090890508181836101605161284051090890508181836101805161286051090890508181836101a05161288051090890508181836101c0516128a051090890508181836101e0516128c05109089050818183610200516128e051090890508181836102205161290051090890508181836102405161292051090890508181836102605161294051090890508181836102805161296051090890508181836102a05161298051090890508181836102c0516129a051090890508181836102e0516129c05109089050818183610300516129e0510908905081818361032051612a00510908905081818361034051612a20510908905081818361036051612a40510908905081818361038051612a6051090890508181836103a051612a8051090890508181836103c051612aa051090890508181836103e051612ac0510908905081818361040051612ae0510908905081818361042051612b00510908905081818361044051612b20510908905081818361046051612b40510908905081818361048051612b6051090890508181836104a051612b8051090890508181836104c051612ba051090890508181836104e051612bc0510908905081818361050051612be0510908905080612c00525080610a6051610a805109612c205280612c2051610a405108612c405280610aa0518203612c405108612c605280610ba051612c605109612c805280612c80516108e05109612ca05280610ae051610b005109612cc05280612cc051610ac05108612ce05280610b20518203612ce05108612d005280610bc051612d005109612d205280612d2051612ca05108612d405280612d40516108e05109612d605280610ca0518203600108612d80528061270051612d805109612da05280612da051612d605108612dc05280612dc0516108e05109612de05280610d6051610d605109612e005280610d60518203612e005108612e20528061262051612e205109612e405280612e4051612de05108612e605280612e60516108e05109612e805280610ce0518203610d005108612ea0528061270051612ea05109612ec05280612ec051612e805108612ee05280612ee0516108e05109612f005280610d40518203610d605108612f20528061270051612f205109612f405280612f4051612f005108612f605280612f60516108e05109612f805280612620518203600108612fa05280612660516126405108612fc0528061268051612fc05108612fe052806126a051612fe0510861300052806126c051613000510861302052806126e05161302051086130405280613040518203612fa0510861306052806106e051610c005109613080528061308051610b6051086130a05280610740516130a051086130c052806106e051610c2051096130e052806130e051610a405108613100528061074051613100510861312052806130c05161312051096131405280610cc051613140510961316052806106e051600109613180528061318051610a0051096131a052806131a051610b6051086131c05280610740516131c051086131e052806106e0517f09226b6e22c6f0ca64ec26aad4c86e715b5f898e5e963f25870e56bbe533e9a209613200528061320051610a005109613220528061322051610a405108613240528061074051613240510861326052806131e05161326051096132805280610ca05161328051096132a052806132a051820361316051086132c05280613060516132c051096132e052806132e051612f8051086133005280613300516108e0510961332052806106e051610c405109613340528061334051610ac05108613360528061074051613360510861338052806106e051610c6051096133a052806133a051610b4051086133c05280610740516133c051086133e05280613380516133e051096134005280610d2051613400510961342052806106e0517f13b360d4e82fe915fed16081038f98c211427b87a281bd733c277dbadf10372b09613440528061344051610a005109613460528061346051610ac0510861348052806107405161348051086134a052806106e0517f18afdf23e9bd9302673fc1e076a492d4d65bd18ebc4d854ed189139bab313e52096134c052806134c051610a0051096134e052806134e051610b405108613500528061074051613500510861352052806134a05161352051096135405280610d005161354051096135605280613560518203613420510861358052806130605161358051096135a052806135a05161332051086135c052806135c0516108e051096135e052806106e051610c805109613600528061360051612c00510861362052806107405161362051086136405280610d8051613640510961366052806106e0517ea136ba13afa6c83eb7b82fb370e228e74155e48fb8f1c1cfc33fb0da8afb4209613680528061368051610a0051096136a052806136a051612c0051086136c05280610740516136c051086136e05280610d60516136e051096137005280613700518203613660510861372052806130605161372051096137405280613740516135e051086137605280613760516108e051096137805280610da05182036001086137a05280612700516137a051096137c052806137c05161378051086137e052806137e0516108e051096138005280610da051610da051096138205280610da05182036138205108613840528061262051613840510961386052806138605161380051086138805280613880516108e051096138a052806106e051610de051086138c05280610dc0516138c051096138e0528061074051610e20510861390052806138e051613900510961392052806106e051610b4051086139405280610da0516139405109613960528061074051610b80510861398052806139605161398051096139a052806139a051820361392051086139c05280613060516139c051096139e052806139e0516138a05108613a005280613a00516108e05109613a205280610e20518203610de05108613a40528061270051613a405109613a605280613a6051613a205108613a805280613a80516108e05109613aa0528061306051613a405109613ac05280610e00518203610de05108613ae05280613ac051613ae05109613b005280613b0051613aa05108613b205280611340516113405109613b40528061134051613b405109613b60528061134051600109613b805280613b4051600109613ba0528061136051613b205109613bc05280610a00516110605109613be05280610a0051613be05109613c0052806001610a005109613c205280613c20518203610f605108613c4052807f1283ba6f4b7b1a76ba2008fe823128bea4adb9269cbfd7c41c223be65bc60863610a005109613c605280613c60518203610f605108613c8052807f1951441010b2b95a6e47a6075066a50a036f5ba978c050f2821df86636c0facb610a005109613ca05280613ca0518203610f605108613cc052807f1c95a1061a42011f8f413d12ae0ccc948afa22e16f3eec65d3ffbe867a6437af610a005109613ce05280613ce0518203610f605108613d0052807f26501ebfe559ea5826f023d3e76e4b66f170cd940408eb5590a4075c80b498d6610a005109613d205280613d20518203610f605108613d4052807f2f835d9f4207df4efa4ffa0b2bbf9a4f54221c57cc506b7a5f8dae90bd2e3d0a610a005109613d605280613d60518203610f605108613d805280817f0ea09ba94d38bb2db2dff170328cd706312ac2c587871932fbf9b5538f158f25613be05109610f605109818183847f21c3b2c993f8e4fc057054464ef48156f7092582f232575e47e8404060ea70dc613be05109610a00510908905080613da0525080817f012c40d95d017e1c567d73fa1d6478776ea4d0097e58e9ccd24f636901636430613be05109610f605109818183847f0ee423d49b8e4790eb37a6348543ba4a587bbd70d6e3109c33e4b3413ab3b594613be05109610a00510908905080613dc0525080817f0ee423d49b8e4790eb37a6348543ba4a587bbd70d6e3109c33e4b3413ab3b594613be05109610f605109818183847f2f516d7025f943b583ec1823e7422e2c90d993b79f42a89eecdf85d12be7ff60613be05109610a00510908905080613de0525080817f2fa9d843de55996a40ebf0b5e8e49312c50733798de64167a4c744cc65a6aaea613be05109610f605109818183847f2fdc44401961fedd454cb4ef2d38e4ada2241ae9baab4567742663f4a3d1bae0613be05109610a00510908905080613e00525080613c4051600109613e205280613cc051613e205109613e405280613c8051613e405109613e605280613d8051613e605109613e8052806001610f6051098181837f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000610a00510908905080613ea0525080817f1ae403a7aeca0db9263e247c8f8c9ffebc805873c5ba38939400104226c8b49c6110605109610f605109818183847f15804acb3267927092122139f1f4b85e6bb38fd4b3ff37fdafe1e551c9374b656110605109610a00510908905080613ec0525080817f25c5cb8d3daeda5c27f323e054d9ef6631b679f0d8f7de6e0baa25ffa77b5e346110605109610f605109818183847f0e029d7b0b7f164f329431f228bae07841eae7397b049ff4791cb4a4d84093346110605109610a00510908905080613ee0525080817f16b01e864c145881be9efe486c5199821e312e31cbecf2a72cb4358ae39a847c6110605109610f605109818183847f1f9bfd60a321850dd028b8676bd55eae93aacde2c01b082a1401ce9670250c376110605109610a00510908905080613f00525080613d0051613e405109613f205280817f17130a62d07ee6cf4a089faf311ab35324c48c9f00f91f9ec1c3fd2db93f0537610a005109610f605109818183847f1951441010b2b95a6e47a6075066a50a036f5ba978c050f2821df86636c0faca610a005109610a00510908905080613f40525080817f1951441010b2b95a6e47a6075066a50a036f5ba978c050f2821df86636c0faca610a005109610f605109818183847f06cd89a0c5379ee3b4279d08ce357c4b5ec1a282dc00792e65fbbc7fdafaf268610a005109610a00510908905080613f60525080817f0a142fb2fbd7b5d1916021e29a130cf636c31ab475b0853bb33dee376f4b672c610a005109610f605109818183847f26501ebfe559ea5826f023d3e76e4b66f170cd940408eb5590a4075c80b498d5610a005109610a00510908905080613f80525080817f26501ebfe559ea5826f023d3e76e4b66f170cd940408eb5590a4075c80b498d5610a005109610f605109818183847f10c69893c865f1a28d9bac8a66a1b8052c0810e82a3d6e4d37c59f04ebe021ab610a005109610a00510908905080613fa0525080613d4051613e205109613fc052613da0518181613dc05109905080613fe0528181613de05109905080614000528181613e005109905080614020528181613ea05109905080614040528181613e205109905080614060528181613ec05109905080614080528181613ee051099050806140a0528181613f0051099050806140c0528181613f2051099050806140e0528181613f405109905080614100528181613f605109905080614120528181613e405109905080614140528181613f805109905080614160528181613fa05109905080614180528181613fc051099050806141a0525060206141e0526020614200526020614220526141a051614240527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff614260527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000016142805282600160206141c060c06141e060055afa141692506141c0516000613fc051905082826141805109613fc052828282099150613fa051905082826141605109613fa052828282099150613f8051905082826141405109613f8052828282099150613e4051905082826141205109613e4052828282099150613f6051905082826141005109613f6052828282099150613f4051905082826140e05109613f4052828282099150613f2051905082826140c05109613f2052828282099150613f0051905082826140a05109613f0052828282099150613ee051905082826140805109613ee052828282099150613ec051905082826140605109613ec052828282099150613e2051905082826140405109613e2052828282099150613ea051905082826140205109613ea052828282099150613e0051905082826140005109613e0052828282099150613de05190508282613fe05109613de052828282099150613dc05190508282613da05109613dc05282828209915081613da0525050613da0518181613dc0510890508181613de0510890508181613e0051089050806142a0525080613e2051613e8051096142c052613ea051806142e0525080613f2051613e80510961430052613ec0518181613ee0510890508181613f005108905080614320525080613e4051613e80510961434052613f40518181613f605108905080614360525080613fc051613e80510961438052613f80518181613fa051089050806143a052506142a05181816142e051099050806143c052818161432051099050806143e052818161436051099050806144005281816143a05109905080614420525060206144605260206144805260206144a052614420516144c0527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff6144e0527f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000161450052826001602061444060c061446060055afa141692506144405160006143a0519050828261440051096143a05282828209915061436051905082826143e051096143605282828209915061432051905082826143c05109614320528282820991506142e051905082826142a051096142e052828282099150816142a0525050806142e0516142c051096145205280614320516143005109614540528061436051614340510961456052806143a05161438051096145805280610e6051610e6051096145a05280610e60516145a051096145c05280610e60516145c051096145e05280610e60516145e051096146005280610e605161460051096146205280610e605161462051096146405280610e605161464051096146605280610e605161466051096146805280610e605161468051096146a05280610e60516146a051096146c05280610e60516146c051096146e05280610e60516146e051096147005280610ec051610ec051096147205280610ec05161472051096147405280610ec05161474051096147605280610ec05161476051096147805280613da051610a405109818183613dc051610a605109089050818183613de051610a805109089050818183613e0051610aa05109089050806147a05250806142a0516147a051096147c0528060016147c0518303096147e05280613da051610ac05109818183613dc051610ae05109089050818183613de051610b005109089050818183613e0051610b205109089050806148005250806142a05161480051096148205280610e6051614820518303096148405280610e60516001096148605280614840516147e051086148805280600161488051096148a05280600161486051096148c052806142c0516001096148e05280613ea051610b405109806149005250806145205161490051096149205280600161492051830309614940528060016148e051096149605280613ea051610e205109806149805250806145205161498051096149a05280610e60516149a0518303096149c05280610e60516148e051096149e052806149c0516149405108614a005280613ea051610b60510980614a2052508061452051614a205109614a4052806145a051614a4051830309614a6052806145a0516148e05109614a805280614a6051614a005108614aa05280613ea051610b80510980614ac052508061452051614ac05109614ae052806145c051614ae051830309614b0052806145c0516148e05109614b205280614b0051614aa05108614b405280613ea051610ba0510980614b6052508061452051614b605109614b8052806145e051614b8051830309614ba052806145e0516148e05109614bc05280614ba051614b405108614be05280613ea051610bc0510980614c0052508061452051614c005109614c20528061460051614c2051830309614c405280614600516148e05109614c605280614c4051614be05108614c805280613ea051610c00510980614ca052508061452051614ca05109614cc0528061462051614cc051830309614ce05280614620516148e05109614d005280614ce051614c805108614d205280613ea051610c20510980614d4052508061452051614d405109614d60528061464051614d6051830309614d805280614640516148e05109614da05280614d8051614d205108614dc05280613ea051610c40510980614de052508061452051614de05109614e00528061466051614e0051830309614e205280614660516148e05109614e405280614e2051614dc05108614e605280613ea051610c60510980614e8052508061452051614e805109614ea0528061468051614ea051830309614ec05280614680516148e05109614ee05280614ec051614e605108614f005280613ea051610c80510980614f2052508061452051614f205109614f4052806146a051614f4051830309614f6052806146a0516148e05109614f805280614f6051614f005108614fa052806142c051613b805109614fc052806142c051613ba05109614fe05280613ea051613bc051098061500052508061452051615000510961502052806146c0516150205183030961504052806146c0516148e0510961506052806146c051614fc0510961508052806146c051614fe051096150a0528061504051614fa051086150c05280613ea051610be05109806150e0525080614520516150e0510961510052806146e0516151005183030961512052806146e0516148e051096151405280615120516150c051086151605280610ec05161516051096151805280610ec05161496051096151a05280610ec0516149e051096151c05280610ec051614a8051096151e05280610ec051614b2051096152005280610ec051614bc051096152205280610ec051614c6051096152405280610ec051614d0051096152605280610ec051614da051096152805280610ec051614e4051096152a05280610ec051614ee051096152c05280610ec051614f8051096152e05280610ec05161506051096153005280610ec05161508051096153205280610ec0516150a051096153405280610ec05161514051096153605280615180516148a051086153805280614300516001096153a05280613ec051610ca05109818183613ee051610cc05109089050818183613f0051610ce05109089050806153c0525080614540516153c051096153e0528060016153e051830309615400528060016153a051096154205280613ec051610d005109818183613ee051610d205109089050818183613f0051610d405109089050806154405250806145405161544051096154605280610e6051615460518303096154805280610e60516153a051096154a052806154805161540051086154c05280614720516154c051096154e052806147205161542051096155005280614720516154a0510961552052806154e05161538051086155405280614340516001096155605280613f4051610d605109818183613f6051610d805109089050806155805250806145605161558051096155a0528060016155a0518303096155c05280600161556051096155e05280613f4051610da05109818183613f6051610dc05109089050806156005250806145605161560051096156205280610e6051615620518303096156405280610e605161556051096156605280615640516155c0510861568052806147405161568051096156a05280614740516155e051096156c052806147405161566051096156e052806156a05161554051086157005280614380516001096157205280613f8051610de05109818183613fa051610e0051090890508061574052508061458051615740510961576052806001615760518303096157805280600161572051096157a052806147605161578051096157c05280614760516157a051096157e052806157c05161570051086158005280613e80516001096158205280610f605160010961584052600161586052600261588052615800516158a0528260016040615860606061586060075afa14169250615860516158c052615880516158e0526105205161590052610540516159205282600160406158c060806158c060065afa14169250610560516159405261058051615960526148c051615980528260016040615940606061594060075afa141692506158c0516159a0526158e0516159c052615940516159e05261596051615a005282600160406159a060806159a060065afa141692506105a051615a20526105c051615a40526151a051615a60528260016040615a206060615a2060075afa141692506159a051615a80526159c051615aa052615a2051615ac052615a4051615ae0528260016040615a806080615a8060065afa1416925061068051615b00526106a051615b20526151c051615b40528260016040615b006060615b0060075afa14169250615a8051615b6052615aa051615b8052615b0051615ba052615b2051615bc0528260016040615b606080615b6060065afa141692507f0c448a495d4810eadc8f28aaf095922a45524caa85bd90afe7042e1a525f3ae3615be0527f1db2655001576ed322c08a0b024c37c45aa1a10d855c5de850b5e2509b0e9f13615c00526151e051615c20528260016040615be06060615be060075afa14169250615b6051615c4052615b8051615c6052615be051615c8052615c0051615ca0528260016040615c406080615c4060065afa141692507f0b637b1163b38615a8a99bc9afc921ae8fc87c1fac452b2e6982d45e28728e11615cc0527f2a363795b4884540835533fdc7423adf4a194c9943dd4bb0acf7115c5579d5bb615ce05261520051615d00528260016040615cc06060615cc060075afa14169250615c4051615d2052615c6051615d4052615cc051615d6052615ce051615d80528260016040615d206080615d2060065afa141692507f2a434b183c23417ec031e2a8a0f466fcd052077e02e7f50de40b77d573f7f440615da0527f0fbf2537f3f871240324db117348b1b92843c07e5c25191bd82d05156b02cbeb615dc05261522051615de0528260016040615da06060615da060075afa14169250615d2051615e0052615d4051615e2052615da051615e4052615dc051615e60528260016040615e006080615e0060065afa141692507f0f3095879e13e8a3e6260958e8c07f886b820b011d8e73602b6139fb5527c4fc615e80527f121d37d8c2d2e91ed59e72392cd383975772fad7bab146d643c85c90ed8d52d9615ea05261524051615ec0528260016040615e806060615e8060075afa14169250615e0051615ee052615e2051615f0052615e8051615f2052615ea051615f40528260016040615ee06080615ee060065afa141692507f0abf3b80c9fdb9c16ea3c94f43b9ef1a8313595e958eb8b7798f21bd5cbb532e615f60527f2dd0626162541e21029bdfa28853011d0e16047447ace5e8ce581930df419f22615f805261526051615fa0528260016040615f606060615f6060075afa14169250615ee051615fc052615f0051615fe052615f605161600052615f8051616020528260016040615fc06080615fc060065afa141692507f2fb7cd58e43fc6749a59b5ab0ceccf09ff20aa19bbb5cc11ad25d5c93e65c3f4616040527f24938ee4850e16d1868de0a244a3f778afa7275f5399f4276d1b0a85c773e1b56160605261528051616080528260016040616040606061604060075afa14169250615fc0516160a052615fe0516160c052616040516160e052616060516161005282600160406160a060806160a060065afa141692507f1864e0f8ad3f0cbf4426489bae3aac6856ca6c957b4bbd655c94c25fc1d76492616120527f1e5b4687f4769fda40a60d85673e813e7633ff77f0847583c10a51779c2316cb616140526152a051616160528260016040616120606061612060075afa141692506160a051616180526160c0516161a052616120516161c052616140516161e0528260016040616180608061618060065afa141692507f1406471db51bf4942a382700371c694b0a3a6ec04bafe7eaa495c5b7d5b0259e616200527f0ff98f6f24a5665c9c63a495199b6c9b8283840bd080749230c42bdebfb500c3616220526152c051616240528260016040616200606061620060075afa1416925061618051616260526161a05161628052616200516162a052616220516162c0528260016040616260608061626060065afa141692507f0bedd790f32406f2c76b410ee92f0ca9a393d7989db2486932a87f858649d8566162e0527f125d3a4a294f939d7e16303fdc151fbeac12b8557226b6e0c8cb8a7bddf37482616300526152e0516163205282600160406162e060606162e060075afa14169250616260516163405261628051616360526162e05161638052616300516163a0528260016040616340608061634060065afa14169250610920516163c052610940516163e052615300516164005282600160406163c060606163c060075afa14169250616340516164205261636051616440526163c051616460526163e051616480528260016040616420608061642060065afa14169250610960516164a052610980516164c052615320516164e05282600160406164a060606164a060075afa14169250616420516165005261644051616520526164a051616540526164c051616560528260016040616500608061650060065afa141692506109a051616580526109c0516165a052615340516165c0528260016040616580606061658060075afa14169250616500516165e052616520516166005261658051616620526165a0516166405282600160406165e060806165e060065afa1416925061088051616660526108a05161668052615360516166a0528260016040616660606061666060075afa141692506165e0516166c052616600516166e0526166605161670052616680516167205282600160406166c060806166c060065afa1416925061078051616740526107a0516167605261550051616780528260016040616740606061674060075afa141692506166c0516167a0526166e0516167c052616740516167e052616760516168005282600160406167a060806167a060065afa141692506107c051616820526107e0516168405261552051616860528260016040616820606061682060075afa141692506167a051616880526167c0516168a052616820516168c052616840516168e0528260016040616880608061688060065afa14169250610800516169005261082051616920526156c051616940528260016040616900606061690060075afa1416925061688051616960526168a05161698052616900516169a052616920516169c0528260016040616960608061696060065afa14169250610840516169e05261086051616a00526156e051616a205282600160406169e060606169e060075afa1416925061696051616a405261698051616a60526169e051616a8052616a0051616aa0528260016040616a406080616a4060065afa1416925061064051616ac05261066051616ae0526157e051616b00528260016040616ac06060616ac060075afa14169250616a4051616b2052616a6051616b4052616ac051616b6052616ae051616b80528260016040616b206080616b2060065afa14169250610f0051616ba052610f2051616bc052615820518103616be0528260016040616ba06060616ba060075afa14169250616b2051616c0052616b4051616c2052616ba051616c4052616bc051616c60528260016040616c006080616c0060065afa14169250610fa051616c8052610fc051616ca05261584051616cc0528260016040616c806060616c8060075afa14169250616c0051616ce052616c2051616d0052616c8051616d2052616ca051616d40528260016040616ce06080616ce060065afa14169250616ce051616d6052616d0051616d8052610fa051616da052610fc051616dc052610fe051616de05261100051616e005261102051616e205261104051616e4052610100616d6020616e605280616e605106616e805280616e8051616e805109616ea05280616e8051600109616ec052616de051616ee052616e0051616f0052616ec051616f20528260016040616ee06060616ee060075afa14169250616d6051616f4052616d8051616f6052616ee051616f8052616f0051616fa0528260016040616f406080616f4060065afa14169250616e2051616fc052616e4051616fe052616ec051617000528260016040616fc06060616fc060075afa14169250616da05161702052616dc05161704052616fc05161706052616fe051617080528260016040617020608061702060065afa14169250616f40516170a052616f60516170c0527f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c26170e0527f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed617100527f090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b617120527f12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa61714052617020516171605261704051617180527f0181624e80f3d6ae28df7e01eaeab1c0e919877a3b8a6b7fbc69a6817d596ea26171a0527f1783d30dcb12d259bb89098addf6280fa4b653be7a152542a28f7b926e27e6486171c0527eae44489d41a0d179e2dfdc03bddd883b7109f8b6ae316a59e815c1a6b353046171e0527f0b2147ab62a386bd63e6de1522109b8c9588ab466f5aadfde8c41ca3749423ee6172005282600160206170a06101806170a060085afa141692508260016170a0511416925082614e8457600080fd5b600080f3"; 10 | 11 | bytes constant proof_1 = hex"000000000000000000000000000000000000000000af06f1de1b4dd625b3b12700000000000000000000000000000000000000000015517331ab5bf18cb1b0a80000000000000000000000000000000000000000000005796ebe996e3deb0135000000000000000000000000000000000000000000c9843b32e69b5ac5ece213000000000000000000000000000000000000000000b1e39a5d92c1895d680f680000000000000000000000000000000000000000000014468adcd3bc48a9d698000000000000000000000000000000000000000000a5079442f7301ed1d8eda1000000000000000000000000000000000000000000b272e41eef6fd456fa873d0000000000000000000000000000000000000000000030038c33ee1bdfd19550000000000000000000000000000000000000000000b7b15d05465ad27106c21a0000000000000000000000000000000000000000004a106c19f2d77b4d9ece58000000000000000000000000000000000000000000000deeea80824e89db114e00000000000000000000000000000000fe2f6fd8f0d09b9e979ffc7afbf9147a00000000000000000000000000000000f588af2cef5395ebc92e22baf76e9e5c00000000000000000000000000000000000000000000000000000000010ed0980000000000000000000000006dc501a9911370285b3ecc2830dd481ffcdda3480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000e90b7bceb6e7df5418fb78d8ee546e9700000000000000000000000000000000c83a08bbccc01a0644d599ccd2a7c2e000000000000000000000000000000000000000000000000000000000f51c53b200000000000000000000000000000000c184aa43a7d24aa4f0cf13d0e8b56c51000000000000000000000000000000006ec2e6c897385970ba04372be8062fac00000000000000000000000000000000f4b86db4389f769313f8c3bb0e04dd31000000000000000000000000000000000000000000000000000000007842ec950000000000000000000000000000000010f6ae46878c09a1a516ef7b3c5ae131000000000000000000000000000000000e6172401a8cb5887a05ed63af6b99f700000000000000000000000000000000e24998adb51b0be73e51d2498e76c174000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d9db270c000000000000000000000000000000001b5e3bd161e8c8503c55ceabee7095520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d9db270c000000000000000000000000000000001b5e3bd161e8c8503c55ceabee7095520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d9db270c000000000000000000000000000000001b5e3bd161e8c8503c55ceabee7095520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d9db270c000000000000000000000000000000001b5e3bd161e8c8503c55ceabee7095520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d9db270c000000000000000000000000000000001b5e3bd161e8c8503c55ceabee7095521b49306e05eea0454642f66bf7d6971d537c06684e8bd074ce17825405583fe00a4f63f6070afccfc1f17a3045e81920c22ca6de1a8bb47950fd7cc32dddda74292bff3a0a50a0c33aaabb144b580512a9e4e4167c65656c139782ec80a443840509a208eeb303328c4fe02b653df34ce2d4d678bdcd22774efb8ba0d5146ab3112b3afe98f3a9dac7c8451223df191bd861159280605aaff5a6360bdb1439f81506f8df3daeabaf9d57c9a7e4523c6e5d1edca8130ff0fc633844fe673a601114ea481464a415434a9e8417bd7101e5f21a90e94e77afa13e8ef3e937df1376041b023656da572fcac7cde195fb554b6678e15e56578c7a87c3ca72aacdce5d0f425a684f8477fc676334d03d0f50cc75821dca9e21d0a681f691b7af98e4d428e803bb4505ce8d3290caf8cd1fd513dc2fd2ed10bee81b48dad38e16d721e202ab392e54d40190aa928f8ab52f8877fc89e9b60e2986a34fa1713d585760790a9ec21dec594d06f7f3ce30fb2edfdcf490cee71f298b0fd09f219c977faf7b12c26114f6e9a1c1fb38f941f4043a01b44f5fab5de20229f963b48541aa811f0105207bd30af74fbc5824e5a63569383a878f6735b12a842d1333bb1fe36b302c436423509752634bc4736248122e470ffc8664bebe42aa4a0da563e2f5610b047ae5b5379815391df0659bddcfb1e120cdff6f3ea79d742badb023399d07861e0738aea9346f7fc4bb1c5edac2353ed28334afc56581a4c661f0b2e0237ffb29cbc6a920372c1db13c670a5f6b7b3fcd598d16b75ae712fe7238d59be550821ab605a7573ee496894054414da520f615455e058f15038e60ee1dd16e57b3ca2a11b393442aee6ca93c34776c01e0abe1d5c22e4da4085253b78551114479a11486fe6d3ef382c25309ffe33ac7f9f9d70fbe6751b894a05281151cf9f86b9725e707685e43e7f766cf970f8049d41a28e05591e00c9c858819a957308c57f51dc83135c59b55d461fea59526bcbcc3cfe5190d125c2725e31eef72556d60a719040fd6721eeba67aa439745759b768aea20bfda95ebd615475ae6601e7200f14fc2916ad50b29ca300e25254f801c07b9e27cf7e01c75ee30a576e7eefb8e902725404c638aaef716d81f1b6e838c88172df70ded41ba0609952e6e6588cbd0971a5d93e49d32a129fe97ee78bba8308579168c3fb8ccfc72e7a67abe2fff41d87850c2efa0bd01e30b81791d8842a08fb4ddfd6e4df310e3ad8e39c74f38a1c14deaba2efe518f1f1a9f5a3679fdbc3ff3eea6d1234e26b7b6e50450bed312bc7291cf8c8600dcf21c9d0e6ee5f4429dbcaacbd4f939c95e12a8616ea7e972048808070d1cc96bc27bc75d0a5ec56c07adf16c31401b42f5f5d3fbaedcc801172255636f44879146b574372b1f4a96542b807a144dd688ea0e0d606d646a02e6661f6fa201a5ea796b762f1885d837e834715053e88e9748c724f32ce72520bde9a1b1b8e9a5a733755ab2f4c119d0c1dceeee79d733072cdb103f3b377b80baf62642d1c2a8c0f3d06478b4ef9b5a499a1ddc92e6ab56effb4dcba8787d61d5ce08d350c9b076ccbed4fb16a6b63fa60634b6eb84868872cf1db130831df052d8b5a24829809c5a787201359d699c5d5023b3c890619ac926184e278063529af0f72eb0195e6775753123d6ab59603d14807fafe9da797d7b80dd9b795560e675c555bbdc089b8f33395a9241d92d5eaa2f28cb2a558fd4b48d095faf3be136ebdc71a9aa6c52852594c060879ebab17514935509c5bd082afdecbd74fec292a41cbba55cc7fc09a75e5840f2472389ebd3e26f3a87a4c85a2725038f94c17ac7224cff82676a917e53a0e946c18342c353ddca552e31f892d53da2fdcaf01f160ed2ef9c8048c1ef621e8e35ab1604dfcd07da0d0987ac9e9b38d9515992a28b99676aca89ac7ec6a6d518aafa81514c3b296bde51d9204b5d9f8f9d1ee181a3560aa5efecc80d70e96ffc544351c02c8a4bdc0105011f35a69634e9cdf2efcda42b5343b07cacf1260e821c20accedfa6bc8e8e1d6a97aa2f8a8d7fbe11203fc2cac8d2f919bdde6535578b0092e4688ed3bdf2dc1248aa9ff19343a6b0fbeb036b058311578e9946e9e794b2a1c854ad2e174abaff07d7898673bff1c27c077a2420516de849dde89047671424e06743b943cd9096431d9cec34d54180a3a3de0e76db2b4a754b36f25efcbc0a5fb73bafcf76bfec2d30102c9f89f080424df03e3bf565ebdff553681174a2bbf2e430bf6f5e54c41b15a5a7f54811306c97b066897dee34a7fcd82420e485c4cfb4b84ee4e85b775890bcce1aee33c1e2e342ed0616c6cbaa2f75d565ee33fa1254d1affbeba02cb144a9d239bf8041114bfa6ee3dd374fe380ec3f3c59949be8cb446287c595c9476be55a8ae33232091ac6b821113bd71e8874ccce89cdfbb8181d349562e030294cff255ec82521d83569616daa01e46c896fdc7ab3267b64b5620e263de742c856b531b5749f32f94737719b2bb48ef61d14d142e35c725869ba4f85ebe4bf2531f1d9af874112e822aa365126de2b39cd9b83cfbb2e47079977bdd315eb7b8e85b0375a49eb330495f34c97f522d6e979f9fd3d1cfa21266edadc08a58bb2d1e1a067bde67ee16d66aeaf30f7e5f8e47f26860b6cad0ab0489b735bc2f6675e78debdfd9cfa71fad93b228bd6fe5ef4ea6ebde15e670feb8c493bf13ef134a3c99a6949836ea29ce0419d94d52aaeb86b1723066a4187d1e1da24769b5a64ae5ab7f1d10df61"; 12 | 13 | contract ParserTest is Test { 14 | ProofParser parser; 15 | address verifier; 16 | 17 | function setUp() public { 18 | bytes memory vb = verifierBytecode; 19 | assembly { 20 | sstore(verifier.slot, create(0, vb, mload(vb))) 21 | } 22 | parser = new ProofParser(verifier); 23 | } 24 | 25 | function test_parseProof_1() public { 26 | uint[] memory slots = new uint[](SLOTS_PER_PROOF); 27 | uint[] memory values = new uint[](SLOTS_PER_PROOF); 28 | 29 | (bytes32 blockHash, uint blockNumber, address account) = parser 30 | .parseSingleProof(proof_1, slots, values, 0); 31 | 32 | assertEq( 33 | blockHash, 34 | 0xfe2f6fd8f0d09b9e979ffc7afbf9147af588af2cef5395ebc92e22baf76e9e5c 35 | ); 36 | assertEq(blockNumber, 17748120); 37 | assertEq(account, 0x6dC501a9911370285B3ECc2830dd481fFCDDa348); 38 | } 39 | 40 | function test_parseProofs_1() public { 41 | bytes[] memory proofs = new bytes[](1); 42 | proofs[0] = proof_1; 43 | 44 | ( 45 | bytes32 blockHash, 46 | uint blockNumber, 47 | address account, 48 | uint[] memory slots, 49 | uint[] memory values 50 | ) = parser.parseMultipleProofs(proofs); 51 | 52 | assertEq( 53 | blockHash, 54 | 0xfe2f6fd8f0d09b9e979ffc7afbf9147af588af2cef5395ebc92e22baf76e9e5c 55 | ); 56 | assertEq(blockNumber, 17748120); 57 | assertEq(account, 0x6dC501a9911370285B3ECc2830dd481fFCDDa348); 58 | 59 | uint[] memory expectedSlots = new uint[](10); 60 | expectedSlots[0] = 3; 61 | expectedSlots[1] = 4; 62 | expectedSlots[ 63 | 2 64 | ] = 0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0; 65 | expectedSlots[ 66 | 3 67 | ] = 0x6ec2e6c897385970ba04372be8062facf4b86db4389f769313f8c3bb0e04dd31; 68 | expectedSlots[ 69 | 4 70 | ] = 0x0e6172401a8cb5887a05ed63af6b99f7e24998adb51b0be73e51d2498e76c174; 71 | 72 | assertEq(slots, expectedSlots); 73 | 74 | uint SAFE_SINGLETON_ADDR = 0x00_d9db270c1b5e3bd161e8c8503c55ceabee709552; 75 | 76 | uint[] memory expectedValues = new uint[](10); 77 | 78 | expectedValues[0] = 2; 79 | expectedValues[1] = 1; 80 | expectedValues[2] = 0x00_f51c53b2c184aa43a7d24aa4f0cf13d0e8b56c51; 81 | expectedValues[3] = 0x00_7842ec9510f6ae46878c09a1a516ef7b3c5ae131; 82 | expectedValues[4] = 1; 83 | expectedValues[5] = SAFE_SINGLETON_ADDR; 84 | expectedValues[6] = SAFE_SINGLETON_ADDR; 85 | expectedValues[7] = SAFE_SINGLETON_ADDR; 86 | expectedValues[8] = SAFE_SINGLETON_ADDR; 87 | expectedValues[9] = SAFE_SINGLETON_ADDR; 88 | 89 | assertEq(values, expectedValues); 90 | } 91 | 92 | function test_parseState_1() public { 93 | uint[] memory slots = new uint[](10); 94 | slots[0] = 3; 95 | slots[1] = 4; 96 | slots[ 97 | 2 98 | ] = 0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0; 99 | slots[ 100 | 3 101 | ] = 0x6ec2e6c897385970ba04372be8062facf4b86db4389f769313f8c3bb0e04dd31; 102 | slots[ 103 | 4 104 | ] = 0x0e6172401a8cb5887a05ed63af6b99f7e24998adb51b0be73e51d2498e76c174; 105 | 106 | uint SAFE_SINGLETON_ADDR = 0x00_d9db270c1b5e3bd161e8c8503c55ceabee709552; 107 | 108 | uint[] memory values = new uint[](10); 109 | 110 | values[0] = 2; 111 | values[1] = 1; 112 | values[2] = 0x00_f51c53b2c184aa43a7d24aa4f0cf13d0e8b56c51; 113 | values[3] = 0x00_7842ec9510f6ae46878c09a1a516ef7b3c5ae131; 114 | values[4] = 1; 115 | values[5] = SAFE_SINGLETON_ADDR; 116 | values[6] = SAFE_SINGLETON_ADDR; 117 | values[7] = SAFE_SINGLETON_ADDR; 118 | values[8] = SAFE_SINGLETON_ADDR; 119 | values[9] = SAFE_SINGLETON_ADDR; 120 | 121 | (uint ownersCount, uint threshold, address[] memory owners) = parser 122 | .parseState(slots, values); 123 | 124 | address[] memory expectedOwners = new address[](2); 125 | expectedOwners[0] = 0xf51C53b2C184Aa43A7d24aA4f0Cf13D0e8b56c51; 126 | expectedOwners[1] = 0x7842Ec9510F6AE46878C09a1a516Ef7b3C5Ae131; 127 | 128 | assertEq(ownersCount, 2); 129 | assertEq(threshold, 1); 130 | assertEq(owners, expectedOwners); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /contracts/test/SunflowerSafePlugin.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/Test.sol"; 5 | import "./ProofParser.t.sol"; 6 | 7 | import "../src/SunflowerSafePlugin.sol"; 8 | import "../src/OptimismBlockCache.sol"; 9 | 10 | contract OptimismBlockCacheMock is OptimismBlockCache { 11 | function setTimestamp(bytes32 blockHash, uint64 timestamp) public { 12 | getTimestamp[blockHash] = timestamp; 13 | } 14 | } 15 | 16 | contract SafeMock is ISafe { 17 | function execTransactionFromModule( 18 | address payable, 19 | uint256, 20 | bytes calldata, 21 | uint8 22 | ) external returns (bool success) { 23 | assembly { 24 | sstore(0, 0) 25 | } 26 | return true; 27 | } 28 | 29 | function execTransactionFromModuleReturnData( 30 | address, 31 | uint256, 32 | bytes memory, 33 | uint8 34 | ) external returns (bool success, bytes memory returnData) { 35 | assembly { 36 | sstore(0, 0) 37 | } 38 | return (true, hex""); 39 | } 40 | } 41 | 42 | contract SunflowerSafePluginTest is Test { 43 | OptimismBlockCacheMock blockCache; 44 | address verifier; 45 | SafeMock safe; 46 | 47 | SunflowerSafePlugin plugin; 48 | 49 | function setUp() public { 50 | bytes memory vb = verifierBytecode; 51 | assembly { 52 | sstore(verifier.slot, create(0, vb, mload(vb))) 53 | } 54 | blockCache = new OptimismBlockCacheMock(); 55 | safe = new SafeMock(); 56 | plugin = new SunflowerSafePlugin(blockCache, verifier); 57 | } 58 | 59 | function testExecTransaction() public { 60 | bytes[] memory zkProof = new bytes[](1); 61 | zkProof[0] = proof_1; 62 | 63 | blockCache.setTimestamp( 64 | 0xfe2f6fd8f0d09b9e979ffc7afbf9147af588af2cef5395ebc92e22baf76e9e5c, 65 | uint64(block.timestamp) 66 | ); 67 | 68 | plugin.executeTransaction({ 69 | manager: ISafeProtocolManager(address(0)), 70 | safe: safe, 71 | action: SafeProtocolAction({ 72 | to: payable(0x1111111111111111111111111111111111111111), 73 | value: 0, 74 | data: hex"" 75 | }), 76 | operation: 0, 77 | zkProof: zkProof, 78 | l1OwnerSignatures: hex"a87648a5ebb4e55877b320a87d9d2d1cb9ad2890fbfc0aae20757b3513b61b1d71ba49ac3482b29df2e83a33c84e6944cafb8b398e2d1d5ab16737b66c4150851c6d686719f9d57222998fb23cfe4bc8ff945526aa0a58244b8a04e7e26c33ee343b406577e9bc06dba2f7665781092ad2139b6538ef6be764bd7a8163d38dede11b" 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.14.1", 7 | "@testing-library/react": "^13.0.0", 8 | "@testing-library/user-event": "^13.2.1", 9 | "@types/jest": "^27.0.1", 10 | "@types/node": "^16.7.13", 11 | "@types/react": "^18.0.0", 12 | "@types/react-dom": "^18.0.0", 13 | "axios": "^1.4.0", 14 | "bootstrap": "^5.3.0", 15 | "ethers": "5", 16 | "react": "^18.2.0", 17 | "react-bootstrap": "^2.8.0", 18 | "react-dom": "^18.2.0", 19 | "react-scripts": "5.0.1", 20 | "typescript": "^4.4.2", 21 | "web-vitals": "^2.1.0" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zemse/sunflower/b99bf4843a78fc360fe4c7bff1521b706bb4b6f4/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zemse/sunflower/b99bf4843a78fc360fe4c7bff1521b706bb4b6f4/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zemse/sunflower/b99bf4843a78fc360fe4c7bff1521b706bb4b6f4/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import logo from './logo.svg'; 3 | // import './App.css'; 4 | import { BigNumber, BigNumberish, ethers } from 'ethers'; 5 | import { safeL1, pluginL2, optimism } from './ethers'; 6 | import { Button, Container, Form, ListGroup, Nav, NavDropdown, Navbar, Spinner } from 'react-bootstrap'; 7 | import { keccak256 } from 'ethers/lib/utils'; 8 | import axios from 'axios'; 9 | 10 | 11 | 12 | function App() { 13 | let [owners, setOwners] = useState(); 14 | useEffect(() => { 15 | safeL1.getOwners().then((owners: string[]) => { 16 | setOwners(owners); 17 | }) 18 | }, []) 19 | 20 | let [pluginNonce, setPluginNonce] = useState(); 21 | useEffect(() => { 22 | pluginL2.pluginNonce().then((nonce: BigNumber) => { 23 | setPluginNonce(nonce.toNumber()); 24 | }) 25 | }, []) 26 | 27 | 28 | let [page, setPage] = useState(1); 29 | 30 | 31 | // L2 32 | // 1 - create 33 | // 2 - loading 34 | // 3 - generating proof 35 | // 4 - sign in meantime 36 | // 5 - proof generated, submitting 37 | // 6 - submitted successfully 38 | // 7 - there was an error 39 | let [l2State, setL2State] = useState(1); 40 | 41 | let [to, setTo] = useState(); 42 | let [data, setData] = useState(); 43 | 44 | let [proof, setProof] = useState(); 45 | 46 | let [signA_state, set_signA_state] = useState(1); 47 | let [signB_state, set_signB_state] = useState(1); 48 | let [sigA, setSigA] = useState(); 49 | let [sigB, setSigB] = useState(); 50 | 51 | let [finalSubmitState, setFinalSubmitState] = useState(1); 52 | 53 | async function fetchProof() { 54 | // await new Promise(res => setTimeout(res, 1000)); 55 | // return '0x12222' 56 | const resp = await axios.get("http://localhost:8000/gen_proof?address=0x6dC501a9911370285B3ECc2830dd481fFCDDa348", { 57 | timeout: 1_000_000 58 | }) 59 | return resp.data 60 | } 61 | 62 | async function submit() { 63 | await pluginL2.connect(new ethers.Wallet( 64 | process.env.REACT_APP_PK_27!, optimism 65 | )).executeTransaction("0x0000000000000000000000000000000000000000", "0x8eB9B5F9b631a52F9c9a47F574ec9eF5d3641421", [ 66 | to, 0, data 67 | ], 0, ['0x'+proof], ethers.utils.concat([sigA!, sigB!])).then(() => setFinalSubmitState(3)) 68 | } 69 | 70 | 71 | function sign(is78: boolean) { 72 | function encodeTx( 73 | to: string, 74 | value: BigNumberish, 75 | data: string, 76 | operation: number, 77 | chainId: number, 78 | nonce: number 79 | ) { 80 | let safeTxHash = keccak256( 81 | ethers.utils.defaultAbiCoder.encode( 82 | ["address", "uint256", "bytes32", "uint8", "uint256"], 83 | [to, value, keccak256(data), operation, nonce] 84 | ) 85 | ); 86 | 87 | return ethers.utils.concat([ 88 | "0x19", 89 | "0x01", 90 | ethers.utils.id("SunflowerSafePluginEthParis"), 91 | ethers.utils.hexZeroPad(ethers.utils.hexlify(chainId), 32), 92 | safeTxHash, 93 | ]); 94 | } 95 | if(!to) throw new Error('to address is required'); 96 | if(!data) throw new Error('calldata is required'); 97 | let encoded = encodeTx( 98 | to, 99 | 0, 100 | data, 101 | 0, 102 | 10, // optimism 103 | 0 104 | ); 105 | 106 | let encodedHash = keccak256(encoded); 107 | 108 | const wallet = new ethers.Wallet( 109 | is78 ? process.env.REACT_APP_PK_78! : process.env.REACT_APP_PK_27! 110 | ); 111 | let sig = wallet._signingKey().signDigest(encodedHash); 112 | return ethers.utils.joinSignature(sig) 113 | } 114 | 115 | return ( 116 |
117 | 118 | 119 | sunflower gnosis safe plugin demo 120 | 124 | 125 | 126 | 127 | 128 | {page === 1 ? <> 129 | 130 |

safe address on L1:

131 | 132 | 0x6dC501a9911370285B3ECc2830dd481fFCDDa348 133 | 134 |

l1 safe owners - 2 of 3 multisig

135 | 136 | {owners?.map(o => {o})} 137 | 138 | : null} 139 | 140 | 141 | {page === 2 ? <> 142 |

safe address on L2:

143 | 144 | 0x8eB9B5F9b631a52F9c9a47F574ec9eF5d3641421 145 | 146 |

create new safe transaction on l2 with nonce {pluginNonce}

147 |
148 | 149 | setTo(e.target.value)} /> 150 | 151 | 152 | 153 | setData(e.target.value)} /> 154 | 155 | 156 | {!proof ? <> 157 | 178 | : <> 179 | 183 | 184 | } 185 | 186 | 187 | 188 | 189 | {l2State === 4 && (!proof || !sigA || !sigB) ? <> 190 |

{l2State === 4? <>in the mean time we can collect signatures: : null}

191 | 197 | 198 | 204 | : null} 205 |
: null} 206 | 207 | {sigA || sigB ? <> 208 |

Signatures:

209 | 210 | {sigA ? {sigA} : null} 211 | {sigB ? {sigB} : null} 212 | 213 | : null} 214 | 215 | {proof ? <> 216 |

proof:

217 | 218 | {proof} 219 | 220 | : null} 221 |
222 |
223 | ); 224 | } 225 | 226 | export default App; 227 | -------------------------------------------------------------------------------- /frontend/src/ethers.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | export const mainnet = new ethers.providers.JsonRpcProvider( 4 | "https://eth-mainnet.g.alchemy.com/v2/JIFe1wEUANNSU8m_zEdclSaq6hPT_3ch" 5 | ); 6 | export const optimism = new ethers.providers.JsonRpcProvider( 7 | "https://opt-mainnet.g.alchemy.com/v2/JIFe1wEUANNSU8m_zEdclSaq6hPT_3ch" 8 | ); 9 | 10 | export const safeL1 = new ethers.Contract( 11 | "0x6dC501a9911370285B3ECc2830dd481fFCDDa348", 12 | ["function getOwners() public view returns (address[] memory)"], 13 | mainnet 14 | ); 15 | export const safeL2 = new ethers.Contract( 16 | "0x8eB9B5F9b631a52F9c9a47F574ec9eF5d3641421", 17 | ["function getOwners() public view returns (address[] memory)"], 18 | optimism 19 | ); 20 | export const pluginL2 = new ethers.Contract( 21 | "0x28F22358159f4B48F7E54E352ACF6a637F06Ab72", 22 | [ 23 | "function executeTransaction(address manager,address safe,(address,uint256,bytes) calldata action,uint8 operation,bytes[] calldata zkProof,bytes calldata l1OwnerSignatures) external", 24 | "function pluginNonce() public view returns (uint256)", 25 | ], 26 | optimism 27 | ); 28 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot( 8 | document.getElementById('root') as HTMLElement 9 | ); 10 | root.render( 11 | 12 | 13 | 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /data 3 | /params -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | ethers = { git = "https://github.com/gakonst/ethers-rs" } 10 | rocket = "=0.5.0-rc.3" 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "1.0" 13 | tokio = { version = "1", features = ["full"] } 14 | 15 | axiom-eth = { path = "/Users/sohamzemse/Workspace/cloned-repos/axiom-eth/axiom-eth" } 16 | snark-verifier = { path = "/Users/sohamzemse/Workspace/cloned-repos/axiom-snark-verifier/snark-verifier", default-features = false, features = ["loader_halo2"], optional = true } 17 | snark-verifier-sdk = { path = "/Users/sohamzemse/Workspace/cloned-repos/axiom-snark-verifier/snark-verifier-sdk", default-features = false, features = ["loader_halo2"], optional = true } -------------------------------------------------------------------------------- /server/configs/mainnet_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 19, 4 | "num_rlc_columns": 1, 5 | "num_range_advice": [ 6 | 4, 7 | 2, 8 | 0 9 | ], 10 | "num_lookup_advice": [ 11 | 1, 12 | 1, 13 | 0 14 | ], 15 | "num_fixed": 1, 16 | "unusable_rows": 77, 17 | "keccak_rows_per_round": 50, 18 | "lookup_bits": 8 19 | }, 20 | "break_points": { 21 | "gate": [ 22 | [ 23 | 524178, 24 | 524178, 25 | 524178 26 | ], 27 | [ 28 | 524178 29 | ], 30 | [] 31 | ], 32 | "rlc": [] 33 | } 34 | } -------------------------------------------------------------------------------- /server/configs/mainnet_10.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 19, 4 | "num_rlc_columns": 2, 5 | "num_range_advice": [ 6 | 17, 7 | 10, 8 | 0 9 | ], 10 | "num_lookup_advice": [ 11 | 1, 12 | 1, 13 | 0 14 | ], 15 | "num_fixed": 1, 16 | "unusable_rows": 77, 17 | "keccak_rows_per_round": 25, 18 | "lookup_bits": 8 19 | }, 20 | "break_points": { 21 | "gate": [ 22 | [ 23 | 524226, 24 | 524226, 25 | 524228, 26 | 524228, 27 | 524228, 28 | 524228, 29 | 524227, 30 | 524228, 31 | 524227, 32 | 524228, 33 | 524227, 34 | 524228, 35 | 524226, 36 | 524226, 37 | 524226, 38 | 524226 39 | ], 40 | [ 41 | 524228, 42 | 524228, 43 | 524226, 44 | 524228, 45 | 524228, 46 | 524228, 47 | 524228, 48 | 524228, 49 | 524228 50 | ], 51 | [] 52 | ], 53 | "rlc": [ 54 | 524227 55 | ] 56 | } 57 | } -------------------------------------------------------------------------------- /server/configs/mainnet_10_evm.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 24, 4 | "num_advice": 2, 5 | "num_lookup_advice": 1, 6 | "num_fixed": 1, 7 | "lookup_bits": 22 8 | }, 9 | "break_points": [ 10 | [ 11 | 16777205 12 | ], 13 | [], 14 | [] 15 | ] 16 | } -------------------------------------------------------------------------------- /server/configs/mainnet_1_evm.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 24, 4 | "num_advice": 1, 5 | "num_lookup_advice": 1, 6 | "num_fixed": 1, 7 | "lookup_bits": 22 8 | }, 9 | "break_points": [ 10 | [], 11 | [], 12 | [] 13 | ] 14 | } -------------------------------------------------------------------------------- /server/configs/mainnet_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 19, 4 | "num_rlc_columns": 1, 5 | "num_range_advice": [ 6 | 4, 7 | 2, 8 | 0 9 | ], 10 | "num_lookup_advice": [ 11 | 1, 12 | 1, 13 | 0 14 | ], 15 | "num_fixed": 1, 16 | "unusable_rows": 77, 17 | "keccak_rows_per_round": 50, 18 | "lookup_bits": 8 19 | }, 20 | "break_points": { 21 | "gate": [ 22 | [ 23 | 524178, 24 | 524178, 25 | 524178 26 | ], 27 | [ 28 | 524178 29 | ], 30 | [] 31 | ], 32 | "rlc": [] 33 | } 34 | } -------------------------------------------------------------------------------- /server/configs/mainnet_2_evm.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 24, 4 | "num_advice": 1, 5 | "num_lookup_advice": 1, 6 | "num_fixed": 1, 7 | "lookup_bits": 22 8 | }, 9 | "break_points": [ 10 | [], 11 | [], 12 | [] 13 | ] 14 | } -------------------------------------------------------------------------------- /server/configs/mainnet_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 19, 4 | "num_rlc_columns": 1, 5 | "num_range_advice": [ 6 | 4, 7 | 2, 8 | 0 9 | ], 10 | "num_lookup_advice": [ 11 | 1, 12 | 1, 13 | 0 14 | ], 15 | "num_fixed": 1, 16 | "unusable_rows": 77, 17 | "keccak_rows_per_round": 50, 18 | "lookup_bits": 8 19 | }, 20 | "break_points": { 21 | "gate": [ 22 | [ 23 | 524178, 24 | 524178, 25 | 524178 26 | ], 27 | [ 28 | 524178 29 | ], 30 | [] 31 | ], 32 | "rlc": [] 33 | } 34 | } -------------------------------------------------------------------------------- /server/configs/mainnet_3_evm.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 24, 4 | "num_advice": 1, 5 | "num_lookup_advice": 1, 6 | "num_fixed": 1, 7 | "lookup_bits": 22 8 | }, 9 | "break_points": [ 10 | [], 11 | [], 12 | [] 13 | ] 14 | } -------------------------------------------------------------------------------- /server/configs/mainnet_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 19, 4 | "num_rlc_columns": 1, 5 | "num_range_advice": [ 6 | 4, 7 | 2, 8 | 0 9 | ], 10 | "num_lookup_advice": [ 11 | 1, 12 | 1, 13 | 0 14 | ], 15 | "num_fixed": 1, 16 | "unusable_rows": 77, 17 | "keccak_rows_per_round": 50, 18 | "lookup_bits": 8 19 | }, 20 | "break_points": { 21 | "gate": [ 22 | [ 23 | 524178, 24 | 524178, 25 | 524178 26 | ], 27 | [ 28 | 524178 29 | ], 30 | [] 31 | ], 32 | "rlc": [] 33 | } 34 | } -------------------------------------------------------------------------------- /server/configs/mainnet_4_evm.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 24, 4 | "num_advice": 1, 5 | "num_lookup_advice": 1, 6 | "num_fixed": 1, 7 | "lookup_bits": 22 8 | }, 9 | "break_points": [ 10 | [], 11 | [], 12 | [] 13 | ] 14 | } -------------------------------------------------------------------------------- /server/configs/mainnet_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 19, 4 | "num_rlc_columns": 1, 5 | "num_range_advice": [ 6 | 9, 7 | 6, 8 | 0 9 | ], 10 | "num_lookup_advice": [ 11 | 1, 12 | 1, 13 | 0 14 | ], 15 | "num_fixed": 1, 16 | "unusable_rows": 77, 17 | "keccak_rows_per_round": 46, 18 | "lookup_bits": 8 19 | }, 20 | "break_points": { 21 | "gate": [ 22 | [ 23 | 524177, 24 | 524176, 25 | 524178, 26 | 524176, 27 | 524177, 28 | 524178, 29 | 524178, 30 | 524177 31 | ], 32 | [ 33 | 524177, 34 | 524177, 35 | 524177, 36 | 524178, 37 | 524178 38 | ], 39 | [] 40 | ], 41 | "rlc": [] 42 | } 43 | } -------------------------------------------------------------------------------- /server/configs/mainnet_5_evm.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 24, 4 | "num_advice": 2, 5 | "num_lookup_advice": 1, 6 | "num_fixed": 1, 7 | "lookup_bits": 22 8 | }, 9 | "break_points": [ 10 | [ 11 | 16777205 12 | ], 13 | [], 14 | [] 15 | ] 16 | } -------------------------------------------------------------------------------- /server/configs/mainnet_6.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 19, 4 | "num_rlc_columns": 1, 5 | "num_range_advice": [ 6 | 11, 7 | 6, 8 | 0 9 | ], 10 | "num_lookup_advice": [ 11 | 1, 12 | 1, 13 | 0 14 | ], 15 | "num_fixed": 1, 16 | "unusable_rows": 77, 17 | "keccak_rows_per_round": 40, 18 | "lookup_bits": 8 19 | }, 20 | "break_points": { 21 | "gate": [ 22 | [ 23 | 524178, 24 | 524178, 25 | 524177, 26 | 524178, 27 | 524177, 28 | 524178, 29 | 524177, 30 | 524178, 31 | 524177, 32 | 524176 33 | ], 34 | [ 35 | 524177, 36 | 524177, 37 | 524177, 38 | 524178, 39 | 524178 40 | ], 41 | [] 42 | ], 43 | "rlc": [] 44 | } 45 | } -------------------------------------------------------------------------------- /server/configs/mainnet_6_evm.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "degree": 24, 4 | "num_advice": 2, 5 | "num_lookup_advice": 1, 6 | "num_fixed": 1, 7 | "lookup_bits": 22 8 | }, 9 | "break_points": [ 10 | [ 11 | 16777206 12 | ], 13 | [], 14 | [] 15 | ] 16 | } -------------------------------------------------------------------------------- /server/rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2022-10-28 -------------------------------------------------------------------------------- /server/src/main.rs: -------------------------------------------------------------------------------- 1 | mod prover; 2 | mod slots; 3 | 4 | use ethers::{ 5 | prelude::SignerMiddleware, 6 | providers::{Http, Middleware, Provider}, 7 | signers::LocalWallet, 8 | types::{Address, Bytes, Eip1559TransactionRequest, U256}, 9 | }; 10 | use slots::GenerateSlotsResult; 11 | use std::{env, thread}; 12 | 13 | #[rocket::get("/gen_slots?
")] 14 | async fn gen_slots(address: &str) -> String { 15 | let result = slots::generate(address.parse().unwrap(), None) 16 | .await 17 | .unwrap(); 18 | serde_json::to_string(&result).unwrap() 19 | } 20 | 21 | // static GLOBAL_PROOF_FLAG: Mutex = Mutex::new(false); 22 | 23 | #[rocket::get("/gen_proof?
")] 24 | async fn gen_proof(address: &str) -> String { 25 | println!("address: {address}"); 26 | 27 | // ethers::signers::LocalWallet 28 | let wallet = env::var("PROVER_PRIVATE_KEY") 29 | .unwrap() 30 | .parse::() 31 | .unwrap(); 32 | 33 | let provider = Provider::::try_from( 34 | std::env::var("OPTIMISM_RPC_URL").expect("please pass JSON_RPC_URL env var"), 35 | ) 36 | .unwrap(); 37 | 38 | let client = SignerMiddleware::new_with_provider_chain(provider, wallet) 39 | .await 40 | .unwrap(); 41 | 42 | let tx = Eip1559TransactionRequest::new() 43 | .to("0xcf7d2b6bffc26497c8f759363d6baeda5074d7f1" 44 | .parse::
() 45 | .unwrap()) 46 | .data("0x2ae3594a".parse::().unwrap()); 47 | 48 | let pending_tx = client.send_transaction(tx, None).await.unwrap(); 49 | 50 | // get the mined tx 51 | let receipt = pending_tx.await.unwrap().expect("tx dropped from mempool"); 52 | 53 | let data = receipt.logs[0].data.to_owned(); 54 | let (bk, _) = data.split_at(32); 55 | 56 | let block_number = U256::from_big_endian(bk).as_u32(); 57 | println!("block_number: {block_number}"); 58 | 59 | let GenerateSlotsResult { slots, values: _ } = 60 | slots::generate(address.parse().unwrap(), Some(block_number)) 61 | .await 62 | .unwrap(); 63 | 64 | let address_parsed: Address = address.parse().unwrap(); 65 | 66 | println!("{slots:?}"); 67 | 68 | // let block_number = 17748120; // TODO pin block hash on optimism and record the block hash 69 | let result = thread::spawn(move || { 70 | println!("start"); 71 | prover::prove(block_number, address_parsed, slots) 72 | }) 73 | .join() 74 | .expect("Thread panicked"); 75 | // *global_proof_flag = false; 76 | result 77 | // serde_json::to_string(&result).unwrap() 78 | } 79 | 80 | #[rocket::launch] 81 | fn rocket() -> _ { 82 | rocket::build().mount("/", rocket::routes![gen_slots, gen_proof]) 83 | } 84 | 85 | // #[tokio::main] 86 | // async fn main() { 87 | // // prover::prove( 88 | // // 17669947, 89 | // // "0x6B175474E89094C44Da98b954EedeAC495271d0F" 90 | // // .parse() 91 | // // .unwrap(), 92 | // // vec![ 93 | // // "0x6ec2e6c897385970ba04372be8062facf4b86db4389f769313f8c3bb0e04dd31" 94 | // // .parse() 95 | // // .unwrap(), 96 | // // ], 97 | // // ); 98 | 99 | // // println!("first"); 100 | 101 | // // let block_number = 17669947; // TODO pin block hash on optimism and record the block hash 102 | // // let address = "0x6dC501a9911370285B3ECc2830dd481fFCDDa348" 103 | // // .parse() 104 | // // .unwrap(); 105 | // // let GenerateSlotsResult { slots, values: _ } = slots::generate(address).await.unwrap(); 106 | // // // let res = prover::prove(block_number, address, slots); 107 | 108 | // let result = thread::spawn(move || { 109 | // let block_number = 17669947; // TODO pin block hash on optimism and record the block hash 110 | // prover::prove( 111 | // 17748120, 112 | // "0x6dC501a9911370285B3ECc2830dd481fFCDDa348" 113 | // .parse() 114 | // .unwrap(), 115 | // vec![ 116 | // "0x0000000000000000000000000000000000000000000000000000000000000003" 117 | // .parse() 118 | // .unwrap(), 119 | // "0x0000000000000000000000000000000000000000000000000000000000000004" 120 | // .parse() 121 | // .unwrap(), 122 | // "0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0" 123 | // .parse() 124 | // .unwrap(), 125 | // "0x6ec2e6c897385970ba04372be8062facf4b86db4389f769313f8c3bb0e04dd31" 126 | // .parse() 127 | // .unwrap(), 128 | // "0x0e6172401a8cb5887a05ed63af6b99f7e24998adb51b0be73e51d2498e76c174" 129 | // .parse() 130 | // .unwrap(), 131 | // // "0x000000000000000000000000000000000000000000000000000000000000dead" 132 | // // .parse() 133 | // // .unwrap(), 134 | // ], 135 | // ) 136 | // }) 137 | // .join() 138 | // .expect("Thread panicked"); 139 | 140 | // println!("Hello, world! {:?}", result); 141 | // } 142 | -------------------------------------------------------------------------------- /server/src/prover.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use axiom_eth::{ 4 | storage::helpers::{StorageScheduler, StorageTask}, 5 | util::scheduler::{evm_wrapper::Wrapper::ForEvm, Scheduler}, 6 | Network, 7 | }; 8 | use ethers::types::{Address, H256}; 9 | 10 | // this needs JSON_RPC_URL env var to be set 11 | pub fn prove(block_number: u32, address: Address, slots: Vec) -> String { 12 | let scheduler = StorageScheduler::new( 13 | Network::Mainnet, 14 | false, 15 | false, 16 | PathBuf::from("configs"), 17 | PathBuf::from("data"), 18 | ); 19 | let task = StorageTask::new(block_number, address, slots, Network::Mainnet); 20 | 21 | scheduler.get_calldata(ForEvm(task), true) 22 | } 23 | -------------------------------------------------------------------------------- /server/src/slots.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use ethers::{ 4 | abi::{self, Address}, 5 | providers::{Http, Middleware, Provider}, 6 | types::{BigEndianHash, BlockId, BlockNumber, H256, U256, U64}, 7 | utils::keccak256, 8 | }; 9 | 10 | use serde::{Deserialize, Serialize}; 11 | 12 | const SLOTS_LENGTH: usize = 10; 13 | 14 | #[derive(Serialize, Deserialize)] 15 | pub struct GenerateSlotsResult { 16 | pub slots: Vec, 17 | pub values: Vec, 18 | } 19 | 20 | pub async fn generate( 21 | addr: Address, 22 | block_number: Option, 23 | ) -> Result { 24 | let block_id = if block_number.is_some() { 25 | BlockId::Number(BlockNumber::Number(U64::from(block_number.unwrap()))) 26 | } else { 27 | BlockId::Number(BlockNumber::Latest) 28 | }; 29 | 30 | let provider = Provider::::try_from( 31 | std::env::var("JSON_RPC_URL").expect("please pass JSON_RPC_URL env var"), 32 | ) 33 | .unwrap(); 34 | 35 | let owners_mapping_slot = H256::from_uint(&U256::from(2)); 36 | let owners_count_slot = H256::from_uint(&U256::from(3)); 37 | let threshold_count_slot = H256::from_uint(&U256::from(4)); 38 | 39 | let mut slots = vec![owners_count_slot, threshold_count_slot]; 40 | let mut values = vec![ 41 | provider 42 | .get_storage_at(addr, slots[0], Some(block_id)) 43 | .await 44 | .unwrap() 45 | .into_uint(), 46 | provider 47 | .get_storage_at(addr, slots[1], Some(block_id)) 48 | .await 49 | .unwrap() 50 | .into_uint(), 51 | ]; 52 | 53 | let mut key = U256::from(1); 54 | loop { 55 | let slot = hash_two(key, owners_mapping_slot); 56 | slots.push(slot); 57 | 58 | let value = provider 59 | .get_storage_at(addr, slot, Some(block_id)) 60 | .await 61 | .unwrap() 62 | .into_uint(); 63 | values.push(value); 64 | 65 | if key == U256::one() && value == U256::zero() { 66 | return Err(format!("The address {addr} is probably not a valid gnosis safe since the owners mapping slot at sentinal address should be non-zero")); 67 | } 68 | 69 | if value == U256::one() { 70 | break; 71 | } else { 72 | key = value; 73 | } 74 | } 75 | 76 | let u256_singleton = U256::from_str("0xd9db270c1b5e3bd161e8c8503c55ceabee709552").unwrap(); 77 | 78 | // TODO handle overflow of slots length 79 | while slots.len() < SLOTS_LENGTH { 80 | slots.push(H256::zero()); 81 | values.push(u256_singleton) 82 | } 83 | 84 | Ok(GenerateSlotsResult { slots, values }) 85 | } 86 | 87 | fn hash_two(one: U256, two: H256) -> H256 { 88 | let bytes = abi::encode(&[abi::Token::Uint(one), abi::Token::Uint(two.into_uint())]); 89 | H256::from(keccak256(bytes)) 90 | } 91 | --------------------------------------------------------------------------------