Skip to content

Instantly share code, notes, and snippets.

@Savio-Sou
Created March 31, 2022 14:54
Show Gist options
  • Select an option

  • Save Savio-Sou/f83d825aa990ace7cb846d5b3c1698e8 to your computer and use it in GitHub Desktop.

Select an option

Save Savio-Sou/f83d825aa990ace7cb846d5b3c1698e8 to your computer and use it in GitHub Desktop.
Review Aztec's loan-dapp-starter-kit
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();
}
}
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