Simple Loan Proposal
1. Summary
PWNSimpleLoanProposal.sol is an abstract contract inherited by Simple Loan Proposal types.
2. Important links
3. Contract details
PWNSimpleLoanProposal.sol is written in Solidity version 0.8.16
Features
Defines base interface for Simple Loan Proposals
Defines
ProposalBase
structImplements
_makeProposal
and_acceptProposal
functions
Functions
revokeNonce
Overview
A helper function for revoking a proposal nonce on behalf of a caller.
This function takes two arguments supplied by the caller:
uint256
nonceSpace
- Nonce space of a proposal nonce to be revokeduint256
nonce
- Proposal nonce to be revoked
Implementation
function revokeNonce(uint256 nonceSpace, uint256 nonce) external {
revokedNonce.revokeNonce(msg.sender, nonceSpace, nonce);
}
Internal Functions
_makeProposal
Overview
Function to make an on-chain proposal.
This function takes two arguments:
bytes32
proposalHash
- hash of the respective proposal type structaddress
proposer
- Address of a proposal proposer
Implementation
function _makeProposal(bytes32 proposalHash, address proposer) internal {
if (msg.sender != proposer) {
revert CallerIsNotStatedProposer({ addr: proposer });
}
proposalsMade[proposalHash] = true;
}
_acceptProposal
Overview
Makes necessary checks for accepting a proposal and reverts if any loan parameters are not valid.
This function takes six arguments:
address
acceptor
- Address of a proposal acceptoruint256
refinancingLoanId
- Refinancing loan IDbytes32
proposalHash
- Proposal hashbytes32[] calldata
proposalInclusionProof
- Multiproposal inclusion proof. Empty if single proposalbytes calldata
signature
- Signature of a proposalProposalBase memory
proposal
-ProposalBase
struct
Implementation
function _acceptProposal(
address acceptor,
uint256 refinancingLoanId,
bytes32 proposalHash,
bytes32[] calldata proposalInclusionProof,
bytes calldata signature,
ProposalBase memory proposal
) internal {
// Check loan contract
if (msg.sender != proposal.loanContract) {
revert CallerNotLoanContract({ caller: msg.sender, loanContract: proposal.loanContract });
}
if (!hub.hasTag(proposal.loanContract, PWNHubTags.ACTIVE_LOAN)) {
revert AddressMissingHubTag({ addr: proposal.loanContract, tag: PWNHubTags.ACTIVE_LOAN });
}
// Check proposal signature or that it was made on-chain
if (proposalInclusionProof.length == 0) {
// Single proposal signature
if (!proposalsMade[proposalHash]) {
if (!PWNSignatureChecker.isValidSignatureNow(proposal.proposer, proposalHash, signature)) {
revert PWNSignatureChecker.InvalidSignature({ signer: proposal.proposer, digest: proposalHash });
}
}
} else {
// Multiproposal signature
bytes32 multiproposalHash = getMultiproposalHash(
Multiproposal({
multiproposalMerkleRoot: MerkleProof.processProofCalldata({
proof: proposalInclusionProof,
leaf: proposalHash
})
})
);
if (!PWNSignatureChecker.isValidSignatureNow(proposal.proposer, multiproposalHash, signature)) {
revert PWNSignatureChecker.InvalidSignature({ signer: proposal.proposer, digest: multiproposalHash });
}
}
// Check proposer is not acceptor
if (proposal.proposer == acceptor) {
revert AcceptorIsProposer({ addr: acceptor});
}
// Check refinancing proposal
if (refinancingLoanId == 0) {
if (proposal.refinancingLoanId != 0) {
revert InvalidRefinancingLoanId({ refinancingLoanId: proposal.refinancingLoanId });
}
} else {
if (refinancingLoanId != proposal.refinancingLoanId) {
if (proposal.refinancingLoanId != 0 || !proposal.isOffer) {
revert InvalidRefinancingLoanId({ refinancingLoanId: proposal.refinancingLoanId });
}
}
}
// Check proposal is not expired
if (block.timestamp >= proposal.expiration) {
revert Expired({ current: block.timestamp, expiration: proposal.expiration });
}
// Check proposal is not revoked
if (!revokedNonce.isNonceUsable(proposal.proposer, proposal.nonceSpace, proposal.nonce)) {
revert PWNRevokedNonce.NonceNotUsable({
addr: proposal.proposer,
nonceSpace: proposal.nonceSpace,
nonce: proposal.nonce
});
}
// Check propsal is accepted by an allowed address
if (proposal.allowedAcceptor != address(0) && acceptor != proposal.allowedAcceptor) {
revert CallerNotAllowedAcceptor({ current: acceptor, allowed: proposal.allowedAcceptor });
}
if (proposal.availableCreditLimit == 0) {
// Revoke nonce if credit limit is 0, proposal can be accepted only once
revokedNonce.revokeNonce(proposal.proposer, proposal.nonceSpace, proposal.nonce);
} else if (creditUsed[proposalHash] + proposal.creditAmount <= proposal.availableCreditLimit) {
// Increase used credit if credit limit is not exceeded
creditUsed[proposalHash] += proposal.creditAmount;
} else {
// Revert if credit limit is exceeded
revert AvailableCreditLimitExceeded({
used: creditUsed[proposalHash] + proposal.creditAmount,
limit: proposal.availableCreditLimit
});
}
// Check collateral state fingerprint if needed
if (proposal.checkCollateralStateFingerprint) {
bytes32 currentFingerprint;
IStateFingerpringComputer computer = config.getStateFingerprintComputer(proposal.collateralAddress);
if (address(computer) != address(0)) {
// Asset has registered computer
currentFingerprint = computer.computeStateFingerprint({
token: proposal.collateralAddress, tokenId: proposal.collateralId
});
} else if (ERC165Checker.supportsInterface(proposal.collateralAddress, type(IERC5646).interfaceId)) {
// Asset implements ERC5646
currentFingerprint = IERC5646(proposal.collateralAddress).getStateFingerprint(proposal.collateralId);
} else {
// Asset is not implementing ERC5646 and no computer is registered
revert MissingStateFingerprintComputer();
}
if (proposal.collateralStateFingerprint != currentFingerprint) {
// Fingerprint mismatch
revert InvalidCollateralStateFingerprint({
current: currentFingerprint,
proposed: proposal.collateralStateFingerprint
});
}
}
}
}
View Functions
getMultiproposalHash
Overview
This function returns a multiproposal hash according to EIP-712.
This function takes one argument supplied by the caller:
Multiproposal memory
multiproposal
-Multiproposal
struct
Implementation
function getMultiproposalHash(Multiproposal memory multiproposal) public view returns (bytes32) {
return keccak256(abi.encodePacked(
hex"1901", MULTIPROPOSAL_DOMAIN_SEPARATOR, keccak256(abi.encodePacked(
MULTIPROPOSAL_TYPEHASH, abi.encode(multiproposal)
))
));
}
_getProposalHash
Overview
This function returns a proposal hash according to EIP-712.
This function takes two arguments supplied by the caller:
bytes32
proposalTypehash
- Hash of the respective proposal typebytes memory
encodedProposal
- Encoded respective proposal type struct
Implementation
function _getProposalHash(
bytes32 proposalTypehash,
bytes memory encodedProposal
) internal view returns (bytes32) {
return keccak256(abi.encodePacked(
hex"1901", DOMAIN_SEPARATOR, keccak256(abi.encodePacked(
proposalTypehash, encodedProposal
))
));
}
Errors
The PWN Simple Loan Offer contract defines eight errors and no events.
error CallerNotLoanContract(address caller, address loanContract);
error MissingStateFingerprintComputer();
error InvalidCollateralStateFingerprint(bytes32 current, bytes32 proposed);
error CallerIsNotStatedProposer(address addr);
error AcceptorIsProposer(address addr);
error InvalidRefinancingLoanId(uint256 refinancingLoanId);
error AvailableCreditLimitExceeded(uint256 used, uint256 limit);
error CallerNotAllowedAcceptor(address current, address allowed);
CallerNotLoanContract
A CallerNotLoanContract error is thrown when a caller is missing a required hub tag.
This error has two parameters:
address
caller
address
loanContract
MissingStateFingerprintComputer
A MissingStateFingerprintComputer error is thrown when a state fingerprint computer is not registered.
This error doesn't define any parameters.
InvalidCollateralStateFingerprint
A InvalidCollateralStateFingerprint error is thrown when a proposed collateral state fingerprint doesn't match the current state.
This error has two parameters:
bytes32
current
bytes32
proposed
CallerIsNotStatedProposer
A CallerIsNotStatedProposer error is thrown when a caller is not a stated proposer.
This error has one parameter:
address
addr
AcceptorIsProposer
An AcceptorIsProposer error is thrown when proposal acceptor and proposer are the same.
This error has one parameter:
address
addr
InvalidRefinancingLoanId
An InvalidRefinancingLoanId error is thrown when provided refinance loan id cannot be used.
This error has one parameter:
uint256
refinancingLoanId
AvailableCreditLimitExceeded
An AvailableCreditLimitExceeded error is thrown when a proposal would exceed the available credit limit.
This error has two parameters:
uint256
used
uint256
limit
CallerNotAllowedAcceptor
A CallerNotAllowedAcceptor error is thrown when caller is not allowed to accept a proposal.
This error has two parameters:
address
current
address
allowed
ProposalBase
Struct
ProposalBase
StructType | Name | Comment |
---|---|---|
|
| Address of a loan collateral |
|
| ID of a collateral. Zero if ERC-20 |
|
| |
|
| |
|
| Amount of credit asset |
|
| Maximum credit limit of credit asset |
|
| Proposal expiration unix timestamp in seconds |
|
| Allowed acceptor address. Zero address if propsal can be accepted by any account |
|
| Proposer address |
|
| Flag to determine if a proposal is an offer or loan request |
|
| ID of a loan to be refinanced. Zero if creating a new loan. |
|
| Nonce space of the proposal |
|
| Nonce of the proposal |
|
| Loan type contract |
Multiproposal
Struct
Multiproposal
StructType | Name | Comment |
---|---|---|
|
| Root of the multiproposal merkle tree |
Last updated