diff --git a/contracts/lending/ttoken/TToken_V1.sol b/contracts/lending/ttoken/TToken_V1.sol index 34bb8a0c6..402bd3a3e 100644 --- a/contracts/lending/ttoken/TToken_V1.sol +++ b/contracts/lending/ttoken/TToken_V1.sol @@ -11,9 +11,11 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol"; // Interfaces import { ITToken } from "./ITToken.sol"; +import { ICErc20 } from "../../shared/interfaces/ICErc20.sol"; // Libraries import { + IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { @@ -444,8 +446,10 @@ contract TToken_V1 is ITToken, ReentryMods { } /** - * @notice it retrieves the value in the underlying tokens - * + * @notice it retrives the value in the underlying tokens + * @param amount the amount of underlying + * @param rate the rate the underlying is exchanging at + * @return value_ the value of the underlying */ function _valueInUnderlying(uint256 amount, uint256 rate) internal diff --git a/contracts/lending/ttoken/storage.sol b/contracts/lending/ttoken/storage.sol new file mode 100644 index 000000000..a32045ea1 --- /dev/null +++ b/contracts/lending/ttoken/storage.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +struct Store { + ERC20 underlying; + address strategy; + uint256 totalBorrowed; + uint256 totalRepaid; + uint256 totalInterestRepaid; + uint8 decimals; + bool restricted; +} + +bytes32 constant POSITION = keccak256("ttoken.storage.position"); + +/** + * @notice it saves the Store struct in a hashed slot + */ +function store() pure returns (Store storage s_) { + bytes32 position = POSITION; + assembly { + s_.slot := position + } +} diff --git a/contracts/lending/ttoken/strategies/compound/storage.sol b/contracts/lending/ttoken/strategies/compound/storage.sol new file mode 100644 index 000000000..2cb50d6d5 --- /dev/null +++ b/contracts/lending/ttoken/strategies/compound/storage.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ICErc20 } from "../../../../shared/interfaces/ICErc20.sol"; + +struct Store { + ICErc20 cToken; + uint16 balanceRatioMax; + uint16 balanceRatioMin; +} + +bytes32 constant POSITION = keccak256( + "ttoken.strategy.compound.storage.position" +); + +function store() pure returns (Store storage s_) { + bytes32 position = POSITION; + assembly { + s_.slot := position + } +} diff --git a/contracts/lending/ttoken/token-storage.sol b/contracts/lending/ttoken/token-storage.sol index 8c94e4223..ee0963725 100644 --- a/contracts/lending/ttoken/token-storage.sol +++ b/contracts/lending/ttoken/token-storage.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { ERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; struct Store { // The underlying asset of the tToken diff --git a/contracts/market/CreateLoanFacet.sol b/contracts/market/CreateLoanFacet.sol index 0253ccfd0..61790f92f 100644 --- a/contracts/market/CreateLoanFacet.sol +++ b/contracts/market/CreateLoanFacet.sol @@ -7,13 +7,13 @@ import { ReentryMods } from "../contexts2/access-control/reentry/ReentryMods.sol"; import { RolesMods } from "../contexts2/access-control/roles/RolesMods.sol"; -import { AUTHORIZED } from "../shared/roles.sol"; +import { AUTHORIZED, ADMIN } from "../shared/roles.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; // Libraries import { LibLoans } from "./libraries/LibLoans.sol"; +import { LibEscrow } from "../escrow/libraries/LibEscrow.sol"; import { LibCollateral } from "./libraries/LibCollateral.sol"; -import { LibConsensus } from "./libraries/LibConsensus.sol"; import { LendingLib } from "../lending/libraries/LendingLib.sol"; import { PlatformSettingsLib @@ -30,6 +30,9 @@ import { } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import { NumbersLib } from "../shared/libraries/NumbersLib.sol"; import { NFTLib } from "../nft/libraries/NFTLib.sol"; +import { Verifier } from "./cra/verifier.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { ProcessRequestLib } from "./cra/ProcessRequestLib.sol"; // Interfaces import { ILoansEscrow } from "../escrow/escrow/ILoansEscrow.sol"; @@ -43,11 +46,13 @@ import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; // Storage import { - LoanRequest, + LoanRequestWithResponse, LoanStatus, LoanTerms, Loan, - MarketStorageLib + MarketStorageLib, + Signature, + DataProviderSignature } from "../storage/market.sol"; import { AppStorageLib } from "../storage/app.sol"; @@ -70,7 +75,10 @@ contract CreateLoanFacet is RolesMods, ReentryMods, PausableMods { * @notice Creates the loan from requests and validator responses then calling the main function. * @param request Struct of the protocol loan request */ - modifier __createLoan(LoanRequest calldata request, bool withNFT) { + modifier __createLoan( + LoanRequestWithResponse calldata request, + bool withNFT + ) { Loan storage loan = CreateLoanLib.createLoan(request, withNFT); _; @@ -86,7 +94,7 @@ contract CreateLoanFacet is RolesMods, ReentryMods, PausableMods { * @param nftIDs IDs of TellerNFTs to use for the loan */ function takeOutLoanWithNFTs( - LoanRequest calldata request, + LoanRequestWithResponse calldata request, uint16[] calldata nftIDs ) external paused(LibLoans.ID, false) __createLoan(request, true) { // Get the ID of the newly created loan @@ -133,7 +141,7 @@ contract CreateLoanFacet is RolesMods, ReentryMods, PausableMods { * @param collateralAmount Amount of collateral required for the loan */ function takeOutLoan( - LoanRequest calldata request, + LoanRequestWithResponse calldata request, address collateralToken, uint256 collateralAmount ) @@ -193,27 +201,25 @@ contract CreateLoanFacet is RolesMods, ReentryMods, PausableMods { } library CreateLoanLib { - function createLoan(LoanRequest calldata request, bool withNFT) + function createLoan(LoanRequestWithResponse calldata request, bool withNFT) internal returns (Loan storage loan) { // Perform loan request checks - require( - msg.sender == request.request.borrower, - "Teller: not loan requester" - ); require( PlatformSettingsLib.getMaximumLoanDurationValue() >= request.request.duration, "Teller: max loan duration exceeded" ); - // Get consensus values from request - ( - uint16 interestRate, - uint16 collateralRatio, - uint256 maxLoanAmount - ) = LibConsensus.processLoanTerms(request); + uint16 interestRate = 15000; + uint16 collateralRatio = 5000; + uint256 maxLoanAmount = 25000; + // ( + // uint16 interestRate, + // uint16 collateralRatio, + // uint256 maxLoanAmount + // ) = ProcessRequestLib.processMarketRequest(request); // Perform loan value checks require( @@ -229,6 +235,7 @@ library CreateLoanLib { // Get and increment new loan ID uint256 loanID = CreateLoanLib.newID(); + // Set loan data based on terms loan = LibLoans.loan(loanID); loan.id = uint128(loanID); @@ -261,23 +268,20 @@ library CreateLoanLib { * @return id_ the new ID requested, which stores it in the loan data */ function newID() internal returns (uint256 id_) { - Counters.Counter storage counter = MarketStorageLib.store() + Counters.Counter storage counter = MarketStorageLib + .store() .loanIDCounter; id_ = Counters.current(counter); Counters.increment(counter); } function currentID() internal view returns (uint256 id_) { - Counters.Counter storage counter = MarketStorageLib.store() + Counters.Counter storage counter = MarketStorageLib + .store() .loanIDCounter; id_ = Counters.current(counter); } - /** - * @notice it creates a new loan escrow contract - * @param loanID the ID that identifies the loan - * @return escrow_ the loanEscrow that gets created - */ function createEscrow(uint256 loanID) internal returns (address escrow_) { // Create escrow escrow_ = AppStorageLib.store().loansEscrowBeacon.cloneProxy(""); diff --git a/contracts/market/CreateLoanNFTFacet.sol b/contracts/market/CreateLoanNFTFacet.sol new file mode 100644 index 000000000..7e5e99116 --- /dev/null +++ b/contracts/market/CreateLoanNFTFacet.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Contracts +import { PausableMods } from "../settings/pausable/PausableMods.sol"; +import { + ReentryMods +} from "../contexts2/access-control/reentry/ReentryMods.sol"; +import { RolesMods } from "../contexts2/access-control/roles/RolesMods.sol"; +import { AUTHORIZED, ADMIN } from "../shared/roles.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// Libraries +import { LibLoans } from "./libraries/LibLoans.sol"; +import { LibEscrow } from "../escrow/libraries/LibEscrow.sol"; +import { LibCollateral } from "./libraries/LibCollateral.sol"; +import { LendingLib } from "../lending/libraries/LendingLib.sol"; +import { + PlatformSettingsLib +} from "../settings/platform/libraries/PlatformSettingsLib.sol"; +import { + MaxDebtRatioLib +} from "../settings/asset/libraries/MaxDebtRatioLib.sol"; +import { + MaxLoanAmountLib +} from "../settings/asset/libraries/MaxLoanAmountLib.sol"; +import { Counters } from "@openzeppelin/contracts/utils/Counters.sol"; +import { + EnumerableSet +} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { NumbersLib } from "../shared/libraries/NumbersLib.sol"; +import { NFTLib } from "../nft/libraries/NFTLib.sol"; +import { Verifier } from "./cra/verifier.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { ProcessRequestLib } from "./cra/ProcessRequestLib.sol"; + +// Interfaces +import { ILoansEscrow } from "../escrow/escrow/ILoansEscrow.sol"; +import { ITToken } from "../lending/ttoken/ITToken.sol"; + +// Proxy +import { + BeaconProxy +} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; +import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; + +// Storage +import { + LoanRequestNFT, + LoanStatus, + LoanTerms, + Loan, + MarketStorageLib, + Signature, + DataProviderSignature +} from "../storage/market.sol"; +import { AppStorageLib } from "../storage/app.sol"; + +contract CreateLoanNFTFacet is RolesMods, ReentryMods, PausableMods { + /** + * @notice This event is emitted when a loan has been successfully taken out + * @param loanID ID of loan from which collateral was withdrawn + * @param borrower Account address of the borrower + * @param amountBorrowed Total amount taken out in the loan + * @param withNFT Boolean indicating if the loan was taken out using NFTs + */ + event LoanTakenOutNFT( + uint256 indexed loanID, + address indexed borrower, + uint256 amountBorrowed, + bool withNFT + ); + + function takeOutLoanNFTs( + LoanRequestNFT calldata request, + uint16[] calldata nftIDs + ) external paused(LibLoans.ID, false) { + // create loan from library + Loan storage loan = CreateLoanNFTLib.createLoanNFT(request); + + // Create loan + uint256 loanID = CreateLoanNFTLib.currentID() - 1; + uint256 amount = LibLoans.loan(loanID).borrowedAmount; + uint8 lendingDecimals = ERC20(request.assetAddress).decimals(); + uint256 allowedBaseLoanSize; + for (uint256 i; i < nftIDs.length; i++) { + NFTLib.applyToLoan(loanID, nftIDs[i]); + + allowedBaseLoanSize += NFTLib.s().nftDictionary.tokenBaseLoanSize( + nftIDs[i] + ); + } + require( + amount <= allowedBaseLoanSize * (10**lendingDecimals), + "Teller: insufficient NFT loan size" + ); + + // Pull funds from Teller Token LP and transfer to the new loan escrow + LendingLib.tToken(LibLoans.loan(loanID).lendingToken).fundLoan( + CreateLoanNFTLib.createEscrow(loanID), + amount + ); + + emit LoanTakenOutNFT( + loanID, + msg.sender, + LibLoans.loan(loanID).borrowedAmount, + true + ); + + // set to active + loan.status = LoanStatus.Active; + loan.loanStartTime = uint32(block.timestamp); + loan.duration = request.duration; + } +} + +library CreateLoanNFTLib { + function createLoanNFT(LoanRequestNFT calldata request) + internal + returns (Loan storage loan) + { + // Perform loan request checks + require( + PlatformSettingsLib.getMaximumLoanDurationValue() >= + request.duration, + "Teller: max loan duration exceeded" + ); + + // tier 3 example + uint256 maxLoanAmount = 25000; + + // Perform loan value checks + require( + MaxLoanAmountLib.get(request.assetAddress) > maxLoanAmount, + "Teller: asset max loan amount exceeded" + ); + require( + LendingLib.tToken(request.assetAddress).debtRatioFor( + maxLoanAmount + ) <= MaxDebtRatioLib.get(request.assetAddress), + "Teller: max supply-to-debt ratio exceeded" + ); + + // Get and increment new loan ID + uint256 loanID = CreateLoanNFTLib.newID(); + + // Set loan data based on terms + loan = LibLoans.loan(loanID); + loan.id = uint128(loanID); + loan.status = LoanStatus.TermsSet; + loan.lendingToken = request.assetAddress; + loan.borrower = request.borrower; + loan.borrowedAmount = maxLoanAmount; + + // since we are creating a loan with nft, we get the nft interest rate from + // our platform settings + loan.interestRate = PlatformSettingsLib.getNFTInterestRate(); + + // Set loan debt + LibLoans.debt(loanID).principalOwed = maxLoanAmount; + LibLoans.debt(loanID).interestOwed = LibLoans.getInterestOwedFor( + uint256(loanID), + maxLoanAmount + ); + + // Add loanID to borrower list + MarketStorageLib.store().borrowerLoans[loan.borrower].push( + uint128(loanID) + ); + } + + /** + * @notice increments the loanIDCounter + * @return id_ the new ID requested, which stores it in the loan data + */ + function newID() internal returns (uint256 id_) { + Counters.Counter storage counter = MarketStorageLib + .store() + .loanIDCounter; + id_ = Counters.current(counter); + Counters.increment(counter); + } + + function currentID() internal view returns (uint256 id_) { + Counters.Counter storage counter = MarketStorageLib + .store() + .loanIDCounter; + id_ = Counters.current(counter); + } + + function createEscrow(uint256 loanID) internal returns (address escrow_) { + // Create escrow + escrow_ = AppStorageLib.store().loansEscrowBeacon.cloneProxy(""); + ILoansEscrow(escrow_).init(); + // Save escrow address for loan + MarketStorageLib.store().loanEscrows[loanID] = ILoansEscrow(escrow_); + } +} diff --git a/contracts/market/CreateLoanSnarkFacet.sol b/contracts/market/CreateLoanSnarkFacet.sol new file mode 100644 index 000000000..58c192246 --- /dev/null +++ b/contracts/market/CreateLoanSnarkFacet.sol @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Contracts +import { PausableMods } from "../settings/pausable/PausableMods.sol"; +import { + ReentryMods +} from "../contexts2/access-control/reentry/ReentryMods.sol"; +import { RolesMods } from "../contexts2/access-control/roles/RolesMods.sol"; +import { AUTHORIZED, ADMIN } from "../shared/roles.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// Libraries +import { LibLoans } from "./libraries/LibLoans.sol"; +import { LibEscrow } from "../escrow/libraries/LibEscrow.sol"; +import { LibCollateral } from "./libraries/LibCollateral.sol"; +import { LendingLib } from "../lending/libraries/LendingLib.sol"; +import { + PlatformSettingsLib +} from "../settings/platform/libraries/PlatformSettingsLib.sol"; +import { + MaxDebtRatioLib +} from "../settings/asset/libraries/MaxDebtRatioLib.sol"; +import { + MaxLoanAmountLib +} from "../settings/asset/libraries/MaxLoanAmountLib.sol"; +import { Counters } from "@openzeppelin/contracts/utils/Counters.sol"; +import { + EnumerableSet +} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { NumbersLib } from "../shared/libraries/NumbersLib.sol"; +import { NFTLib } from "../nft/libraries/NFTLib.sol"; +import { Verifier } from "./cra/verifier.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { ProcessRequestLib } from "./cra/ProcessRequestLib.sol"; + +// Interfaces +import { ILoansEscrow } from "../escrow/escrow/ILoansEscrow.sol"; +import { ITToken } from "../lending/ttoken/ITToken.sol"; + +// Proxy +import { + BeaconProxy +} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; +import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; + +// Storage +import { + LoanRequestSnark, + LoanStatus, + LoanTerms, + Loan, + MarketStorageLib, + Signature, + DataProviderSignature +} from "../storage/market.sol"; +import { AppStorageLib } from "../storage/app.sol"; + +contract CreateLoanSnarkFacet is RolesMods, ReentryMods, PausableMods { + /** + * @notice This event is emitted when a loan has been successfully taken out + * @param loanID ID of loan from which collateral was withdrawn + * @param borrower Account address of the borrower + * @param amountBorrowed Total amount taken out in the loan + * @param withNFT Boolean indicating if the loan was taken out using NFTs + */ + event LoanTakenOutSnark( + uint256 indexed loanID, + address indexed borrower, + uint256 amountBorrowed, + bool withNFT + ); + + function takeOutLoanSnark( + LoanRequestSnark calldata request, + address collateralToken, + uint256 collateralAmount + ) + external + payable + paused(LibLoans.ID, false) + nonReentry("") + authorized(AUTHORIZED, msg.sender) + { + // create loan from library + Loan storage loan = CreateLoanSnarkLib.createLoanSnark(request); + // Check if collateral token is zero + require( + collateralToken != address(0x0), + "Teller: token addr can't be 0" + ); + + // Verify collateral token is acceptable + require( + EnumerableSet.contains( + MarketStorageLib.store().collateralTokens[ + request.request.assetAddress + ], + collateralToken + ), + "Teller: collateral token not allowed" + ); + + // Save collateral token to loan + loan.collateralToken = collateralToken; + + // Pay in collateral + if (collateralAmount > 0) { + LibCollateral.deposit(loan.id, collateralToken, collateralAmount); + } + + // Check that enough collateral has been provided for this loan + require( + LibLoans.getCollateralNeeded(loan.id) <= + LibCollateral.e(loan.id).loanSupply(loan.id), + "Teller: more collateral required" + ); + // Pull funds from Teller token LP and and transfer to the recipient + ITToken tToken = LendingLib.tToken(request.request.assetAddress); + + tToken.fundLoan( + LibLoans.canGoToEOAWithCollateralRatio(loan.collateralRatio) + ? loan.borrower + : CreateLoanSnarkLib.createEscrow(loan.id), + loan.borrowedAmount + ); + + emit LoanTakenOutSnark(loan.id, msg.sender, loan.borrowedAmount, false); + // set to active + loan.status = LoanStatus.Active; + loan.loanStartTime = uint32(block.timestamp); + loan.duration = request.request.duration; + } +} + +library CreateLoanSnarkLib { + function createLoanSnark(LoanRequestSnark calldata request) + internal + returns (Loan storage loan) + { + // Perform loan request checks + require( + PlatformSettingsLib.getMaximumLoanDurationValue() >= + request.request.duration, + "Teller: max loan duration exceeded" + ); + + // Get market values + ( + uint16 interestRate, + uint16 collateralRatio, + uint256 maxLoanAmount + ) = ProcessRequestLib.processMarketRequest(request); + + // Perform loan value checks + require( + MaxLoanAmountLib.get(request.request.assetAddress) > maxLoanAmount, + "Teller: asset max loan amount exceeded" + ); + require( + LendingLib.tToken(request.request.assetAddress).debtRatioFor( + maxLoanAmount + ) <= MaxDebtRatioLib.get(request.request.assetAddress), + "Teller: max supply-to-debt ratio exceeded" + ); + + // Get and increment new loan ID + uint256 loanID = CreateLoanSnarkLib.newID(); + + // Set loan data based on terms + loan = LibLoans.loan(loanID); + loan.id = uint128(loanID); + loan.status = LoanStatus.TermsSet; + loan.lendingToken = request.request.assetAddress; + loan.borrower = request.request.borrower; + loan.borrowedAmount = maxLoanAmount; + loan.interestRate = interestRate; + loan.collateralRatio = collateralRatio; + + // Set loan debt + LibLoans.debt(loanID).principalOwed = maxLoanAmount; + LibLoans.debt(loanID).interestOwed = LibLoans.getInterestOwedFor( + uint256(loanID), + maxLoanAmount + ); + + // Add loanID to borrower list + MarketStorageLib.store().borrowerLoans[loan.borrower].push( + uint128(loanID) + ); + } + + /** + * @notice increments the loanIDCounter + * @return id_ the new ID requested, which stores it in the loan data + */ + function newID() internal returns (uint256 id_) { + Counters.Counter storage counter = MarketStorageLib + .store() + .loanIDCounter; + id_ = Counters.current(counter); + Counters.increment(counter); + } + + function currentID() internal view returns (uint256 id_) { + Counters.Counter storage counter = MarketStorageLib + .store() + .loanIDCounter; + id_ = Counters.current(counter); + } + + function createEscrow(uint256 loanID) internal returns (address escrow_) { + // Create escrow + escrow_ = AppStorageLib.store().loansEscrowBeacon.cloneProxy(""); + ILoansEscrow(escrow_).init(); + // Save escrow address for loan + MarketStorageLib.store().loanEscrows[loanID] = ILoansEscrow(escrow_); + } +} diff --git a/contracts/market/LendingFacet.sol b/contracts/market/LendingFacet.sol new file mode 100644 index 000000000..e69de29bb diff --git a/contracts/market/ProviderFactoryFacet.sol b/contracts/market/ProviderFactoryFacet.sol new file mode 100644 index 000000000..bf5657b48 --- /dev/null +++ b/contracts/market/ProviderFactoryFacet.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.3; + +import { DataProvider } from "./cra/DataProvider.sol"; + +contract ProviderFactoryFacet { + address public admin; + DataProvider[] public providers; + + modifier onlyAdmin() { + require(admin == msg.sender, "Only the admin can call this!"); + _; + } + + // whichever address that deploys the provider factory facet is the admin of the factory + constructor() { + admin = msg.sender; + } + + /** + * @notice it creates a new provider. whichever address that creates the new provider is the + * admin of the said provider. + */ + function createProvider() public { + DataProvider provider = new DataProvider(msg.sender); + providers.push(provider); + } + + function getProviders() public view returns (DataProvider[] memory) { + return providers; + } +} diff --git a/contracts/market/cra/DataProvider.sol b/contracts/market/cra/DataProvider.sol new file mode 100644 index 000000000..3ba8b9979 --- /dev/null +++ b/contracts/market/cra/DataProvider.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.3; + +contract DataProvider { + mapping(address => bool) public admins; + mapping(address => bool) public signers; + + modifier onlyAdmin() { + require(admins[msg.sender], "Teller: not admin"); + _; + } + + constructor(address initAdmin) { + admins[initAdmin] = true; + } + + /** + * @notice it sets the admin of a provider + * @param signerAddress the address of the new or existing signer + * @param signerValue the bool value for the admin + */ + function setSigner(address signerAddress, bool signerValue) + public + onlyAdmin + { + signers[signerAddress] = signerValue; + } + + /** + * @notice it sets the admin of a provider + * @param adminAddress the address of the new or existing admin + * @param adminValue the bool value for the admin + */ + function setAdmin(address adminAddress, bool adminValue) public onlyAdmin { + admins[adminAddress] = adminValue; + } +} diff --git a/contracts/market/cra/ProcessRequestLib.sol b/contracts/market/cra/ProcessRequestLib.sol new file mode 100644 index 000000000..5b4cad3d0 --- /dev/null +++ b/contracts/market/cra/ProcessRequestLib.sol @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { + LoanRequestSnark, + DataProviderSignature, + Signature +} from "../../storage/market.sol"; +import { MarketHandler } from "../cra/market-handler/MarketHandler.sol"; +import { LibLoans } from "../libraries/LibLoans.sol"; +import { Verifier } from "../cra/verifier.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { DataProvider } from "./DataProvider.sol"; + +library ProcessRequestLib { + /** + * @notice it uses our request to verify the returned proof and witness with each other, + * verifies our signature data with our respective data providers, then retrieves our interest rate, + * collateral ratio and max loan amount + * @param request contains all the needed data to do the above + * @return interestRate the rate of the loan + * @return collateralRatio the collateral ratio required for the loan, if any + * @return maxLoanAmount the max loan amount the user is entitled to + */ + function processMarketRequest(LoanRequestSnark memory request) + public + returns ( + uint16 interestRate, + uint16 collateralRatio, + uint256 maxLoanAmount + ) + { + MarketHandler marketHandler = MarketHandler( + request.marketHandlerAddress + ); + // Overwrite the first snark witness item with the on-chain identifier + // for the loan (msg.sender ^ nonce). This forces the CRA to have been + // run with the proper identifier. + request.snarkWitnesses[0] = + uint256(uint160(msg.sender)) ^ + LibLoans.s().borrowerLoans[msg.sender].length; + + // Verify the snark proof. + require( + Verifier.verifyTx(request.snarkProof, request.snarkWitnesses), + "Proof not verified" + ); + + // signatures length + uint8 signaturesLength = marketHandler.numberOfSignaturesRequired(); + + // get variable amount of commitments from market handler + bytes32[] memory commitments = new bytes32[](signaturesLength); + + // constructing our commitments to verify with our signature data + for (uint8 i = 0; i < commitments.length; i++) { + for (uint8 j = 0; j < 8; j++) { + commitments[i] = + (commitments[i] << 32) ^ + bytes32(request.snarkWitnesses[2 + i * 8 + j]); + } + commitments[i] ^= bytes32( + request.dataProviderSignatures[i].signedAt + ); + } + + // equate this require statement to amount of commitments from market handler + require( + request.dataProviderSignatures.length == 3, + "Must have 3 providers!" + ); + + // Verify that the commitment signatures are valid and that the data + // is not too old for the market's liking. + _verifySignatures( + commitments, + request.dataProviderSignatures, + request.marketHandlerAddress, + request.providers + ); + + // The second witness item (after identifier) is the market + // score + uint256 marketScore = uint256(request.snarkWitnesses[1]); + require(marketScore > 5, "Teller: market score not high enough"); + + // Let the market handle the loan request and disperse the loan. + + // create default teller market handler + // pass it the marketId and return max loan amount, collateral ratio, interest rate + // upper and lower bound for loan amount, interest rate and collateral ratio depending on + // market id + (interestRate, collateralRatio, maxLoanAmount) = marketHandler.handler( + marketScore, + request + ); + return (interestRate, collateralRatio, maxLoanAmount); + } + + function _verifySignatures( + bytes32[] memory commitments, + DataProviderSignature[] memory signatureData, + address marketHandlerAddress, + address[] memory providers + ) private { + MarketHandler marketHandler = MarketHandler(marketHandlerAddress); + for (uint256 i = 0; i < commitments.length; i++) { + address providerAddress = providers[i]; + require( + signatureData[i].signedAt > block.timestamp - 5 days, + "Signed at less than max age" + ); + require( + marketHandler.usedCommitments(commitments[i]) == false, + "Teller: commitment already used" + ); + marketHandler.addCommitment(commitments[i]); + + _validateSignature( + signatureData[i].signature, + commitments[i], + providerAddress + ); + } + } + + /** + * @notice It validates whether a signature is valid or not. + * @param signature signature to validate. + * @param commitment used to recover the signer. + * @param providerAddress the provider address to check for the recovered signer. + */ + function _validateSignature( + Signature memory signature, + bytes32 commitment, + address providerAddress + ) private view { + address recoveredSigner = ECDSA.recover( + keccak256( + abi.encodePacked( + "\x19Ethereum Signed Message:\n32", + uint256(commitment) + ) + ), + signature.v, + signature.r, + signature.s + ); + DataProvider provider = DataProvider(providerAddress); + require( + provider.signers(recoveredSigner), + "Teller: not valid signature" + ); + } +} diff --git a/contracts/market/cra/cra.zok b/contracts/market/cra/cra.zok new file mode 100644 index 000000000..1b4c54ac3 --- /dev/null +++ b/contracts/market/cra/cra.zok @@ -0,0 +1,37 @@ +import "hashes/sha256/256bitPadded.zok" as sha256 + +/* + Zero-Knowledge CRA. Each market configures up to 4 data providers used to + create their market score. Markets also configure the weight of each value + given for the user by the data provider. Each data provider gives the user + a score between 0 and 4,294,967,295 along with a secret. These two are + combined to create a commitment, which is also signed by the data provider + and verified on-chain. + + @param private u32[4][8] data - Array of 4 uint256's. The first 32 bits of + each value represents the user's score given by a specific data provider. + The last 224 bits represent a secret value used to create a commitment hash. + Data providers sign the value sha256(value, secret) ^ timestamp, and that + signature is verified on-chain. + + @param public field identifier - user address ^ user borrow nonce. This + param is used to prevent replaying proofs on-chain. + + @param public u32[4] weights - Array of 4 uint32's. Each value represents + a weight given by the market for the data provider at that index. If + data[i][0] (value) == 10 and weights[i] == 5, then market score += 50. + + @return field MARKET_SCORE - uint256 accumulated markets score. + @return u32[4][8] commitments - Array of the 4 commitments. +*/ +def main(private u32[3][8] data, public field identifier) -> (u32, u32[3][8]): + u32[3][8] commitments = data + u32 MARKET_SCORE = 0 + u32 MASK = 0x0000000a + + for u32 i in 0..3 do + MARKET_SCORE = MARKET_SCORE + data[i][0] & MASK + commitments[i] = sha256(data[i]) + endfor + + return MARKET_SCORE,commitments \ No newline at end of file diff --git a/contracts/market/cra/market-handler/MarketHandler.sol b/contracts/market/cra/market-handler/MarketHandler.sol new file mode 100644 index 000000000..cdc99951a --- /dev/null +++ b/contracts/market/cra/market-handler/MarketHandler.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.3; + +import { LoanRequestSnark } from "../../../storage/market.sol"; +import { DataProvider } from "../DataProvider.sol"; +import { + EnumerableSet +} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +abstract contract MarketHandler { + using EnumerableSet for EnumerableSet.AddressSet; + // market info + + uint8 public numberOfSignaturesRequired; + uint16 public maxInterestRate; + uint16 public maxCollateralRatio; + uint256 public maxLoanAmount; + + // admin stuff + mapping(address => bool) public admins; + + // signature related variables + mapping(bytes32 => bool) public usedCommitments; + EnumerableSet.AddressSet internal providers; + + modifier onlyAdmin() { + require(admins[msg.sender], "Teller: not market admin!"); + _; + } + + constructor( + uint16 maxInterestRate_, + uint16 maxCollateralRatio_, + uint256 maxLoanAmount_ + ) { + admins[msg.sender] = true; + maxInterestRate = maxInterestRate_; + maxCollateralRatio = maxCollateralRatio_; + maxLoanAmount = maxLoanAmount_; + } + + /** + * @notice it gets the user score and user request then returns the loan interest rate, + * loan collateral ratio and loan amount. + * @param marketScore the score of the user + * @param request the user's request for the loan + * @return userInterestRate returns the interest rate for the user based on his score + * @return userCollateralRatio returns the collateral ratio of the user based on his score + * @return userLoanAmount returns the amount for the user to take a loan out based on his score + */ + function handler(uint256 marketScore, LoanRequestSnark memory request) + external + view + virtual + returns ( + uint16 userInterestRate, + uint16 userCollateralRatio, + uint256 userLoanAmount + ); + + function addCommitment(bytes32 commitment) public { + usedCommitments[commitment] = true; + } + + function getProviders() public view returns (address[] memory providers_) { + providers_ = new address[](providers.length()); + for (uint256 i; i < providers.length(); i++) { + providers_[i] = providers.at(i); + } + } + + function addProviders(address[] memory providerAddresses) public onlyAdmin { + for (uint256 i; i < providerAddresses.length; i++) { + providers.add(providerAddresses[i]); + } + numberOfSignaturesRequired = uint8(providers.length()); + } + + function removeProviders(address[] calldata providerAddresses) + public + onlyAdmin + { + for (uint256 i; i < providerAddresses.length; i++) { + providers.remove(providerAddresses[i]); + } + } +} diff --git a/contracts/market/cra/market-handler/TellerMarketHandler.sol b/contracts/market/cra/market-handler/TellerMarketHandler.sol new file mode 100644 index 000000000..5371f40c1 --- /dev/null +++ b/contracts/market/cra/market-handler/TellerMarketHandler.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.3; + +import { LoanRequestSnark } from "../../../storage/market.sol"; +import { + EnumerableSet +} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { MarketHandler } from "./MarketHandler.sol"; +import { Rates } from "../../data/states.sol"; + +contract TellerMarketHandler is MarketHandler, Rates { + constructor( + uint16 maxInterestRate, + uint16 collateralRatio, + uint256 maxLoanAmount + ) MarketHandler(maxInterestRate, collateralRatio, maxLoanAmount) {} + + // teller market handler + function handler(uint256 marketScore, LoanRequestSnark memory request) + external + view + override + returns ( + uint16 userInterestRate, + uint16 userCollateralRatio, + uint256 userLoanAmount + ) + { + uint256 amount = 0; + + // get amount for user based on market score + if (marketScore >= 5 && marketScore < 7) { + amount = _loanAmount(18000, 10000, 7, 5, marketScore); + } else if (marketScore >= 7 && marketScore < 9) { + amount = _loanAmount(23000, 18000, 9, 7, marketScore); + } else if (marketScore >= 9 && marketScore <= 10) { + amount = _loanAmount(25000, 23000, 10, 9, marketScore); + } + + // get interest rate + uint16 baseInterestRate = 1000; + + uint16 interestRate = baseInterestRate * + ((maxCollateralRatio / request.request.collateralRatio) / 2); + + // Illinois interest rate for testing + uint16 sampleCappedInterestRate = rates[uint16(request.request.code)]; + + bool useLegalIR = interestRate > sampleCappedInterestRate; + + if (useLegalIR) { + userInterestRate = sampleCappedInterestRate; + userLoanAmount = + (amount) / + (interestRate / (sampleCappedInterestRate + 1)); + } else { + userInterestRate = interestRate; + // + userLoanAmount = amount; + } + + userCollateralRatio = request.request.collateralRatio; + } + + /** + * @notice it calculates the loan amount from different bounds + * @param highestAmountBound the highest amount in a select bound (i.e 20000) + * @param lowestAmountBound the lowest amount in a select bound (i.e 10000) + * @param highestScoreBound the highest score in a select bound (i.e 7.5) + * @param lowestScoreBound the lowest score in a select bound + */ + function _loanAmount( + uint256 highestAmountBound, + uint256 lowestAmountBound, + uint16 highestScoreBound, + uint16 lowestScoreBound, + uint256 scoreToCalculate + ) internal pure returns (uint256 amount) { + // calculate for slope + uint256 m = (highestAmountBound - lowestAmountBound) / + (highestScoreBound - lowestScoreBound); + + // return amount + amount = + highestAmountBound - + ((m) * uint256((highestScoreBound - scoreToCalculate))); + } +} diff --git a/contracts/market/cra/proving.key b/contracts/market/cra/proving.key new file mode 100644 index 000000000..cb3b63c95 Binary files /dev/null and b/contracts/market/cra/proving.key differ diff --git a/contracts/market/cra/proving.key.hash b/contracts/market/cra/proving.key.hash new file mode 100644 index 000000000..bfb239cff --- /dev/null +++ b/contracts/market/cra/proving.key.hash @@ -0,0 +1 @@ +cb3b63c95187982905f966f3fadbb1f9d14adda3 diff --git a/contracts/market/cra/utils.sol b/contracts/market/cra/utils.sol new file mode 100644 index 000000000..4f2989119 --- /dev/null +++ b/contracts/market/cra/utils.sol @@ -0,0 +1,92 @@ +// // SPDX-License-Identifier: MIT + +// pragma solidity ^0.8.0; + +// address constant CORE = address(0); +// bytes32 constant CHAINLINK_REGISTRY_SALT = keccak256( +// "teller.finance.chainlink.registry.salt" +// ); + +// function c2a( +// address deployer, +// bytes32 salt, +// bytes32 initCodeHash +// ) pure returns (address) { +// return +// address( +// uint160( +// uint256( +// keccak256( +// abi.encodePacked(hex"ff", deployer, salt, initCodeHash) +// ) +// ) +// ) +// ); +// } + +// library TellerAssets { +// uint32 internal constant TLR = 0; +// uint32 internal constant ETH = 1; +// uint32 internal constant DAI = 2; +// uint32 internal constant YFI = 3; +// uint32 internal constant LINK = 4; + +// enum AssetType { ERC20, ERC721, ERC1155 } + +// function assetType(uint256 assetId) pure returns (AssetType assetType_) { +// return AssetType(uint8(bytes1(bytes32(assetId)))); +// } + +// /** +// Get the Teller ERC20 token ID encoded in the bytes [1..5] of an assetId. +// This ID can help reduce the cost of retrieving metadata about a token, like +// the chainlink price feed oracle address for that token and ETH. +// @param assetId uint256 id of the asset to get the Teller erc20 token id for. +// @return uint32 Teller erc20 token id. +// */ +// function get(uint256 assetId) internal pure returns (uint32) { +// return uint32(bytes4(bytes32(assetId << 8))); +// } + +// function value(uint256 assetId, uint256 amount) +// internal +// view +// returns (uint256 value_) +// { +// AssetType typ = assetType(assetId); + +// if (typ == AssetType.ERC20) { +// return erc20Value(assetId, amount); +// // chainlink +// } else if (typ == AssetType.ERC721) { +// // nft price? +// revert("Teller: no price for asset"); +// } else if (typ == ERC1155) { +// // erc1155 price? +// revert("Teller: no price for asset"); +// } else { +// revert("Teller: no price for asset"); +// } +// } + +// function erc20Value(uint256 assetId, uint256 amount) +// internal +// view +// returns (uint256 value_) +// { +// address core = CORE; +// uint256 erc20Id = get(assetId); +// uint256 assetIndex = erc20Id % 200; +// bytes32 targetSalt = CHAINLINK_REGISTRY_SALT ^ bytes32(erc20Id / 200); +// address target = c2a(core, targetSalt, mmCodeHash); +// address chainlinkAggregator; + +// assembly { +// extcodecopy(target, 0, assetIndex, 20) +// chainlinkAggregator := mload(0) +// } + +// value_ = ChainlinkAggregatorV2V3Interface(chainlinkAggregator) +// .latestRound(); +// } +// } diff --git a/contracts/market/cra/verification.key b/contracts/market/cra/verification.key new file mode 100644 index 000000000..5c1d571cd --- /dev/null +++ b/contracts/market/cra/verification.key @@ -0,0 +1,146 @@ +{ + "alpha": [ + "0x0c2c2e168d87d1684035486de2420c58c37da837f7bfb2dbfe0f287afe4a2eea", + "0x0e3444bdc6a17ede7e33c698147d3863ac0496da0eb73e34a8f3cfd1304c858a" + ], + "beta": [ + [ + "0x2f7a251d8acb64e8463ee8ca0dd08db5aa90403937f5aa61ee63974d1d898c30", + "0x24816c66ec102927b56f3e68b27eca3a47b090452fd54b6da2f54a50b40da6a5" + ], + [ + "0x10be79a5ef2d53ad8843791072b811757dcaf41e3e4fc7401f8f7830b8f7f2bb", + "0x1348e74ec819116944535cb6367b745f92e5c784adc5043b930bf0548548c6b4" + ] + ], + "gamma": [ + [ + "0x0c74bf255f3e329b340dbc67f6a9d8c02c3f5021d6e9a13db1b4a6f8487e4a11", + "0x14d8fcc4aab4968b769def3d390b41bc423fe995c44dbed47b20bc3bb117f805" + ], + [ + "0x1fac18d4f1f6b0973047abc2f9e4e2361453e8686c483a4a717c5d5b549e5596", + "0x067f11b360d20c3510184de12d660dbdc01a11011032b92e9730f66e9305af0e" + ] + ], + "delta": [ + [ + "0x2d6496e60bf4733f70c8ce6910aeeb87cd089d1b29bf5d69beda21d6ede23899", + "0x088c702a6a3c939b00d29ae5a8da00d77d6d283404726a6ffa33c482396c1fc1" + ], + [ + "0x0f5ceb82977b16796d31bb72d2175aec5dd56983a8f6941c6f34749607068ccd", + "0x1e07d06a10ef154350ae1b03e7938968aeb95b986cff74e502886ba947e33d4b" + ] + ], + "gamma_abc": [ + [ + "0x048c774bb304a3703b9c0fb66b48dd2f0815c272ad8684106f2079f40c17b635", + "0x2bb537ccdbe718d6a7f5f632ad989d9648643357b6d7ced2ec2335749d720015" + ], + [ + "0x235bec5a5dd7653a1f4bd40524732479be1ec3a069293c7e648985ad9c73a421", + "0x0d74c345dcfd970d07a192d2e67b8381f1eb7ae21a6eac65405b27e223ccd224" + ], + [ + "0x163f3c4a5398e396fb61c28f33e12ba62c5656a4d9358d5f9d9450e7a999336a", + "0x2314fb832d97759ae603745ef89d559e9b6a0fdf381f0de4be062aff3d03e918" + ], + [ + "0x037ed0d8ad3138a761917ab122183df90edb64427fd801129beb2187b482e6e4", + "0x00a402a2772280119ed200af9bd8efbf2ab675b4fb4197043530b09ed6beaf6b" + ], + [ + "0x2568574b41afc9c9228a09acee3ce824ac82adfca40bdf8095ac1530b9d58ec8", + "0x2a36865b457c454391b4ef1470835fa58f9c5ac35fb3687b309cd25f3143bea8" + ], + [ + "0x05c1562939c17dd3e871dcd22da5e550bcd8e353131d96944207bbc3db3dc320", + "0x06a907b9522826493cd8c379123a4de66dd35204cebc47d7c7a3adf8b6d507a0" + ], + [ + "0x14b20a07e94bf7ff609ea512835520a49aafc3c886154f8eb06b4c3d77625ef6", + "0x17582b8644d07221f572e242a2b3d0b4a09a12601a17cc2ea5a531cae46e09d3" + ], + [ + "0x20662047733d71659032e059f4a7d0263fce812299e7ac82d6497f1921b20ca6", + "0x13fbd98112c34a350e24dcd663d35e0680eae2a84c1f831a33d141d1e1385963" + ], + [ + "0x0060b6cfa606deed7b6dae8104e892f5e7fbe7847c6fb1277197a2dddeca75c4", + "0x136b9edbd2554c2752d2304cf89b0e079fd75fee51e52a22800afb4de638161e" + ], + [ + "0x1fbbf2e9f1004e9f39d9f74dc7241f94b5e57c78fff1e4be446b108ac1e16910", + "0x0227ac39493820d595a984e8911dfa0fe019c98faa60cc4352c668c8f8e25c81" + ], + [ + "0x0dbb3f53e6b580752a509a397eabacb727a808980d461676cb0d2568e90a3c07", + "0x157fc2d22d16e901274c3a2f44011ec307386cbd0ab34b2d05a1d40f3f231049" + ], + [ + "0x1a22ef3bfdc413d5c60e27c3c58a2eba28a5842fc4659997c4020da81791ace5", + "0x0ffddc700ba6acac16e5a0f5b38a8627d4407875afa09b20a139fe6b706975dd" + ], + [ + "0x02c3ae50a8bf4d28b108b169f450ecb39a812f069da980353374f318be3a28b0", + "0x1f7c504e20d128d331c183b2afdba0a23642249fbc496584ae12983927514ed7" + ], + [ + "0x10ccbb99683bbd1c18b8142507551b4c7c1146d09968d3808d273c187659f476", + "0x21a4f68a9cb6f793b0af06d584597c2120db311819655ba87e526c6082c4a957" + ], + [ + "0x2e1360fbc5d0c1be6fdc094312308186e0e5efac92f4459b53e3e36dc291db41", + "0x2cc44704510f38d6c0004f3e6eb05de91b1628a49a4173937a7fd6e2bca8255f" + ], + [ + "0x15a747c034ec8946e0982797044d51fd7c5039d0dc7a6c7ab32e61ca3e7f0d36", + "0x16b7e40d6a47ca7d7301fa62abf6a1a2602ab05e577634da81ec0d1365c0f563" + ], + [ + "0x13362639e6acf6ae865e4680a8fdf3d74c26fd7e98f5f9ef7c399eb6948a6653", + "0x23c42150e11edef6226e4b7abce281b46cd746d59dea5f67340f791e9ad27d3b" + ], + [ + "0x2715cfdd56d0ad0e257562f5960552c5653d24d39b99833945b4cc01804a01f2", + "0x18e214ab3a7feca580377c6d7e9d8d7a59373a47110e84614b8e4df3f22c3ec9" + ], + [ + "0x193ed4a1027b5f0de0f80cf8138e0cc176cdbaaaf0a3e32b7db79b0aa8250f6c", + "0x024f9d88b54465831b1d449fcb9fe88e44a80cae1e959122709850006b667d21" + ], + [ + "0x1185ad1747fbe95bec47c10a4a165b9dfaec64c3771a6dd2315515b61d13e62c", + "0x2597080b83f49f9d3bb81b5d4a29737ee99887b361513cc8af1c15bf98799717" + ], + [ + "0x07fee61e25fdc9896bbaf21bda05674091d847f570b8b97591ce5e475183f95c", + "0x0d4e38f3884879a8c88b32833027d718100d3c64740d9b556304df02231e13eb" + ], + [ + "0x12416db4dce0ac6b6fe53af3d053917cdb7d2bacaa6f70e62d16723af2cf8cac", + "0x1dcc06908670dc8c32fb92ce699b0f9ad2f8c1cad712d09cd37f9f25800795f5" + ], + [ + "0x00ff1b4cf07f5338ccd8bd8e38e6c47271bc169499fd21c5b2382493ff6f4481", + "0x20142bbb11e4c4680e54b6d88606674658c59eaf1cd0bc1cc024718145f79ca9" + ], + [ + "0x221b518df5f867010f5d410d957ca535ed82c001262219da60b36e3199b0ef81", + "0x174f2a8f077043cc9f0c7f2497be95b57866b2a8f7ce4b611df224d7df2c42f0" + ], + [ + "0x2a97f439f8a45c402c2a94000ba2af0660a01c8c2c3450a4f77eadf0d2a9569f", + "0x231c640b63d7a50e9988c2b6f2c95024336a0d52a8f79689ef22a4afaf2a11b8" + ], + [ + "0x20a0729e4668231bb82921be1809381b44b26b25e94bdc51a2aaf4ef38f2a6f4", + "0x1ba23046e491e6bbed2554c0f3e8412a787227310ee64eebc5ceb6b24ab621ad" + ], + [ + "0x0396df78e09ffe44fe1919675e3a30df164f2ac08a70b0cdddfe69b71f96b986", + "0x28edd87998214844a8f88c6632d65e969a0c65a852257fdf6b7893cb729115ab" + ] + ] +} \ No newline at end of file diff --git a/contracts/market/cra/verification.key.hash b/contracts/market/cra/verification.key.hash new file mode 100644 index 000000000..a9eb323ef --- /dev/null +++ b/contracts/market/cra/verification.key.hash @@ -0,0 +1 @@ +5c1d571cdf6b04863db5ba5f138f359d4d32d53e diff --git a/contracts/market/cra/verifier.sol b/contracts/market/cra/verifier.sol new file mode 100644 index 000000000..7f3bbf54d --- /dev/null +++ b/contracts/market/cra/verifier.sol @@ -0,0 +1,1040 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// This file is LGPL3 Licensed +pragma solidity ^0.8.0; + +/** + * @title Elliptic curve operations on twist points for alt_bn128 + * @author Mustafa Al-Bassam (mus@musalbas.com) + * @dev Homepage: https://github.com/musalbas/solidity-BN256G2 + */ + +// GENERATED FROM ZOKRATES: DO NOT MODIFY +library BN256G2 { + uint256 internal constant FIELD_MODULUS = + 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 internal constant TWISTBX = + 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 internal constant TWISTBY = + 0x9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2; + uint256 internal constant PTXX = 0; + uint256 internal constant PTXY = 1; + uint256 internal constant PTYX = 2; + uint256 internal constant PTYY = 3; + uint256 internal constant PTZX = 4; + uint256 internal constant PTZY = 5; + + /** + * @notice Add two twist points + * @param pt1xx Coefficient 1 of x on point 1 + * @param pt1xy Coefficient 2 of x on point 1 + * @param pt1yx Coefficient 1 of y on point 1 + * @param pt1yy Coefficient 2 of y on point 1 + * @param pt2xx Coefficient 1 of x on point 2 + * @param pt2xy Coefficient 2 of x on point 2 + * @param pt2yx Coefficient 1 of y on point 2 + * @param pt2yy Coefficient 2 of y on point 2 + * @return (pt3xx, pt3xy, pt3yx, pt3yy) + */ + function ECTwistAdd( + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt2xx, + uint256 pt2xy, + uint256 pt2yx, + uint256 pt2yy + ) + public + view + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + if (pt1xx == 0 && pt1xy == 0 && pt1yx == 0 && pt1yy == 0) { + if (!(pt2xx == 0 && pt2xy == 0 && pt2yx == 0 && pt2yy == 0)) { + assert(_isOnCurve(pt2xx, pt2xy, pt2yx, pt2yy)); + } + return (pt2xx, pt2xy, pt2yx, pt2yy); + } else if (pt2xx == 0 && pt2xy == 0 && pt2yx == 0 && pt2yy == 0) { + assert(_isOnCurve(pt1xx, pt1xy, pt1yx, pt1yy)); + return (pt1xx, pt1xy, pt1yx, pt1yy); + } + + assert(_isOnCurve(pt1xx, pt1xy, pt1yx, pt1yy)); + assert(_isOnCurve(pt2xx, pt2xy, pt2yx, pt2yy)); + + uint256[6] memory pt3 = + _ECTwistAddJacobian( + pt1xx, + pt1xy, + pt1yx, + pt1yy, + 1, + 0, + pt2xx, + pt2xy, + pt2yx, + pt2yy, + 1, + 0 + ); + + return + _fromJacobian( + pt3[PTXX], + pt3[PTXY], + pt3[PTYX], + pt3[PTYY], + pt3[PTZX], + pt3[PTZY] + ); + } + + /** + * @notice Multiply a twist point by a scalar + * @param s Scalar to multiply by + * @param pt1xx Coefficient 1 of x + * @param pt1xy Coefficient 2 of x + * @param pt1yx Coefficient 1 of y + * @param pt1yy Coefficient 2 of y + * @return (pt2xx, pt2xy, pt2yx, pt2yy) + */ + function ECTwistMul( + uint256 s, + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy + ) + public + view + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + uint256 pt1zx = 1; + if (pt1xx == 0 && pt1xy == 0 && pt1yx == 0 && pt1yy == 0) { + pt1xx = 1; + pt1yx = 1; + pt1zx = 0; + } else { + assert(_isOnCurve(pt1xx, pt1xy, pt1yx, pt1yy)); + } + + uint256[6] memory pt2 = + _ECTwistMulJacobian(s, pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, 0); + + return + _fromJacobian( + pt2[PTXX], + pt2[PTXY], + pt2[PTYX], + pt2[PTYY], + pt2[PTZX], + pt2[PTZY] + ); + } + + /** + * @notice Get the field modulus + * @return The field modulus + */ + function GetFieldModulus() public pure returns (uint256) { + return FIELD_MODULUS; + } + + function submod( + uint256 a, + uint256 b, + uint256 n + ) internal pure returns (uint256) { + return addmod(a, n - b, n); + } + + function _FQ2Mul( + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy + ) internal pure returns (uint256, uint256) { + return ( + submod( + mulmod(xx, yx, FIELD_MODULUS), + mulmod(xy, yy, FIELD_MODULUS), + FIELD_MODULUS + ), + addmod( + mulmod(xx, yy, FIELD_MODULUS), + mulmod(xy, yx, FIELD_MODULUS), + FIELD_MODULUS + ) + ); + } + + function _FQ2Muc( + uint256 xx, + uint256 xy, + uint256 c + ) internal pure returns (uint256, uint256) { + return (mulmod(xx, c, FIELD_MODULUS), mulmod(xy, c, FIELD_MODULUS)); + } + + function _FQ2Add( + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy + ) internal pure returns (uint256, uint256) { + return (addmod(xx, yx, FIELD_MODULUS), addmod(xy, yy, FIELD_MODULUS)); + } + + function _FQ2Sub( + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy + ) internal pure returns (uint256 rx, uint256 ry) { + return (submod(xx, yx, FIELD_MODULUS), submod(xy, yy, FIELD_MODULUS)); + } + + function _FQ2Div( + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy + ) internal view returns (uint256, uint256) { + (yx, yy) = _FQ2Inv(yx, yy); + return _FQ2Mul(xx, xy, yx, yy); + } + + function _FQ2Inv(uint256 x, uint256 y) + internal + view + returns (uint256, uint256) + { + uint256 inv = + _modInv( + addmod( + mulmod(y, y, FIELD_MODULUS), + mulmod(x, x, FIELD_MODULUS), + FIELD_MODULUS + ), + FIELD_MODULUS + ); + return ( + mulmod(x, inv, FIELD_MODULUS), + FIELD_MODULUS - mulmod(y, inv, FIELD_MODULUS) + ); + } + + function _isOnCurve( + uint256 xx, + uint256 xy, + uint256 yx, + uint256 yy + ) internal pure returns (bool) { + uint256 yyx; + uint256 yyy; + uint256 xxxx; + uint256 xxxy; + (yyx, yyy) = _FQ2Mul(yx, yy, yx, yy); + (xxxx, xxxy) = _FQ2Mul(xx, xy, xx, xy); + (xxxx, xxxy) = _FQ2Mul(xxxx, xxxy, xx, xy); + (yyx, yyy) = _FQ2Sub(yyx, yyy, xxxx, xxxy); + (yyx, yyy) = _FQ2Sub(yyx, yyy, TWISTBX, TWISTBY); + return yyx == 0 && yyy == 0; + } + + function _modInv(uint256 a, uint256 n) + internal + view + returns (uint256 result) + { + bool success; + assembly { + let freemem := mload(0x40) + mstore(freemem, 0x20) + mstore(add(freemem, 0x20), 0x20) + mstore(add(freemem, 0x40), 0x20) + mstore(add(freemem, 0x60), a) + mstore(add(freemem, 0x80), sub(n, 2)) + mstore(add(freemem, 0xA0), n) + success := staticcall( + sub(gas(), 2000), + 5, + freemem, + 0xC0, + freemem, + 0x20 + ) + result := mload(freemem) + } + require(success); + } + + function _fromJacobian( + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy + ) + internal + view + returns ( + uint256 pt2xx, + uint256 pt2xy, + uint256 pt2yx, + uint256 pt2yy + ) + { + uint256 invzx; + uint256 invzy; + (invzx, invzy) = _FQ2Inv(pt1zx, pt1zy); + (pt2xx, pt2xy) = _FQ2Mul(pt1xx, pt1xy, invzx, invzy); + (pt2yx, pt2yy) = _FQ2Mul(pt1yx, pt1yy, invzx, invzy); + } + + function _ECTwistAddJacobian( + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy, + uint256 pt2xx, + uint256 pt2xy, + uint256 pt2yx, + uint256 pt2yy, + uint256 pt2zx, + uint256 pt2zy + ) internal pure returns (uint256[6] memory pt3) { + if (pt1zx == 0 && pt1zy == 0) { + ( + pt3[PTXX], + pt3[PTXY], + pt3[PTYX], + pt3[PTYY], + pt3[PTZX], + pt3[PTZY] + ) = (pt2xx, pt2xy, pt2yx, pt2yy, pt2zx, pt2zy); + return pt3; + } else if (pt2zx == 0 && pt2zy == 0) { + ( + pt3[PTXX], + pt3[PTXY], + pt3[PTYX], + pt3[PTYY], + pt3[PTZX], + pt3[PTZY] + ) = (pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy); + return pt3; + } + + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // U1 = y2 * z1 + (pt3[PTYX], pt3[PTYY]) = _FQ2Mul(pt1yx, pt1yy, pt2zx, pt2zy); // U2 = y1 * z2 + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // V1 = x2 * z1 + (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1xx, pt1xy, pt2zx, pt2zy); // V2 = x1 * z2 + + if (pt2xx == pt3[PTZX] && pt2xy == pt3[PTZY]) { + if (pt2yx == pt3[PTYX] && pt2yy == pt3[PTYY]) { + ( + pt3[PTXX], + pt3[PTXY], + pt3[PTYX], + pt3[PTYY], + pt3[PTZX], + pt3[PTZY] + ) = _ECTwistDoubleJacobian( + pt1xx, + pt1xy, + pt1yx, + pt1yy, + pt1zx, + pt1zy + ); + return pt3; + } + ( + pt3[PTXX], + pt3[PTXY], + pt3[PTYX], + pt3[PTYY], + pt3[PTZX], + pt3[PTZY] + ) = (1, 0, 1, 0, 0, 0); + return pt3; + } + + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // W = z1 * z2 + (pt1xx, pt1xy) = _FQ2Sub(pt2yx, pt2yy, pt3[PTYX], pt3[PTYY]); // U = U1 - U2 + (pt1yx, pt1yy) = _FQ2Sub(pt2xx, pt2xy, pt3[PTZX], pt3[PTZY]); // V = V1 - V2 + (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1yx, pt1yy); // V_squared = V * V + (pt2yx, pt2yy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTZX], pt3[PTZY]); // V_squared_times_V2 = V_squared * V2 + (pt1zx, pt1zy) = _FQ2Mul(pt1zx, pt1zy, pt1yx, pt1yy); // V_cubed = V * V_squared + (pt3[PTZX], pt3[PTZY]) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // newz = V_cubed * W + (pt2xx, pt2xy) = _FQ2Mul(pt1xx, pt1xy, pt1xx, pt1xy); // U * U + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // U * U * W + (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt1zx, pt1zy); // U * U * W - V_cubed + (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 2); // 2 * V_squared_times_V2 + (pt2xx, pt2xy) = _FQ2Sub(pt2xx, pt2xy, pt2zx, pt2zy); // A = U * U * W - V_cubed - 2 * V_squared_times_V2 + (pt3[PTXX], pt3[PTXY]) = _FQ2Mul(pt1yx, pt1yy, pt2xx, pt2xy); // newx = V * A + (pt1yx, pt1yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // V_squared_times_V2 - A + (pt1yx, pt1yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // U * (V_squared_times_V2 - A) + (pt1xx, pt1xy) = _FQ2Mul(pt1zx, pt1zy, pt3[PTYX], pt3[PTYY]); // V_cubed * U2 + (pt3[PTYX], pt3[PTYY]) = _FQ2Sub(pt1yx, pt1yy, pt1xx, pt1xy); // newy = U * (V_squared_times_V2 - A) - V_cubed * U2 + } + + function _ECTwistDoubleJacobian( + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy + ) + internal + pure + returns ( + uint256 pt2xx, + uint256 pt2xy, + uint256 pt2yx, + uint256 pt2yy, + uint256 pt2zx, + uint256 pt2zy + ) + { + (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 3); // 3 * x + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1xx, pt1xy); // W = 3 * x * x + (pt1zx, pt1zy) = _FQ2Mul(pt1yx, pt1yy, pt1zx, pt1zy); // S = y * z + (pt2yx, pt2yy) = _FQ2Mul(pt1xx, pt1xy, pt1yx, pt1yy); // x * y + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt1zx, pt1zy); // B = x * y * S + (pt1xx, pt1xy) = _FQ2Mul(pt2xx, pt2xy, pt2xx, pt2xy); // W * W + (pt2zx, pt2zy) = _FQ2Muc(pt2yx, pt2yy, 8); // 8 * B + (pt1xx, pt1xy) = _FQ2Sub(pt1xx, pt1xy, pt2zx, pt2zy); // H = W * W - 8 * B + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt1zx, pt1zy); // S_squared = S * S + (pt2yx, pt2yy) = _FQ2Muc(pt2yx, pt2yy, 4); // 4 * B + (pt2yx, pt2yy) = _FQ2Sub(pt2yx, pt2yy, pt1xx, pt1xy); // 4 * B - H + (pt2yx, pt2yy) = _FQ2Mul(pt2yx, pt2yy, pt2xx, pt2xy); // W * (4 * B - H) + (pt2xx, pt2xy) = _FQ2Muc(pt1yx, pt1yy, 8); // 8 * y + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1yx, pt1yy); // 8 * y * y + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt2zx, pt2zy); // 8 * y * y * S_squared + (pt2yx, pt2yy) = _FQ2Sub(pt2yx, pt2yy, pt2xx, pt2xy); // newy = W * (4 * B - H) - 8 * y * y * S_squared + (pt2xx, pt2xy) = _FQ2Muc(pt1xx, pt1xy, 2); // 2 * H + (pt2xx, pt2xy) = _FQ2Mul(pt2xx, pt2xy, pt1zx, pt1zy); // newx = 2 * H * S + (pt2zx, pt2zy) = _FQ2Mul(pt1zx, pt1zy, pt2zx, pt2zy); // S * S_squared + (pt2zx, pt2zy) = _FQ2Muc(pt2zx, pt2zy, 8); // newz = 8 * S * S_squared + } + + function _ECTwistMulJacobian( + uint256 d, + uint256 pt1xx, + uint256 pt1xy, + uint256 pt1yx, + uint256 pt1yy, + uint256 pt1zx, + uint256 pt1zy + ) internal pure returns (uint256[6] memory pt2) { + while (d != 0) { + if ((d & 1) != 0) { + pt2 = _ECTwistAddJacobian( + pt2[PTXX], + pt2[PTXY], + pt2[PTYX], + pt2[PTYY], + pt2[PTZX], + pt2[PTZY], + pt1xx, + pt1xy, + pt1yx, + pt1yy, + pt1zx, + pt1zy + ); + } + (pt1xx, pt1xy, pt1yx, pt1yy, pt1zx, pt1zy) = _ECTwistDoubleJacobian( + pt1xx, + pt1xy, + pt1yx, + pt1yy, + pt1zx, + pt1zy + ); + + d = d / 2; + } + } +} + +// This file is MIT Licensed. +// +// Copyright 2017 Christian Reitwiessner +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// GENERATED FROM ZOKRATES: DO NOT MODIFY +library Pairing { + struct G1Point { + uint256 X; + uint256 Y; + } + // Encoding of field elements is: X[0] * z + X[1] + struct G2Point { + uint256[2] X; + uint256[2] Y; + } + + /// @return the generator of G1 + function P1() internal pure returns (G1Point memory) { + return G1Point(1, 2); + } + + /// @return the generator of G2 + function P2() internal pure returns (G2Point memory) { + return + G2Point( + [ + 10857046999023057135944570762232829481370756359578518086990519993285655852781, + 11559732032986387107991004021392285783925812861821192530917403151452391805634 + ], + [ + 8495653923123431417604973247489272438418190587263600148770280649306958101930, + 4082367875863433681332203403145435568316851327593401208105741076214120093531 + ] + ); + } + + /// @return the negation of p, i.e. p.addition(p.negate()) should be zero. + function negate(G1Point memory p) internal pure returns (G1Point memory) { + // The prime q in the base field F_q for G1 + uint256 q = + 21888242871839275222246405745257275088696311157297823662689037894645226208583; + if (p.X == 0 && p.Y == 0) return G1Point(0, 0); + return G1Point(p.X, q - (p.Y % q)); + } + + /// @return r the sum of two points of G1 + function addition(G1Point memory p1, G1Point memory p2) + internal + view + returns (G1Point memory r) + { + uint256[4] memory input; + input[0] = p1.X; + input[1] = p1.Y; + input[2] = p2.X; + input[3] = p2.Y; + bool success; + assembly { + success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success); + } + + /// @return r the sum of two points of G2 + function addition(G2Point memory p1, G2Point memory p2) + internal + view + returns (G2Point memory r) + { + (r.X[0], r.X[1], r.Y[0], r.Y[1]) = BN256G2.ECTwistAdd( + p1.X[0], + p1.X[1], + p1.Y[0], + p1.Y[1], + p2.X[0], + p2.X[1], + p2.Y[0], + p2.Y[1] + ); + } + + /// @return r the product of a point on G1 and a scalar, i.e. + /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p. + function scalar_mul(G1Point memory p, uint256 s) + internal + view + returns (G1Point memory r) + { + uint256[3] memory input; + input[0] = p.X; + input[1] = p.Y; + input[2] = s; + bool success; + assembly { + success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success); + } + + /// @return the result of computing the pairing check + /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 + /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should + /// return true. + function pairing(G1Point[] memory p1, G2Point[] memory p2) + internal + view + returns (bool) + { + require(p1.length == p2.length); + uint256 elements = p1.length; + uint256 inputSize = elements * 6; + uint256[] memory input = new uint256[](inputSize); + for (uint256 i = 0; i < elements; i++) { + input[i * 6 + 0] = p1[i].X; + input[i * 6 + 1] = p1[i].Y; + input[i * 6 + 2] = p2[i].X[1]; + input[i * 6 + 3] = p2[i].X[0]; + input[i * 6 + 4] = p2[i].Y[1]; + input[i * 6 + 5] = p2[i].Y[0]; + } + uint256[1] memory out; + bool success; + assembly { + success := staticcall( + sub(gas(), 2000), + 8, + add(input, 0x20), + mul(inputSize, 0x20), + out, + 0x20 + ) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success); + return out[0] != 0; + } + + /// Convenience method for a pairing check for two pairs. + function pairingProd2( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](2); + G2Point[] memory p2 = new G2Point[](2); + p1[0] = a1; + p1[1] = b1; + p2[0] = a2; + p2[1] = b2; + return pairing(p1, p2); + } + + /// Convenience method for a pairing check for three pairs. + function pairingProd3( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](3); + G2Point[] memory p2 = new G2Point[](3); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + return pairing(p1, p2); + } + + /// Convenience method for a pairing check for four pairs. + function pairingProd4( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2, + G1Point memory d1, + G2Point memory d2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](4); + G2Point[] memory p2 = new G2Point[](4); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p1[3] = d1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + p2[3] = d2; + return pairing(p1, p2); + } +} + +// GENERATED FROM ZOKRATES: DO NOT MODIFY +library Verifier { + using Pairing for *; + struct VerifyingKey { + Pairing.G1Point alpha; + Pairing.G2Point beta; + Pairing.G2Point gamma; + Pairing.G2Point delta; + Pairing.G1Point[] gamma_abc; + } + struct Proof { + Pairing.G1Point a; + Pairing.G2Point b; + Pairing.G1Point c; + } + + function verifyingKey() internal pure returns (VerifyingKey memory vk) { + vk.alpha = Pairing.G1Point( + uint256( + 0x0c2c2e168d87d1684035486de2420c58c37da837f7bfb2dbfe0f287afe4a2eea + ), + uint256( + 0x0e3444bdc6a17ede7e33c698147d3863ac0496da0eb73e34a8f3cfd1304c858a + ) + ); + vk.beta = Pairing.G2Point( + [ + uint256( + 0x2f7a251d8acb64e8463ee8ca0dd08db5aa90403937f5aa61ee63974d1d898c30 + ), + uint256( + 0x24816c66ec102927b56f3e68b27eca3a47b090452fd54b6da2f54a50b40da6a5 + ) + ], + [ + uint256( + 0x10be79a5ef2d53ad8843791072b811757dcaf41e3e4fc7401f8f7830b8f7f2bb + ), + uint256( + 0x1348e74ec819116944535cb6367b745f92e5c784adc5043b930bf0548548c6b4 + ) + ] + ); + vk.gamma = Pairing.G2Point( + [ + uint256( + 0x0c74bf255f3e329b340dbc67f6a9d8c02c3f5021d6e9a13db1b4a6f8487e4a11 + ), + uint256( + 0x14d8fcc4aab4968b769def3d390b41bc423fe995c44dbed47b20bc3bb117f805 + ) + ], + [ + uint256( + 0x1fac18d4f1f6b0973047abc2f9e4e2361453e8686c483a4a717c5d5b549e5596 + ), + uint256( + 0x067f11b360d20c3510184de12d660dbdc01a11011032b92e9730f66e9305af0e + ) + ] + ); + vk.delta = Pairing.G2Point( + [ + uint256( + 0x2d6496e60bf4733f70c8ce6910aeeb87cd089d1b29bf5d69beda21d6ede23899 + ), + uint256( + 0x088c702a6a3c939b00d29ae5a8da00d77d6d283404726a6ffa33c482396c1fc1 + ) + ], + [ + uint256( + 0x0f5ceb82977b16796d31bb72d2175aec5dd56983a8f6941c6f34749607068ccd + ), + uint256( + 0x1e07d06a10ef154350ae1b03e7938968aeb95b986cff74e502886ba947e33d4b + ) + ] + ); + vk.gamma_abc = new Pairing.G1Point[](27); + vk.gamma_abc[0] = Pairing.G1Point( + uint256( + 0x048c774bb304a3703b9c0fb66b48dd2f0815c272ad8684106f2079f40c17b635 + ), + uint256( + 0x2bb537ccdbe718d6a7f5f632ad989d9648643357b6d7ced2ec2335749d720015 + ) + ); + vk.gamma_abc[1] = Pairing.G1Point( + uint256( + 0x235bec5a5dd7653a1f4bd40524732479be1ec3a069293c7e648985ad9c73a421 + ), + uint256( + 0x0d74c345dcfd970d07a192d2e67b8381f1eb7ae21a6eac65405b27e223ccd224 + ) + ); + vk.gamma_abc[2] = Pairing.G1Point( + uint256( + 0x163f3c4a5398e396fb61c28f33e12ba62c5656a4d9358d5f9d9450e7a999336a + ), + uint256( + 0x2314fb832d97759ae603745ef89d559e9b6a0fdf381f0de4be062aff3d03e918 + ) + ); + vk.gamma_abc[3] = Pairing.G1Point( + uint256( + 0x037ed0d8ad3138a761917ab122183df90edb64427fd801129beb2187b482e6e4 + ), + uint256( + 0x00a402a2772280119ed200af9bd8efbf2ab675b4fb4197043530b09ed6beaf6b + ) + ); + vk.gamma_abc[4] = Pairing.G1Point( + uint256( + 0x2568574b41afc9c9228a09acee3ce824ac82adfca40bdf8095ac1530b9d58ec8 + ), + uint256( + 0x2a36865b457c454391b4ef1470835fa58f9c5ac35fb3687b309cd25f3143bea8 + ) + ); + vk.gamma_abc[5] = Pairing.G1Point( + uint256( + 0x05c1562939c17dd3e871dcd22da5e550bcd8e353131d96944207bbc3db3dc320 + ), + uint256( + 0x06a907b9522826493cd8c379123a4de66dd35204cebc47d7c7a3adf8b6d507a0 + ) + ); + vk.gamma_abc[6] = Pairing.G1Point( + uint256( + 0x14b20a07e94bf7ff609ea512835520a49aafc3c886154f8eb06b4c3d77625ef6 + ), + uint256( + 0x17582b8644d07221f572e242a2b3d0b4a09a12601a17cc2ea5a531cae46e09d3 + ) + ); + vk.gamma_abc[7] = Pairing.G1Point( + uint256( + 0x20662047733d71659032e059f4a7d0263fce812299e7ac82d6497f1921b20ca6 + ), + uint256( + 0x13fbd98112c34a350e24dcd663d35e0680eae2a84c1f831a33d141d1e1385963 + ) + ); + vk.gamma_abc[8] = Pairing.G1Point( + uint256( + 0x0060b6cfa606deed7b6dae8104e892f5e7fbe7847c6fb1277197a2dddeca75c4 + ), + uint256( + 0x136b9edbd2554c2752d2304cf89b0e079fd75fee51e52a22800afb4de638161e + ) + ); + vk.gamma_abc[9] = Pairing.G1Point( + uint256( + 0x1fbbf2e9f1004e9f39d9f74dc7241f94b5e57c78fff1e4be446b108ac1e16910 + ), + uint256( + 0x0227ac39493820d595a984e8911dfa0fe019c98faa60cc4352c668c8f8e25c81 + ) + ); + vk.gamma_abc[10] = Pairing.G1Point( + uint256( + 0x0dbb3f53e6b580752a509a397eabacb727a808980d461676cb0d2568e90a3c07 + ), + uint256( + 0x157fc2d22d16e901274c3a2f44011ec307386cbd0ab34b2d05a1d40f3f231049 + ) + ); + vk.gamma_abc[11] = Pairing.G1Point( + uint256( + 0x1a22ef3bfdc413d5c60e27c3c58a2eba28a5842fc4659997c4020da81791ace5 + ), + uint256( + 0x0ffddc700ba6acac16e5a0f5b38a8627d4407875afa09b20a139fe6b706975dd + ) + ); + vk.gamma_abc[12] = Pairing.G1Point( + uint256( + 0x02c3ae50a8bf4d28b108b169f450ecb39a812f069da980353374f318be3a28b0 + ), + uint256( + 0x1f7c504e20d128d331c183b2afdba0a23642249fbc496584ae12983927514ed7 + ) + ); + vk.gamma_abc[13] = Pairing.G1Point( + uint256( + 0x10ccbb99683bbd1c18b8142507551b4c7c1146d09968d3808d273c187659f476 + ), + uint256( + 0x21a4f68a9cb6f793b0af06d584597c2120db311819655ba87e526c6082c4a957 + ) + ); + vk.gamma_abc[14] = Pairing.G1Point( + uint256( + 0x2e1360fbc5d0c1be6fdc094312308186e0e5efac92f4459b53e3e36dc291db41 + ), + uint256( + 0x2cc44704510f38d6c0004f3e6eb05de91b1628a49a4173937a7fd6e2bca8255f + ) + ); + vk.gamma_abc[15] = Pairing.G1Point( + uint256( + 0x15a747c034ec8946e0982797044d51fd7c5039d0dc7a6c7ab32e61ca3e7f0d36 + ), + uint256( + 0x16b7e40d6a47ca7d7301fa62abf6a1a2602ab05e577634da81ec0d1365c0f563 + ) + ); + vk.gamma_abc[16] = Pairing.G1Point( + uint256( + 0x13362639e6acf6ae865e4680a8fdf3d74c26fd7e98f5f9ef7c399eb6948a6653 + ), + uint256( + 0x23c42150e11edef6226e4b7abce281b46cd746d59dea5f67340f791e9ad27d3b + ) + ); + vk.gamma_abc[17] = Pairing.G1Point( + uint256( + 0x2715cfdd56d0ad0e257562f5960552c5653d24d39b99833945b4cc01804a01f2 + ), + uint256( + 0x18e214ab3a7feca580377c6d7e9d8d7a59373a47110e84614b8e4df3f22c3ec9 + ) + ); + vk.gamma_abc[18] = Pairing.G1Point( + uint256( + 0x193ed4a1027b5f0de0f80cf8138e0cc176cdbaaaf0a3e32b7db79b0aa8250f6c + ), + uint256( + 0x024f9d88b54465831b1d449fcb9fe88e44a80cae1e959122709850006b667d21 + ) + ); + vk.gamma_abc[19] = Pairing.G1Point( + uint256( + 0x1185ad1747fbe95bec47c10a4a165b9dfaec64c3771a6dd2315515b61d13e62c + ), + uint256( + 0x2597080b83f49f9d3bb81b5d4a29737ee99887b361513cc8af1c15bf98799717 + ) + ); + vk.gamma_abc[20] = Pairing.G1Point( + uint256( + 0x07fee61e25fdc9896bbaf21bda05674091d847f570b8b97591ce5e475183f95c + ), + uint256( + 0x0d4e38f3884879a8c88b32833027d718100d3c64740d9b556304df02231e13eb + ) + ); + vk.gamma_abc[21] = Pairing.G1Point( + uint256( + 0x12416db4dce0ac6b6fe53af3d053917cdb7d2bacaa6f70e62d16723af2cf8cac + ), + uint256( + 0x1dcc06908670dc8c32fb92ce699b0f9ad2f8c1cad712d09cd37f9f25800795f5 + ) + ); + vk.gamma_abc[22] = Pairing.G1Point( + uint256( + 0x00ff1b4cf07f5338ccd8bd8e38e6c47271bc169499fd21c5b2382493ff6f4481 + ), + uint256( + 0x20142bbb11e4c4680e54b6d88606674658c59eaf1cd0bc1cc024718145f79ca9 + ) + ); + vk.gamma_abc[23] = Pairing.G1Point( + uint256( + 0x221b518df5f867010f5d410d957ca535ed82c001262219da60b36e3199b0ef81 + ), + uint256( + 0x174f2a8f077043cc9f0c7f2497be95b57866b2a8f7ce4b611df224d7df2c42f0 + ) + ); + vk.gamma_abc[24] = Pairing.G1Point( + uint256( + 0x2a97f439f8a45c402c2a94000ba2af0660a01c8c2c3450a4f77eadf0d2a9569f + ), + uint256( + 0x231c640b63d7a50e9988c2b6f2c95024336a0d52a8f79689ef22a4afaf2a11b8 + ) + ); + vk.gamma_abc[25] = Pairing.G1Point( + uint256( + 0x20a0729e4668231bb82921be1809381b44b26b25e94bdc51a2aaf4ef38f2a6f4 + ), + uint256( + 0x1ba23046e491e6bbed2554c0f3e8412a787227310ee64eebc5ceb6b24ab621ad + ) + ); + vk.gamma_abc[26] = Pairing.G1Point( + uint256( + 0x0396df78e09ffe44fe1919675e3a30df164f2ac08a70b0cdddfe69b71f96b986 + ), + uint256( + 0x28edd87998214844a8f88c6632d65e969a0c65a852257fdf6b7893cb729115ab + ) + ); + } + + function verify(uint256[] memory input, Proof memory proof) + internal + view + returns (uint256) + { + uint256 snark_scalar_field = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; + VerifyingKey memory vk = verifyingKey(); + require(input.length + 1 == vk.gamma_abc.length); + // Compute the linear combination vk_x + Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); + for (uint256 i = 0; i < input.length; i++) { + require(input[i] < snark_scalar_field); + vk_x = Pairing.addition( + vk_x, + Pairing.scalar_mul(vk.gamma_abc[i + 1], input[i]) + ); + } + vk_x = Pairing.addition(vk_x, vk.gamma_abc[0]); + if ( + !Pairing.pairingProd4( + proof.a, + proof.b, + Pairing.negate(vk_x), + vk.gamma, + Pairing.negate(proof.c), + vk.delta, + Pairing.negate(vk.alpha), + vk.beta + ) + ) return 1; + return 0; + } + + function verifyTx(Proof memory proof, uint256[26] memory input) + internal + view + returns (bool r) + { + uint256[] memory inputValues = new uint256[](26); + + for (uint256 i = 0; i < input.length; i++) { + inputValues[i] = input[i]; + } + if (verify(inputValues, proof) == 0) { + return true; + } else { + return false; + } + } +} diff --git a/contracts/market/data/states.sol b/contracts/market/data/states.sol new file mode 100644 index 000000000..1c619ba91 --- /dev/null +++ b/contracts/market/data/states.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.3; + +library States { + enum StatesCode { + AK, + AL, + AR, + AZ, + CA, + CO, + CT, + DC, + DE, + FL, + GA, + HI, + IA, + ID, + IL, + IN, + KS, + KY, + LA, + MA, + MD, + ME, + MI, + MN, + MO, + MS, + MT, + NC, + ND, + NE, + NH, + NJ, + NM, + NV, + NY, + OH, + OK, + OR, + PA, + RI, + SC, + SD, + TN, + TX, + UT, + VA, + VT, + WA, + WI, + WV, + WY + } +} + +contract Rates { + uint16[] public rates = [ + 1050, + 600, + 500, + 1000, + 1000, + 800, + 1200, + 600, + 500, + 1200, + 700, + 1200, + 500, + 1200, + 900, + 2100, + 1000, + 800, + 1200, + 600, + 600, + 800, + 500, + 800, + 900, + 800, + 1000, + 800, + 550, + 600, + 1000, + 600, + 1500, + 0, + 1600, + 800, + 600, + 900, + 600, + 1200, + 875, + 1500, + 1000, + 600, + 1000, + 800, + 1200, + 1200, + 500, + 600, + 700 + ]; +} diff --git a/contracts/market/libraries/LibConsensus.sol b/contracts/market/libraries/LibConsensus.sol deleted file mode 100644 index 747261fa9..000000000 --- a/contracts/market/libraries/LibConsensus.sol +++ /dev/null @@ -1,282 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { AppStorageLib } from "../../storage/app.sol"; -import { - MarketStorageLib, - MarketStorage, - LoanRequest, - LoanUserRequest, - LoanConsensusResponse, - Signature -} from "../../storage/market.sol"; -import { NumbersLib } from "../../shared/libraries/NumbersLib.sol"; -import { NumbersList } from "../../shared/libraries/NumbersList.sol"; -import { - PlatformSettingsLib -} from "../../settings/platform/libraries/PlatformSettingsLib.sol"; -import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import { RolesLib } from "../../contexts2/access-control/roles/RolesLib.sol"; -import { - EnumerableSet -} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - -library LibConsensus { - using NumbersList for NumbersList.Values; - - /** - * @notice Represents loan terms based on consensus values - * @param interestRate The consensus value for the interest rate based on all the loan responses from the signers - * @param collateralRatio The consensus value for the ratio of collateral to loan amount required for the loan, based on all the loan responses from the signers - * @param maxLoanAmount The consensus value for the largest amount of tokens that can be taken out in the loan, based on all the loan responses from the signers - */ - struct AccruedLoanTerms { - NumbersList.Values interestRate; - NumbersList.Values collateralRatio; - NumbersList.Values maxLoanAmount; - } - - function s() private pure returns (MarketStorage storage) { - return MarketStorageLib.store(); - } - - /** - * @notice it processes a loan terms by doing multiple checks on the LoanRequest request and LoanResponse[] responses - * @param request LoanRequest is the borrower request object to take out a loan - * @return interestRate the borrower needs to pay back - * @return collateralRatio the ratio of collateral the borrower needs to put up for the loan with an underlying asset - * @return maxLoanAmount the borrower is entitled for - */ - function processLoanTerms(LoanRequest calldata request) - internal - view - returns ( - uint16 interestRate, - uint16 collateralRatio, - uint256 maxLoanAmount - ) - { - // get the signers from the asset address - EnumerableSet.AddressSet storage signers = s().signers[ - request.request.assetAddress - ]; - - require( - uint256( - NumbersLib.ratioOf( - request.responses.length, - EnumerableSet.length(signers) - ) - ) >= PlatformSettingsLib.getRequiredSubmissionsPercentageValue(), - "Teller: insufficient signer responses" - ); - - _validateLoanRequest( - request.request.borrower, - request.request.requestNonce - ); - - uint32 chainId = _getChainId(); - bytes32 requestHash = _hashRequest(request.request, chainId); - - // create term submissions for every response ... - AccruedLoanTerms memory termSubmissions; - - for (uint256 i = 0; i < request.responses.length; i++) { - LoanConsensusResponse memory response = request.responses[i]; - - // check if the signers contains the response's signer - require( - EnumerableSet.contains(signers, response.signer), - "Teller: invalid signer" - ); - - // check if the request's asset address equates to the response's asset address - require( - response.assetAddress == request.request.assetAddress, - "Teller: consensus address mismatch" - ); - - // check if consensus response has expired - require( - uint256(response.responseTime) >= - block.timestamp - - PlatformSettingsLib.getTermsExpiryTimeValue(), - "Teller: consensus response expired" - ); - - // check if the signature of hashed response data matches - require( - _signatureValid( - response.signature, - _hashResponse(requestHash, response, chainId), - response.signer - ), - "Teller: response signature invalid" - ); - - // TODO: use a local AddressArrayLib instead to save gas - for (uint8 j = 0; j < i; j++) { - require( - response.signer != request.responses[j].signer, - "Teller: dup signer response" - ); - } - - termSubmissions.interestRate.addValue(response.interestRate); - termSubmissions.collateralRatio.addValue(response.collateralRatio); - termSubmissions.maxLoanAmount.addValue(response.maxLoanAmount); - } - - // get maximum tolerance value in order to receive the interestRate, collateralRatio and maxLoanAmount - uint16 tolerance = uint16( - PlatformSettingsLib.getMaximumToleranceValue() - ); - interestRate = uint16( - _getConsensus(termSubmissions.interestRate, tolerance) - ); - collateralRatio = uint16( - _getConsensus(termSubmissions.collateralRatio, tolerance) - ); - maxLoanAmount = _getConsensus(termSubmissions.maxLoanAmount, tolerance); - } - - /** - * @dev Checks if the nonce provided in the request is equal to the borrower's number of loans. - * @dev Also verifies if the borrower has taken out a loan recently (rate limit). - * @param borrower the borrower's address. - * @param nonce the nonce included in the loan request. - */ - function _validateLoanRequest(address borrower, uint256 nonce) - private - view - { - uint128[] storage borrowerLoans = s().borrowerLoans[borrower]; - uint256 numberOfLoans = borrowerLoans.length; - - require(nonce == numberOfLoans, "Teller: bad request nonce"); - - // In case it is the first time that borrower requests loan terms, we don't - // validate the rate limit. - if (numberOfLoans == 0) { - return; - } - - require( - uint256( - s() - .loans[uint256(borrowerLoans[numberOfLoans - 1])] - .loanStartTime - ) + - PlatformSettingsLib.getRequestLoanTermsRateLimitValue() <= - block.timestamp, - "Teller: loan terms rate limit reached" - ); - } - - /** - * @notice Gets the current chain id using the opcode 'chainid()'. - * @return id_ The current chain id. - */ - function _getChainId() private view returns (uint32 id_) { - // silence state mutability warning without generating bytecode. - // see https://github.com/ethereum/solidity/issues/2691 - assembly { - id_ := chainid() - } - } - - /** - * @notice Generates a hash for the loan request - * @param request Struct of the protocol loan request - * @return bytes32 Hash of the loan request - */ - function _hashRequest(LoanUserRequest memory request, uint32 chainId) - private - pure - returns (bytes32) - { - return - keccak256( - abi.encode( - request.borrower, - request.assetAddress, - request.amount, - request.requestNonce, - request.duration, - request.requestTime, - chainId - ) - ); - } - - /** - * @notice Generates a hash for the loan response - * @param requestHash Hash of the loan request - * @param response Structs of the protocol loan responses - * @return bytes32 Hash of the loan response - */ - function _hashResponse( - bytes32 requestHash, - LoanConsensusResponse memory response, - uint32 chainId - ) internal pure returns (bytes32) { - return - keccak256( - abi.encode( - response.assetAddress, - response.maxLoanAmount, - requestHash, - response.responseTime, - response.interestRate, - response.collateralRatio, - chainId - ) - ); - } - - /** - * @notice It validates whether a signature is valid or not. - * @param signature signature to validate. - * @param dataHash used to recover the signer. - * @param expectedSigner the expected signer address. - * @return true if the expected signer is equal to the signer. Otherwise it returns false. - */ - function _signatureValid( - Signature memory signature, - bytes32 dataHash, - address expectedSigner - ) internal pure returns (bool) { - return - expectedSigner == - ECDSA.recover( - keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - dataHash - ) - ), - signature.v, - signature.r, - signature.s - ); - } - - /** - * @notice Gets the consensus value for a list of values (uint values). - * @notice The values must be in a maximum tolerance range. - * @return the consensus value. - */ - function _getConsensus(NumbersList.Values memory values, uint16 tolerance) - internal - pure - returns (uint256) - { - require( - values.isWithinTolerance(tolerance), - "Teller: consensus response values too varied" - ); - - return values.getAverage(); - } -} diff --git a/contracts/market/libraries/LibLoans.sol b/contracts/market/libraries/LibLoans.sol index fc91f6799..3f93170f2 100644 --- a/contracts/market/libraries/LibLoans.sol +++ b/contracts/market/libraries/LibLoans.sol @@ -48,7 +48,12 @@ library LibLoans { d_ = s().loanDebt[loanID]; } - // DEPRECATED + /** + * @notice it returns the loan terms from a respective loan + * @notice DEPRECATED + * @param loanID the ID of the respective loan + * @return t_ the loan terms from a respective loan + */ function terms(uint256 loanID) internal view @@ -86,6 +91,11 @@ library LibLoans { return amountBorrow.percent(uint16(getInterestRatio(loanID))); } + /** + * @notice it returns the collateral needed in tokens + * @param loanID the identifier of the loan to return the collateral from + * @return _needed the collateral tokens needed for a loan + */ function getCollateralNeeded(uint256 loanID) internal returns (uint256 _needed) diff --git a/contracts/shared/interfaces/ITellerDiamond.sol b/contracts/shared/interfaces/ITellerDiamond.sol index 74524a29d..e7fa9f54e 100644 --- a/contracts/shared/interfaces/ITellerDiamond.sol +++ b/contracts/shared/interfaces/ITellerDiamond.sol @@ -17,6 +17,8 @@ import { import { PausableFacet } from "../../settings/pausable/PausableFacet.sol"; import { LendingFacet } from "../../lending/LendingFacet.sol"; import { CreateLoanFacet } from "../../market/CreateLoanFacet.sol"; +import { CreateLoanNFTFacet } from "../../market/CreateLoanNFTFacet.sol"; +import { CreateLoanSnarkFacet } from "../../market/CreateLoanSnarkFacet.sol"; import { LoanDataFacet } from "../../market/LoanDataFacet.sol"; import { RepayFacet } from "../../market/RepayFacet.sol"; import { SignersFacet } from "../../market/SignersFacet.sol"; @@ -28,7 +30,13 @@ import { } from "../../escrow/dapps/CompoundClaimComp.sol"; import { AaveFacet } from "../../escrow/dapps/AaveFacet.sol"; import { PoolTogetherFacet } from "../../escrow/dapps/PoolTogetherFacet.sol"; +import { ProviderFactoryFacet } from "../../market/ProviderFactoryFacet.sol"; +// When adding a new Facet, kindly make sure to order the inherited contracts around so +// that it's consistent with all the Facets' inheritance order. For example, if all the +// Facets import the mods such as `is ReentryMods, PausableMods`, but a new facet imports +// the mod as `is PausableMods, ReentryMods`, we will get an error called `Linearization of inheritance +// impossible`. Kindly order the Inheritances around in the same order to pass this error. abstract contract ITellerDiamond is SettingsFacet, PlatformSettingsFacet, @@ -38,6 +46,8 @@ abstract contract ITellerDiamond is LendingFacet, CollateralFacet, CreateLoanFacet, + CreateLoanNFTFacet, + CreateLoanSnarkFacet, LoanDataFacet, RepayFacet, SignersFacet, @@ -47,5 +57,8 @@ abstract contract ITellerDiamond is AaveFacet, PoolTogetherFacet, IDiamondCut, - IDiamondLoupe -{} + IDiamondLoupe, + ProviderFactoryFacet +{ + +} diff --git a/contracts/storage/market.sol b/contracts/storage/market.sol index 99ae60928..7ecbb6c81 100644 --- a/contracts/storage/market.sol +++ b/contracts/storage/market.sol @@ -1,17 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -// Libraries +// External Libraries import { Counters } from "@openzeppelin/contracts/utils/Counters.sol"; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +// Teller Libraries import "../shared/libraries/NumbersList.sol"; +import { Verifier } from "../market/cra/verifier.sol"; -// Interfaces +// Teller Interfaces import { ILoansEscrow } from "../escrow/escrow/ILoansEscrow.sol"; import { ICollateralEscrow } from "../market/collateral/ICollateralEscrow.sol"; import { ITToken } from "../lending/ttoken/ITToken.sol"; +import { States } from "../market/data/states.sol"; // DEPRECATED struct LoanTerms { @@ -59,27 +63,49 @@ struct LoanDebt { uint256 interestOwed; } -struct LoanRequest { +struct LoanRequestNFT { + address payable borrower; + address assetAddress; + uint32 duration; +} + +/** + * @notice our loan request to be sent to our borrow function to verify Proof with the witness, + verify our signature data, process the market score to get our interest rate, then create a loan + if market score is sufficient + * @param request the user request containing all the necessary information for our loan request + * @param marketId the market we are borrowing a loan from + * @param proof the proof to verify + * @param witness the witness that contains our identifier, score and commitment data + * @param signatureData the signatureData that is used to validate against the commitments we construct + */ +struct LoanRequestSnark { LoanUserRequest request; - LoanConsensusResponse[] responses; + address marketHandlerAddress; + Verifier.Proof snarkProof; + uint256[26] snarkWitnesses; + DataProviderSignature[] dataProviderSignatures; + address[] providers; } /** * @notice Borrower request object to take out a loan * @param borrower The wallet address of the borrower * @param assetAddress The address of the asset for the requested loan - * @param amount The amount of tokens requested by the borrower for the loan - * @param requestNonce The nonce of the borrower wallet address required for authentication + * @param assetAmount The amount of tokens requested by the borrower for the loan + * @param collateralAsset the asset provided by the user as collateral to the loan + * @param collateralAmount the amount of the above collateral * @param duration The length of time in seconds that the loan has been requested for - * @param requestTime The timestamp at which the loan was requested */ struct LoanUserRequest { address payable borrower; address assetAddress; - uint256 amount; - uint32 requestNonce; + address collateralAsset; + uint16 collateralRatio; uint32 duration; - uint32 requestTime; + uint256 collateralAmount; + uint256 assetAmount; + States.StatesCode code; } /** @@ -102,6 +128,16 @@ struct LoanConsensusResponse { Signature signature; } +/** + * @notice Loan request object with the cra responses + * @param request the loan user request + * @param responses the cra responses + */ +struct LoanRequestWithResponse { + LoanUserRequest request; + LoanConsensusResponse[] responses; +} + /** * @notice Represents a user signature * @param v The recovery identifier represented by the last byte of a ECDSA signature as an int @@ -114,6 +150,52 @@ struct Signature { bytes32 s; } +/** + * @notice It represents signature data from our data providers + * @param signature signature from our data provider + * @param signedAt the timed they signed at + */ +struct DataProviderSignature { + Signature signature; + uint256 signedAt; +} + +/** + * @notice It represents the provider configuration respective to a market + * @param admin a mapping of all admins in a provider configuration + * @param signer a mapping of all accepted signers in a provider configuration + * @param maxAge a uint used as a check to see if current blocktime stamp - maxAge is less than + * the moment the provider signed at + */ +struct ProviderConfig { + mapping(address => bool) admin; + mapping(address => bool) signer; + uint32 maxAge; +} + +/** + * @notice It represents the information needed from a market when users request loans from the + * respective market + * @param maxInterestRate the max interest rate in a market + * @param maxCollateralRatio the max collateral ratio in a market + * @param maxLoanAmount the max loan amount in a market + */ +struct MarketInformation { + uint16 maxInterestRate; + uint16 maxCollateralRatio; + uint256 maxLoanAmount; +} + +/** + * @notice it represents information of a market configuration which contains the admin and provider configurations + * @param admin a mapping of addresses to an administrator + * @param providerConfigs a mapping of ID to provider config + */ +struct MarketConfig { + mapping(address => bool) admin; + MarketInformation marketInformation; +} + struct MarketStorage { // Holds the index for the next loan ID Counters.Counter loanIDCounter; diff --git a/deploy/protocol.ts b/deploy/protocol.ts index 0aab70bbc..72be026ac 100644 --- a/deploy/protocol.ts +++ b/deploy/protocol.ts @@ -25,6 +25,7 @@ const deployProtocol: DeployFunction = async (hre) => { const { address: nftAddress } = await contracts.get('TellerNFT') const loansEscrowBeacon = await deployLoansEscrowBeacon(hre) + const market = await deployMarket(hre) const collateralEscrowBeacon = await deployCollateralEscrowBeacon(hre) const tTokenBeacon = await deployTTokenBeacon(hre) const nftDictionary = await contracts.get('TellerNFTDictionary') @@ -111,11 +112,14 @@ const deployProtocol: DeployFunction = async (hre) => { // Loans { contract: 'CollateralFacet', - skipIfAlreadyDeployed: false, + skipIfAlreadyDeployed: true, }, { contract: 'CreateLoanFacet', skipIfAlreadyDeployed: false, + libraries: { + ProcessRequestLib: '0xe7168c514A022345ed07E4Fad73eC3921C2b7bDb', + }, }, { contract: 'LoanDataFacet', @@ -129,6 +133,14 @@ const deployProtocol: DeployFunction = async (hre) => { contract: 'SignersFacet', skipIfAlreadyDeployed: false, }, + { + contract: 'ProviderFactoryFacet', + skipIfAlreadyDeployed: true, + }, + { + contract: 'ProviderFactoryFacet', + skipIfAlreadyDeployed: true, + }, // NFT { contract: 'NFTFacet', @@ -245,6 +257,28 @@ const addAuthorizedAddresses = async ( .then(({ wait }) => wait()) } +const deployMarket = async (hre: HardhatRuntimeEnvironment): Promise => { + const { ethers, log } = hre + log('********** Deploying Teller Market **********', { indent: 2 }) + log('') + const processRequestLib = await deploy({ + hre, + contract: 'ProcessRequestLib', + log: true, + }) + + // teller market values + const maxInterestRate = 3500 + const maxCollateralRatio = 15000 + const maxLoanAmount = 25000 + const tellerMarketHandler = await deploy({ + hre, + contract: 'TellerMarketHandler', + log: true, + args: [maxInterestRate, maxCollateralRatio, maxLoanAmount], + }) +} + const deployLoansEscrowBeacon = async ( hre: HardhatRuntimeEnvironment ): Promise => { diff --git a/hardhat.config.ts b/hardhat.config.ts index 92b5d98b5..c15869d8b 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -220,6 +220,6 @@ export default { }), }, mocha: { - timeout: 10000000, + timeout: 1000000000, }, } diff --git a/package.json b/package.json index 158cceac7..d3ecee423 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,8 @@ "ts-generator": "^0.1.1", "ts-node": "^9.1.1", "typechain": "^4.0.3", - "typescript": "^4.1.3" + "typescript": "^4.1.3", + "zokrates-js": "^1.0.33" }, "husky": { "hooks": { diff --git a/test/fixtures/zk-scores.ts b/test/fixtures/zk-scores.ts new file mode 100644 index 000000000..180231b6a --- /dev/null +++ b/test/fixtures/zk-scores.ts @@ -0,0 +1,71 @@ +let scores: any +export default scores = { + // this is a good score as the first array element of each array has a hex value of 10 + // the rest of the elements is the mock secret provided by the provider + good: [ + [ + '0x0000000a', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000001', + ], + [ + '0x0000000a', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000002', + ], + [ + '0x0000000a', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000003', + ], + ], + // this is a good score as the first array element of each array has a hex value of 3 + // the rest of the elements is the mock secret provided by the provider + bad: [ + [ + '0x00000003', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000001', + ], + [ + '0x00000003', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000002', + ], + [ + '0x00000003', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000000', + '0x00000003', + ], + ], +} diff --git a/test/helpers/loans.ts b/test/helpers/loans.ts index b0389c98a..974e31b60 100644 --- a/test/helpers/loans.ts +++ b/test/helpers/loans.ts @@ -1,3 +1,7 @@ +import { isBytesLike } from '@ethersproject/bytes' +import { toUtf8Bytes } from '@ethersproject/strings' +import { time, timeStamp } from 'console' +import { createSign } from 'crypto' import { BigNumber, BigNumberish, @@ -7,10 +11,27 @@ import { } from 'ethers' import { HardhatRuntimeEnvironment } from 'hardhat/types' import moment from 'moment' - +// zkcra imports +import { + CompilationArtifacts, + ComputationResult, + initialize, + Proof, + SetupKeypair, + ZoKratesProvider, + // @ts-expect-error: because we are looking for the /node pkg +} from 'zokrates-js/node' + +const zkcraJson = `https://ipfs.io/ipfs/QmPRctNbW2q1TdrJAp2E1CkafJuCEzDKYtrqpYoHDkpXuR?filename=zkcra.json` +import { JsonRpcBatchProvider } from '@ethersproject/providers' +import { readFileSync, writeFile, writeFileSync } from 'fs' +import { join } from 'path' + +// teller files import { getNativeToken } from '../../config' import { claimNFT, getPrice } from '../../tasks' import { ERC20, ITellerDiamond, TellerNFT } from '../../types/typechain' +import scores from '../fixtures/zk-scores' import { getFunds } from './get-funds' import { mockCRAResponse } from './mock-cra-response' @@ -65,7 +86,6 @@ export const loanHelpers = async ( }, } } - interface CreateLoanWithNftArgs { lendToken: string | ERC20 borrower?: string @@ -84,6 +104,21 @@ export interface CreateLoanArgs { duration?: moment.Duration nft?: boolean } + +interface ZKCRAConfigArgs { + numberOfProviders: any +} + +interface CreateLoanWithZKCRA { + proof: Proof + computation?: ComputationResult + providerAddresses?: any +} + +interface ZKCRAConfigReturn { + numberOfSignaturesRequired: any + providerAddresses: string[] +} export interface CreateLoanReturn { tx: Promise getHelpers: () => Promise @@ -152,101 +187,53 @@ export const createLoan = async ( } /** - * @description: function helper that sets the collateral token and ratio and creates a mock CRA - * response to plug into the newly merged create loan function that: - * - sets the terms - * - deposits collateral - * - takes out the loan - * - * @param args: CreateLoanArgs parameters to create the loan - * @returns: Promise helper variables to help run our tests + * @description: It creates, signs, apply with NFTs and takes out a loan in one function + * @param args: Arguments we specify to create our Loan by depositing NFT + * @returns Promise that gives us data to help run our tests */ -export const takeOutLoanWithoutNfts = async ( +export const takeOutLoanWithNfts = async ( hre: HardhatRuntimeEnvironment, - args: CreateLoanArgs + args: CreateLoanWithNftArgs ): Promise => { - const { - lendToken, - collToken, - loanType, - amount = 100, - amountBN, - duration = moment.duration(1, 'day'), - } = args - const { contracts, tokens, getNamedAccounts, toBN } = hre - // define diamond contract + const { contracts, tokens, toBN, getNamedSigner } = hre + const { lendToken, amount = 100, duration = moment.duration(1, 'day') } = args + + // diamond contract const diamond = await contracts.get('TellerDiamond') - // lending token - const lendingToken = - typeof lendToken === 'string' ? await tokens.get(lendToken) : lendToken + // get the borrower, deployer and borrower's signer + // const deployer = await getNamedSigner('deployer') + const borrower = '0x86a41524cb61edd8b115a72ad9735f8068996688' + const { signer: borrowerSigner } = await hre.evm.impersonate(borrower) - // collateral token - const collateralToken = - typeof collToken === 'string' ? await tokens.get(collToken) : collToken - const collateralIsNative = - collateralToken.address === getNativeToken(hre.network) + // Claim user's NFT as borrower + await claimNFT({ account: borrower, merkleIndex: 0 }, hre) - // set borrower and loan amount - const borrower = args.borrower ?? (await getNamedAccounts()).borrower - const loanAmount = amountBN ?? toBN(amount, await lendingToken.decimals()) + // Get the sum of loan amount to take out + const nft = await contracts.get('TellerNFT') - // depending on the loan type, we set a different collateral ratio. 10000 = 100% - let collateralRatio = 0 - switch (loanType) { - case LoanType.ZERO_COLLATERAL: - break - case LoanType.UNDER_COLLATERALIZED: - collateralRatio = 5000 - break - case LoanType.OVER_COLLATERALIZED: - collateralRatio = 15000 - break - } + // get all the borrower's NFTs + const ownedNFTs = await nft + .getOwnedTokens(borrower) + .then((arr) => (arr.length > 2 ? arr.slice(0, 2) : arr)) - // create our mock CRA response - const craReturn = await mockCRAResponse(hre, { - lendingToken: lendingToken.address, - loanAmount, - loanTermLength: duration.asSeconds(), - collateralRatio: collateralRatio, - interestRate: '400', - borrower, - }) + // Set NFT approval + await nft.connect(borrowerSigner).setApprovalForAll(diamond.address, true) - const { value: collValue } = await getPrice( + // Stake NFTs by transferring from the msg.sender (borrower) to the diamond + await diamond.connect(borrowerSigner).stakeNFTs(ownedNFTs) + + // plug it in the takeOutLoanWithNfts function along with the proofs to apply to the loan! + const tx = diamond.connect(borrowerSigner).takeOutLoanNFTs( { - src: await lendingToken.symbol(), - dst: await collateralToken.symbol(), - amount: hre.fromBN(loanAmount, await lendingToken.decimals()), + borrower, + assetAddress: '0x6b175474e89094c44da98b954eedeac495271d0f', + duration: moment.duration(1, 'day').asSeconds(), }, - hre + ownedNFTs ) - const collAmount = hre.toBN(collValue, await collateralToken.decimals()) - const nativeAmount = collateralIsNative ? collAmount : BigNumber.from(0) - if (!collateralIsNative) { - await getFunds({ - tokenSym: await collateralToken.symbol(), - amount: collAmount, - to: borrower, - hre, - }) - await collateralToken - .connect(hre.ethers.provider.getSigner(borrower)) - .approve(diamond.address, collAmount) - } - - // call the takeOutLoan function from the diamond - const tx = diamond - .connect(hre.ethers.provider.getSigner(borrower)) - .takeOutLoan( - { request: craReturn.request, responses: craReturn.responses }, - collateralToken.address, - collAmount, - { value: nativeAmount.toString() } - ) - // return our transaction and our helper variable + // return our transaction and our helper variables return { tx, getHelpers: async (): Promise => { @@ -258,69 +245,279 @@ export const takeOutLoanWithoutNfts = async ( } } -/** - * @description: It creates, signs, apply with NFTs and takes out a loan in one function - * @param args: Arguments we specify to create our Loan by depositing NFT - * @returns Promise that gives us data to help run our tests - */ -export const takeOutLoanWithNfts = async ( +// we fill zkCRAConfigInfo before we sign +export const fillZKCRAConfigInfo = async ( hre: HardhatRuntimeEnvironment, - args: CreateLoanWithNftArgs + args: ZKCRAConfigArgs +): Promise => { + const { getNamedAccounts, getNamedSigner, contracts } = hre + + const diamond = await contracts.get('TellerDiamond') + + // get signers (providers) + const { craSigner } = await getNamedAccounts() + + const deployer = await getNamedSigner('deployer') + const providerAddresses_ = [] + + // create random provider + for (let i = 0; i < args.numberOfProviders; i++) { + await diamond.connect(deployer).createProvider() + const providerAddress = await diamond + .connect(deployer) + .providers(i.toString()) + providerAddresses_.push(providerAddress) + const provider = await contracts.get('DataProvider', { + at: providerAddress, + }) + console.log('about to set signer #' + i.toString()) + await provider.connect(deployer).functions.setSigner(craSigner, true) + } + + // getting tellerMarketHandler contract + const marketHandlerAddress = '0x2858023076c86347CDd7DEa4F38aa215cbbCa91b' + const tellerMarketHandler = await contracts.get('MarketHandler', { + at: marketHandlerAddress, + }) + + await tellerMarketHandler + .connect(deployer) + .functions.addProviders(providerAddresses_) + + // number of signatures required + const numberOfSignaturesRequired_ = await tellerMarketHandler + .connect(deployer) + .numberOfSignaturesRequired() + + return { + numberOfSignaturesRequired: numberOfSignaturesRequired_, + providerAddresses: providerAddresses_, + } +} + +export const outputCraValues = async ( + hre: HardhatRuntimeEnvironment, + goodScore: boolean +): Promise => { + const { getNamedAccounts, contracts } = hre + // local variables + let zokratesProvider: ZoKratesProvider + let compilationArtifacts: CompilationArtifacts = null + let keyPair: SetupKeypair + let computation: ComputationResult + let proof: Proof = null + // set provider after initialization + const provider: ZoKratesProvider = await initialize() + console.log('provider initialized') + // zok file to compile + const source = `import "hashes/sha256/256bitPadded.zok" as sha256 + def main(private u32[3][8] data, public field identifier) -> (u32, u32[3][8]): + u32[3][8] commitments = data + u32 MARKET_SCORE = 0 + u32 MASK = 0x0000000a + + for u32 i in 0..3 do + MARKET_SCORE = MARKET_SCORE + data[i][0] & MASK + commitments[i] = sha256(data[i]) + endfor + + return MARKET_SCORE,commitments` + // compile into circuit + console.log('about to compile source') + compilationArtifacts = provider.compile(source) + console.log('compiled source') + + // get borrower nonce and identifier + const diamond = await contracts.get('TellerDiamond') + const borrower = (await getNamedAccounts()).borrower + const { length: nonce } = await diamond.getBorrowerLoans(borrower) + const identifier = BigNumber.from(borrower).xor(nonce) + + // sample data. first element of each array element is the value (Score). + // next 7 elements are the secrets + + // get computation + if (goodScore) { + computation = provider.computeWitness(compilationArtifacts, [ + scores.good, + identifier.toString(), + ]) + } else { + computation = provider.computeWitness(compilationArtifacts, [ + scores.bad, + identifier.toString(), + ]) + } + console.log('witness computed') + + // compute proof + const provingKey = new Uint8Array( + readFileSync( + join(__dirname, '../../contracts/market/cra/proving.key') + ).buffer + ) + proof = provider.generateProof( + compilationArtifacts.program, + computation.witness, + provingKey + ) + console.log('proof generated') + return { + computation: computation, + proof: proof, + } +} +// take out function with zkcra implemented +export const borrowWithZKCRA = async ( + hre: HardhatRuntimeEnvironment, + args: CreateLoanWithZKCRA ): Promise => { - const { contracts, tokens, toBN, getNamedSigner } = hre - const { lendToken, amount = 100, duration = moment.duration(1, 'day') } = args + // get proof and witness from args + const { getNamedAccounts, getNamedSigner, contracts, ethers, tokens, toBN } = + hre + + const { proof, computation, providerAddresses } = args - // diamond contract const diamond = await contracts.get('TellerDiamond') - // lending token - const lendingToken = - typeof lendToken === 'string' ? await tokens.get(lendToken) : lendToken + // cutting the proof inputs and concatenating them into our input variables + const firstProofSlice: string = proof.inputs + .slice(2, 10) + .map((input: string) => input.substr(2).substr(56)) + .join('') - // amount in loan - const loanAmount = toBN(amount, await lendingToken.decimals()) + const secondProofSlice: string = proof.inputs + .slice(10, 18) + .map((input: string) => input.substr(2).substr(56)) + .join('') - // get the borrower, deployer and borrower's signer - // const deployer = await getNamedSigner('deployer') - const borrower = '0x86a41524cb61edd8b115a72ad9735f8068996688' - const { signer: borrowerSigner } = await hre.evm.impersonate(borrower) + const thirdProofSlice: string = proof.inputs + .slice(18, 26) + .map((input: string) => input.substr(2).substr(56)) + .join('') - // Claim user's NFT as borrower - await claimNFT({ account: borrower, merkleIndex: 0 }, hre) + const firstInput = '0x' + firstProofSlice - // Get the sum of loan amount to take out - const nft = await contracts.get('TellerNFT') + const secondInput = '0x' + secondProofSlice - // get all the borrower's NFTs - const ownedNFTs = await nft - .getOwnedTokens(borrower) - .then((arr) => (arr.length > 2 ? arr.slice(0, 2) : arr)) + const thirdInput = '0x' + thirdProofSlice - // Set NFT approval - await nft.connect(borrowerSigner).setApprovalForAll(diamond.address, true) + console.log('Third input made') + // get the signer + const signer = await getNamedSigner('craSigner') + // get the time stamp + const timestampOne = moment().unix() + // create our message - // Stake NFTs by transferring from the msg.sender (borrower) to the diamond - await diamond.connect(borrowerSigner).stakeNFTs(ownedNFTs) + const messageOne = ethers.BigNumber.from(firstInput) + .xor(timestampOne) + .toHexString() - // Create mockCRAResponse - const craReturn = await mockCRAResponse(hre, { - lendingToken: lendingToken.address, - loanAmount, - loanTermLength: duration.asSeconds(), - collateralRatio: 0, - interestRate: '400', - borrower, - }) + // signing first message + const credentialsSignerOne = await signer.signMessage( + ethers.utils.arrayify(messageOne) + ) + // split our signature + const sigOne = ethers.utils.splitSignature(credentialsSignerOne) + + // construct our signature data to pass onchain + const signatureDataOne = { + signature: { + v: sigOne.v, + r: sigOne.r, + s: sigOne.s, + }, + signedAt: timestampOne, + } + // second signature + const timestampTwo = moment().unix() + const messageTwo = ethers.BigNumber.from(secondInput) + .xor(timestampTwo) + .toHexString() + const credentialsSignerTwo = await signer.signMessage( + ethers.utils.arrayify(messageTwo) + ) + const sigTwo = ethers.utils.splitSignature(credentialsSignerTwo) + const signatureDataTwo = { + signature: { + v: sigTwo.v, + r: sigTwo.r, + s: sigTwo.s, + }, + signedAt: timestampTwo, + } - // plug it in the takeOutLoanWithNfts function along with the proofs to apply to the loan! + // third signature + console.log('about to sign third signature') + const timestampThree = moment().unix() + const messageThree = ethers.BigNumber.from(thirdInput) + .xor(timestampThree) + .toHexString() + const credentialsSignerThree = await signer.signMessage( + ethers.utils.arrayify(messageThree) + ) + const sigThree = ethers.utils.splitSignature(credentialsSignerThree) + const signatureDataThree = { + signature: { + v: sigThree.v, + r: sigThree.r, + s: sigThree.s, + }, + signedAt: timestampThree, + } + console.log('signed all data') + + // all borrow variables + const proof_ = proof.proof + const witness_ = proof.inputs + const borrower = (await getNamedAccounts()).borrower + + // get tokens + const lendToken = '0x6b175474e89094c44da98b954eedeac495271d0f' + const collToken = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' + const lendingToken = + typeof lendToken === 'string' ? await tokens.get(lendToken) : lendToken + + // get loan amount + const loanAmount = 1 + const assetAmount = toBN(loanAmount, await lendingToken.decimals()) + + // collateral amount + const collAmount = '100000' + + console.log('coll amount ') + // create loan user request object + const request_ = { + borrower: borrower, + assetAddress: '0x6b175474e89094c44da98b954eedeac495271d0f', + assetAmount: assetAmount, + collateralAsset: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + collateralAmount: collAmount, + collateralRatio: 5000, + duration: moment.duration(1, 'day').asSeconds(), + code: 2, + } + + // teller market address + const marketHandlerAddress_ = '0x2858023076c86347CDd7DEa4F38aa215cbbCa91b' + console.log('taking out loan') + // create loan request object + const loanRequest = { + request: request_, + marketHandlerAddress: marketHandlerAddress_, + snarkProof: proof_, + snarkWitnesses: witness_, + dataProviderSignatures: [ + signatureDataOne, + signatureDataTwo, + signatureDataThree, + ], + providers: providerAddresses, + } const tx = diamond - .connect(borrowerSigner) - .takeOutLoanWithNFTs( - { request: craReturn.request, responses: craReturn.responses }, - ownedNFTs - ) + .connect(ethers.provider.getSigner(borrower)) + .takeOutLoanSnark(loanRequest, collToken, collAmount) - // return our transaction and our helper variables return { tx, getHelpers: async (): Promise => { diff --git a/test/helpers/story/drivers/loan-story-test-driver.ts b/test/helpers/story/drivers/loan-story-test-driver.ts index 18af9867d..812f36789 100644 --- a/test/helpers/story/drivers/loan-story-test-driver.ts +++ b/test/helpers/story/drivers/loan-story-test-driver.ts @@ -18,7 +18,6 @@ import { repayLoan, RepayLoanArgs, takeOutLoanWithNfts, - takeOutLoanWithoutNfts, } from '../../loans' import { TestAction, TestArgs, TestScenario } from '../story-helpers' import StoryTestDriver from './story-test-driver' @@ -192,16 +191,10 @@ export default class LoanStoryTestDriver extends StoryTestDriver { ? args.loanType : LoanType.UNDER_COLLATERALIZED await hre.evm.advanceTime(rateLimit) - const funcToRun = args.nft - ? takeOutLoanWithNfts(hre, { - amount: 100, - lendToken: market.lendingToken, - }) - : takeOutLoanWithoutNfts(hre, { - lendToken: market.lendingToken, - collToken: market.collateralTokens[0], - loanType, - }) + const funcToRun = takeOutLoanWithNfts(hre, { + amount: 100, + lendToken: market.lendingToken, + }) const { tx, getHelpers } = await funcToRun const helpers = await getHelpers() // borrower data from our helpers diff --git a/test/integration/dictionary.test.ts b/test/integration/dictionary.test.ts index 78f7d5ec6..d8f7ae33b 100644 --- a/test/integration/dictionary.test.ts +++ b/test/integration/dictionary.test.ts @@ -37,6 +37,7 @@ describe('NFT Dictionary', () => { deployer = await getNamedSigner('deployer') }) + describe('Dictionary test', () => { beforeEach(async () => { // Advance time diff --git a/test/integration/loans.test.ts b/test/integration/loans.test.ts index 86031aa27..c0e4a2cd7 100644 --- a/test/integration/loans.test.ts +++ b/test/integration/loans.test.ts @@ -1,156 +1,230 @@ -import chai, { expect } from 'chai' -import { solidity } from 'ethereum-waffle' -import { Signer } from 'ethers' -import hre from 'hardhat' - -import { getMarkets } from '../../config' -import { getPlatformSetting, updatePlatformSetting } from '../../tasks' -import { Market } from '../../types/custom/config-types' -import { ITellerDiamond } from '../../types/typechain' -import { fundedMarket } from '../fixtures' -import { - LoanType, - takeOutLoanWithNfts, - takeOutLoanWithoutNfts, -} from '../helpers/loans' - -chai.should() -chai.use(solidity) - -const { getNamedSigner, evm } = hre - -describe.skip('Loans', () => { - getMarkets(hre.network).forEach(testLoans) - - function testLoans(market: Market): void { - let deployer: Signer - let diamond: ITellerDiamond - // let borrower: Signer - - before(async () => { - // eslint-disable-next-line - ({ diamond } = await fundedMarket(hre, { - assetSym: market.lendingToken, - amount: 100000, - })) - - deployer = await getNamedSigner('deployer') - }) - // tests for merged loan functions - describe('merge create loan', () => { - let helpers: any = null - before(async () => { - // update percentage submission percentage value to 0 for this test - const percentageSubmission = { - name: 'RequiredSubmissionsPercentage', - value: 0, - } - await updatePlatformSetting(percentageSubmission, hre) - - // Advance time - const { value: rateLimit } = await getPlatformSetting( - 'RequestLoanTermsRateLimit', - hre - ) - await evm.advanceTime(rateLimit) - }) - describe('without NFT', () => { - it('should create a loan', async () => { - // get helpers variables after function returns our transaction and - // helper variables - const { getHelpers } = await takeOutLoanWithoutNfts(hre, { - lendToken: market.lendingToken, - collToken: market.collateralTokens[0], - loanType: LoanType.UNDER_COLLATERALIZED, - }) - helpers = await getHelpers() - - // borrower data from our helpers - // borrower = helpers.details.borrower.signer - - // check if loan exists - expect(helpers.details.loan).to.exist - }) - it('should have collateral deposited', async () => { - // get collateral - const { collateral } = helpers - const amount = await collateral.current() - - // check if collateral is > 0 - amount.gt(0).should.eq(true, 'Loan must have collateral') - }) - it('should be taken out', () => { - // get loanStatus from helpers and check if it's equal to 2, which means - // it's active and taken out - const loanStatus = helpers.details.loan.status - expect(loanStatus).to.equal(2) - }) - - it('should not be able to take out a loan when loan facet is paused', async () => { - const LOANS_ID = hre.ethers.utils.id('LOANS') - - // Pause lending - await diamond - .connect(deployer) - .pause(LOANS_ID, true) - .should.emit(diamond, 'Paused') - .withArgs(LOANS_ID, await deployer.getAddress()) - - // trying to run the function will revert with the same error message - // written in our PausableMods file - const { tx } = await takeOutLoanWithoutNfts(hre, { - lendToken: market.lendingToken, - collToken: market.collateralTokens[0], - loanType: LoanType.UNDER_COLLATERALIZED, - }) - await tx.should.be.revertedWith('Pausable: paused') - - // Unpause lending - await diamond - .connect(deployer) - .pause(LOANS_ID, false) - .should.emit(diamond, 'UnPaused') - .withArgs(LOANS_ID, await deployer.getAddress()) - }) - // it('should not be able to take out a loan without enough collateral', async () => { - // const { tx } = await takeOutLoanWithoutNfts({ - // lendToken: market.lendingToken, - // collToken: market.collateralTokens[0], - // loanType: LoanType.OVER_COLLATERALIZED, - // collAmount: 1 - // }) - - // // Try to take out loan which should fail - // await tx.should.be.revertedWith('Teller: more collateral required') - // }) - }) - - describe('with NFT', () => { - let helpers: any - before(async () => { - // Advance time - const { value: rateLimit } = await getPlatformSetting( - 'RequestLoanTermsRateLimit', - hre - ) - await evm.advanceTime(rateLimit) - }) - it('creates a loan', async () => { - // get helpers - const { getHelpers } = await takeOutLoanWithNfts(hre, { - amount: 100, - lendToken: market.lendingToken, - }) - helpers = await getHelpers() - - expect(helpers.details.loan).to.exist - }) - it('should be an active loan', () => { - // get loanStatus from helpers and check if it's equal to 2, which means it's active - const loanStatus = helpers.details.loan.status - expect(loanStatus).to.equal(2) - }) - }) - }) - } -}) +// import chai, { expect } from 'chai' +// import { solidity } from 'ethereum-waffle' +// import { providers, Signer } from 'ethers' +// import { +// ComputationResult, +// Proof, +// // @ts-ignore: because we are looking for the /node pkg +// } from 'zokrates-js/node' +// import { defaultMaxListeners } from 'events' +// import hre from 'hardhat' + +// import { getMarkets } from '../../config' +// import { getPlatformSetting, updatePlatformSetting } from '../../tasks' +// import { Market } from '../../types/custom/config-types' +// import { ITellerDiamond } from '../../types/typechain' +// import { fundedMarket } from '../fixtures' +// import { +// LoanType, +// takeOutLoanWithNfts, +// outputCraValues, +// fillZKCRAConfigInfo, +// borrowWithZKCRA, +// } from '../helpers/loans' + +// chai.should() +// chai.use(solidity) + +// const { getNamedSigner, evm } = hre + +// describe('Loans', () => { +// getMarkets(hre.network).forEach(testLoans) + +// function testLoans(market: Market): void { +// let deployer: Signer +// let diamond: ITellerDiamond +// // let borrower: Signer + +// before(async () => { +// // eslint-disable-next-line +// ({ diamond } = await fundedMarket(hre, { +// assetSym: market.lendingToken, +// amount: 100000, +// })) + +// deployer = await getNamedSigner('deployer') +// }) +// // tests for merged loan functions +// describe('merge create loan', () => { +// let helpers: any = null +// before(async () => { +// // update percentage submission percentage value to 0 for this test +// const percentageSubmission = { +// name: 'RequiredSubmissionsPercentage', +// value: 0, +// } +// await updatePlatformSetting(percentageSubmission, hre) + +// // Advance time +// const { value: rateLimit } = await getPlatformSetting( +// 'RequestLoanTermsRateLimit', +// hre +// ) +// await evm.advanceTime(rateLimit) +// }) +// describe('without NFT', () => { +// // it('should create a loan', async () => { +// // // get helpers variables after function returns our transaction and +// // // helper variables +// // const { getHelpers } = await takeOutLoanWithoutNfts(hre, { +// // lendToken: market.lendingToken, +// // collToken: market.collateralTokens[0], +// // loanType: LoanType.UNDER_COLLATERALIZED, +// // }) +// // helpers = await getHelpers() + +// // // borrower data from our helpers +// // // borrower = helpers.details.borrower.signer + +// // // check if loan exists +// // expect(helpers.details.loan).to.exist +// // }) +// it('should have collateral deposited', async () => { +// // get collateral +// const { collateral } = helpers +// const amount = await collateral.current() + +// // check if collateral is > 0 +// amount.gt(0).should.eq(true, 'Loan must have collateral') +// }) +// it('should be taken out', () => { +// // get loanStatus from helpers and check if it's equal to 2, which means +// // it's active and taken out +// const loanStatus = helpers.details.loan.status +// expect(loanStatus).to.equal(2) +// }) + +// // it('should not be able to take out a loan when loan facet is paused', async () => { +// // const LOANS_ID = hre.ethers.utils.id('LOANS') + +// // // Pause lending +// // await diamond +// // .connect(deployer) +// // .pause(LOANS_ID, true) +// // .should.emit(diamond, 'Paused') +// // .withArgs(LOANS_ID, await deployer.getAddress()) + +// // // trying to run the function will revert with the same error message +// // // written in our PausableMods file +// // const { tx } = await takeOutLoanWithoutNfts(hre, { +// // lendToken: market.lendingToken, +// // collToken: market.collateralTokens[0], +// // loanType: LoanType.UNDER_COLLATERALIZED, +// // }) +// // await tx.should.be.revertedWith('Pausable: paused') + +// // // Unpause lending +// // await diamond +// // .connect(deployer) +// // .pause(LOANS_ID, false) +// // .should.emit(diamond, 'UnPaused') +// // .withArgs(LOANS_ID, await deployer.getAddress()) +// // }) +// // it('should not be able to take out a loan without enough collateral', async () => { +// // const { tx } = await takeOutLoanWithoutNfts({ +// // lendToken: market.lendingToken, +// // collToken: market.collateralTokens[0], +// // loanType: LoanType.OVER_COLLATERALIZED, +// // collAmount: 1 +// // }) + +// // // Try to take out loan which should fail +// // await tx.should.be.revertedWith('Teller: more collateral required') +// // }) +// }) + +// describe('with NFT', () => { +// let helpers: any +// before(async () => { +// // Advance time +// const { value: rateLimit } = await getPlatformSetting( +// 'RequestLoanTermsRateLimit', +// hre +// ) +// await evm.advanceTime(rateLimit).catch((err) => { console.log(err) }).finally(() => { console.log("cra values outputted") }) +// }) +// }) +// it('creates a loan', async () => { +// console.log(helpers.details.loan) +// expect(helpers.details.loan).to.exist +// }) +// it('should be an active loan', () => { +// // get loanStatus from helpers and check if it's equal to 2, which means it's active +// const loanStatus = helpers.details.loan.status +// expect(loanStatus).to.equal(2) +// }) +// }); + +// describe.only('create loan w/ zkCRA', () => { +// (async () => { +// // declare computation and proof variables to be used throughout the test +// let goodScoreComputation: ComputationResult +// let goodProof_: Proof +// let badProof_: Proof +// let helpers: any +// let numberOfSignaturesRequired_: any +// let providerAddresses_: any +// before(async () => { +// // we fill the necessary config information (admins mostly) into our providers +// // and market +// console.log('filling zkCRAConfigInfo') +// await fillZKCRAConfigInfo(hre, { numberOfProviders: 2 }).then(x => { +// numberOfSignaturesRequired_ = x.numberOfSignaturesRequired +// providerAddresses_ = x.providerAddresses +// }).catch((err) => { console.log(err) }).finally(() => { console.log("cra values outputted") }) +// }); + +// describe.only('good score', () => { +// (async () => { +// // check if computation and proof exist +// it('checks if proof are returned from good score', async () => { +// const goodScore = true; +// await outputCraValues(hre, goodScore).then(x => { +// goodProof_ = x.proof +// goodProof_.should.exist +// }).catch((err) => { console.log(err) }).finally(() => { console.log("cra values outputted") }); +// }); +// it('uses witness, output and proof to take out a loan with a good score', async () => { +// await borrowWithZKCRA(hre, { +// proof: goodProof_, +// providerAddresses: providerAddresses_, +// }).then(async (x) => { +// await x.getHelpers().then((x) => { +// const takenOutLoan = x.details.loan +// console.log(takenOutLoan) +// // check if loan exists +// expect(takenOutLoan).to.exist +// }).catch((err) => { console.log(err) }).finally(() => { console.log("cra values outputted") }); +// }).catch((err) => { console.log(err) }).finally(() => { console.log("cra values outputted") }) +// }); +// })(); +// }); + +// describe('bad score', () => { +// (async () => { +// // check if computation and proof exist +// it('checks if proof are returned from bad score', async () => { +// const goodScore = false +// await outputCraValues(hre, goodScore).then(x => { +// badProof_ = x.proof +// badProof_.should.exist +// }).catch((err) => { console.log(err) }).finally(() => { console.log("cra values outputted") }) +// }) + +// it('take out a loan should fail with bad score', () => { +// (async () => { +// const { tx } = await borrowWithZKCRA(hre, { +// proof: badProof_, +// providerAddresses: providerAddresses_, +// }); +// await tx.should.be.revertedWith( +// 'Teller: market score not high enough' +// ).catch((err) => { console.log(err) }).finally(() => { console.log("cra values outputted") }) +// })(); +// }) +// }) +// }) +// })(); +// }); +// } +// }); diff --git a/yarn.lock b/yarn.lock index d9a9f453d..184d3e8ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13324,3 +13324,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zokrates-js@^1.0.33: + version "1.0.33" + resolved "https://registry.yarnpkg.com/zokrates-js/-/zokrates-js-1.0.33.tgz#da7ad298c2aa8f15ebc9d1554503e6fe93bb9e97" + integrity sha512-2Chq2PvCbRrKh7lJqA4wPun4z//hsYGqI09kyTcV4e71scHSfcsQ9RO501tgQHLc92Y1rM374opIILm0V6CgnQ==