From ba3c33f21475695c953bad80304ae705c4c9d214 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sat, 1 Mar 2025 16:37:41 +0200 Subject: [PATCH 01/23] forge install: openzeppelin-contracts v5.2.0 --- .gitmodules | 3 +++ lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts 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/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 From 7e4c986e16be067614b25e133e6598c53a1fc670 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sat, 1 Mar 2025 17:04:03 +0200 Subject: [PATCH 02/23] When we deploy the contract it set the owner to be the entity that deploys the contract --- src/CollateralizedLoan.sol | 11 +++++++++++ test/CollateralizedLoanTest.t.sol | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/CollateralizedLoan.sol create mode 100644 test/CollateralizedLoanTest.t.sol diff --git a/src/CollateralizedLoan.sol b/src/CollateralizedLoan.sol new file mode 100644 index 0000000..34062c7 --- /dev/null +++ b/src/CollateralizedLoan.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +// The borrowed asset is going to be Ether. +// The collateral asset is going to be an ERC-20 token. +// +contract CollateralizedLoan is Ownable { + constructor(address initialOwner) Ownable(initialOwner) {} +} diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol new file mode 100644 index 0000000..c576e8d --- /dev/null +++ b/test/CollateralizedLoanTest.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {CollateralizedLoan} from "../src/CollateralizedLoan.sol"; +import {Test, console} from "forge-std/Test.sol"; + +contract CollateralizedLoanTest is Test { + // ------- + // Ownable + // ------- + function test_ownerIsTheDeployer() public { + CollateralizedLoan load = new CollateralizedLoan(msg.sender); + + assertEq(address(load.owner()), address(msg.sender)); + } +} From 735a686f30c67e3ddeffba902802d300a6f3ba5a Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sat, 1 Mar 2025 17:22:22 +0200 Subject: [PATCH 03/23] When deploying we set the ERC20 collateralToken --- src/CollateralizedLoan.sol | 26 +++++++++++++++++++++++++- test/CollateralizedLoanTest.t.sol | 24 +++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/CollateralizedLoan.sol b/src/CollateralizedLoan.sol index 34062c7..29e2412 100644 --- a/src/CollateralizedLoan.sol +++ b/src/CollateralizedLoan.sol @@ -2,10 +2,34 @@ 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. // contract CollateralizedLoan is Ownable { - constructor(address initialOwner) Ownable(initialOwner) {} + 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 public collateralToken; + + constructor( + address initialOwner, + IERC20 _collateralToken + ) Ownable(initialOwner) { + collateralToken = IERC20(_collateralToken); + } } diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index c576e8d..17d63ae 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -3,14 +3,36 @@ 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 {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract CollateralizedLoanTest is Test { // ------- // Ownable // ------- function test_ownerIsTheDeployer() public { - CollateralizedLoan load = new CollateralizedLoan(msg.sender); + IERC20 collateralToken = new ERC20Mock(); + + CollateralizedLoan load = new CollateralizedLoan( + msg.sender, + collateralToken + ); assertEq(address(load.owner()), address(msg.sender)); } + + // ---------------- + // collateralToken + // ---------------- + function test_onDeploymentWeSetPublicCollateralERC20Token() public { + address initialOwner = makeAddr("panos"); + IERC20 erc20Token = new ERC20Mock(); + + CollateralizedLoan loan = new CollateralizedLoan( + initialOwner, + erc20Token + ); + + assertEq(address(loan.collateralToken()), address(erc20Token)); + } } From 0ff51764f715ec2dd8d6bf1f10d1bb020a80db5a Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sat, 1 Mar 2025 17:22:57 +0200 Subject: [PATCH 04/23] Removed sample contract 'Counter' --- script/Counter.s.sol | 26 +++++++++++++------------- src/Counter.sol | 14 -------------- test/Counter.t.sol | 24 ------------------------ 3 files changed, 13 insertions(+), 51 deletions(-) delete mode 100644 src/Counter.sol delete mode 100644 test/Counter.t.sol diff --git a/script/Counter.s.sol b/script/Counter.s.sol index cdc1fe9..4ba102c 100644 --- a/script/Counter.s.sol +++ b/script/Counter.s.sol @@ -1,19 +1,19 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +// // SPDX-License-Identifier: UNLICENSED +// pragma solidity ^0.8.13; -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; +// import {Script, console} from "forge-std/Script.sol"; +// import {Counter} from "../src/Counter.sol"; -contract CounterScript is Script { - Counter public counter; +// contract CounterScript is Script { +// Counter public counter; - function setUp() public {} +// function setUp() public {} - function run() public { - vm.startBroadcast(); +// function run() public { +// vm.startBroadcast(); - counter = new Counter(); +// counter = new Counter(); - vm.stopBroadcast(); - } -} +// vm.stopBroadcast(); +// } +// } 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/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); - } -} From b32784ecdff4c896ce5cdad78458ea30f4e1d56b Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sat, 1 Mar 2025 17:29:59 +0200 Subject: [PATCH 05/23] Added 'remappings.txt'. Makes VS Code work better --- remappings.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 remappings.txt 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/ From 60d097bdbc620e367d9e8cefc3adef060d9af393 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sat, 1 Mar 2025 17:33:17 +0200 Subject: [PATCH 06/23] Use a 'setUp()' function to initialize state of the Test contract before each run --- test/CollateralizedLoanTest.t.sol | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index 17d63ae..ab8b026 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -7,32 +7,25 @@ import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract CollateralizedLoanTest is Test { + CollateralizedLoan loan; + IERC20 collateralToken; + + function setUp() public { + collateralToken = new ERC20Mock(); + loan = new CollateralizedLoan(msg.sender, collateralToken); + } + // ------- // Ownable // ------- - function test_ownerIsTheDeployer() public { - IERC20 collateralToken = new ERC20Mock(); - - CollateralizedLoan load = new CollateralizedLoan( - msg.sender, - collateralToken - ); - - assertEq(address(load.owner()), address(msg.sender)); + function test_ownerIsTheDeployer() public view { + assertEq(address(loan.owner()), address(msg.sender)); } // ---------------- // collateralToken // ---------------- - function test_onDeploymentWeSetPublicCollateralERC20Token() public { - address initialOwner = makeAddr("panos"); - IERC20 erc20Token = new ERC20Mock(); - - CollateralizedLoan loan = new CollateralizedLoan( - initialOwner, - erc20Token - ); - - assertEq(address(loan.collateralToken()), address(erc20Token)); + function test_onDeploymentWeSetPublicCollateralERC20Token() public view { + assertEq(address(loan.collateralToken()), address(collateralToken)); } } From 1b7a76d2d8d37cf568c69dccf494f143d8a3a0a3 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sat, 1 Mar 2025 17:46:14 +0200 Subject: [PATCH 07/23] Setting interest rate --- src/CollateralizedLoan.sol | 21 ++++++++++++++++----- test/CollateralizedLoanTest.t.sol | 20 ++++++++++++++++---- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/CollateralizedLoan.sol b/src/CollateralizedLoan.sol index 29e2412..4abdd17 100644 --- a/src/CollateralizedLoan.sol +++ b/src/CollateralizedLoan.sol @@ -24,12 +24,23 @@ contract CollateralizedLoan is Ownable { // address lender; } - IERC20 public collateralToken; + IERC20 private s_collateralToken; + uint256 private s_interestRate; // percentage, e.g. 20% constructor( - address initialOwner, - IERC20 _collateralToken - ) Ownable(initialOwner) { - collateralToken = IERC20(_collateralToken); + address _initialOwner, + IERC20 _collateralToken, + uint256 _interestRate + ) Ownable(_initialOwner) { + s_collateralToken = IERC20(_collateralToken); + s_interestRate = _interestRate; + } + + function collateralToken() public view returns (IERC20) { + return s_collateralToken; + } + + function interestRate() public view returns (uint256) { + return s_interestRate; } } diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index ab8b026..9575085 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -9,10 +9,15 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract CollateralizedLoanTest is Test { CollateralizedLoan loan; IERC20 collateralToken; + uint256 constant INTEREST_RATE = 20; // 20% function setUp() public { collateralToken = new ERC20Mock(); - loan = new CollateralizedLoan(msg.sender, collateralToken); + loan = new CollateralizedLoan( + msg.sender, + collateralToken, + INTEREST_RATE + ); } // ------- @@ -22,10 +27,17 @@ contract CollateralizedLoanTest is Test { assertEq(address(loan.owner()), address(msg.sender)); } - // ---------------- - // collateralToken - // ---------------- + // ------------------ + // collateralToken() + // ------------------ function test_onDeploymentWeSetPublicCollateralERC20Token() public view { assertEq(address(loan.collateralToken()), address(collateralToken)); } + + // ------------- + // interestRate() + // ------------- + function test_interestRate_returnsTheInterestRate() public view { + assertEq(loan.interestRate(), INTEREST_RATE); + } } From 338c5c04c57d8b28476965f19145ff8bf212ae72 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 08:57:25 +0200 Subject: [PATCH 08/23] First test for requestLoan --- src/CollateralizedLoan.sol | 167 +++++++++++++++++++++++++++++- test/CollateralizedLoanTest.t.sol | 84 +++++++++++++-- 2 files changed, 238 insertions(+), 13 deletions(-) diff --git a/src/CollateralizedLoan.sol b/src/CollateralizedLoan.sol index 4abdd17..7d6ce91 100644 --- a/src/CollateralizedLoan.sol +++ b/src/CollateralizedLoan.sol @@ -6,6 +6,7 @@ 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 { @@ -24,18 +25,72 @@ contract CollateralizedLoan is Ownable { // address lender; } - IERC20 private s_collateralToken; - uint256 private s_interestRate; // percentage, e.g. 20% + 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 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 _interestRate, + uint256 _minCollateralizationRatio ) Ownable(_initialOwner) { s_collateralToken = IERC20(_collateralToken); s_interestRate = _interestRate; + s_minCollateralizationRatio = _minCollateralizationRatio; } + // ---------------------------------------------------------- + // Public View Functions + // ---------------------------------------------------------- function collateralToken() public view returns (IERC20) { return s_collateralToken; } @@ -43,4 +98,110 @@ contract CollateralizedLoan is Ownable { 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); + } + + // ---------------------------------------------------------- + // 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 + // But has the +msg.sender+ approved the +CollateralizedLoan+ to + // get money/collateralToken from +msg.sender+ and put it to 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/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index 9575085..510244d 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -4,19 +4,29 @@ 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 {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract CollateralizedLoanTest is Test { - CollateralizedLoan loan; - IERC20 collateralToken; + CollateralizedLoan s_loan; + ERC20Mock s_collateralToken; uint256 constant INTEREST_RATE = 20; // 20% + uint256 constant MIN_COLLATERALIZATION_RATIO = 10; // ? function setUp() public { - collateralToken = new ERC20Mock(); - loan = new CollateralizedLoan( + console.log( + "CollateralizedLoanTest#setUp(): msg.sender = ", + msg.sender + ); + console.log( + "CollateralizedLoanTest#setUp(): address(this) = ", + address(this) + ); + + s_collateralToken = new ERC20Mock(); + s_loan = new CollateralizedLoan( msg.sender, - collateralToken, - INTEREST_RATE + s_collateralToken, + INTEREST_RATE, + MIN_COLLATERALIZATION_RATIO ); } @@ -24,20 +34,74 @@ contract CollateralizedLoanTest is Test { // Ownable // ------- function test_ownerIsTheDeployer() public view { - assertEq(address(loan.owner()), address(msg.sender)); + assertEq(address(s_loan.owner()), address(msg.sender)); } // ------------------ // collateralToken() // ------------------ function test_onDeploymentWeSetPublicCollateralERC20Token() public view { - assertEq(address(loan.collateralToken()), address(collateralToken)); + assertEq(address(s_loan.collateralToken()), address(s_collateralToken)); } // ------------- // interestRate() // ------------- function test_interestRate_returnsTheInterestRate() public view { - assertEq(loan.interestRate(), INTEREST_RATE); + 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); + } + + // ----------------------------- + // requestLoan() + // ----------------------------- + function test_requestLoan_whenBorrowerHasUnpaidLoan_itReverts() public { + // TODO: Maybe I could use +beforeTestSetup+ to set up + // some transaction state? + address borrower = makeAddr("panos"); + uint256 borrowedAmount = 10 ether; + uint256 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); + + // ---- + // fire + // ---- + + vm.prank(borrower); + vm.expectRevert( + abi.encodeWithSelector( + CollateralizedLoan.BorrowerHasUnpaidLoanError.selector, + borrower, + borrowedAmount, + block.timestamp + ) + ); + s_loan.requestLoan(borrowedAmount, collateralAmount); } } From b883700afddc24a892f881dee28f51f42197ebb9 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 09:11:26 +0200 Subject: [PATCH 09/23] Use a function to make borrower have an active loan --- test/CollateralizedLoanTest.t.sol | 49 +++++++++++++++++++------------ 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index 510244d..197b194 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -68,26 +68,11 @@ contract CollateralizedLoanTest is Test { // requestLoan() // ----------------------------- function test_requestLoan_whenBorrowerHasUnpaidLoan_itReverts() public { - // TODO: Maybe I could use +beforeTestSetup+ to set up - // some transaction state? address borrower = makeAddr("panos"); - uint256 borrowedAmount = 10 ether; - uint256 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); + ( + uint256 borrowedAmount, + uint256 collateralAmount + ) = borrowerHasActiveLoan(borrower); // ---- // fire @@ -104,4 +89,30 @@ contract CollateralizedLoanTest is Test { ); s_loan.requestLoan(borrowedAmount, collateralAmount); } + + // ----------------------------- + // 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); + } } From 7a8c459e32f94483a3f3da93f455bfa6acb0735a Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 09:12:50 +0200 Subject: [PATCH 10/23] Forge fmt --- src/CollateralizedLoan.sol | 84 ++++++++----------------------- test/CollateralizedLoanTest.t.sol | 38 ++++---------- 2 files changed, 32 insertions(+), 90 deletions(-) diff --git a/src/CollateralizedLoan.sol b/src/CollateralizedLoan.sol index 7d6ce91..208ca85 100644 --- a/src/CollateralizedLoan.sol +++ b/src/CollateralizedLoan.sol @@ -15,15 +15,15 @@ contract CollateralizedLoan is Ownable { uint256 collateralAmount; uint256 requestedAt; bool paid; - // uint256 amount; - // uint256 collateral; - // uint256 interest; - // uint256 duration; - // uint256 start; - // uint256 end; - // address borrower; - // address lender; } + // 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 @@ -34,11 +34,7 @@ contract CollateralizedLoan is Ownable { // -------------------------------------- // Events // -------------------------------------- - event LoanGranted( - address borrower, - uint256 borrowedAmount, - uint256 collateralAmount - ); + event LoanGranted(address borrower, uint256 borrowedAmount, uint256 collateralAmount); event LoanRepaid(address borrower, uint256 repaidAmount); //----------------------------------------------------------------------- @@ -53,28 +49,13 @@ contract CollateralizedLoan is Ownable { uint256 collateralProvided ); - error LenderDoesNotHaveEnoughEtherError( - uint256 amountRequested, - uint256 lenderBalance - ); + error LenderDoesNotHaveEnoughEtherError(uint256 amountRequested, uint256 lenderBalance); - error BorrowerDoesNotHaveEnoughCollateralError( - address borrower, - uint256 amountRequired, - uint256 borrowerBalance - ); + error BorrowerDoesNotHaveEnoughCollateralError(address borrower, uint256 amountRequired, uint256 borrowerBalance); - error BorrowerHasUnpaidLoanError( - address borrower, - uint256 borrowedAmount, - uint256 requestedAt - ); + error BorrowerHasUnpaidLoanError(address borrower, uint256 borrowedAmount, uint256 requestedAt); - error SendingEtherFailedError( - address sender, - address recipient, - uint256 value - ); + error SendingEtherFailedError(address sender, address recipient, uint256 value); // ---------------------------------------------------- constructor( @@ -99,11 +80,8 @@ contract CollateralizedLoan is Ownable { return s_interestRate; } - function minimumCollateralRequired( - uint256 _borrowedAmount - ) public view returns (uint256) { - uint256 l_extraAmountToLiquidate = (_borrowedAmount * - s_minCollateralizationRatio) / 100; + function minimumCollateralRequired(uint256 _borrowedAmount) public view returns (uint256) { + uint256 l_extraAmountToLiquidate = (_borrowedAmount * s_minCollateralizationRatio) / 100; return (_borrowedAmount + l_extraAmountToLiquidate); } @@ -119,10 +97,7 @@ contract CollateralizedLoan is Ownable { // send. It is equal to the +_borrowedAmount+ increased by // its +s_minCollateralizationRatio+. // - function requestLoan( - uint256 _borrowedAmount, - uint256 _collateralAmount - ) external { + function requestLoan(uint256 _borrowedAmount, uint256 _collateralAmount) external { LoanInfo storage loanInfo = s_loans[msg.sender]; if (loanInfo.borrowedAmount > 0 && !loanInfo.paid) { revert BorrowerHasUnpaidLoanError({ @@ -132,9 +107,7 @@ contract CollateralizedLoan is Ownable { }); } - uint256 l_minimumCollateralRequired = minimumCollateralRequired( - _borrowedAmount - ); + uint256 l_minimumCollateralRequired = minimumCollateralRequired(_borrowedAmount); if (_collateralAmount < l_minimumCollateralRequired) { revert NotEnoughCollateralProvidedForBorrowedAmountError({ @@ -152,9 +125,7 @@ contract CollateralizedLoan is Ownable { }); } - uint256 borrowerCollateralBalance = s_collateralToken.balanceOf( - msg.sender - ); + uint256 borrowerCollateralBalance = s_collateralToken.balanceOf(msg.sender); if (borrowerCollateralBalance < _collateralAmount) { revert BorrowerDoesNotHaveEnoughCollateralError({ @@ -168,11 +139,7 @@ contract CollateralizedLoan is Ownable { // But has the +msg.sender+ approved the +CollateralizedLoan+ to // get money/collateralToken from +msg.sender+ and put it to itself? // - s_collateralToken.transferFrom( - msg.sender, - address(this), - _collateralAmount - ); + s_collateralToken.transferFrom(msg.sender, address(this), _collateralAmount); _sendEthersTo(payable(msg.sender), _borrowedAmount); @@ -191,17 +158,10 @@ contract CollateralizedLoan is Ownable { // private // ------- - function _sendEthersTo( - address payable _recipient, - uint256 _amount - ) private { - (bool sent, ) = _recipient.call{value: _amount}(""); + 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 - }); + revert SendingEtherFailedError({sender: address(this), recipient: _recipient, value: _amount}); } } } diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index 197b194..e364fa7 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -12,22 +12,11 @@ contract CollateralizedLoanTest is Test { 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) - ); + console.log("CollateralizedLoanTest#setUp(): msg.sender = ", msg.sender); + console.log("CollateralizedLoanTest#setUp(): address(this) = ", address(this)); s_collateralToken = new ERC20Mock(); - s_loan = new CollateralizedLoan( - msg.sender, - s_collateralToken, - INTEREST_RATE, - MIN_COLLATERALIZATION_RATIO - ); + s_loan = new CollateralizedLoan(msg.sender, s_collateralToken, INTEREST_RATE, MIN_COLLATERALIZATION_RATIO); } // ------- @@ -57,9 +46,7 @@ contract CollateralizedLoanTest is Test { function test_returnsTheMinimumCollateralRequired() public view { uint256 borrowedAmount = 100 ether; uint256 result = s_loan.minimumCollateralRequired(borrowedAmount); - uint256 expectedResult = borrowedAmount + - (borrowedAmount * MIN_COLLATERALIZATION_RATIO) / - 100; + uint256 expectedResult = borrowedAmount + (borrowedAmount * MIN_COLLATERALIZATION_RATIO) / 100; assertEq(result, expectedResult); } @@ -69,10 +56,7 @@ contract CollateralizedLoanTest is Test { // ----------------------------- function test_requestLoan_whenBorrowerHasUnpaidLoan_itReverts() public { address borrower = makeAddr("panos"); - ( - uint256 borrowedAmount, - uint256 collateralAmount - ) = borrowerHasActiveLoan(borrower); + (uint256 borrowedAmount, uint256 collateralAmount) = borrowerHasActiveLoan(borrower); // ---- // fire @@ -81,10 +65,7 @@ contract CollateralizedLoanTest is Test { vm.prank(borrower); vm.expectRevert( abi.encodeWithSelector( - CollateralizedLoan.BorrowerHasUnpaidLoanError.selector, - borrower, - borrowedAmount, - block.timestamp + CollateralizedLoan.BorrowerHasUnpaidLoanError.selector, borrower, borrowedAmount, block.timestamp ) ); s_loan.requestLoan(borrowedAmount, collateralAmount); @@ -94,9 +75,10 @@ contract CollateralizedLoanTest is Test { // private utility functions // ----------------------------- - function borrowerHasActiveLoan( - address _borrower - ) private returns (uint256 _borrowedAmount, uint256 _collateralAmount) { + function borrowerHasActiveLoan(address _borrower) + private + returns (uint256 _borrowedAmount, uint256 _collateralAmount) + { _borrowedAmount = 10 ether; _collateralAmount = 30 ether; From e5886f5a2ba1f7757b8545a105fac34ef15d005f Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 09:19:05 +0200 Subject: [PATCH 11/23] Tune the github workflow actions --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 From 70132ec4f35e3c434c52f9245a54a1250ac3be3b Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 09:58:13 +0200 Subject: [PATCH 12/23] Test for NotEnoughCollateralProvidedForBorrowedAmountError --- test/CollateralizedLoanTest.t.sol | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index e364fa7..df94566 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -58,9 +58,7 @@ contract CollateralizedLoanTest is Test { address borrower = makeAddr("panos"); (uint256 borrowedAmount, uint256 collateralAmount) = borrowerHasActiveLoan(borrower); - // ---- // fire - // ---- vm.prank(borrower); vm.expectRevert( @@ -71,6 +69,28 @@ contract CollateralizedLoanTest is Test { 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); + } + // ----------------------------- // private utility functions // ----------------------------- From 884a11c917eead61af2ea98ff5ccc25386796999 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 10:02:00 +0200 Subject: [PATCH 13/23] Test for LenderDoesNotHaveEnoughEtherError --- test/CollateralizedLoanTest.t.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index df94566..01673ff 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -91,6 +91,19 @@ contract CollateralizedLoanTest is Test { 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; + + // fire + vm.prank(borrower); + vm.expectRevert( + abi.encodeWithSelector(CollateralizedLoan.LenderDoesNotHaveEnoughEtherError.selector, borrowedAmount, 0) + ); + s_loan.requestLoan(borrowedAmount, collateralAmount); + } + // ----------------------------- // private utility functions // ----------------------------- From e929af3753a8b448851bd48e9850d9dac069e71e Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 10:08:10 +0200 Subject: [PATCH 14/23] Test for BorrowerDoesNotHaveEnoughCollateralError --- test/CollateralizedLoanTest.t.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index 01673ff..7768548 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -104,6 +104,22 @@ contract CollateralizedLoanTest is Test { 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); + } + // ----------------------------- // private utility functions // ----------------------------- From 151e9b2cc58e8f56e074ab137f7c79d31ac97c87 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 11:59:02 +0200 Subject: [PATCH 15/23] Finished testing the requestLoan --- src/CollateralizedLoan.sol | 10 +++-- test/CollateralizedLoanTest.t.sol | 73 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/CollateralizedLoan.sol b/src/CollateralizedLoan.sol index 208ca85..e9e3635 100644 --- a/src/CollateralizedLoan.sol +++ b/src/CollateralizedLoan.sol @@ -34,7 +34,7 @@ contract CollateralizedLoan is Ownable { // -------------------------------------- // Events // -------------------------------------- - event LoanGranted(address borrower, uint256 borrowedAmount, uint256 collateralAmount); + event LoanGranted(address indexed borrower, uint256 borrowedAmount, uint256 collateralAmount); event LoanRepaid(address borrower, uint256 repaidAmount); //----------------------------------------------------------------------- @@ -86,6 +86,10 @@ contract CollateralizedLoan is Ownable { return (_borrowedAmount + l_extraAmountToLiquidate); } + function myLoanInfo() public view returns (LoanInfo memory) { + return s_loans[msg.sender]; + } + // ---------------------------------------------------------- // External Transactions // ---------------------------------------------------------- @@ -136,8 +140,8 @@ contract CollateralizedLoan is Ownable { } // we take from the borrower and we move Collateral to the contract - // But has the +msg.sender+ approved the +CollateralizedLoan+ to - // get money/collateralToken from +msg.sender+ and put it to itself? + // 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); diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index 7768548..c417556 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -4,6 +4,9 @@ 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; @@ -51,6 +54,10 @@ contract CollateralizedLoanTest is Test { assertEq(result, expectedResult); } + // ----------------------------- + // myLoanInfo() + // ----------------------------- + // ----------------------------- // requestLoan() // ----------------------------- @@ -120,6 +127,72 @@ contract CollateralizedLoanTest is Test { 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 // ----------------------------- From 11f3fe34c2174bba267cc12c1b9eb9351cdf45d7 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 12:03:58 +0200 Subject: [PATCH 16/23] Tests for #myLoanInfo() --- test/CollateralizedLoanTest.t.sol | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index c417556..f45bb1b 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -57,6 +57,40 @@ contract CollateralizedLoanTest is Test { // ----------------------------- // 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() From 149a32b3d0bb1f2756e8e3e0a4d5974fdd5da4f1 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 17:54:47 +0200 Subject: [PATCH 17/23] [doc] - About the asdf version --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4bc5896..dbc6c81 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`. From 09914d32385b96e2be30798bd0f6bb45332ba5fe Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 18:35:38 +0200 Subject: [PATCH 18/23] Some bash scripts that use 'forge create' to deploy locally --- README.md | 31 +++++++++++++++++++++++++++++++ deploy_collateralized_loan.sh | 15 +++++++++++++++ deploy_ercmock.sh | 10 ++++++++++ 3 files changed, 56 insertions(+) create mode 100755 deploy_collateralized_loan.sh create mode 100755 deploy_ercmock.sh diff --git a/README.md b/README.md index dbc6c81..2c1eaea 100644 --- a/README.md +++ b/README.md @@ -48,3 +48,34 @@ Then I restarted my shell and used the command: ``` foundryup --install stable ``` + +## Deployment and Verification of Smart Contract(s) + +Start a local chain: + +```bash +$ anvil +... +Listening 127.0.0.1:8545 + +``` + +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 +$ ./deploy_ercmock.sh 'http://127.0.0.1:8545' '0xac..a...private key....80' +``` + +### Step 2 + +Deploy the Collateralized Loan contract: + +```bash +$ ./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 +``` diff --git a/deploy_collateralized_loan.sh b/deploy_collateralized_loan.sh new file mode 100755 index 0000000..7b114f2 --- /dev/null +++ b/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/deploy_ercmock.sh b/deploy_ercmock.sh new file mode 100755 index 0000000..4aa9068 --- /dev/null +++ b/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 From 1531c89e6c182ac27a9a9a3b6ac9e293c0b6626a Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 2 Mar 2025 18:48:29 +0200 Subject: [PATCH 19/23] Deploy to local anvil using 'forge script' --- README.md | 16 ++++++--- ...oan.sh => fc_deploy_collateralized_loan.sh | 0 deploy_ercmock.sh => fc_deploy_ercmock.sh | 0 fs_deploy_collateralized_loan.sh | 10 ++++++ script/CollateralizedLoan.s.sol | 33 +++++++++++++++++++ script/Counter.s.sol | 19 ----------- 6 files changed, 55 insertions(+), 23 deletions(-) rename deploy_collateralized_loan.sh => fc_deploy_collateralized_loan.sh (100%) rename deploy_ercmock.sh => fc_deploy_ercmock.sh (100%) create mode 100755 fs_deploy_collateralized_loan.sh create mode 100644 script/CollateralizedLoan.s.sol delete mode 100644 script/Counter.s.sol diff --git a/README.md b/README.md index 2c1eaea..533fcbc 100644 --- a/README.md +++ b/README.md @@ -60,22 +60,30 @@ 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 +#### Step 1 Deploy the ERCMock contract. Note down its address: ```bash -$ ./deploy_ercmock.sh 'http://127.0.0.1:8545' '0xac..a...private key....80' +$ ./fc_deploy_ercmock.sh 'http://127.0.0.1:8545' '0xac..a...private key....80' ``` -### Step 2 +#### Step 2 Deploy the Collateralized Loan contract: ```bash -$ ./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 +$ ./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' '0xac0 private key ae784d7bf4f2ff80' ``` diff --git a/deploy_collateralized_loan.sh b/fc_deploy_collateralized_loan.sh similarity index 100% rename from deploy_collateralized_loan.sh rename to fc_deploy_collateralized_loan.sh diff --git a/deploy_ercmock.sh b/fc_deploy_ercmock.sh similarity index 100% rename from deploy_ercmock.sh rename to fc_deploy_ercmock.sh diff --git a/fs_deploy_collateralized_loan.sh b/fs_deploy_collateralized_loan.sh new file mode 100755 index 0000000..b8a7fd2 --- /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 +PRIVATE_KEY=$2 + +forge script script/CollateralizedLoan.s.sol --broadcast \ + --rpc-url "${RPC_URL}" --private-key "${PRIVATE_KEY}" diff --git a/script/CollateralizedLoan.s.sol b/script/CollateralizedLoan.s.sol new file mode 100644 index 0000000..306a0ec --- /dev/null +++ b/script/CollateralizedLoan.s.sol @@ -0,0 +1,33 @@ +// 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(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 4ba102c..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(); -// } -// } From 46b7892c7b876b81b8584641fc16caacc242831a Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Mon, 3 Mar 2025 08:08:21 +0200 Subject: [PATCH 20/23] Examples of deploying and running the contract locally --- README.md | 32 +++++++++++++++++++++++++++++-- fs_deploy_collateralized_loan.sh | 4 ++-- fs_request_loan.sh | 14 ++++++++++++++ script/CollateralizedLoan.s.sol | 4 +++- script/ExampleRequestLoan.s.sol | 31 ++++++++++++++++++++++++++++++ src/CollateralizedLoan.sol | 2 +- test/CollateralizedLoanTest.t.sol | 12 ++++++++++-- 7 files changed, 91 insertions(+), 8 deletions(-) create mode 100755 fs_request_loan.sh create mode 100644 script/ExampleRequestLoan.s.sol diff --git a/README.md b/README.md index 533fcbc..f4d115a 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Then I restarted my shell and used the command: foundryup --install stable ``` -## Deployment and Verification of Smart Contract(s) +## Deployment of Smart Contract(s) Start a local chain: @@ -85,5 +85,33 @@ $ ./fc_deploy_collateralized_loan.sh 'http://127.0.0.1:8545' '0xac..a...private ### Using `forge script` ```bash -$ ./fs_deploy_collateralized_loan.sh 'http://127.0.0.1:8545' '0xac0 private key ae784d7bf4f2ff80' +$ ./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/fs_deploy_collateralized_loan.sh b/fs_deploy_collateralized_loan.sh index b8a7fd2..58812b1 100755 --- a/fs_deploy_collateralized_loan.sh +++ b/fs_deploy_collateralized_loan.sh @@ -4,7 +4,7 @@ set -euox pipefail shopt -s globstar RPC_URL=$1 -PRIVATE_KEY=$2 +KEYSTORE=$2 forge script script/CollateralizedLoan.s.sol --broadcast \ - --rpc-url "${RPC_URL}" --private-key "${PRIVATE_KEY}" + --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/script/CollateralizedLoan.s.sol b/script/CollateralizedLoan.s.sol index 306a0ec..40323c1 100644 --- a/script/CollateralizedLoan.s.sol +++ b/script/CollateralizedLoan.s.sol @@ -17,7 +17,9 @@ contract CollateralizedLoanScript is Script { uint256 interestRate = 20; uint256 minCollateralizationRatio = 5; - new CollateralizedLoan(initialOwner, collateralToken, interestRate, minCollateralizationRatio); + new CollateralizedLoan{value: 100 ether}( + initialOwner, collateralToken, interestRate, minCollateralizationRatio + ); vm.stopBroadcast(); } diff --git a/script/ExampleRequestLoan.s.sol b/script/ExampleRequestLoan.s.sol new file mode 100644 index 0000000..f1b538d --- /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/CollateralizedLoan.sol b/src/CollateralizedLoan.sol index e9e3635..9525b41 100644 --- a/src/CollateralizedLoan.sol +++ b/src/CollateralizedLoan.sol @@ -63,7 +63,7 @@ contract CollateralizedLoan is Ownable { IERC20 _collateralToken, uint256 _interestRate, uint256 _minCollateralizationRatio - ) Ownable(_initialOwner) { + ) payable Ownable(_initialOwner) { s_collateralToken = IERC20(_collateralToken); s_interestRate = _interestRate; s_minCollateralizationRatio = _minCollateralizationRatio; diff --git a/test/CollateralizedLoanTest.t.sol b/test/CollateralizedLoanTest.t.sol index f45bb1b..fd04e8e 100644 --- a/test/CollateralizedLoanTest.t.sol +++ b/test/CollateralizedLoanTest.t.sol @@ -19,7 +19,9 @@ contract CollateralizedLoanTest is Test { console.log("CollateralizedLoanTest#setUp(): address(this) = ", address(this)); s_collateralToken = new ERC20Mock(); - s_loan = new CollateralizedLoan(msg.sender, s_collateralToken, INTEREST_RATE, MIN_COLLATERALIZATION_RATIO); + s_loan = new CollateralizedLoan{value: 100 ether}( + msg.sender, s_collateralToken, INTEREST_RATE, MIN_COLLATERALIZATION_RATIO + ); } // ------- @@ -137,10 +139,16 @@ contract CollateralizedLoanTest is Test { 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, 0) + abi.encodeWithSelector( + CollateralizedLoan.LenderDoesNotHaveEnoughEtherError.selector, borrowedAmount, borrowedAmount - 1 + ) ); s_loan.requestLoan(borrowedAmount, collateralAmount); } From ead4fc4884b5b0beb0067988adcb05eb950d8a54 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 9 Mar 2025 09:22:48 +0200 Subject: [PATCH 21/23] Pin Python 3.12.3 --- .tool-versions | 1 + 1 file changed, 1 insertion(+) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..1d7a709 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +python 3.12.3 From ab4f31370cc0692fb2616492d26749899281f342 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 9 Mar 2025 11:01:29 +0200 Subject: [PATCH 22/23] Added CollateralCalculator --- src/CollateralCalculator.sol | 93 +++++++++++++++++++++++ src/ICollateralCalculator.sol | 4 + test/CollateralCalculatorTest.t.sol | 114 ++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 src/CollateralCalculator.sol create mode 100644 test/CollateralCalculatorTest.t.sol 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/ICollateralCalculator.sol b/src/ICollateralCalculator.sol index ed4f846..6c6c6d8 100644 --- a/src/ICollateralCalculator.sol +++ b/src/ICollateralCalculator.sol @@ -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/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)); + } +} From bfb230870f5b62a5e50f0d36093593174ced20f6 Mon Sep 17 00:00:00 2001 From: Panos Matsinopoulos Date: Sun, 9 Mar 2025 12:53:49 +0200 Subject: [PATCH 23/23] Trying to test the verifyProof --- .tool-versions | 1 + foundry.toml | 4 + script/ExampleRequestLoan.s.sol | 50 +- src/CreditVerifier.sol | 1533 +++++++++++++++++++++++++++++++ src/ICollateralCalculator.sol | 2 +- src/IZKCreditVerifier.sol | 4 +- test/CreditVerifierTest.t.sol | 29 + 7 files changed, 1595 insertions(+), 28 deletions(-) create mode 100644 src/CreditVerifier.sol create mode 100644 test/CreditVerifierTest.t.sol diff --git a/.tool-versions b/.tool-versions index 1d7a709..6d14caa 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,2 @@ python 3.12.3 +solidity 0.8.28 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/script/ExampleRequestLoan.s.sol b/script/ExampleRequestLoan.s.sol index f1b538d..2aeb976 100644 --- a/script/ExampleRequestLoan.s.sol +++ b/script/ExampleRequestLoan.s.sol @@ -1,31 +1,31 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.28; +// // SPDX-License-Identifier: UNLICENSED +// pragma solidity ^0.8.28; -import {Vm} from "lib/forge-std/src/Vm.sol"; +// 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"; +// 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"))))); +// 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); +// /// @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(); +// collateralToken.mint(borrower, minimumCollateral); +// vm.stopBroadcast(); - vm.startBroadcast(borrower); - collateralToken.approve(address(cl), minimumCollateral); - cl.requestLoan(30 ether, minimumCollateral); - vm.stopBroadcast(); - } -} +// vm.startBroadcast(borrower); +// collateralToken.approve(address(cl), minimumCollateral); +// cl.requestLoan(30 ether, minimumCollateral); +// vm.stopBroadcast(); +// } +// } 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 6c6c6d8..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 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/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"); + } +}