Skip to content

Commit

Permalink
fix(chainlink-elastic-proposal): erc712 proposal array encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
ashhanai committed Dec 16, 2024
1 parent bbe7d90 commit 1f1a1cc
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ contract PWNSimpleLoanElasticChainlinkProposal is PWNSimpleLoanProposal {
* @return Proposal struct hash.
*/
function getProposalHash(Proposal calldata proposal) public view returns (bytes32) {
return _getProposalHash(PROPOSAL_TYPEHASH, abi.encode(proposal));
return _getProposalHash(PROPOSAL_TYPEHASH, _erc712EncodeProposal(proposal));
}

/**
Expand Down Expand Up @@ -270,7 +270,7 @@ contract PWNSimpleLoanElasticChainlinkProposal is PWNSimpleLoanProposal {
(Proposal memory proposal, ProposalValues memory proposalValues) = decodeProposalData(proposalData);

// Make proposal hash
proposalHash = _getProposalHash(PROPOSAL_TYPEHASH, abi.encode(proposal));
proposalHash = _getProposalHash(PROPOSAL_TYPEHASH, _erc712EncodeProposal(proposal));

// Check min credit amount
if (proposal.minCreditAmount == 0) {
Expand Down Expand Up @@ -340,4 +340,45 @@ contract PWNSimpleLoanElasticChainlinkProposal is PWNSimpleLoanProposal {
});
}


/**
* @notice Encode proposal data for EIP-712.
* @param proposal Proposal struct to be encoded.
* @return encodedProposal Encoded proposal data.
*/
function _erc712EncodeProposal(Proposal memory proposal) internal pure returns (bytes memory encodedProposal) {
encodedProposal = abi.encode(
proposal.collateralCategory,
proposal.collateralAddress,
proposal.collateralId,
proposal.checkCollateralStateFingerprint,
proposal.collateralStateFingerprint,
proposal.creditAddress,
keccak256(abi.encodePacked(proposal.feedIntermediaryDenominations)),
keccak256(abi.encodePacked(proposal.feedInvertFlags)),
proposal.loanToValue,
proposal.minCreditAmount,
proposal.availableCreditLimit,
proposal.utilizedCreditId
);

encodedProposal = abi.encodePacked(
encodedProposal,
abi.encode(
proposal.fixedInterestAmount,
proposal.accruingInterestAPR,
proposal.durationOrDate,
proposal.expiration,
proposal.allowedAcceptor,
proposal.proposer,
proposal.proposerSpecHash,
proposal.isOffer,
proposal.refinancingLoanId,
proposal.nonceSpace,
proposal.nonce,
proposal.loanContract
)
);
}

}
34 changes: 34 additions & 0 deletions test/harness/PWNSimpleLoanElasticChainlinkProposalHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.16;

import {
PWNSimpleLoanElasticChainlinkProposal
} from "pwn/loan/terms/simple/proposal/PWNSimpleLoanElasticChainlinkProposal.sol";


contract PWNSimpleLoanElasticChainlinkProposalHarness is PWNSimpleLoanElasticChainlinkProposal {

constructor(
address _hub,
address _revokedNonce,
address _config,
address _utilizedCredit,
address _chainlinkFeedRegistry,
address _l2SequencerUptimeFeed,
address _weth
) PWNSimpleLoanElasticChainlinkProposal(
_hub,
_revokedNonce,
_config,
_utilizedCredit,
_chainlinkFeedRegistry,
_l2SequencerUptimeFeed,
_weth
) {}


function exposed_erc712EncodeProposal(Proposal memory proposal) external pure returns (bytes memory) {
return _erc712EncodeProposal(proposal);
}

}
100 changes: 89 additions & 11 deletions test/unit/PWNSimpleLoanElasticChainlinkProposal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Chainlink
} from "pwn/loan/terms/simple/proposal/PWNSimpleLoanElasticChainlinkProposal.sol";

import { PWNSimpleLoanElasticChainlinkProposalHarness } from "test/harness/PWNSimpleLoanElasticChainlinkProposalHarness.sol";
import { ChainlinkDenominations } from "test/helper/ChainlinkDenominations.sol";
import {
MultiToken,
Expand All @@ -22,7 +23,7 @@ import {

abstract contract PWNSimpleLoanElasticChainlinkProposalTest is PWNSimpleLoanProposalTest {

PWNSimpleLoanElasticChainlinkProposal proposalContract;
PWNSimpleLoanElasticChainlinkProposalHarness proposalContract;
PWNSimpleLoanElasticChainlinkProposal.Proposal proposal;
PWNSimpleLoanElasticChainlinkProposal.ProposalValues proposalValues;

Expand All @@ -38,7 +39,7 @@ abstract contract PWNSimpleLoanElasticChainlinkProposalTest is PWNSimpleLoanProp

vm.etch(token, "bytes");

proposalContract = new PWNSimpleLoanElasticChainlinkProposal(hub, revokedNonce, config, utilizedCredit, feedRegistry, address(0), weth);
proposalContract = new PWNSimpleLoanElasticChainlinkProposalHarness(hub, revokedNonce, config, utilizedCredit, feedRegistry, address(0), weth);
proposalContractAddr = PWNSimpleLoanProposal(proposalContract);

bool[] memory feedInvertFlags = new bool[](1);
Expand Down Expand Up @@ -94,7 +95,7 @@ abstract contract PWNSimpleLoanElasticChainlinkProposalTest is PWNSimpleLoanProp
)),
keccak256(abi.encodePacked(
keccak256("Proposal(uint8 collateralCategory,address collateralAddress,uint256 collateralId,bool checkCollateralStateFingerprint,bytes32 collateralStateFingerprint,address creditAddress,address[] feedIntermediaryDenominations,bool[] feedInvertFlags,uint256 loanToValue,uint256 minCreditAmount,uint256 availableCreditLimit,bytes32 utilizedCreditId,uint256 fixedInterestAmount,uint24 accruingInterestAPR,uint32 durationOrDate,uint40 expiration,address allowedAcceptor,address proposer,bytes32 proposerSpecHash,bool isOffer,uint256 refinancingLoanId,uint256 nonceSpace,uint256 nonce,address loanContract)"),
abi.encode(_proposal)
proposalContract.exposed_erc712EncodeProposal(_proposal)
))
));
}
Expand Down Expand Up @@ -350,7 +351,7 @@ contract PWNSimpleLoanElasticChainlinkProposal_GetCollateralAmount_Test is PWNSi
function test_shouldFetchSequencerUptimeFeed_whenFeedSet() external {
vm.warp(1e9);

proposalContract = new PWNSimpleLoanElasticChainlinkProposal(hub, revokedNonce, config, utilizedCredit, feedRegistry, l2SequencerUptimeFeed, weth);
proposalContract = new PWNSimpleLoanElasticChainlinkProposalHarness(hub, revokedNonce, config, utilizedCredit, feedRegistry, l2SequencerUptimeFeed, weth);
_mockSequencerUptimeFeed(true, block.timestamp - L2_GRACE_PERIOD - 1);
_mockLastRoundData(feed, 1e18, block.timestamp);

Expand All @@ -365,7 +366,7 @@ contract PWNSimpleLoanElasticChainlinkProposal_GetCollateralAmount_Test is PWNSi
function test_shouldFail_whenL2SequencerDown_whenFeedSet() external {
vm.warp(1e9);

proposalContract = new PWNSimpleLoanElasticChainlinkProposal(hub, revokedNonce, config, utilizedCredit, feedRegistry, l2SequencerUptimeFeed, weth);
proposalContract = new PWNSimpleLoanElasticChainlinkProposalHarness(hub, revokedNonce, config, utilizedCredit, feedRegistry, l2SequencerUptimeFeed, weth);
_mockSequencerUptimeFeed(false, block.timestamp - L2_GRACE_PERIOD - 1);

vm.expectRevert(abi.encodeWithSelector(Chainlink.L2SequencerDown.selector));
Expand All @@ -376,7 +377,7 @@ contract PWNSimpleLoanElasticChainlinkProposal_GetCollateralAmount_Test is PWNSi
vm.warp(1e9);
startedAt = bound(startedAt, block.timestamp - L2_GRACE_PERIOD, block.timestamp);

proposalContract = new PWNSimpleLoanElasticChainlinkProposal(hub, revokedNonce, config, utilizedCredit, feedRegistry, l2SequencerUptimeFeed, weth);
proposalContract = new PWNSimpleLoanElasticChainlinkProposalHarness(hub, revokedNonce, config, utilizedCredit, feedRegistry, l2SequencerUptimeFeed, weth);
_mockSequencerUptimeFeed(true, startedAt);

vm.expectRevert(
Expand Down Expand Up @@ -521,14 +522,16 @@ contract PWNSimpleLoanElasticChainlinkProposal_AcceptProposal_Test is PWNSimpleL
function test_shouldFail_whenZeroMinCreditAmount() external {
proposal.minCreditAmount = 0;

bytes32 proposalHash = _proposalHash(proposal);

vm.expectRevert(abi.encodeWithSelector(PWNSimpleLoanElasticChainlinkProposal.MinCreditAmountNotSet.selector));
vm.prank(activeLoanContract);
proposalContract.acceptProposal({
acceptor: acceptor,
refinancingLoanId: 0,
proposalData: abi.encode(proposal, proposalValues),
proposalInclusionProof: new bytes32[](0),
signature: _sign(proposerPK, _proposalHash(proposal))
signature: _sign(proposerPK, proposalHash)
});
}

Expand All @@ -538,6 +541,8 @@ contract PWNSimpleLoanElasticChainlinkProposal_AcceptProposal_Test is PWNSimpleL
proposal.minCreditAmount = bound(minCreditAmount, 1, type(uint256).max);
proposalValues.creditAmount = bound(creditAmount, 0, proposal.minCreditAmount - 1);

bytes32 proposalHash = _proposalHash(proposal);

vm.expectRevert(
abi.encodeWithSelector(
PWNSimpleLoanElasticChainlinkProposal.InsufficientCreditAmount.selector,
Expand All @@ -551,24 +556,26 @@ contract PWNSimpleLoanElasticChainlinkProposal_AcceptProposal_Test is PWNSimpleL
refinancingLoanId: 0,
proposalData: abi.encode(proposal, proposalValues),
proposalInclusionProof: new bytes32[](0),
signature: _sign(proposerPK, _proposalHash(proposal))
signature: _sign(proposerPK, proposalHash)
});
}

function testFuzz_shouldCallLoanContractWithLoanTerms(uint256 creditAmount, bool isOffer) external {
proposalValues.creditAmount = bound(creditAmount, proposal.minCreditAmount, 1e40);
proposal.isOffer = isOffer;

bytes32 proposalHash = _proposalHash(proposal);

vm.prank(activeLoanContract);
(bytes32 proposalHash, PWNSimpleLoan.Terms memory terms) = proposalContract.acceptProposal({
(bytes32 proposalHash_, PWNSimpleLoan.Terms memory terms) = proposalContract.acceptProposal({
acceptor: acceptor,
refinancingLoanId: 0,
proposalData: abi.encode(proposal, proposalValues),
proposalInclusionProof: new bytes32[](0),
signature: _sign(proposerPK, _proposalHash(proposal))
signature: _sign(proposerPK, proposalHash)
});

assertEq(proposalHash, _proposalHash(proposal));
assertEq(proposalHash_, proposalHash);
assertEq(terms.lender, isOffer ? proposal.proposer : acceptor);
assertEq(terms.borrower, isOffer ? acceptor : proposal.proposer);
assertEq(terms.duration, proposal.durationOrDate);
Expand All @@ -587,3 +594,74 @@ contract PWNSimpleLoanElasticChainlinkProposal_AcceptProposal_Test is PWNSimpleL
}

}


/*----------------------------------------------------------*|
|* # ERC712 ENCODE PROPOSAL *|
|*----------------------------------------------------------*/

contract PWNSimpleLoanElasticChainlinkProposal_Erc712EncodeProposal_Test is PWNSimpleLoanElasticChainlinkProposalTest {

struct Proposal_ERC712 {
MultiToken.Category collateralCategory;
address collateralAddress;
uint256 collateralId;
bool checkCollateralStateFingerprint;
bytes32 collateralStateFingerprint;
address creditAddress;
bytes32 feedIntermediaryDenominationsHash; // encode array hash
bytes32 feedInvertFlagsHash; // encode array hash
uint256 loanToValue;
uint256 minCreditAmount;
uint256 availableCreditLimit;
bytes32 utilizedCreditId;
uint256 fixedInterestAmount;
uint24 accruingInterestAPR;
uint32 durationOrDate;
uint40 expiration;
address allowedAcceptor;
address proposer;
bytes32 proposerSpecHash;
bool isOffer;
uint256 refinancingLoanId;
uint256 nonceSpace;
uint256 nonce;
address loanContract;
}


function test_shouldERC712EncodeProposal() external {
Proposal_ERC712 memory proposalErc712 = Proposal_ERC712(
proposal.collateralCategory,
proposal.collateralAddress,
proposal.collateralId,
proposal.checkCollateralStateFingerprint,
proposal.collateralStateFingerprint,
proposal.creditAddress,
keccak256(abi.encodePacked(proposal.feedIntermediaryDenominations)),
keccak256(abi.encodePacked(proposal.feedInvertFlags)),
proposal.loanToValue,
proposal.minCreditAmount,
proposal.availableCreditLimit,
proposal.utilizedCreditId,
proposal.fixedInterestAmount,
proposal.accruingInterestAPR,
proposal.durationOrDate,
proposal.expiration,
proposal.allowedAcceptor,
proposal.proposer,
proposal.proposerSpecHash,
proposal.isOffer,
proposal.refinancingLoanId,
proposal.nonceSpace,
proposal.nonce,
proposal.loanContract
);

assertEq(
keccak256(abi.encode(proposalErc712)),
keccak256(proposalContract.exposed_erc712EncodeProposal(proposal))
);
}

}

0 comments on commit 1f1a1cc

Please sign in to comment.