PWNSimpleLoanProposal.sol is an abstract contract inherited by Simple Loan Proposal types.
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 revoked
uint256
nonce
- Proposal nonce to be revoked
Implementation
function revokeNonce(uint256 nonceSpace, uint256 nonce) external {
revokedNonce.revokeNonce(msg.sender, nonceSpace, nonce);
}
_makeProposal
Overview
Function to make an on-chain proposal.
This function takes two arguments:
bytes32
proposalHash
- hash of the respective proposal type struct
address
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 acceptor
uint256
refinancingLoanId
- Refinancing loan ID
bytes32
proposalHash
- Proposal hash
bytes32[] calldata
proposalInclusionProof
- Multiproposal inclusion proof. Empty if single proposal
bytes calldata
signature
- Signature of a proposal
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
});
}
}
}
}
getMultiproposalHash
Overview
This function returns a multiproposal hash according to EIP-712.
This function takes one argument supplied by the caller:
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 type
bytes 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
))
));
}
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:
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:
CallerIsNotStatedProposer
A CallerIsNotStatedProposer error is thrown when a caller is not a stated proposer.
This error has one parameter:
AcceptorIsProposer
An AcceptorIsProposer error is thrown when proposal acceptor and proposer are the same.
This error has one parameter:
InvalidRefinancingLoanId
An InvalidRefinancingLoanId error is thrown when provided refinance loan id cannot be used.
This error has one parameter:
AvailableCreditLimitExceeded
An AvailableCreditLimitExceeded error is thrown when a proposal would exceed the available credit limit.
This error has two parameters:
CallerNotAllowedAcceptor
A CallerNotAllowedAcceptor error is thrown when caller is not allowed to accept a proposal.
This error has two parameters: