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 
ProposalBasestruct - Implements 
_makeProposaland_acceptProposalfunctions 
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:
uint256nonceSpace- Nonce space of a proposal nonce to be revokeduint256nonce- 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:
bytes32proposalHash- hash of the respective proposal type structaddressproposer- 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:
bytes32 proposalHash- Hash of the proposalbytes32[] calldata proposalInclusionProof- Multiproposal inclusion proof. Empty if single proposal.bytes calldata signature- Signature of the proposalProposalBase memory proposal- ProposalBase structProposalValuesBase memory proposalValues- ProposalValues struct
Implementation
function _acceptProposal(
    bytes32 proposalHash,
    bytes32[] calldata proposalInclusionProof,
    bytes calldata signature,
    ProposalBase memory proposal,
    ProposalValuesBase memory proposalValues
) 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 == proposalValues.acceptor) {
        revert AcceptorIsProposer({ addr: proposalValues.acceptor});
    }
    // Check refinancing proposal
    if (proposalValues.refinancingLoanId == 0) {
        if (proposal.refinancingLoanId != 0) {
            revert InvalidRefinancingLoanId({ refinancingLoanId: proposal.refinancingLoanId });
        }
    } else {
        if (proposalValues.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 proposal acceptor controller
    if (proposal.acceptorController != address(0)) {
        if (IPWNAcceptorController(proposal.acceptorController).checkAcceptor({
            acceptor: proposalValues.acceptor,
            proposerData: proposal.acceptorControllerData,
            acceptorData: proposalValues.acceptorControllerData
        }) != type(IPWNAcceptorController).interfaceId) {
            revert InvalidAcceptorController({ acceptorController: proposal.acceptorController });
        }
    }
    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 {
        // Update utilized credit
        // Note: This will revert if utilized credit would exceed the available credit limit
        utilizedCredit.utilizeCredit(
            proposal.proposer, proposal.utilizedCreditId, proposal.creditAmount, 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 memorymultiproposal-Multiproposalstruct
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:
bytes32proposalTypehash- Hash of the respective proposal typebytes memoryencodedProposal- 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
        ))
    ));
}
_getLoanDuration
Overview
This function returns loan duration given supplied timestamp or duration.
This function takes one argument supplied by the caller:
uint32durationOrDate- Duration of a loan in seconds. If the value is greater than10^9, it's considered a timestamp of the loan end
Implementation
function _getLoanDuration(uint32 durationOrDate) internal view returns (uint32) {
    if (durationOrDate <= 1e9) {
        // Value is duration
        return durationOrDate;
    } else if (durationOrDate >= block.timestamp) {
        // Value is date
        return uint32(uint256(durationOrDate) - block.timestamp);
    } else {
        revert DefaultDateInPast({ defaultDate: durationOrDate, current: uint32(block.timestamp) });
    }
}
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 InvalidAcceptorController(address acceptorController);
error DefaultDateInPast(uint32 defaultDate, uint32 current);
CallerNotLoanContract
A CallerNotLoanContract error is thrown when a caller is missing a required hub tag.
This error has two parameters:
addresscalleraddressloanContract
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:
bytes32currentbytes32proposed
CallerIsNotStatedProposer
A CallerIsNotStatedProposer error is thrown when a caller is not a stated proposer.
This error has one parameter:
addressaddr
AcceptorIsProposer
An AcceptorIsProposer error is thrown when proposal acceptor and proposer are the same.
This error has one parameter:
addressaddr
InvalidRefinancingLoanId
An InvalidRefinancingLoanId error is thrown when provided refinance loan id cannot be used.
This error has one parameter:
uint256refinancingLoanId
AvailableCreditLimitExceeded
An AvailableCreditLimitExceeded error is thrown when a proposal would exceed the available credit limit.
This error has two parameters:
uint256useduint256limit
InvalidAcceptorController
A InvalidAcceptorController error is thrown when supplied acceptor controller isn't a valid acceptor controller contract.
This error has one parameter:
addressacceptorController
DefaultDateInPast
A DefaultDateInPast error is thrown when caller supplies a loan default date that's in the past.
This error has two parameters:
uint32defaultDateuint32current
ProposalBase Struct
| Type | Name | Comment | 
|---|---|---|
address | collateralAddress | Address of a loan collateral | 
uint256 | collateralId | ID of a collateral. Zero if ERC-20 | 
bool | checkCollateralStateFingerprint | Flag to enable check of collaterals state fingerprint (see ERC-5646) | 
bytes32 | collateralStateFingerprint | A collateral state fingerprint (see ERC-5646) | 
uint256 | creditAmount | Amount of credit asset | 
uint256 | availableCreditLimit | Maximum credit limit of credit asset | 
uint40 | expiration | Proposal expiration unix timestamp in seconds | 
address | acceptorController | Address of Acceptor Controller contract that will verify submitted acceptor data | 
bytes | acceptorControllerData | Data provided by proposer to be verified by Acceptor Controller | 
address | proposer | Proposer address | 
bool | isOffer | Flag to determine if a proposal is an offer or loan request | 
uint256 | refinancingLoanId | ID of a loan to be refinanced. Zero if creating a new loan. | 
uint256 | nonceSpace | Nonce space of the proposal | 
uint256 | nonce | Nonce of the proposal | 
address | loanContract | Loan type contract | 
ProposalValues Struct
| Type | Name | Comment | 
|---|---|---|
uint256 | refinancingLoanId | Loan ID to refinance if refinancing a loan | 
address | acceptor | Address of the acceptor | 
bytes | acceptorControllerData | Data provided by proposal acceptor to be verified by Acceptor Controller | 
Multiproposal Struct
| Type | Name | Comment | 
|---|---|---|
bytes32 | multiproposalMerkleRoot | Root of the multiproposal merkle tree |