Skip to content

Commit

Permalink
add vaulted currency to interest rate swap sigs
Browse files Browse the repository at this point in the history
  • Loading branch information
PowVT committed Jun 28, 2024
1 parent c35874b commit 946d614
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 42 deletions.
11 changes: 11 additions & 0 deletions contracts/interfaces/IOriginationControllerInterestRateSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,15 @@ interface IOriginationControllerInterestRateSwap is IOriginationControllerBase {
Signature calldata sig,
SigProperties calldata sigProperties
) external returns (uint256 loanId, uint256 bundleId);

// ============= Signature Verification =============

function recoverInterestRateSwapSignature(
LoanLibrary.LoanTerms calldata loanTerms,
Signature calldata sig,
SigProperties calldata sigProperties,
address vaultedCurrency,
Side side,
address signingCounterparty
) external view returns (bytes32 sighash, address signer);
}
70 changes: 41 additions & 29 deletions contracts/libraries/OriginationLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

pragma solidity 0.8.18;

import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";

import "../libraries/LoanLibrary.sol";

import "../interfaces/IOriginationController.sol";
Expand All @@ -13,7 +10,9 @@ import "../interfaces/IOriginationController.sol";
* @title OriginationLibrary
* @author Non-Fungible Technologies, Inc.
*
* Library for loan origination functions.
* This library is a collection of shared logic used across various origination controller contracts.
* It includes constants for EIP712 type hashes, the functions for encoding these type hashes, and
* various data structures shared by the origination controller contracts.
*/
library OriginationLibrary {
// ======================================= STRUCTS ================================================
Expand Down Expand Up @@ -42,21 +41,26 @@ library OriginationLibrary {
}

// ======================================= CONSTANTS ==============================================
// solhint-disable max-line-length

/// @notice EIP712 type hash for bundle-based signatures.
bytes32 public constant _TOKEN_ID_TYPEHASH =
keccak256(
// solhint-disable-next-line max-line-length
"LoanTerms(uint32 interestRate,uint64 durationSecs,address collateralAddress,uint96 deadline,address payableCurrency,uint256 principal,uint256 collateralId,bytes32 affiliateCode,SigProperties sigProperties,uint8 side,address signingCounterparty)SigProperties(uint160 nonce,uint96 maxUses)"
);

/// @notice EIP712 type hash for item-based signatures.
bytes32 public constant _ITEMS_TYPEHASH =
keccak256(
// solhint-disable max-line-length
"LoanTermsWithItems(uint32 interestRate,uint64 durationSecs,address collateralAddress,uint96 deadline,address payableCurrency,uint256 principal,bytes32 affiliateCode,Predicate[] items,SigProperties sigProperties,uint8 side,address signingCounterparty)Predicate(bytes data,address verifier)SigProperties(uint160 nonce,uint96 maxUses)"
);

/// @notice EIP712 type hash for interest rate swap signatures.
bytes32 public constant _INTEREST_RATE_SWAP_TYPEHASH =
keccak256(
"LoanTermsWithCurrencyPair(uint32 interestRate,uint64 durationSecs,address vaultedCurrency,address collateralAddress,uint96 deadline,address payableCurrency,uint256 principal,uint256 collateralId,bytes32 affiliateCode,SigProperties sigProperties,uint8 side,address signingCounterparty)SigProperties(uint160 nonce,uint96 maxUses)"
);

/// @notice EIP712 type hash for Predicate.
bytes32 public constant _PREDICATE_TYPEHASH =
keccak256(
Expand Down Expand Up @@ -192,33 +196,41 @@ library OriginationLibrary {
);
}

// ==================================== PERMISSION MANAGEMENT =====================================

/**
* @notice Reports whether the signer matches the target or is approved by the target.
* @notice Hashes a loan with interest rate swap for inclusion in the EIP712 signature.
*
* @param target The grantor of permission - should be a smart contract.
* @param sig A struct containing the signature data (for checking EIP-1271).
* @param sighash The hash of the signature payload (used for EIP-1271 check).
* @param terms The loan terms.
* @param sigProperties The signature properties.
* @param vaultedCurrency The currency to be vaulted.
* @param side The side of the signature.
* @param signingCounterparty The address of the signing counterparty.
*
* @return bool Whether the signer is either the grantor themselves, or approved.
* @return loanHash The hash of the loan.
*/
function isApprovedForContract(
address target,
IOriginationController.Signature memory sig,
bytes32 sighash
) public view returns (bool) {
bytes memory signature = abi.encodePacked(sig.r, sig.s, sig.v);

// Append extra data if it exists
if (sig.extraData.length > 0) {
signature = bytes.concat(signature, sig.extraData);
}

// Convert sig struct to bytes
(bool success, bytes memory result) = target.staticcall(
abi.encodeWithSelector(IERC1271.isValidSignature.selector, sighash, signature)
function encodeLoanWithInterestRateSwap(
LoanLibrary.LoanTerms calldata terms,
IOriginationController.SigProperties calldata sigProperties,
address vaultedCurrency,
uint8 side,
address signingCounterparty
) public pure returns (bytes32 loanHash) {
loanHash = keccak256(
abi.encode(
_INTEREST_RATE_SWAP_TYPEHASH,
terms.interestRate,
terms.durationSecs,
vaultedCurrency,
terms.collateralAddress,
terms.deadline,
terms.payableCurrency,
terms.principal,
terms.collateralId,
terms.affiliateCode,
encodeSigProperties(sigProperties),
uint8(side),
signingCounterparty
)
);
return (success && result.length == 32 && abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
}
}
}
31 changes: 30 additions & 1 deletion contracts/origination/OriginationControllerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity 0.8.18;

import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";

import "./OriginationCalculator.sol";

Expand Down Expand Up @@ -102,6 +103,34 @@ abstract contract OriginationControllerBase is IOriginationControllerBase, EIP71
return target == signer || isApproved(target, signer);
}

/**
* @notice Reports whether the signer matches the target or is approved by the target.
*
* @param target The grantor of permission - should be a smart contract.
* @param sig A struct containing the signature data (for checking EIP-1271).
* @param sighash The hash of the signature payload (used for EIP-1271 check).
*
* @return bool Whether the signer is either the grantor themselves, or approved.
*/
function isApprovedForContract(
address target,
IOriginationController.Signature memory sig,
bytes32 sighash
) public view returns (bool) {
bytes memory signature = abi.encodePacked(sig.r, sig.s, sig.v);

// Append extra data if it exists
if (sig.extraData.length > 0) {
signature = bytes.concat(signature, sig.extraData);
}

// Convert sig struct to bytes
(bool success, bytes memory result) = target.staticcall(
abi.encodeWithSelector(IERC1271.isValidSignature.selector, sighash, signature)
);
return (success && result.length == 32 && abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
}

// ==================================== SIGNATURE VERIFICATION ======================================

/**
Expand Down Expand Up @@ -246,7 +275,7 @@ abstract contract OriginationControllerBase is IOriginationControllerBase, EIP71
}

// Check signature validity
if (!isSelfOrApproved(signingCounterparty, signer) && !OriginationLibrary.isApprovedForContract(signingCounterparty, sig, sighash)) {
if (!isSelfOrApproved(signingCounterparty, signer) && !isApprovedForContract(signingCounterparty, sig, sighash)) {
revert OC_InvalidSignature(signingCounterparty, signer);
}

Expand Down
38 changes: 36 additions & 2 deletions contracts/origination/OriginationControllerInterestRateSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ contract OriginationControllerInterestRateSwap is
address lender,
Signature calldata sig,
SigProperties calldata sigProperties
) external returns (uint256 loanId, uint256 bundleId) {
) public override returns (uint256 loanId, uint256 bundleId) {
// input validation
originationHelpers.validateLoanTerms(loanTerms);

Expand All @@ -109,10 +109,11 @@ contract OriginationControllerInterestRateSwap is
address signingCounterparty = neededSide == Side.LEND ? lender : borrower;
address callingCounterparty = neededSide == Side.LEND ? borrower : lender;

(bytes32 sighash, address externalSigner) = recoverTokenSignature(
(bytes32 sighash, address externalSigner) = recoverInterestRateSwapSignature(
loanTerms,
sig,
sigProperties,
swapData.vaultedCurrency,
neededSide,
signingCounterparty
);
Expand Down Expand Up @@ -149,6 +150,39 @@ contract OriginationControllerInterestRateSwap is
if(!currencyPairs[key]) revert OCIRS_InvalidPair(loanTerms.payableCurrency, swapData.vaultedCurrency);
}

/**
* @notice Determine the external signer for a signature.
*
* @param loanTerms The terms of the loan.
* @param sig The signature, with v, r, s fields.
* @param sigProperties Signature nonce and max uses for this nonce.
* @param vaultedCurrency The currency to be vaulted.
* @param side The side of the loan being signed.
* @param signingCounterparty The address of the counterparty who signed the terms.
*
* @return sighash The hash that was signed.
* @return signer The address of the recovered signer.
*/
function recoverInterestRateSwapSignature(
LoanLibrary.LoanTerms calldata loanTerms,
Signature calldata sig,
SigProperties calldata sigProperties,
address vaultedCurrency,
Side side,
address signingCounterparty
) public view override returns (bytes32 sighash, address signer) {
bytes32 loanHash = OriginationLibrary.encodeLoanWithInterestRateSwap(
loanTerms,
sigProperties,
vaultedCurrency,
uint8(side),
signingCounterparty
);

sighash = _hashTypedDataV4(loanHash);
signer = ECDSA.recover(sighash, sig.v, sig.r, sig.s);
}

/**
* @notice Mint a new asset vault, then deposit the vaulted currency amounts from both parties
* into the asset vault. Update the loan terms to reflect the new bundle ID. Then,
Expand Down
2 changes: 1 addition & 1 deletion contracts/origination/OriginationControllerMigrate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ contract OriginationControllerMigrate is IMigrationBase, OriginationController,
(bytes32 sighash, address externalSigner) = _recoverSignature(newTerms, sig, sigProperties, Side.LEND, lender, itemPredicates);

// counterparty validation
if (!isSelfOrApproved(lender, externalSigner) && !OriginationLibrary.isApprovedForContract(lender, sig, sighash)) {
if (!isSelfOrApproved(lender, externalSigner) && !isApprovedForContract(lender, sig, sighash)) {
revert OCM_SideMismatch(externalSigner);
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/rollover/CrossCurrencyRollover.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ contract CrossCurrencyRollover is
(bytes32 sighash, address externalSigner) = _recoverSignature(newTerms, sig, sigProperties, Side.LEND, lender, itemPredicates);

// counterparty validation
if (!isSelfOrApproved(lender, externalSigner) && !OriginationLibrary.isApprovedForContract(lender, sig, sighash)) {
if (!isSelfOrApproved(lender, externalSigner) && !isApprovedForContract(lender, sig, sighash)) {
revert CCR_SideMismatch(externalSigner);
}

Expand Down
Loading

0 comments on commit 946d614

Please sign in to comment.