├── .env.sample ├── remappings.txt ├── .gitignore ├── foundry.toml ├── .gitmodules ├── src ├── interfaces │ ├── IDataAttestation.sol │ ├── IERC20.sol │ └── IFriendtechSharesV1.sol ├── pattern │ ├── Delegated.sol │ ├── Banked.sol │ ├── Permissioned.sol │ └── Configured.sol ├── FriendtechManager.sol └── Frenrug.sol ├── .pre-commit-config.yaml ├── .github └── workflows │ └── test_contracts.yml ├── LICENSE ├── test ├── Delegated.t.sol ├── mocks │ └── MockKey.sol ├── Configured.t.sol ├── Permissioned.t.sol ├── Banked.t.sol ├── interfaces │ └── FriendtechSharesV1.sol ├── deployed │ └── DataAttestation.sol └── Frenrug.t.sol ├── scripts ├── UpdateVerifier.sol └── Deploy.sol ├── Makefile ├── .gas-snapshot ├── README.md └── compiled └── Verifier.sol └── Verifier.sol /.env.sample: -------------------------------------------------------------------------------- 1 | # Environment variables consumed in Forge scripts (./scripts) 2 | 3 | # RPC URL 4 | RPC_URL=http://localhost:8545 5 | # Deployer private key 6 | PRIVATE_KEY=0x... 7 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | solady/=lib/solady/src 2 | @prb/math/=lib/prb-math/src 3 | forge-std/=lib/forge-std/src 4 | solady-tests/=lib/solady/test 5 | infernet/=lib/infernet-sdk/src 6 | infernet-tests/=lib/infernet-sdk/test 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | broadcast/ 7 | 8 | # Docs 9 | docs/ 10 | 11 | # Dotenv file 12 | .env 13 | 14 | # VSCode workspace config 15 | .vscode/ 16 | 17 | # OS cache 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | solc-version = "0.8.19" 6 | optimizer_runs = 1000000 7 | evm_version = "paris" 8 | via_ir = true 9 | 10 | [fmt] 11 | number_underscore = "thousands" 12 | single_line_statement_blocks = "multi" 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/solady"] 5 | path = lib/solady 6 | url = https://github.com/vectorized/solady 7 | [submodule "lib/prb-math"] 8 | path = lib/prb-math 9 | url = https://github.com/PaulRBerg/prb-math 10 | [submodule "lib/infernet-sdk"] 11 | path = lib/infernet-sdk 12 | url = https://github.com/ritual-net/infernet-sdk 13 | -------------------------------------------------------------------------------- /src/interfaces/IDataAttestation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @title IDataAttestation 5 | /// @notice EZKL Data Attestation contract interface to verify DA + proof 6 | /// @dev Uses: https://docs.ezkl.xyz/visibility_what_is_private/#data-provenance-signatures-and-linking-data 7 | interface IDataAttestation { 8 | /// @notice Verifies inputs against contract, verifies proof 9 | /// @param verifier EZKL verifier address 10 | /// @param encoded DA + proof data 11 | /// @return success status 12 | function verifyWithDataAttestation(address verifier, bytes calldata encoded) external view returns (bool); 13 | } 14 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | # Default pre-commit hooks 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v3.2.0 5 | hooks: 6 | # Ensure EOF exists 7 | - id: end-of-file-fixer 8 | # Prevent adding large files 9 | - id: check-added-large-files 10 | args: ["--maxkb=5000"] 11 | # Solidity pre-commit hooks 12 | - repo: local 13 | hooks: 14 | - id: lint 15 | name: Lint solidity files via forge fmt 16 | description: Lint solidity code via forge fmt 17 | language: system 18 | stages: [commit] 19 | entry: bash -c 'make format' 20 | pass_filenames: false 21 | -------------------------------------------------------------------------------- /src/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @title IERC20 5 | /// @notice Basic ERC20-compliant token interface 6 | interface IERC20 { 7 | /// @notice Returns the amount of tokens owned by `account` 8 | /// @param account address to check 9 | /// @return balance of `account` 10 | function balanceOf(address account) external view returns (uint256); 11 | 12 | /// @notice Moves a `value` amount of tokens from the caller's account to the `to` account 13 | /// @param to address to move tokens to 14 | /// @param value amount of tokens to move 15 | /// @return successful transfer status 16 | function transfer(address to, uint256 value) external returns (bool); 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/test_contracts.yml: -------------------------------------------------------------------------------- 1 | name: Contracts CI 2 | 3 | on: push 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | run_ci: 10 | strategy: 11 | fail-fast: true 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | submodules: recursive 17 | 18 | - name: Install Foundry 19 | uses: foundry-rs/foundry-toolchain@v1 20 | with: 21 | version: nightly 22 | 23 | - name: Install contract deps 24 | run: forge --version && forge install 25 | id: install 26 | 27 | - name: Build contracts 28 | run: forge build --sizes 29 | id: build 30 | 31 | - name: Copy compiled artifacts 32 | run: cp -r compiled/. out/ 33 | id: artifacts 34 | 35 | - name: Run tests 36 | run: forge test -vvv 37 | id: test 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Frenrug 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/interfaces/IFriendtechSharesV1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @title IFriendtechSharesV1 5 | /// @notice FriendtechSharesV1 contract interface to buy & sell keys 6 | /// @dev Source: https://basescan.org/address/0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4#code 7 | interface IFriendtechSharesV1 { 8 | /// @notice Buy Friendtech keys 9 | /// @param sharesSubject key address 10 | /// @param amount number of keys to purchase 11 | function buyShares(address sharesSubject, uint256 amount) external payable; 12 | 13 | /// @notice Sell Friendtech keys 14 | /// @param sharesSubject key address 15 | /// @param amount number of keys to sell 16 | function sellShares(address sharesSubject, uint256 amount) external payable; 17 | 18 | /// @notice Collect price in ETH to purchase `amount` of `sharesSubject`'s keys 19 | /// @param sharesSubject key address 20 | /// @param amount number of keys to purchase 21 | function getBuyPriceAfterFee(address sharesSubject, uint256 amount) external view returns (uint256); 22 | } 23 | -------------------------------------------------------------------------------- /test/Delegated.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {FrenrugTest} from "./Frenrug.t.sol"; 5 | import {Ownable} from "solady/auth/Ownable.sol"; 6 | 7 | /// @title DelegatedTest 8 | /// @notice Tests isolated `Delegated.sol` functionality 9 | contract DelelegatedTest is FrenrugTest { 10 | /// @notice Delegatee is properly initialized 11 | function testDelegateeIsProperlyInitialized() public { 12 | assertEq(FRENRUG.signer(), DELEGATEE_ADDRESS); 13 | } 14 | 15 | /// @notice Delegatee can be updated by owner 16 | function testOwnerCanUpdateDelegatee() public { 17 | // Assert initial 18 | assertEq(FRENRUG.signer(), DELEGATEE_ADDRESS); 19 | 20 | // Update delegatee 21 | FRENRUG.updateDelegatee(address(1)); 22 | 23 | // Assert new 24 | assertEq(FRENRUG.signer(), address(1)); 25 | } 26 | 27 | /// @notice Delegatee cannot be updated by non-owner 28 | function testNonOwnerCannotUpdatedDelegatee() public { 29 | // Mock non-owner 30 | vm.startPrank(address(1)); 31 | 32 | // Attempt to update delegatee 33 | vm.expectRevert(Ownable.Unauthorized.selector); 34 | FRENRUG.updateDelegatee(address(1)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/pattern/Delegated.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {Ownable} from "solady/auth/Ownable.sol"; 5 | import {Delegator} from "infernet/pattern/Delegator.sol"; 6 | 7 | /// @title Delegated 8 | /// @notice Allows delegating Infernet-compatible EIP-712 signer 9 | /// @notice Exposes `onlyOwner`-permissioned `updateDelegatee()` function to update EIP-712 signer 10 | /// @dev Defaults to delegating to msg.sender 11 | abstract contract Delegated is Ownable, Delegator { 12 | /*////////////////////////////////////////////////////////////// 13 | CONSTRUCTOR 14 | //////////////////////////////////////////////////////////////*/ 15 | 16 | /// @notice Create new Delegated 17 | /// @dev Initializes delegatee signer to msg.sender 18 | constructor() Delegator(msg.sender) {} 19 | 20 | /*////////////////////////////////////////////////////////////// 21 | FUNCTIONS 22 | //////////////////////////////////////////////////////////////*/ 23 | 24 | /// @notice Allows owner to update delegatee signer 25 | /// @param signer new signer to update 26 | function updateDelegatee(address signer) external onlyOwner { 27 | _updateSigner(signer); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/UpdateVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {console} from "forge-std/console.sol"; 5 | import {Frenrug} from "../src/Frenrug.sol"; 6 | import {Script} from "forge-std/Script.sol"; 7 | import {Configured} from "../src/pattern/Configured.sol"; 8 | 9 | contract UpdateVerifier is Script { 10 | function run() public { 11 | // Setup wallet 12 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 13 | vm.startBroadcast(deployerPrivateKey); 14 | 15 | // Load up Frenrug contract 16 | Configured FRENRUG = Configured(payable(0x...)); 17 | 18 | // Collect current params 19 | (address attestor, uint16 nodes, uint32 maxCallbackGasLimit, address verifier, string memory containerId) = FRENRUG.config(); 20 | console.log(attestor); 21 | console.log(nodes); 22 | console.log(maxCallbackGasLimit); 23 | console.log(verifier); 24 | console.log(containerId); 25 | 26 | // Update just new verifier 27 | Configured.Config memory newConfig = Configured.Config({ 28 | attestor: attestor, 29 | nodes: nodes, 30 | maxCallbackGasLimit: maxCallbackGasLimit, 31 | verifier: 0x..., 32 | containerId: containerId 33 | }); 34 | 35 | // Update config 36 | FRENRUG.updateConfig(newConfig); 37 | 38 | vm.stopBroadcast(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Use bash as shell 2 | SHELL := /bin/bash 3 | 4 | # Load environment variables 5 | ifneq (,$(wildcard ./.env)) 6 | include .env 7 | export 8 | endif 9 | 10 | # Phony targets 11 | .PHONY: install clean build test format docs snapshot diff deploy 12 | 13 | # Default: clean build outputs, install deps, format code, build code, run tests 14 | all: clean install format build test 15 | 16 | # Install dependencies 17 | install: 18 | @forge install 19 | 20 | # Clean build outputs 21 | clean: 22 | @forge clean 23 | 24 | # Build contracts + tests 25 | build: 26 | @forge build 27 | @cp -r compiled/. out/ 28 | 29 | # Run tests 30 | test: 31 | @forge test -vvv 32 | 33 | # Execute scripts/deploy given environment variables 34 | deploy: 35 | @forge script scripts/Deploy.sol:Deploy \ 36 | --broadcast \ 37 | --optimize \ 38 | --optimizer-runs 1000000 \ 39 | --via-ir \ 40 | --use 0.8.19 \ 41 | --extra-output-files abi \ 42 | --rpc-url $(RPC_URL) 43 | 44 | # Execute update verifier script 45 | update-verifier: 46 | @forge script scripts/UpdateVerifier.sol:UpdateVerifier \ 47 | --broadcast \ 48 | --rpc-url $(RPC_URL) 49 | 50 | # Save gas snapshot 51 | snapshot: 52 | @forge snapshot 53 | 54 | # Compare current gas profile to saved gas snapshot 55 | diff: 56 | @forge snapshot --diff 57 | 58 | # Format contracts 59 | format: 60 | @forge fmt 61 | 62 | # Generate and serve docs 63 | docs: 64 | @forge doc --build 65 | @open http://localhost:4000 66 | @forge doc --serve --port 4000 67 | -------------------------------------------------------------------------------- /.gas-snapshot: -------------------------------------------------------------------------------- 1 | BankedNoReceiverTest:testETHTransferFailsIfTransferringToNonReceiver() (gas: 24642) 2 | BankedTest:testCanTransferETHToContract() (gas: 12561) 3 | BankedTest:testNonOwnerCannotWithdrawERC20Tokens() (gas: 1031050) 4 | BankedTest:testNonOwnerCannotWithdrawETHFromContract() (gas: 10891) 5 | BankedTest:testOwnerCanWithdrawERC20Tokens() (gas: 1034420) 6 | BankedTest:testOwnerCanWithdrawETHFromContract() (gas: 22217) 7 | ConfiguredTest:testConfigurationCanBeUpdated() (gas: 39685) 8 | ConfiguredTest:testConfigurationSetCorrectly() (gas: 18975) 9 | ConfiguredTest:testNonOwnerCannotUpdateConfiguration() (gas: 11830) 10 | DelelegatedTest:testDelegateeIsProperlyInitialized() (gas: 10023) 11 | DelelegatedTest:testNonOwnerCannotUpdatedDelegatee() (gas: 11100) 12 | DelelegatedTest:testOwnerCanUpdateDelegatee() (gas: 16508) 13 | FrenrugE2ETest:testAveraging() (gas: 2893397) 14 | FrenrugE2ETest:testInsufficientFundsFlow() (gas: 3362785) 15 | FrenrugE2ETest:testKeyNotActiveFlow() (gas: 3311430) 16 | FrenrugE2ETest:testKeyNotOwnedFlow() (gas: 3380788) 17 | FrenrugE2ETest:testLastKeyLeftFlow() (gas: 6517800) 18 | FrenrugE2ETest:testSuccessfulBuyFlow() (gas: 3405722) 19 | FrenrugE2ETest:testSuccessfulNoopFlow() (gas: 3275019) 20 | FrenrugE2ETest:testSuccessfulSellFlow() (gas: 6517848) 21 | PermissionedTest:testAllowlistIsCorrectlyInitialized() (gas: 21387) 22 | PermissionedTest:testNonOwnerCannotUpdateAllowlist() (gas: 12377) 23 | PermissionedTest:testOwnerCanUpdateAllowlistAndNewNodeCanRespond() (gas: 2100456) 24 | PermissionedTest:testOwnerCanUpdateAllowlistAndRemovedNodeCannotRespond() (gas: 255444) 25 | -------------------------------------------------------------------------------- /test/mocks/MockKey.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {FriendtechSharesV1} from "../interfaces/FriendtechSharesV1.sol"; 5 | 6 | /// @title MockKey 7 | /// @notice Mocks the functionality of a Friendtech Key account 8 | /// @dev Friend.tech keys have royalties / need inbound payment, thus cannot mock a random address 9 | contract MockKey { 10 | /*////////////////////////////////////////////////////////////// 11 | INTERNAL 12 | //////////////////////////////////////////////////////////////*/ 13 | 14 | /// @notice FriendtechSharesV1 15 | FriendtechSharesV1 internal FRIENDTECH; 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | CONSTRUCTOR 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | /// Creates new MockKey 22 | /// @param _friendtech Friendtech 23 | constructor(FriendtechSharesV1 _friendtech) { 24 | FRIENDTECH = _friendtech; 25 | } 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | UTILITY FUNCTIONS 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | /// @notice Buys Friendtech key for account 32 | function buyKey() external { 33 | uint256 price = FRIENDTECH.getBuyPriceAfterFee(address(this), 1); 34 | FRIENDTECH.buyShares{value: price}(address(this), 1); 35 | } 36 | 37 | /// @notice Sells Friendtech key for account 38 | function sellKey() external { 39 | FRIENDTECH.sellShares(address(this), 1); 40 | } 41 | 42 | /// @notice Required to enable royalties + key sales 43 | receive() external payable {} 44 | } 45 | -------------------------------------------------------------------------------- /src/pattern/Banked.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {IERC20} from "../interfaces/IERC20.sol"; 5 | import {Ownable} from "solady/auth/Ownable.sol"; 6 | 7 | /// @title Banked 8 | /// @notice Provides contract owner with ETH, ERC20 management functionality 9 | /// @dev Requires inheriting contract to call `_initializeOwner()` to initialize ownership 10 | /// @dev Allows withdrawing arbitrary token balances 11 | /// @dev Allows emptying contract ETH balance 12 | /// @dev Exposes payable receive function to allow inheriting contracts to receive ETH 13 | abstract contract Banked is Ownable { 14 | /*////////////////////////////////////////////////////////////// 15 | ERRORS 16 | //////////////////////////////////////////////////////////////*/ 17 | 18 | /// @notice Thrown if ETH transfer fails to succeed 19 | /// @dev 4-byte signature: `0x90b8ec18` 20 | error TransferFailed(); 21 | 22 | /*////////////////////////////////////////////////////////////// 23 | FUNCTIONS 24 | //////////////////////////////////////////////////////////////*/ 25 | 26 | /// @notice Allows owner to withdraw ETH balance 27 | function withdrawBalance() external onlyOwner { 28 | // Attempt to transfer ETH balance of contract to owner 29 | (bool success,) = msg.sender.call{value: address(this).balance}(""); 30 | 31 | // Revert if not successful 32 | if (!success) { 33 | revert TransferFailed(); 34 | } 35 | } 36 | 37 | /// @notice Allows owner to withdraw arbitrary ERC20 token balances 38 | /// @param tokenAddress token contract address 39 | function withdrawTokenBalance(address tokenAddress) external onlyOwner { 40 | // Setup token 41 | IERC20 token = IERC20(tokenAddress); 42 | 43 | // Get balance of this address in token 44 | uint256 balance = token.balanceOf(address(this)); 45 | 46 | // Send token balance to caller (owner) 47 | token.transfer(msg.sender, balance); 48 | } 49 | 50 | /// @notice Allow receiving ETH 51 | receive() external payable {} 52 | } 53 | -------------------------------------------------------------------------------- /test/Configured.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {FrenrugTest} from "./Frenrug.t.sol"; 5 | import {Ownable} from "solady/auth/Ownable.sol"; 6 | import {Configured} from "../src/pattern/Configured.sol"; 7 | 8 | /// @title ConfiguredTest 9 | /// @notice Tests isolated `Configured.sol` functionality 10 | contract ConfiguredTest is FrenrugTest { 11 | /// @notice Configuration is correctly set at deploy 12 | function testConfigurationSetCorrectly() public { 13 | (address attestor, uint16 nodes, uint32 maxCallbackGasLimit, address verifier, string memory containerId) = 14 | FRENRUG.config(); 15 | 16 | // Assert params 17 | assertEq(attestor, address(ATTESTOR)); 18 | assertEq(nodes, 3); 19 | assertEq(maxCallbackGasLimit, 10_000_000 wei); 20 | assertEq(verifier, VERIFIER_ADDRESS); 21 | assertEq(containerId, "inference,proving"); 22 | } 23 | 24 | /// @notice Configuration can be updated 25 | function testConfigurationCanBeUpdated() public { 26 | // Setup new config with old parameters + new container ID 27 | (address attestor, uint16 nodes, uint32 maxCallbackGasLimit, address verifier, string memory containerId) = 28 | FRENRUG.config(); 29 | 30 | string memory newContainerId = "new-container-id"; 31 | Configured.Config memory newConfig = Configured.Config({ 32 | attestor: attestor, 33 | nodes: nodes, 34 | maxCallbackGasLimit: maxCallbackGasLimit, 35 | verifier: verifier, 36 | containerId: newContainerId 37 | }); 38 | 39 | // Update config and expect emit 40 | vm.expectEmit(address(FRENRUG)); 41 | emit ConfigUpdated(newConfig); 42 | FRENRUG.updateConfig(newConfig); 43 | 44 | // Assert new params 45 | (attestor, nodes, maxCallbackGasLimit, verifier, containerId) = FRENRUG.config(); 46 | assertEq(attestor, address(ATTESTOR)); 47 | assertEq(nodes, 3); 48 | assertEq(maxCallbackGasLimit, 10_000_000 wei); 49 | assertEq(verifier, VERIFIER_ADDRESS); 50 | assertEq(containerId, newContainerId); 51 | } 52 | 53 | /// @notice Configuration cannot be updated by non-owner 54 | function testNonOwnerCannotUpdateConfiguration() public { 55 | // Setup new dummy configuration 56 | Configured.Config memory newConfig = Configured.Config({ 57 | attestor: address(0), 58 | nodes: 0, 59 | maxCallbackGasLimit: 0 wei, 60 | verifier: address(0), 61 | containerId: "" 62 | }); 63 | 64 | // Mock non-owner 65 | vm.startPrank(address(1)); 66 | 67 | // Attempt to update configuration 68 | vm.expectRevert(Ownable.Unauthorized.selector); 69 | FRENRUG.updateConfig(newConfig); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/pattern/Permissioned.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {Configured} from "./Configured.sol"; 5 | import {Ownable} from "solady/auth/Ownable.sol"; 6 | 7 | /// @title Permissioned 8 | /// @notice Exposes interface to restrict subscription-fulfillment to permissioned set of nodes 9 | /// @dev Should be initialized after global Config (via Configured) is initialized (could be restricted via modifier, but unnecessary for now) 10 | /// @dev Useful when requesting a subset of Infernet network with private containers to respond 11 | /// @dev Useful when initiating subscription requests off-chain w/ redundancy > 1 12 | /// @dev Useful when using containers that do not supply a succinct proof 13 | abstract contract Permissioned is Ownable, Configured { 14 | /*////////////////////////////////////////////////////////////// 15 | MUTABLE 16 | //////////////////////////////////////////////////////////////*/ 17 | 18 | /// @notice List of nodes with permission to respond to subscriptions 19 | mapping(address => bool) public allowedNodes; 20 | 21 | /*////////////////////////////////////////////////////////////// 22 | ERRORS 23 | //////////////////////////////////////////////////////////////*/ 24 | 25 | /// @notice Thrown if attempting to call an `onlyPermissionedNode()` function as a non-permissioned node 26 | /// @dev 4-byte signature: `0xd65548b9` 27 | error NotPermissionedNode(); 28 | 29 | /*////////////////////////////////////////////////////////////// 30 | MODIFIERS 31 | //////////////////////////////////////////////////////////////*/ 32 | 33 | /// @notice Only nodes on the permissioned allowlist 34 | /// @dev msg.sender in callbacks is always address(Coordinator), thus we must explicitly check against node address 35 | /// @param node address to check 36 | modifier onlyPermissionedNode(address node) { 37 | if (!allowedNodes[node]) { 38 | revert NotPermissionedNode(); 39 | } 40 | _; 41 | } 42 | 43 | /*////////////////////////////////////////////////////////////// 44 | INTERNAL FUNCTIONS 45 | //////////////////////////////////////////////////////////////*/ 46 | 47 | /// @notice Allows updating allowlist 48 | /// @dev Does not perform any checks against `config.nodes` to ensure allowed redundany count 49 | /// @dev Does not perform any checks to ensure `nodes.length == status.length` 50 | /// @dev Does not perform any checks to ensure that you are not redundantly updating status (false to false, etc.) 51 | /// @param nodes node addresses to update 52 | /// @param status node statuses 53 | function _updateAllowlist(address[] memory nodes, bool[] memory status) internal { 54 | for (uint256 i = 0; i < nodes.length; i++) { 55 | allowedNodes[nodes[i]] = status[i]; 56 | } 57 | } 58 | 59 | /// @notice Allows owner to update allowlist 60 | function updateAllowlist(address[] memory nodes, bool[] memory status) external onlyOwner { 61 | _updateAllowlist(nodes, status); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/pattern/Configured.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {Ownable} from "solady/auth/Ownable.sol"; 5 | 6 | /// @title Configured 7 | /// @notice Allows storing and updating global contract configuration parameters 8 | /// @dev `_initializeConfig()` must be called at contract creation 9 | abstract contract Configured is Ownable { 10 | /*////////////////////////////////////////////////////////////// 11 | STRUCTS 12 | //////////////////////////////////////////////////////////////*/ 13 | 14 | /// @notice Config represents global Frenrug configuration parameters 15 | /// @dev Tightly-packed struct: 16 | /// - [attestor, nodes, maxCallbackGasLimit]: [160, 16, 32] = 208 17 | /// - [verifier]: [160] = 160 18 | struct Config { 19 | /// @notice Address for the EZKL Data Attestation contract (summarizer model) 20 | address attestor; 21 | /// @notice Number of node responses required before kicking off summarizer model request 22 | /// @dev To prevent re-triggering completed summarizations (increase count, resubmit signed message) prefer to only decrease this count 23 | uint16 nodes; 24 | /// @notice Maximum gas limit in wei for summarizer model callback 25 | /// @dev Can change dynamically to account for changes to model verifier 26 | uint32 maxCallbackGasLimit; 27 | /// @notice Address for the EZKL Proof Verification contract (summarizer model) 28 | address verifier; 29 | /// @notice Summarizer Infernet container IDs 30 | string containerId; 31 | } 32 | 33 | /*////////////////////////////////////////////////////////////// 34 | MUTABLE 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | /// @notice Global configuration parameters 38 | Config public config; 39 | 40 | /*////////////////////////////////////////////////////////////// 41 | EVENTS 42 | //////////////////////////////////////////////////////////////*/ 43 | 44 | /// @notice Emitted when contract config is updated 45 | /// @param newConfig new config 46 | event ConfigUpdated(Config newConfig); 47 | 48 | /*////////////////////////////////////////////////////////////// 49 | INTERNAL FUNCTIONS 50 | //////////////////////////////////////////////////////////////*/ 51 | 52 | /// @notice Updates global config 53 | /// @dev Must be called at contract creation 54 | /// @param newConfig new config to set 55 | function _updateConfig(Config memory newConfig) internal { 56 | config = newConfig; 57 | } 58 | 59 | /*////////////////////////////////////////////////////////////// 60 | FUNCTIONS 61 | //////////////////////////////////////////////////////////////*/ 62 | 63 | /// @notice Allows owner to update global config 64 | /// @param newConfig new config to update 65 | function updateConfig(Config calldata newConfig) external onlyOwner { 66 | _updateConfig(newConfig); 67 | 68 | // Emit change 69 | emit ConfigUpdated(newConfig); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frenrug Smart Contracts 2 | 3 | [![Tests](https://github.com/frenrug/contracts/actions/workflows/test_contracts.yml/badge.svg)](https://github.com/frenrug/contracts/actions/workflows/test_contracts.yml) 4 | 5 | [Frenrug](https://frenrug.com) is an on-chain AI agent that lives in a [friend.tech](https://friend.tech) chatroom managing a portfolio of keys. Behind the scenes, Frenrug is powered by [Ritual](https://ritual.net). 6 | 7 | These smart contracts implement the [Infernet SDK](https://github.com/ritual-net/infernet-sdk) to power the on-chain components of Frenrug. 8 | 9 | > [!IMPORTANT] 10 | > You can find the complete documentation of these smart contracts in the [Frenrug docs](https://frenrug.com/docs/smart-contracts). 11 | 12 | > [!WARNING] 13 | > The Frenrug smart contracts have not been audited, and while we won’t rug you, you may rug yourself. We recommend proceeding with caution. 14 | 15 | ## Local deployment and usage 16 | 17 | First, ensure you have [Foundry installed locally](https://book.getfoundry.sh/getting-started/installation). A simple way to install is to run to the following command: 18 | 19 | ```bash 20 | # Install foundryup, follow instructions 21 | curl -L https://foundry.paradigm.xyz | bash 22 | ``` 23 | 24 | ### Building and running 25 | 26 | To build, run, or execute other commands, you can reference the [Makefile](./Makefile). 27 | 28 | The default target (`make`) will: 29 | 30 | - Clean existing build outputs 31 | - Install all dependencies 32 | - Format code 33 | - Build code and copy compiled artifacts 34 | - Run test suite 35 | 36 | ### Deploying 37 | 38 | There are also two scripts provided for convenience ([./scripts/Deploy.sol](./scripts/Deploy.sol) and [./scripts/UpdateVerifier.sol](./scripts/UpdateVerifier.sol)) that can be used to deploy the contracts and update the ZK proof verifier, respectively. 39 | 40 | ## Deployed contracts 41 | 42 | The live, deployed contracts can be found as follows: 43 | 44 | - Infernet Coordinator contract ([deployed by the Ritual team](https://docs.ritual.net/infernet/sdk/introduction#deployed-contracts)): [0x8D871Ef2826ac9001fB2e33fDD6379b6aaBF449c](https://basescan.org/address/0x8d871ef2826ac9001fb2e33fdd6379b6aabf449c) 45 | - Frenrug contract: [0x5bfe1Ed1741c690eC3e42795cf06a4c38Ed3BC0c](https://basescan.org/address/0x5bfe1Ed1741c690eC3e42795cf06a4c38Ed3BC0c) 46 | - Data Attestation contract: [0xe768F5cf207c4A9919e2259c36Ad289bf26C1439](https://basescan.org/address/0xe768F5cf207c4A9919e2259c36Ad289bf26C1439) 47 | - ZK Proof Verification contract: [0xc4C748261cE010CcB482640e1Ab9a6869af1766F](https://basescan.org/address/0xc4C748261cE010CcB482640e1Ab9a6869af1766F) 48 | 49 | You can see `MessageResponse` events emitted by the Frenrug contract [via BaseScan](https://basescan.org/address/0x5bfe1Ed1741c690eC3e42795cf06a4c38Ed3BC0c#events). 50 | 51 | > [!WARNING] 52 | > Users cannot interface with these contracts directly (as they are called by 53 | > Infernet nodes processing friend.tech chatroom messages), and as such, you 54 | > should never find yourself in a situation where you need to send a transaction 55 | > to these contracts directly. Do not listen to anyone that suggests otherwise 56 | > and do your own research. 57 | 58 | ## License 59 | 60 | [MIT](./LICENSE) 61 | -------------------------------------------------------------------------------- /test/Permissioned.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {FrenrugTest} from "./Frenrug.t.sol"; 5 | import {Ownable} from "solady/auth/Ownable.sol"; 6 | import {Coordinator} from "infernet/Coordinator.sol"; 7 | import {Permissioned} from "../src/pattern/Permissioned.sol"; 8 | 9 | /// @title PermissionedTest 10 | /// @notice Tests isolated `Permissioned.sol` functionality 11 | contract PermissionedTest is FrenrugTest { 12 | /// @notice Allow list is correctly initialized 13 | function testAllowlistIsCorrectlyInitialized() public { 14 | assertTrue(FRENRUG.allowedNodes(address(ALICE))); 15 | assertTrue(FRENRUG.allowedNodes(address(BOB))); 16 | assertTrue(FRENRUG.allowedNodes(address(CHARLIE))); 17 | } 18 | 19 | /// @notice Owner can update allow list and new node can respond 20 | function testOwnerCanUpdateAllowlistAndNewNodeCanRespond() public { 21 | // Add David to the mix as an allowed node 22 | address DAVID = address(8); 23 | address[] memory nodes = new address[](1); 24 | bool[] memory status = new bool[](1); 25 | nodes[0] = DAVID; 26 | status[0] = true; 27 | FRENRUG.updateAllowlist(nodes, status); 28 | assertTrue(FRENRUG.allowedNodes(DAVID)); 29 | 30 | // Mock calls as David 31 | vm.startPrank(DAVID); 32 | 33 | // Register David as node 34 | vm.warp(0); 35 | COORDINATOR.registerNode(DAVID); 36 | vm.warp(COORDINATOR.cooldown()); 37 | COORDINATOR.activateNode(); 38 | 39 | // Deliver subscription 40 | ( 41 | uint32 nonce, 42 | uint32 expiry, 43 | Coordinator.Subscription memory subscription, 44 | uint8 v, 45 | bytes32 r, 46 | bytes32 s, 47 | uint32 interval, 48 | bytes memory input, 49 | bytes memory output, 50 | bytes memory proof 51 | ) = getMockLLMResponse(0); 52 | COORDINATOR.deliverComputeDelegatee(nonce, expiry, subscription, v, r, s, interval, input, output, proof); 53 | } 54 | 55 | /// @notice Owner can update allow list and removed node can no longer respond 56 | function testOwnerCanUpdateAllowlistAndRemovedNodeCannotRespond() public { 57 | // Remove Charlie from allowlist 58 | assertTrue(FRENRUG.allowedNodes(address(CHARLIE))); 59 | address[] memory nodes = new address[](1); 60 | bool[] memory status = new bool[](1); 61 | nodes[0] = address(CHARLIE); 62 | status[0] = false; 63 | FRENRUG.updateAllowlist(nodes, status); 64 | assertFalse(FRENRUG.allowedNodes(address(CHARLIE))); 65 | 66 | // Attempt to deliver output from Charlie 67 | ( 68 | uint32 nonce, 69 | uint32 expiry, 70 | Coordinator.Subscription memory subscription, 71 | uint8 v, 72 | bytes32 r, 73 | bytes32 s, 74 | uint32 interval, 75 | bytes memory input, 76 | bytes memory output, 77 | bytes memory proof 78 | ) = getMockLLMResponse(0); 79 | vm.expectRevert(Permissioned.NotPermissionedNode.selector); 80 | CHARLIE.deliverComputeDelegatee(nonce, expiry, subscription, v, r, s, interval, input, output, proof); 81 | } 82 | 83 | /// @notice Non-owner cannot update allowlist 84 | function testNonOwnerCannotUpdateAllowlist() public { 85 | // Mock non-owner 86 | vm.startPrank(address(1)); 87 | 88 | // Attempt to update allowlist 89 | address[] memory nodes = new address[](0); 90 | bool[] memory status = new bool[](0); 91 | vm.expectRevert(Ownable.Unauthorized.selector); 92 | FRENRUG.updateAllowlist(nodes, status); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /scripts/Deploy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {Frenrug} from "../src/Frenrug.sol"; 5 | import {Script} from "forge-std/Script.sol"; 6 | import {console} from "forge-std/console.sol"; 7 | import {DataAttestation} from "../test/DataAttestor.sol"; 8 | import {Configured} from "../src/pattern/Configured.sol"; 9 | import {EIP712Coordinator} from "infernet/EIP712Coordinator.sol"; 10 | 11 | contract Deploy is Script { 12 | function run() public { 13 | // Setup wallet 14 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 15 | vm.startBroadcast(deployerPrivateKey); 16 | 17 | // Log address 18 | address deployerAddress = vm.addr(deployerPrivateKey); 19 | console.log("Deployer address: ", deployerAddress); 20 | 21 | // Log coordinator 22 | address COORDINATOR = 0x...; 23 | console.log("Coordinator address: ", COORDINATOR); 24 | 25 | // Setup input parameters for attestor contract 26 | // Contract address to staticcall (our consumer contract, in this case, address(FRENRUG)) 27 | address frenrugAddress = 0x...; 28 | address[] memory _contractAddresses = new address[](2); 29 | _contractAddresses[0] = frenrugAddress; 30 | _contractAddresses[1] = frenrugAddress; 31 | 32 | // Function calldata to get int256[1] input parameters 33 | bytes[][] memory _calldata = new bytes[][](2); 34 | _calldata[0] = new bytes[](1); 35 | _calldata[1] = new bytes[](1); 36 | // We expose the current int256[1] parameters via Frenrug.currentData 37 | // We can simply encode the getter function for this public int256[1] state 38 | bytes4 GETTER_SELECTOR = bytes4(keccak256("attestedInputs(uint256)")); 39 | _calldata[0][0] = abi.encodeWithSelector(GETTER_SELECTOR, 0); 40 | _calldata[1][0] = abi.encodeWithSelector(GETTER_SELECTOR, 1); 41 | 42 | // Decimals and scaling are default set to 0 43 | uint256[][] memory _decimals = new uint256[][](2); 44 | _decimals[0] = new uint256[](1); 45 | _decimals[1] = new uint256[](1); 46 | _decimals[0][0] = 0; 47 | _decimals[1][0] = 0; 48 | uint256[] memory _scales = new uint256[](2); 49 | _scales[0] = 0; 50 | _scales[1] = 0; 51 | 52 | // Initialize new attestor contract 53 | DataAttestation ATTESTOR = new DataAttestation( 54 | _contractAddresses, 55 | _calldata, 56 | _decimals, 57 | _scales, 58 | 0, 59 | deployerAddress 60 | ); 61 | console.log("Attestor address: ", address(ATTESTOR)); 62 | 63 | // Verifier contract 64 | address VERIFIER = 0x...; 65 | console.log("Verifier address: ", VERIFIER); 66 | 67 | // Frenrug contract setup 68 | Configured.Config memory config = Configured.Config({ 69 | attestor: address(ATTESTOR), 70 | nodes: 3, 71 | maxCallbackGasLimit: 3_000_000 wei, 72 | verifier: address(VERIFIER), 73 | containerId: "56bee1d5a6ab406e366c76f5ad2444c32c9d96539945b34f8ce3ba7c05c2e2ae" 74 | }); 75 | address friendTechSharesV1 = 0xCF205808Ed36593aa40a44F10c7f7C2F67d4A4d4; 76 | address[] memory allowedNodes = new address[](3); 77 | allowedNodes[0] = 0x...; 78 | allowedNodes[1] = 0x...; 79 | allowedNodes[2] = 0x...; 80 | bool[] memory status = new bool[](3); 81 | status[0] = true; 82 | status[1] = true; 83 | status[2] = true; 84 | Frenrug FRENRUG = new Frenrug( 85 | config, 86 | friendTechSharesV1, 87 | COORDINATOR, 88 | allowedNodes, 89 | status 90 | ); 91 | console.log("Frenrug address: ", address(FRENRUG)); 92 | 93 | // Update frenrug delegated address 94 | FRENRUG.updateDelegatee(0x...); 95 | 96 | vm.stopBroadcast(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /test/Banked.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {FrenrugTest} from "./Frenrug.t.sol"; 5 | import {Ownable} from "solady/auth/Ownable.sol"; 6 | import {Banked} from "../src/pattern/Banked.sol"; 7 | import {MockERC20} from "solady-tests/utils/mocks/MockERC20.sol"; 8 | 9 | /// @title BankedTest 10 | /// @notice Tests isolated `Banked.sol` functionality 11 | contract BankedTest is FrenrugTest { 12 | /// @notice Can transfer ETH to contract 13 | function testCanTransferETHToContract() public { 14 | // Assert no starting balance 15 | assertEq(address(FRENRUG).balance, 0 ether); 16 | 17 | // Transfer in 1 ETH 18 | payable(FRENRUG).transfer(1 ether); 19 | 20 | // Assert balance 21 | assertEq(address(FRENRUG).balance, 1 ether); 22 | } 23 | 24 | /// @notice Owner can withdraw ETH from contract 25 | function testOwnerCanWithdrawETHFromContract() public { 26 | // Transfer in ETH 27 | uint256 startBalance = address(this).balance; 28 | payable(FRENRUG).transfer(1 ether); 29 | uint256 endBalance = address(this).balance; 30 | assertEq(address(FRENRUG).balance, 1 ether); 31 | assertEq(startBalance - 1 ether, endBalance); 32 | 33 | // Attempt to withdraw 34 | FRENRUG.withdrawBalance(); 35 | assertEq(address(FRENRUG).balance, 0 ether); 36 | assertEq(address(this).balance, startBalance); 37 | } 38 | 39 | /// @notice Non-owner cannot withdraw ETH from contract 40 | function testNonOwnerCannotWithdrawETHFromContract() public { 41 | // Mock non-owner 42 | vm.startPrank(address(1)); 43 | 44 | // Attempt to withdraw balance 45 | vm.expectRevert(Ownable.Unauthorized.selector); 46 | FRENRUG.withdrawBalance(); 47 | } 48 | 49 | /// @notice Owner can withdraw ERC20 tokens 50 | function testOwnerCanWithdrawERC20Tokens() public { 51 | // Create token 52 | MockERC20 token = new MockERC20("", "", 18); 53 | 54 | // Assert current balances 55 | assertEq(token.balanceOf(address(this)), 0); 56 | assertEq(token.balanceOf(address(FRENRUG)), 0); 57 | 58 | // Mint 5 tokens to Frenrug 59 | token.mint(address(FRENRUG), 5e18); 60 | assertEq(token.balanceOf(address(FRENRUG)), 5e18); 61 | 62 | // Attempt to withdraw 63 | FRENRUG.withdrawTokenBalance(address(token)); 64 | assertEq(token.balanceOf(address(this)), 5e18); 65 | assertEq(token.balanceOf(address(FRENRUG)), 0); 66 | } 67 | 68 | /// @notice Non-owner cannot withdraw ERC20 tokens 69 | function testNonOwnerCannotWithdrawERC20Tokens() public { 70 | // Create token 71 | MockERC20 token = new MockERC20("", "", 18); 72 | 73 | // Assert current balances 74 | assertEq(token.balanceOf(address(this)), 0); 75 | assertEq(token.balanceOf(address(FRENRUG)), 0); 76 | 77 | // Mint 5 tokens to Frenrug 78 | token.mint(address(FRENRUG), 5e18); 79 | assertEq(token.balanceOf(address(FRENRUG)), 5e18); 80 | 81 | // Mock non-owner 82 | vm.startPrank(address(1)); 83 | 84 | // Attempt to withdraw 85 | vm.expectRevert(Ownable.Unauthorized.selector); 86 | FRENRUG.withdrawTokenBalance(address(token)); 87 | } 88 | 89 | receive() external payable {} 90 | } 91 | 92 | /// @title BankedNoReceiverTest 93 | /// @notice Tests isolated `Banked.sol` ETH transfer functionality to non-receiver 94 | contract BankedNoReceiverTest is FrenrugTest { 95 | /// @notice ETH transfer fails if transferring to a non-receiver contract 96 | function testETHTransferFailsIfTransferringToNonReceiver() public { 97 | // Transfer in 1 ETH 98 | payable(FRENRUG).transfer(1 ether); 99 | 100 | // Attempt to withdraw balance 101 | vm.expectRevert(Banked.TransferFailed.selector); 102 | FRENRUG.withdrawBalance(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/FriendtechManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {Ownable} from "solady/auth/Ownable.sol"; 5 | import {IFriendtechSharesV1} from "./interfaces/IFriendtechSharesV1.sol"; 6 | 7 | /// @title FriendtechManager 8 | /// @notice Manages interfacing with the FriendtechSharesV1 contract 9 | abstract contract FriendtechManager is Ownable { 10 | /*////////////////////////////////////////////////////////////// 11 | ENUMS 12 | //////////////////////////////////////////////////////////////*/ 13 | 14 | /// @notice Possible reasons for a messages' execution to fail 15 | enum ExecutionFailure { 16 | None, // No failure 17 | InsufficientFunds, // Bot does not have enough ETH to buy key 18 | KeyNotActive, // Key owner has yet to purchase their first key 19 | KeyNotOwned, // Bot does not own key which is trying to be sold 20 | LastKeyLeft, // Bot cannot sell the last key 21 | Unknown // Unknown error (value transfer failure) 22 | } 23 | 24 | /*////////////////////////////////////////////////////////////// 25 | CONSTANTS 26 | //////////////////////////////////////////////////////////////*/ 27 | 28 | /// @notice Error thrown from FriendtechSharesV1 if trying to buy inactive key 29 | bytes32 private constant KEY_NOT_ACTIVE_ERROR = keccak256(bytes("Only the shares' subject can buy the first share")); 30 | 31 | /// @notice Error thrown from FriendtechSharesV1 if trying to sell key without owning it 32 | bytes32 private constant INSUFFICIENT_KEYS_OWNED_ERROR = keccak256(bytes("Insufficient shares")); 33 | 34 | /// @notice Error thrown from FriendtechSharesV1 if trying to sell last key 35 | bytes32 private constant CANNOT_SELL_LAST_KEY_ERROR = keccak256(bytes("Cannot sell the last share")); 36 | 37 | /*////////////////////////////////////////////////////////////// 38 | IMMUTABLE 39 | //////////////////////////////////////////////////////////////*/ 40 | 41 | /// @notice FriendtechSharesV1 contract 42 | IFriendtechSharesV1 private immutable FRIENDTECH; 43 | 44 | /*////////////////////////////////////////////////////////////// 45 | CONSTRUCTOR 46 | //////////////////////////////////////////////////////////////*/ 47 | 48 | /// Create new FriendtechOperator 49 | /// @param _friendtech FriendtechSharesV1 address 50 | constructor(address _friendtech) { 51 | // Initialize friendtech interface 52 | FRIENDTECH = IFriendtechSharesV1(_friendtech); 53 | } 54 | 55 | /*////////////////////////////////////////////////////////////// 56 | FUNCTIONS 57 | //////////////////////////////////////////////////////////////*/ 58 | 59 | /// @notice Attempts to buy a key 60 | /// @param key key address to purchase 61 | /// @return execution failure 62 | function buyKey(address key) internal returns (ExecutionFailure) { 63 | // Calculate price to buy key 64 | uint256 price = FRIENDTECH.getBuyPriceAfterFee(key, 1); 65 | 66 | // If insufficient balance, return `InsufficientFunds` 67 | if (address(this).balance < price) { 68 | return ExecutionFailure.InsufficientFunds; 69 | } 70 | 71 | // Attempt to purchase key 72 | try FRIENDTECH.buyShares{value: price}(key, 1) { 73 | // If successful, return no failure 74 | return ExecutionFailure.None; 75 | } catch Error(string memory reason) { 76 | // Check if key is inactive and throw 77 | if (keccak256(bytes(reason)) == KEY_NOT_ACTIVE_ERROR) { 78 | return ExecutionFailure.KeyNotActive; 79 | } 80 | 81 | // Else, return unknown error 82 | return ExecutionFailure.Unknown; 83 | } 84 | } 85 | 86 | /// @notice Attempts to sell a key 87 | /// @param key key address to sell 88 | /// @return execution failure 89 | function sellKey(address key) internal returns (ExecutionFailure) { 90 | // Attempt to sell key 91 | try FRIENDTECH.sellShares(key, 1) { 92 | // If successful, return no failure 93 | return ExecutionFailure.None; 94 | } catch Error(string memory reason) { 95 | bytes32 reasonHash = keccak256(bytes(reason)); 96 | 97 | // Throw if attempting to sell last key 98 | if (reasonHash == CANNOT_SELL_LAST_KEY_ERROR) { 99 | return ExecutionFailure.LastKeyLeft; 100 | } 101 | 102 | // Throw if attempting to sell unowned key 103 | if (reasonHash == INSUFFICIENT_KEYS_OWNED_ERROR) { 104 | return ExecutionFailure.KeyNotOwned; 105 | } 106 | 107 | // Else, return unknown error 108 | return ExecutionFailure.Unknown; 109 | } 110 | } 111 | 112 | /// @notice Allows owner to directly sell key 113 | /// @param key key address to sell 114 | function ownerSellKey(address key) external onlyOwner { 115 | FRIENDTECH.sellShares(key, 1); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /test/interfaces/FriendtechSharesV1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | // File: contracts/Context.sol 3 | 4 | // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) 5 | 6 | pragma solidity ^0.8.0; 7 | 8 | /** 9 | * @dev Provides information about the current execution context, including the 10 | * sender of the transaction and its data. While these are generally available 11 | * via msg.sender and msg.data, they should not be accessed in such a direct 12 | * manner, since when dealing with meta-transactions the account sending and 13 | * paying for execution may not be the actual sender (as far as an application 14 | * is concerned). 15 | * 16 | * This contract is only required for intermediate, library-like contracts. 17 | */ 18 | abstract contract Context { 19 | function _msgSender() internal view virtual returns (address) { 20 | return msg.sender; 21 | } 22 | 23 | function _msgData() internal view virtual returns (bytes calldata) { 24 | return msg.data; 25 | } 26 | } 27 | 28 | /** 29 | * @dev Contract module which provides a basic access control mechanism, where 30 | * there is an account (an owner) that can be granted exclusive access to 31 | * specific functions. 32 | * 33 | * By default, the owner account will be the one that deploys the contract. This 34 | * can later be changed with {transferOwnership}. 35 | * 36 | * This module is used through inheritance. It will make available the modifier 37 | * `onlyOwner`, which can be applied to your functions to restrict their use to 38 | * the owner. 39 | */ 40 | abstract contract Ownable is Context { 41 | address private _owner; 42 | 43 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 44 | 45 | /** 46 | * @dev Initializes the contract setting the deployer as the initial owner. 47 | */ 48 | constructor() { 49 | _transferOwnership(_msgSender()); 50 | } 51 | 52 | /** 53 | * @dev Throws if called by any account other than the owner. 54 | */ 55 | modifier onlyOwner() { 56 | _checkOwner(); 57 | _; 58 | } 59 | 60 | /** 61 | * @dev Returns the address of the current owner. 62 | */ 63 | function owner() public view virtual returns (address) { 64 | return _owner; 65 | } 66 | 67 | /** 68 | * @dev Throws if the sender is not the owner. 69 | */ 70 | function _checkOwner() internal view virtual { 71 | require(owner() == _msgSender(), "Ownable: caller is not the owner"); 72 | } 73 | 74 | /** 75 | * @dev Leaves the contract without owner. It will not be possible to call 76 | * `onlyOwner` functions anymore. Can only be called by the current owner. 77 | * 78 | * NOTE: Renouncing ownership will leave the contract without an owner, 79 | * thereby removing any functionality that is only available to the owner. 80 | */ 81 | function renounceOwnership() public virtual onlyOwner { 82 | _transferOwnership(address(0)); 83 | } 84 | 85 | /** 86 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 87 | * Can only be called by the current owner. 88 | */ 89 | function transferOwnership(address newOwner) public virtual onlyOwner { 90 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 91 | _transferOwnership(newOwner); 92 | } 93 | 94 | /** 95 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 96 | * Internal function without access restriction. 97 | */ 98 | function _transferOwnership(address newOwner) internal virtual { 99 | address oldOwner = _owner; 100 | _owner = newOwner; 101 | emit OwnershipTransferred(oldOwner, newOwner); 102 | } 103 | } 104 | 105 | // File: contracts/FriendtechShares.sol 106 | 107 | pragma solidity >=0.8.19 <0.9.0; 108 | 109 | // TODO: Events, final pricing model, 110 | 111 | contract FriendtechSharesV1 is Ownable { 112 | address public protocolFeeDestination; 113 | uint256 public protocolFeePercent; 114 | uint256 public subjectFeePercent; 115 | 116 | event Trade( 117 | address trader, 118 | address subject, 119 | bool isBuy, 120 | uint256 shareAmount, 121 | uint256 ethAmount, 122 | uint256 protocolEthAmount, 123 | uint256 subjectEthAmount, 124 | uint256 supply 125 | ); 126 | 127 | // SharesSubject => (Holder => Balance) 128 | mapping(address => mapping(address => uint256)) public sharesBalance; 129 | 130 | // SharesSubject => Supply 131 | mapping(address => uint256) public sharesSupply; 132 | 133 | function setFeeDestination(address _feeDestination) public onlyOwner { 134 | protocolFeeDestination = _feeDestination; 135 | } 136 | 137 | function setProtocolFeePercent(uint256 _feePercent) public onlyOwner { 138 | protocolFeePercent = _feePercent; 139 | } 140 | 141 | function setSubjectFeePercent(uint256 _feePercent) public onlyOwner { 142 | subjectFeePercent = _feePercent; 143 | } 144 | 145 | function getPrice(uint256 supply, uint256 amount) public pure returns (uint256) { 146 | uint256 sum1 = supply == 0 ? 0 : (supply - 1) * (supply) * (2 * (supply - 1) + 1) / 6; 147 | uint256 sum2 = supply == 0 && amount == 1 148 | ? 0 149 | : (supply - 1 + amount) * (supply + amount) * (2 * (supply - 1 + amount) + 1) / 6; 150 | uint256 summation = sum2 - sum1; 151 | return summation * 1 ether / 16_000; 152 | } 153 | 154 | function getBuyPrice(address sharesSubject, uint256 amount) public view returns (uint256) { 155 | return getPrice(sharesSupply[sharesSubject], amount); 156 | } 157 | 158 | function getSellPrice(address sharesSubject, uint256 amount) public view returns (uint256) { 159 | return getPrice(sharesSupply[sharesSubject] - amount, amount); 160 | } 161 | 162 | function getBuyPriceAfterFee(address sharesSubject, uint256 amount) public view returns (uint256) { 163 | uint256 price = getBuyPrice(sharesSubject, amount); 164 | uint256 protocolFee = price * protocolFeePercent / 1 ether; 165 | uint256 subjectFee = price * subjectFeePercent / 1 ether; 166 | return price + protocolFee + subjectFee; 167 | } 168 | 169 | function getSellPriceAfterFee(address sharesSubject, uint256 amount) public view returns (uint256) { 170 | uint256 price = getSellPrice(sharesSubject, amount); 171 | uint256 protocolFee = price * protocolFeePercent / 1 ether; 172 | uint256 subjectFee = price * subjectFeePercent / 1 ether; 173 | return price - protocolFee - subjectFee; 174 | } 175 | 176 | function buyShares(address sharesSubject, uint256 amount) public payable { 177 | uint256 supply = sharesSupply[sharesSubject]; 178 | require(supply > 0 || sharesSubject == msg.sender, "Only the shares' subject can buy the first share"); 179 | uint256 price = getPrice(supply, amount); 180 | uint256 protocolFee = price * protocolFeePercent / 1 ether; 181 | uint256 subjectFee = price * subjectFeePercent / 1 ether; 182 | require(msg.value >= price + protocolFee + subjectFee, "Insufficient payment"); 183 | sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] + amount; 184 | sharesSupply[sharesSubject] = supply + amount; 185 | emit Trade(msg.sender, sharesSubject, true, amount, price, protocolFee, subjectFee, supply + amount); 186 | (bool success1,) = protocolFeeDestination.call{value: protocolFee}(""); 187 | (bool success2,) = sharesSubject.call{value: subjectFee}(""); 188 | require(success1 && success2, "Unable to send funds"); 189 | } 190 | 191 | function sellShares(address sharesSubject, uint256 amount) public payable { 192 | uint256 supply = sharesSupply[sharesSubject]; 193 | require(supply > amount, "Cannot sell the last share"); 194 | uint256 price = getPrice(supply - amount, amount); 195 | uint256 protocolFee = price * protocolFeePercent / 1 ether; 196 | uint256 subjectFee = price * subjectFeePercent / 1 ether; 197 | require(sharesBalance[sharesSubject][msg.sender] >= amount, "Insufficient shares"); 198 | sharesBalance[sharesSubject][msg.sender] = sharesBalance[sharesSubject][msg.sender] - amount; 199 | sharesSupply[sharesSubject] = supply - amount; 200 | emit Trade(msg.sender, sharesSubject, false, amount, price, protocolFee, subjectFee, supply - amount); 201 | (bool success1,) = msg.sender.call{value: price - protocolFee - subjectFee}(""); 202 | (bool success2,) = protocolFeeDestination.call{value: protocolFee}(""); 203 | (bool success3,) = sharesSubject.call{value: subjectFee}(""); 204 | require(success1 && success2 && success3, "Unable to send funds"); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /test/deployed/DataAttestation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | contract LoadInstances { 5 | /** 6 | * @dev Parse the instances array from the Halo2Verifier encoded calldata. 7 | * @notice must pass encoded bytes from memory 8 | * @param encoded - verifier calldata 9 | */ 10 | function getInstancesMemory(bytes memory encoded) internal pure returns (uint256[] memory instances) { 11 | bytes4 funcSig; 12 | uint256 instances_offset; 13 | uint256 instances_length; 14 | assembly { 15 | // fetch function sig. Either `verifyProof(bytes,uint256[])` or `verifyProof(address,bytes,uint256[])` 16 | funcSig := mload(add(encoded, 0x20)) 17 | 18 | // Fetch instances offset which is 4 + 32 + 32 bytes away from 19 | // start of encoded for `verifyProof(bytes,uint256[])`, 20 | // and 4 + 32 + 32 +32 away for `verifyProof(address,bytes,uint256[])` 21 | 22 | instances_offset := mload(add(encoded, add(0x44, mul(0x20, eq(funcSig, 0xaf83a18d))))) 23 | 24 | instances_length := mload(add(add(encoded, 0x24), instances_offset)) 25 | } 26 | instances = new uint256[](instances_length); // Allocate memory for the instances array. 27 | assembly { 28 | // Now instances points to the start of the array data 29 | // (right after the length field). 30 | for { let i := 0x20 } lt(i, add(mul(instances_length, 0x20), 0x20)) { i := add(i, 0x20) } { 31 | mstore(add(instances, i), mload(add(add(encoded, add(i, 0x24)), instances_offset))) 32 | } 33 | } 34 | } 35 | /** 36 | * @dev Parse the instances array from the Halo2Verifier encoded calldata. 37 | * @notice must pass encoded bytes from calldata 38 | * @param encoded - verifier calldata 39 | */ 40 | 41 | function getInstancesCalldata(bytes calldata encoded) internal pure returns (uint256[] memory instances) { 42 | bytes4 funcSig; 43 | uint256 instances_offset; 44 | uint256 instances_length; 45 | assembly { 46 | // fetch function sig. Either `verifyProof(bytes,uint256[])` or `verifyProof(address,bytes,uint256[])` 47 | funcSig := calldataload(encoded.offset) 48 | 49 | // Fetch instances offset which is 4 + 32 + 32 bytes away from 50 | // start of encoded for `verifyProof(bytes,uint256[])`, 51 | // and 4 + 32 + 32 +32 away for `verifyProof(address,bytes,uint256[])` 52 | 53 | instances_offset := calldataload(add(encoded.offset, add(0x24, mul(0x20, eq(funcSig, 0xaf83a18d))))) 54 | 55 | instances_length := calldataload(add(add(encoded.offset, 0x04), instances_offset)) 56 | } 57 | instances = new uint256[](instances_length); // Allocate memory for the instances array. 58 | assembly { 59 | // Now instances points to the start of the array data 60 | // (right after the length field). 61 | 62 | for { let i := 0x20 } lt(i, add(mul(instances_length, 0x20), 0x20)) { i := add(i, 0x20) } { 63 | mstore(add(instances, i), calldataload(add(add(encoded.offset, add(i, 0x04)), instances_offset))) 64 | } 65 | } 66 | } 67 | } 68 | 69 | // This contract serves as a Data Attestation Verifier for the EZKL model. 70 | // It is designed to read and attest to instances of proofs generated from a specified circuit. 71 | // It is particularly constructed to read only int256 data from specified on-chain contracts' view functions. 72 | 73 | // Overview of the contract functionality: 74 | // 1. Initialization: Through the constructor, it sets up the contract calls that the EZKL model will read from. 75 | // 2. Data Quantization: Quantizes the returned data into a scaled fixed-point representation. See the `quantizeData` method for details. 76 | // 3. Static Calls: Makes static calls to fetch data from other contracts. See the `staticCall` method. 77 | // 4. Field Element Conversion: The fixed-point representation is then converted into a field element modulo P using the `toFieldElement` method. 78 | // 5. Data Attestation: The `attestData` method validates that the public instances match the data fetched and processed by the contract. 79 | // 6. Proof Verification: The `verifyWithDataAttestation` method parses the instances out of the encoded calldata and calls the `attestData` method to validate the public instances, 80 | // then calls the `verifyProof` method to verify the proof on the verifier. 81 | 82 | contract DataAttestation is LoadInstances { 83 | /** 84 | * @notice Struct used to make view only calls to accounts to fetch the data that EZKL reads from. 85 | * @param the address of the account to make calls to 86 | * @param the abi encoded function calls to make to the `contractAddress` 87 | */ 88 | struct AccountCall { 89 | address contractAddress; 90 | mapping(uint256 => bytes) callData; 91 | mapping(uint256 => uint256) decimals; 92 | uint256 callCount; 93 | } 94 | 95 | AccountCall[2] public accountCalls; 96 | 97 | uint256[] public scales; 98 | 99 | address public admin; 100 | 101 | /** 102 | * @notice EZKL P value 103 | * @dev In order to prevent the verifier from accepting two version of the same pubInput, n and the quantity (n + P), where n + P <= 2^256, we require that all instances are stricly less than P. a 104 | * @dev The reason for this is that the assmebly code of the verifier performs all arithmetic operations modulo P and as a consequence can't distinguish between n and n + P. 105 | */ 106 | uint256 constant ORDER = uint256(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001); 107 | 108 | uint256 constant INPUT_CALLS = 1; 109 | 110 | uint256 constant OUTPUT_CALLS = 1; 111 | 112 | uint8 public instanceOffset; 113 | 114 | /** 115 | * @dev Initialize the contract with account calls the EZKL model will read from. 116 | * @param _contractAddresses - The calls to all the contracts EZKL reads storage from. 117 | * @param _callData - The abi encoded function calls to make to the `contractAddress` that EZKL reads storage from. 118 | */ 119 | constructor( 120 | address[] memory _contractAddresses, 121 | bytes[][] memory _callData, 122 | uint256[][] memory _decimals, 123 | uint256[] memory _scales, 124 | uint8 _instanceOffset, 125 | address _admin 126 | ) { 127 | admin = _admin; 128 | for (uint256 i; i < _scales.length; i++) { 129 | scales.push(1 << _scales[i]); 130 | } 131 | populateAccountCalls(_contractAddresses, _callData, _decimals); 132 | instanceOffset = _instanceOffset; 133 | } 134 | 135 | function updateAdmin(address _admin) external { 136 | require(msg.sender == admin, "Only admin can update admin"); 137 | if (_admin == address(0)) { 138 | revert(); 139 | } 140 | admin = _admin; 141 | } 142 | 143 | function updateAccountCalls( 144 | address[] memory _contractAddresses, 145 | bytes[][] memory _callData, 146 | uint256[][] memory _decimals 147 | ) external { 148 | require(msg.sender == admin, "Only admin can update instanceOffset"); 149 | populateAccountCalls(_contractAddresses, _callData, _decimals); 150 | } 151 | 152 | function populateAccountCalls( 153 | address[] memory _contractAddresses, 154 | bytes[][] memory _callData, 155 | uint256[][] memory _decimals 156 | ) internal { 157 | require( 158 | _contractAddresses.length == _callData.length && accountCalls.length == _contractAddresses.length, 159 | "Invalid input length" 160 | ); 161 | require(_decimals.length == _contractAddresses.length, "Invalid number of decimals"); 162 | // fill in the accountCalls storage array 163 | uint256 counter = 0; 164 | for (uint256 i = 0; i < _contractAddresses.length; i++) { 165 | AccountCall storage accountCall = accountCalls[i]; 166 | accountCall.contractAddress = _contractAddresses[i]; 167 | accountCall.callCount = _callData[i].length; 168 | for (uint256 j = 0; j < _callData[i].length; j++) { 169 | accountCall.callData[j] = _callData[i][j]; 170 | accountCall.decimals[j] = 10 ** _decimals[i][j]; 171 | } 172 | // count the total number of storage reads across all of the accounts 173 | counter += _callData[i].length; 174 | } 175 | require(counter == INPUT_CALLS + OUTPUT_CALLS, "Invalid number of calls"); 176 | } 177 | 178 | function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { 179 | unchecked { 180 | uint256 prod0; 181 | uint256 prod1; 182 | assembly { 183 | let mm := mulmod(x, y, not(0)) 184 | prod0 := mul(x, y) 185 | prod1 := sub(sub(mm, prod0), lt(mm, prod0)) 186 | } 187 | 188 | if (prod1 == 0) { 189 | return prod0 / denominator; 190 | } 191 | 192 | require(denominator > prod1, "Math: mulDiv overflow"); 193 | 194 | uint256 remainder; 195 | assembly { 196 | remainder := mulmod(x, y, denominator) 197 | prod1 := sub(prod1, gt(remainder, prod0)) 198 | prod0 := sub(prod0, remainder) 199 | } 200 | 201 | uint256 twos = denominator & (~denominator + 1); 202 | assembly { 203 | denominator := div(denominator, twos) 204 | prod0 := div(prod0, twos) 205 | twos := add(div(sub(0, twos), twos), 1) 206 | } 207 | 208 | prod0 |= prod1 * twos; 209 | 210 | uint256 inverse = (3 * denominator) ^ 2; 211 | 212 | inverse *= 2 - denominator * inverse; 213 | inverse *= 2 - denominator * inverse; 214 | inverse *= 2 - denominator * inverse; 215 | inverse *= 2 - denominator * inverse; 216 | inverse *= 2 - denominator * inverse; 217 | inverse *= 2 - denominator * inverse; 218 | 219 | result = prod0 * inverse; 220 | return result; 221 | } 222 | } 223 | /** 224 | * @dev Quantize the data returned from the account calls to the scale used by the EZKL model. 225 | * @param data - The data returned from the account calls. 226 | * @param decimals - The number of decimals the data returned from the account calls has (for floating point representation). 227 | * @param scale - The scale used to convert the floating point value into a fixed point value. 228 | */ 229 | 230 | function quantizeData(bytes memory data, uint256 decimals, uint256 scale) 231 | internal 232 | pure 233 | returns (int256 quantized_data) 234 | { 235 | int256 x = abi.decode(data, (int256)); 236 | bool neg = x < 0; 237 | if (neg) { 238 | x = -x; 239 | } 240 | uint256 output = mulDiv(uint256(x), scale, decimals); 241 | if (mulmod(uint256(x), scale, decimals) * 2 >= decimals) { 242 | output += 1; 243 | } 244 | quantized_data = neg ? -int256(output) : int256(output); 245 | } 246 | /** 247 | * @dev Make a static call to the account to fetch the data that EZKL reads from. 248 | * @param target - The address of the account to make calls to. 249 | * @param data - The abi encoded function calls to make to the `contractAddress` that EZKL reads storage from. 250 | * @return The data returned from the account calls. (Must come from either a view or pure function. Will throw an error otherwise) 251 | */ 252 | 253 | function staticCall(address target, bytes memory data) internal view returns (bytes memory) { 254 | (bool success, bytes memory returndata) = target.staticcall(data); 255 | if (success) { 256 | if (returndata.length == 0) { 257 | require(target.code.length > 0, "Address: call to non-contract"); 258 | } 259 | return returndata; 260 | } else { 261 | revert("Address: low-level call failed"); 262 | } 263 | } 264 | /** 265 | * @dev Convert the fixed point quantized data into a field element. 266 | * @param x - The quantized data. 267 | * @return field_element - The field element. 268 | */ 269 | 270 | function toFieldElement(int256 x) internal pure returns (uint256 field_element) { 271 | // The casting down to uint256 is safe because the order is about 2^254, and the value 272 | // of x ranges of -2^127 to 2^127, so x + int(ORDER) is always positive. 273 | return uint256(x + int256(ORDER)) % ORDER; 274 | } 275 | 276 | /** 277 | * @dev Make the account calls to fetch the data that EZKL reads from and attest to the data. 278 | * @param instances - The public instances to the proof (the data in the proof that publicly accessible to the verifier). 279 | */ 280 | function attestData(uint256[] memory instances) internal view { 281 | require(instances.length >= INPUT_CALLS + OUTPUT_CALLS, "Invalid public inputs length"); 282 | uint256 _accountCount = accountCalls.length; 283 | uint256 counter = 0; 284 | for (uint8 i = 0; i < _accountCount; ++i) { 285 | address account = accountCalls[i].contractAddress; 286 | for (uint8 j = 0; j < accountCalls[i].callCount; j++) { 287 | bytes memory returnData = staticCall(account, accountCalls[i].callData[j]); 288 | uint256 scale = scales[counter]; 289 | int256 quantized_data = quantizeData(returnData, accountCalls[i].decimals[j], scale); 290 | uint256 field_element = toFieldElement(quantized_data); 291 | require(field_element == instances[counter + instanceOffset], "Public input does not match"); 292 | counter++; 293 | } 294 | } 295 | } 296 | 297 | function verifyWithDataAttestation(address verifier, bytes calldata encoded) public view returns (bool) { 298 | require(verifier.code.length > 0, "Address: call to non-contract"); 299 | attestData(getInstancesCalldata(encoded)); 300 | // static call the verifier contract to verify the proof 301 | (bool success, bytes memory returndata) = verifier.staticcall(encoded); 302 | 303 | if (success) { 304 | return abi.decode(returndata, (bool)); 305 | } else { 306 | revert("low-level call to verifier failed"); 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/Frenrug.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {Banked} from "./pattern/Banked.sol"; 5 | import {Ownable} from "solady/auth/Ownable.sol"; 6 | import {Delegated} from "./pattern/Delegated.sol"; 7 | import {Configured} from "./pattern/Configured.sol"; 8 | import {SD59x18, div, sd} from "@prb/math/SD59x18.sol"; 9 | import {Permissioned} from "./pattern/Permissioned.sol"; 10 | import {FriendtechManager} from "./FriendtechManager.sol"; 11 | import {CallbackConsumer} from "infernet/consumer/Callback.sol"; 12 | import {IDataAttestation} from "./interfaces/IDataAttestation.sol"; 13 | 14 | /// @title Frenrug 15 | /// @notice https://frenrug.com 16 | /// @notice Uses Infernet SDK: https://ritual.net 17 | /// @notice Frenrug is an on-chain AI agent that lives in a friend.tech chatroom managing a portfolio of friend.tech keys 18 | /// @dev Inherited contracts also inherit Ownable; ensure base Ownable is first in inheritance hiearchy 19 | /// @dev Allows off-chain created Infernet subscriptions to initialize flow 20 | /// @dev After set number of received LLM responses, initializes a summarization callback 21 | /// @dev Enables summarization callback to execute trade and emit result 22 | /// @dev Notice that while gas optimizations (particularly in calldata reduction for L2 costs by packing vector inputs) are possible they have been forgone in favor of simplicity and readability 23 | contract Frenrug is Ownable, FriendtechManager, Banked, Configured, Permissioned, Delegated, CallbackConsumer { 24 | /*////////////////////////////////////////////////////////////// 25 | STRUCTS 26 | //////////////////////////////////////////////////////////////*/ 27 | 28 | /// @notice A message is a proposal to take some action for a (key, id) pair 29 | struct Message { 30 | /// @notice Chatroom message identifier 31 | uint48 id; 32 | /// @notice Proposed key to take action on 33 | address key; 34 | /// @notice LLM hashed inputs 35 | bytes32[] inputs; 36 | /// @notice LLM rationales for action 37 | string[] rationales; 38 | /// @notice Responding nodes (mapped 1:1 to rationales) 39 | address[] nodes; 40 | /// @notice LLM output embedding vectors 41 | int256[] vectors; 42 | } 43 | 44 | /*////////////////////////////////////////////////////////////// 45 | ENUMS 46 | //////////////////////////////////////////////////////////////*/ 47 | 48 | /// @notice Possible actions to take for a message 49 | enum MessageAction { 50 | Noop, 51 | Buy, 52 | Sell 53 | } 54 | 55 | /*////////////////////////////////////////////////////////////// 56 | CONSTANTS 57 | //////////////////////////////////////////////////////////////*/ 58 | 59 | /// @notice Upper bound on gas price for acceptable Infernet callback response 60 | /// @dev Set to MAX_UINT48 to keep unbounded 61 | uint48 private constant MAX_CALLBACK_GAS_PRICE = type(uint48).max; 62 | 63 | /*////////////////////////////////////////////////////////////// 64 | MUTABLE 65 | //////////////////////////////////////////////////////////////*/ 66 | 67 | /// @notice subscriptionId => associated message 68 | mapping(uint32 => Message) public messages; 69 | 70 | /// @notice Summarizer callback subscriptionId => associated message subscriptionId 71 | mapping(uint32 => uint32) public summarizerToMessage; 72 | 73 | /// @notice index => attested inputs 74 | /// @dev Always modified prior to calling `verifyWithDataAttestation` 75 | /// @dev Index 0: poseidon hash 76 | /// @dev Index 1: message action 77 | mapping(uint256 => uint256) public attestedInputs; 78 | 79 | /*////////////////////////////////////////////////////////////// 80 | EVENTS 81 | //////////////////////////////////////////////////////////////*/ 82 | 83 | /// @notice Emitted when an action is executed and a message response generated 84 | /// @param id room message ID 85 | /// @param key executed friend.tech key 86 | /// @param action proposed by summarizer 87 | /// @param failure execution failure status 88 | /// @param rationales LLM rationales 89 | /// @param nodes responding LLM nodes 90 | /// @param rawInput raw input embedding vector 91 | /// @param hashedInput poseidon hash input 92 | /// @dev Note that it is not necessary to emit much of this data (since present in `messages`), but we emit for indexer convenience 93 | event MessageResponse( 94 | uint48 indexed id, 95 | address indexed key, 96 | MessageAction action, 97 | ExecutionFailure failure, 98 | string[] rationales, 99 | address[] nodes, 100 | bytes rawInput, 101 | uint256 hashedInput 102 | ); 103 | 104 | /*////////////////////////////////////////////////////////////// 105 | ERRORS 106 | //////////////////////////////////////////////////////////////*/ 107 | 108 | /// @notice Thrown if DA or proof is invalid when verified 109 | /// @dev 4-byte signature: `0x25e255b8` 110 | error InvalidVerification(); 111 | 112 | /*////////////////////////////////////////////////////////////// 113 | CONSTRUCTOR 114 | //////////////////////////////////////////////////////////////*/ 115 | 116 | /// Create new Frenrug 117 | /// @param _config global configuration 118 | /// @param _friendtech FriendtechSharesV1 address 119 | /// @param _coordinator Infernet coordinator 120 | /// @param _nodes initial permissioned Infernet nodes 121 | /// @param _status initial permissioned Infernet node statuses 122 | constructor( 123 | Config memory _config, 124 | address _friendtech, 125 | address _coordinator, 126 | address[] memory _nodes, 127 | bool[] memory _status 128 | ) FriendtechManager(_friendtech) CallbackConsumer(_coordinator) { 129 | // Intialize contract ownership to caller 130 | _initializeOwner(msg.sender); 131 | // Initialize global configuration 132 | _updateConfig(_config); 133 | // Initialize node allowlist 134 | _updateAllowlist(_nodes, _status); 135 | } 136 | 137 | /*////////////////////////////////////////////////////////////// 138 | INTERNAL FUNCTIONS 139 | //////////////////////////////////////////////////////////////*/ 140 | 141 | /// @notice Requests summarizer compute container to process LLM responses 142 | /// @dev Called after sufficient LLM container responses have been received 143 | /// @param subscriptionId ID of message subscription to summarize 144 | function _initiateSummarizerRequest(uint32 subscriptionId) internal { 145 | // Initialize callback request for summarization 146 | uint32 id = _requestCompute( 147 | config.containerId, 148 | "", // Inputs empty since we opt to use `getContainerInputs()` 149 | MAX_CALLBACK_GAS_PRICE, 150 | config.maxCallbackGasLimit, 151 | 1 // `redundancy == 1` since executing a model with a succinct proof 152 | ); 153 | 154 | // Store summarizer ID to message ID 155 | summarizerToMessage[id] = subscriptionId; 156 | } 157 | 158 | /// @notice Processes an off-chain created LLM response subscription 159 | /// @param subscriptionId subscription ID 160 | /// @param redundancy request redundancy count 161 | /// @param node delivering node (permissioned) 162 | /// @param input compute container input (in this case: {hashed input, id, key}) 163 | /// @param output compute container output (in this case: {LLM rationale string, LLM embedding vectors}) 164 | /// @param proof empty (no succinct proof for LLM response) 165 | function _processLLMResponse( 166 | uint32 subscriptionId, 167 | uint16 redundancy, 168 | address node, 169 | bytes calldata input, 170 | bytes calldata output, 171 | bytes calldata proof 172 | ) internal { 173 | // Collect message (or initialize) 174 | Message storage message = messages[subscriptionId]; 175 | 176 | // Decode output (LLM rationale string, LLM embedding vectors) 177 | (string memory rationale, bytes memory vectorString) = abi.decode(output, (string, bytes)); 178 | (int256[] memory vectors) = abi.decode(vectorString, (int256[])); 179 | 180 | // Store node, rationale 181 | message.nodes.push(node); 182 | message.rationales.push(rationale); 183 | 184 | // If first response for subscription 185 | if (redundancy == 1) { 186 | // Decode {hashed input, id, key} from input 187 | (bytes32 hashed, uint48 id, address key) = abi.decode(input, (bytes32, uint48, address)); 188 | 189 | // Update message 190 | message.id = id; 191 | message.key = key; 192 | message.inputs.push(hashed); 193 | 194 | // Initialize vectors array 195 | for (uint256 i = 0; i < vectors.length; i++) { 196 | message.vectors.push(vectors[i]); 197 | } 198 | } else { 199 | // In-place update vectors array (online addition) 200 | for (uint256 i = 0; i < vectors.length; i++) { 201 | message.vectors[i] += vectors[i]; 202 | } 203 | } 204 | 205 | // If this is the last response for a subscriptionId, kick off summarizer request 206 | if (redundancy == config.nodes) { 207 | _initiateSummarizerRequest(subscriptionId); 208 | } 209 | } 210 | 211 | /// @notice Process a summarized decision, execution an action, emitting a response + rationale 212 | /// @param subscriptionId summarizer callback subscription ID 213 | /// @param input compute container input (in this case, {raw input vector, poseidon hash}) 214 | /// @param output compute container output (in this case, {action}) 215 | /// @param proof compute container proof (in this case, {EZKL proof}) 216 | function _processSummarizerResponse( 217 | uint32 subscriptionId, 218 | bytes calldata input, 219 | bytes calldata output, 220 | bytes calldata proof 221 | ) internal { 222 | // Collect associated message subscription ID 223 | uint32 id = summarizerToMessage[subscriptionId]; 224 | 225 | // Decode input, output 226 | (bytes memory rawInputVector, uint256 poseidonHash) = abi.decode(input, (bytes, uint256)); 227 | (MessageAction action) = abi.decode(output, (MessageAction)); 228 | 229 | // Update Data Attestation inputs (poseiden hash, action) 230 | attestedInputs[0] = poseidonHash; 231 | attestedInputs[1] = uint256(action); 232 | 233 | // Verify EKZL proof 234 | bool verified = IDataAttestation(config.attestor).verifyWithDataAttestation(config.verifier, proof); 235 | if (!verified) { 236 | revert InvalidVerification(); 237 | } 238 | 239 | // Collect message 240 | Message memory message = messages[id]; 241 | 242 | // Execute based on action 243 | ExecutionFailure failure = ExecutionFailure.None; 244 | if (action == MessageAction.Buy) { 245 | failure = buyKey(message.key); 246 | } else if (action == MessageAction.Sell) { 247 | failure = sellKey(message.key); 248 | } 249 | 250 | // Emit execution event 251 | emit MessageResponse( 252 | message.id, message.key, action, failure, message.rationales, message.nodes, rawInputVector, poseidonHash 253 | ); 254 | } 255 | 256 | /// @notice Incoming callback receive function 257 | /// @dev Overriding `CallbackConsumer._receiveCompute` 258 | /// @dev Restricted to allowlist of permissioned nodes 259 | function _receiveCompute( 260 | uint32 subscriptionId, 261 | uint32 interval, 262 | uint16 redundancy, 263 | address node, 264 | bytes calldata input, 265 | bytes calldata output, 266 | bytes calldata proof 267 | ) internal override onlyPermissionedNode(node) { 268 | // Check if receiving compute output for a known summarization request 269 | if (summarizerToMessage[subscriptionId] != 0) { 270 | // Process summarizer response 271 | return _processSummarizerResponse(subscriptionId, input, output, proof); 272 | } 273 | 274 | // Else, we are receiving container response for some off-chain LLM subscription 275 | // At this point, we could perform a check to ensure that redundancy > configured redundancy, but not necessary since permissioned node set 276 | // At this point, we could enforce `interval == 1`, but not required since we can assume off-chain subscription creator is enforcing in subscription config 277 | // Thus, we process some LLM response 278 | return _processLLMResponse(subscriptionId, redundancy, node, input, output, proof); 279 | } 280 | 281 | /*////////////////////////////////////////////////////////////// 282 | FUNCTIONS 283 | //////////////////////////////////////////////////////////////*/ 284 | 285 | /// @notice View function to broadcast dynamic container inputs to off-chain Infernet nodes 286 | /// @notice Broadcasts averaged output embedding vectors 287 | /// @param subscriptionId subscription ID to collect container inputs for 288 | /// @param interval subscription interval to collect container inputs for 289 | /// @param timestamp timestamp at which container inputs are collected 290 | /// @param caller calling address 291 | function getContainerInputs(uint32 subscriptionId, uint32 interval, uint32 timestamp, address caller) 292 | external 293 | view 294 | virtual 295 | returns (bytes memory) 296 | { 297 | // Collect message subscription ID based on summarizer subscription ID 298 | uint32 messageId = summarizerToMessage[subscriptionId]; 299 | 300 | // Setup reference to LLM output embedding vectors 301 | int256[] memory vectors = messages[messageId].vectors; 302 | 303 | // Create new averaged vector array 304 | int256[] memory averaged = new int256[](vectors.length); 305 | 306 | // Average all vectors 307 | SD59x18 divisor = sd(int256(uint256(config.nodes)) * 1e18); 308 | for (uint256 i = 0; i < vectors.length; i++) { 309 | averaged[i] = div(sd(vectors[i]), divisor).unwrap(); 310 | } 311 | 312 | // Encode averaged vectors 313 | bytes memory inputs = abi.encode(averaged); 314 | return inputs; 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /test/Frenrug.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {Frenrug} from "../src/Frenrug.sol"; 6 | import {MockKey} from "./mocks/MockKey.sol"; 7 | import {Coordinator} from "infernet/Coordinator.sol"; 8 | import {LibSign} from "infernet-tests/lib/LibSign.sol"; 9 | import {Configured} from "../src/pattern/Configured.sol"; 10 | import {MockNode} from "infernet-tests/mocks/MockNode.sol"; 11 | import {DataAttestation} from "./deployed/DataAttestation.sol"; 12 | import {FriendtechManager} from "../src/FriendtechManager.sol"; 13 | import {EIP712Coordinator} from "infernet/EIP712Coordinator.sol"; 14 | import {FriendtechSharesV1} from "./interfaces/FriendtechSharesV1.sol"; 15 | 16 | /// @title IFrenrugEvents 17 | /// @notice Events emitted by Frenrug 18 | contract IFrenrugEvents { 19 | event ConfigUpdated(Frenrug.Config newConfig); 20 | event MessageResponse( 21 | uint48 indexed id, 22 | address indexed key, 23 | Frenrug.MessageAction action, 24 | FriendtechManager.ExecutionFailure failure, 25 | string[] rationales, 26 | address[] nodes, 27 | bytes rawInput, 28 | uint256 hashedInput 29 | ); 30 | } 31 | 32 | /// @title FrenrugParams 33 | /// @notice Useful mock parameters 34 | contract FrenrugParams { 35 | /*////////////////////////////////////////////////////////////// 36 | CONSTANTS 37 | //////////////////////////////////////////////////////////////*/ 38 | 39 | /// @notice Mock noop action poseidon hash 40 | uint256 internal constant HASH_NOOP = 41 | 11_839_161_816_435_908_610_894_408_149_244_111_300_119_165_065_228_251_838_388_298_906_407_508_472_069; 42 | 43 | /// @notice Mock noop action proof 44 | bytes internal constant PROOF_NOOP = 45 | hex"1e8e1e1300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000be00000000000000000000000000000000000000000000000000000000000000b8016b0b9b8f03952ece18dbb887b5d7cdc6f8c45077be0c35e0494bb2975872d4e28638954e2d270db592e504b2fcbaba2ec050a2e62482325f6c2ba9c52a260db0647d15f2287cfdf0ae2b66d33c51748a332e01b6b9146d606d8d2e9257e4b031dc2dabace3eabebac4db3ec5530e12c240372cbad5b9fc5934d98b2b974e46b009040181c5809350786237291b3ce821b33095fc465a98e732abb5426e70dfb287fbbbb9dbf575a3ca9d491b8cc9006e213c4fe096810481f4a4f7a453ea1d71c03b3aa1fc9e394a10a9519331c2ee0610fe7ddee2ad9283adddcbe1b03e5fa2700e6f1c68d74546101c8f9ae00f6fb0c0352e1aceac8c6d10d74fce02ad4de0bf344b9f1223f49b7d4ca9d30bffefcc0ca700a1d243711e5cabf3d6920f6311ce9fe1e58894a3be4d0940bbae4af67f533d35d8cea758f5d143a6ba24c3b190996747678828dd35d9082b5085b60c183e472f219da31c6699f173628e8e32e2e795c73d04a5de8ad271aa2090fa5b34ff6ebf25e7d5cfc0f4a7f3dc7d58cd12c872e16ce26fc901d581a0870f14b051f4bd86f9f972f658008ae2a650518a214abe851e847b69e50acd12e1c807669b03eb98e0c817d10b36047bf1a2b760825352e5574e3f198a1ec3ff46fd47090a7a60bb001570fe19cb6c81a2f85f5a32ba4c7b1373a2f5998e3ce576c4bcb6d7bbe4287c681501abc2fcad3ebf27e971a2ecc1b2b8074e56dfd6c7e795e7cb589908e6e276ba7f4e0772626b9c0d93601cc3ef974bd0481fd4762a3a314e0d09b71d510d92b55915453316bda70ca680a8ecdf8f5710e229fd1b48324e8db5ee269b29a0d406ca8ac7b6cda7b7dfbdb0bc118e4c994c62b080548396c1d307264903571745d1ab0573a9273c84a705901027476125b8eb86cf2c454c2346178dabf4ab20ab36b46e80827c33f756540164e75d4f0a97f04fff8c4990d74fa61ee07074faf2a3ba935a90d2c69da4dad08cc6fe441300309085e7606533d6d6f3ceed38640fd5bea75f7c6e8fede0812129e3655133457a2d39f911a9676ed34ef016ebdbe00362be06ac8e3033c18000a1f0840e92529003c99247d8dd94b9f2a24230c520c0203da1f3982d2450e090e260d86d97710e383c48d625b3da2c737fc6de7b8880bb16b17643845fb80be14a872ebd0d82eb6123f2d71d2a3f7e84c8a4a1b74c667113c972ad8d3aef70d035ee2bc6f5d288287cb37359ae0aae54317e00f5061d5ba16af6f092f021c291c0b8db0bebcdcf71e74c332cac6074d04c7ea17291602e19fd67ac347678bf21847874c150f9cc65fc1039a7655f057c836ab6df6d12bb7852f8b4d88aaa6111b542f351c6173fd6c59b1d31fb27bf2f8f2d825edcbe8038c5cc211659f5f7415ac5045553616f5d4f9aadef8c5a0593b657a818d5a6996b76a060522d420e0016286fd52279ecf2d20eb1f339540d8938610dd6d7d9da16707b6543e2b454f01d51e7af2c8efd3c5fbe011dcc39aa25cff25b24d8e1347b15694f036ac93d11bab5cc1b8e2bbc619ed51d19fdfa894e881045b59e1dac231fc6f36bc3a050e24ae5f4da7bcc08dcc404c0b95610e7475b34505f6b421ea991199fee2725bae04133c79ab269854d78846e3470c4fa9f606ea7c54e729e0a734a6b290c812302f7149c359321fd44860a0eaa8b0f9a43d20eab1b309119e5afc07d52ff4791325b8f542a4b87867100cc0eb15a8e98b11f04a28fba51b341cf10337fc2f81a829c1ad03c49c6e9f6bba873ff05be83ea504148afc1720c844f9b257394d244814436a81d5890d857c536a4fb8001c407016c1718bbf06bf49f6240595d560551447959a7b42d68d755edfee1091cd041bc7040c64afab4a2e96ed995f90b4f620866794e7457417a81f0a4cd4bc4e39a4a2a649ef5cda27d07eb2107db4aeb50189c1e55f917141401cad015dc427c7d018cf1e44f377aef6a1606e4846ac4b262f0fc3e9857919197d9be2c8f5a004c3f3baec147b9f5d986bcc75f43b06a119d9acc386bd0d69cc461d5e3bdfb0e7a0d7952910bb255db6e471171c5f353619affac2b03a43e62a8b465eac28b38758343465cb89a1bcaf5d21619b03a31b14dbc3c85d6065f93f730f77d6329ff4d43dbc6340eb5d1ed558b1391f5141a20a167c2922c4cc5713910b5791790742148d104a1036b9c7914abc09d4f7587e0d0a572e7c4d8e135ff25d842669226685ce68d4df8db8614a5cf357a322c35123bbf937a5ebd1a0d8548e06154aa6a92f3f7b9d3fa5a80e3ae52315b876e6a217afc790516d1436751ded5746a023059a3a92b4fc0491ae70af48acbc1b4f99257ffadb009de443689bde3256945bf6cb954f83f6e69f9dc220a618a8e25744034ac14fa7e491c9101c853d78a3fe62175701e5100d5eee6a4d66890f6e78d918ef7e967b780826f40ee744136a099156a4542ceae9d3e6d3f70de17872c9e51b03f43c68d8a35b0564dab508e8771d4e5b9fb48618aa2dd5c80f6b98092c1e1d7cd53b8f713b52ede7dfed602da27e698324501c4239c7ef6d7423f3effd852193df1f80cc606db48102b9e36486553bfb7a4d3cb8d9372d0860dd0420b8ff1c1e23d9be7388ce79e0499b0b04f27b717f2ddbabb55e0620079e7b89ae7ee80f33b4d52890b36f6d27c693035b386e195ec034e4c08a124b4206ee57450901268e9e545f58a9165e988fd6fcf191a299262406d31c0f86bd893b4f2cfc96b626de350ab9211dc99882e2654c610b04f4fb2835d3c3d4c52180b1a3f9c723f62c68b3f99751d35c3ca0e4f53b807a1365d7453fadb988b5f89fedaeb1ac91601ea13888f58cab9b7659e8795abefb5626b8e4d1d06e779ece310ef2890722df0a9122eadaa42fbc6cac59c7ce97b978e9df5023b94d805c242cb5f3fa37ed531c64962ef870c50f48990328508e1e76db3bcd4e53269f01282dca7d97587abc22d1000cf6fa72fd4b4ceab4f3314fd24a68ee2a00025a13e7619c9dff0f31eb0dca9a1d722e0cf8f937637854005d148043b5327aa836306f465c8346db5a952b1ed32debd5d303ba13557700bbe175718f38867e89790ed3c3a89b0d6e270713928620f164cd22de654a7d0c8a6c61f496024ec3c696eeb9de36c83574a6d009f0335d42bc18756fe2375bf3bc3dd13642b7fa2158f908d37a691a14a1cdc808b4852629cb90f160b0c17d942c2625c89752f0812bcd00284e93ceb702a7480a6bdfd87264a65cbfeeaba72a71091323b46af40e2c80060688508bd99d88cd188b52e4964b956734e1e21c6b8b3630fd80e11b576e0f3fec8c272b7e02f91e2982b8ec85bd07136fd48f3869634b3c7d70900087b36a49573488fc0343880109c0ac83b77ad26ef8b679c9ae2b3f6965c4520226363571e8433909f13dcd962207a087b41b6b7739a8c6b6ef0666ee3a14edd51b6755ea93f57c95d51a93311a8769cd5c74f51aa9856c58ea8c643938d408030cec5c15e15d1be743106015176c28e2fd7971275057e23efeff0fbbb021d57a165e7d2526a50bd7b2b97c221865d008200cbf1ffef4df9454c78fd1cf58272869cc81f8be4d5e0c6a64a5db2049650077f19a730ec24053e3c785d706e1c9d27100d39d761d9b2547ddcb4f25b2e1c3d8318720009e9b44a04e77b6bafd6d871f7e200bfca3dc583afeb7dc065caadcd5158283dc0e0a7f26fc99f782e10ff32d18f9019fd4694e4f66491a26d40b955a25d94057e7fe6178d0eccbcb6ca42b02578a16699623aa06e4cf700cfaa63cfa5b14ec5d412ecdaf56cf0a9f5d3edfb7b47dbd4a6dde08e59868fa0be642bd8840febfb2ae52c3638ff32ffba44baba17b4da84fea0f17a0bb84cd24579dfc6b20c93a92a65b3b27ae6851f24f45656429c7f4b1d29ebf36e03622055d6153181c8a579582b16662042951bc76c5a388ca955c84b1149012a264462331f9c1e7d0ae31a8cfe1b50ce364e5f1327a2c7fcca00c7259d2dbfaa204a61350dd86f07ea593641a443d2bf21c5e71b3fbcbbafc9306dba5ab9a5ac44dfd1672a7d94941c3332b9ad46ca5098e0e7c19da9f8147c4862cb6bb824c9a454625c24477b6f333f01c18426028504445a8c497e8c34f7b7c82d4ac20e6dee59200000000000000000000000000000000000000000000000000000000000000021a2cba6648fad47fdd7a13a015b26f13858902475533d36946268f636ef17105000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; 46 | 47 | /// @notice Mock buy action posiedon hash 48 | uint256 internal constant HASH_BUY = 49 | 17_639_449_752_237_132_562_357_721_156_698_828_966_712_960_511_397_511_862_471_908_578_071_080_397_629; 50 | 51 | /// @notice Mock buy action proof 52 | bytes internal constant PROOF_BUY = 53 | hex"1e8e1e1300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000be00000000000000000000000000000000000000000000000000000000000000b802b76c6d408a2a541941680a0faea440033bc327e3c5211afb89a9d061db685f501ce9cc7cb6d2a4bae4fa9f74b7b86fe634051b07853faf4fe30b2a1f201f640292b97f4b18fe70370a9ebeb7e5bcd531aef23518a11e5d536fe12d1135c22591ee3e989568b4ae5a953c486bcb87a42ac2eae481c3b31d0ced57c0795de265f00dfa63a19a734b7decdaab273c5446ac3fda648565b2e94283d42db8e6258e8266cb1ef798ce9a78607ae162e4fd720a11345024d996deb06fcfa98240a252807e3d5110babb62f8f5b362d142525f43e5f92b89ea24e82a3e53414f38405da26f6c21999dee5b49ac9c0d722e1ded06ca9d93e07dded1895eb989199362fc4212f41dfeb66f995cb2fd12853ae4e28d1341ef2f97063baa7ad1c881d3cf6480e55dfccbd206fd813d1cc4484bd56345e9c7900ef296ba4d6e96fc5a517ae862a2b387425e1f871daab83d58bdb512cd7e81016f7f9502a163bb1351a5e5d6a181f61ea385f6186b6b8641f1033b3b67151f286bf30b9696a45adbef2fbb74215320aeb913598c268160c55a00b032ea04d161d653c01e0138d16fa361a2f5d259411188512df21f5a291faf0600c36fe07c3dfd5e86af9f05d88784c6911f31838b1a9a3805372acab96e1561fb3fac6d3c450c8f725381c2467d70e98623717ef3735092228428cee2282885435f4e913db54a28a9089a20f469827b446350c38870644a0750db7e2fac56c20c9c4db5af0fde15c71ae72a5e455c815daae06ee56359c6aa95999684e584c3b31172a6d75f91e07c87c2e0409d6d64f1cc01380093a2432976abaf9d6e53a4d6f31cea52e7ec02f67225c55c613e3f6066413129294aee2c431cef80b6818455af4f8ce6deaa336f37f4f7e1da3244f23c40d412267fce3a3b3cad2c0e5fe93a54349cf1234f9bf87373b72313c206809fa051cba1f4c37ce16aabcd92b180c7ed71f9ea5a9eae83f5a28976d58db05c8a807f33d921c4032bd30e2a65d7589584bd4223c88c4548535f89e8180680b18ed29733d1a5ec3251f7d7a7167aa12bce647b23e5cfe5fb3035a3704aba916a0b71682c2602688b8f66b65ef04f0649e64c5b72950e786c8ed90a861859050d33f12b2143815ee63a6995c037dcfb483e03a3aecec411bb8ed0f483ba0d4a6701704f45af3d255a26204fbb00b9a81cfbfabccecb7f8a45bb87e7aaf15ee46936c0f1523e3bb99b1168c8517da071be39af0ea8856888df2eec61ce1a5d1a7ef3a2af1ae77b8884e06070000525c6a4c399287d7506e6877a60724e212480e36122b3b2e299099466d0bbdeed2b7831224dd960a5b7b40e8832df9e1cfd34215fd09d0edc34f813e185895b8b2d5492b115cbea30e4bdac1f5cc1c8b4a5fb8730f2e8f16697a3dba36aeada03fd7c932a182d2805d9911bdc77f6ac2f9390e97dd21059088aba6f93beb7eb575cdbdc3805f3c0070cf2ef259cade059185e0d7f91d9f56eb446a64ec22b8cc0a93ac26bcb43ece95c7a20fc668f72ed93be8ef6f06c3349a1c17bc7185e79539c59fc271ab4d11385b68f6beeb128611e39d7bac0c90720fcff4bf98b0604ea667ad6123dd4d436e8c158e3cf5da2f58e745aee0263e40e7485a730a70ccecc5bc076c1957a518665b7250c17745346f7be11a460cdd99c1d92c39ccbcf2a9df9431043b798fa821dea9724752c5763aa91815e61d08fca4d6a0e7fbf68cdae321987880b80e4a15e35dfb7e80f3f9fa5f3add2d0d887043812d259eb928474528d6f181b5ee667c509a1801e2e13440648dc5d6285d40c87b27b52396b29725be4383833dee5b75d865b9ef0390d7687f51a2bc2f900d0f3d8f1191e9b5287696cdd4dda0a5ce8cb134c02e8efe17a3e93e8675142e9e34ed369eb6ed9a552716c732cf686a05fe0426b44898a575cf43436f4d2baf5044809db4ef5e26c49d34175da3300d003fc4fff11b73ae2799ecd589e12f01198e889cc4cdef1c568e8029ed0d1436b6cca0b644c192a8e1c9da2ecbd2028d22fd06e61a111ded7c7c040237a8a91f84bb08f3aa7de339dcbd7077f61c18f36ff19fd67139bf82aa3764dac21bcf5b4df736bc72f717dfd939068d59db2fa89f4b6fbc7c3f93b419ae0c13bd906a8599e4124910b86c53aafb27ecd9b62ec6fb552aa0e9b3b9033c1e0836e5932806d6ba27a50860625e7219afe502db0770d2db2496dac3e8bee7bf163205992cae477bbc2c5e00b192c9b19505335407834ce154aaa050c8b9d8a05508a54ab6adf268a04c4b556611851981f073582febde5b1f810bf39f002af9eeb89e827b3deeb5c7eb91074554d56f3c0df0ec0b0490bccb3568d527629a09dd96157e5363acb14756d08564fdbf36afc40ae22910648a8ce567d258ad71290a4a252b86268c47fba98d248a28785c76cc73362ff0606a99468351176d8e426eeb63ad6c53ce4042d3c266958d07efdae52ab91a5f88e678b56ddb89eb9aacd84089fa5e553e6fe2dfa7e1b736a2571547e65629ecce45da4c4c17d43b5147137813396434d4249bb9e2d305f912cff43768be2c2d09e96fda10f733544a95c5408451480e77c4537adfb250a7631e2595f9ff10d93861aa46751c91870386fa2a0c5e55aa30b0c60f6572f5c45f009578c5c902d23b33b4447f3914b06d80003b51e210a81c907fc6f30a6a26cb866c86f7092413989f50bc58057ebc33ed920521ba6980fe54225eb11345c699f6e7a1c1eb128ca9523cd76c6971d0f665c74cb6ed78703b311de633872ec09f0db34dc8fb008b577b269c27f2175384c852e9314fe57ec6bc6254d24cd303b0e465aac19e075a359855d858a33eb0194adc35922fff8645831253dc1082a3b0ea7f9f2de00fb22b3f8714a9972e444ff0421b79355cef1d459be09bd39cc869bb2eb16787044db3a69598c0a532c90837b60e443226a552a2045fcadf058384c47929f53a29bdc69f4261da00cb5275fe2078dd68121fa1ce4356cac91e15c7e41928d11a1a2a93099a26d330ba33195be3262b1eea2887005928c34bb716ed0412568b7e21612490238a568dca8db02f9089fd9a234108b335ea3af5263e58fc26f73f281324ed6f823f63a1b2a03e324ac0e20be8bdcf9257ec4b4b697ce8367d348f640d1fafadc943fab2f97e48351da3a8c9b7bdbef6011fb6bf99a5e11fd89bd1b52a53cbe9d92ff7140c30386b7f35dbd7ac7c80b4f839515151207db77e71b55c1d3130ce3a288c84b9ef84af611702a94026365a055673dab7cf0c3098637c700574da043c003e69ab029d8b47f1bceac42d512e0f30195f2308587f70972e3319fd467def3207ba36156f009b09e8d135a2367ad7f1d7dc17cbb4b23afe828a1d3c0f566f359fb8a81871890645bb25fa3d5945c1ca3ce0c64b076c914ca1b02895dc058d461478b5be4f9e23a9a62b22e7780fcf04a85d1920114608ed6d910f7c94c17d3420336c8e504a8bd5182c7f11a4dccfa4f47201d985f7528da0f3207c34952849a58ef202ce644f4f9c9358d8b7865ae914d3b94797105625f72f0de31db275e862cd7f7b42792f69dda67cf5ccc82c93d7ed0b1e5b04dd2d400026722856292c421f82e7472cef3859a98e6a59846cf207c7d0b608bcdc755bcd0596bcd5d353a68fe3497e0d1256a9f8f8787e8b5c1c97c6392ba42c60b186681a7314efc229f33d98ef054fe66b124dadf6a2f86b70bbff14ef9d5ae46e3355184be41799f815ffb9c1be21a1ebb27bf533f595251762c0e92d1217c0aa4fe62419fcb42f646038aaf62bf73ebe4ae9555952cdcf630a1f4044c79456ebf0882f3261db9447e8ed43be3baef3b8a73ac0004cb4bed1fa9eb92dc78b2595d89e246a0c9aeabee70f4a2699aac8163cd8611cfa833b8a00a68c1a05259d8bca3c039953dccf9f8a50e2ec8f2af7553072c173eacf94aa113997b8c9a1701fe41c048dcba460376c0119ad0429396e5dc53353dea7f5dde9c429a114ca19b9656e2db7536afcacf5800c3d3cbcfdebb112457d1d2ec3bdabbd6b98b747a04d91ba296f36a9fb778ed64cde3343b81454793392cff6057a23ad0fa1ad63d991a32904c81236fdab1c7786b40e46f12dd1da7db7cd1f398742087dcb2ca456f9defe000000000000000000000000000000000000000000000000000000000000000226ff9323284b28ac6a2a8bd60a4d7426d526b18287a46066dbe1e41b9830eb3d000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000"; 54 | 55 | /// @notice Mock sell action poseidon hash 56 | uint256 internal constant HASH_SELL = 57 | 6_318_868_613_873_274_998_327_244_393_014_889_399_014_915_282_795_275_949_553_354_692_790_429_023_198; 58 | 59 | /// @notice Mock sell action proof 60 | bytes internal constant PROOF_SELL = 61 | hex"1e8e1e1300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000be00000000000000000000000000000000000000000000000000000000000000b802c4d1f6a78568e0b3d80ae2b330cb143a585d7c559433ba3f59b1e689c4b8caa005961c0855306ea841836c74dcf4d05c26faa279a87d334846a1e284e63130300c6f3003145cbbb1080a3a36d33e419f0dbc38c5ee5db2d325fbd95fe20c724301752abbaad67a8bb649298ab2ce47f0133da9ab82f0d99b8a2ac8c6867172227165b90ea199af5b395cddf747f649e4ff87c3053714b9a376790c7e21f77af171cb8686a3ed12f8b75c791b22f6bdaae0c3bbb57bf4e9b0fec89828b7305a72d2c4b0c5f31398cd98ba84a3cfb033e184103af4402bfd1aef34059773150bc165cfd43b02b1b3d56a91154124210bfaaf02c9f4fd66af6b764db8724a4593c063d3b81955ddfc186cf7f4ba755a477d1f92d1df3878f84dea8279499cc16b4112e265ac0396ea2ff89dc85d9c93ed6c3110e0678bd2ff5957e0e739c4595390872ac7e5aa226af1477ccabd615a8c16481cc2ddcd24727fe1eb3f90aa7583218c7f453720ac7013581707ccdc94275866450600667bbda80368e243f6320782494b33fc7c6053c0bf0aee7e4ace81180f6ec0366f6707b71388cfd08d6d87b0a3dbc6194d81c4a1c83f289a39e6feb0ae7f3f17e41aa45bade86bb3c10a03e1d12b95f771ff98a68344cd686b0f1ff581cc4bf5af560df58b12c8296b2395b0e8e3fb2a2c3a458d379635fd603144f7fbae90993ba5b97460a2f443fe1c6e60e07dabdf1c0d6ab24b2d0e6ec879dccd52a30dd4006a1222f0e783272e1259f1fcc216fb4f1b097d5a70007973d61732050cc362e6559f5bed8e6f668ee5c3505588aff2a1983bb20f7b597d6158758752a3384d1adbc8078f36ec565d7b6882afa584ac95141d78e76c10605ed85f85138c173dce8035258313335e2881d682f610466ed1bf43273c3e185df1396f86410c81d8b4015f258232ea288a8a0bc0126450a3ffd16fbedb02f4f78b50f7a79e12dc6805f15b264f492c419a7e0ba03b56d6c2546c737549108bce1baa8f6803ff3ab0e45d8ab74cfe4f312c94a1e21af6040c134d784c123f624051b1db03c554e1f40ba1d6fa183c5082083636207fc281760f371605c898695b4a2a3efc58337b1e052e5141d8fc18a05c1e0101263c22e1df1f309fdd201b56865e1239da1c1d058d9479b0452851f1055ec9020020923782509077aa4546e970b8e384d177e4645437bd801e6c6cf780b401301329180d9f3c7ac4a01bb79c27d020861dddcb4bd502fbdc84f6ac77fdf03011c0edef67b871bf3097c63d68f679fbe4500810eada72cae70c46d4aa1bf103e0b0fdebd4674be47fd9770d500b9e911727f8bdc76f5dd7d3bf75702a0bdfbf121debd5daf38aa742f130c884f53e4d1ca69250490614ce2b37f81f1506130cd1a0ddba13b3f9ff46933a866ca1f6cebaea956a1b312e401079e68c04395b08a0ea75eab3fd5ff142b55ed414de1ef9990714a02369a479bfb9309b0007c72b412c6bee100ef289fdec1e4b3b6270da4f585e640f5ed1fbadb51be3c4e215aa111c4d01566be7294515641075251e2b672226df3f9f1dcddef3487c019c168e20aefc2b69536c4f00dcc77baf4d0bf1444ec27389914f355bb3bc0b8482bee0000bfa15084c6f12f87194d1c59bbdd828ecc4d77c0b013b1593e313c87bbb73c0558f443bdf5efa1cc8615d76de935495875dc128a3a1272a40e2386d8c6ff2a1599adb300ba9e4c2df33877a8ef476b2a6dd5a7add35e62e96234755093a42a1c69458a975d36348e64f8177ae1ac198cae9786bf2be73835c4980b20a0bed013b66d852ddec68781722111082edfa53b5396b7cd55f3cffa07ca66de7127d71b3c428b5598d0d62947f87af70939359a9c904b226f4eeef609b611ed9b2e1e22ac659dbf8ca38299349b987c5c930c74f7f641af980c4744f3334af8dcf911184afff3c188f8786f5a207fb27225cc2ea9df4417895f77330b8701292932bc084cec49ee7d0188d5d8a69f3302a6c40f25894ab72b16b6ce738359fa813dc514c984c44dd6fc814fdb8a92f7832d60230d8f7f1188d800e1ce8a76267ddd4218bae6e01d3a67c1e7fce88e142f03745161a6b412e8bb1861ca0327eb112b1c141ccaaecd3bb6634f808e8cad6d81199ad4ab4d995ecf0afeb2046e54e8a078218b928e3d820f85fd11d09071ccbb018050f98d2872c3c2dadc3a60cc729fef2d646bf4bf2259517d7446cf436052c8e62bb506144f569fc41e8091a7457afb0f9dffea24b3b27c67f2fd76e459a7766238a91337b08ced9db35d7c9ebca14a086905806bc27e1f3e066e12f6b8122ec83b533572616e266069852623b56d021de5cb06ca919441df9ba169c8319da760f471517b54ffe0ba6d8d269c73edef2e902ab0e560ab4b882d5183522cad336a26a7c76d2d639807f58b85eddd78e81b8a3342f44c4dacf3ac76bcfd43b0ccad7ff1ee4093247132951c8b4526b01a11344acc5788941e56ba6f4306ca783e647c6c5774a81a4775192a3e555093960ad3bcb3cabf2b6b0afb70f0edb9d2204e6a7cfc3f4d8413d8cfe6fab61d61430bde193b220d9a7d37427eb3dec724e79b3faa38e901efe3253b5bad7fa874462c4a350d3d4e802e74a1207d390dd70bef49070f5ea63fd6cb680b77476014ff0ffbad16fac086798e622e1083cbeef09e2e1b1ee93f9ed14f98f712b97c86702de24dcc0038d041a853fa53a70685a2efc90f3eb56f5f6bd5d5ccd2c01d98ae3028a8aea65076d7b27a844517fb15ee73242514b8477cfe55112270ada6088b1446362b14a4df3736840405af3834cbc0f345aa7ae14cf47c69b8eabaab9f1c0088a5312e2256ceb4ba9dabf6a739c0b323d5c744b678a4e499fe868d71741826c2d6572d75ac83be887d0dae93c8347a8804a0ec851119e37ecf64b380e2bd182b17e86688a7fd24f8ae475e6d4f248b248de5c6aa990c5951c51a31ad7c002e6e9c2858c0eeb5adfc53fbf00a5044aed2285b21ad67236aaab1b274a840ff07fe2ddc001482d66502aa680bd62cb44d0928fdbbad29a426f39795234357160a6e3193d6323a73e4edb757871caa36f4de39a736829eebf9f1510dfa27cd851e1c61d4fe6fc889dd9dde2f86e2521da36fcf1771e53dc5fdfdd6014efe8a5e2679401b6f4d1fe3160bb5cc90087aec4dc6b453a147eb1de107f06908d162492f3d6a7438dcac5253dc99ed2d84aecfd81305facbd30333be09ea2cf660b0b317facc87c6433d451388db7a0faa93fef488d632f91480224321cd019f3883861aefd0ca40ef55cadb666d1899f6a6f2647e66eb0132f4f75b98849a6a90ce61203bfa2af612da2c3da7fc1e26967b70b3a5c1b71a20206fc32ba53ec6085bb926d366a0c73d9c611add30654769f312e4fc2377285f5e46994ea4723e116a592a4e1363ec6ffa683fbaa9c0474f229b551b9bbc11985bf91b37552219266acc010c0f999d69d689a8c72de336dd24b8f03021961aa4797a70c085826ffe9db212692364b62c9004a2db2da20f193d39bdc9e498fac5704ca925b6ce8f3d7567106079f67e2de104ec08944b5ff84d5306e075b1e68bb9fd6ed1b28812cfb75906df61bb6627f1dcd5d2f89b6460733a33d5e728a77da3996463c54693264d882305209e4042e98f85586144f3cea2e25fd70c98e3b59462e80de1a81975cfbf21e29ef92b97e0d91eaba9c67da8c8a6e9740b2f63add2914d15ecc144ed27a2059a5d9c7725faadd34ac4ee16d7ccf890a3ea236e1e130d11afac46cd797ba624ab0be9ac9cfd58e084cea61eefc084deb76360635deb4be256c28353bee76402c2cc908f621341aca643965e232dd30bf4c687e243a482c7d5cab5c38eb56f03e52cd429b4edf21968a9c93d9aa1cb4eb218101db540d779cca669697dd8521335263ba47751dd80436173c1294642db2bb2d6bcb41324ac15fab206123d08146e4c0b93b94918fbf5bd400990de0b85f41402d9a058e31afdb451873a31062879706037c28533ef35980b80aef494b7933625e51712d78ea0e2926b7fc8ca07b86be729f7ea19b95ba824685acf484c597073ee15f172ca161b3f45188ccb193050bee9bad14cfc6ecebc47d320fef3a2716b556320a36ee7ff1e7b924da100000000000000000000000000000000000000000000000000000000000000020df85a574796c89381ba727799c5ae697c71978e6aad51a447cdd95e7b0dfbde000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000"; 62 | 63 | /// @notice Expected averaged output of `getMockVectors()`, computed via off-chain script 64 | /// @dev Not actually declared as a constant because of value type 65 | uint256[] internal AVERAGED_VECTOR = [ 66 | 201_226_443_052_291_870, 67 | 115_792_089_237_316_200, 68 | 115_792_089_237_316_200, 69 | 115_792_089_237_316_200, 70 | 213_924_318_552_017_200, 71 | 246_073_365_211_486_820, 72 | 221_474_930_644_035_340, 73 | 244_280_248_880_386_350, 74 | 115_792_089_237_316_200, 75 | 115_792_089_237_316_200, 76 | 115_792_089_237_316_200, 77 | 115_792_089_237_316_200, 78 | 115_792_089_237_316_200, 79 | 115_792_089_237_316_200, 80 | 115_792_089_237_316_200, 81 | 115_792_089_237_316_200, 82 | 68_120_352_923_870_090, 83 | 616_199_254_989_624_000, 84 | 9_048_122_912_645_340, 85 | 138_878_688_216_209_400, 86 | 194_584_712_386_131_300, 87 | 115_792_089_237_316_200, 88 | 115_792_089_237_316_200, 89 | 115_792_089_237_316_200, 90 | 339_984_089_136_123_660, 91 | 218_376_532_196_998_600, 92 | 115_792_089_237_316_200, 93 | 115_792_089_237_316_200, 94 | 115_792_089_237_316_200, 95 | 115_792_089_237_316_200, 96 | 115_792_089_237_316_200, 97 | 115_792_089_237_316_200, 98 | 413_409_233_093_261_700, 99 | 115_792_089_237_316_200, 100 | 199_697_002_768_516_540, 101 | 891_243_100_166_320_800, 102 | 115_792_089_237_316_200, 103 | 115_792_089_237_316_200, 104 | 101_351_298_391_819_000, 105 | 115_792_089_237_316_200, 106 | 115_792_089_237_316_200, 107 | 115_792_089_237_316_200, 108 | 34_314_319_491_386_414, 109 | 115_792_089_237_316_200, 110 | 365_631_014_108_657_840, 111 | 115_792_089_237_316_200, 112 | 115_792_089_237_316_200, 113 | 209_139_227_867_126_460, 114 | 115_792_089_237_316_200, 115 | 115_792_089_237_316_200, 116 | 115_792_089_237_316_200, 117 | 141_819_819_808_006_300, 118 | 304_662_257_432_937_600, 119 | 100_395_515_561_103_820, 120 | 115_792_089_237_316_200, 121 | 115_792_089_237_316_200, 122 | 115_792_089_237_316_200, 123 | 115_792_089_237_316_200, 124 | 135_752_618_312_835_700, 125 | 402_446_895_837_783_800, 126 | 115_792_089_237_316_200, 127 | 178_313_910_961_151_120, 128 | 115_792_089_237_316_200, 129 | 115_792_089_237_316_200, 130 | 136_949_479_579_925_540, 131 | 156_344_577_670_097_350, 132 | 115_792_089_237_316_200, 133 | 82_512_013_614_177_700, 134 | 115_792_089_237_316_200 135 | ]; 136 | 137 | /*////////////////////////////////////////////////////////////// 138 | FUNCTIONS 139 | //////////////////////////////////////////////////////////////*/ 140 | 141 | /// @notice Get LLM rationales 142 | function getMockRationales() internal pure returns (string[] memory) { 143 | string[] memory rationales = new string[](3); 144 | rationales[0] = unicode"😌Sure thing, let's do this. 🔑"; 145 | rationales[1] = unicode"😌Sure thing, let's do this. 🔑"; 146 | rationales[2] = unicode"😌Sure thing, let's do this. 🔑"; 147 | 148 | return rationales; 149 | } 150 | 151 | /// @notice Get encoded LLM output vector embeddings 152 | function getMockVectors() internal pure returns (bytes[] memory) { 153 | bytes[] memory vectors = new bytes[](3); 154 | vectors[0] = 155 | hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000000000000002cae662418c171efffffffffffffffffffffffffffffffffffffffffffffffffddfbb9461b1847cfffffffffffffffffffffffffffffffffffffffffffffffffb2bc36b31ecf618ffffffffffffffffffffffffffffffffffffffffffffffffff0fd1c51ab2153c00000000000000000000000000000000000000000000000002f80308c81c4930000000000000000000000000000000000000000000000000036a3a6c021d9a640000000000000000000000000000000000000000000000000312d646a7b1270c0000000000000000000000000000000000000000000000000363db979428d12efffffffffffffffffffffffffffffffffffffffffffffffffb02bf845a3143e0fffffffffffffffffffffffffffffffffffffffffffffffffd5b12d80ae7570afffffffffffffffffffffffffffffffffffffffffffffffffd2732fe5d020690fffffffffffffffffffffffffffffffffffffffffffffffffb500c6de43727fcfffffffffffffffffffffffffffffffffffffffffffffffff644f6743e7c4c08fffffffffffffffffffffffffffffffffffffffffffffffffde2063a54f26dc6ffffffffffffffffffffffffffffffffffffffffffffffffd920ff658dfa0f28fffffffffffffffffffffffffffffffffffffffffffffffffb64f38943ce834c00000000000000000000000000000000000000000000000000f203199e00638a000000000000000000000000000000000000000000000000088d2df5288452c0000000000000000000000000000000000000000000000000002025384817a0dc00000000000000000000000000000000000000000000000001ed656e5dffd7f800000000000000000000000000000000000000000000000002b34dc3d47f3564fffffffffffffffffffffffffffffffffffffffffffffffffadb043ca8c7b730fffffffffffffffffffffffffffffffffffffffffffffffff889540b6942a8d4ffffffffffffffffffffffffffffffffffffffffffffffffff2cb5d5aa4229c900000000000000000000000000000000000000000000000004b7ddba4e3e4f0c0000000000000000000000000000000000000000000000000307d44c8ce23dc8fffffffffffffffffffffffffffffffffffffffffffffffffd00debf424a5190ffffffffffffffffffffffffffffffffffffffffffffffffd2d9737a1f9a3590fffffffffffffffffffffffffffffffffffffffffffffffffc67d49d1c32b508fffffffffffffffffffffffffffffffffffffffffffffffffc1bcec73427f6a0ffffffffffffffffffffffffffffffffffffffffffffffffffe18a64ee210ccbfffffffffffffffffffffffffffffffffffffffffffffffffc4fe1f0358c814400000000000000000000000000000000000000000000000005bcb981d563f984ffffffffffffffffffffffffffffffffffffffffffffffffff1336d363dbe15400000000000000000000000000000000000000000000000002c5775db0ba45bc0000000000000000000000000000000000000000000000000c5e54e0a9ee72a0fffffffffffffffffffffffffffffffffffffffffffffffff9c923ffe1a7f644fffffffffffffffffffffffffffffffffffffffffffffffff9ed1a56f395f402000000000000000000000000000000000000000000000000016812780a2b42f8fffffffffffffffffffffffffffffffffffffffffffffffff9b8a2ba67bf0678fffffffffffffffffffffffffffffffffffffffffffffffffb9b55b8da152280ffffffffffffffffffffffffffffffffffffffffffffffffe9d30d5d3286a0700000000000000000000000000000000000000000000000000079e8b117fe4c2efffffffffffffffffffffffffffffffffffffffffffffffff9666aad7d1b0dec0000000000000000000000000000000000000000000000000512fb785acff8b0fffffffffffffffffffffffffffffffffffffffffffffffff978986cffeecdb8fffffffffffffffffffffffffffffffffffffffffffffffff9c966a69f1fcaba00000000000000000000000000000000000000000000000002e70305099e9abcffffffffffffffffffffffffffffffffffffffffffffffffffee698062619d9bffffffffffffffffffffffffffffffffffffffffffffffffff4362708a2c9ec2fffffffffffffffffffffffffffffffffffffffffffffffffc2c83325f6c05b000000000000000000000000000000000000000000000000001f7d85fedc4209c000000000000000000000000000000000000000000000000043a60b57ec244800000000000000000000000000000000000000000000000000164ad30820fd1ccfffffffffffffffffffffffffffffffffffffffffffffffffac9317413818a7cfffffffffffffffffffffffffffffffffffffffffffffffffa95e97904fb3e4cfffffffffffffffffffffffffffffffffffffffffffffffff6a3fe53141eef4cffffffffffffffffffffffffffffffffffffffffffffffffe247462dbdfe2ce800000000000000000000000000000000000000000000000001e24a497fdda2740000000000000000000000000000000000000000000000000595c751cb5ea6f8fffffffffffffffffffffffffffffffffffffffffffffffff5dc6bca72ae3b3c00000000000000000000000000000000000000000000000002797f8ea9599c90fffffffffffffffffffffffffffffffffffffffffffffffffbe4037fac10d4f2fffffffffffffffffffffffffffffffffffffffffffffffff75dd617757b7cc800000000000000000000000000000000000000000000000001e68ad37a893c24000000000000000000000000000000000000000000000000022b7290a695a9c6fffffffffffffffffffffffffffffffffffffffffffffffff586a98da7a47b300000000000000000000000000000000000000000000000000125243d3d2cdda4fffffffffffffffffffffffffffffffffffffffffffffffffc333481db3d8e3c"; 156 | vectors[1] = 157 | hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000000000000002cae662418c171efffffffffffffffffffffffffffffffffffffffffffffffffddfbb9461b1847cfffffffffffffffffffffffffffffffffffffffffffffffffb2bc36b31ecf618ffffffffffffffffffffffffffffffffffffffffffffffffff0fd1c51ab2153c00000000000000000000000000000000000000000000000002f80308c81c4930000000000000000000000000000000000000000000000000036a3a6c021d9a640000000000000000000000000000000000000000000000000312d646a7b1270c0000000000000000000000000000000000000000000000000363db979428d12efffffffffffffffffffffffffffffffffffffffffffffffffb02bf845a3143e0fffffffffffffffffffffffffffffffffffffffffffffffffd5b12d80ae7570afffffffffffffffffffffffffffffffffffffffffffffffffd2732fe5d020690fffffffffffffffffffffffffffffffffffffffffffffffffb500c6de43727fcfffffffffffffffffffffffffffffffffffffffffffffffff644f6743e7c4c08fffffffffffffffffffffffffffffffffffffffffffffffffde2063a54f26dc6ffffffffffffffffffffffffffffffffffffffffffffffffd920ff658dfa0f28fffffffffffffffffffffffffffffffffffffffffffffffffb64f38943ce834c00000000000000000000000000000000000000000000000000f203199e00638a000000000000000000000000000000000000000000000000088d2df5288452c0000000000000000000000000000000000000000000000000002025384817a0dc00000000000000000000000000000000000000000000000001ed656e5dffd7f800000000000000000000000000000000000000000000000002b34dc3d47f3564fffffffffffffffffffffffffffffffffffffffffffffffffadb043ca8c7b730fffffffffffffffffffffffffffffffffffffffffffffffff889540b6942a8d4ffffffffffffffffffffffffffffffffffffffffffffffffff2cb5d5aa4229c900000000000000000000000000000000000000000000000004b7ddba4e3e4f0c0000000000000000000000000000000000000000000000000307d44c8ce23dc8fffffffffffffffffffffffffffffffffffffffffffffffffd00debf424a5190ffffffffffffffffffffffffffffffffffffffffffffffffd2d9737a1f9a3590fffffffffffffffffffffffffffffffffffffffffffffffffc67d49d1c32b508fffffffffffffffffffffffffffffffffffffffffffffffffc1bcec73427f6a0ffffffffffffffffffffffffffffffffffffffffffffffffffe18a64ee210ccbfffffffffffffffffffffffffffffffffffffffffffffffffc4fe1f0358c814400000000000000000000000000000000000000000000000005bcb981d563f984ffffffffffffffffffffffffffffffffffffffffffffffffff1336d363dbe15400000000000000000000000000000000000000000000000002c5775db0ba45bc0000000000000000000000000000000000000000000000000c5e54e0a9ee72a0fffffffffffffffffffffffffffffffffffffffffffffffff9c923ffe1a7f644fffffffffffffffffffffffffffffffffffffffffffffffff9ed1a56f395f402000000000000000000000000000000000000000000000000016812780a2b42f8fffffffffffffffffffffffffffffffffffffffffffffffff9b8a2ba67bf0678fffffffffffffffffffffffffffffffffffffffffffffffffb9b55b8da152280ffffffffffffffffffffffffffffffffffffffffffffffffe9d30d5d3286a0700000000000000000000000000000000000000000000000000079e8b117fe4c2efffffffffffffffffffffffffffffffffffffffffffffffff9666aad7d1b0dec0000000000000000000000000000000000000000000000000512fb785acff8b0fffffffffffffffffffffffffffffffffffffffffffffffff978986cffeecdb8fffffffffffffffffffffffffffffffffffffffffffffffff9c966a69f1fcaba00000000000000000000000000000000000000000000000002e70305099e9abcffffffffffffffffffffffffffffffffffffffffffffffffffee698062619d9bffffffffffffffffffffffffffffffffffffffffffffffffff4362708a2c9ec2fffffffffffffffffffffffffffffffffffffffffffffffffc2c83325f6c05b000000000000000000000000000000000000000000000000001f7d85fedc4209c000000000000000000000000000000000000000000000000043a60b57ec244800000000000000000000000000000000000000000000000000164ad30820fd1ccfffffffffffffffffffffffffffffffffffffffffffffffffac9317413818a7cfffffffffffffffffffffffffffffffffffffffffffffffffa95e97904fb3e4cfffffffffffffffffffffffffffffffffffffffffffffffff6a3fe53141eef4cffffffffffffffffffffffffffffffffffffffffffffffffe247462dbdfe2ce800000000000000000000000000000000000000000000000001e24a497fdda2740000000000000000000000000000000000000000000000000595c751cb5ea6f8fffffffffffffffffffffffffffffffffffffffffffffffff5dc6bca72ae3b3c00000000000000000000000000000000000000000000000002797f8ea9599c90fffffffffffffffffffffffffffffffffffffffffffffffffbe4037fac10d4f2fffffffffffffffffffffffffffffffffffffffffffffffff75dd617757b7cc800000000000000000000000000000000000000000000000001e68ad37a893c24000000000000000000000000000000000000000000000000022b7290a695a9c6fffffffffffffffffffffffffffffffffffffffffffffffff586a98da7a47b300000000000000000000000000000000000000000000000000125243d3d2cdda4fffffffffffffffffffffffffffffffffffffffffffffffffc333481db3d8e3c"; 158 | vectors[2] = 159 | hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000000000000002cae662418c171efffffffffffffffffffffffffffffffffffffffffffffffffddfbb9461b1847cfffffffffffffffffffffffffffffffffffffffffffffffffb2bc36b31ecf618ffffffffffffffffffffffffffffffffffffffffffffffffff0fd1c51ab2153c00000000000000000000000000000000000000000000000002f80308c81c4930000000000000000000000000000000000000000000000000036a3a6c021d9a640000000000000000000000000000000000000000000000000312d646a7b1270c0000000000000000000000000000000000000000000000000363db979428d12efffffffffffffffffffffffffffffffffffffffffffffffffb02bf845a3143e0fffffffffffffffffffffffffffffffffffffffffffffffffd5b12d80ae7570afffffffffffffffffffffffffffffffffffffffffffffffffd2732fe5d020690fffffffffffffffffffffffffffffffffffffffffffffffffb500c6de43727fcfffffffffffffffffffffffffffffffffffffffffffffffff644f6743e7c4c08fffffffffffffffffffffffffffffffffffffffffffffffffde2063a54f26dc6ffffffffffffffffffffffffffffffffffffffffffffffffd920ff658dfa0f28fffffffffffffffffffffffffffffffffffffffffffffffffb64f38943ce834c00000000000000000000000000000000000000000000000000f203199e00638a000000000000000000000000000000000000000000000000088d2df5288452c0000000000000000000000000000000000000000000000000002025384817a0dc00000000000000000000000000000000000000000000000001ed656e5dffd7f800000000000000000000000000000000000000000000000002b34dc3d47f3564fffffffffffffffffffffffffffffffffffffffffffffffffadb043ca8c7b730fffffffffffffffffffffffffffffffffffffffffffffffff889540b6942a8d4ffffffffffffffffffffffffffffffffffffffffffffffffff2cb5d5aa4229c900000000000000000000000000000000000000000000000004b7ddba4e3e4f0c0000000000000000000000000000000000000000000000000307d44c8ce23dc8fffffffffffffffffffffffffffffffffffffffffffffffffd00debf424a5190ffffffffffffffffffffffffffffffffffffffffffffffffd2d9737a1f9a3590fffffffffffffffffffffffffffffffffffffffffffffffffc67d49d1c32b508fffffffffffffffffffffffffffffffffffffffffffffffffc1bcec73427f6a0ffffffffffffffffffffffffffffffffffffffffffffffffffe18a64ee210ccbfffffffffffffffffffffffffffffffffffffffffffffffffc4fe1f0358c814400000000000000000000000000000000000000000000000005bcb981d563f984ffffffffffffffffffffffffffffffffffffffffffffffffff1336d363dbe15400000000000000000000000000000000000000000000000002c5775db0ba45bc0000000000000000000000000000000000000000000000000c5e54e0a9ee72a0fffffffffffffffffffffffffffffffffffffffffffffffff9c923ffe1a7f644fffffffffffffffffffffffffffffffffffffffffffffffff9ed1a56f395f402000000000000000000000000000000000000000000000000016812780a2b42f8fffffffffffffffffffffffffffffffffffffffffffffffff9b8a2ba67bf0678fffffffffffffffffffffffffffffffffffffffffffffffffb9b55b8da152280ffffffffffffffffffffffffffffffffffffffffffffffffe9d30d5d3286a0700000000000000000000000000000000000000000000000000079e8b117fe4c2efffffffffffffffffffffffffffffffffffffffffffffffff9666aad7d1b0dec0000000000000000000000000000000000000000000000000512fb785acff8b0fffffffffffffffffffffffffffffffffffffffffffffffff978986cffeecdb8fffffffffffffffffffffffffffffffffffffffffffffffff9c966a69f1fcaba00000000000000000000000000000000000000000000000002e70305099e9abcffffffffffffffffffffffffffffffffffffffffffffffffffee698062619d9bffffffffffffffffffffffffffffffffffffffffffffffffff4362708a2c9ec2fffffffffffffffffffffffffffffffffffffffffffffffffc2c83325f6c05b000000000000000000000000000000000000000000000000001f7d85fedc4209c000000000000000000000000000000000000000000000000043a60b57ec244800000000000000000000000000000000000000000000000000164ad30820fd1ccfffffffffffffffffffffffffffffffffffffffffffffffffac9317413818a7cfffffffffffffffffffffffffffffffffffffffffffffffffa95e97904fb3e4cfffffffffffffffffffffffffffffffffffffffffffffffff6a3fe53141eef4cffffffffffffffffffffffffffffffffffffffffffffffffe247462dbdfe2ce800000000000000000000000000000000000000000000000001e24a497fdda2740000000000000000000000000000000000000000000000000595c751cb5ea6f8fffffffffffffffffffffffffffffffffffffffffffffffff5dc6bca72ae3b3c00000000000000000000000000000000000000000000000002797f8ea9599c90fffffffffffffffffffffffffffffffffffffffffffffffffbe4037fac10d4f2fffffffffffffffffffffffffffffffffffffffffffffffff75dd617757b7cc800000000000000000000000000000000000000000000000001e68ad37a893c24000000000000000000000000000000000000000000000000022b7290a695a9c6fffffffffffffffffffffffffffffffffffffffffffffffff586a98da7a47b300000000000000000000000000000000000000000000000000125243d3d2cdda4fffffffffffffffffffffffffffffffffffffffffffffffffc333481db3d8e3c"; 160 | 161 | return vectors; 162 | } 163 | } 164 | 165 | /// @title FrenrugTest 166 | /// @notice Base setup to inherit for Frenrug subtests 167 | contract FrenrugTest is Test, IFrenrugEvents, FrenrugParams { 168 | /*////////////////////////////////////////////////////////////// 169 | INTERNAL 170 | //////////////////////////////////////////////////////////////*/ 171 | 172 | /// @notice Coordinator 173 | EIP712Coordinator internal COORDINATOR; 174 | 175 | /// @notice Mock node (Alice) 176 | MockNode internal ALICE; 177 | 178 | /// @notice Mock node (Bob) 179 | MockNode internal BOB; 180 | 181 | /// @notice Mock node (Charlie) 182 | MockNode internal CHARLIE; 183 | 184 | /// @notice Aggregate nodes 185 | MockNode[] internal NODES; 186 | 187 | /// @notice Aggregate node addresses 188 | address[] internal NODE_ADDRESSES; 189 | 190 | /// @notice FriendtechSharesV1 contract 191 | FriendtechSharesV1 internal FRIENDTECH; 192 | 193 | /// @notice EZKL DataAttestation contract 194 | DataAttestation internal ATTESTOR; 195 | 196 | /// @notice EZKL Verifier contract address 197 | address internal VERIFIER_ADDRESS; 198 | 199 | /// @notice Delegatee address 200 | address internal DELEGATEE_ADDRESS; 201 | 202 | /// @notice Delegatee private key 203 | uint256 internal DELEGATEE_PRIVATE_KEY; 204 | 205 | /// @notice Frenrug 206 | Frenrug internal FRENRUG; 207 | 208 | /// @notice Mock Friendtech key 209 | MockKey internal KEY; 210 | 211 | /*////////////////////////////////////////////////////////////// 212 | SETUP 213 | //////////////////////////////////////////////////////////////*/ 214 | 215 | function setUp() public { 216 | // Initialize coordinator 217 | COORDINATOR = new EIP712Coordinator(); 218 | 219 | // Initialize mock nodes 220 | ALICE = new MockNode(COORDINATOR); 221 | BOB = new MockNode(COORDINATOR); 222 | CHARLIE = new MockNode(COORDINATOR); 223 | 224 | // Initialize node allowlist 225 | address[] memory nodeAllowlist = new address[](3); 226 | bool[] memory nodeStatus = new bool[](3); 227 | 228 | // For each node 229 | NODES = [ALICE, BOB, CHARLIE]; 230 | NODE_ADDRESSES = [address(ALICE), address(BOB), address(CHARLIE)]; 231 | for (uint256 i = 0; i < 3; i++) { 232 | // Select node 233 | MockNode node = NODES[i]; 234 | 235 | // Activate nodes 236 | vm.warp(0); 237 | node.registerNode(address(node)); 238 | vm.warp(COORDINATOR.cooldown()); 239 | node.activateNode(); 240 | 241 | // Add node to allowlist 242 | nodeAllowlist[i] = address(node); 243 | nodeStatus[i] = true; 244 | } 245 | 246 | // Create new FriendtechSharesV1 247 | FRIENDTECH = new FriendtechSharesV1(); 248 | 249 | // Initialize Frenrug 250 | // Setup DA parameters 251 | 252 | // Contract address static-call'd by DA contract (in this case, address(FRENRUG)) 253 | address predictedFrenrugAddress = 0x87B2d08110B7D50861141D7bBDd49326af3Ecb31; 254 | address[] memory _contractAddresses = new address[](2); 255 | _contractAddresses[0] = predictedFrenrugAddress; 256 | _contractAddresses[1] = predictedFrenrugAddress; 257 | 258 | // Function calldata to get int256 DA input parameters 259 | bytes[][] memory _calldata = new bytes[][](2); 260 | _calldata[0] = new bytes[](1); 261 | _calldata[1] = new bytes[](1); 262 | bytes4 GETTER_SELECTOR = bytes4(keccak256("attestedInputs(uint256)")); 263 | _calldata[0][0] = abi.encodeWithSelector(GETTER_SELECTOR, 0); 264 | _calldata[1][0] = abi.encodeWithSelector(GETTER_SELECTOR, 1); 265 | 266 | // Decimals and scaling set to default 0 267 | uint256[][] memory _decimals = new uint256[][](2); 268 | _decimals[0] = new uint256[](1); 269 | _decimals[1] = new uint256[](1); 270 | _decimals[0][0] = 0; 271 | _decimals[1][0] = 0; 272 | uint256[] memory _scales = new uint256[](2); 273 | _scales[0] = 0; 274 | _scales[1] = 0; 275 | 276 | // Initialize DataAttestation 277 | ATTESTOR = new DataAttestation( 278 | _contractAddresses, 279 | _calldata, 280 | _decimals, 281 | _scales, 282 | 0, 283 | address(this) 284 | ); 285 | 286 | // Deploy verifier contract 287 | VERIFIER_ADDRESS = deployCode("Verifier.sol:Halo2Verifier"); 288 | 289 | // Setup Frenrug configuration 290 | Configured.Config memory config = Configured.Config({ 291 | attestor: address(ATTESTOR), 292 | nodes: uint16(NODES.length), 293 | maxCallbackGasLimit: 10_000_000 wei, 294 | verifier: VERIFIER_ADDRESS, 295 | containerId: "inference,proving" 296 | }); 297 | 298 | // Initialize Frenrug 299 | FRENRUG = new Frenrug( 300 | config, 301 | address(FRIENDTECH), 302 | address(COORDINATOR), 303 | nodeAllowlist, 304 | nodeStatus 305 | ); 306 | 307 | // Create new delegatee 308 | DELEGATEE_PRIVATE_KEY = 0xA11CE; 309 | DELEGATEE_ADDRESS = vm.addr(DELEGATEE_PRIVATE_KEY); 310 | 311 | // Update Frenrug delegatee 312 | FRENRUG.updateDelegatee(DELEGATEE_ADDRESS); 313 | 314 | // Setup mock key 315 | KEY = new MockKey(FRIENDTECH); 316 | 317 | // Fund mock key with 1 ETH 318 | payable(KEY).transfer(1 ether); 319 | } 320 | 321 | /*////////////////////////////////////////////////////////////// 322 | UTILITY FUNCTIONS 323 | //////////////////////////////////////////////////////////////*/ 324 | 325 | /// @notice Creates new mock subscription with sane defaults 326 | function getMockSubscription() public view returns (Coordinator.Subscription memory) { 327 | return Coordinator.Subscription({ 328 | activeAt: uint32(block.timestamp), 329 | owner: address(FRENRUG), 330 | maxGasPrice: 1 gwei, 331 | redundancy: 1, 332 | maxGasLimit: 5_000_000 wei + uint32(COORDINATOR.DELEGATEE_OVERHEAD_CREATE_WEI()) 333 | + uint32(COORDINATOR.DELIVERY_OVERHEAD_WEI()), 334 | frequency: 1, 335 | period: 0, 336 | containerId: "llm-inference", 337 | inputs: "" 338 | }); 339 | } 340 | 341 | /// @notice Generates the hash of the fully encoded EIP-712 message, based on environment domain config 342 | /// @param nonce subscriber contract nonce 343 | /// @param expiry signature expiry 344 | /// @param sub subscription 345 | /// @return typed EIP-712 message hash 346 | function getMessage(uint32 nonce, uint32 expiry, Coordinator.Subscription memory sub) 347 | public 348 | view 349 | returns (bytes32) 350 | { 351 | return LibSign.getTypedMessageHash( 352 | COORDINATOR.EIP712_NAME(), COORDINATOR.EIP712_VERSION(), address(COORDINATOR), nonce, expiry, sub 353 | ); 354 | } 355 | 356 | /// @notice Generates mock LLM response 357 | /// @dev Overriding style preferences (explicitly-named returns) for ease of reading 358 | /// @param i frenrug params index 359 | /// @return nonce delegatee nonce 360 | /// @return expiry signed delegate tx expiry 361 | /// @return subscription created delegated subscription 362 | /// @return v signature param 363 | /// @return r signature param 364 | /// @return s signature param 365 | /// @return interval delivery interval 366 | /// @return input subscription input 367 | /// @return output subscription output 368 | /// @return proof optional subscription proof 369 | function getMockLLMResponse(uint256 i) 370 | public 371 | view 372 | returns ( 373 | uint32 nonce, 374 | uint32 expiry, 375 | Coordinator.Subscription memory subscription, 376 | uint8 v, 377 | bytes32 r, 378 | bytes32 s, 379 | uint32 interval, 380 | bytes memory input, 381 | bytes memory output, 382 | bytes memory proof 383 | ) 384 | { 385 | // Collect nonce based on subscriber nonce 386 | nonce = COORDINATOR.maxSubscriberNonce(address(FRENRUG)) + 1; 387 | 388 | // Setup expiry 1 hour in future 389 | expiry = uint32(block.timestamp) + 1 hours; 390 | 391 | // Generate new subscription 392 | subscription = getMockSubscription(); 393 | 394 | // Override subscription defaults to conform to LLM response (3 responding nodes) 395 | subscription.redundancy = 3; 396 | 397 | // Generate message & sign 398 | bytes32 message = getMessage(nonce, expiry, subscription); 399 | (v, r, s) = vm.sign(DELEGATEE_PRIVATE_KEY, message); 400 | 401 | // Set default interval (callback, interval==1) 402 | interval = 1; 403 | 404 | // Setup input (hashed vector, messageId, subject) 405 | bytes[] memory vectors = getMockVectors(); 406 | bytes32 hashedVector = keccak256(vectors[i]); 407 | uint48 messageId = 1; 408 | address subject = address(KEY); 409 | input = abi.encode(hashedVector, messageId, subject); 410 | 411 | // Setup output (rationale, vector) 412 | string[] memory rationales = getMockRationales(); 413 | output = abi.encode(rationales[i], vectors[i]); 414 | 415 | // Setup proof (LLM response has no succinct proof) 416 | proof = ""; 417 | } 418 | 419 | /// @notice Delivers mock LLM responses for each node 420 | function deliverLLMResponses() public { 421 | // Cache nonce locally 422 | uint32 nonce = COORDINATOR.maxSubscriberNonce(address(FRENRUG)) + 1; 423 | 424 | // For each node 425 | for (uint256 i = 0; i < NODES.length; i++) { 426 | // Generate mock LLM response 427 | ( 428 | , // Skip nonce (delivering same subscription) 429 | uint32 expiry, 430 | Coordinator.Subscription memory subscription, 431 | uint8 v, 432 | bytes32 r, 433 | bytes32 s, 434 | uint32 interval, 435 | bytes memory llmInput, 436 | bytes memory llmOutput, 437 | bytes memory llmProof 438 | ) = getMockLLMResponse(i); 439 | 440 | // Deliver signed response 441 | NODES[i].deliverComputeDelegatee( 442 | nonce, expiry, subscription, v, r, s, interval, llmInput, llmOutput, llmProof 443 | ); 444 | } 445 | } 446 | 447 | /// @notice Delivers setup summarizer response 448 | /// @param subscriptionId summarizer subscription ID to deliver 449 | /// @param action executed action 450 | /// @param status execution failure status 451 | function deliverSummarizerResponse( 452 | uint32 subscriptionId, 453 | Frenrug.MessageAction action, 454 | FriendtechManager.ExecutionFailure status 455 | ) public { 456 | // Setup inputs 457 | uint256 poseidon_hash; 458 | bytes memory rawInputVector = "mock-raw-input-vector"; 459 | bytes memory input; 460 | bytes memory output = abi.encode(uint8(action)); 461 | bytes memory proof; 462 | 463 | // Setup input + proof based on action type 464 | if (action == Frenrug.MessageAction.Noop) { 465 | // Noop 466 | poseidon_hash = HASH_NOOP; 467 | input = abi.encode(rawInputVector, poseidon_hash); 468 | proof = PROOF_NOOP; 469 | } else if (action == Frenrug.MessageAction.Buy) { 470 | // Buy 471 | poseidon_hash = HASH_BUY; 472 | input = abi.encode(rawInputVector, poseidon_hash); 473 | proof = PROOF_BUY; 474 | } else if (action == Frenrug.MessageAction.Sell) { 475 | // Sell 476 | poseidon_hash = HASH_SELL; 477 | input = abi.encode(rawInputVector, poseidon_hash); 478 | proof = PROOF_SELL; 479 | } 480 | 481 | // Expect successful data emit 482 | vm.expectEmit(address(FRENRUG)); 483 | emit MessageResponse( 484 | 1, address(KEY), action, status, getMockRationales(), NODE_ADDRESSES, rawInputVector, poseidon_hash 485 | ); 486 | 487 | // Deliver summarizer response 488 | ALICE.deliverCompute(subscriptionId, 1, input, output, proof); 489 | } 490 | } 491 | 492 | /// @title FrenrugE2ETest 493 | /// @notice Tests E2E Frenrug functionality (combined testing rather than isolated) 494 | contract FrenrugE2ETest is FrenrugTest { 495 | /// @notice Can successfully execute flow culiminating in a Noop 496 | function testSuccessfulNoopFlow() public { 497 | // Deliver LLM responses 498 | deliverLLMResponses(); 499 | 500 | // Assert initial key balance and ETH balance 501 | uint256 initialBalance = address(FRENRUG).balance; 502 | uint256 initialKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 503 | assertEq(initialBalance, 0); 504 | assertEq(initialKeys, 0); 505 | 506 | // Deliver summarizer response 507 | deliverSummarizerResponse(2, Frenrug.MessageAction.Noop, FriendtechManager.ExecutionFailure.None); 508 | 509 | // Assert final key balance and ETH balance 510 | uint256 finalBalance = address(FRENRUG).balance; 511 | uint256 finalKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 512 | assertEq(initialBalance, finalBalance); 513 | assertEq(initialKeys, finalKeys); 514 | } 515 | 516 | /// @notice Can successfully execute flow culminating in InsufficientFunds 517 | function testInsufficientFundsFlow() public { 518 | // Ensure mock key has registered w/ Frenrug and purchased first key 519 | KEY.buyKey(); 520 | 521 | // Deliver LLM responses 522 | deliverLLMResponses(); 523 | 524 | // Assert initial key balance and ETH balance 525 | uint256 initialBalance = address(FRENRUG).balance; 526 | uint256 initialKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 527 | assertEq(initialBalance, 0); 528 | assertEq(initialKeys, 0); 529 | 530 | // Deliver summarizer response 531 | deliverSummarizerResponse(2, Frenrug.MessageAction.Buy, FriendtechManager.ExecutionFailure.InsufficientFunds); 532 | 533 | // Assert final key balance and ETH balance 534 | uint256 finalBalance = address(FRENRUG).balance; 535 | uint256 finalKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 536 | assertEq(initialBalance, finalBalance); 537 | assertEq(initialKeys, finalKeys); 538 | } 539 | 540 | /// @notice Can successfully execute flow culminating in KeyNotActive 541 | function testKeyNotActiveFlow() public { 542 | // Deliver LLM responses 543 | deliverLLMResponses(); 544 | 545 | // Transfer 10 ether to Frenrug contract 546 | payable(FRENRUG).transfer(10 ether); 547 | 548 | // Assert initial key balance and ETH balance 549 | uint256 initialBalance = address(FRENRUG).balance; 550 | uint256 initialKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 551 | assertEq(initialBalance, 10 ether); 552 | assertEq(initialKeys, 0); 553 | 554 | // Deliver summarizer response 555 | deliverSummarizerResponse(2, Frenrug.MessageAction.Buy, FriendtechManager.ExecutionFailure.KeyNotActive); 556 | 557 | // Assert final key balance and ETH balance 558 | uint256 finalBalance = address(FRENRUG).balance; 559 | uint256 finalKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 560 | assertEq(initialBalance, finalBalance); 561 | assertEq(initialKeys, finalKeys); 562 | } 563 | 564 | /// @notice Can successfully execute flow culminating in KeyNotOwned 565 | function testKeyNotOwnedFlow() public { 566 | // Ensure mock key has registered w/ Frenrug and purchased at least two key 567 | KEY.buyKey(); 568 | KEY.buyKey(); 569 | 570 | // Deliver LLM responses 571 | deliverLLMResponses(); 572 | 573 | // Assert initial key balance and ETH balance 574 | uint256 initialBalance = address(FRENRUG).balance; 575 | uint256 initialKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 576 | assertEq(initialBalance, 0 ether); 577 | assertEq(initialKeys, 0); 578 | 579 | // Deliver summarizer response 580 | deliverSummarizerResponse(2, Frenrug.MessageAction.Sell, FriendtechManager.ExecutionFailure.KeyNotOwned); 581 | 582 | // Assert final key balance and ETH balance 583 | uint256 finalBalance = address(FRENRUG).balance; 584 | uint256 finalKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 585 | assertEq(initialBalance, finalBalance); 586 | assertEq(initialKeys, finalKeys); 587 | } 588 | 589 | /// @notice Can successfully execute flow culminating in a key purchase 590 | function testSuccessfulBuyFlow() public { 591 | // Ensure mock key has registered w/ Frenrug and purchased first key 592 | KEY.buyKey(); 593 | 594 | // Deliver LLM responses 595 | deliverLLMResponses(); 596 | 597 | // Transfer 10 ether to Frenrug contract 598 | payable(FRENRUG).transfer(10 ether); 599 | 600 | // Calculate cost to purchase key 601 | uint256 keyCost = FRIENDTECH.getBuyPriceAfterFee(address(KEY), 1); 602 | 603 | // Assert initial key balance and ETH balance 604 | uint256 initialBalance = address(FRENRUG).balance; 605 | uint256 initialKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 606 | assertEq(initialBalance, 10 ether); 607 | assertEq(initialKeys, 0); 608 | 609 | // Deliver summarizer response 610 | deliverSummarizerResponse(2, Frenrug.MessageAction.Buy, FriendtechManager.ExecutionFailure.None); 611 | 612 | // Assert final key balance and ETH balance 613 | uint256 finalBalance = address(FRENRUG).balance; 614 | uint256 finalKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 615 | assertEq(initialBalance - keyCost, finalBalance); 616 | assertEq(initialKeys + 1, finalKeys); 617 | } 618 | 619 | /// @notice Can successfully execute flow culminating in a key sale 620 | function testSuccessfulSellFlow() public { 621 | // Ensure mock key has registered w/ Frenrug and purchased first key 622 | KEY.buyKey(); 623 | 624 | // Deliver LLM responses 625 | deliverLLMResponses(); 626 | 627 | // Transfer 10 ether to Frenrug contract 628 | payable(FRENRUG).transfer(10 ether); 629 | 630 | // Calculate cost to purchase key 631 | uint256 keyCost = FRIENDTECH.getBuyPriceAfterFee(address(KEY), 1); 632 | 633 | // Assert initial key balance and ETH balance 634 | uint256 initialBalance = address(FRENRUG).balance; 635 | uint256 initialKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 636 | assertEq(initialBalance, 10 ether); 637 | assertEq(initialKeys, 0); 638 | 639 | // Deliver summarizer response to purchase key 640 | deliverSummarizerResponse(2, Frenrug.MessageAction.Buy, FriendtechManager.ExecutionFailure.None); 641 | 642 | // Assert final key balance and ETH balance 643 | uint256 finalBalance = address(FRENRUG).balance; 644 | uint256 finalKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 645 | assertEq(initialBalance - keyCost, finalBalance); 646 | assertEq(initialKeys + 1, finalKeys); 647 | 648 | // Deliver LLM responses 649 | deliverLLMResponses(); 650 | 651 | // Calculate expected return from selling key 652 | uint256 keyRefund = FRIENDTECH.getSellPriceAfterFee(address(KEY), 1); 653 | 654 | // Deliver summarizer response to sell key 655 | deliverSummarizerResponse(4, Frenrug.MessageAction.Sell, FriendtechManager.ExecutionFailure.None); 656 | 657 | // Assert final key balance and ETH balance 658 | finalBalance = address(FRENRUG).balance; 659 | finalKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 660 | assertEq(initialBalance - keyCost + keyRefund, finalBalance); 661 | assertEq(initialKeys, finalKeys); 662 | } 663 | 664 | /// @notice Can successfully execute flow culminating in a LastKeyLeft 665 | function testLastKeyLeftFlow() public { 666 | // Ensure mock key has registered w/ Frenrug and purchased first key 667 | KEY.buyKey(); 668 | 669 | // Deliver LLM responses 670 | deliverLLMResponses(); 671 | 672 | // Transfer 10 ether to Frenrug contract 673 | payable(FRENRUG).transfer(10 ether); 674 | 675 | // Calculate cost to purchase key 676 | uint256 keyCost = FRIENDTECH.getBuyPriceAfterFee(address(KEY), 1); 677 | 678 | // Assert initial key balance and ETH balance 679 | uint256 initialBalance = address(FRENRUG).balance; 680 | uint256 initialKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 681 | assertEq(initialBalance, 10 ether); 682 | assertEq(initialKeys, 0); 683 | 684 | // Deliver summarizer response to purchase key 685 | deliverSummarizerResponse(2, Frenrug.MessageAction.Buy, FriendtechManager.ExecutionFailure.None); 686 | 687 | // Assert final key balance and ETH balance 688 | uint256 finalBalance = address(FRENRUG).balance; 689 | uint256 finalKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 690 | assertEq(initialBalance - keyCost, finalBalance); 691 | assertEq(initialKeys + 1, finalKeys); 692 | 693 | // Sell mock key (by owner) 694 | KEY.sellKey(); 695 | 696 | // Deliver LLM responses 697 | deliverLLMResponses(); 698 | 699 | // Deliver summarizer response to sell key 700 | deliverSummarizerResponse(4, Frenrug.MessageAction.Sell, FriendtechManager.ExecutionFailure.LastKeyLeft); 701 | 702 | // Assert final key balance and ETH balance 703 | finalBalance = address(FRENRUG).balance; 704 | finalKeys = FRIENDTECH.sharesBalance(address(KEY), address(FRENRUG)); 705 | assertEq(initialBalance - keyCost, finalBalance); 706 | assertEq(initialKeys + 1, finalKeys); 707 | } 708 | 709 | /// @notice On-chain averaging is correctly performed 710 | function testAveraging() public { 711 | // Deliver LLM responses 712 | deliverLLMResponses(); 713 | 714 | // Collect averaged inputs 715 | bytes memory inputs = FRENRUG.getContainerInputs(2, 1, 0, address(0)); 716 | (uint256[] memory decoded) = abi.decode(inputs, (uint256[])); 717 | 718 | // Verify approximate equality with off-chain computed counterparts 719 | for (uint256 i = 0; i < decoded.length; i++) { 720 | // Ignore rounding based on required precision 721 | if (decoded[i] > 10e59) { 722 | assertApproxEqAbs(decoded[i] / 10e59, AVERAGED_VECTOR[i], 5); 723 | } else { 724 | assertApproxEqAbs(decoded[i], AVERAGED_VECTOR[i], 5); 725 | } 726 | } 727 | } 728 | } 729 | -------------------------------------------------------------------------------- /compiled/Verifier.sol/Verifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract Halo2Verifier { 6 | uint256 internal constant PROOF_LEN_CPTR = 0x44; 7 | uint256 internal constant PROOF_CPTR = 0x64; 8 | uint256 internal constant NUM_INSTANCE_CPTR = 0x0be4; 9 | uint256 internal constant INSTANCE_CPTR = 0x0c04; 10 | 11 | uint256 internal constant FIRST_QUOTIENT_X_CPTR = 0x03e4; 12 | uint256 internal constant LAST_QUOTIENT_X_CPTR = 0x04e4; 13 | 14 | uint256 internal constant VK_MPTR = 0x06c0; 15 | uint256 internal constant VK_DIGEST_MPTR = 0x06c0; 16 | uint256 internal constant K_MPTR = 0x06e0; 17 | uint256 internal constant N_INV_MPTR = 0x0700; 18 | uint256 internal constant OMEGA_MPTR = 0x0720; 19 | uint256 internal constant OMEGA_INV_MPTR = 0x0740; 20 | uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x0760; 21 | uint256 internal constant NUM_INSTANCES_MPTR = 0x0780; 22 | uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x07a0; 23 | uint256 internal constant ACC_OFFSET_MPTR = 0x07c0; 24 | uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x07e0; 25 | uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x0800; 26 | uint256 internal constant G1_X_MPTR = 0x0820; 27 | uint256 internal constant G1_Y_MPTR = 0x0840; 28 | uint256 internal constant G2_X_1_MPTR = 0x0860; 29 | uint256 internal constant G2_X_2_MPTR = 0x0880; 30 | uint256 internal constant G2_Y_1_MPTR = 0x08a0; 31 | uint256 internal constant G2_Y_2_MPTR = 0x08c0; 32 | uint256 internal constant NEG_S_G2_X_1_MPTR = 0x08e0; 33 | uint256 internal constant NEG_S_G2_X_2_MPTR = 0x0900; 34 | uint256 internal constant NEG_S_G2_Y_1_MPTR = 0x0920; 35 | uint256 internal constant NEG_S_G2_Y_2_MPTR = 0x0940; 36 | 37 | uint256 internal constant CHALLENGE_MPTR = 0x0f60; 38 | 39 | uint256 internal constant THETA_MPTR = 0x0f60; 40 | uint256 internal constant BETA_MPTR = 0x0f80; 41 | uint256 internal constant GAMMA_MPTR = 0x0fa0; 42 | uint256 internal constant Y_MPTR = 0x0fc0; 43 | uint256 internal constant X_MPTR = 0x0fe0; 44 | uint256 internal constant ZETA_MPTR = 0x1000; 45 | uint256 internal constant NU_MPTR = 0x1020; 46 | uint256 internal constant MU_MPTR = 0x1040; 47 | 48 | uint256 internal constant ACC_LHS_X_MPTR = 0x1060; 49 | uint256 internal constant ACC_LHS_Y_MPTR = 0x1080; 50 | uint256 internal constant ACC_RHS_X_MPTR = 0x10a0; 51 | uint256 internal constant ACC_RHS_Y_MPTR = 0x10c0; 52 | uint256 internal constant X_N_MPTR = 0x10e0; 53 | uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x1100; 54 | uint256 internal constant L_LAST_MPTR = 0x1120; 55 | uint256 internal constant L_BLIND_MPTR = 0x1140; 56 | uint256 internal constant L_0_MPTR = 0x1160; 57 | uint256 internal constant INSTANCE_EVAL_MPTR = 0x1180; 58 | uint256 internal constant QUOTIENT_EVAL_MPTR = 0x11a0; 59 | uint256 internal constant QUOTIENT_X_MPTR = 0x11c0; 60 | uint256 internal constant QUOTIENT_Y_MPTR = 0x11e0; 61 | uint256 internal constant R_EVAL_MPTR = 0x1200; 62 | uint256 internal constant PAIRING_LHS_X_MPTR = 0x1220; 63 | uint256 internal constant PAIRING_LHS_Y_MPTR = 0x1240; 64 | uint256 internal constant PAIRING_RHS_X_MPTR = 0x1260; 65 | uint256 internal constant PAIRING_RHS_Y_MPTR = 0x1280; 66 | 67 | function verifyProof( 68 | bytes calldata proof, 69 | uint256[] calldata instances 70 | ) public returns (bool) { 71 | assembly { 72 | // Read EC point (x, y) at (proof_cptr, proof_cptr + 0x20), 73 | // and check if the point is on affine plane, 74 | // and store them in (hash_mptr, hash_mptr + 0x20). 75 | // Return updated (success, proof_cptr, hash_mptr). 76 | function read_ec_point(success, proof_cptr, hash_mptr, q) -> ret0, ret1, ret2 { 77 | let x := calldataload(proof_cptr) 78 | let y := calldataload(add(proof_cptr, 0x20)) 79 | ret0 := and(success, lt(x, q)) 80 | ret0 := and(ret0, lt(y, q)) 81 | ret0 := and(ret0, eq(mulmod(y, y, q), addmod(mulmod(x, mulmod(x, x, q), q), 3, q))) 82 | mstore(hash_mptr, x) 83 | mstore(add(hash_mptr, 0x20), y) 84 | ret1 := add(proof_cptr, 0x40) 85 | ret2 := add(hash_mptr, 0x40) 86 | } 87 | 88 | // Squeeze challenge by keccak256(memory[0..hash_mptr]), 89 | // and store hash mod r as challenge in challenge_mptr, 90 | // and push back hash in 0x00 as the first input for next squeeze. 91 | // Return updated (challenge_mptr, hash_mptr). 92 | function squeeze_challenge(challenge_mptr, hash_mptr, r) -> ret0, ret1 { 93 | let hash := keccak256(0x00, hash_mptr) 94 | mstore(challenge_mptr, mod(hash, r)) 95 | mstore(0x00, hash) 96 | ret0 := add(challenge_mptr, 0x20) 97 | ret1 := 0x20 98 | } 99 | 100 | // Squeeze challenge without absorbing new input from calldata, 101 | // by putting an extra 0x01 in memory[0x20] and squeeze by keccak256(memory[0..21]), 102 | // and store hash mod r as challenge in challenge_mptr, 103 | // and push back hash in 0x00 as the first input for next squeeze. 104 | // Return updated (challenge_mptr). 105 | function squeeze_challenge_cont(challenge_mptr, r) -> ret { 106 | mstore8(0x20, 0x01) 107 | let hash := keccak256(0x00, 0x21) 108 | mstore(challenge_mptr, mod(hash, r)) 109 | mstore(0x00, hash) 110 | ret := add(challenge_mptr, 0x20) 111 | } 112 | 113 | // Batch invert values in memory[mptr_start..mptr_end] in place. 114 | // Return updated (success). 115 | function batch_invert(success, mptr_start, mptr_end, r) -> ret { 116 | let gp_mptr := mptr_end 117 | let gp := mload(mptr_start) 118 | let mptr := add(mptr_start, 0x20) 119 | for 120 | {} 121 | lt(mptr, sub(mptr_end, 0x20)) 122 | {} 123 | { 124 | gp := mulmod(gp, mload(mptr), r) 125 | mstore(gp_mptr, gp) 126 | mptr := add(mptr, 0x20) 127 | gp_mptr := add(gp_mptr, 0x20) 128 | } 129 | gp := mulmod(gp, mload(mptr), r) 130 | 131 | mstore(gp_mptr, 0x20) 132 | mstore(add(gp_mptr, 0x20), 0x20) 133 | mstore(add(gp_mptr, 0x40), 0x20) 134 | mstore(add(gp_mptr, 0x60), gp) 135 | mstore(add(gp_mptr, 0x80), sub(r, 2)) 136 | mstore(add(gp_mptr, 0xa0), r) 137 | ret := and(success, staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20)) 138 | let all_inv := mload(gp_mptr) 139 | 140 | let first_mptr := mptr_start 141 | let second_mptr := add(first_mptr, 0x20) 142 | gp_mptr := sub(gp_mptr, 0x20) 143 | for 144 | {} 145 | lt(second_mptr, mptr) 146 | {} 147 | { 148 | let inv := mulmod(all_inv, mload(gp_mptr), r) 149 | all_inv := mulmod(all_inv, mload(mptr), r) 150 | mstore(mptr, inv) 151 | mptr := sub(mptr, 0x20) 152 | gp_mptr := sub(gp_mptr, 0x20) 153 | } 154 | let inv_first := mulmod(all_inv, mload(second_mptr), r) 155 | let inv_second := mulmod(all_inv, mload(first_mptr), r) 156 | mstore(first_mptr, inv_first) 157 | mstore(second_mptr, inv_second) 158 | } 159 | 160 | // Add (x, y) into point at (0x00, 0x20). 161 | // Return updated (success). 162 | function ec_add_acc(success, x, y) -> ret { 163 | mstore(0x40, x) 164 | mstore(0x60, y) 165 | ret := and(success, staticcall(gas(), 0x06, 0x00, 0x80, 0x00, 0x40)) 166 | } 167 | 168 | // Scale point at (0x00, 0x20) by scalar. 169 | function ec_mul_acc(success, scalar) -> ret { 170 | mstore(0x40, scalar) 171 | ret := and(success, staticcall(gas(), 0x07, 0x00, 0x60, 0x00, 0x40)) 172 | } 173 | 174 | // Add (x, y) into point at (0x80, 0xa0). 175 | // Return updated (success). 176 | function ec_add_tmp(success, x, y) -> ret { 177 | mstore(0xc0, x) 178 | mstore(0xe0, y) 179 | ret := and(success, staticcall(gas(), 0x06, 0x80, 0x80, 0x80, 0x40)) 180 | } 181 | 182 | // Scale point at (0x80, 0xa0) by scalar. 183 | // Return updated (success). 184 | function ec_mul_tmp(success, scalar) -> ret { 185 | mstore(0xc0, scalar) 186 | ret := and(success, staticcall(gas(), 0x07, 0x80, 0x60, 0x80, 0x40)) 187 | } 188 | 189 | // Perform pairing check. 190 | // Return updated (success). 191 | function ec_pairing(success, lhs_x, lhs_y, rhs_x, rhs_y) -> ret { 192 | mstore(0x00, lhs_x) 193 | mstore(0x20, lhs_y) 194 | mstore(0x40, mload(G2_X_1_MPTR)) 195 | mstore(0x60, mload(G2_X_2_MPTR)) 196 | mstore(0x80, mload(G2_Y_1_MPTR)) 197 | mstore(0xa0, mload(G2_Y_2_MPTR)) 198 | mstore(0xc0, rhs_x) 199 | mstore(0xe0, rhs_y) 200 | mstore(0x100, mload(NEG_S_G2_X_1_MPTR)) 201 | mstore(0x120, mload(NEG_S_G2_X_2_MPTR)) 202 | mstore(0x140, mload(NEG_S_G2_Y_1_MPTR)) 203 | mstore(0x160, mload(NEG_S_G2_Y_2_MPTR)) 204 | ret := and(success, staticcall(gas(), 0x08, 0x00, 0x180, 0x00, 0x20)) 205 | ret := and(ret, mload(0x00)) 206 | } 207 | 208 | // Modulus 209 | let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583 // BN254 base field 210 | let r := 21888242871839275222246405745257275088548364400416034343698204186575808495617 // BN254 scalar field 211 | 212 | // Initialize success as true 213 | let success := true 214 | 215 | { 216 | // Load vk into memory 217 | mstore(0x06c0, 0x0f9bd2e657c4beabd41d3ddf0fbc5c3c10e876d1ad6d9b7102631f50a16e4351) // vk_digest 218 | mstore(0x06e0, 0x000000000000000000000000000000000000000000000000000000000000000d) // k 219 | mstore(0x0700, 0x3062cb506d9a969cb702833453cd4c52654aa6a93775a2c5bf57d68443608001) // n_inv 220 | mstore(0x0720, 0x10e3d295c1599ff535a1bb49f23d81aa03bd0ed25881f9ed12b179af67f67ae1) // omega 221 | mstore(0x0740, 0x09ff38534bd08f2b08b6010aaee9ac485d3afb3a9ae4280907537b08fc6e53e5) // omega_inv 222 | mstore(0x0760, 0x1fe62c4a3c6640bbac666390d8ab7318a0de5374d46b2921e3217838d26470ad) // omega_inv_to_l 223 | mstore(0x0780, 0x0000000000000000000000000000000000000000000000000000000000000002) // num_instances 224 | mstore(0x07a0, 0x0000000000000000000000000000000000000000000000000000000000000000) // has_accumulator 225 | mstore(0x07c0, 0x0000000000000000000000000000000000000000000000000000000000000000) // acc_offset 226 | mstore(0x07e0, 0x0000000000000000000000000000000000000000000000000000000000000000) // num_acc_limbs 227 | mstore(0x0800, 0x0000000000000000000000000000000000000000000000000000000000000000) // num_acc_limb_bits 228 | mstore(0x0820, 0x0000000000000000000000000000000000000000000000000000000000000001) // g1_x 229 | mstore(0x0840, 0x0000000000000000000000000000000000000000000000000000000000000002) // g1_y 230 | mstore(0x0860, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) // g2_x_1 231 | mstore(0x0880, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) // g2_x_2 232 | mstore(0x08a0, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) // g2_y_1 233 | mstore(0x08c0, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) // g2_y_2 234 | mstore(0x08e0, 0x186282957db913abd99f91db59fe69922e95040603ef44c0bd7aa3adeef8f5ac) // neg_s_g2_x_1 235 | mstore(0x0900, 0x17944351223333f260ddc3b4af45191b856689eda9eab5cbcddbbe570ce860d2) // neg_s_g2_x_2 236 | mstore(0x0920, 0x06d971ff4a7467c3ec596ed6efc674572e32fd6f52b721f97e35b0b3d3546753) // neg_s_g2_y_1 237 | mstore(0x0940, 0x06ecdb9f9567f59ed2eee36e1e1d58797fd13cc97fafc2910f5e8a12f202fa9a) // neg_s_g2_y_2 238 | mstore(0x0960, 0x1ffcf714d8e287c84c8115cb88741ec644df2cbabeb269528b8880d86c9e11b2) // fixed_comms[0].x 239 | mstore(0x0980, 0x2d6859e37e1d800f17a4ec8578c41aeb31c7cfbc468dcce9674bc3d8e53995d5) // fixed_comms[0].y 240 | mstore(0x09a0, 0x020c483159a6275b54ed5cfd84057c5472ad26f74b643d7022bdc4e76dc67e59) // fixed_comms[1].x 241 | mstore(0x09c0, 0x2d7e94f2e189541810ee5b24a66bdc9710851ec522aebd1b2d388e43df004a88) // fixed_comms[1].y 242 | mstore(0x09e0, 0x24624a1ec8225b808fed5ff99e2cab075786a8bfc7e7ade6c0e484180f8dce5f) // fixed_comms[2].x 243 | mstore(0x0a00, 0x0e0e4fb66fdad4e288113b9cb0766b2053d0b4a4c1d1deadf2755a70a4ea6df8) // fixed_comms[2].y 244 | mstore(0x0a20, 0x2f89d6e4ff13fc4867ac37abc6d4b5306390e99e89fe8d73b051be5bf12864c9) // fixed_comms[3].x 245 | mstore(0x0a40, 0x0ff3b77d315399f0d63b79a459a4a5862fb914b9f9732831aba4c0f956185e29) // fixed_comms[3].y 246 | mstore(0x0a60, 0x303924113a234ce615dba9d4f450c24e865631507f2dd3b6a51f9c21925dcb6c) // fixed_comms[4].x 247 | mstore(0x0a80, 0x2722a819fe3cbb9fbe73cfb99228c7bebf15b5f16ea65f0e42d3d7305cba3a48) // fixed_comms[4].y 248 | mstore(0x0aa0, 0x2461ca030f3fa93cab5cade16892baac45f2ce7999214df23b297a0989816462) // fixed_comms[5].x 249 | mstore(0x0ac0, 0x27f8d3bb759583f26e141b93574e44d397fc35c1a9b56576636b2560543df2ac) // fixed_comms[5].y 250 | mstore(0x0ae0, 0x26692a8bf01e9c59a33a740b31e4aadab931da44d29075d6914f233da6c6bcf3) // fixed_comms[6].x 251 | mstore(0x0b00, 0x0dbfc652acbf463a80604a2c096e4ebfa626ec9c5e7b34dba6efbf77189834c0) // fixed_comms[6].y 252 | mstore(0x0b20, 0x2f23244aa53ac0febf88362358078a0f322711efd1132e308fde9a7bb7c0e512) // fixed_comms[7].x 253 | mstore(0x0b40, 0x029e88a0df223a6d3e6bea4feb4b49ebc49160bb637ad7fbc824179e0286c789) // fixed_comms[7].y 254 | mstore(0x0b60, 0x0cbd2b16119ac137bcb46cbc6b1631fd33c04f00cb232d51a18ba397b899549e) // fixed_comms[8].x 255 | mstore(0x0b80, 0x1c934057195fc39dd895426736704b0fc9a2bedb43c0322b997d53094d368712) // fixed_comms[8].y 256 | mstore(0x0ba0, 0x1991332e29a50e19074148f92ddfbb8c220be0293f618e773f5f0294a61caf63) // fixed_comms[9].x 257 | mstore(0x0bc0, 0x16f3a2f98109079a086be46d74b79ccf6b75191a07981d779b2006626501b2c0) // fixed_comms[9].y 258 | mstore(0x0be0, 0x17c8b3900dd4c1255f17339d878fd5678b37ce7aeab5314f90c41139b9bf4207) // fixed_comms[10].x 259 | mstore(0x0c00, 0x28a4e48f6fd2134ca20548254c6daf3b0dfefe9f2f5b8dc1d0d15d1b387aab4e) // fixed_comms[10].y 260 | mstore(0x0c20, 0x0cdf758f99ee4b77912ab5202f5776655cc0bf63a4f36071e7a88c2713860fea) // fixed_comms[11].x 261 | mstore(0x0c40, 0x17fc91c2b8821937deb023f12564ec0b6f54775d61a6c281534f85e0b5f9680d) // fixed_comms[11].y 262 | mstore(0x0c60, 0x2481e141322fb1baf11a34ac5f203fe2a502d85e2faa7c238b28f04f6d8768f6) // fixed_comms[12].x 263 | mstore(0x0c80, 0x113820fb2c7635daa2f346036913158be5c22703708e2b622a81c1a04c9b018f) // fixed_comms[12].y 264 | mstore(0x0ca0, 0x0ea1d4b36d7af5a284dd9b14e7c4a378b298ec46079de7cb03e84d48f40dc97d) // fixed_comms[13].x 265 | mstore(0x0cc0, 0x17f1b8fd13e6488fdba54d9e7c864ff57ec80f09fc62b62bd5f28cbc8aca985e) // fixed_comms[13].y 266 | mstore(0x0ce0, 0x221c1e44a1ce5796d36c55b90e35031aed8b17cb593f653f3c50e34278f71a60) // fixed_comms[14].x 267 | mstore(0x0d00, 0x0fce26ddd1ff35fe42dc760c2c7d2047fa4848679f5c9d3f1056ef5706f02d01) // fixed_comms[14].y 268 | mstore(0x0d20, 0x088d0c911d67886808689646261ffa557af48dc19f328a6131516c7ccb3c2871) // permutation_comms[0].x 269 | mstore(0x0d40, 0x2bbe18abc41657fdeeeafcb106ae4b8627ef7f26790913efcb2c1f581f2b4825) // permutation_comms[0].y 270 | mstore(0x0d60, 0x19418a26c846e357d36923c4408728febd26dad219078210e1c3cc10068e4ffb) // permutation_comms[1].x 271 | mstore(0x0d80, 0x302fc51dfb392cb3a079ccef36cc5ed13b8dfa6eb26055040a5533fa585b97af) // permutation_comms[1].y 272 | mstore(0x0da0, 0x279aa56932218dd0da4392058fb83c6d37fefd798f79c63aac3bd82cb3814622) // permutation_comms[2].x 273 | mstore(0x0dc0, 0x05bc71b8caed20d18970d2e400bb5644ddd900c97b47cf805eb9cd655b23c242) // permutation_comms[2].y 274 | mstore(0x0de0, 0x090ccee5286dad5ac7aa82ffe6fd5702f8f09214bc2068e71a6c61c8dfbf47b3) // permutation_comms[3].x 275 | mstore(0x0e00, 0x017020dbfca6dbb2c0dbf63b158315cbbcdd3ea724591182d4196c80d34c5a36) // permutation_comms[3].y 276 | mstore(0x0e20, 0x03d456abf2ec59e95b65463bb3d65a19345304982c98cd14d525f1dcf6ff7149) // permutation_comms[4].x 277 | mstore(0x0e40, 0x17005aec6c509ed601aab60968e53dc0965323d165ed8aaf12ee7487fc4def5b) // permutation_comms[4].y 278 | mstore(0x0e60, 0x2b1a77d938f48b710c18d156d70453037b11a27e5683046a2eb9503d38904599) // permutation_comms[5].x 279 | mstore(0x0e80, 0x3009afbd26e6675c67b332cae3d64deef997ae917ec605ffae8b366896769ba4) // permutation_comms[5].y 280 | mstore(0x0ea0, 0x0a7996aa36917b2d360c2b7483d55745e68081d52528c518c61e34ae6ae959de) // permutation_comms[6].x 281 | mstore(0x0ec0, 0x0008bebe37e9ab56bb961edd911f65d07f60860309390fe53e4d0b0894b8ea42) // permutation_comms[6].y 282 | mstore(0x0ee0, 0x2f753937e56119f7b839e62aae84671ad78e883a79dec53c3edf68beffbd35d8) // permutation_comms[7].x 283 | mstore(0x0f00, 0x1490e5e2443452ab8c3886ff3ed675261e281cad52086104b34c4612d354c284) // permutation_comms[7].y 284 | mstore(0x0f20, 0x01a7837e6470babb230978a3079af34e382588caaf1c0c791e81f37222798929) // permutation_comms[8].x 285 | mstore(0x0f40, 0x155c9bea95acf37b018196229c82254163940010ab902b96a4dcde67c6a99a6f) // permutation_comms[8].y 286 | 287 | // Check valid length of proof 288 | success := and(success, eq(0x0b80, calldataload(PROOF_LEN_CPTR))) 289 | 290 | // Check valid length of instances 291 | let num_instances := mload(NUM_INSTANCES_MPTR) 292 | success := and(success, eq(num_instances, calldataload(NUM_INSTANCE_CPTR))) 293 | 294 | // Absorb vk diegst 295 | mstore(0x00, mload(VK_DIGEST_MPTR)) 296 | 297 | // Read instances and witness commitments and generate challenges 298 | let hash_mptr := 0x20 299 | let instance_cptr := INSTANCE_CPTR 300 | for 301 | { let instance_cptr_end := add(instance_cptr, mul(0x20, num_instances)) } 302 | lt(instance_cptr, instance_cptr_end) 303 | {} 304 | { 305 | let instance := calldataload(instance_cptr) 306 | success := and(success, lt(instance, r)) 307 | mstore(hash_mptr, instance) 308 | instance_cptr := add(instance_cptr, 0x20) 309 | hash_mptr := add(hash_mptr, 0x20) 310 | } 311 | 312 | let proof_cptr := PROOF_CPTR 313 | let challenge_mptr := CHALLENGE_MPTR 314 | 315 | // Phase 1 316 | for 317 | { let proof_cptr_end := add(proof_cptr, 0x0180) } 318 | lt(proof_cptr, proof_cptr_end) 319 | {} 320 | { 321 | success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) 322 | } 323 | 324 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) 325 | 326 | // Phase 2 327 | for 328 | { let proof_cptr_end := add(proof_cptr, 0x80) } 329 | lt(proof_cptr, proof_cptr_end) 330 | {} 331 | { 332 | success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) 333 | } 334 | 335 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) 336 | challenge_mptr := squeeze_challenge_cont(challenge_mptr, r) 337 | 338 | // Phase 3 339 | for 340 | { let proof_cptr_end := add(proof_cptr, 0x0180) } 341 | lt(proof_cptr, proof_cptr_end) 342 | {} 343 | { 344 | success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) 345 | } 346 | 347 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) 348 | 349 | // Phase 4 350 | for 351 | { let proof_cptr_end := add(proof_cptr, 0x0140) } 352 | lt(proof_cptr, proof_cptr_end) 353 | {} 354 | { 355 | success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) 356 | } 357 | 358 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) 359 | 360 | // Read evaluations 361 | for 362 | { let proof_cptr_end := add(proof_cptr, 0x0640) } 363 | lt(proof_cptr, proof_cptr_end) 364 | {} 365 | { 366 | let eval := calldataload(proof_cptr) 367 | success := and(success, lt(eval, r)) 368 | mstore(hash_mptr, eval) 369 | proof_cptr := add(proof_cptr, 0x20) 370 | hash_mptr := add(hash_mptr, 0x20) 371 | } 372 | 373 | // Read batch opening proof and generate challenges 374 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) // zeta 375 | challenge_mptr := squeeze_challenge_cont(challenge_mptr, r) // nu 376 | 377 | success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) // W 378 | 379 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) // mu 380 | 381 | success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) // W' 382 | 383 | // Read accumulator from instances 384 | if mload(HAS_ACCUMULATOR_MPTR) { 385 | let num_limbs := mload(NUM_ACC_LIMBS_MPTR) 386 | let num_limb_bits := mload(NUM_ACC_LIMB_BITS_MPTR) 387 | 388 | let cptr := add(INSTANCE_CPTR, mul(mload(ACC_OFFSET_MPTR), 0x20)) 389 | let lhs_y_off := mul(num_limbs, 0x20) 390 | let rhs_x_off := mul(lhs_y_off, 2) 391 | let rhs_y_off := mul(lhs_y_off, 3) 392 | let lhs_x := calldataload(cptr) 393 | let lhs_y := calldataload(add(cptr, lhs_y_off)) 394 | let rhs_x := calldataload(add(cptr, rhs_x_off)) 395 | let rhs_y := calldataload(add(cptr, rhs_y_off)) 396 | for 397 | { 398 | let cptr_end := add(cptr, mul(0x20, num_limbs)) 399 | let shift := num_limb_bits 400 | } 401 | lt(cptr, cptr_end) 402 | {} 403 | { 404 | cptr := add(cptr, 0x20) 405 | lhs_x := add(lhs_x, shl(shift, calldataload(cptr))) 406 | lhs_y := add(lhs_y, shl(shift, calldataload(add(cptr, lhs_y_off)))) 407 | rhs_x := add(rhs_x, shl(shift, calldataload(add(cptr, rhs_x_off)))) 408 | rhs_y := add(rhs_y, shl(shift, calldataload(add(cptr, rhs_y_off)))) 409 | shift := add(shift, num_limb_bits) 410 | } 411 | 412 | success := and(success, eq(mulmod(lhs_y, lhs_y, q), addmod(mulmod(lhs_x, mulmod(lhs_x, lhs_x, q), q), 3, q))) 413 | success := and(success, eq(mulmod(rhs_y, rhs_y, q), addmod(mulmod(rhs_x, mulmod(rhs_x, rhs_x, q), q), 3, q))) 414 | 415 | mstore(ACC_LHS_X_MPTR, lhs_x) 416 | mstore(ACC_LHS_Y_MPTR, lhs_y) 417 | mstore(ACC_RHS_X_MPTR, rhs_x) 418 | mstore(ACC_RHS_Y_MPTR, rhs_y) 419 | } 420 | 421 | pop(q) 422 | } 423 | 424 | // Revert earlier if anything from calldata is invalid 425 | if iszero(success) { 426 | revert(0, 0) 427 | } 428 | 429 | // Compute lagrange evaluations and instance evaluation 430 | { 431 | let k := mload(K_MPTR) 432 | let x := mload(X_MPTR) 433 | let x_n := x 434 | for 435 | { let idx := 0 } 436 | lt(idx, k) 437 | { idx := add(idx, 1) } 438 | { 439 | x_n := mulmod(x_n, x_n, r) 440 | } 441 | 442 | let omega := mload(OMEGA_MPTR) 443 | 444 | let mptr := X_N_MPTR 445 | let mptr_end := add(mptr, mul(0x20, add(mload(NUM_INSTANCES_MPTR), 6))) 446 | for 447 | { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } 448 | lt(mptr, mptr_end) 449 | { mptr := add(mptr, 0x20) } 450 | { 451 | mstore(mptr, addmod(x, sub(r, pow_of_omega), r)) 452 | pow_of_omega := mulmod(pow_of_omega, omega, r) 453 | } 454 | let x_n_minus_1 := addmod(x_n, sub(r, 1), r) 455 | mstore(mptr_end, x_n_minus_1) 456 | success := batch_invert(success, X_N_MPTR, add(mptr_end, 0x20), r) 457 | 458 | mptr := X_N_MPTR 459 | let l_i_common := mulmod(x_n_minus_1, mload(N_INV_MPTR), r) 460 | for 461 | { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } 462 | lt(mptr, mptr_end) 463 | { mptr := add(mptr, 0x20) } 464 | { 465 | mstore(mptr, mulmod(l_i_common, mulmod(mload(mptr), pow_of_omega, r), r)) 466 | pow_of_omega := mulmod(pow_of_omega, omega, r) 467 | } 468 | 469 | let l_blind := mload(add(X_N_MPTR, 0x20)) 470 | let l_i_cptr := add(X_N_MPTR, 0x40) 471 | for 472 | { let l_i_cptr_end := add(X_N_MPTR, 0xc0) } 473 | lt(l_i_cptr, l_i_cptr_end) 474 | { l_i_cptr := add(l_i_cptr, 0x20) } 475 | { 476 | l_blind := addmod(l_blind, mload(l_i_cptr), r) 477 | } 478 | 479 | let instance_eval := mulmod(mload(l_i_cptr), calldataload(INSTANCE_CPTR), r) 480 | let instance_cptr := add(INSTANCE_CPTR, 0x20) 481 | l_i_cptr := add(l_i_cptr, 0x20) 482 | for 483 | { let instance_cptr_end := add(INSTANCE_CPTR, mul(0x20, mload(NUM_INSTANCES_MPTR))) } 484 | lt(instance_cptr, instance_cptr_end) 485 | { 486 | instance_cptr := add(instance_cptr, 0x20) 487 | l_i_cptr := add(l_i_cptr, 0x20) 488 | } 489 | { 490 | instance_eval := addmod(instance_eval, mulmod(mload(l_i_cptr), calldataload(instance_cptr), r), r) 491 | } 492 | 493 | let x_n_minus_1_inv := mload(mptr_end) 494 | let l_last := mload(X_N_MPTR) 495 | let l_0 := mload(add(X_N_MPTR, 0xc0)) 496 | 497 | mstore(X_N_MPTR, x_n) 498 | mstore(X_N_MINUS_1_INV_MPTR, x_n_minus_1_inv) 499 | mstore(L_LAST_MPTR, l_last) 500 | mstore(L_BLIND_MPTR, l_blind) 501 | mstore(L_0_MPTR, l_0) 502 | mstore(INSTANCE_EVAL_MPTR, instance_eval) 503 | } 504 | 505 | // Compute quotient evavluation 506 | { 507 | let quotient_eval_numer 508 | let delta := 4131629893567559867359510883348571134090853742863529169391034518566172092834 509 | let y := mload(Y_MPTR) 510 | { 511 | let f_10 := calldataload(0x07c4) 512 | let a_3 := calldataload(0x0584) 513 | let f_1 := calldataload(0x06e4) 514 | let var0 := addmod(a_3, f_1, r) 515 | let var1 := mulmod(var0, var0, r) 516 | let var2 := mulmod(var1, var1, r) 517 | let var3 := mulmod(var2, var0, r) 518 | let var4 := mulmod(var3, 0x066f6f85d6f68a85ec10345351a23a3aaf07f38af8c952a7bceca70bd2af7ad5, r) 519 | let a_4 := calldataload(0x05a4) 520 | let f_2 := calldataload(0x0704) 521 | let var5 := addmod(a_4, f_2, r) 522 | let var6 := mulmod(var5, var5, r) 523 | let var7 := mulmod(var6, var6, r) 524 | let var8 := mulmod(var7, var5, r) 525 | let var9 := mulmod(var8, 0x2b9d4b4110c9ae997782e1509b1d0fdb20a7c02bbd8bea7305462b9f8125b1e8, r) 526 | let var10 := addmod(var4, var9, r) 527 | let a_3_next_1 := calldataload(0x05c4) 528 | let var11 := sub(r, a_3_next_1) 529 | let var12 := addmod(var10, var11, r) 530 | let var13 := mulmod(f_10, var12, r) 531 | quotient_eval_numer := var13 532 | } 533 | { 534 | let f_10 := calldataload(0x07c4) 535 | let a_3 := calldataload(0x0584) 536 | let f_1 := calldataload(0x06e4) 537 | let var0 := addmod(a_3, f_1, r) 538 | let var1 := mulmod(var0, var0, r) 539 | let var2 := mulmod(var1, var1, r) 540 | let var3 := mulmod(var2, var0, r) 541 | let var4 := mulmod(var3, 0x0cc57cdbb08507d62bf67a4493cc262fb6c09d557013fff1f573f431221f8ff9, r) 542 | let a_4 := calldataload(0x05a4) 543 | let f_2 := calldataload(0x0704) 544 | let var5 := addmod(a_4, f_2, r) 545 | let var6 := mulmod(var5, var5, r) 546 | let var7 := mulmod(var6, var6, r) 547 | let var8 := mulmod(var7, var5, r) 548 | let var9 := mulmod(var8, 0x1274e649a32ed355a31a6ed69724e1adade857e86eb5c3a121bcd147943203c8, r) 549 | let var10 := addmod(var4, var9, r) 550 | let a_4_next_1 := calldataload(0x05e4) 551 | let var11 := sub(r, a_4_next_1) 552 | let var12 := addmod(var10, var11, r) 553 | let var13 := mulmod(f_10, var12, r) 554 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var13, r) 555 | } 556 | { 557 | let f_11 := calldataload(0x07e4) 558 | let a_3 := calldataload(0x0584) 559 | let f_1 := calldataload(0x06e4) 560 | let var0 := addmod(a_3, f_1, r) 561 | let var1 := mulmod(var0, var0, r) 562 | let var2 := mulmod(var1, var1, r) 563 | let var3 := mulmod(var2, var0, r) 564 | let a_5 := calldataload(0x0604) 565 | let var4 := sub(r, a_5) 566 | let var5 := addmod(var3, var4, r) 567 | let var6 := mulmod(f_11, var5, r) 568 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var6, r) 569 | } 570 | { 571 | let f_11 := calldataload(0x07e4) 572 | let a_5 := calldataload(0x0604) 573 | let var0 := mulmod(a_5, 0x066f6f85d6f68a85ec10345351a23a3aaf07f38af8c952a7bceca70bd2af7ad5, r) 574 | let a_4 := calldataload(0x05a4) 575 | let f_2 := calldataload(0x0704) 576 | let var1 := addmod(a_4, f_2, r) 577 | let var2 := mulmod(var1, 0x2b9d4b4110c9ae997782e1509b1d0fdb20a7c02bbd8bea7305462b9f8125b1e8, r) 578 | let var3 := addmod(var0, var2, r) 579 | let f_3 := calldataload(0x06a4) 580 | let var4 := addmod(var3, f_3, r) 581 | let var5 := mulmod(var4, var4, r) 582 | let var6 := mulmod(var5, var5, r) 583 | let var7 := mulmod(var6, var4, r) 584 | let a_3_next_1 := calldataload(0x05c4) 585 | let var8 := mulmod(a_3_next_1, 0x13abec390ada7f4370819ab1c7846f210554569d9b29d1ea8dbebd0fa8c53e66, r) 586 | let a_4_next_1 := calldataload(0x05e4) 587 | let var9 := mulmod(a_4_next_1, 0x1eb9e1dc19a33a624c9862a1d97d1510bd521ead5dfe0345aaf6185b1a1e60fe, r) 588 | let var10 := addmod(var8, var9, r) 589 | let var11 := sub(r, var10) 590 | let var12 := addmod(var7, var11, r) 591 | let var13 := mulmod(f_11, var12, r) 592 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var13, r) 593 | } 594 | { 595 | let f_11 := calldataload(0x07e4) 596 | let a_5 := calldataload(0x0604) 597 | let var0 := mulmod(a_5, 0x0cc57cdbb08507d62bf67a4493cc262fb6c09d557013fff1f573f431221f8ff9, r) 598 | let a_4 := calldataload(0x05a4) 599 | let f_2 := calldataload(0x0704) 600 | let var1 := addmod(a_4, f_2, r) 601 | let var2 := mulmod(var1, 0x1274e649a32ed355a31a6ed69724e1adade857e86eb5c3a121bcd147943203c8, r) 602 | let var3 := addmod(var0, var2, r) 603 | let f_4 := calldataload(0x06c4) 604 | let var4 := addmod(var3, f_4, r) 605 | let a_3_next_1 := calldataload(0x05c4) 606 | let var5 := mulmod(a_3_next_1, 0x0fc1c9394db89bb2601abc49fdad4f038ce5169030a2ad69763f7875036bcb02, r) 607 | let a_4_next_1 := calldataload(0x05e4) 608 | let var6 := mulmod(a_4_next_1, 0x16a9e98c493a902b9502054edc03e7b22b7eac34345961bc8abced6bd147c8be, r) 609 | let var7 := addmod(var5, var6, r) 610 | let var8 := sub(r, var7) 611 | let var9 := addmod(var4, var8, r) 612 | let var10 := mulmod(f_11, var9, r) 613 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var10, r) 614 | } 615 | { 616 | let f_12 := calldataload(0x0804) 617 | let var0 := 0x2 618 | let var1 := sub(r, f_12) 619 | let var2 := addmod(var0, var1, r) 620 | let var3 := mulmod(f_12, var2, r) 621 | let var4 := 0x3 622 | let var5 := addmod(var4, var1, r) 623 | let var6 := mulmod(var3, var5, r) 624 | let var7 := 0x4 625 | let var8 := addmod(var7, var1, r) 626 | let var9 := mulmod(var6, var8, r) 627 | let a_3_prev_1 := calldataload(0x0644) 628 | let a_3 := calldataload(0x0584) 629 | let var10 := addmod(a_3_prev_1, a_3, r) 630 | let a_3_next_1 := calldataload(0x05c4) 631 | let var11 := sub(r, a_3_next_1) 632 | let var12 := addmod(var10, var11, r) 633 | let var13 := mulmod(var9, var12, r) 634 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var13, r) 635 | } 636 | { 637 | let f_12 := calldataload(0x0804) 638 | let var0 := 0x2 639 | let var1 := sub(r, f_12) 640 | let var2 := addmod(var0, var1, r) 641 | let var3 := mulmod(f_12, var2, r) 642 | let var4 := 0x3 643 | let var5 := addmod(var4, var1, r) 644 | let var6 := mulmod(var3, var5, r) 645 | let var7 := 0x4 646 | let var8 := addmod(var7, var1, r) 647 | let var9 := mulmod(var6, var8, r) 648 | let a_4_prev_1 := calldataload(0x0624) 649 | let a_4_next_1 := calldataload(0x05e4) 650 | let var10 := sub(r, a_4_next_1) 651 | let var11 := addmod(a_4_prev_1, var10, r) 652 | let var12 := mulmod(var9, var11, r) 653 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var12, r) 654 | } 655 | { 656 | let f_13 := calldataload(0x0824) 657 | let var0 := 0x2 658 | let var1 := sub(r, f_13) 659 | let var2 := addmod(var0, var1, r) 660 | let var3 := mulmod(f_13, var2, r) 661 | let var4 := 0x3 662 | let var5 := addmod(var4, var1, r) 663 | let var6 := mulmod(var3, var5, r) 664 | let var7 := 0x4 665 | let var8 := addmod(var7, var1, r) 666 | let var9 := mulmod(var6, var8, r) 667 | let a_2 := calldataload(0x0564) 668 | let a_0 := calldataload(0x0524) 669 | let a_1 := calldataload(0x0544) 670 | let var10 := mulmod(a_0, a_1, r) 671 | let a_2_prev_1 := calldataload(0x0664) 672 | let var11 := addmod(var10, a_2_prev_1, r) 673 | let var12 := sub(r, var11) 674 | let var13 := addmod(a_2, var12, r) 675 | let var14 := mulmod(var9, var13, r) 676 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var14, r) 677 | } 678 | { 679 | let f_12 := calldataload(0x0804) 680 | let var0 := 0x1 681 | let var1 := sub(r, f_12) 682 | let var2 := addmod(var0, var1, r) 683 | let var3 := mulmod(f_12, var2, r) 684 | let var4 := 0x2 685 | let var5 := addmod(var4, var1, r) 686 | let var6 := mulmod(var3, var5, r) 687 | let var7 := 0x3 688 | let var8 := addmod(var7, var1, r) 689 | let var9 := mulmod(var6, var8, r) 690 | let a_2 := calldataload(0x0564) 691 | let a_1 := calldataload(0x0544) 692 | let a_2_prev_1 := calldataload(0x0664) 693 | let var10 := mulmod(a_1, a_2_prev_1, r) 694 | let var11 := sub(r, var10) 695 | let var12 := addmod(a_2, var11, r) 696 | let var13 := mulmod(var9, var12, r) 697 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var13, r) 698 | } 699 | { 700 | let f_14 := calldataload(0x0844) 701 | let var0 := 0x1 702 | let var1 := sub(r, f_14) 703 | let var2 := addmod(var0, var1, r) 704 | let var3 := mulmod(f_14, var2, r) 705 | let var4 := 0x3 706 | let var5 := addmod(var4, var1, r) 707 | let var6 := mulmod(var3, var5, r) 708 | let a_2 := calldataload(0x0564) 709 | let a_1 := calldataload(0x0544) 710 | let var7 := sub(r, a_1) 711 | let var8 := addmod(a_2, var7, r) 712 | let var9 := mulmod(var6, var8, r) 713 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var9, r) 714 | } 715 | { 716 | let f_12 := calldataload(0x0804) 717 | let var0 := 0x1 718 | let var1 := sub(r, f_12) 719 | let var2 := addmod(var0, var1, r) 720 | let var3 := mulmod(f_12, var2, r) 721 | let var4 := 0x3 722 | let var5 := addmod(var4, var1, r) 723 | let var6 := mulmod(var3, var5, r) 724 | let var7 := 0x4 725 | let var8 := addmod(var7, var1, r) 726 | let var9 := mulmod(var6, var8, r) 727 | let a_2 := calldataload(0x0564) 728 | let a_0 := calldataload(0x0524) 729 | let a_1 := calldataload(0x0544) 730 | let var10 := addmod(a_0, a_1, r) 731 | let var11 := sub(r, var10) 732 | let var12 := addmod(a_2, var11, r) 733 | let var13 := mulmod(var9, var12, r) 734 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var13, r) 735 | } 736 | { 737 | let f_13 := calldataload(0x0824) 738 | let var0 := 0x1 739 | let var1 := sub(r, f_13) 740 | let var2 := addmod(var0, var1, r) 741 | let var3 := mulmod(f_13, var2, r) 742 | let var4 := 0x2 743 | let var5 := addmod(var4, var1, r) 744 | let var6 := mulmod(var3, var5, r) 745 | let var7 := 0x3 746 | let var8 := addmod(var7, var1, r) 747 | let var9 := mulmod(var6, var8, r) 748 | let a_2 := calldataload(0x0564) 749 | let a_0 := calldataload(0x0524) 750 | let a_1 := calldataload(0x0544) 751 | let var10 := mulmod(a_0, a_1, r) 752 | let var11 := sub(r, var10) 753 | let var12 := addmod(a_2, var11, r) 754 | let var13 := mulmod(var9, var12, r) 755 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var13, r) 756 | } 757 | { 758 | let f_12 := calldataload(0x0804) 759 | let var0 := 0x1 760 | let var1 := sub(r, f_12) 761 | let var2 := addmod(var0, var1, r) 762 | let var3 := mulmod(f_12, var2, r) 763 | let var4 := 0x2 764 | let var5 := addmod(var4, var1, r) 765 | let var6 := mulmod(var3, var5, r) 766 | let var7 := 0x4 767 | let var8 := addmod(var7, var1, r) 768 | let var9 := mulmod(var6, var8, r) 769 | let a_2 := calldataload(0x0564) 770 | let a_0 := calldataload(0x0524) 771 | let a_1 := calldataload(0x0544) 772 | let var10 := sub(r, a_1) 773 | let var11 := addmod(a_0, var10, r) 774 | let var12 := sub(r, var11) 775 | let var13 := addmod(a_2, var12, r) 776 | let var14 := mulmod(var9, var13, r) 777 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var14, r) 778 | } 779 | { 780 | let f_13 := calldataload(0x0824) 781 | let var0 := 0x1 782 | let var1 := sub(r, f_13) 783 | let var2 := addmod(var0, var1, r) 784 | let var3 := mulmod(f_13, var2, r) 785 | let var4 := 0x3 786 | let var5 := addmod(var4, var1, r) 787 | let var6 := mulmod(var3, var5, r) 788 | let var7 := 0x4 789 | let var8 := addmod(var7, var1, r) 790 | let var9 := mulmod(var6, var8, r) 791 | let a_2 := calldataload(0x0564) 792 | let a_1 := calldataload(0x0544) 793 | let a_2_prev_1 := calldataload(0x0664) 794 | let var10 := addmod(a_1, a_2_prev_1, r) 795 | let var11 := sub(r, var10) 796 | let var12 := addmod(a_2, var11, r) 797 | let var13 := mulmod(var9, var12, r) 798 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var13, r) 799 | } 800 | { 801 | let f_13 := calldataload(0x0824) 802 | let var0 := 0x1 803 | let var1 := sub(r, f_13) 804 | let var2 := addmod(var0, var1, r) 805 | let var3 := mulmod(f_13, var2, r) 806 | let var4 := 0x2 807 | let var5 := addmod(var4, var1, r) 808 | let var6 := mulmod(var3, var5, r) 809 | let var7 := 0x4 810 | let var8 := addmod(var7, var1, r) 811 | let var9 := mulmod(var6, var8, r) 812 | let a_2 := calldataload(0x0564) 813 | let a_1 := calldataload(0x0544) 814 | let var10 := sub(r, a_1) 815 | let var11 := sub(r, var10) 816 | let var12 := addmod(a_2, var11, r) 817 | let var13 := mulmod(var9, var12, r) 818 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var13, r) 819 | } 820 | { 821 | let f_14 := calldataload(0x0844) 822 | let var0 := 0x2 823 | let var1 := sub(r, f_14) 824 | let var2 := addmod(var0, var1, r) 825 | let var3 := mulmod(f_14, var2, r) 826 | let var4 := 0x3 827 | let var5 := addmod(var4, var1, r) 828 | let var6 := mulmod(var3, var5, r) 829 | let a_1 := calldataload(0x0544) 830 | let var7 := mulmod(var6, a_1, r) 831 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var7, r) 832 | } 833 | { 834 | let f_14 := calldataload(0x0844) 835 | let var0 := 0x1 836 | let var1 := sub(r, f_14) 837 | let var2 := addmod(var0, var1, r) 838 | let var3 := mulmod(f_14, var2, r) 839 | let var4 := 0x2 840 | let var5 := addmod(var4, var1, r) 841 | let var6 := mulmod(var3, var5, r) 842 | let a_1 := calldataload(0x0544) 843 | let var7 := sub(r, var0) 844 | let var8 := addmod(a_1, var7, r) 845 | let var9 := mulmod(a_1, var8, r) 846 | let var10 := mulmod(var6, var9, r) 847 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var10, r) 848 | } 849 | { 850 | let l_0 := mload(L_0_MPTR) 851 | let eval := addmod(l_0, sub(r, mulmod(l_0, calldataload(0x09a4), r)), r) 852 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 853 | } 854 | { 855 | let perm_z_last := calldataload(0x0a64) 856 | let eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(perm_z_last, perm_z_last, r), sub(r, perm_z_last), r), r) 857 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 858 | } 859 | { 860 | let eval := mulmod(mload(L_0_MPTR), addmod(calldataload(0x0a04), sub(r, calldataload(0x09e4)), r), r) 861 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 862 | } 863 | { 864 | let eval := mulmod(mload(L_0_MPTR), addmod(calldataload(0x0a64), sub(r, calldataload(0x0a44)), r), r) 865 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 866 | } 867 | { 868 | let gamma := mload(GAMMA_MPTR) 869 | let beta := mload(BETA_MPTR) 870 | let lhs := calldataload(0x09c4) 871 | let rhs := calldataload(0x09a4) 872 | lhs := mulmod(lhs, addmod(addmod(calldataload(0x0524), mulmod(beta, calldataload(0x0884), r), r), gamma, r), r) 873 | lhs := mulmod(lhs, addmod(addmod(calldataload(0x0544), mulmod(beta, calldataload(0x08a4), r), r), gamma, r), r) 874 | lhs := mulmod(lhs, addmod(addmod(calldataload(0x0564), mulmod(beta, calldataload(0x08c4), r), r), gamma, r), r) 875 | lhs := mulmod(lhs, addmod(addmod(calldataload(0x0684), mulmod(beta, calldataload(0x08e4), r), r), gamma, r), r) 876 | mstore(0x00, mulmod(beta, mload(X_MPTR), r)) 877 | rhs := mulmod(rhs, addmod(addmod(calldataload(0x0524), mload(0x00), r), gamma, r), r) 878 | mstore(0x00, mulmod(mload(0x00), delta, r)) 879 | rhs := mulmod(rhs, addmod(addmod(calldataload(0x0544), mload(0x00), r), gamma, r), r) 880 | mstore(0x00, mulmod(mload(0x00), delta, r)) 881 | rhs := mulmod(rhs, addmod(addmod(calldataload(0x0564), mload(0x00), r), gamma, r), r) 882 | mstore(0x00, mulmod(mload(0x00), delta, r)) 883 | rhs := mulmod(rhs, addmod(addmod(calldataload(0x0684), mload(0x00), r), gamma, r), r) 884 | mstore(0x00, mulmod(mload(0x00), delta, r)) 885 | let left_sub_right := addmod(lhs, sub(r, rhs), r) 886 | let eval := addmod(left_sub_right, sub(r, mulmod(left_sub_right, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r), r)), r) 887 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 888 | } 889 | { 890 | let gamma := mload(GAMMA_MPTR) 891 | let beta := mload(BETA_MPTR) 892 | let lhs := calldataload(0x0a24) 893 | let rhs := calldataload(0x0a04) 894 | lhs := mulmod(lhs, addmod(addmod(calldataload(0x0584), mulmod(beta, calldataload(0x0904), r), r), gamma, r), r) 895 | lhs := mulmod(lhs, addmod(addmod(calldataload(0x05a4), mulmod(beta, calldataload(0x0924), r), r), gamma, r), r) 896 | lhs := mulmod(lhs, addmod(addmod(calldataload(0x06a4), mulmod(beta, calldataload(0x0944), r), r), gamma, r), r) 897 | lhs := mulmod(lhs, addmod(addmod(mload(INSTANCE_EVAL_MPTR), mulmod(beta, calldataload(0x0964), r), r), gamma, r), r) 898 | rhs := mulmod(rhs, addmod(addmod(calldataload(0x0584), mload(0x00), r), gamma, r), r) 899 | mstore(0x00, mulmod(mload(0x00), delta, r)) 900 | rhs := mulmod(rhs, addmod(addmod(calldataload(0x05a4), mload(0x00), r), gamma, r), r) 901 | mstore(0x00, mulmod(mload(0x00), delta, r)) 902 | rhs := mulmod(rhs, addmod(addmod(calldataload(0x06a4), mload(0x00), r), gamma, r), r) 903 | mstore(0x00, mulmod(mload(0x00), delta, r)) 904 | rhs := mulmod(rhs, addmod(addmod(mload(INSTANCE_EVAL_MPTR), mload(0x00), r), gamma, r), r) 905 | mstore(0x00, mulmod(mload(0x00), delta, r)) 906 | let left_sub_right := addmod(lhs, sub(r, rhs), r) 907 | let eval := addmod(left_sub_right, sub(r, mulmod(left_sub_right, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r), r)), r) 908 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 909 | } 910 | { 911 | let gamma := mload(GAMMA_MPTR) 912 | let beta := mload(BETA_MPTR) 913 | let lhs := calldataload(0x0a84) 914 | let rhs := calldataload(0x0a64) 915 | lhs := mulmod(lhs, addmod(addmod(calldataload(0x06c4), mulmod(beta, calldataload(0x0984), r), r), gamma, r), r) 916 | rhs := mulmod(rhs, addmod(addmod(calldataload(0x06c4), mload(0x00), r), gamma, r), r) 917 | let left_sub_right := addmod(lhs, sub(r, rhs), r) 918 | let eval := addmod(left_sub_right, sub(r, mulmod(left_sub_right, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r), r)), r) 919 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 920 | } 921 | { 922 | let l_0 := mload(L_0_MPTR) 923 | let eval := mulmod(l_0, calldataload(0x0aa4), r) 924 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 925 | } 926 | { 927 | let l_last := mload(L_LAST_MPTR) 928 | let eval := mulmod(l_last, calldataload(0x0aa4), r) 929 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 930 | } 931 | { 932 | let theta := mload(THETA_MPTR) 933 | let beta := mload(BETA_MPTR) 934 | let table 935 | { 936 | let f_5 := calldataload(0x0724) 937 | let f_6 := calldataload(0x0744) 938 | table := f_5 939 | table := addmod(mulmod(table, theta, r), f_6, r) 940 | table := addmod(table, beta, r) 941 | } 942 | let input_0 943 | { 944 | let f_8 := calldataload(0x0784) 945 | let var0 := 0x1 946 | let var1 := mulmod(f_8, var0, r) 947 | let a_0 := calldataload(0x0524) 948 | let var2 := mulmod(var1, a_0, r) 949 | let var3 := sub(r, var1) 950 | let var4 := addmod(var0, var3, r) 951 | let var5 := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffe9c5 952 | let var6 := mulmod(var4, var5, r) 953 | let var7 := addmod(var2, var6, r) 954 | let a_1 := calldataload(0x0544) 955 | let var8 := mulmod(var1, a_1, r) 956 | let var9 := 0x0 957 | let var10 := mulmod(var4, var9, r) 958 | let var11 := addmod(var8, var10, r) 959 | input_0 := var7 960 | input_0 := addmod(mulmod(input_0, theta, r), var11, r) 961 | input_0 := addmod(input_0, beta, r) 962 | } 963 | let lhs 964 | let rhs 965 | rhs := table 966 | { 967 | let tmp := input_0 968 | rhs := addmod(rhs, sub(r, mulmod(calldataload(0x0ae4), tmp, r)), r) 969 | lhs := mulmod(mulmod(table, tmp, r), addmod(calldataload(0x0ac4), sub(r, calldataload(0x0aa4)), r), r) 970 | } 971 | let eval := mulmod(addmod(1, sub(r, addmod(mload(L_BLIND_MPTR), mload(L_LAST_MPTR), r)), r), addmod(lhs, sub(r, rhs), r), r) 972 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 973 | } 974 | { 975 | let l_0 := mload(L_0_MPTR) 976 | let eval := mulmod(l_0, calldataload(0x0b04), r) 977 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 978 | } 979 | { 980 | let l_last := mload(L_LAST_MPTR) 981 | let eval := mulmod(l_last, calldataload(0x0b04), r) 982 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 983 | } 984 | { 985 | let theta := mload(THETA_MPTR) 986 | let beta := mload(BETA_MPTR) 987 | let table 988 | { 989 | let f_5 := calldataload(0x0724) 990 | let f_7 := calldataload(0x0764) 991 | table := f_5 992 | table := addmod(mulmod(table, theta, r), f_7, r) 993 | table := addmod(table, beta, r) 994 | } 995 | let input_0 996 | { 997 | let f_9 := calldataload(0x07a4) 998 | let var0 := 0x1 999 | let var1 := mulmod(f_9, var0, r) 1000 | let a_0 := calldataload(0x0524) 1001 | let var2 := mulmod(var1, a_0, r) 1002 | let var3 := sub(r, var1) 1003 | let var4 := addmod(var0, var3, r) 1004 | let var5 := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffe9c5 1005 | let var6 := mulmod(var4, var5, r) 1006 | let var7 := addmod(var2, var6, r) 1007 | let a_1 := calldataload(0x0544) 1008 | let var8 := mulmod(var1, a_1, r) 1009 | let var9 := 0x0 1010 | let var10 := mulmod(var4, var9, r) 1011 | let var11 := addmod(var8, var10, r) 1012 | input_0 := var7 1013 | input_0 := addmod(mulmod(input_0, theta, r), var11, r) 1014 | input_0 := addmod(input_0, beta, r) 1015 | } 1016 | let lhs 1017 | let rhs 1018 | rhs := table 1019 | { 1020 | let tmp := input_0 1021 | rhs := addmod(rhs, sub(r, mulmod(calldataload(0x0b44), tmp, r)), r) 1022 | lhs := mulmod(mulmod(table, tmp, r), addmod(calldataload(0x0b24), sub(r, calldataload(0x0b04)), r), r) 1023 | } 1024 | let eval := mulmod(addmod(1, sub(r, addmod(mload(L_BLIND_MPTR), mload(L_LAST_MPTR), r)), r), addmod(lhs, sub(r, rhs), r), r) 1025 | quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) 1026 | } 1027 | 1028 | pop(y) 1029 | pop(delta) 1030 | 1031 | let quotient_eval := mulmod(quotient_eval_numer, mload(X_N_MINUS_1_INV_MPTR), r) 1032 | mstore(QUOTIENT_EVAL_MPTR, quotient_eval) 1033 | } 1034 | 1035 | // Compute quotient commitment 1036 | { 1037 | mstore(0x00, calldataload(LAST_QUOTIENT_X_CPTR)) 1038 | mstore(0x20, calldataload(add(LAST_QUOTIENT_X_CPTR, 0x20))) 1039 | let x_n := mload(X_N_MPTR) 1040 | for 1041 | { 1042 | let cptr := sub(LAST_QUOTIENT_X_CPTR, 0x40) 1043 | let cptr_end := sub(FIRST_QUOTIENT_X_CPTR, 0x40) 1044 | } 1045 | lt(cptr_end, cptr) 1046 | {} 1047 | { 1048 | success := ec_mul_acc(success, x_n) 1049 | success := ec_add_acc(success, calldataload(cptr), calldataload(add(cptr, 0x20))) 1050 | cptr := sub(cptr, 0x40) 1051 | } 1052 | mstore(QUOTIENT_X_MPTR, mload(0x00)) 1053 | mstore(QUOTIENT_Y_MPTR, mload(0x20)) 1054 | } 1055 | 1056 | // Compute pairing lhs and rhs 1057 | { 1058 | { 1059 | let x := mload(X_MPTR) 1060 | let omega := mload(OMEGA_MPTR) 1061 | let omega_inv := mload(OMEGA_INV_MPTR) 1062 | let x_pow_of_omega := mulmod(x, omega, r) 1063 | mstore(0x0420, x_pow_of_omega) 1064 | mstore(0x0400, x) 1065 | x_pow_of_omega := mulmod(x, omega_inv, r) 1066 | mstore(0x03e0, x_pow_of_omega) 1067 | x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) 1068 | x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) 1069 | x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) 1070 | x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) 1071 | x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) 1072 | mstore(0x03c0, x_pow_of_omega) 1073 | } 1074 | { 1075 | let mu := mload(MU_MPTR) 1076 | for 1077 | { 1078 | let mptr := 0x0440 1079 | let mptr_end := 0x04c0 1080 | let point_mptr := 0x03c0 1081 | } 1082 | lt(mptr, mptr_end) 1083 | { 1084 | mptr := add(mptr, 0x20) 1085 | point_mptr := add(point_mptr, 0x20) 1086 | } 1087 | { 1088 | mstore(mptr, addmod(mu, sub(r, mload(point_mptr)), r)) 1089 | } 1090 | let s 1091 | s := mload(0x0480) 1092 | mstore(0x04c0, s) 1093 | let diff 1094 | diff := mload(0x0440) 1095 | diff := mulmod(diff, mload(0x0460), r) 1096 | diff := mulmod(diff, mload(0x04a0), r) 1097 | mstore(0x04e0, diff) 1098 | mstore(0x00, diff) 1099 | diff := mload(0x0440) 1100 | diff := mulmod(diff, mload(0x04a0), r) 1101 | mstore(0x0500, diff) 1102 | diff := mload(0x0440) 1103 | mstore(0x0520, diff) 1104 | diff := mload(0x0460) 1105 | mstore(0x0540, diff) 1106 | diff := mload(0x0440) 1107 | diff := mulmod(diff, mload(0x0460), r) 1108 | mstore(0x0560, diff) 1109 | } 1110 | { 1111 | let point_2 := mload(0x0400) 1112 | let coeff 1113 | coeff := 1 1114 | coeff := mulmod(coeff, mload(0x0480), r) 1115 | mstore(0x20, coeff) 1116 | } 1117 | { 1118 | let point_1 := mload(0x03e0) 1119 | let point_2 := mload(0x0400) 1120 | let coeff 1121 | coeff := addmod(point_1, sub(r, point_2), r) 1122 | coeff := mulmod(coeff, mload(0x0460), r) 1123 | mstore(0x40, coeff) 1124 | coeff := addmod(point_2, sub(r, point_1), r) 1125 | coeff := mulmod(coeff, mload(0x0480), r) 1126 | mstore(0x60, coeff) 1127 | } 1128 | { 1129 | let point_1 := mload(0x03e0) 1130 | let point_2 := mload(0x0400) 1131 | let point_3 := mload(0x0420) 1132 | let coeff 1133 | coeff := addmod(point_1, sub(r, point_2), r) 1134 | coeff := mulmod(coeff, addmod(point_1, sub(r, point_3), r), r) 1135 | coeff := mulmod(coeff, mload(0x0460), r) 1136 | mstore(0x80, coeff) 1137 | coeff := addmod(point_2, sub(r, point_1), r) 1138 | coeff := mulmod(coeff, addmod(point_2, sub(r, point_3), r), r) 1139 | coeff := mulmod(coeff, mload(0x0480), r) 1140 | mstore(0xa0, coeff) 1141 | coeff := addmod(point_3, sub(r, point_1), r) 1142 | coeff := mulmod(coeff, addmod(point_3, sub(r, point_2), r), r) 1143 | coeff := mulmod(coeff, mload(0x04a0), r) 1144 | mstore(0xc0, coeff) 1145 | } 1146 | { 1147 | let point_0 := mload(0x03c0) 1148 | let point_2 := mload(0x0400) 1149 | let point_3 := mload(0x0420) 1150 | let coeff 1151 | coeff := addmod(point_0, sub(r, point_2), r) 1152 | coeff := mulmod(coeff, addmod(point_0, sub(r, point_3), r), r) 1153 | coeff := mulmod(coeff, mload(0x0440), r) 1154 | mstore(0xe0, coeff) 1155 | coeff := addmod(point_2, sub(r, point_0), r) 1156 | coeff := mulmod(coeff, addmod(point_2, sub(r, point_3), r), r) 1157 | coeff := mulmod(coeff, mload(0x0480), r) 1158 | mstore(0x0100, coeff) 1159 | coeff := addmod(point_3, sub(r, point_0), r) 1160 | coeff := mulmod(coeff, addmod(point_3, sub(r, point_2), r), r) 1161 | coeff := mulmod(coeff, mload(0x04a0), r) 1162 | mstore(0x0120, coeff) 1163 | } 1164 | { 1165 | let point_2 := mload(0x0400) 1166 | let point_3 := mload(0x0420) 1167 | let coeff 1168 | coeff := addmod(point_2, sub(r, point_3), r) 1169 | coeff := mulmod(coeff, mload(0x0480), r) 1170 | mstore(0x0140, coeff) 1171 | coeff := addmod(point_3, sub(r, point_2), r) 1172 | coeff := mulmod(coeff, mload(0x04a0), r) 1173 | mstore(0x0160, coeff) 1174 | } 1175 | { 1176 | success := batch_invert(success, 0, 0x0180, r) 1177 | let diff_0_inv := mload(0x00) 1178 | mstore(0x04e0, diff_0_inv) 1179 | for 1180 | { 1181 | let mptr := 0x0500 1182 | let mptr_end := 0x0580 1183 | } 1184 | lt(mptr, mptr_end) 1185 | { mptr := add(mptr, 0x20) } 1186 | { 1187 | mstore(mptr, mulmod(mload(mptr), diff_0_inv, r)) 1188 | } 1189 | } 1190 | { 1191 | let coeff := mload(0x20) 1192 | let zeta := mload(ZETA_MPTR) 1193 | let r_eval := 0 1194 | r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0864), r), r) 1195 | r_eval := mulmod(r_eval, zeta, r) 1196 | r_eval := addmod(r_eval, mulmod(coeff, mload(QUOTIENT_EVAL_MPTR), r), r) 1197 | for 1198 | { 1199 | let mptr := 0x0984 1200 | let mptr_end := 0x0864 1201 | } 1202 | lt(mptr_end, mptr) 1203 | { mptr := sub(mptr, 0x20) } 1204 | { 1205 | r_eval := addmod(mulmod(r_eval, zeta, r), mulmod(coeff, calldataload(mptr), r), r) 1206 | } 1207 | for 1208 | { 1209 | let mptr := 0x0844 1210 | let mptr_end := 0x0664 1211 | } 1212 | lt(mptr_end, mptr) 1213 | { mptr := sub(mptr, 0x20) } 1214 | { 1215 | r_eval := addmod(mulmod(r_eval, zeta, r), mulmod(coeff, calldataload(mptr), r), r) 1216 | } 1217 | r_eval := mulmod(r_eval, zeta, r) 1218 | r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0b44), r), r) 1219 | r_eval := mulmod(r_eval, zeta, r) 1220 | r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0ae4), r), r) 1221 | r_eval := mulmod(r_eval, zeta, r) 1222 | r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0604), r), r) 1223 | r_eval := mulmod(r_eval, zeta, r) 1224 | r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0544), r), r) 1225 | r_eval := mulmod(r_eval, zeta, r) 1226 | r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0524), r), r) 1227 | mstore(0x0580, r_eval) 1228 | } 1229 | { 1230 | let zeta := mload(ZETA_MPTR) 1231 | let r_eval := 0 1232 | r_eval := addmod(r_eval, mulmod(mload(0x40), calldataload(0x0664), r), r) 1233 | r_eval := addmod(r_eval, mulmod(mload(0x60), calldataload(0x0564), r), r) 1234 | r_eval := mulmod(r_eval, mload(0x0500), r) 1235 | mstore(0x05a0, r_eval) 1236 | } 1237 | { 1238 | let zeta := mload(ZETA_MPTR) 1239 | let r_eval := 0 1240 | r_eval := addmod(r_eval, mulmod(mload(0x80), calldataload(0x0624), r), r) 1241 | r_eval := addmod(r_eval, mulmod(mload(0xa0), calldataload(0x05a4), r), r) 1242 | r_eval := addmod(r_eval, mulmod(mload(0xc0), calldataload(0x05e4), r), r) 1243 | r_eval := mulmod(r_eval, zeta, r) 1244 | r_eval := addmod(r_eval, mulmod(mload(0x80), calldataload(0x0644), r), r) 1245 | r_eval := addmod(r_eval, mulmod(mload(0xa0), calldataload(0x0584), r), r) 1246 | r_eval := addmod(r_eval, mulmod(mload(0xc0), calldataload(0x05c4), r), r) 1247 | r_eval := mulmod(r_eval, mload(0x0520), r) 1248 | mstore(0x05c0, r_eval) 1249 | } 1250 | { 1251 | let zeta := mload(ZETA_MPTR) 1252 | let r_eval := 0 1253 | r_eval := addmod(r_eval, mulmod(mload(0xe0), calldataload(0x0a44), r), r) 1254 | r_eval := addmod(r_eval, mulmod(mload(0x0100), calldataload(0x0a04), r), r) 1255 | r_eval := addmod(r_eval, mulmod(mload(0x0120), calldataload(0x0a24), r), r) 1256 | r_eval := mulmod(r_eval, zeta, r) 1257 | r_eval := addmod(r_eval, mulmod(mload(0xe0), calldataload(0x09e4), r), r) 1258 | r_eval := addmod(r_eval, mulmod(mload(0x0100), calldataload(0x09a4), r), r) 1259 | r_eval := addmod(r_eval, mulmod(mload(0x0120), calldataload(0x09c4), r), r) 1260 | r_eval := mulmod(r_eval, mload(0x0540), r) 1261 | mstore(0x05e0, r_eval) 1262 | } 1263 | { 1264 | let zeta := mload(ZETA_MPTR) 1265 | let r_eval := 0 1266 | r_eval := addmod(r_eval, mulmod(mload(0x0140), calldataload(0x0b04), r), r) 1267 | r_eval := addmod(r_eval, mulmod(mload(0x0160), calldataload(0x0b24), r), r) 1268 | r_eval := mulmod(r_eval, zeta, r) 1269 | r_eval := addmod(r_eval, mulmod(mload(0x0140), calldataload(0x0aa4), r), r) 1270 | r_eval := addmod(r_eval, mulmod(mload(0x0160), calldataload(0x0ac4), r), r) 1271 | r_eval := mulmod(r_eval, zeta, r) 1272 | r_eval := addmod(r_eval, mulmod(mload(0x0140), calldataload(0x0a64), r), r) 1273 | r_eval := addmod(r_eval, mulmod(mload(0x0160), calldataload(0x0a84), r), r) 1274 | r_eval := mulmod(r_eval, mload(0x0560), r) 1275 | mstore(0x0600, r_eval) 1276 | } 1277 | { 1278 | let sum := mload(0x20) 1279 | mstore(0x0620, sum) 1280 | } 1281 | { 1282 | let sum := mload(0x40) 1283 | sum := addmod(sum, mload(0x60), r) 1284 | mstore(0x0640, sum) 1285 | } 1286 | { 1287 | let sum := mload(0x80) 1288 | sum := addmod(sum, mload(0xa0), r) 1289 | sum := addmod(sum, mload(0xc0), r) 1290 | mstore(0x0660, sum) 1291 | } 1292 | { 1293 | let sum := mload(0xe0) 1294 | sum := addmod(sum, mload(0x0100), r) 1295 | sum := addmod(sum, mload(0x0120), r) 1296 | mstore(0x0680, sum) 1297 | } 1298 | { 1299 | let sum := mload(0x0140) 1300 | sum := addmod(sum, mload(0x0160), r) 1301 | mstore(0x06a0, sum) 1302 | } 1303 | { 1304 | for 1305 | { 1306 | let mptr := 0x00 1307 | let mptr_end := 0xa0 1308 | let sum_mptr := 0x0620 1309 | } 1310 | lt(mptr, mptr_end) 1311 | { 1312 | mptr := add(mptr, 0x20) 1313 | sum_mptr := add(sum_mptr, 0x20) 1314 | } 1315 | { 1316 | mstore(mptr, mload(sum_mptr)) 1317 | } 1318 | success := batch_invert(success, 0, 0xa0, r) 1319 | let r_eval := mulmod(mload(0x80), mload(0x0600), r) 1320 | for 1321 | { 1322 | let sum_inv_mptr := 0x60 1323 | let sum_inv_mptr_end := 0xa0 1324 | let r_eval_mptr := 0x05e0 1325 | } 1326 | lt(sum_inv_mptr, sum_inv_mptr_end) 1327 | { 1328 | sum_inv_mptr := sub(sum_inv_mptr, 0x20) 1329 | r_eval_mptr := sub(r_eval_mptr, 0x20) 1330 | } 1331 | { 1332 | r_eval := mulmod(r_eval, mload(NU_MPTR), r) 1333 | r_eval := addmod(r_eval, mulmod(mload(sum_inv_mptr), mload(r_eval_mptr), r), r) 1334 | } 1335 | mstore(R_EVAL_MPTR, r_eval) 1336 | } 1337 | { 1338 | let nu := mload(NU_MPTR) 1339 | mstore(0x00, calldataload(0x03a4)) 1340 | mstore(0x20, calldataload(0x03c4)) 1341 | success := ec_mul_acc(success, mload(ZETA_MPTR)) 1342 | success := ec_add_acc(success, mload(QUOTIENT_X_MPTR), mload(QUOTIENT_Y_MPTR)) 1343 | for 1344 | { 1345 | let mptr := 0x0f20 1346 | let mptr_end := 0x0a60 1347 | } 1348 | lt(mptr_end, mptr) 1349 | { mptr := sub(mptr, 0x40) } 1350 | { 1351 | success := ec_mul_acc(success, mload(ZETA_MPTR)) 1352 | success := ec_add_acc(success, mload(mptr), mload(add(mptr, 0x20))) 1353 | } 1354 | success := ec_mul_acc(success, mload(ZETA_MPTR)) 1355 | success := ec_add_acc(success, mload(0x09e0), mload(0x0a00)) 1356 | success := ec_mul_acc(success, mload(ZETA_MPTR)) 1357 | success := ec_add_acc(success, mload(0x09a0), mload(0x09c0)) 1358 | success := ec_mul_acc(success, mload(ZETA_MPTR)) 1359 | success := ec_add_acc(success, mload(0x0a60), mload(0x0a80)) 1360 | success := ec_mul_acc(success, mload(ZETA_MPTR)) 1361 | success := ec_add_acc(success, mload(0x0a20), mload(0x0a40)) 1362 | success := ec_mul_acc(success, mload(ZETA_MPTR)) 1363 | success := ec_add_acc(success, mload(0x0960), mload(0x0980)) 1364 | for 1365 | { 1366 | let mptr := 0x0224 1367 | let mptr_end := 0x0164 1368 | } 1369 | lt(mptr_end, mptr) 1370 | { mptr := sub(mptr, 0x40) } 1371 | { 1372 | success := ec_mul_acc(success, mload(ZETA_MPTR)) 1373 | success := ec_add_acc(success, calldataload(mptr), calldataload(add(mptr, 0x20))) 1374 | } 1375 | success := ec_mul_acc(success, mload(ZETA_MPTR)) 1376 | success := ec_add_acc(success, calldataload(0xa4), calldataload(0xc4)) 1377 | success := ec_mul_acc(success, mload(ZETA_MPTR)) 1378 | success := ec_add_acc(success, calldataload(0x64), calldataload(0x84)) 1379 | mstore(0x80, calldataload(0xe4)) 1380 | mstore(0xa0, calldataload(0x0104)) 1381 | success := ec_mul_tmp(success, mulmod(nu, mload(0x0500), r)) 1382 | success := ec_add_acc(success, mload(0x80), mload(0xa0)) 1383 | nu := mulmod(nu, mload(NU_MPTR), r) 1384 | mstore(0x80, calldataload(0x0164)) 1385 | mstore(0xa0, calldataload(0x0184)) 1386 | success := ec_mul_tmp(success, mload(ZETA_MPTR)) 1387 | success := ec_add_tmp(success, calldataload(0x0124), calldataload(0x0144)) 1388 | success := ec_mul_tmp(success, mulmod(nu, mload(0x0520), r)) 1389 | success := ec_add_acc(success, mload(0x80), mload(0xa0)) 1390 | nu := mulmod(nu, mload(NU_MPTR), r) 1391 | mstore(0x80, calldataload(0x02a4)) 1392 | mstore(0xa0, calldataload(0x02c4)) 1393 | success := ec_mul_tmp(success, mload(ZETA_MPTR)) 1394 | success := ec_add_tmp(success, calldataload(0x0264), calldataload(0x0284)) 1395 | success := ec_mul_tmp(success, mulmod(nu, mload(0x0540), r)) 1396 | success := ec_add_acc(success, mload(0x80), mload(0xa0)) 1397 | nu := mulmod(nu, mload(NU_MPTR), r) 1398 | mstore(0x80, calldataload(0x0364)) 1399 | mstore(0xa0, calldataload(0x0384)) 1400 | success := ec_mul_tmp(success, mload(ZETA_MPTR)) 1401 | success := ec_add_tmp(success, calldataload(0x0324), calldataload(0x0344)) 1402 | success := ec_mul_tmp(success, mload(ZETA_MPTR)) 1403 | success := ec_add_tmp(success, calldataload(0x02e4), calldataload(0x0304)) 1404 | success := ec_mul_tmp(success, mulmod(nu, mload(0x0560), r)) 1405 | success := ec_add_acc(success, mload(0x80), mload(0xa0)) 1406 | mstore(0x80, mload(G1_X_MPTR)) 1407 | mstore(0xa0, mload(G1_Y_MPTR)) 1408 | success := ec_mul_tmp(success, sub(r, mload(R_EVAL_MPTR))) 1409 | success := ec_add_acc(success, mload(0x80), mload(0xa0)) 1410 | mstore(0x80, calldataload(0x0b64)) 1411 | mstore(0xa0, calldataload(0x0b84)) 1412 | success := ec_mul_tmp(success, sub(r, mload(0x04c0))) 1413 | success := ec_add_acc(success, mload(0x80), mload(0xa0)) 1414 | mstore(0x80, calldataload(0x0ba4)) 1415 | mstore(0xa0, calldataload(0x0bc4)) 1416 | success := ec_mul_tmp(success, mload(MU_MPTR)) 1417 | success := ec_add_acc(success, mload(0x80), mload(0xa0)) 1418 | mstore(PAIRING_LHS_X_MPTR, mload(0x00)) 1419 | mstore(PAIRING_LHS_Y_MPTR, mload(0x20)) 1420 | mstore(PAIRING_RHS_X_MPTR, calldataload(0x0ba4)) 1421 | mstore(PAIRING_RHS_Y_MPTR, calldataload(0x0bc4)) 1422 | } 1423 | } 1424 | 1425 | // Random linear combine with accumulator 1426 | if mload(HAS_ACCUMULATOR_MPTR) { 1427 | mstore(0x00, mload(ACC_LHS_X_MPTR)) 1428 | mstore(0x20, mload(ACC_LHS_Y_MPTR)) 1429 | mstore(0x40, mload(ACC_RHS_X_MPTR)) 1430 | mstore(0x60, mload(ACC_RHS_Y_MPTR)) 1431 | mstore(0x80, mload(PAIRING_LHS_X_MPTR)) 1432 | mstore(0xa0, mload(PAIRING_LHS_Y_MPTR)) 1433 | mstore(0xc0, mload(PAIRING_RHS_X_MPTR)) 1434 | mstore(0xe0, mload(PAIRING_RHS_Y_MPTR)) 1435 | let challenge := mod(keccak256(0x00, 0x100), r) 1436 | 1437 | // [pairing_lhs] += challenge * [acc_lhs] 1438 | success := ec_mul_acc(success, challenge) 1439 | success := ec_add_acc(success, mload(PAIRING_LHS_X_MPTR), mload(PAIRING_LHS_Y_MPTR)) 1440 | mstore(PAIRING_LHS_X_MPTR, mload(0x00)) 1441 | mstore(PAIRING_LHS_Y_MPTR, mload(0x20)) 1442 | 1443 | // [pairing_rhs] += challenge * [acc_rhs] 1444 | mstore(0x00, mload(ACC_RHS_X_MPTR)) 1445 | mstore(0x20, mload(ACC_RHS_Y_MPTR)) 1446 | success := ec_mul_acc(success, challenge) 1447 | success := ec_add_acc(success, mload(PAIRING_RHS_X_MPTR), mload(PAIRING_RHS_Y_MPTR)) 1448 | mstore(PAIRING_RHS_X_MPTR, mload(0x00)) 1449 | mstore(PAIRING_RHS_Y_MPTR, mload(0x20)) 1450 | } 1451 | 1452 | // Perform pairing 1453 | success := ec_pairing( 1454 | success, 1455 | mload(PAIRING_LHS_X_MPTR), 1456 | mload(PAIRING_LHS_Y_MPTR), 1457 | mload(PAIRING_RHS_X_MPTR), 1458 | mload(PAIRING_RHS_Y_MPTR) 1459 | ) 1460 | 1461 | // Revert if anything fails 1462 | if iszero(success) { 1463 | revert(0x00, 0x00) 1464 | } 1465 | 1466 | // Return 1 as result if everything succeeds 1467 | mstore(0x00, 1) 1468 | return(0x00, 0x20) 1469 | } 1470 | } 1471 | } 1472 | --------------------------------------------------------------------------------