diff --git a/src/loan/terms/simple/loan/PWNSimpleLoan.sol b/src/loan/terms/simple/loan/PWNSimpleLoan.sol index ff4d173..8c62edf 100644 --- a/src/loan/terms/simple/loan/PWNSimpleLoan.sol +++ b/src/loan/terms/simple/loan/PWNSimpleLoan.sol @@ -94,11 +94,13 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider { * @notice Loan proposal specification during loan creation. * @param proposalContract Address of a loan proposal contract. * @param proposalData Encoded proposal data that is passed to the loan proposal contract. + * @param proposalInclusionProof Inclusion proof of the proposal in the proposal contract. * @param signature Signature of the proposal. */ struct ProposalSpec { address proposalContract; bytes proposalData; + bytes32[] proposalInclusionProof; bytes signature; } @@ -297,6 +299,7 @@ contract PWNSimpleLoan is PWNVault, IERC5646, IPWNLoanMetadataProvider { acceptor: msg.sender, refinancingLoanId: callerSpec.refinancingLoanId, proposalData: proposalSpec.proposalData, + proposalInclusionProof: proposalSpec.proposalInclusionProof, signature: proposalSpec.signature }); diff --git a/src/loan/terms/simple/proposal/PWNSimpleLoanDutchAuctionProposal.sol b/src/loan/terms/simple/proposal/PWNSimpleLoanDutchAuctionProposal.sol index d175986..887dc3a 100644 --- a/src/loan/terms/simple/proposal/PWNSimpleLoanDutchAuctionProposal.sol +++ b/src/loan/terms/simple/proposal/PWNSimpleLoanDutchAuctionProposal.sol @@ -211,6 +211,7 @@ contract PWNSimpleLoanDutchAuctionProposal is PWNSimpleLoanProposal { address acceptor, uint256 refinancingLoanId, bytes calldata proposalData, + bytes32[] calldata proposalInclusionProof, bytes calldata signature ) override external returns (bytes32 proposalHash, PWNSimpleLoan.Terms memory loanTerms) { // Decode proposal data @@ -252,6 +253,7 @@ contract PWNSimpleLoanDutchAuctionProposal is PWNSimpleLoanProposal { acceptor, refinancingLoanId, proposalHash, + proposalInclusionProof, signature, ProposalBase({ collateralAddress: proposal.collateralAddress, diff --git a/src/loan/terms/simple/proposal/PWNSimpleLoanFungibleProposal.sol b/src/loan/terms/simple/proposal/PWNSimpleLoanFungibleProposal.sol index 882bab0..47e9ed5 100644 --- a/src/loan/terms/simple/proposal/PWNSimpleLoanFungibleProposal.sol +++ b/src/loan/terms/simple/proposal/PWNSimpleLoanFungibleProposal.sol @@ -161,6 +161,7 @@ contract PWNSimpleLoanFungibleProposal is PWNSimpleLoanProposal { address acceptor, uint256 refinancingLoanId, bytes calldata proposalData, + bytes32[] calldata proposalInclusionProof, bytes calldata signature ) override external returns (bytes32 proposalHash, PWNSimpleLoan.Terms memory loanTerms) { // Decode proposal data @@ -188,6 +189,7 @@ contract PWNSimpleLoanFungibleProposal is PWNSimpleLoanProposal { acceptor, refinancingLoanId, proposalHash, + proposalInclusionProof, signature, ProposalBase({ collateralAddress: proposal.collateralAddress, diff --git a/src/loan/terms/simple/proposal/PWNSimpleLoanListProposal.sol b/src/loan/terms/simple/proposal/PWNSimpleLoanListProposal.sol index 694aaf2..4008999 100644 --- a/src/loan/terms/simple/proposal/PWNSimpleLoanListProposal.sol +++ b/src/loan/terms/simple/proposal/PWNSimpleLoanListProposal.sol @@ -149,6 +149,7 @@ contract PWNSimpleLoanListProposal is PWNSimpleLoanProposal { address acceptor, uint256 refinancingLoanId, bytes calldata proposalData, + bytes32[] calldata proposalInclusionProof, bytes calldata signature ) override external returns (bytes32 proposalHash, PWNSimpleLoan.Terms memory loanTerms) { // Decode proposal data @@ -176,6 +177,7 @@ contract PWNSimpleLoanListProposal is PWNSimpleLoanProposal { acceptor, refinancingLoanId, proposalHash, + proposalInclusionProof, signature, ProposalBase({ collateralAddress: proposal.collateralAddress, diff --git a/src/loan/terms/simple/proposal/PWNSimpleLoanProposal.sol b/src/loan/terms/simple/proposal/PWNSimpleLoanProposal.sol index 1de93c5..99bbbb6 100644 --- a/src/loan/terms/simple/proposal/PWNSimpleLoanProposal.sol +++ b/src/loan/terms/simple/proposal/PWNSimpleLoanProposal.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity 0.8.16; +import { MerkleProof } from "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol"; + import { PWNConfig, IERC5646 } from "@pwn/config/PWNConfig.sol"; import { PWNHub } from "@pwn/hub/PWNHub.sol"; import { PWNHubTags } from "@pwn/hub/PWNHubTags.sol"; @@ -16,11 +18,18 @@ import "@pwn/PWNErrors.sol"; abstract contract PWNSimpleLoanProposal { bytes32 public immutable DOMAIN_SEPARATOR; + bytes32 public immutable MULTIPROPOSAL_DOMAIN_SEPARATOR; PWNHub public immutable hub; PWNRevokedNonce public immutable revokedNonce; PWNConfig public immutable config; + bytes32 public constant MULTIPROPOSAL_TYPEHASH = keccak256("Multiproposal(bytes32 multiproposalMerkleRoot)"); + + struct Multiproposal { + bytes32 multiproposalMerkleRoot; + } + struct ProposalBase { address collateralAddress; uint256 collateralId; @@ -69,6 +78,11 @@ abstract contract PWNSimpleLoanProposal { block.chainid, address(this) )); + + MULTIPROPOSAL_DOMAIN_SEPARATOR = keccak256(abi.encode( + keccak256("EIP712Domain(string name)"), + keccak256("PWNMultiproposal") + )); } @@ -76,6 +90,19 @@ abstract contract PWNSimpleLoanProposal { |* # EXTERNALS *| |*----------------------------------------------------------*/ + /** + * @notice Get a multiproposal hash according to EIP-712. + * @param multiproposal Multiproposal struct. + * @return Multiproposal hash. + */ + 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) + )) + )); + } + /** * @notice Helper function for revoking a proposal nonce on behalf of a caller. * @param nonceSpace Nonce space of a proposal nonce to be revoked. @@ -91,6 +118,7 @@ abstract contract PWNSimpleLoanProposal { * @param acceptor Address of a proposal acceptor. * @param refinancingLoanId Id of a loan to be refinanced. 0 if creating a new loan. * @param proposalData Encoded proposal data with signature. + * @param proposalInclusionProof Multiproposal inclusion proof. Empty if single proposal. * @return proposalHash Proposal hash. * @return loanTerms Loan terms. */ @@ -98,6 +126,7 @@ abstract contract PWNSimpleLoanProposal { address acceptor, uint256 refinancingLoanId, bytes calldata proposalData, + bytes32[] calldata proposalInclusionProof, bytes calldata signature ) virtual external returns (bytes32 proposalHash, PWNSimpleLoan.Terms memory loanTerms); @@ -141,6 +170,7 @@ abstract contract PWNSimpleLoanProposal { * @param acceptor Address of a proposal acceptor. * @param refinancingLoanId Refinancing loan ID. * @param proposalHash Proposal hash. + * @param proposalInclusionProof Multiproposal inclusion proof. Empty if single proposal. * @param signature Signature of a proposal. * @param proposal Proposal base struct. */ @@ -148,7 +178,8 @@ abstract contract PWNSimpleLoanProposal { address acceptor, uint256 refinancingLoanId, bytes32 proposalHash, - bytes memory signature, + bytes32[] calldata proposalInclusionProof, + bytes calldata signature, ProposalBase memory proposal ) internal { // Check loan contract @@ -159,10 +190,26 @@ abstract contract PWNSimpleLoanProposal { revert AddressMissingHubTag({ addr: proposal.loanContract, tag: PWNHubTags.ACTIVE_LOAN }); } - // Check proposal has been made via on-chain tx, EIP-1271 or signed off-chain - if (!proposalsMade[proposalHash]) { - if (!PWNSignatureChecker.isValidSignatureNow(proposal.proposer, proposalHash, signature)) { - revert InvalidSignature({ signer: proposal.proposer, digest: proposalHash }); + // 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 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 InvalidSignature({ signer: proposal.proposer, digest: multiproposalHash }); } } diff --git a/src/loan/terms/simple/proposal/PWNSimpleLoanSimpleProposal.sol b/src/loan/terms/simple/proposal/PWNSimpleLoanSimpleProposal.sol index 14930a9..c1096ad 100644 --- a/src/loan/terms/simple/proposal/PWNSimpleLoanSimpleProposal.sol +++ b/src/loan/terms/simple/proposal/PWNSimpleLoanSimpleProposal.sol @@ -129,6 +129,7 @@ contract PWNSimpleLoanSimpleProposal is PWNSimpleLoanProposal { address acceptor, uint256 refinancingLoanId, bytes calldata proposalData, + bytes32[] calldata proposalInclusionProof, bytes calldata signature ) override external returns (bytes32 proposalHash, PWNSimpleLoan.Terms memory loanTerms) { // Decode proposal data @@ -142,6 +143,7 @@ contract PWNSimpleLoanSimpleProposal is PWNSimpleLoanProposal { acceptor, refinancingLoanId, proposalHash, + proposalInclusionProof, signature, ProposalBase({ collateralAddress: proposal.collateralAddress, diff --git a/test/unit/PWNSimpleLoan.t.sol b/test/unit/PWNSimpleLoan.t.sol index 127ac49..dc28d22 100644 --- a/test/unit/PWNSimpleLoan.t.sol +++ b/test/unit/PWNSimpleLoan.t.sol @@ -32,11 +32,11 @@ abstract contract PWNSimpleLoanTest is Test { address borrower = makeAddr("borrower"); address sourceOfFunds = address(new DummyCompoundPool()); uint256 loanDurationInDays = 101; - PWNSimpleLoan.LenderSpec lenderSpec; PWNSimpleLoan.LOAN simpleLoan; PWNSimpleLoan.LOAN nonExistingLoan; PWNSimpleLoan.Terms simpleLoanTerms; PWNSimpleLoan.ProposalSpec proposalSpec; + PWNSimpleLoan.LenderSpec lenderSpec; PWNSimpleLoan.CallerSpec callerSpec; PWNSimpleLoan.ExtensionProposal extension; T20 fungibleAsset; @@ -114,6 +114,7 @@ abstract contract PWNSimpleLoanTest is Test { proposalSpec = PWNSimpleLoan.ProposalSpec({ proposalContract: proposalContract, proposalData: proposalData, + proposalInclusionProof: new bytes32[](0), signature: signature }); @@ -246,7 +247,7 @@ abstract contract PWNSimpleLoanTest is Test { function _mockLoanTerms(PWNSimpleLoan.Terms memory _terms) internal { vm.mockCall( proposalContract, - abi.encodeWithSignature("acceptProposal(address,uint256,bytes,bytes)"), + abi.encodeWithSignature("acceptProposal(address,uint256,bytes,bytes32[],bytes)"), abi.encode(proposalHash, _terms) ); } @@ -372,10 +373,19 @@ contract PWNSimpleLoan_CreateLOAN_Test is PWNSimpleLoanTest { }); } - function testFuzz_shouldCallProposalContract(address caller) external { + function testFuzz_shouldCallProposalContract( + address caller, bytes memory _proposalData, bytes32[] memory _proposalInclusionProof, bytes memory _signature + ) external { + proposalSpec.proposalData = _proposalData; + proposalSpec.proposalInclusionProof = _proposalInclusionProof; + proposalSpec.signature = _signature; + vm.expectCall( proposalContract, - abi.encodeWithSignature("acceptProposal(address,uint256,bytes,bytes)", caller, 0, proposalData, signature) + abi.encodeWithSignature( + "acceptProposal(address,uint256,bytes,bytes32[],bytes)", + caller, 0, _proposalData, _proposalInclusionProof, _signature + ) ); vm.prank(caller); diff --git a/test/unit/PWNSimpleLoanDutchAuctionProposal.t.sol b/test/unit/PWNSimpleLoanDutchAuctionProposal.t.sol index 5e77c1d..8db63b7 100644 --- a/test/unit/PWNSimpleLoanDutchAuctionProposal.t.sol +++ b/test/unit/PWNSimpleLoanDutchAuctionProposal.t.sol @@ -82,55 +82,46 @@ abstract contract PWNSimpleLoanDutchAuctionProposalTest is PWNSimpleLoanProposal )); } - function _updateProposal(Params memory _params) internal { - if (_params.base.isOffer) { - proposal.minCreditAmount = _params.base.creditAmount; + function _updateProposal(PWNSimpleLoanProposal.ProposalBase memory _proposal) internal { + if (_proposal.isOffer) { + proposal.minCreditAmount = _proposal.creditAmount; proposal.maxCreditAmount = proposal.minCreditAmount * 10; proposalValues.intendedCreditAmount = proposal.minCreditAmount; } else { - proposal.maxCreditAmount = _params.base.creditAmount; + proposal.maxCreditAmount = _proposal.creditAmount; proposal.minCreditAmount = proposal.maxCreditAmount / 10; proposalValues.intendedCreditAmount = proposal.maxCreditAmount; } - proposal.collateralAddress = _params.base.collateralAddress; - proposal.collateralId = _params.base.collateralId; - proposal.checkCollateralStateFingerprint = _params.base.checkCollateralStateFingerprint; - proposal.collateralStateFingerprint = _params.base.collateralStateFingerprint; - proposal.availableCreditLimit = _params.base.availableCreditLimit; - proposal.auctionDuration = _params.base.expiration - proposal.auctionStart - 1 minutes; - proposal.allowedAcceptor = _params.base.allowedAcceptor; - proposal.proposer = _params.base.proposer; - proposal.isOffer = _params.base.isOffer; - proposal.refinancingLoanId = _params.base.refinancingLoanId; - proposal.nonceSpace = _params.base.nonceSpace; - proposal.nonce = _params.base.nonce; - proposal.loanContract = _params.base.loanContract; - } - - function _proposalSignature(Params memory _params) internal view returns (bytes memory signature) { - if (_params.signerPK != 0) { - if (_params.compactSignature) { - signature = _signProposalHashCompact(_params.signerPK, _proposalHash(proposal)); - } else { - signature = _signProposalHash(_params.signerPK, _proposalHash(proposal)); - } - } + proposal.collateralAddress = _proposal.collateralAddress; + proposal.collateralId = _proposal.collateralId; + proposal.checkCollateralStateFingerprint = _proposal.checkCollateralStateFingerprint; + proposal.collateralStateFingerprint = _proposal.collateralStateFingerprint; + proposal.availableCreditLimit = _proposal.availableCreditLimit; + proposal.auctionDuration = _proposal.expiration - proposal.auctionStart - 1 minutes; + proposal.allowedAcceptor = _proposal.allowedAcceptor; + proposal.proposer = _proposal.proposer; + proposal.isOffer = _proposal.isOffer; + proposal.refinancingLoanId = _proposal.refinancingLoanId; + proposal.nonceSpace = _proposal.nonceSpace; + proposal.nonce = _proposal.nonce; + proposal.loanContract = _proposal.loanContract; } function _callAcceptProposalWith(Params memory _params) internal override returns (bytes32, PWNSimpleLoan.Terms memory) { - _updateProposal(_params); + _updateProposal(_params.base); return proposalContract.acceptProposal({ acceptor: _params.acceptor, refinancingLoanId: _params.refinancingLoanId, proposalData: abi.encode(proposal, proposalValues), - signature: _proposalSignature(_params) + proposalInclusionProof: _params.proposalInclusionProof, + signature: _params.signature }); } function _getProposalHashWith(Params memory _params) internal override returns (bytes32) { - _updateProposal(_params); + _updateProposal(_params.base); return _proposalHash(proposal); } @@ -430,7 +421,8 @@ contract PWNSimpleLoanDutchAuctionProposal_AcceptProposal_Test is PWNSimpleLoanD acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal, proposalValues), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); } @@ -464,7 +456,8 @@ contract PWNSimpleLoanDutchAuctionProposal_AcceptProposal_Test is PWNSimpleLoanD acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal, proposalValues), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); } @@ -496,7 +489,8 @@ contract PWNSimpleLoanDutchAuctionProposal_AcceptProposal_Test is PWNSimpleLoanD acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal, proposalValues), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); assertEq(proposalHash, _proposalHash(proposal)); diff --git a/test/unit/PWNSimpleLoanFungibleProposal.t.sol b/test/unit/PWNSimpleLoanFungibleProposal.t.sol index 330462f..5731fb8 100644 --- a/test/unit/PWNSimpleLoanFungibleProposal.t.sol +++ b/test/unit/PWNSimpleLoanFungibleProposal.t.sol @@ -79,47 +79,38 @@ abstract contract PWNSimpleLoanFungibleProposalTest is PWNSimpleLoanProposalTest )); } - function _updateProposal(Params memory _params) internal { - proposal.collateralAddress = _params.base.collateralAddress; - proposal.collateralId = _params.base.collateralId; - proposal.checkCollateralStateFingerprint = _params.base.checkCollateralStateFingerprint; - proposal.collateralStateFingerprint = _params.base.collateralStateFingerprint; - proposal.availableCreditLimit = _params.base.availableCreditLimit; - proposal.expiration = _params.base.expiration; - proposal.allowedAcceptor = _params.base.allowedAcceptor; - proposal.proposer = _params.base.proposer; - proposal.isOffer = _params.base.isOffer; - proposal.refinancingLoanId = _params.base.refinancingLoanId; - proposal.nonceSpace = _params.base.nonceSpace; - proposal.nonce = _params.base.nonce; - proposal.loanContract = _params.base.loanContract; - - proposalValues.collateralAmount = _params.base.creditAmount; - } - - function _proposalSignature(Params memory _params) internal view returns (bytes memory signature) { - if (_params.signerPK != 0) { - if (_params.compactSignature) { - signature = _signProposalHashCompact(_params.signerPK, _proposalHash(proposal)); - } else { - signature = _signProposalHash(_params.signerPK, _proposalHash(proposal)); - } - } + function _updateProposal(PWNSimpleLoanProposal.ProposalBase memory _proposal) internal { + proposal.collateralAddress = _proposal.collateralAddress; + proposal.collateralId = _proposal.collateralId; + proposal.checkCollateralStateFingerprint = _proposal.checkCollateralStateFingerprint; + proposal.collateralStateFingerprint = _proposal.collateralStateFingerprint; + proposal.availableCreditLimit = _proposal.availableCreditLimit; + proposal.expiration = _proposal.expiration; + proposal.allowedAcceptor = _proposal.allowedAcceptor; + proposal.proposer = _proposal.proposer; + proposal.isOffer = _proposal.isOffer; + proposal.refinancingLoanId = _proposal.refinancingLoanId; + proposal.nonceSpace = _proposal.nonceSpace; + proposal.nonce = _proposal.nonce; + proposal.loanContract = _proposal.loanContract; + + proposalValues.collateralAmount = _proposal.creditAmount; } function _callAcceptProposalWith(Params memory _params) internal override returns (bytes32, PWNSimpleLoan.Terms memory) { - _updateProposal(_params); + _updateProposal(_params.base); return proposalContract.acceptProposal({ acceptor: _params.acceptor, refinancingLoanId: _params.refinancingLoanId, proposalData: abi.encode(proposal, proposalValues), - signature: _proposalSignature(_params) + proposalInclusionProof: _params.proposalInclusionProof, + signature: _params.signature }); } function _getProposalHashWith(Params memory _params) internal override returns (bytes32) { - _updateProposal(_params); + _updateProposal(_params.base); return _proposalHash(proposal); } @@ -306,7 +297,8 @@ contract PWNSimpleLoanFungibleProposal_AcceptProposal_Test is PWNSimpleLoanFungi acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal, proposalValues), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); } @@ -324,7 +316,8 @@ contract PWNSimpleLoanFungibleProposal_AcceptProposal_Test is PWNSimpleLoanFungi acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal, proposalValues), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); } @@ -340,7 +333,8 @@ contract PWNSimpleLoanFungibleProposal_AcceptProposal_Test is PWNSimpleLoanFungi acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal, proposalValues), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); assertEq(proposalHash, _proposalHash(proposal)); diff --git a/test/unit/PWNSimpleLoanListProposal.t.sol b/test/unit/PWNSimpleLoanListProposal.t.sol index 5f6e03e..052a199 100644 --- a/test/unit/PWNSimpleLoanListProposal.t.sol +++ b/test/unit/PWNSimpleLoanListProposal.t.sol @@ -80,47 +80,38 @@ abstract contract PWNSimpleLoanListProposalTest is PWNSimpleLoanProposalTest { )); } - function _updateProposal(Params memory _params) internal { - proposal.collateralAddress = _params.base.collateralAddress; - proposal.checkCollateralStateFingerprint = _params.base.checkCollateralStateFingerprint; - proposal.collateralStateFingerprint = _params.base.collateralStateFingerprint; - proposal.creditAmount = _params.base.creditAmount; - proposal.availableCreditLimit = _params.base.availableCreditLimit; - proposal.expiration = _params.base.expiration; - proposal.allowedAcceptor = _params.base.allowedAcceptor; - proposal.proposer = _params.base.proposer; - proposal.isOffer = _params.base.isOffer; - proposal.refinancingLoanId = _params.base.refinancingLoanId; - proposal.nonceSpace = _params.base.nonceSpace; - proposal.nonce = _params.base.nonce; - proposal.loanContract = _params.base.loanContract; - - proposalValues.collateralId = _params.base.collateralId; - } - - function _proposalSignature(Params memory _params) internal view returns (bytes memory signature) { - if (_params.signerPK != 0) { - if (_params.compactSignature) { - signature = _signProposalHashCompact(_params.signerPK, _proposalHash(proposal)); - } else { - signature = _signProposalHash(_params.signerPK, _proposalHash(proposal)); - } - } + function _updateProposal(PWNSimpleLoanProposal.ProposalBase memory _proposal) internal { + proposal.collateralAddress = _proposal.collateralAddress; + proposal.checkCollateralStateFingerprint = _proposal.checkCollateralStateFingerprint; + proposal.collateralStateFingerprint = _proposal.collateralStateFingerprint; + proposal.creditAmount = _proposal.creditAmount; + proposal.availableCreditLimit = _proposal.availableCreditLimit; + proposal.expiration = _proposal.expiration; + proposal.allowedAcceptor = _proposal.allowedAcceptor; + proposal.proposer = _proposal.proposer; + proposal.isOffer = _proposal.isOffer; + proposal.refinancingLoanId = _proposal.refinancingLoanId; + proposal.nonceSpace = _proposal.nonceSpace; + proposal.nonce = _proposal.nonce; + proposal.loanContract = _proposal.loanContract; + + proposalValues.collateralId = _proposal.collateralId; } function _callAcceptProposalWith(Params memory _params) internal override returns (bytes32, PWNSimpleLoan.Terms memory) { - _updateProposal(_params); + _updateProposal(_params.base); return proposalContract.acceptProposal({ acceptor: _params.acceptor, refinancingLoanId: _params.refinancingLoanId, proposalData: abi.encode(proposal, proposalValues), - signature: _proposalSignature(_params) + proposalInclusionProof: _params.proposalInclusionProof, + signature: _params.signature }); } function _getProposalHashWith(Params memory _params) internal override returns (bytes32) { - _updateProposal(_params); + _updateProposal(_params.base); return _proposalHash(proposal); } @@ -290,7 +281,8 @@ contract PWNSimpleLoanListProposal_AcceptProposal_Test is PWNSimpleLoanListPropo acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal, proposalValues), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); } @@ -312,7 +304,8 @@ contract PWNSimpleLoanListProposal_AcceptProposal_Test is PWNSimpleLoanListPropo acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal, proposalValues), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); } @@ -339,7 +332,8 @@ contract PWNSimpleLoanListProposal_AcceptProposal_Test is PWNSimpleLoanListPropo acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal, proposalValues), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); } @@ -351,7 +345,8 @@ contract PWNSimpleLoanListProposal_AcceptProposal_Test is PWNSimpleLoanListPropo acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal, proposalValues), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); assertEq(proposalHash, _proposalHash(proposal)); diff --git a/test/unit/PWNSimpleLoanProposal.t.sol b/test/unit/PWNSimpleLoanProposal.t.sol index 19af484..7436af6 100644 --- a/test/unit/PWNSimpleLoanProposal.t.sol +++ b/test/unit/PWNSimpleLoanProposal.t.sol @@ -5,6 +5,7 @@ import "forge-std/Test.sol"; import { MultiToken } from "MultiToken/MultiToken.sol"; +import { MerkleProof } from "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol"; import { Math } from "openzeppelin-contracts/contracts/utils/math/Math.sol"; import { PWNHubTags } from "@pwn/hub/PWNHubTags.sol"; @@ -39,8 +40,8 @@ abstract contract PWNSimpleLoanProposalTest is Test { PWNSimpleLoanProposal.ProposalBase base; address acceptor; uint256 refinancingLoanId; - uint256 signerPK; - bool compactSignature; + bytes32[] proposalInclusionProof; + bytes signature; } function setUp() virtual public { @@ -56,8 +57,6 @@ abstract contract PWNSimpleLoanProposalTest is Test { params.base.loanContract = activeLoanContract; params.acceptor = acceptor; params.refinancingLoanId = 0; - params.signerPK = proposerPK; - params.compactSignature = false; vm.mockCall( revokedNonce, @@ -84,12 +83,12 @@ abstract contract PWNSimpleLoanProposalTest is Test { ); } - function _signProposalHash(uint256 pk, bytes32 proposalHash) internal pure returns (bytes memory) { + function _sign(uint256 pk, bytes32 proposalHash) internal pure returns (bytes memory) { (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, proposalHash); return abi.encodePacked(r, s, v); } - function _signProposalHashCompact(uint256 pk, bytes32 proposalHash) internal pure returns (bytes memory) { + function _signCompact(uint256 pk, bytes32 proposalHash) internal pure returns (bytes memory) { (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, proposalHash); return abi.encodePacked(r, bytes32(uint256(v) - 27) << 255 | s); } @@ -118,6 +117,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp function testFuzz_shouldFail_whenCallerIsNotProposedLoanContract(address caller) external { vm.assume(caller != activeLoanContract); params.base.loanContract = activeLoanContract; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.expectRevert(abi.encodeWithSelector(CallerNotLoanContract.selector, caller, activeLoanContract)); vm.prank(caller); @@ -127,25 +127,93 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp function testFuzz_shouldFail_whenCallerNotTagged_ACTIVE_LOAN(address caller) external { vm.assume(caller != activeLoanContract); params.base.loanContract = caller; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.expectRevert(abi.encodeWithSelector(AddressMissingHubTag.selector, caller, PWNHubTags.ACTIVE_LOAN)); vm.prank(caller); _callAcceptProposalWith(); } - function test_shouldFail_whenInvalidSignature_whenEOA() external { - params.signerPK = 1; + function testFuzz_shouldFail_whenInvalidSignature_whenEOA(uint256 randomPK) external { + randomPK = boundPrivateKey(randomPK); + vm.assume(randomPK != proposerPK); + params.signature = _sign(randomPK, _getProposalHashWith()); - vm.expectRevert(abi.encodeWithSelector(InvalidSignature.selector, proposer, _getProposalHashWith(params))); + vm.expectRevert(abi.encodeWithSelector(InvalidSignature.selector, proposer, _getProposalHashWith())); vm.prank(activeLoanContract); _callAcceptProposalWith(); } function test_shouldFail_whenInvalidSignature_whenContractAccount() external { vm.etch(proposer, bytes("data")); - params.signerPK = 0; + params.signature = ""; - vm.expectRevert(abi.encodeWithSelector(InvalidSignature.selector, proposer, _getProposalHashWith(params))); + vm.expectRevert(abi.encodeWithSelector(InvalidSignature.selector, proposer, _getProposalHashWith())); + vm.prank(activeLoanContract); + _callAcceptProposalWith(); + } + + function testFuzz_shouldFail_withInvalidSignature_whenEOA_whenMultiproposal(uint256 randomPK) external { + randomPK = boundPrivateKey(randomPK); + vm.assume(randomPK != proposerPK); + + bytes32 proposalHash = _getProposalHashWith(); + bytes32[] memory proposalInclusionProof = new bytes32[](1); + proposalInclusionProof[0] = keccak256("leaf1"); + bytes32 root = keccak256( + uint256(proposalHash) < uint256(proposalInclusionProof[0]) + ? abi.encode(proposalHash, proposalInclusionProof[0]) + : abi.encode(proposalInclusionProof[0], proposalHash) + ); + bytes32 multiproposalHash = proposalContractAddr.getMultiproposalHash(PWNSimpleLoanProposal.Multiproposal(root)); + params.signature = _sign(randomPK, multiproposalHash); + params.proposalInclusionProof = proposalInclusionProof; + + vm.expectRevert(abi.encodeWithSelector(InvalidSignature.selector, proposer, multiproposalHash)); + vm.prank(activeLoanContract); + _callAcceptProposalWith(); + } + + function test_shouldFail_whenInvalidSignature_whenContractAccount_whenMultiproposal() external { + vm.etch(proposer, bytes("data")); + bytes32 proposalHash = _getProposalHashWith(); + bytes32[] memory proposalInclusionProof = new bytes32[](1); + proposalInclusionProof[0] = keccak256("leaf1"); + bytes32 root = keccak256( + uint256(proposalHash) < uint256(proposalInclusionProof[0]) + ? abi.encode(proposalHash, proposalInclusionProof[0]) + : abi.encode(proposalInclusionProof[0], proposalHash) + ); + bytes32 multiproposalHash = proposalContractAddr.getMultiproposalHash(PWNSimpleLoanProposal.Multiproposal(root)); + params.signature = ""; + params.proposalInclusionProof = proposalInclusionProof; + + vm.expectRevert(abi.encodeWithSelector(InvalidSignature.selector, proposer, multiproposalHash)); + vm.prank(activeLoanContract); + _callAcceptProposalWith(); + } + + function test_shouldFail_withInvalidInclusionProof() external { + bytes32 proposalHash = _getProposalHashWith(); + bytes32[] memory proposalInclusionProof = new bytes32[](1); + proposalInclusionProof[0] = keccak256("other leaf1"); + bytes32 leaf = keccak256("leaf1"); + bytes32 root = keccak256( + uint256(proposalHash) < uint256(leaf) + ? abi.encode(proposalHash, leaf) + : abi.encode(leaf, proposalHash) + ); + bytes32 multiproposalHash = proposalContractAddr.getMultiproposalHash(PWNSimpleLoanProposal.Multiproposal(root)); + params.signature = _sign(proposerPK, multiproposalHash); + params.proposalInclusionProof = proposalInclusionProof; + + bytes32 actualRoot = keccak256( + uint256(proposalHash) < uint256(proposalInclusionProof[0]) + ? abi.encode(proposalHash, proposalInclusionProof[0]) + : abi.encode(proposalInclusionProof[0], proposalHash) + ); + bytes32 actualMultiproposalHash = proposalContractAddr.getMultiproposalHash(PWNSimpleLoanProposal.Multiproposal(actualRoot)); + vm.expectRevert(abi.encodeWithSelector(InvalidSignature.selector, proposer, actualMultiproposalHash)); vm.prank(activeLoanContract); _callAcceptProposalWith(); } @@ -153,24 +221,24 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp function test_shouldPass_whenProposalMadeOnchain() external { vm.store( address(proposalContractAddr), - keccak256(abi.encode(_getProposalHashWith(params), PROPOSALS_MADE_SLOT)), + keccak256(abi.encode(_getProposalHashWith(), PROPOSALS_MADE_SLOT)), bytes32(uint256(1)) ); - params.signerPK = 0; + params.signature = ""; vm.prank(activeLoanContract); _callAcceptProposalWith(); } function test_shouldPass_withValidSignature_whenEOA_whenStandardSignature() external { - params.compactSignature = false; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.prank(activeLoanContract); _callAcceptProposalWith(); } function test_shouldPass_withValidSignature_whenEOA_whenCompactEIP2098Signature() external { - params.compactSignature = true; + params.signature = _signCompact(proposerPK, _getProposalHashWith()); vm.prank(activeLoanContract); _callAcceptProposalWith(); @@ -178,11 +246,71 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp function test_shouldPass_whenValidSignature_whenContractAccount() external { vm.etch(proposer, bytes("data")); - params.signerPK = 0; + params.signature = bytes("some signature"); + + bytes32 proposalHash = _getProposalHashWith(); vm.mockCall( proposer, - abi.encodeWithSignature("isValidSignature(bytes32,bytes)"), + abi.encodeWithSignature("isValidSignature(bytes32,bytes)", proposalHash, params.signature), + abi.encode(bytes4(0x1626ba7e)) + ); + + vm.prank(activeLoanContract); + _callAcceptProposalWith(); + } + + function test_shouldPass_withValidSignature_whenEOA_whenStandardSignature_whenMultiproposal() external { + bytes32 proposalHash = _getProposalHashWith(); + bytes32[] memory proposalInclusionProof = new bytes32[](1); + proposalInclusionProof[0] = keccak256("leaf1"); + bytes32 root = keccak256( + uint256(proposalHash) < uint256(proposalInclusionProof[0]) + ? abi.encode(proposalHash, proposalInclusionProof[0]) + : abi.encode(proposalInclusionProof[0], proposalHash) + ); + bytes32 multiproposalHash = proposalContractAddr.getMultiproposalHash(PWNSimpleLoanProposal.Multiproposal(root)); + params.signature = _sign(proposerPK, multiproposalHash); + params.proposalInclusionProof = proposalInclusionProof; + + vm.prank(activeLoanContract); + _callAcceptProposalWith(); + } + + function test_shouldPass_withValidSignature_whenEOA_whenCompactEIP2098Signature_whenMultiproposal() external { + bytes32 proposalHash = _getProposalHashWith(); + bytes32[] memory proposalInclusionProof = new bytes32[](1); + proposalInclusionProof[0] = keccak256("leaf1"); + bytes32 root = keccak256( + uint256(proposalHash) < uint256(proposalInclusionProof[0]) + ? abi.encode(proposalHash, proposalInclusionProof[0]) + : abi.encode(proposalInclusionProof[0], proposalHash) + ); + bytes32 multiproposalHash = proposalContractAddr.getMultiproposalHash(PWNSimpleLoanProposal.Multiproposal(root)); + params.signature = _signCompact(proposerPK, multiproposalHash); + params.proposalInclusionProof = proposalInclusionProof; + + vm.prank(activeLoanContract); + _callAcceptProposalWith(); + } + + function test_shouldPass_whenValidSignature_whenContractAccount_whenMultiproposal() external { + vm.etch(proposer, bytes("data")); + bytes32 proposalHash = _getProposalHashWith(); + bytes32[] memory proposalInclusionProof = new bytes32[](1); + proposalInclusionProof[0] = keccak256("leaf1"); + bytes32 root = keccak256( + uint256(proposalHash) < uint256(proposalInclusionProof[0]) + ? abi.encode(proposalHash, proposalInclusionProof[0]) + : abi.encode(proposalInclusionProof[0], proposalHash) + ); + bytes32 multiproposalHash = proposalContractAddr.getMultiproposalHash(PWNSimpleLoanProposal.Multiproposal(root)); + params.signature = bytes("some random string"); + params.proposalInclusionProof = proposalInclusionProof; + + vm.mockCall( + proposer, + abi.encodeWithSignature("isValidSignature(bytes32,bytes)", multiproposalHash, params.signature), abi.encode(bytes4(0x1626ba7e)) ); @@ -194,6 +322,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp vm.assume(proposedRefinancingLoanId != 0); params.base.refinancingLoanId = proposedRefinancingLoanId; params.refinancingLoanId = 0; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.expectRevert(abi.encodeWithSelector(InvalidRefinancingLoanId.selector, proposedRefinancingLoanId)); vm.prank(activeLoanContract); @@ -208,6 +337,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp params.base.refinancingLoanId = proposedRefinancingLoanId; params.base.isOffer = true; params.refinancingLoanId = refinancingLoanId; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.expectRevert(abi.encodeWithSelector(InvalidRefinancingLoanId.selector, proposedRefinancingLoanId)); vm.prank(activeLoanContract); @@ -221,6 +351,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp params.base.refinancingLoanId = 0; params.base.isOffer = true; params.refinancingLoanId = refinancingLoanId; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.prank(activeLoanContract); _callAcceptProposalWith(); @@ -233,6 +364,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp params.base.refinancingLoanId = proposedRefinancingLoanId; params.base.isOffer = false; params.refinancingLoanId = refinancingLoanId; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.expectRevert(abi.encodeWithSelector(InvalidRefinancingLoanId.selector, proposedRefinancingLoanId)); vm.prank(activeLoanContract); @@ -243,6 +375,8 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp timestamp = bound(timestamp, params.base.expiration, type(uint256).max); vm.warp(timestamp); + params.signature = _sign(proposerPK, _getProposalHashWith()); + vm.expectRevert(abi.encodeWithSelector(Expired.selector, timestamp, params.base.expiration)); vm.prank(activeLoanContract); _callAcceptProposalWith(); @@ -251,6 +385,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp function testFuzz_shouldFail_whenOfferNonceNotUsable(uint256 nonceSpace, uint256 nonce) external { params.base.nonceSpace = nonceSpace; params.base.nonce = nonce; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.mockCall( revokedNonce, @@ -272,6 +407,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp vm.assume(caller != allowedAcceptor); params.base.allowedAcceptor = allowedAcceptor; params.acceptor = caller; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.expectRevert(abi.encodeWithSelector(CallerNotAllowedAcceptor.selector, caller, allowedAcceptor)); vm.prank(activeLoanContract); @@ -282,6 +418,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp params.base.availableCreditLimit = 0; params.base.nonceSpace = nonceSpace; params.base.nonce = nonce; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.expectCall( revokedNonce, @@ -297,10 +434,11 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp limit = bound(limit, used, used + params.base.creditAmount - 1); params.base.availableCreditLimit = limit; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.store( address(proposalContractAddr), - keccak256(abi.encode(_getProposalHashWith(params), CREDIT_USED_SLOT)), + keccak256(abi.encode(_getProposalHashWith(), CREDIT_USED_SLOT)), bytes32(used) ); @@ -316,8 +454,9 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp limit = bound(limit, used + params.base.creditAmount, type(uint256).max); params.base.availableCreditLimit = limit; + params.signature = _sign(proposerPK, _getProposalHashWith()); - bytes32 proposalHash = _getProposalHashWith(params); + bytes32 proposalHash = _getProposalHashWith(); vm.store( address(proposalContractAddr), @@ -333,6 +472,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp function test_shouldNotCallComputerRegistry_whenShouldNotCheckStateFingerprint() external { params.base.checkCollateralStateFingerprint = false; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.expectCall({ callee: config, @@ -346,6 +486,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp function test_shouldFail_whenComputerRegistryReturnsZeroAddress_whenShouldCheckStateFingerprint() external { params.base.collateralAddress = token; + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.mockCall( config, @@ -362,6 +503,7 @@ abstract contract PWNSimpleLoanProposal_AcceptProposal_Test is PWNSimpleLoanProp bytes32 stateFingerprint ) external { vm.assume(stateFingerprint != params.base.collateralStateFingerprint); + params.signature = _sign(proposerPK, _getProposalHashWith()); vm.mockCall( stateFingerprintComputer, diff --git a/test/unit/PWNSimpleLoanSimpleProposal.t.sol b/test/unit/PWNSimpleLoanSimpleProposal.t.sol index 4926043..dd44c8e 100644 --- a/test/unit/PWNSimpleLoanSimpleProposal.t.sol +++ b/test/unit/PWNSimpleLoanSimpleProposal.t.sol @@ -72,46 +72,37 @@ abstract contract PWNSimpleLoanSimpleProposalTest is PWNSimpleLoanProposalTest { )); } - function _updateProposal(Params memory _params) internal { - proposal.collateralAddress = _params.base.collateralAddress; - proposal.collateralId = _params.base.collateralId; - proposal.checkCollateralStateFingerprint = _params.base.checkCollateralStateFingerprint; - proposal.collateralStateFingerprint = _params.base.collateralStateFingerprint; - proposal.creditAmount = _params.base.creditAmount; - proposal.availableCreditLimit = _params.base.availableCreditLimit; - proposal.expiration = _params.base.expiration; - proposal.allowedAcceptor = _params.base.allowedAcceptor; - proposal.proposer = _params.base.proposer; - proposal.isOffer = _params.base.isOffer; - proposal.refinancingLoanId = _params.base.refinancingLoanId; - proposal.nonceSpace = _params.base.nonceSpace; - proposal.nonce = _params.base.nonce; - proposal.loanContract = _params.base.loanContract; - } - - function _proposalSignature(Params memory _params) internal view returns (bytes memory signature) { - if (_params.signerPK != 0) { - if (_params.compactSignature) { - signature = _signProposalHashCompact(_params.signerPK, _proposalHash(proposal)); - } else { - signature = _signProposalHash(_params.signerPK, _proposalHash(proposal)); - } - } + function _updateProposal(PWNSimpleLoanProposal.ProposalBase memory _proposal) internal { + proposal.collateralAddress = _proposal.collateralAddress; + proposal.collateralId = _proposal.collateralId; + proposal.checkCollateralStateFingerprint = _proposal.checkCollateralStateFingerprint; + proposal.collateralStateFingerprint = _proposal.collateralStateFingerprint; + proposal.creditAmount = _proposal.creditAmount; + proposal.availableCreditLimit = _proposal.availableCreditLimit; + proposal.expiration = _proposal.expiration; + proposal.allowedAcceptor = _proposal.allowedAcceptor; + proposal.proposer = _proposal.proposer; + proposal.isOffer = _proposal.isOffer; + proposal.refinancingLoanId = _proposal.refinancingLoanId; + proposal.nonceSpace = _proposal.nonceSpace; + proposal.nonce = _proposal.nonce; + proposal.loanContract = _proposal.loanContract; } function _callAcceptProposalWith(Params memory _params) internal override returns (bytes32, PWNSimpleLoan.Terms memory) { - _updateProposal(_params); + _updateProposal(_params.base); return proposalContract.acceptProposal({ acceptor: _params.acceptor, refinancingLoanId: _params.refinancingLoanId, proposalData: abi.encode(proposal), - signature: _proposalSignature(_params) + proposalInclusionProof: _params.proposalInclusionProof, + signature: _params.signature }); } function _getProposalHashWith(Params memory _params) internal override returns (bytes32) { - _updateProposal(_params); + _updateProposal(_params.base); return _proposalHash(proposal); } @@ -268,7 +259,8 @@ contract PWNSimpleLoanSimpleProposal_AcceptProposal_Test is PWNSimpleLoanSimpleP acceptor: acceptor, refinancingLoanId: 0, proposalData: abi.encode(proposal), - signature: _signProposalHash(proposerPK, _proposalHash(proposal)) + proposalInclusionProof: new bytes32[](0), + signature: _sign(proposerPK, _proposalHash(proposal)) }); assertEq(proposalHash, _proposalHash(proposal));