Skip to content

Instantly share code, notes, and snippets.

@Rizary
Created April 4, 2022 23:55
Show Gist options
  • Save Rizary/5a84ae086744fed6157d16ec66e35c4f to your computer and use it in GitHub Desktop.
Save Rizary/5a84ae086744fed6157d16ec66e35c4f to your computer and use it in GitHub Desktop.
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";
// Loan is inherit from ZkAssetMintable.sol from AZTEC protocol.
// each Loan contract represent a loan in the network.
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 );
}
// The Loan contract has its own constructor that is override version from ZkAssetMintable.
// It adds settlementCurrency, loanVariables, and borrower specifically used commonly in loan transaction.
constructor(
bytes32 _notional,
uint256[] memory _loanVariables,
address _borrower,
address _aceAddress,
address _settlementCurrency
) public ZkAssetMintable(_aceAddress, address(0), 1, true, false) {
// LoanVariables is a loan note to be executed.
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;
}
// settleLoan function is for executing a loan in which borrowers borrow the money
// and lenders lends the money. It has lender address, current balance and proofData.
// proofData usually act as ZK proof.
function settleLoan(
bytes calldata _proofData,
bytes32 _currentInterestBalance,
address _lender
) external {
LoanUtilities.onlyLoanDapp(msg.sender, loanVariables.loanFactory); // make sure that msg.sender == loanFactory
// This is where the bilateral swap proofs is done with ACE. It validates and then execute loan using atomic
// swapping lender's and borrower's note.
LoanUtilities._processLoanSettlement(_proofData, loanVariables);
// update the loanVariables again with the new state.
loanVariables.loanSettlementDate = block.timestamp;
loanVariables.lastInterestPaymentDate = block.timestamp;
loanVariables.currentInterestBalance = _currentInterestBalance;
loanVariables.lender = _lender;
lender = _lender;
}
// This function is to mint a loan note from proof identifier and proofData from ZK proof provided
// by aztec.
function confidentialMint(uint24 _proof, bytes calldata _proofData) external {
// make sure that msg.sender == loanFactory
LoanUtilities.onlyLoanDapp(msg.sender, loanVariables.loanFactory);
// make sure that msg.sender has the authority to mint
require(msg.sender == owner, "only owner can call the confidentialMint() method");
require(_proofData.length != 0, "proof invalid");
// When ace mint using proof, proofData, and msg.sender, it validate the mints and
// then mints the loan note. It will minted the loan note, and give back the proofOutputs.
(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();
// This where emit event happened
logOutputNotes(mintedNotes); // print log on note creation event.
emit UpdateTotalMinted(noteHash, metadata);
}
// This function is to withdraw the interest. It takes proof1 as a dividen proof
// and proof2 as joint split proof.
function withdrawInterest(
bytes memory _proof1,
bytes memory _proof2,
uint256 _interestDurationToWithdraw
) public {
// this event validate proof1 with ACE
(,bytes memory _proof1OutputNotes) = LoanUtilities._validateInterestProof(_proof1, _interestDurationToWithdraw, loanVariables);
// This line make sure that interest duration to withdraw is within the range of current block timestamp.
require(_interestDurationToWithdraw.add(loanVariables.lastInterestPaymentDate) < block.timestamp, ' withdraw is greater than accrued interest');
// this event validate proof2 with ACE and transfer the interest in settlement
(bytes32 newCurrentInterestNoteHash) = LoanUtilities._processInterestWithdrawal(_proof2, _proof1OutputNotes, loanVariables);
// Modify the state of the loan note.
loanVariables.currentInterestBalance = newCurrentInterestNoteHash;
loanVariables.lastInterestPaymentDate = loanVariables.lastInterestPaymentDate.add(_interestDurationToWithdraw);
// Emith the event
emit LoanPayment('INTEREST', loanVariables.lastInterestPaymentDate);
}
// Borrower can withdraw borrowed tokens or repay the loan
function adjustInterestBalance(bytes memory _proofData) public {
LoanUtilities.onlyBorrower(msg.sender,borrower); // make sure that msg.sender == borrower
// this line validate the joint split proof using ACE. After then, it transfer the interest to the contract.
(bytes32 newCurrentInterestBalance) = LoanUtilities._processAdjustInterest(_proofData, loanVariables);
// modify the state of the loan note
loanVariables.currentInterestBalance = newCurrentInterestBalance;
}
// This function is used for borrower that want to repay the loan.
function repayLoan(
bytes memory _proof1,
bytes memory _proof2
) public {
LoanUtilities.onlyBorrower(msg.sender, borrower); // make sure that msg.sender == borrower
uint256 remainingInterestDuration = loanVariables.loanSettlementDate.add(loanVariables.duration).sub(loanVariables.lastInterestPaymentDate);
// this line validate that _proof1 is correct using ACE
(,bytes memory _proof1OutputNotes) = LoanUtilities._validateInterestProof(_proof1, remainingInterestDuration, loanVariables);
// this line checks if the loan is mature or not
require(loanVariables.loanSettlementDate.add(loanVariables.duration) < block.timestamp, 'loan has not matured');
// This line validate _proof2 with ACE, then transfer the interest in settlement tokens to lender.
LoanUtilities._processLoanRepayment(
_proof2,
_proof1OutputNotes,
loanVariables
);
// this is where emit happened
emit LoanRepaid();
}
// This line validates whether the accrued interest is greater than interest duration to withdraw compared to current block timestamp
require(_interestDurationToWithdraw.add(loanVariables.lastInterestPaymentDate) < block.timestamp, 'withdraw is greater than accrued interest');
// This line validates the proofs with ACE to check if loan is default or not.
LoanUtilities._validateDefaultProofs(_proof1, _proof2, _interestDurationToWithdraw, loanVariables);
// This is where emit event happened.
emit LoanDefault();
}
}
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";
// LoanDapp is a contract that inheret from IAZTEC. It manages and related
// to Loan.sol above.
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
);
}
// Creating a loan that is an instance in loan.sol, and execute the confidentialMint on it.
function _createLoan(
bytes32 _notional,
uint256[] memory _loanVariables,
bytes memory _proofData
) private returns (address) {
address loanCurrency = _getCurrencyContract(_loanVariables[3]);
// creating the New Loan
Loan newLoan = new Loan(
_notional,
_loanVariables,
msg.sender,
aceAddress,
loanCurrency
);
loans.push(address(newLoan));
Loan loanContract = Loan(address(newLoan));
loanContract.setProofs(1, uint256(-1));
// This line use confidentialMint to mint the 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);
}
// this function usually called by borrower
// to instantiate a loan.
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 {
// this line create and mint loan contract.
address loanId = _createLoan(
_notional,
_loanVariables,
_proofData
);
// This is where loanCreated function is emmited.
emit LoanCreated(
loanId,
msg.sender,
_notional,
_borrowerPublicKey,
_loanVariables,
block.timestamp
);
// If the approval is success, this line approve note acces to msg.sender
_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);
}
// Lender then called this function to request the loan note.
function submitViewRequest(address _loanId, string calldata _lenderPublicKey) external {
emit ViewRequestCreated(
_loanId,
msg.sender,
_lenderPublicKey
);
}
// Borrower use this function if they want to let lender see the loan note.
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;
// Lender that approve to give loan to borrower use this function.
// it will execute the loan.
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