Created
March 31, 2022 14:54
-
-
Save Savio-Sou/f83d825aa990ace7cb846d5b3c1698e8 to your computer and use it in GitHub Desktop.
Review Aztec's loan-dapp-starter-kit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| pragma solidity >= 0.5.0 <0.7.0; | |
| import "@aztec/protocol/contracts/ERC1724/ZkAssetMintable.sol"; | |
| import "@aztec/protocol/contracts/libs/NoteUtils.sol"; | |
| import "@aztec/protocol/contracts/interfaces/IZkAsset.sol"; | |
| import "./LoanUtilities.sol"; | |
| // A peer-to-peer loan with hiddden notional, each loan contract represents a loan | |
| // Inherit private asset contract "ZkAssetMintable.sol" | |
| contract Loan is ZkAssetMintable { | |
| using SafeMath for uint256; | |
| using NoteUtils for bytes; | |
| using LoanUtilities for LoanUtilities.LoanVariables; | |
| LoanUtilities.LoanVariables public loanVariables; | |
| IZkAsset public settlementToken; | |
| // [0] interestRate | |
| // [1] interestPeriod | |
| // [2] duration | |
| // [3] settlementCurrencyId | |
| // [4] loanSettlementDate | |
| // [5] lastInterestPaymentDate address public borrower; | |
| address public lender; | |
| address public borrower; | |
| mapping(address => bytes) lenderApprovals; | |
| event LoanPayment(string paymentType, uint256 lastInterestPaymentDate); | |
| event LoanDefault(); | |
| event LoanRepaid(); | |
| struct Note { | |
| address owner; | |
| bytes32 noteHash; | |
| } | |
| function _noteCoderToStruct(bytes memory note) internal pure returns (Note memory codedNote) { | |
| (address owner, bytes32 noteHash,) = note.extractNote(); | |
| return Note(owner, noteHash ); | |
| } | |
| // Override constructor | |
| // ZkAssetMintable(_aceAddress, _linkedTokenAddress, _scalingFactor, _canAdjustSupply) | |
| // https://github.com/AztecProtocol/AZTEC/blob/develop/packages/protocol/contracts/ERC1724/base/ZkAssetBase.sol | |
| constructor( | |
| bytes32 _notional, | |
| uint256[] memory _loanVariables, | |
| address _borrower, | |
| address _aceAddress, | |
| address _settlementCurrency | |
| ) public ZkAssetMintable(_aceAddress, address(0), 1, true, false) { | |
| // Set loan note variables | |
| loanVariables.loanFactory = msg.sender; // an instance of LoanDapp.sol | |
| loanVariables.notional = _notional; | |
| loanVariables.id = address(this); | |
| loanVariables.interestRate = _loanVariables[0]; | |
| loanVariables.interestPeriod = _loanVariables[1]; | |
| loanVariables.duration = _loanVariables[2]; | |
| loanVariables.borrower = _borrower; | |
| borrower = _borrower; | |
| loanVariables.settlementToken = IZkAsset(_settlementCurrency); | |
| loanVariables.aceAddress = _aceAddress; | |
| } | |
| function requestAccess() public { | |
| lenderApprovals[msg.sender] = '0x'; | |
| } | |
| function approveAccess(address _lender, bytes memory _sharedSecret) public { | |
| lenderApprovals[_lender] = _sharedSecret; | |
| } | |
| // Execute loan: borrower borrows, lender lends | |
| /// @param _proofData mint proof constructed with "aztec.js" | |
| /// @param _currentInterestBalance | |
| /// @param _lender lender's address | |
| function settleLoan( | |
| bytes calldata _proofData, | |
| bytes32 _currentInterestBalance, | |
| address _lender | |
| ) external { | |
| LoanUtilities.onlyLoanDapp(msg.sender, loanVariables.loanFactory); // check msg.sender == loanFactory | |
| // Validate bilateral swap proofs with ACE, then execute loan via atomic swapping lender's settlement tokens (i.e. ERC20 tokens) and borrower's loan note | |
| LoanUtilities._processLoanSettlement(_proofData, loanVariables); | |
| // Set loan note variables | |
| loanVariables.loanSettlementDate = block.timestamp; | |
| loanVariables.lastInterestPaymentDate = block.timestamp; | |
| loanVariables.currentInterestBalance = _currentInterestBalance; | |
| loanVariables.lender = _lender; | |
| lender = _lender; | |
| } | |
| // Mint loan note | |
| /// @param _proof mint proof identifier | |
| /// @param _proofData mint proof constructed with "aztec.js" | |
| function confidentialMint(uint24 _proof, bytes calldata _proofData) external { | |
| LoanUtilities.onlyLoanDapp(msg.sender, loanVariables.loanFactory); // check msg.sender == loanFactory | |
| require(msg.sender == owner, "only owner can call the confidentialMint() method"); // check if msg.sender is authorized minter | |
| require(_proofData.length != 0, "proof invalid"); | |
| // ACE validates mint proof, mints the loan note, and returns _proofOutputs that contains: | |
| // 1. the new confidentialTotalSupply note | |
| // 2. the loan note minted | |
| (bytes memory _proofOutputs) = ace.mint(_proof, _proofData, msg.sender); | |
| (, bytes memory newTotal, ,) = _proofOutputs.get(0).extractProofOutput(); | |
| (, bytes memory mintedNotes, ,) = _proofOutputs.get(1).extractProofOutput(); | |
| (, | |
| bytes32 noteHash, | |
| bytes memory metadata) = newTotal.extractNote(); | |
| // Emit events | |
| logOutputNotes(mintedNotes); // emit note creation event, CreateNote(noteOwner, noteHash, metaData) | |
| emit UpdateTotalMinted(noteHash, metadata); | |
| } | |
| // Lender withdraws interest accrued | |
| /// @param _proof1 dividend proof, i.e. proof of interest accural | |
| /// @param _proof2 joint split proof, i.e. proof of interest transfer | |
| function withdrawInterest( | |
| bytes memory _proof1, | |
| bytes memory _proof2, | |
| uint256 _interestDurationToWithdraw | |
| ) public { | |
| // Validate _proof1 with ACE | |
| (,bytes memory _proof1OutputNotes) = LoanUtilities._validateInterestProof(_proof1, _interestDurationToWithdraw, loanVariables); | |
| // Check if interest duration requested for withdrawal is valid (i.e. up to current block time) | |
| require(_interestDurationToWithdraw.add(loanVariables.lastInterestPaymentDate) < block.timestamp, ' withdraw is greater than accrued interest'); | |
| // Validate _proof2 with ACE, then transfer interest in settlement tokens to lender | |
| (bytes32 newCurrentInterestNoteHash) = LoanUtilities._processInterestWithdrawal(_proof2, _proof1OutputNotes, loanVariables); | |
| // Update loan note | |
| loanVariables.currentInterestBalance = newCurrentInterestNoteHash; | |
| loanVariables.lastInterestPaymentDate = loanVariables.lastInterestPaymentDate.add(_interestDurationToWithdraw); | |
| // Emit event | |
| emit LoanPayment('INTEREST', loanVariables.lastInterestPaymentDate); | |
| } | |
| // Borrower withdraw borrowed tokens in contract / partially repay loan | |
| function adjustInterestBalance(bytes memory _proofData) public { | |
| LoanUtilities.onlyBorrower(msg.sender,borrower); // check msg.sender == borrower | |
| // Validate joint split proof (i.e. interest transfer) with ACE, then transfer interest in settlement tokens to contract | |
| (bytes32 newCurrentInterestBalance) = LoanUtilities._processAdjustInterest(_proofData, loanVariables); | |
| // Update loan note | |
| loanVariables.currentInterestBalance = newCurrentInterestBalance; | |
| } | |
| // Borrower repay loan | |
| /// @param _proof1 dividend proof, i.e. proof of interest accural | |
| /// @param _proof2 joint split proof, i.e. proof of interest transfer | |
| function repayLoan( | |
| bytes memory _proof1, | |
| bytes memory _proof2 | |
| ) public { | |
| LoanUtilities.onlyBorrower(msg.sender, borrower); // check msg.sender == borrower | |
| uint256 remainingInterestDuration = loanVariables.loanSettlementDate.add(loanVariables.duration).sub(loanVariables.lastInterestPaymentDate); | |
| // Validate _proof1 with ACE | |
| (,bytes memory _proof1OutputNotes) = LoanUtilities._validateInterestProof(_proof1, remainingInterestDuration, loanVariables); | |
| // Check if loan has matured | |
| require(loanVariables.loanSettlementDate.add(loanVariables.duration) < block.timestamp, 'loan has not matured'); | |
| // Validate _proof2 with ACE, then transfer interest and principal in settlement tokens to lender | |
| LoanUtilities._processLoanRepayment( | |
| _proof2, | |
| _proof1OutputNotes, | |
| loanVariables | |
| ); | |
| // Emit event | |
| emit LoanRepaid(); | |
| } | |
| // Mark this loan as defaulted | |
| /// @param _proof1 dividend proof, i.e. proof of interest accural | |
| /// @param _proof2 private range proof, i.e. proof of debt > borrower's balance in contract | |
| function markLoanAsDefault(bytes memory _proof1, bytes memory _proof2, uint256 _interestDurationToWithdraw) public { | |
| // Check if interest duration requested for withdrawal is valid (i.e. up to current block time) | |
| require(_interestDurationToWithdraw.add(loanVariables.lastInterestPaymentDate) < block.timestamp, 'withdraw is greater than accrued interest'); | |
| // Validate proofs with ACE, check if loan is underwater | |
| LoanUtilities._validateDefaultProofs(_proof1, _proof2, _interestDurationToWithdraw, loanVariables); | |
| // Emit event | |
| emit LoanDefault(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| pragma solidity >= 0.5.0 <0.7.0; | |
| import "@aztec/protocol/contracts/interfaces/IAZTEC.sol"; | |
| import "@aztec/protocol/contracts/libs/NoteUtils.sol"; | |
| import "@aztec/protocol/contracts/ERC1724/ZkAsset.sol"; | |
| import "./ZKERC20/ZKERC20.sol"; | |
| import "./Loan.sol"; | |
| // Controller for peer-to-peer loans with hiddden notional | |
| // Manage instances of Loan.sol | |
| contract LoanDapp is IAZTEC { | |
| using NoteUtils for bytes; | |
| event SettlementCurrencyAdded( | |
| uint id, | |
| address settlementAddress | |
| ); | |
| event LoanApprovedForSettlement( | |
| address loanId | |
| ); | |
| event LoanCreated( | |
| address id, | |
| address borrower, | |
| bytes32 notional, | |
| string borrowerPublicKey, | |
| uint256[] loanVariables, | |
| uint createdAt | |
| ); | |
| event ViewRequestCreated( | |
| address loanId, | |
| address lender, | |
| string lenderPublicKey | |
| ); | |
| event ViewRequestApproved( | |
| uint accessId, | |
| address loanId, | |
| address user, | |
| string sharedSecret | |
| ); | |
| event NoteAccessApproved( | |
| uint accessId, | |
| bytes32 note, | |
| address user, | |
| string sharedSecret | |
| ); | |
| address owner = msg.sender; | |
| address aceAddress; | |
| address[] public loans; | |
| mapping(uint => address) public settlementCurrencies; | |
| uint24 MINT_PRO0F = 66049; | |
| uint24 BILATERAL_SWAP_PROOF = 65794; | |
| modifier onlyOwner() { | |
| require(msg.sender == owner); | |
| _; | |
| } | |
| modifier onlyBorrower(address _loanAddress) { | |
| Loan loanContract = Loan(_loanAddress); | |
| require(msg.sender == loanContract.borrower()); | |
| _; | |
| } | |
| constructor(address _aceAddress) public { | |
| aceAddress = _aceAddress; | |
| } | |
| function _getCurrencyContract(uint _settlementCurrencyId) internal view returns (address) { | |
| require(settlementCurrencies[_settlementCurrencyId] != address(0), 'Settlement Currency is not defined'); | |
| return settlementCurrencies[_settlementCurrencyId]; | |
| } | |
| function _generateAccessId(bytes32 _note, address _user) internal pure returns (uint) { | |
| return uint(keccak256(abi.encodePacked(_note, _user))); | |
| } | |
| function _approveNoteAccess( | |
| bytes32 _note, | |
| address _userAddress, | |
| string memory _sharedSecret | |
| ) internal { | |
| uint accessId = _generateAccessId(_note, _userAddress); | |
| emit NoteAccessApproved( | |
| accessId, | |
| _note, | |
| _userAddress, | |
| _sharedSecret | |
| ); | |
| } | |
| // Deploy an instance of Loan.sol, and execute confidentialMint on it | |
| function _createLoan( | |
| bytes32 _notional, | |
| uint256[] memory _loanVariables, | |
| bytes memory _proofData | |
| ) private returns (address) { | |
| address loanCurrency = _getCurrencyContract(_loanVariables[3]); | |
| // Deploy loan contract | |
| Loan newLoan = new Loan( | |
| _notional, | |
| _loanVariables, | |
| msg.sender, | |
| aceAddress, | |
| loanCurrency | |
| ); | |
| loans.push(address(newLoan)); | |
| Loan loanContract = Loan(address(newLoan)); | |
| loanContract.setProofs(1, uint256(-1)); | |
| // Mint loan note | |
| loanContract.confidentialMint(MINT_PROOF, bytes(_proofData)); | |
| return address(newLoan); | |
| } | |
| function addSettlementCurrency(uint _id, address _address) external onlyOwner { | |
| settlementCurrencies[_id] = _address; | |
| emit SettlementCurrencyAdded(_id, _address); | |
| } | |
| // Create a loan, i.e. borrow offer | |
| // Called by borrower | |
| function createLoan( | |
| bytes32 _notional, | |
| string calldata _viewingKey, | |
| string calldata _borrowerPublicKey, | |
| uint256[] calldata _loanVariables, | |
| // [0] interestRate | |
| // [1] interestPeriod | |
| // [2] loanDuration | |
| // [3] settlementCurrencyId | |
| bytes calldata _proofData | |
| ) external { | |
| // Deploy loan contract and mint loan note | |
| address loanId = _createLoan( | |
| _notional, | |
| _loanVariables, | |
| _proofData | |
| ); | |
| // Emit event | |
| emit LoanCreated( | |
| loanId, | |
| msg.sender, | |
| _notional, | |
| _borrowerPublicKey, | |
| _loanVariables, | |
| block.timestamp | |
| ); | |
| // Grant note access approval to msg.sender (i.e. note creator) | |
| _approveNoteAccess( | |
| _notional, | |
| msg.sender, | |
| _viewingKey | |
| ); | |
| } | |
| function approveLoanNotional( | |
| bytes32 _noteHash, | |
| bytes memory _signature, | |
| address _loanId | |
| ) public { | |
| Loan loanContract = Loan(_loanId); | |
| loanContract.confidentialApprove(_noteHash, _loanId, true, _signature); | |
| emit LoanApprovedForSettlement(_loanId); | |
| } | |
| // Request to view notional of loan _loanId | |
| // Called by potential lender(s) | |
| function submitViewRequest(address _loanId, string calldata _lenderPublicKey) external { | |
| emit ViewRequestCreated( | |
| _loanId, | |
| msg.sender, | |
| _lenderPublicKey | |
| ); | |
| } | |
| // Allow _lender to view notional of loan _loanId | |
| // Called by borrower of loan _loanId | |
| function approveViewRequest( | |
| address _loanId, | |
| address _lender, | |
| bytes32 _notionalNote, | |
| string calldata _sharedSecret | |
| ) external onlyBorrower(_loanId) { | |
| uint accessId = _generateAccessId(_notionalNote, _lender); | |
| emit ViewRequestApproved( | |
| accessId, | |
| _loanId, | |
| _lender, | |
| _sharedSecret | |
| ); | |
| } | |
| event SettlementSuccesfull( | |
| address indexed from, | |
| address indexed to, | |
| address loanId, | |
| uint256 timestamp | |
| ); | |
| struct LoanPayment { | |
| address from; | |
| address to; | |
| bytes notional; | |
| } | |
| mapping(uint => mapping(uint => LoanPayment)) public loanPayments; | |
| // Execute loan: borrower borrows, lender lends | |
| // Called by lender | |
| function settleInitialBalance( | |
| address _loanId, | |
| bytes calldata _proofData, | |
| bytes32 _currentInterestBalance | |
| ) external { | |
| Loan loanContract = Loan(_loanId); | |
| loanContract.settleLoan(_proofData, _currentInterestBalance, msg.sender); | |
| emit SettlementSuccesfull( | |
| msg.sender, | |
| loanContract.borrower(), | |
| _loanId, | |
| block.timestamp | |
| ); | |
| } | |
| function approveNoteAccess( | |
| bytes32 _note, | |
| string calldata _viewingKey, | |
| string calldata _sharedSecret, | |
| address _sharedWith | |
| ) external { | |
| if (bytes(_viewingKey).length != 0) { | |
| _approveNoteAccess( | |
| _note, | |
| msg.sender, | |
| _viewingKey | |
| ); | |
| } | |
| if (bytes(_sharedSecret).length != 0) { | |
| _approveNoteAccess( | |
| _note, | |
| _sharedWith, | |
| _sharedSecret | |
| ); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment