diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34a4a52..5cc599e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,12 @@ name: CI on: push: + branches: + - main pull_request: + branches: + - main + types: [ready_for_review, opened, reopened, synchronize] workflow_dispatch: env: @@ -39,5 +44,5 @@ jobs: - name: Run Forge tests run: | - forge test -vvv + forge test -vvvvv id: test diff --git a/.gitmodules b/.gitmodules index 888d42d..690924b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..6d14caa --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +python 3.12.3 +solidity 0.8.28 diff --git a/README.md b/README.md index 4bc5896..f4d115a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ ## `asdf` - 3rd Party Tools Version Manager +**Important:** I have encountered installation problems with `v0.16.x`. So, I have +stayed to `v0.15.x`. + We are suggesting [asdf](https://asdf-vm.com/) as the version manager for 3rd party tools. This is an awesome tool that runs on many different platforms and uses one single file to manage all tools versions. The file is `.tool-versions`. @@ -45,3 +48,70 @@ Then I restarted my shell and used the command: ``` foundryup --install stable ``` + +## Deployment of Smart Contract(s) + +Start a local chain: + +```bash +$ anvil +... +Listening 127.0.0.1:8545 + +``` + +### Using `forge create` (not preferred) + +Then you can run the following commands to deploy to the local network: + +Note: For owner I use the first owner that `anvil` provides me with. Same for private key. + +#### Step 1 + +Deploy the ERCMock contract. Note down its address: + +```bash +$ ./fc_deploy_ercmock.sh 'http://127.0.0.1:8545' '0xac..a...private key....80' +``` + +#### Step 2 + +Deploy the Collateralized Loan contract: + +```bash +$ ./fc_deploy_collateralized_loan.sh 'http://127.0.0.1:8545' '0xac..a...private key....80' '0xf39Fd...an owner...ffFb92266' '0x5Fb...ERC20Mock Address...0aa3' 20 5 +``` + +### Using `forge script` + +```bash +$ ./fs_deploy_collateralized_loan.sh 'http://127.0.0.1:8545' +``` + +### Request Some Loan + +After having done the deployment, the following can be used to execute a request for loan: + +```bash +$ ./fs_request_loan.sh 'http://127.0.0.1:8545' +``` + +This will send 30 Ether to `0x70997970C51812dc3A010C7d01b50e0d17dc79C8`. Hence, this account will then have +balance: `10029.999872713728857791`, which you can check with: + +```bash +cast balance --rpc-url 'http://127.0.0.1:8545' 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --ether +``` + +And the contract will have 30 ether less: `70.000000000000000000`. You can check it with: + +```bash +$ cast balance --rpc-url 'http://127.0.0.1:8545' 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 --ether +``` + +And you can also run the following script to see what is the smart contract in `ERC20Mock` +tokens. + +```bash +$ cast balance --rpc-url 'http://127.0.0.1:8545' --ether --erc20 0x5FbDB2315678afecb367f032d93F642f64180aa3 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +``` diff --git a/fc_deploy_collateralized_loan.sh b/fc_deploy_collateralized_loan.sh new file mode 100755 index 0000000..7b114f2 --- /dev/null +++ b/fc_deploy_collateralized_loan.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euox pipefail +shopt -s globstar + +RPC_URL=$1 +PRIVATE_KEY=$2 +INITIAL_OWNER=$3 +ERC20_TOKEN=$4 +INTEREST_RATE=$5 +MIN_COLLATERALIZATION_RATIO=$6 + +forge create --rpc-url "${RPC_URL}" --private-key "${PRIVATE_KEY}" src/CollateralizedLoan.sol:CollateralizedLoan \ + --broadcast \ + --constructor-args "${INITIAL_OWNER}" "${ERC20_TOKEN}" ${INTEREST_RATE} ${MIN_COLLATERALIZATION_RATIO} \ diff --git a/fc_deploy_ercmock.sh b/fc_deploy_ercmock.sh new file mode 100755 index 0000000..4aa9068 --- /dev/null +++ b/fc_deploy_ercmock.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -euox pipefail +shopt -s globstar + +RPC_URL=$1 +PRIVATE_KEY=$2 + +forge create --rpc-url "${RPC_URL}" --private-key "${PRIVATE_KEY}" lib/openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol:ERC20Mock \ + --broadcast diff --git a/foundry.toml b/foundry.toml index 25b918f..24ae9a3 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,5 +2,9 @@ src = "src" out = "out" libs = ["lib"] +optimizer = true +optimizer_runs = 2000 +via_ir = true +solc_version = "0.8.28" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/fs_deploy_collateralized_loan.sh b/fs_deploy_collateralized_loan.sh new file mode 100755 index 0000000..58812b1 --- /dev/null +++ b/fs_deploy_collateralized_loan.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -euox pipefail +shopt -s globstar + +RPC_URL=$1 +KEYSTORE=$2 + +forge script script/CollateralizedLoan.s.sol --broadcast \ + --rpc-url "${RPC_URL}" --keystore "${HOME}/.foundry/keystores/${KEYSTORE}" diff --git a/fs_request_loan.sh b/fs_request_loan.sh new file mode 100755 index 0000000..100c07f --- /dev/null +++ b/fs_request_loan.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -euox pipefail +shopt -s globstar + +RPC_URL=$1 +SENDER=$2 +KEYSTORE1=$3 +KEYSTORE2=$4 + +forge script script/ExampleRequestLoan.s.sol \ + --rpc-url "${RPC_URL}" --keystore $HOME/.foundry/keystores/${KEYSTORE1} \ + --keystore $HOME/.foundry/keystores/${KEYSTORE2} \ + --broadcast --sender ${SENDER} diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..acd4ff7 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit acd4ff74de833399287ed6b31b4debf6b2b35527 diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..918ed31 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,5 @@ +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ +forge-std/=lib/forge-std/src/ +halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/ +openzeppelin-contracts/=lib/openzeppelin-contracts/ diff --git a/script/CollateralizedLoan.s.sol b/script/CollateralizedLoan.s.sol new file mode 100644 index 0000000..40323c1 --- /dev/null +++ b/script/CollateralizedLoan.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; +import {Script, console} from "forge-std/Script.sol"; +import {CollateralizedLoan} from "../src/CollateralizedLoan.sol"; + +contract CollateralizedLoanScript is Script { + uint256 constant LOCAL_ANVIL_CHAIN_ID = 31337; + + function run() public { + if (localAnvilChainId()) { + vm.startBroadcast(); + + address initialOwner = msg.sender; + ERC20Mock collateralToken = new ERC20Mock(); + uint256 interestRate = 20; + uint256 minCollateralizationRatio = 5; + + new CollateralizedLoan{value: 100 ether}( + initialOwner, collateralToken, interestRate, minCollateralizationRatio + ); + + vm.stopBroadcast(); + } + } + + // ------------------ + // PRIVATE + // ------------------ + + function localAnvilChainId() private view returns (bool) { + return (block.chainid == LOCAL_ANVIL_CHAIN_ID); + } +} diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index cdc1fe9..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/script/ExampleRequestLoan.s.sol b/script/ExampleRequestLoan.s.sol new file mode 100644 index 0000000..2aeb976 --- /dev/null +++ b/script/ExampleRequestLoan.s.sol @@ -0,0 +1,31 @@ +// // SPDX-License-Identifier: UNLICENSED +// pragma solidity ^0.8.28; + +// import {Vm} from "lib/forge-std/src/Vm.sol"; + +// import {CollateralizedLoan} from "src/CollateralizedLoan.sol"; +// import {console} from "forge-std/Test.sol"; +// import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; + +// contract ExampleRequestLoan { +// Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + +// /// @notice REPL contract entry point +// function run() public { +// CollateralizedLoan cl = CollateralizedLoan(0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512); +// address owner = cl.owner(); +// console.log("owner", owner); +// ERC20Mock collateralToken = ERC20Mock(0x5FbDB2315678afecb367f032d93F642f64180aa3); +// address borrower = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; +// uint256 minimumCollateral = cl.minimumCollateralRequired(30 ether); + +// vm.startBroadcast(); +// collateralToken.mint(borrower, minimumCollateral); +// vm.stopBroadcast(); + +// vm.startBroadcast(borrower); +// collateralToken.approve(address(cl), minimumCollateral); +// cl.requestLoan(30 ether, minimumCollateral); +// vm.stopBroadcast(); +// } +// } diff --git a/src/CollateralCalculator.sol b/src/CollateralCalculator.sol new file mode 100644 index 0000000..823769a --- /dev/null +++ b/src/CollateralCalculator.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {ICollateralCalculator} from "./ICollateralCalculator.sol"; + +/** + * @title CollateralCalculator + * @dev Calculates required loan collateral based on credit score + * This contract is used to calculate the required collateral for a loan based on the borrower's credit score. + * The credit score is verified on-chain with another smart contract (to be provided) and passed to this contract to calculate the required collateral. + * The required collateral is calculated as a percentage of the loan amount based on the borrower's credit tier. + * + */ +contract CollateralCalculator is ICollateralCalculator { + mapping(address borrower => CreditTier creditTier) private s_borrowerCreditTiers; + + uint8[4] private s_creditTierCollateralPercentages = [ + 120, // 120 % + 100, // 100 % + 90, // 90 % + 80 // 80 % + ]; + + // ------------------ + // external + // ------------------ + + /* + * This should only be called by the Smart Contract that delivers the Loan, i.e. by the +CollateralizedLoan+ Smart Contract. + */ + function updateCreditScore(address _borrower, uint256 _creditScore, bool _proofValidated) external { + CreditTier newTier; + + if (!_proofValidated) { + newTier = CreditTier.UNKNOWN; + } else { + if (_creditScore < 400) { + // _creditScore < 400 + newTier = CreditTier.LOW; + } else if (_creditScore < 700) { + // 400 <= _creditScore < 700 + newTier = CreditTier.MEDIUM; + } else { + // > 0.7 + newTier = CreditTier.HIGH; + } + } + + s_borrowerCreditTiers[_borrower] = newTier; + + emit CreditScoreUpdated(_borrower, _creditScore, newTier); + } + + // ------------------ + // external view + // ------------------ + + function getBorrowerCreditTier(address _borrower) external view override returns (CreditTier) { + return s_borrowerCreditTiers[_borrower]; + } + + /** + * TODO: We will see how: This may be called only by the +CollateralizedLoan+ Smart Contract. + */ + function getCollateralRequirement(address _borrower, uint256 _loanAmount) + external + view + override + returns (CollateralRequirement memory) + { + // Get borrower credit tier + CreditTier tier = s_borrowerCreditTiers[_borrower]; + + // Get required collateral percentage for that tier + uint8 requiredPercentage = s_creditTierCollateralPercentages[uint256(tier)]; + + // Calculate required collateral amount + uint256 requiredAmount = calculateRequiredAmount(_loanAmount, requiredPercentage); + + return CollateralRequirement(requiredPercentage, requiredAmount, tier); + } + + // ------------------ + // internal + // ------------------ + function calculateRequiredAmount(uint256 _loanAmount, uint256 _requiredPercentage) + internal + pure + returns (uint256) + { + return (_loanAmount * _requiredPercentage) / 100; + } +} diff --git a/src/CollateralizedLoan.sol b/src/CollateralizedLoan.sol new file mode 100644 index 0000000..9525b41 --- /dev/null +++ b/src/CollateralizedLoan.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +// The borrowed asset is going to be Ether. +// The collateral asset is going to be an ERC-20 token. +// The lender is the Smart Contract itself. +// +contract CollateralizedLoan is Ownable { + struct LoanInfo { + address borrower; + uint256 borrowedAmount; + uint256 collateralAmount; + uint256 requestedAt; + bool paid; + } + // uint256 amount; + // uint256 collateral; + // uint256 interest; + // uint256 duration; + // uint256 start; + // uint256 end; + // address borrower; + // address lender; + + IERC20 s_collateralToken; + uint256 s_interestRate; // percentage, e.g. 20%. It can be more than 100 + uint256 s_minCollateralizationRatio; // percentage. It can be more than 100 + + mapping(address => LoanInfo) s_loans; + + // -------------------------------------- + // Events + // -------------------------------------- + event LoanGranted(address indexed borrower, uint256 borrowedAmount, uint256 collateralAmount); + + event LoanRepaid(address borrower, uint256 repaidAmount); + //----------------------------------------------------------------------- + + // -------------------------------------- + // Errors + // -------------------------------------- + error NotEnoughCollateralProvidedForBorrowedAmountError( + uint256 borrowedAmountRequested, + uint256 minCollateralizationRatio, + uint256 minimumCollateralRequired, + uint256 collateralProvided + ); + + error LenderDoesNotHaveEnoughEtherError(uint256 amountRequested, uint256 lenderBalance); + + error BorrowerDoesNotHaveEnoughCollateralError(address borrower, uint256 amountRequired, uint256 borrowerBalance); + + error BorrowerHasUnpaidLoanError(address borrower, uint256 borrowedAmount, uint256 requestedAt); + + error SendingEtherFailedError(address sender, address recipient, uint256 value); + // ---------------------------------------------------- + + constructor( + address _initialOwner, + IERC20 _collateralToken, + uint256 _interestRate, + uint256 _minCollateralizationRatio + ) payable Ownable(_initialOwner) { + s_collateralToken = IERC20(_collateralToken); + s_interestRate = _interestRate; + s_minCollateralizationRatio = _minCollateralizationRatio; + } + + // ---------------------------------------------------------- + // Public View Functions + // ---------------------------------------------------------- + function collateralToken() public view returns (IERC20) { + return s_collateralToken; + } + + function interestRate() public view returns (uint256) { + return s_interestRate; + } + + function minimumCollateralRequired(uint256 _borrowedAmount) public view returns (uint256) { + uint256 l_extraAmountToLiquidate = (_borrowedAmount * s_minCollateralizationRatio) / 100; + + return (_borrowedAmount + l_extraAmountToLiquidate); + } + + function myLoanInfo() public view returns (LoanInfo memory) { + return s_loans[msg.sender]; + } + + // ---------------------------------------------------------- + // External Transactions + // ---------------------------------------------------------- + + // The caller is requesting +_borrowedAmount+ of Ether. + // To get the loan, they send +_collateralAmount+ of the + // +s_collateralToken+. + // There is a minimum amount of collateral that they should + // send. It is equal to the +_borrowedAmount+ increased by + // its +s_minCollateralizationRatio+. + // + function requestLoan(uint256 _borrowedAmount, uint256 _collateralAmount) external { + LoanInfo storage loanInfo = s_loans[msg.sender]; + if (loanInfo.borrowedAmount > 0 && !loanInfo.paid) { + revert BorrowerHasUnpaidLoanError({ + borrower: msg.sender, + borrowedAmount: loanInfo.borrowedAmount, + requestedAt: loanInfo.requestedAt + }); + } + + uint256 l_minimumCollateralRequired = minimumCollateralRequired(_borrowedAmount); + + if (_collateralAmount < l_minimumCollateralRequired) { + revert NotEnoughCollateralProvidedForBorrowedAmountError({ + borrowedAmountRequested: _borrowedAmount, + minCollateralizationRatio: s_minCollateralizationRatio, + minimumCollateralRequired: l_minimumCollateralRequired, + collateralProvided: _collateralAmount + }); + } + + if (address(this).balance < _borrowedAmount) { + revert LenderDoesNotHaveEnoughEtherError({ + amountRequested: _borrowedAmount, + lenderBalance: address(this).balance + }); + } + + uint256 borrowerCollateralBalance = s_collateralToken.balanceOf(msg.sender); + + if (borrowerCollateralBalance < _collateralAmount) { + revert BorrowerDoesNotHaveEnoughCollateralError({ + borrower: msg.sender, + amountRequired: _collateralAmount, + borrowerBalance: borrowerCollateralBalance + }); + } + + // we take from the borrower and we move Collateral to the contract + // The +msg.sender+ (the borrower) needs to have approved the +CollateralizedLoan+ to + // get money/collateralToken from +msg.sender+ and deposit to it itself + // + s_collateralToken.transferFrom(msg.sender, address(this), _collateralAmount); + + _sendEthersTo(payable(msg.sender), _borrowedAmount); + + s_loans[msg.sender] = LoanInfo({ + borrower: msg.sender, + borrowedAmount: _borrowedAmount, + collateralAmount: _collateralAmount, + requestedAt: block.timestamp, + paid: false + }); + + emit LoanGranted(msg.sender, _borrowedAmount, _collateralAmount); + } + + // ------- + // private + // ------- + + function _sendEthersTo(address payable _recipient, uint256 _amount) private { + (bool sent,) = _recipient.call{value: _amount}(""); + if (!sent) { + revert SendingEtherFailedError({sender: address(this), recipient: _recipient, value: _amount}); + } + } +} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/CreditVerifier.sol b/src/CreditVerifier.sol new file mode 100644 index 0000000..8d5646f --- /dev/null +++ b/src/CreditVerifier.sol @@ -0,0 +1,1533 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.28; + +import {IZKCreditVerifier} from "./IZKCreditVerifier.sol"; + +contract CreditVerifier is IZKCreditVerifier { + uint256 internal constant DELTA = 4131629893567559867359510883348571134090853742863529169391034518566172092834; + uint256 internal constant R = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + uint256 internal constant PROOF_LEN_CPTR = 0x6014f51944; + uint256 internal constant PROOF_CPTR = 0x64; + uint256 internal constant NUM_INSTANCE_CPTR = 0x0ea4; + uint256 internal constant INSTANCE_CPTR = 0x0ec4; + + uint256 internal constant FIRST_QUOTIENT_X_CPTR = 0x05e4; + uint256 internal constant LAST_QUOTIENT_X_CPTR = 0x06a4; + + uint256 internal constant VK_MPTR = 0x05a0; + uint256 internal constant VK_DIGEST_MPTR = 0x05a0; + uint256 internal constant NUM_INSTANCES_MPTR = 0x05c0; + uint256 internal constant K_MPTR = 0x05e0; + uint256 internal constant N_INV_MPTR = 0x0600; + uint256 internal constant OMEGA_MPTR = 0x0620; + uint256 internal constant OMEGA_INV_MPTR = 0x0640; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x0660; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x0680; + uint256 internal constant ACC_OFFSET_MPTR = 0x06a0; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x06c0; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x06e0; + uint256 internal constant G1_X_MPTR = 0x0700; + uint256 internal constant G1_Y_MPTR = 0x0720; + uint256 internal constant G2_X_1_MPTR = 0x0740; + uint256 internal constant G2_X_2_MPTR = 0x0760; + uint256 internal constant G2_Y_1_MPTR = 0x0780; + uint256 internal constant G2_Y_2_MPTR = 0x07a0; + uint256 internal constant NEG_S_G2_X_1_MPTR = 0x07c0; + uint256 internal constant NEG_S_G2_X_2_MPTR = 0x07e0; + uint256 internal constant NEG_S_G2_Y_1_MPTR = 0x0800; + uint256 internal constant NEG_S_G2_Y_2_MPTR = 0x0820; + + uint256 internal constant CHALLENGE_MPTR = 0x0e40; + + uint256 internal constant THETA_MPTR = 0x0e40; + uint256 internal constant BETA_MPTR = 0x0e60; + uint256 internal constant GAMMA_MPTR = 0x0e80; + uint256 internal constant Y_MPTR = 0x0ea0; + uint256 internal constant X_MPTR = 0x0ec0; + uint256 internal constant ZETA_MPTR = 0x0ee0; + uint256 internal constant NU_MPTR = 0x0f00; + uint256 internal constant MU_MPTR = 0x0f20; + + uint256 internal constant ACC_LHS_X_MPTR = 0x0f40; + uint256 internal constant ACC_LHS_Y_MPTR = 0x0f60; + uint256 internal constant ACC_RHS_X_MPTR = 0x0f80; + uint256 internal constant ACC_RHS_Y_MPTR = 0x0fa0; + uint256 internal constant X_N_MPTR = 0x0fc0; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x0fe0; + uint256 internal constant L_LAST_MPTR = 0x1000; + uint256 internal constant L_BLIND_MPTR = 0x1020; + uint256 internal constant L_0_MPTR = 0x1040; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x1060; + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x1080; + uint256 internal constant QUOTIENT_X_MPTR = 0x10a0; + uint256 internal constant QUOTIENT_Y_MPTR = 0x10c0; + uint256 internal constant R_EVAL_MPTR = 0x10e0; + uint256 internal constant PAIRING_LHS_X_MPTR = 0x1100; + uint256 internal constant PAIRING_LHS_Y_MPTR = 0x1120; + uint256 internal constant PAIRING_RHS_X_MPTR = 0x1140; + uint256 internal constant PAIRING_RHS_Y_MPTR = 0x1160; + + function verifyProof(bytes calldata proof, uint256[] calldata instances) public view override returns (bool) { + assembly ("memory-safe") { + // Read EC point (x, y) at (proof_cptr, proof_cptr + 0x20), + // and check if the point is on affine plane, + // and store them in (hash_mptr, hash_mptr + 0x20). + // Return updated (success, proof_cptr, hash_mptr). + function read_ec_point(success, proof_cptr, hash_mptr, q) -> ret0, ret1, ret2 { + let x := calldataload(proof_cptr) + let y := calldataload(add(proof_cptr, 0x20)) + ret0 := and(success, lt(x, q)) + ret0 := and(ret0, lt(y, q)) + ret0 := and(ret0, eq(mulmod(y, y, q), addmod(mulmod(x, mulmod(x, x, q), q), 3, q))) + mstore(hash_mptr, x) + mstore(add(hash_mptr, 0x20), y) + ret1 := add(proof_cptr, 0x40) + ret2 := add(hash_mptr, 0x40) + } + + // Squeeze challenge by keccak256(memory[0..hash_mptr]), + // and store hash mod r as challenge in challenge_mptr, + // and push back hash in 0x00 as the first input for next squeeze. + // Return updated (challenge_mptr, hash_mptr). + function squeeze_challenge(challenge_mptr, hash_mptr, r) -> ret0, ret1 { + let hash := keccak256(0x00, hash_mptr) + mstore(challenge_mptr, mod(hash, r)) + mstore(0x00, hash) + ret0 := add(challenge_mptr, 0x20) + ret1 := 0x20 + } + + // Squeeze challenge without absorbing new input from calldata, + // by putting an extra 0x01 in memory[0x20] and squeeze by keccak256(memory[0..21]), + // and store hash mod r as challenge in challenge_mptr, + // and push back hash in 0x00 as the first input for next squeeze. + // Return updated (challenge_mptr). + function squeeze_challenge_cont(challenge_mptr, r) -> ret { + mstore8(0x20, 0x01) + let hash := keccak256(0x00, 0x21) + mstore(challenge_mptr, mod(hash, r)) + mstore(0x00, hash) + ret := add(challenge_mptr, 0x20) + } + + // Batch invert values in memory[mptr_start..mptr_end] in place. + // Return updated (success). + function batch_invert(success, mptr_start, mptr_end) -> ret { + let gp_mptr := mptr_end + let gp := mload(mptr_start) + let mptr := add(mptr_start, 0x20) + for {} lt(mptr, sub(mptr_end, 0x20)) {} { + gp := mulmod(gp, mload(mptr), R) + mstore(gp_mptr, gp) + mptr := add(mptr, 0x20) + gp_mptr := add(gp_mptr, 0x20) + } + gp := mulmod(gp, mload(mptr), R) + + mstore(gp_mptr, 0x20) + mstore(add(gp_mptr, 0x20), 0x20) + mstore(add(gp_mptr, 0x40), 0x20) + mstore(add(gp_mptr, 0x60), gp) + mstore(add(gp_mptr, 0x80), sub(R, 2)) + mstore(add(gp_mptr, 0xa0), R) + ret := and(success, staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20)) + let all_inv := mload(gp_mptr) + + let first_mptr := mptr_start + let second_mptr := add(first_mptr, 0x20) + gp_mptr := sub(gp_mptr, 0x20) + for {} lt(second_mptr, mptr) {} { + let inv := mulmod(all_inv, mload(gp_mptr), R) + all_inv := mulmod(all_inv, mload(mptr), R) + mstore(mptr, inv) + mptr := sub(mptr, 0x20) + gp_mptr := sub(gp_mptr, 0x20) + } + let inv_first := mulmod(all_inv, mload(second_mptr), R) + let inv_second := mulmod(all_inv, mload(first_mptr), R) + mstore(first_mptr, inv_first) + mstore(second_mptr, inv_second) + } + + // Add (x, y) into point at (0x00, 0x20). + // Return updated (success). + function ec_add_acc(success, x, y) -> ret { + mstore(0x40, x) + mstore(0x60, y) + ret := and(success, staticcall(gas(), 0x06, 0x00, 0x80, 0x00, 0x40)) + } + + // Scale point at (0x00, 0x20) by scalar. + function ec_mul_acc(success, scalar) -> ret { + mstore(0x40, scalar) + ret := and(success, staticcall(gas(), 0x07, 0x00, 0x60, 0x00, 0x40)) + } + + // Add (x, y) into point at (0x80, 0xa0). + // Return updated (success). + function ec_add_tmp(success, x, y) -> ret { + mstore(0xc0, x) + mstore(0xe0, y) + ret := and(success, staticcall(gas(), 0x06, 0x80, 0x80, 0x80, 0x40)) + } + + // Scale point at (0x80, 0xa0) by scalar. + // Return updated (success). + function ec_mul_tmp(success, scalar) -> ret { + mstore(0xc0, scalar) + ret := and(success, staticcall(gas(), 0x07, 0x80, 0x60, 0x80, 0x40)) + } + + // Perform pairing check. + // Return updated (success). + function ec_pairing(success, lhs_x, lhs_y, rhs_x, rhs_y) -> ret { + mstore(0x00, lhs_x) + mstore(0x20, lhs_y) + mstore(0x40, mload(G2_X_1_MPTR)) + mstore(0x60, mload(G2_X_2_MPTR)) + mstore(0x80, mload(G2_Y_1_MPTR)) + mstore(0xa0, mload(G2_Y_2_MPTR)) + mstore(0xc0, rhs_x) + mstore(0xe0, rhs_y) + mstore(0x100, mload(NEG_S_G2_X_1_MPTR)) + mstore(0x120, mload(NEG_S_G2_X_2_MPTR)) + mstore(0x140, mload(NEG_S_G2_Y_1_MPTR)) + mstore(0x160, mload(NEG_S_G2_Y_2_MPTR)) + ret := and(success, staticcall(gas(), 0x08, 0x00, 0x180, 0x00, 0x20)) + ret := and(ret, mload(0x00)) + } + + // Modulus + let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583 // BN254 base field + let r := 21888242871839275222246405745257275088548364400416034343698204186575808495617 // BN254 scalar field + + // Initialize success as true + let success := true + + { + // Load vk_digest and num_instances of vk into memory + mstore(0x05a0, 0x26c182c695297802d78de2f6872548ff56eee1276238ee6843abd7143a51f9bb) // vk_digest + mstore(0x05c0, 0x0000000000000000000000000000000000000000000000000000000000000001) // num_instances + + // Check valid length of proof + success := and(success, eq(0x0e40, calldataload(sub(PROOF_LEN_CPTR, 0x6014F51900)))) + + // Check valid length of instances + let num_instances := mload(NUM_INSTANCES_MPTR) + success := and(success, eq(num_instances, calldataload(NUM_INSTANCE_CPTR))) + + // Absorb vk diegst + mstore(0x00, mload(VK_DIGEST_MPTR)) + + // Read instances and witness commitments and generate challenges + let hash_mptr := 0x20 + let instance_cptr := INSTANCE_CPTR + for { let instance_cptr_end := add(instance_cptr, mul(0x20, num_instances)) } lt( + instance_cptr, instance_cptr_end + ) {} { + let instance := calldataload(instance_cptr) + success := and(success, lt(instance, r)) + mstore(hash_mptr, instance) + instance_cptr := add(instance_cptr, 0x20) + hash_mptr := add(hash_mptr, 0x20) + } + + let proof_cptr := PROOF_CPTR + let challenge_mptr := CHALLENGE_MPTR + + // Phase 1 + for { let proof_cptr_end := add(proof_cptr, 0x0180) } lt(proof_cptr, proof_cptr_end) {} { + success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) + } + + challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) + + // Phase 2 + for { let proof_cptr_end := add(proof_cptr, 0x0180) } lt(proof_cptr, proof_cptr_end) {} { + success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) + } + + challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) + challenge_mptr := squeeze_challenge_cont(challenge_mptr, r) + + // Phase 3 + for { let proof_cptr_end := add(proof_cptr, 0x0280) } lt(proof_cptr, proof_cptr_end) {} { + success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) + } + + challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) + + // Phase 4 + for { let proof_cptr_end := add(proof_cptr, 0x0100) } lt(proof_cptr, proof_cptr_end) {} { + success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) + } + + challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) + + // Read evaluations + for { let proof_cptr_end := add(proof_cptr, 0x0740) } lt(proof_cptr, proof_cptr_end) {} { + let eval := calldataload(proof_cptr) + success := and(success, lt(eval, r)) + mstore(hash_mptr, eval) + proof_cptr := add(proof_cptr, 0x20) + hash_mptr := add(hash_mptr, 0x20) + } + + // Read batch opening proof and generate challenges + challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) // zeta + challenge_mptr := squeeze_challenge_cont(challenge_mptr, r) // nu + + success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) // W + + challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) // mu + + success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) // W' + + // Load full vk into memory + mstore(0x05a0, 0x26c182c695297802d78de2f6872548ff56eee1276238ee6843abd7143a51f9bb) // vk_digest + mstore(0x05c0, 0x0000000000000000000000000000000000000000000000000000000000000001) // num_instances + mstore(0x05e0, 0x000000000000000000000000000000000000000000000000000000000000000f) // k + mstore(0x0600, 0x3063edaa444bddc677fcd515f614555a777997e0a9287d1e62bf6dd004d82001) // n_inv + mstore(0x0620, 0x2b7ddfe4383c8d806530b94d3120ce6fcb511871e4d44a65f0acd0b96a8a942e) // omega + mstore(0x0640, 0x1f67bc4574eaef5e630a13c710221a3e3d491e59fddabaf321e56f3ca8d91624) // omega_inv + mstore(0x0660, 0x2427343dea588e4242e165ef52d4c1f5986149f372f5c87534f7f6274ef4eeff) // omega_inv_to_l + mstore(0x0680, 0x0000000000000000000000000000000000000000000000000000000000000000) // has_accumulator + mstore(0x06a0, 0x0000000000000000000000000000000000000000000000000000000000000000) // acc_offset + mstore(0x06c0, 0x0000000000000000000000000000000000000000000000000000000000000000) // num_acc_limbs + mstore(0x06e0, 0x0000000000000000000000000000000000000000000000000000000000000000) // num_acc_limb_bits + mstore(0x0700, 0x0000000000000000000000000000000000000000000000000000000000000001) // g1_x + mstore(0x0720, 0x0000000000000000000000000000000000000000000000000000000000000002) // g1_y + mstore(0x0740, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) // g2_x_1 + mstore(0x0760, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) // g2_x_2 + mstore(0x0780, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) // g2_y_1 + mstore(0x07a0, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) // g2_y_2 + mstore(0x07c0, 0x186282957db913abd99f91db59fe69922e95040603ef44c0bd7aa3adeef8f5ac) // neg_s_g2_x_1 + mstore(0x07e0, 0x17944351223333f260ddc3b4af45191b856689eda9eab5cbcddbbe570ce860d2) // neg_s_g2_x_2 + mstore(0x0800, 0x06d971ff4a7467c3ec596ed6efc674572e32fd6f52b721f97e35b0b3d3546753) // neg_s_g2_y_1 + mstore(0x0820, 0x06ecdb9f9567f59ed2eee36e1e1d58797fd13cc97fafc2910f5e8a12f202fa9a) // neg_s_g2_y_2 + mstore(0x0840, 0x1c060a0a885baa8ad6170ed68127f0161acf791b113ec5b32ea4b75e3ea177e9) // fixed_comms[0].x + mstore(0x0860, 0x256ebbf286c3c6d7ccaa5aecf6b9e2778a2b26a3387b146ef33b8dec2487210c) // fixed_comms[0].y + mstore(0x0880, 0x22ad6a6a2a686df5518d9a3db71449ad7ccc0a42857ad5ddea04d1bc9e586900) // fixed_comms[1].x + mstore(0x08a0, 0x1d0541173252663b139a9f7e300c5437ebc8c81286d9eed12b009e3dca1c2766) // fixed_comms[1].y + mstore(0x08c0, 0x1a257be136e68e05b4f596cda6ae4a85a40e8e46d440b77c8ee76a28d31bd70a) // fixed_comms[2].x + mstore(0x08e0, 0x1be665dc42dabc0a20db1c317b2e671f70d7719795c70ec562be3316367db597) // fixed_comms[2].y + mstore(0x0900, 0x15a51539aff2087850cd2d7a60e8c87d1f9a8ef180c289d96294e328767e4ba2) // fixed_comms[3].x + mstore(0x0920, 0x24886af2f62be3465d54a10798576d26eca16de4af6d40eb3df1f89f58e4b2aa) // fixed_comms[3].y + mstore(0x0940, 0x2b50655bb4560d7529bf4070e81524fb13b859ecaa13aaed24a594441ad71918) // fixed_comms[4].x + mstore(0x0960, 0x0eeb09447c8dc370c352e005bd887ed783b4ec37a76e462a3aae7de6fbdde0d1) // fixed_comms[4].y + mstore(0x0980, 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[5].x + mstore(0x09a0, 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[5].y + mstore(0x09c0, 0x02f4c9b91d9caf37cf27171958b6703b58ee66bb51a7cf838491d587519830d5) // fixed_comms[6].x + mstore(0x09e0, 0x116f77a6ff14b0ec62ea14b95d8b216fb24d6814b5017dbcb57c727b8985bd4c) // fixed_comms[6].y + mstore(0x0a00, 0x2489557dccc2e449e4f5d925d14b827aeacc6dfabce6d6edc0392815f515dbef) // fixed_comms[7].x + mstore(0x0a20, 0x1b0a4019159c1f17183d05dd35e780972518fa9cc16284addfb48ba1b345010b) // fixed_comms[7].y + mstore(0x0a40, 0x1e116f6e4cca5a2bd070e6e956f8c46778c357195742e7e89e5f73ce995531c6) // fixed_comms[8].x + mstore(0x0a60, 0x0004da5774424b07e74d8c3439652652ffb43ab88cb8af6bb7e17b6e8435b313) // fixed_comms[8].y + mstore(0x0a80, 0x022eda164ddc02ff78bf7865c7d23a2567ffee2c646dc6bec193e5747ff40c07) // fixed_comms[9].x + mstore(0x0aa0, 0x1abbe83d6dd12b6fc32fdb4e089f8c4441a4922d7d6c1df8b60d6e7218d41eec) // fixed_comms[9].y + mstore(0x0ac0, 0x022eda164ddc02ff78bf7865c7d23a2567ffee2c646dc6bec193e5747ff40c07) // fixed_comms[10].x + mstore(0x0ae0, 0x1abbe83d6dd12b6fc32fdb4e089f8c4441a4922d7d6c1df8b60d6e7218d41eec) // fixed_comms[10].y + mstore(0x0b00, 0x2b9e3227266cf98352b01496c129c1f84edacb1154c1f3d1af1eb5fda103daa8) // fixed_comms[11].x + mstore(0x0b20, 0x23dfbecc443e32ad9110fd50c6cb8836d6affcd6d5dfd5ea63bb7c3e9d363d93) // fixed_comms[11].y + mstore(0x0b40, 0x06f36ef1cf80ed74927c997ca4c39a8ae3a50a3108aa7762d7f575b42a70c830) // fixed_comms[12].x + mstore(0x0b60, 0x2941f8f1e440b03dd52e103afe10f558a5d2bed5d39641fd9c9a7ea2d499ca57) // fixed_comms[12].y + mstore(0x0b80, 0x2696f033aed51e07d4243d9675029ea7e969e3451892c48ea663780b4f38bea8) // fixed_comms[13].x + mstore(0x0ba0, 0x2a01e660505a5579e03f450fc22d01903b665bd0484cc0ea77b72de19963108f) // fixed_comms[13].y + mstore(0x0bc0, 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[14].x + mstore(0x0be0, 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[14].y + mstore(0x0c00, 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[15].x + mstore(0x0c20, 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[15].y + mstore(0x0c40, 0x1b51d2e92317f6946bf226ec4db6fd43c2b56a952c7a07d691ce72f3f712eb10) // permutation_comms[0].x + mstore(0x0c60, 0x2c58979451ed2220cd53841b8b798aec156ad785d7eccfd84338da587db47ad7) // permutation_comms[0].y + mstore(0x0c80, 0x102a1dfe3ab5dcdf6fade0967246e425de6605dfb4ce44333878c62a0805daae) // permutation_comms[1].x + mstore(0x0ca0, 0x1b6f14442666985fe783375d94322dd39312a594b5941b93a76dbc28fca76d48) // permutation_comms[1].y + mstore(0x0cc0, 0x29f61b150cc2e37f8c28b0f072de6d3cac9fabccd74189426db9df67ba779aa2) // permutation_comms[2].x + mstore(0x0ce0, 0x07deec31c76b4d460bdb03131b216acc4b1ff453db08c7d3e0c4ff189ede25f1) // permutation_comms[2].y + mstore(0x0d00, 0x2a12e9991e0b67eaf352a22b1fa55440362d642bf19325f37855b3f39a0ee3e0) // permutation_comms[3].x + mstore(0x0d20, 0x016a1a471ee419cd49d170f0057cf21ec829973d566f6a30577cb0d9eaba11f1) // permutation_comms[3].y + mstore(0x0d40, 0x1639ddc2de54102d9ba013fc009d12169ca6697b0746ba2ddc0aef5d510179ab) // permutation_comms[4].x + mstore(0x0d60, 0x008bb281c3dc2187f02a2458efaa78724f38ae8fbfb8bc04dbf725ac5e4e1f8c) // permutation_comms[4].y + mstore(0x0d80, 0x2cc166fdeed0b096a4cac1f7f5e5eab47fd53c1a8216a9f313a25acf0a9bbcc0) // permutation_comms[5].x + mstore(0x0da0, 0x0a5e50d7ff6f0fb3c725031d190ab438079947afb789a418ca1a627530bbb92a) // permutation_comms[5].y + mstore(0x0dc0, 0x0adbc51ab1dd439ba50652da71c7b89f94639d08db94b4ce07c86074dbf584d1) // permutation_comms[6].x + mstore(0x0de0, 0x1d9a939e5c6e8c7078b878c00456795d2def2b0462516807c27e78828c84947d) // permutation_comms[6].y + mstore(0x0e00, 0x10d30b9eacd293165b0fe77f7dba1d40367e843d4c07e36cf5a9a42b99795546) // permutation_comms[7].x + mstore(0x0e20, 0x0604c04f47a08cf7a604a44c62bfc6b9bd3ac2eaa752ef073011dcd4c16cc3b5) // permutation_comms[7].y + + // Read accumulator from instances + if mload(HAS_ACCUMULATOR_MPTR) { + let num_limbs := mload(NUM_ACC_LIMBS_MPTR) + let num_limb_bits := mload(NUM_ACC_LIMB_BITS_MPTR) + + let cptr := add(INSTANCE_CPTR, mul(mload(ACC_OFFSET_MPTR), 0x20)) + let lhs_y_off := mul(num_limbs, 0x20) + let rhs_x_off := mul(lhs_y_off, 2) + let rhs_y_off := mul(lhs_y_off, 3) + let lhs_x := calldataload(cptr) + let lhs_y := calldataload(add(cptr, lhs_y_off)) + let rhs_x := calldataload(add(cptr, rhs_x_off)) + let rhs_y := calldataload(add(cptr, rhs_y_off)) + for { + let cptr_end := add(cptr, mul(0x20, num_limbs)) + let shift := num_limb_bits + } lt(cptr, cptr_end) {} { + cptr := add(cptr, 0x20) + lhs_x := add(lhs_x, shl(shift, calldataload(cptr))) + lhs_y := add(lhs_y, shl(shift, calldataload(add(cptr, lhs_y_off)))) + rhs_x := add(rhs_x, shl(shift, calldataload(add(cptr, rhs_x_off)))) + rhs_y := add(rhs_y, shl(shift, calldataload(add(cptr, rhs_y_off)))) + shift := add(shift, num_limb_bits) + } + + success := + and(success, eq(mulmod(lhs_y, lhs_y, q), addmod(mulmod(lhs_x, mulmod(lhs_x, lhs_x, q), q), 3, q))) + success := + and(success, eq(mulmod(rhs_y, rhs_y, q), addmod(mulmod(rhs_x, mulmod(rhs_x, rhs_x, q), q), 3, q))) + + mstore(ACC_LHS_X_MPTR, lhs_x) + mstore(ACC_LHS_Y_MPTR, lhs_y) + mstore(ACC_RHS_X_MPTR, rhs_x) + mstore(ACC_RHS_Y_MPTR, rhs_y) + } + + pop(q) + } + + // Revert earlier if anything from calldata is invalid + if iszero(success) { revert(0, 0) } + + // Compute lagrange evaluations and instance evaluation + { + let k := mload(K_MPTR) + let x := mload(X_MPTR) + let x_n := x + for { let idx := 0 } lt(idx, k) { idx := add(idx, 1) } { x_n := mulmod(x_n, x_n, r) } + + let omega := mload(OMEGA_MPTR) + + let mptr := X_N_MPTR + let mptr_end := add(mptr, mul(0x20, add(mload(NUM_INSTANCES_MPTR), 6))) + if iszero(mload(NUM_INSTANCES_MPTR)) { mptr_end := add(mptr_end, 0x20) } + for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } lt(mptr, mptr_end) { mptr := add(mptr, 0x20) } { + mstore(mptr, addmod(x, sub(r, pow_of_omega), r)) + pow_of_omega := mulmod(pow_of_omega, omega, r) + } + let x_n_minus_1 := addmod(x_n, sub(r, 1), r) + mstore(mptr_end, x_n_minus_1) + success := batch_invert(success, X_N_MPTR, add(mptr_end, 0x20)) + + mptr := X_N_MPTR + let l_i_common := mulmod(x_n_minus_1, mload(N_INV_MPTR), r) + for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } lt(mptr, mptr_end) { mptr := add(mptr, 0x20) } { + mstore(mptr, mulmod(l_i_common, mulmod(mload(mptr), pow_of_omega, r), r)) + pow_of_omega := mulmod(pow_of_omega, omega, r) + } + + let l_blind := mload(add(X_N_MPTR, 0x20)) + let l_i_cptr := add(X_N_MPTR, 0x40) + for { let l_i_cptr_end := add(X_N_MPTR, 0xc0) } lt(l_i_cptr, l_i_cptr_end) { + l_i_cptr := add(l_i_cptr, 0x20) + } { l_blind := addmod(l_blind, mload(l_i_cptr), r) } + + let instance_eval := 0 + for { + let instance_cptr := INSTANCE_CPTR + let instance_cptr_end := add(instance_cptr, mul(0x20, mload(NUM_INSTANCES_MPTR))) + } lt(instance_cptr, instance_cptr_end) { + instance_cptr := add(instance_cptr, 0x20) + l_i_cptr := add(l_i_cptr, 0x20) + } { instance_eval := addmod(instance_eval, mulmod(mload(l_i_cptr), calldataload(instance_cptr), r), r) } + + let x_n_minus_1_inv := mload(mptr_end) + let l_last := mload(X_N_MPTR) + let l_0 := mload(add(X_N_MPTR, 0xc0)) + + mstore(X_N_MPTR, x_n) + mstore(X_N_MINUS_1_INV_MPTR, x_n_minus_1_inv) + mstore(L_LAST_MPTR, l_last) + mstore(L_BLIND_MPTR, l_blind) + mstore(L_0_MPTR, l_0) + mstore(INSTANCE_EVAL_MPTR, instance_eval) + } + + // Compute quotient evavluation + { + let quotient_eval_numer + let y := mload(Y_MPTR) + { + let f_11 := calldataload(0x0924) + let var0 := 0x2 + let var1 := sub(R, f_11) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_11, var2, R) + let var4 := 0x3 + let var5 := addmod(var4, var1, R) + let var6 := mulmod(var3, var5, R) + let a_4 := calldataload(0x0764) + let a_0 := calldataload(0x06e4) + let a_2 := calldataload(0x0724) + let var7 := addmod(a_0, a_2, R) + let var8 := sub(R, var7) + let var9 := addmod(a_4, var8, R) + let var10 := mulmod(var6, var9, R) + quotient_eval_numer := var10 + } + { + let f_12 := calldataload(0x0944) + let var0 := 0x2 + let var1 := sub(R, f_12) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_12, var2, R) + let var4 := 0x3 + let var5 := addmod(var4, var1, R) + let var6 := mulmod(var3, var5, R) + let a_5 := calldataload(0x0784) + let a_1 := calldataload(0x0704) + let a_3 := calldataload(0x0744) + let var7 := addmod(a_1, a_3, R) + let var8 := sub(R, var7) + let var9 := addmod(a_5, var8, R) + let var10 := mulmod(var6, var9, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var10, r) + } + { + let f_11 := calldataload(0x0924) + let var0 := 0x1 + let var1 := sub(R, f_11) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_11, var2, R) + let var4 := 0x2 + let var5 := addmod(var4, var1, R) + let var6 := mulmod(var3, var5, R) + let a_4 := calldataload(0x0764) + let a_0 := calldataload(0x06e4) + let a_2 := calldataload(0x0724) + let var7 := mulmod(a_0, a_2, R) + let var8 := sub(R, var7) + let var9 := addmod(a_4, var8, R) + let var10 := mulmod(var6, var9, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var10, r) + } + { + let f_12 := calldataload(0x0944) + let var0 := 0x1 + let var1 := sub(R, f_12) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_12, var2, R) + let var4 := 0x2 + let var5 := addmod(var4, var1, R) + let var6 := mulmod(var3, var5, R) + let a_5 := calldataload(0x0784) + let a_1 := calldataload(0x0704) + let a_3 := calldataload(0x0744) + let var7 := mulmod(a_1, a_3, R) + let var8 := sub(R, var7) + let var9 := addmod(a_5, var8, R) + let var10 := mulmod(var6, var9, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var10, r) + } + { + let f_11 := calldataload(0x0924) + let var0 := 0x1 + let var1 := sub(R, f_11) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_11, var2, R) + let var4 := 0x3 + let var5 := addmod(var4, var1, R) + let var6 := mulmod(var3, var5, R) + let a_4 := calldataload(0x0764) + let a_0 := calldataload(0x06e4) + let a_2 := calldataload(0x0724) + let var7 := sub(R, a_2) + let var8 := addmod(a_0, var7, R) + let var9 := sub(R, var8) + let var10 := addmod(a_4, var9, R) + let var11 := mulmod(var6, var10, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var11, r) + } + { + let f_12 := calldataload(0x0944) + let var0 := 0x1 + let var1 := sub(R, f_12) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_12, var2, R) + let var4 := 0x3 + let var5 := addmod(var4, var1, R) + let var6 := mulmod(var3, var5, R) + let a_5 := calldataload(0x0784) + let a_1 := calldataload(0x0704) + let a_3 := calldataload(0x0744) + let var7 := sub(R, a_3) + let var8 := addmod(a_1, var7, R) + let var9 := sub(R, var8) + let var10 := addmod(a_5, var9, R) + let var11 := mulmod(var6, var10, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var11, r) + } + { + let f_13 := calldataload(0x0964) + let var0 := 0x1 + let var1 := sub(R, f_13) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_13, var2, R) + let var4 := 0x3 + let var5 := addmod(var4, var1, R) + let var6 := mulmod(var3, var5, R) + let a_4 := calldataload(0x0764) + let a_4_prev_1 := calldataload(0x07a4) + let var7 := 0x0 + let a_0 := calldataload(0x06e4) + let a_2 := calldataload(0x0724) + let var8 := mulmod(a_0, a_2, R) + let var9 := addmod(var7, var8, R) + let a_1 := calldataload(0x0704) + let a_3 := calldataload(0x0744) + let var10 := mulmod(a_1, a_3, R) + let var11 := addmod(var9, var10, R) + let var12 := addmod(a_4_prev_1, var11, R) + let var13 := sub(R, var12) + let var14 := addmod(a_4, var13, R) + let var15 := mulmod(var6, var14, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var15, r) + } + { + let f_13 := calldataload(0x0964) + let var0 := 0x2 + let var1 := sub(R, f_13) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_13, var2, R) + let var4 := 0x3 + let var5 := addmod(var4, var1, R) + let var6 := mulmod(var3, var5, R) + let a_4 := calldataload(0x0764) + let var7 := 0x0 + let a_0 := calldataload(0x06e4) + let a_2 := calldataload(0x0724) + let var8 := mulmod(a_0, a_2, R) + let var9 := addmod(var7, var8, R) + let a_1 := calldataload(0x0704) + let a_3 := calldataload(0x0744) + let var10 := mulmod(a_1, a_3, R) + let var11 := addmod(var9, var10, R) + let var12 := sub(R, var11) + let var13 := addmod(a_4, var12, R) + let var14 := mulmod(var6, var13, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var14, r) + } + { + let f_13 := calldataload(0x0964) + let var0 := 0x1 + let var1 := sub(R, f_13) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_13, var2, R) + let var4 := 0x2 + let var5 := addmod(var4, var1, R) + let var6 := mulmod(var3, var5, R) + let a_4 := calldataload(0x0764) + let a_2 := calldataload(0x0724) + let var7 := mulmod(var0, a_2, R) + let a_3 := calldataload(0x0744) + let var8 := mulmod(var7, a_3, R) + let var9 := sub(R, var8) + let var10 := addmod(a_4, var9, R) + let var11 := mulmod(var6, var10, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var11, r) + } + { + let f_14 := calldataload(0x0984) + let var0 := 0x2 + let var1 := sub(R, f_14) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_14, var2, R) + let a_4 := calldataload(0x0764) + let a_4_prev_1 := calldataload(0x07a4) + let var4 := 0x1 + let a_2 := calldataload(0x0724) + let var5 := mulmod(var4, a_2, R) + let a_3 := calldataload(0x0744) + let var6 := mulmod(var5, a_3, R) + let var7 := mulmod(a_4_prev_1, var6, R) + let var8 := sub(R, var7) + let var9 := addmod(a_4, var8, R) + let var10 := mulmod(var3, var9, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var10, r) + } + { + let f_15 := calldataload(0x09a4) + let a_4 := calldataload(0x0764) + let var0 := 0x0 + let a_2 := calldataload(0x0724) + let var1 := addmod(var0, a_2, R) + let a_3 := calldataload(0x0744) + let var2 := addmod(var1, a_3, R) + let var3 := sub(R, var2) + let var4 := addmod(a_4, var3, R) + let var5 := mulmod(f_15, var4, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var5, r) + } + { + let f_14 := calldataload(0x0984) + let var0 := 0x1 + let var1 := sub(R, f_14) + let var2 := addmod(var0, var1, R) + let var3 := mulmod(f_14, var2, R) + let a_4 := calldataload(0x0764) + let a_4_prev_1 := calldataload(0x07a4) + let var4 := 0x0 + let a_2 := calldataload(0x0724) + let var5 := addmod(var4, a_2, R) + let a_3 := calldataload(0x0744) + let var6 := addmod(var5, a_3, R) + let var7 := addmod(a_4_prev_1, var6, R) + let var8 := sub(R, var7) + let var9 := addmod(a_4, var8, R) + let var10 := mulmod(var3, var9, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var10, r) + } + { + let f_5 := calldataload(0x0864) + let var0 := 0x0 + let var1 := mulmod(f_5, var0, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var1, r) + } + { + let f_6 := calldataload(0x0884) + let var0 := 0x0 + let var1 := mulmod(f_6, var0, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var1, r) + } + { + let f_7 := calldataload(0x08a4) + let var0 := 0x0 + let var1 := mulmod(f_7, var0, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var1, r) + } + { + let f_8 := calldataload(0x08c4) + let var0 := 0x0 + let var1 := mulmod(f_8, var0, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var1, r) + } + { + let f_9 := calldataload(0x08e4) + let var0 := 0x0 + let var1 := mulmod(f_9, var0, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var1, r) + } + { + let f_10 := calldataload(0x0904) + let var0 := 0x0 + let var1 := mulmod(f_10, var0, R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), var1, r) + } + { + let l_0 := mload(L_0_MPTR) + let eval := addmod(l_0, sub(R, mulmod(l_0, calldataload(0x0ae4), R)), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let perm_z_last := calldataload(0x0ba4) + let eval := + mulmod(mload(L_LAST_MPTR), addmod(mulmod(perm_z_last, perm_z_last, R), sub(R, perm_z_last), R), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let eval := + mulmod(mload(L_0_MPTR), addmod(calldataload(0x0b44), sub(R, calldataload(0x0b24)), R), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let eval := + mulmod(mload(L_0_MPTR), addmod(calldataload(0x0ba4), sub(R, calldataload(0x0b84)), R), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let gamma := mload(GAMMA_MPTR) + let beta := mload(BETA_MPTR) + let lhs := calldataload(0x0b04) + let rhs := calldataload(0x0ae4) + lhs := + mulmod( + lhs, addmod(addmod(calldataload(0x06e4), mulmod(beta, calldataload(0x09e4), R), R), gamma, R), R + ) + lhs := + mulmod( + lhs, addmod(addmod(calldataload(0x0704), mulmod(beta, calldataload(0x0a04), R), R), gamma, R), R + ) + lhs := + mulmod( + lhs, addmod(addmod(calldataload(0x0724), mulmod(beta, calldataload(0x0a24), R), R), gamma, R), R + ) + mstore(0x00, mulmod(beta, mload(X_MPTR), R)) + rhs := mulmod(rhs, addmod(addmod(calldataload(0x06e4), mload(0x00), R), gamma, R), R) + mstore(0x00, mulmod(mload(0x00), DELTA, R)) + rhs := mulmod(rhs, addmod(addmod(calldataload(0x0704), mload(0x00), R), gamma, R), R) + mstore(0x00, mulmod(mload(0x00), DELTA, R)) + rhs := mulmod(rhs, addmod(addmod(calldataload(0x0724), mload(0x00), R), gamma, R), R) + mstore(0x00, mulmod(mload(0x00), DELTA, R)) + let left_sub_right := addmod(lhs, sub(R, rhs), R) + let eval := + addmod( + left_sub_right, + sub(R, mulmod(left_sub_right, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), R), R)), + R + ) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let gamma := mload(GAMMA_MPTR) + let beta := mload(BETA_MPTR) + let lhs := calldataload(0x0b64) + let rhs := calldataload(0x0b44) + lhs := + mulmod( + lhs, addmod(addmod(calldataload(0x0744), mulmod(beta, calldataload(0x0a44), R), R), gamma, R), R + ) + lhs := + mulmod( + lhs, addmod(addmod(calldataload(0x0764), mulmod(beta, calldataload(0x0a64), R), R), gamma, R), R + ) + lhs := + mulmod( + lhs, addmod(addmod(calldataload(0x0784), mulmod(beta, calldataload(0x0a84), R), R), gamma, R), R + ) + rhs := mulmod(rhs, addmod(addmod(calldataload(0x0744), mload(0x00), R), gamma, R), R) + mstore(0x00, mulmod(mload(0x00), DELTA, R)) + rhs := mulmod(rhs, addmod(addmod(calldataload(0x0764), mload(0x00), R), gamma, R), R) + mstore(0x00, mulmod(mload(0x00), DELTA, R)) + rhs := mulmod(rhs, addmod(addmod(calldataload(0x0784), mload(0x00), R), gamma, R), R) + mstore(0x00, mulmod(mload(0x00), DELTA, R)) + let left_sub_right := addmod(lhs, sub(R, rhs), R) + let eval := + addmod( + left_sub_right, + sub(R, mulmod(left_sub_right, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), R), R)), + R + ) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let gamma := mload(GAMMA_MPTR) + let beta := mload(BETA_MPTR) + let lhs := calldataload(0x0bc4) + let rhs := calldataload(0x0ba4) + lhs := + mulmod( + lhs, addmod(addmod(calldataload(0x07c4), mulmod(beta, calldataload(0x0aa4), R), R), gamma, R), R + ) + lhs := + mulmod( + lhs, + addmod(addmod(mload(INSTANCE_EVAL_MPTR), mulmod(beta, calldataload(0x0ac4), R), R), gamma, R), + R + ) + rhs := mulmod(rhs, addmod(addmod(calldataload(0x07c4), mload(0x00), R), gamma, R), R) + mstore(0x00, mulmod(mload(0x00), DELTA, R)) + rhs := mulmod(rhs, addmod(addmod(mload(INSTANCE_EVAL_MPTR), mload(0x00), R), gamma, R), R) + let left_sub_right := addmod(lhs, sub(R, rhs), R) + let eval := + addmod( + left_sub_right, + sub(R, mulmod(left_sub_right, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), R), R)), + R + ) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_0 := mload(L_0_MPTR) + let eval := mulmod(l_0, calldataload(0x0be4), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_last := mload(L_LAST_MPTR) + let eval := mulmod(l_last, calldataload(0x0be4), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let theta := mload(THETA_MPTR) + let beta := mload(BETA_MPTR) + let table + { + let f_1 := calldataload(0x07e4) + let f_2 := calldataload(0x0804) + table := f_1 + table := addmod(mulmod(table, theta, R), f_2, R) + table := addmod(table, beta, R) + } + let input_0 + { + let f_5 := calldataload(0x0864) + let var0 := 0x1 + let var1 := mulmod(f_5, var0, R) + let a_0 := calldataload(0x06e4) + let var2 := mulmod(var1, a_0, R) + let var3 := sub(R, var1) + let var4 := addmod(var0, var3, R) + let var5 := 0x0 + let var6 := mulmod(var4, var5, R) + let var7 := addmod(var2, var6, R) + let a_4 := calldataload(0x0764) + let var8 := mulmod(var1, a_4, R) + let var9 := 0x1000 + let var10 := mulmod(var4, var9, R) + let var11 := addmod(var8, var10, R) + input_0 := var7 + input_0 := addmod(mulmod(input_0, theta, R), var11, R) + input_0 := addmod(input_0, beta, R) + } + let lhs + let rhs + rhs := table + { + let tmp := input_0 + rhs := addmod(rhs, sub(R, mulmod(calldataload(0x0c24), tmp, R)), R) + lhs := + mulmod(mulmod(table, tmp, R), addmod(calldataload(0x0c04), sub(R, calldataload(0x0be4)), R), R) + } + 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 + ) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_0 := mload(L_0_MPTR) + let eval := mulmod(l_0, calldataload(0x0c44), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_last := mload(L_LAST_MPTR) + let eval := mulmod(l_last, calldataload(0x0c44), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let theta := mload(THETA_MPTR) + let beta := mload(BETA_MPTR) + let table + { + let f_1 := calldataload(0x07e4) + let f_2 := calldataload(0x0804) + table := f_1 + table := addmod(mulmod(table, theta, R), f_2, R) + table := addmod(table, beta, R) + } + let input_0 + { + let f_6 := calldataload(0x0884) + let var0 := 0x1 + let var1 := mulmod(f_6, var0, R) + let a_1 := calldataload(0x0704) + let var2 := mulmod(var1, a_1, R) + let var3 := sub(R, var1) + let var4 := addmod(var0, var3, R) + let var5 := 0x0 + let var6 := mulmod(var4, var5, R) + let var7 := addmod(var2, var6, R) + let a_5 := calldataload(0x0784) + let var8 := mulmod(var1, a_5, R) + let var9 := 0x1000 + let var10 := mulmod(var4, var9, R) + let var11 := addmod(var8, var10, R) + input_0 := var7 + input_0 := addmod(mulmod(input_0, theta, R), var11, R) + input_0 := addmod(input_0, beta, R) + } + let lhs + let rhs + rhs := table + { + let tmp := input_0 + rhs := addmod(rhs, sub(R, mulmod(calldataload(0x0c84), tmp, R)), R) + lhs := + mulmod(mulmod(table, tmp, R), addmod(calldataload(0x0c64), sub(R, calldataload(0x0c44)), R), R) + } + 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 + ) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_0 := mload(L_0_MPTR) + let eval := mulmod(l_0, calldataload(0x0ca4), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_last := mload(L_LAST_MPTR) + let eval := mulmod(l_last, calldataload(0x0ca4), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let theta := mload(THETA_MPTR) + let beta := mload(BETA_MPTR) + let table + { + let f_3 := calldataload(0x0824) + table := f_3 + table := addmod(table, beta, R) + } + let input_0 + { + let f_7 := calldataload(0x08a4) + let var0 := 0x1 + let var1 := mulmod(f_7, var0, R) + let a_0 := calldataload(0x06e4) + let var2 := mulmod(var1, a_0, R) + let var3 := sub(R, var1) + let var4 := addmod(var0, var3, R) + let var5 := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000 + let var6 := mulmod(var4, var5, R) + let var7 := addmod(var2, var6, R) + input_0 := var7 + input_0 := addmod(input_0, beta, R) + } + let lhs + let rhs + rhs := table + { + let tmp := input_0 + rhs := addmod(rhs, sub(R, mulmod(calldataload(0x0ce4), tmp, R)), R) + lhs := + mulmod(mulmod(table, tmp, R), addmod(calldataload(0x0cc4), sub(R, calldataload(0x0ca4)), R), R) + } + 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 + ) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_0 := mload(L_0_MPTR) + let eval := mulmod(l_0, calldataload(0x0d04), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_last := mload(L_LAST_MPTR) + let eval := mulmod(l_last, calldataload(0x0d04), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let theta := mload(THETA_MPTR) + let beta := mload(BETA_MPTR) + let table + { + let f_3 := calldataload(0x0824) + table := f_3 + table := addmod(table, beta, R) + } + let input_0 + { + let f_8 := calldataload(0x08c4) + let var0 := 0x1 + let var1 := mulmod(f_8, var0, R) + let a_1 := calldataload(0x0704) + let var2 := mulmod(var1, a_1, R) + let var3 := sub(R, var1) + let var4 := addmod(var0, var3, R) + let var5 := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000 + let var6 := mulmod(var4, var5, R) + let var7 := addmod(var2, var6, R) + input_0 := var7 + input_0 := addmod(input_0, beta, R) + } + let lhs + let rhs + rhs := table + { + let tmp := input_0 + rhs := addmod(rhs, sub(R, mulmod(calldataload(0x0d44), tmp, R)), R) + lhs := + mulmod(mulmod(table, tmp, R), addmod(calldataload(0x0d24), sub(R, calldataload(0x0d04)), R), R) + } + 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 + ) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_0 := mload(L_0_MPTR) + let eval := mulmod(l_0, calldataload(0x0d64), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_last := mload(L_LAST_MPTR) + let eval := mulmod(l_last, calldataload(0x0d64), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let theta := mload(THETA_MPTR) + let beta := mload(BETA_MPTR) + let table + { + let f_4 := calldataload(0x0844) + table := f_4 + table := addmod(table, beta, R) + } + let input_0 + { + let f_9 := calldataload(0x08e4) + let var0 := 0x1 + let var1 := mulmod(f_9, var0, R) + let a_0 := calldataload(0x06e4) + let var2 := mulmod(var1, a_0, R) + let var3 := sub(R, var1) + let var4 := addmod(var0, var3, R) + let var5 := 0x0 + let var6 := mulmod(var4, var5, R) + let var7 := addmod(var2, var6, R) + input_0 := var7 + input_0 := addmod(input_0, beta, R) + } + let lhs + let rhs + rhs := table + { + let tmp := input_0 + rhs := addmod(rhs, sub(R, mulmod(calldataload(0x0da4), tmp, R)), R) + lhs := + mulmod(mulmod(table, tmp, R), addmod(calldataload(0x0d84), sub(R, calldataload(0x0d64)), R), R) + } + 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 + ) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_0 := mload(L_0_MPTR) + let eval := mulmod(l_0, calldataload(0x0dc4), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let l_last := mload(L_LAST_MPTR) + let eval := mulmod(l_last, calldataload(0x0dc4), R) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + { + let theta := mload(THETA_MPTR) + let beta := mload(BETA_MPTR) + let table + { + let f_4 := calldataload(0x0844) + table := f_4 + table := addmod(table, beta, R) + } + let input_0 + { + let f_10 := calldataload(0x0904) + let var0 := 0x1 + let var1 := mulmod(f_10, var0, R) + let a_1 := calldataload(0x0704) + let var2 := mulmod(var1, a_1, R) + let var3 := sub(R, var1) + let var4 := addmod(var0, var3, R) + let var5 := 0x0 + let var6 := mulmod(var4, var5, R) + let var7 := addmod(var2, var6, R) + input_0 := var7 + input_0 := addmod(input_0, beta, R) + } + let lhs + let rhs + rhs := table + { + let tmp := input_0 + rhs := addmod(rhs, sub(R, mulmod(calldataload(0x0e04), tmp, R)), R) + lhs := + mulmod(mulmod(table, tmp, R), addmod(calldataload(0x0de4), sub(R, calldataload(0x0dc4)), R), R) + } + 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 + ) + quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r) + } + + pop(y) + + let quotient_eval := mulmod(quotient_eval_numer, mload(X_N_MINUS_1_INV_MPTR), r) + mstore(QUOTIENT_EVAL_MPTR, quotient_eval) + } + + // Compute quotient commitment + { + mstore(0x00, calldataload(LAST_QUOTIENT_X_CPTR)) + mstore(0x20, calldataload(add(LAST_QUOTIENT_X_CPTR, 0x20))) + let x_n := mload(X_N_MPTR) + for { + let cptr := sub(LAST_QUOTIENT_X_CPTR, 0x40) + let cptr_end := sub(FIRST_QUOTIENT_X_CPTR, 0x40) + } lt(cptr_end, cptr) {} { + success := ec_mul_acc(success, x_n) + success := ec_add_acc(success, calldataload(cptr), calldataload(add(cptr, 0x20))) + cptr := sub(cptr, 0x40) + } + mstore(QUOTIENT_X_MPTR, mload(0x00)) + mstore(QUOTIENT_Y_MPTR, mload(0x20)) + } + + // Compute pairing lhs and rhs + { + { + let x := mload(X_MPTR) + let omega := mload(OMEGA_MPTR) + let omega_inv := mload(OMEGA_INV_MPTR) + let x_pow_of_omega := mulmod(x, omega, R) + mstore(0x0360, x_pow_of_omega) + mstore(0x0340, x) + x_pow_of_omega := mulmod(x, omega_inv, R) + mstore(0x0320, x_pow_of_omega) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, R) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, R) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, R) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, R) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, R) + mstore(0x0300, x_pow_of_omega) + } + { + let mu := mload(MU_MPTR) + for { + let mptr := 0x0380 + let mptr_end := 0x0400 + let point_mptr := 0x0300 + } lt(mptr, mptr_end) { + mptr := add(mptr, 0x20) + point_mptr := add(point_mptr, 0x20) + } { mstore(mptr, addmod(mu, sub(R, mload(point_mptr)), R)) } + let s + s := mload(0x03c0) + mstore(0x0400, s) + let diff + diff := mload(0x0380) + diff := mulmod(diff, mload(0x03a0), R) + diff := mulmod(diff, mload(0x03e0), R) + mstore(0x0420, diff) + mstore(0x00, diff) + diff := mload(0x0380) + diff := mulmod(diff, mload(0x03e0), R) + mstore(0x0440, diff) + diff := mload(0x03a0) + mstore(0x0460, diff) + diff := mload(0x0380) + diff := mulmod(diff, mload(0x03a0), R) + mstore(0x0480, diff) + } + { + let point_2 := mload(0x0340) + let coeff + coeff := 1 + coeff := mulmod(coeff, mload(0x03c0), R) + mstore(0x20, coeff) + } + { + let point_1 := mload(0x0320) + let point_2 := mload(0x0340) + let coeff + coeff := addmod(point_1, sub(R, point_2), R) + coeff := mulmod(coeff, mload(0x03a0), R) + mstore(0x40, coeff) + coeff := addmod(point_2, sub(R, point_1), R) + coeff := mulmod(coeff, mload(0x03c0), R) + mstore(0x60, coeff) + } + { + let point_0 := mload(0x0300) + let point_2 := mload(0x0340) + let point_3 := mload(0x0360) + let coeff + coeff := addmod(point_0, sub(R, point_2), R) + coeff := mulmod(coeff, addmod(point_0, sub(R, point_3), R), R) + coeff := mulmod(coeff, mload(0x0380), R) + mstore(0x80, coeff) + coeff := addmod(point_2, sub(R, point_0), R) + coeff := mulmod(coeff, addmod(point_2, sub(R, point_3), R), R) + coeff := mulmod(coeff, mload(0x03c0), R) + mstore(0xa0, coeff) + coeff := addmod(point_3, sub(R, point_0), R) + coeff := mulmod(coeff, addmod(point_3, sub(R, point_2), R), R) + coeff := mulmod(coeff, mload(0x03e0), R) + mstore(0xc0, coeff) + } + { + let point_2 := mload(0x0340) + let point_3 := mload(0x0360) + let coeff + coeff := addmod(point_2, sub(R, point_3), R) + coeff := mulmod(coeff, mload(0x03c0), R) + mstore(0xe0, coeff) + coeff := addmod(point_3, sub(R, point_2), R) + coeff := mulmod(coeff, mload(0x03e0), R) + mstore(0x0100, coeff) + } + { + success := batch_invert(success, 0, 0x0120) + let diff_0_inv := mload(0x00) + mstore(0x0420, diff_0_inv) + for { + let mptr := 0x0440 + let mptr_end := 0x04a0 + } lt(mptr, mptr_end) { mptr := add(mptr, 0x20) } { + mstore(mptr, mulmod(mload(mptr), diff_0_inv, R)) + } + } + { + let coeff := mload(0x20) + let zeta := mload(ZETA_MPTR) + let r_eval := 0 + r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x09c4), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(coeff, mload(QUOTIENT_EVAL_MPTR), R), R) + for { + let mptr := 0x0ac4 + let mptr_end := 0x09c4 + } lt(mptr_end, mptr) { mptr := sub(mptr, 0x20) } { + r_eval := addmod(mulmod(r_eval, zeta, R), mulmod(coeff, calldataload(mptr), R), R) + } + for { + let mptr := 0x09a4 + let mptr_end := 0x07a4 + } lt(mptr_end, mptr) { mptr := sub(mptr, 0x20) } { + r_eval := addmod(mulmod(r_eval, zeta, R), mulmod(coeff, calldataload(mptr), R), R) + } + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0e04), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0da4), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0d44), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0ce4), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0c84), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0c24), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(coeff, calldataload(0x0784), R), R) + for { + let mptr := 0x0744 + let mptr_end := 0x06c4 + } lt(mptr_end, mptr) { mptr := sub(mptr, 0x20) } { + r_eval := addmod(mulmod(r_eval, zeta, R), mulmod(coeff, calldataload(mptr), R), R) + } + mstore(0x04a0, r_eval) + } + { + let zeta := mload(ZETA_MPTR) + let r_eval := 0 + r_eval := addmod(r_eval, mulmod(mload(0x40), calldataload(0x07a4), R), R) + r_eval := addmod(r_eval, mulmod(mload(0x60), calldataload(0x0764), R), R) + r_eval := mulmod(r_eval, mload(0x0440), R) + mstore(0x04c0, r_eval) + } + { + let zeta := mload(ZETA_MPTR) + let r_eval := 0 + r_eval := addmod(r_eval, mulmod(mload(0x80), calldataload(0x0b84), R), R) + r_eval := addmod(r_eval, mulmod(mload(0xa0), calldataload(0x0b44), R), R) + r_eval := addmod(r_eval, mulmod(mload(0xc0), calldataload(0x0b64), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(mload(0x80), calldataload(0x0b24), R), R) + r_eval := addmod(r_eval, mulmod(mload(0xa0), calldataload(0x0ae4), R), R) + r_eval := addmod(r_eval, mulmod(mload(0xc0), calldataload(0x0b04), R), R) + r_eval := mulmod(r_eval, mload(0x0460), R) + mstore(0x04e0, r_eval) + } + { + let zeta := mload(ZETA_MPTR) + let r_eval := 0 + r_eval := addmod(r_eval, mulmod(mload(0xe0), calldataload(0x0dc4), R), R) + r_eval := addmod(r_eval, mulmod(mload(0x0100), calldataload(0x0de4), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(mload(0xe0), calldataload(0x0d64), R), R) + r_eval := addmod(r_eval, mulmod(mload(0x0100), calldataload(0x0d84), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(mload(0xe0), calldataload(0x0d04), R), R) + r_eval := addmod(r_eval, mulmod(mload(0x0100), calldataload(0x0d24), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(mload(0xe0), calldataload(0x0ca4), R), R) + r_eval := addmod(r_eval, mulmod(mload(0x0100), calldataload(0x0cc4), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(mload(0xe0), calldataload(0x0c44), R), R) + r_eval := addmod(r_eval, mulmod(mload(0x0100), calldataload(0x0c64), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(mload(0xe0), calldataload(0x0be4), R), R) + r_eval := addmod(r_eval, mulmod(mload(0x0100), calldataload(0x0c04), R), R) + r_eval := mulmod(r_eval, zeta, R) + r_eval := addmod(r_eval, mulmod(mload(0xe0), calldataload(0x0ba4), R), R) + r_eval := addmod(r_eval, mulmod(mload(0x0100), calldataload(0x0bc4), R), R) + r_eval := mulmod(r_eval, mload(0x0480), R) + mstore(0x0500, r_eval) + } + { + let sum := mload(0x20) + mstore(0x0520, sum) + } + { + let sum := mload(0x40) + sum := addmod(sum, mload(0x60), R) + mstore(0x0540, sum) + } + { + let sum := mload(0x80) + sum := addmod(sum, mload(0xa0), R) + sum := addmod(sum, mload(0xc0), R) + mstore(0x0560, sum) + } + { + let sum := mload(0xe0) + sum := addmod(sum, mload(0x0100), R) + mstore(0x0580, sum) + } + { + for { + let mptr := 0x00 + let mptr_end := 0x80 + let sum_mptr := 0x0520 + } lt(mptr, mptr_end) { + mptr := add(mptr, 0x20) + sum_mptr := add(sum_mptr, 0x20) + } { mstore(mptr, mload(sum_mptr)) } + success := batch_invert(success, 0, 0x80) + let r_eval := mulmod(mload(0x60), mload(0x0500), R) + for { + let sum_inv_mptr := 0x40 + let sum_inv_mptr_end := 0x80 + let r_eval_mptr := 0x04e0 + } lt(sum_inv_mptr, sum_inv_mptr_end) { + sum_inv_mptr := sub(sum_inv_mptr, 0x20) + r_eval_mptr := sub(r_eval_mptr, 0x20) + } { + r_eval := mulmod(r_eval, mload(NU_MPTR), R) + r_eval := addmod(r_eval, mulmod(mload(sum_inv_mptr), mload(r_eval_mptr), R), R) + } + mstore(R_EVAL_MPTR, r_eval) + } + { + let nu := mload(NU_MPTR) + mstore(0x00, calldataload(0x05a4)) + mstore(0x20, calldataload(0x05c4)) + success := ec_mul_acc(success, mload(ZETA_MPTR)) + success := ec_add_acc(success, mload(QUOTIENT_X_MPTR), mload(QUOTIENT_Y_MPTR)) + for { + let mptr := 0x0e00 + let mptr_end := 0x0800 + } lt(mptr_end, mptr) { mptr := sub(mptr, 0x40) } { + success := ec_mul_acc(success, mload(ZETA_MPTR)) + success := ec_add_acc(success, mload(mptr), mload(add(mptr, 0x20))) + } + for { + let mptr := 0x0324 + let mptr_end := 0x0164 + } lt(mptr_end, mptr) { mptr := sub(mptr, 0x40) } { + success := ec_mul_acc(success, mload(ZETA_MPTR)) + success := ec_add_acc(success, calldataload(mptr), calldataload(add(mptr, 0x20))) + } + for { + let mptr := 0x0124 + let mptr_end := 0x24 + } lt(mptr_end, mptr) { mptr := sub(mptr, 0x40) } { + success := ec_mul_acc(success, mload(ZETA_MPTR)) + success := ec_add_acc(success, calldataload(mptr), calldataload(add(mptr, 0x20))) + } + mstore(0x80, calldataload(0x0164)) + mstore(0xa0, calldataload(0x0184)) + success := ec_mul_tmp(success, mulmod(nu, mload(0x0440), R)) + success := ec_add_acc(success, mload(0x80), mload(0xa0)) + nu := mulmod(nu, mload(NU_MPTR), R) + mstore(0x80, calldataload(0x03a4)) + mstore(0xa0, calldataload(0x03c4)) + success := ec_mul_tmp(success, mload(ZETA_MPTR)) + success := ec_add_tmp(success, calldataload(0x0364), calldataload(0x0384)) + success := ec_mul_tmp(success, mulmod(nu, mload(0x0460), R)) + success := ec_add_acc(success, mload(0x80), mload(0xa0)) + nu := mulmod(nu, mload(NU_MPTR), R) + mstore(0x80, calldataload(0x0564)) + mstore(0xa0, calldataload(0x0584)) + for { + let mptr := 0x0524 + let mptr_end := 0x03a4 + } lt(mptr_end, mptr) { mptr := sub(mptr, 0x40) } { + success := ec_mul_tmp(success, mload(ZETA_MPTR)) + success := ec_add_tmp(success, calldataload(mptr), calldataload(add(mptr, 0x20))) + } + success := ec_mul_tmp(success, mulmod(nu, mload(0x0480), R)) + success := ec_add_acc(success, mload(0x80), mload(0xa0)) + mstore(0x80, mload(G1_X_MPTR)) + mstore(0xa0, mload(G1_Y_MPTR)) + success := ec_mul_tmp(success, sub(R, mload(R_EVAL_MPTR))) + success := ec_add_acc(success, mload(0x80), mload(0xa0)) + mstore(0x80, calldataload(0x0e24)) + mstore(0xa0, calldataload(0x0e44)) + success := ec_mul_tmp(success, sub(R, mload(0x0400))) + success := ec_add_acc(success, mload(0x80), mload(0xa0)) + mstore(0x80, calldataload(0x0e64)) + mstore(0xa0, calldataload(0x0e84)) + success := ec_mul_tmp(success, mload(MU_MPTR)) + success := ec_add_acc(success, mload(0x80), mload(0xa0)) + mstore(PAIRING_LHS_X_MPTR, mload(0x00)) + mstore(PAIRING_LHS_Y_MPTR, mload(0x20)) + mstore(PAIRING_RHS_X_MPTR, calldataload(0x0e64)) + mstore(PAIRING_RHS_Y_MPTR, calldataload(0x0e84)) + } + } + + // Random linear combine with accumulator + if mload(HAS_ACCUMULATOR_MPTR) { + mstore(0x00, mload(ACC_LHS_X_MPTR)) + mstore(0x20, mload(ACC_LHS_Y_MPTR)) + mstore(0x40, mload(ACC_RHS_X_MPTR)) + mstore(0x60, mload(ACC_RHS_Y_MPTR)) + mstore(0x80, mload(PAIRING_LHS_X_MPTR)) + mstore(0xa0, mload(PAIRING_LHS_Y_MPTR)) + mstore(0xc0, mload(PAIRING_RHS_X_MPTR)) + mstore(0xe0, mload(PAIRING_RHS_Y_MPTR)) + let challenge := mod(keccak256(0x00, 0x100), r) + + // [pairing_lhs] += challenge * [acc_lhs] + success := ec_mul_acc(success, challenge) + success := ec_add_acc(success, mload(PAIRING_LHS_X_MPTR), mload(PAIRING_LHS_Y_MPTR)) + mstore(PAIRING_LHS_X_MPTR, mload(0x00)) + mstore(PAIRING_LHS_Y_MPTR, mload(0x20)) + + // [pairing_rhs] += challenge * [acc_rhs] + mstore(0x00, mload(ACC_RHS_X_MPTR)) + mstore(0x20, mload(ACC_RHS_Y_MPTR)) + success := ec_mul_acc(success, challenge) + success := ec_add_acc(success, mload(PAIRING_RHS_X_MPTR), mload(PAIRING_RHS_Y_MPTR)) + mstore(PAIRING_RHS_X_MPTR, mload(0x00)) + mstore(PAIRING_RHS_Y_MPTR, mload(0x20)) + } + + // Perform pairing + success := + ec_pairing( + success, + mload(PAIRING_LHS_X_MPTR), + mload(PAIRING_LHS_Y_MPTR), + mload(PAIRING_RHS_X_MPTR), + mload(PAIRING_RHS_Y_MPTR) + ) + + // Revert if anything fails + if iszero(success) { revert(0x00, 0x00) } + + // Return 1 as result if everything succeeds + mstore(0x00, 1) + return(0x00, 0x20) + } + } +} diff --git a/src/ICollateralCalculator.sol b/src/ICollateralCalculator.sol index ed4f846..db807b8 100644 --- a/src/ICollateralCalculator.sol +++ b/src/ICollateralCalculator.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; /** * @title ICollateralCalculator @@ -17,6 +17,8 @@ interface ICollateralCalculator { } + event CreditScoreUpdated(address indexed borrower, uint256 creditScore, CreditTier newTier); + /** * @dev Struct containing collateral requirement details */ @@ -38,6 +40,8 @@ interface ICollateralCalculator { view returns (CollateralRequirement memory); + function getBorrowerCreditTier(address _borrower) external view returns (CreditTier); + /** * @dev Update credit score for an address after verifying ZK proof * @param _borrower Address of the borrower diff --git a/src/IZKCreditVerifier.sol b/src/IZKCreditVerifier.sol index c2d36c0..8233d85 100644 --- a/src/IZKCreditVerifier.sol +++ b/src/IZKCreditVerifier.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; /** * @title IZKCreditVerifier @@ -19,5 +19,5 @@ interface IZKCreditVerifier { * @dev Gets the verification key hash used for this verifier * @return The hash of the verification key */ - function getVerificationKeyHash() external view returns (bytes32); + //function getVerificationKeyHash() external view returns (bytes32); } diff --git a/test/CollateralCalculatorTest.t.sol b/test/CollateralCalculatorTest.t.sol new file mode 100644 index 0000000..647944c --- /dev/null +++ b/test/CollateralCalculatorTest.t.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {Test, console} from "forge-std/Test.sol"; +import {CollateralCalculator} from "../src/CollateralCalculator.sol"; +import {ICollateralCalculator} from "../src/ICollateralCalculator.sol"; + +contract CollateralCalculatorTest is Test { + CollateralCalculator s_collateralCalculator; + + function setUp() public { + s_collateralCalculator = new CollateralCalculator(); + } + + // --------------------------------- + // updateCreditScore() + // --------------------------------- + function test_updateCreditScore_whenProofIsInvalid_setsCreditTierToUnknown() public { + address borrower = makeAddr("panos"); + uint256 creditScore = 0; + bool proofValidated = false; + + // fire + vm.expectEmit(true, false, false, true, address(s_collateralCalculator)); + emit ICollateralCalculator.CreditScoreUpdated(borrower, creditScore, ICollateralCalculator.CreditTier.UNKNOWN); + s_collateralCalculator.updateCreditScore(borrower, creditScore, proofValidated); + + // check the credit tier of the borrower + ICollateralCalculator.CreditTier tier = s_collateralCalculator.getBorrowerCreditTier(borrower); + assertEq(uint256(tier), uint256(ICollateralCalculator.CreditTier.UNKNOWN)); + } + + function test_updateCreditScore_whenProofIsValidAndCreditScoreLessThan400_setsCreditTierToLow() public { + address borrower = makeAddr("panos"); + uint256 creditScore = 399; + bool proofValidated = true; + + // fire + vm.expectEmit(true, false, false, true, address(s_collateralCalculator)); + emit ICollateralCalculator.CreditScoreUpdated(borrower, creditScore, ICollateralCalculator.CreditTier.LOW); + s_collateralCalculator.updateCreditScore(borrower, creditScore, proofValidated); + + // check the credit tier of the borrower + ICollateralCalculator.CreditTier tier = s_collateralCalculator.getBorrowerCreditTier(borrower); + assertEq(uint256(tier), uint256(ICollateralCalculator.CreditTier.LOW)); + } + + function test_updateCreditScore_whenProofIsValidAndCreditScoreLessThan700_setsCreditTierToMedium() public { + address borrower = makeAddr("panos"); + uint256 creditScore = 699; + bool proofValidated = true; + + // fire + vm.expectEmit(true, false, false, true, address(s_collateralCalculator)); + emit ICollateralCalculator.CreditScoreUpdated(borrower, creditScore, ICollateralCalculator.CreditTier.MEDIUM); + s_collateralCalculator.updateCreditScore(borrower, creditScore, proofValidated); + + // check the credit tier of the borrower + ICollateralCalculator.CreditTier tier = s_collateralCalculator.getBorrowerCreditTier(borrower); + assertEq(uint256(tier), uint256(ICollateralCalculator.CreditTier.MEDIUM)); + } + + function test_updateCreditScore_whenProofIsValidAndCreditScoreGreaterThanOrEqualTo700_setsCreditTierToHigh() + public + { + address borrower = makeAddr("panos"); + uint256 creditScore = 700; + bool proofValidated = true; + + // fire + vm.expectEmit(true, false, false, true, address(s_collateralCalculator)); + emit ICollateralCalculator.CreditScoreUpdated(borrower, creditScore, ICollateralCalculator.CreditTier.HIGH); + s_collateralCalculator.updateCreditScore(borrower, creditScore, proofValidated); + + // check the credit tier of the borrower + ICollateralCalculator.CreditTier tier = s_collateralCalculator.getBorrowerCreditTier(borrower); + assertEq(uint256(tier), uint256(ICollateralCalculator.CreditTier.HIGH)); + } + + // --------------------------------- + // getBorrowerCreditTier() + // --------------------------------- + + function test_getBorrowerCreditTier_whenBorrowerIsNotRegistered_itReturnsUnknown() public { + address borrower = makeAddr("panos"); + + // fire + ICollateralCalculator.CreditTier tier = s_collateralCalculator.getBorrowerCreditTier(borrower); + + // check the credit tier of the borrower + assertEq(uint256(tier), uint256(ICollateralCalculator.CreditTier.UNKNOWN)); + } + + // ------------------------------ + // getCollateralRequirement() + // ------------------------------ + + function test_getCollateralRequirement_whenBorrowerDoesNotHaveCredit_itReturnsAmountWithUnknownTier() public { + console.log("Testing getCollateralRequirement()"); + + // Arrange + address panos = makeAddr("panos"); + uint256 borrowedAmount = 1_000 ether; + + // Act + CollateralCalculator.CollateralRequirement memory collateralRequirement = + s_collateralCalculator.getCollateralRequirement(panos, borrowedAmount); + + // Assert + assertEq(collateralRequirement.requiredPercentage, 120); + assertEq(collateralRequirement.requiredAmount, 1_200 ether); + assertEq(uint256(collateralRequirement.tier), uint256(ICollateralCalculator.CreditTier.UNKNOWN)); + } +} diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol new file mode 100644 index 0000000..fd04e8e --- /dev/null +++ b/test/CollateralizedLoanTest.t.sol @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {CollateralizedLoan} from "../src/CollateralizedLoan.sol"; +import {Test, console} from "forge-std/Test.sol"; +import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; +import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; // Adjust the import path as necessary + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract CollateralizedLoanTest is Test { + CollateralizedLoan s_loan; + ERC20Mock s_collateralToken; + uint256 constant INTEREST_RATE = 20; // 20% + uint256 constant MIN_COLLATERALIZATION_RATIO = 10; // ? + + function setUp() public { + console.log("CollateralizedLoanTest#setUp(): msg.sender = ", msg.sender); + console.log("CollateralizedLoanTest#setUp(): address(this) = ", address(this)); + + s_collateralToken = new ERC20Mock(); + s_loan = new CollateralizedLoan{value: 100 ether}( + msg.sender, s_collateralToken, INTEREST_RATE, MIN_COLLATERALIZATION_RATIO + ); + } + + // ------- + // Ownable + // ------- + function test_ownerIsTheDeployer() public view { + assertEq(address(s_loan.owner()), address(msg.sender)); + } + + // ------------------ + // collateralToken() + // ------------------ + function test_onDeploymentWeSetPublicCollateralERC20Token() public view { + assertEq(address(s_loan.collateralToken()), address(s_collateralToken)); + } + + // ------------- + // interestRate() + // ------------- + function test_interestRate_returnsTheInterestRate() public view { + assertEq(s_loan.interestRate(), INTEREST_RATE); + } + + // ---------------------------- + // minimumCollateralRequired() + // ---------------------------- + function test_returnsTheMinimumCollateralRequired() public view { + uint256 borrowedAmount = 100 ether; + uint256 result = s_loan.minimumCollateralRequired(borrowedAmount); + uint256 expectedResult = borrowedAmount + (borrowedAmount * MIN_COLLATERALIZATION_RATIO) / 100; + + assertEq(result, expectedResult); + } + + // ----------------------------- + // myLoanInfo() + // ----------------------------- + function test_myLoanInfo_whenCallerHasBorrowed_itReturnsWhetherCallerHasBorrowedAndInfoAboutIt() public { + address borrower = makeAddr("panos"); + (uint256 borrowedAmount, uint256 collateralAmount) = borrowerHasActiveLoan(borrower); + + // fire + vm.prank(borrower); + CollateralizedLoan.LoanInfo memory loanInfo; + loanInfo = s_loan.myLoanInfo(); + // ----- + + assertEq(loanInfo.borrower, borrower); + assertEq(loanInfo.borrowedAmount, borrowedAmount); + assertEq(loanInfo.collateralAmount, collateralAmount); + assertEq(loanInfo.requestedAt, block.timestamp); + assertEq(loanInfo.paid, false); + } + + function test_myLoanInfo_whenCallerHasNotBorrowed_itReturnsEmptyInformation() public { + address borrower = makeAddr("panos"); + + // fire + vm.prank(borrower); + CollateralizedLoan.LoanInfo memory loanInfo; + loanInfo = s_loan.myLoanInfo(); + // ----- + + assertEq(loanInfo.borrower, address(0)); + assertEq(loanInfo.borrowedAmount, 0); + assertEq(loanInfo.collateralAmount, 0); + assertEq(loanInfo.requestedAt, 0); + assertEq(loanInfo.paid, false); + } + + // TODO: We may need to add some more tests here when the user has returned the loan + + // ----------------------------- + // requestLoan() + // ----------------------------- + function test_requestLoan_whenBorrowerHasUnpaidLoan_itReverts() public { + address borrower = makeAddr("panos"); + (uint256 borrowedAmount, uint256 collateralAmount) = borrowerHasActiveLoan(borrower); + + // fire + + vm.prank(borrower); + vm.expectRevert( + abi.encodeWithSelector( + CollateralizedLoan.BorrowerHasUnpaidLoanError.selector, borrower, borrowedAmount, block.timestamp + ) + ); + s_loan.requestLoan(borrowedAmount, collateralAmount); + } + + function test_requestLoan_whenCollateralAmountIsBelowMinimumRequired_itReverts() public { + address borrower = makeAddr("panos"); + uint256 borrowedAmountRequested = 30 ether; + uint256 minCollateralizationRatio = MIN_COLLATERALIZATION_RATIO; + uint256 minimumCollateralRequired = + borrowedAmountRequested + (borrowedAmountRequested * minCollateralizationRatio) / 100; + uint256 collateralProvided = minimumCollateralRequired - 1; + + // fire + vm.prank(borrower); + vm.expectRevert( + abi.encodeWithSelector( + CollateralizedLoan.NotEnoughCollateralProvidedForBorrowedAmountError.selector, + borrowedAmountRequested, + minCollateralizationRatio, + minimumCollateralRequired, + collateralProvided + ) + ); + s_loan.requestLoan(borrowedAmountRequested, collateralProvided); + } + + function test_requestLoan_whenContractDoesNotHaveEnoughEther_itReverts() public { + address borrower = makeAddr("panos"); + uint256 borrowedAmount = 30 ether; + uint256 collateralAmount = borrowedAmount + (borrowedAmount * MIN_COLLATERALIZATION_RATIO) / 100; + + s_loan = new CollateralizedLoan{value: (borrowedAmount - 1)}( + msg.sender, s_collateralToken, INTEREST_RATE, MIN_COLLATERALIZATION_RATIO + ); + + // fire + vm.prank(borrower); + vm.expectRevert( + abi.encodeWithSelector( + CollateralizedLoan.LenderDoesNotHaveEnoughEtherError.selector, borrowedAmount, borrowedAmount - 1 + ) + ); + s_loan.requestLoan(borrowedAmount, collateralAmount); + } + + function test_requestLoan_whenBorrowerDoesNotHaveEnoughCollateral_itReverts() public { + address borrower = makeAddr("panos"); + uint256 borrowedAmount = 30 ether; + uint256 collateralAmount = borrowedAmount + (borrowedAmount * MIN_COLLATERALIZATION_RATIO) / 100; + vm.deal(address(s_loan), borrowedAmount); + + // fire + vm.prank(borrower); + vm.expectRevert( + abi.encodeWithSelector( + CollateralizedLoan.BorrowerDoesNotHaveEnoughCollateralError.selector, borrower, collateralAmount, 0 + ) + ); + s_loan.requestLoan(borrowedAmount, collateralAmount); + } + + function test_requestLoan_whenBorrowerHasNotApprovedContractToGetCollateralFromTheirAccount_itReverts() public { + address borrower = makeAddr("panos"); + uint256 borrowedAmount = 30 ether; + uint256 collateralAmount = borrowedAmount + (borrowedAmount * MIN_COLLATERALIZATION_RATIO) / 100; + vm.deal(address(s_loan), borrowedAmount); + s_collateralToken.mint(borrower, collateralAmount); + + // fire + vm.prank(borrower); + vm.expectRevert( + abi.encodeWithSelector( + IERC20Errors.ERC20InsufficientAllowance.selector, address(s_loan), 0, collateralAmount + ) + ); + s_loan.requestLoan(borrowedAmount, collateralAmount); + } + + function test_requestLoan_movesMoneyAcrossBorrowerAndLoanContract() public { + address borrower = makeAddr("panos"); + uint256 borrowedAmount = 30 ether; + uint256 collateralAmount = borrowedAmount + (borrowedAmount * MIN_COLLATERALIZATION_RATIO) / 100; + vm.deal(address(s_loan), borrowedAmount); + s_collateralToken.mint(borrower, collateralAmount); + vm.prank(borrower); + s_collateralToken.approve(address(s_loan), collateralAmount); + + uint256 loanEtherBalanceBefore = address(s_loan).balance; + uint256 borrowerEtherBalanceBefore = borrower.balance; + uint256 loanCollateralBalanceBefore = s_collateralToken.balanceOf(address(s_loan)); + uint256 borrowerCollateralBalanceBefore = s_collateralToken.balanceOf(borrower); + + // fire + vm.prank(borrower); + + vm.expectEmit(true, true, false, true, address(s_collateralToken)); + emit IERC20.Transfer(borrower, address(s_loan), collateralAmount); + + vm.expectEmit(true, false, false, true, address(s_loan)); + emit CollateralizedLoan.LoanGranted(borrower, borrowedAmount, collateralAmount); + + s_loan.requestLoan(borrowedAmount, collateralAmount); + // ----- + + uint256 loanEtherBalanceAfter = address(s_loan).balance; + assertEq(loanEtherBalanceAfter, loanEtherBalanceBefore - borrowedAmount); + + uint256 borrowerEtherBalanceAfter = borrower.balance; + assertEq(borrowerEtherBalanceAfter, borrowerEtherBalanceBefore + borrowedAmount); + + uint256 loanCollateralBalanceAfter = s_collateralToken.balanceOf(address(s_loan)); + assertEq(loanCollateralBalanceAfter, loanCollateralBalanceBefore + collateralAmount); + + uint256 borrowerCollateralBalanceAfter = s_collateralToken.balanceOf(borrower); + assertEq(borrowerCollateralBalanceAfter, borrowerCollateralBalanceBefore - collateralAmount); + + vm.prank(borrower); + CollateralizedLoan.LoanInfo memory loanInfo; + loanInfo = s_loan.myLoanInfo(); + + assertEq(loanInfo.borrower, borrower); + assertEq(loanInfo.borrowedAmount, borrowedAmount); + assertEq(loanInfo.collateralAmount, collateralAmount); + assertEq(loanInfo.requestedAt, block.timestamp); + assertEq(loanInfo.paid, false); + } + + // ----------------------------- + // private utility functions + // ----------------------------- + + function borrowerHasActiveLoan(address _borrower) + private + returns (uint256 _borrowedAmount, uint256 _collateralAmount) + { + _borrowedAmount = 10 ether; + _collateralAmount = 30 ether; + + // the lender needs to have enough in order to lend + vm.deal(address(s_loan), _borrowedAmount); + + // the borrower needs to have enough in order to send collateral + s_collateralToken.mint(_borrower, _collateralAmount); + + // the borrower needs to have given the CollateralizedLoan + // smart contract the allowance to transfer collateral from their + // account to the smart contract account. + vm.prank(_borrower); + s_collateralToken.approve(address(s_loan), _collateralAmount); + + vm.prank(_borrower); + s_loan.requestLoan(_borrowedAmount, _collateralAmount); + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/CreditVerifierTest.t.sol b/test/CreditVerifierTest.t.sol new file mode 100644 index 0000000..357c29d --- /dev/null +++ b/test/CreditVerifierTest.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {Test, console} from "forge-std/Test.sol"; +import {CreditVerifier} from "../src/CreditVerifier.sol"; +import {IZKCreditVerifier} from "../src/IZKCreditVerifier.sol"; + +contract CreditVerifierTest is Test { + IZKCreditVerifier s_creditVerifier; + + function setUp() public { + s_creditVerifier = new CreditVerifier(); + } + + // ---------------- + // verifyProof() + // ---------------- + function test_caseOne() public view { + bytes memory _proof = + "0x20f3bf5e005a9206606a133d89f67332d1b86780bf7f6b4cd68d858f8b843bea23247e82a78164ca47a65ecde48d961447336f5273c72d207e740a78483f910d136126ab80a094ab28bad7e552c54235703f8f77ce7e5e4ae45aa106318aa3f61e9ca20bec53bbcbb9d18a828442d2ab1695d7fd0f21b419506a7824bd39213d0c7f87b957b5063826679f97283df918a649def879fc342c0b6651eda0e5de930981ff0f39c2f64b57037c2ce06b2d503d70c3c0cc23f874f7cb5d058bac101c29d1726cb3fc309e603ed3d1e34850b37c95f968f8cbf47d394b453ee8add9700fac2d4a7a5c608cf4cabe97e7d93c74d7d45c285875f2ffa52ef4f33961cc8e205ffc6915f3b90d663000bdf8a5ca3ea7dc890171baa26bf2ae855ff1be32f01f6a8898b29286dcc7e1a8b6c0e31ec1c5a077498f83a5d8c8453615b958df32266309c3f0992a9319ea7e71bb15d7df1e75069db72bcd0285f08e0ab11e93cd1e10e58e3463fe31a5ebcabf5471e0f8998e0d323ec263343eaedf4d5b1946b900577c5dc7510b687fd03b40c8718ca1b8a1330daa170d56349c5a79de6d10f90593096b71407e34ff579ee7b86792ea873f58f24933247328a34c3e0d1f9118234ee8a677e3e0e929f829a472cded78738354d0a53074b983f85060ecbae7c921c66d31e11793ce4cc2c4af2613c573aba9fffba163e39421b2e46c18caa92a05f36dc0fc00954375711c8ac6107d2e55b2a8a4536033f59966fd3b10855ed50042cc3e896b8a30bb4e157c12d8dff02bc706dc3bb1ee910eb23801ff2415142585cad7c0594b801008339509aeecc130da8ca562a70821343e1a438559ba3114670541b73f88f56d8e3e3f7f1cf9b7a7b476cd963984b0ca7849d4b9acfb5000577c5dc7510b687fd03b40c8718ca1b8a1330daa170d56349c5a79de6d10f90593096b71407e34ff579ee7b86792ea873f58f24933247328a34c3e0d1f91181672ac7fe3a941c0d5a9e51492123b466bb28063df42016f36d2f1df6a0a18eb2f9a4fa75e87f6ddc4885c6bfaba37bf6ef4865424d12cca5f1f0c8310ebc819019dabd79e189aeb33686a91e8d0d070c8faf9cf34b75600b5f3bb2697d93a4a1f1c32e34c8c9ff1453d675fe27a2a5428b7061ef1023342771389e85354f93d2e38bb6a712de63e3c674461eefe8a577ff7ab2e8f59dfea0a6511842ea322fa2f29eb090a92e64fb3904b4571c63ac1f4cc0572a0c00fee3133b6bdc844ae1c00c3435a4234f84fd71a1ddbf8db77b8b7f49d47b15a6d21640ed3c58f3968b903987a665660a102f3a092cadee814ab1074279d1204a04199310b1184cdad79256f1dd73c75e9c756d6ef8d90595884419aa61799a2fd57223b08ee6d4f62da232eb95f981b11a86cca1505668cb9cd333d9719d53f396ae5058822ef6c4c451ff238f3f7ef48aa90e9277bd17704c4503d992b5e068505b352dc59e916e3ff0c06ab22a7d70eb366b7e0270a7112358f2e541302589187ab1690ea1589d60b03222367cce91d6d53e4071a484f9eeacf6bf204276fc62386dcb9e105c335ce06dd5f3c90a6ffb0c822f539d2bed42c9312fa278f8cd755ca0bf8ec1c9053981b5d8128823fb3ee6403b9e8dc4902e2bcc8a2cdf4bebb1cb477a60b837760ad15b71b0b05d5958d8b33bd4f63a1d2c1a44bbb7650a19c96216f06c80c7d38dd25127434fa57de8f7bbce72801ecb003134c1d61c3f31151e5582a8ee3653f622dd63fd716f4b6609c6e319817cb9ad620f4986a60dda8f0e9c8165ab283771d2ea857ca8abf4abda3988f158da98697a1afea8cb7012a901e8f2e27f90bf7a61c9417eb7b53ce667b3652caef521ba1b33370b2fb22b3be3789942127fd64d00675fcdbb3d13eaa737a68641d1ad2ea0eb5b94ee208bf002337fde052d28dfd040a6783c8c073f062dc4589c7f1585e4ee531dbe65097089d46e01e23e39b441ce181d5489538449a064ca4d96e39ed57da18ea94db0cc47823c8e6409f31262622612f81b13c4191cdb3c5fd3b60bbe5c480756435ee31d8ec8476d0f9760e250aa16f031b1a74dc4f1daa471318150683d1690a26d8458ecfeef626f0f42d1835803409bef61d308bdc2316333d9f5b73c35da429fb3bddd4b83d9dc57d1717c37343450ce12b451ce26e729c8c5ba98f38d4055e8b7a5dc10e844d762bea20e11c275fffe3a4d99ae703422b69de900c4cbcf8f48e25bb3418b202fcf0c42e122ed1c5908194f8b34053ee98b7880adf737afd7bf1c01510df7d9508d825014634a126ba3983f23e7260fbf51480f7e39ef8504d750581d4a809380760e510161d01a7a5cf1ba2ad0444f79e04d513b441126a81f8d2c5fa02fa7e5d9167057463070cf85bbcfb92b05133326f646270c8d11758dfb30760979d7e23bd241d18de153fcefa0688bfb236191186023aacdeb1ebfabdfcb8546796cbcc216f273356e957199b130fb32ad2e35e157576ca885c8fcd0089015a7e9f01519aa723dc3b9f338777b30b28f7a7622a6eb13a0751f7f7eb7609492d67ae8239414f0595debaf2c91da0eeda03289b0fce3ebea6a4823664c6b3ccf7026a354791aa1f07862e5391f855180c53d6f25b05567c44d5ebf53d87cba3f7eb11fa295873108580882061b7b7ee4b59db995555ff21743f49d2fb85f1b02816a0377dca201c611f24c83751e54d6b48d80fd063e888a65bb227ba0b5098ed9ab83043736c22cd1b09ea8f7edfb881814b5df22cd45230cbb8400caa78b5e2929f7badf1cc174723ca2a73f65625199a14b2e7bca81389596a991020d7c776150cd837c43a07eb36b4f42aa226583b906e4086ed5001be9b596cd33ab1b82bc763faa0f5ab00000000000000000000000000000000000000000000000000000000000000002cd28c9419e93f54c4b85965b8cd095ff54afaf05112c0260f8dbe84e4470f181afe752751bd9353a8e059e59febd11793f26daf126f034bd2aabb24026dca45056a1c94cbb202facdac319f656e6182a73477322f2718ac3bad1f9b926b4459045f00ed2539a4751ed54498d0687b26d0d3affcb55283b60b266d13fb955fd6045f00ed2539a4751ed54498d0687b26d0d3affcb55283b60b266d13fb955fd610614ef95e857ddd9b27934950f8f64284723d1aa2cda50625547b1a10b4b5552ba298bf62e5c0abdefa637f8e2bf1deb78f4e401d5dc18924ec00fecaed46ea105ed5e9b674d2e4280838557526cf325de5376d4f978cd115c902693de691b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000275ef4444a59ea46e2a2abf4450c587971842b09fd678efb451b38f0b01db2801bf0bdd1cb5790e4318654983c806d1fddde39d32e3f41c058f547d1e4f16255130176fd2870c73283eab3a0c63b241cfb596bda39397f5de155394aff8c8edb2ad67140c09fe469360d9f112593a5ba93cc5ab1fbc176ca70c8258fd37e1580259251028d07f70b620b3269ac73ff108069fe21b91b3fd4c8d46fb776adae40005163799e826261a1b772661f0bb910185f69df9358169e73fd311271caec071b55cc77b2d82215186966bfa63953fc1f9411ec11f8ad4f3a7259de4466ac1c2993c5655d537f5bed1944291d23f297984fc59008cec0b3fa17c085830718d322aab6e5c85eb06787d69e2bb691505470d13755e57c4a40263794f0f43196022a6c2e4e5d03ba2ab322b7680bfe58d6e88447036b5163177ed9d5b408e661382bf8a138c639f9d0d58fcc265cb25f45e6bc69fbe1c84112e0ed5a201ab616d706ff0794f19bc1b79cf76bccb601a2169cea9fbe4790530ad040e2ecbc3e54ee2e0d04f0f320176e11d8fc2fc08b6a81b074fc41d1f8d611620cde2c33dbd4d50b35685ea760577c6a892fb4f23bd4226a4518e5ebc89e00c7613973f55ddad516a7a5e79b0ececb765026d7334322043dfb634a664aed141e46034213f2589220f49193e84f31d196e3e5bd8b969106c0b010af9e4eebb0f966b56e09cefe7c21286cd7c385531a03aa652f75e9e675fd7ac906aa33e9473738803f4a78e9b82fe94a0cecef387315efb482e0412c8d8c26d875960ae8799f49cdaa1d66f0a629f971b395fd63b9c3470f41fb51b221bbbda9fecbe4fdbafcb957b7e0de000d2ff5b964ae3322eb6dda5b7f0a1d442d1ee4b7d1cb677035a8c66b6ee54e65b61822ea54713eb04e18e46ad7aaca4db6390bf7e7e08b4bcc2fa017829b79b79f2cab1677790e6b05d29150bafb4ea58aa4b4dce3219442eaab8fe840bc02b41611b91ac931a1725b65b9948bf9dc59c9e183090c47912d886d55ad3610ec7d6a0b01e01868c259191f299c7752c5f09200d0896bcabb09caee09b139449b430e03e18f48dc5403308e835692d3f441b8a451e4491f18fb3cf471c6e48dd63c30121acc672e3653c33f85ed2fd5783929aba0590693e81a0b923633dfa89b23231cf03ea97e074bd3d45679970250c18fa524119955edd45c34ff04d11fc4e69c3007e28f77fd8aedf80c128b2658f778a5a40e848ac26afafcb7234a7b2d0d781477b437eeb070987460589cf42766fb10d8f4f4dc992958d09bec9e14deda6511e91bb89d27e5405533adeff6e03aac62c55880943c1ba08c26e1d9b493b8eb2985c5dcf0178339becf8338c79beaa83c4fa8de70b1e9c3f68288ca5f20ed542ff5b964ae3322eb6dda5b7f0a1d442d1ee4b7d1cb677035a8c66b6ee54e65b627c0a6487b74923f41bed158cab26c99e95126b6e3af41e7c0778f0e73b0733f2ac3432c3d20230b76f9fdee568c69dcf66d909cb215b19b0dbb92699118cabd19729532d03d5799c7d9c3c5efd92e6651bfcc42917cb6bd17461ec43d03926f11f64e24df87cdfaf0bbbfd29de8c0115f76dec64057dc5415c8d3acad4df2e70bebafd71d3b6ba7802abf469833dd06d292575b76267233f6ba6d7b7b626afe02e70a257898092378aeb6b5aaff9f706f6a76929bb05d21ace71fd35ce6f94f13b34418fb478f9ef1045af6ab5ca8879e0164c5dbc0c174e4c01b61ab68be63"; + + uint256[] memory _publicInputs = new uint256[](1); + _publicInputs[0] = uint256(0x6d14000000000000000000000000000000000000000000000000000000000000); + + bool verified = s_creditVerifier.verifyProof(_proof, _publicInputs); + + assertEq(verified, true, "Invalid Proof"); + } +}