Skip to content

Commit 397f612

Browse files
committed
feat: add eip712 usage
1 parent aa2f48f commit 397f612

File tree

6 files changed

+128
-27
lines changed

6 files changed

+128
-27
lines changed

src/PegInContract.sol

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.25;
33

4+
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
45
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
56
import {BtcUtils} from "@rsksmart/btc-transaction-solidity-helper/contracts/BtcUtils.sol";
67
import {OpCodes} from "@rsksmart/btc-transaction-solidity-helper/contracts/OpCodes.sol";
@@ -19,6 +20,7 @@ import {SignatureValidator} from "./libraries/SignatureValidator.sol";
1920
contract PegInContract is
2021
EmergencyPause,
2122
ReentrancyGuardUpgradeable,
23+
EIP712Upgradeable,
2224
IPegIn
2325
{
2426

@@ -32,6 +34,8 @@ contract PegInContract is
3234

3335
/// @notice The version of the contract
3436
string constant public VERSION = "1.0.0";
37+
/// @notice The name of the contract (used for EIP712)
38+
string constant public NAME = "PegInContract";
3539
Flyover.ProviderType constant private _PEG_TYPE = Flyover.ProviderType.PegIn;
3640
uint256 constant private _REFUND_ADDRESS_LENGTH = 21;
3741

@@ -90,6 +94,7 @@ contract PegInContract is
9094
) external initializer {
9195
if (collateralManagement.code.length == 0) revert Flyover.NoContract(collateralManagement);
9296
__ReentrancyGuard_init();
97+
__EIP712_init(NAME, VERSION);
9398
// Initialize EmergencyPause (includes AccessControl, Pausable, and grants PAUSER_ROLE)
9499
__EmergencyPause_init(0, defaultAdmin);
95100
_bridge = IBridge(bridge);
@@ -285,6 +290,11 @@ contract PegInContract is
285290
return _hashPegInQuote(quote);
286291
}
287292

293+
/// @inheritdoc IPegIn
294+
function hashPegInQuoteEIP712(Quotes.PegInQuote calldata quote) external view override returns (bytes32) {
295+
return _hashPegInQuoteEIP712(quote);
296+
}
297+
288298
/// @inheritdoc IPegIn
289299
function getQuoteStatus(bytes32 quoteHash) external view override returns (PegInStates) {
290300
if (_reentrancyGuardEntered()) revert ReentrancyGuardReentrantCall();
@@ -465,8 +475,9 @@ contract PegInContract is
465475
if (_processedQuotes[quoteHash] == PegInStates.PROCESSED_QUOTE) {
466476
revert QuoteAlreadyProcessed(quoteHash);
467477
}
468-
if (!SignatureValidator.verify(quote.liquidityProviderRskAddress, quoteHash, signature)) {
469-
revert SignatureValidator.IncorrectSignature(quote.liquidityProviderRskAddress, quoteHash, signature);
478+
bytes32 eip712hash = _hashPegInQuoteEIP712(quote);
479+
if (!SignatureValidator.verify(quote.liquidityProviderRskAddress, eip712hash, signature)) {
480+
revert SignatureValidator.IncorrectSignature(quote.liquidityProviderRskAddress, eip712hash, signature);
470481
}
471482
// the actual type in the RSKj node source code is a java int which is equivalent to int32
472483
if (height > uint256(int(type(int32).max)) - 1) {
@@ -485,6 +496,26 @@ contract PegInContract is
485496
/// @param quote The peg in quote
486497
/// @return quoteHash The hash of the quote
487498
function _hashPegInQuote(Quotes.PegInQuote calldata quote) private view returns (bytes32) {
499+
_validatePegInQuote(quote);
500+
return keccak256(Quotes.encodeQuote(quote));
501+
}
502+
503+
/// @notice This function is used to hash a peg in quote using EIP712 specification
504+
/// @dev The function also validates the following:
505+
/// - The quote belongs to this contract
506+
/// - The quote destination is not the bridge contract
507+
/// - The quote BTC refund address is valid
508+
/// - The quote liquidity provider BTC address is valid
509+
/// - The quote total amount is greater than the bridge minimum peg in amount
510+
/// - The sum of the timestamp values is not greater than the maximum uint32 value
511+
/// @param quote The peg in quote
512+
/// @return quoteHash The hash struct to be combined with the domain separator
513+
function _hashPegInQuoteEIP712(Quotes.PegInQuote calldata quote) private view returns (bytes32) {
514+
_validatePegInQuote(quote);
515+
return _hashTypedDataV4(Quotes.hashPegInQuoteEIP712(quote));
516+
}
517+
518+
function _validatePegInQuote(Quotes.PegInQuote calldata quote) private view {
488519
if (address(this) != quote.lbcAddress) {
489520
revert Flyover.IncorrectContract(address(this), quote.lbcAddress);
490521
}
@@ -504,7 +535,6 @@ contract PegInContract is
504535
if (type(uint32).max < uint64(quote.agreementTimestamp) + uint64(quote.timeForDeposit)) {
505536
revert Flyover.Overflow(type(uint32).max);
506537
}
507-
return keccak256(Quotes.encodeQuote(quote));
508538
}
509539

510540
/// @notice This function is used to determine if the liquidity provider should be penalized

src/PegOutContract.sol

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.25;
33

4+
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
45
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
56
import {BtcUtils} from "@rsksmart/btc-transaction-solidity-helper/contracts/BtcUtils.sol";
67
import {EmergencyPause} from "./EmergencyPause/EmergencyPause.sol";
@@ -17,6 +18,7 @@ import {SignatureValidator} from "./libraries/SignatureValidator.sol";
1718
contract PegOutContract is
1819
EmergencyPause,
1920
ReentrancyGuardUpgradeable,
21+
EIP712Upgradeable,
2022
IPegOut
2123
{
2224

@@ -31,6 +33,8 @@ contract PegOutContract is
3133

3234
/// @notice The version of the contract
3335
string constant public VERSION = "1.0.0";
36+
/// @notice The name of the contract (used for EIP712)
37+
string constant public NAME = "PegOutContract";
3438
Flyover.ProviderType constant private _PEG_TYPE = Flyover.ProviderType.PegOut;
3539
uint256 constant private _PAY_TO_ADDRESS_OUTPUT = 0;
3640
uint256 constant private _QUOTE_HASH_OUTPUT = 1;
@@ -83,10 +87,11 @@ contract PegOutContract is
8387
revert QuoteExpiredByBlocks(quote.expireBlock);
8488
}
8589

86-
bytes32 quoteHash = _hashPegOutQuote(quote);
87-
if (!SignatureValidator.verify(quote.lpRskAddress, quoteHash, signature)) {
88-
revert SignatureValidator.IncorrectSignature(quote.lpRskAddress, quoteHash, signature);
90+
bytes32 eip712Hash = _hashPegOutQuoteEIP712(quote);
91+
if (!SignatureValidator.verify(quote.lpRskAddress, eip712Hash, signature)) {
92+
revert SignatureValidator.IncorrectSignature(quote.lpRskAddress, eip712Hash, signature);
8993
}
94+
bytes32 quoteHash = _hashPegOutQuote(quote);
9095

9196
Quotes.PegOutQuote storage registeredQuote = _pegOutQuotes[quoteHash];
9297

@@ -132,6 +137,7 @@ contract PegOutContract is
132137
) external initializer {
133138
if (collateralManagement.code.length == 0) revert Flyover.NoContract(collateralManagement);
134139
__ReentrancyGuard_init();
140+
__EIP712_init(NAME, VERSION);
135141
// Initialize EmergencyPause (includes AccessControl, Pausable, and grants PAUSER_ROLE)
136142
__EmergencyPause_init(0, defaultAdmin);
137143
_bridge = IBridge(bridge);
@@ -233,6 +239,13 @@ contract PegOutContract is
233239
return _hashPegOutQuote(quote);
234240
}
235241

242+
/// @inheritdoc IPegOut
243+
function hashPegOutQuoteEIP712(
244+
Quotes.PegOutQuote calldata quote
245+
) external view override returns (bytes32) {
246+
return _hashPegOutQuoteEIP712(quote);
247+
}
248+
236249
/// @inheritdoc IPegOut
237250
function isQuoteCompleted(bytes32 quoteHash) external view override returns (bool) {
238251
return _isQuoteCompleted(quoteHash);
@@ -245,10 +258,25 @@ contract PegOutContract is
245258
function _hashPegOutQuote(
246259
Quotes.PegOutQuote calldata quote
247260
) private view returns (bytes32) {
261+
_validatePegOutQuote(quote);
262+
return keccak256(Quotes.encodePegOutQuote(quote));
263+
}
264+
265+
/// @notice This function is used to hash a peg out quote using EIP712 specification
266+
/// @dev The function also validates the quote belongs to this contract
267+
/// @param quote the peg out quote to hash
268+
/// @return quoteHash the hash of the peg out quote
269+
function _hashPegOutQuoteEIP712(Quotes.PegOutQuote calldata quote) private view returns (bytes32) {
270+
_validatePegOutQuote(quote);
271+
return _hashTypedDataV4(Quotes.hashPegOutQuoteEIP712(quote));
272+
}
273+
274+
/// @notice This function is used to validate a peg out quote before hashing it
275+
/// @param quote The peg out quote to validate
276+
function _validatePegOutQuote(Quotes.PegOutQuote calldata quote) private view {
248277
if (address(this) != quote.lbcAddress) {
249278
revert Flyover.IncorrectContract(address(this), quote.lbcAddress);
250279
}
251-
return keccak256(Quotes.encodePegOutQuote(quote));
252280
}
253281

254282
/// @notice This function is used to check if a quote has been completed (refunded by any party)

src/interfaces/IPegIn.sol

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.25;
33

4+
import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol";
45
import {Quotes} from "../libraries/Quotes.sol";
56
import {IPausable} from "./IPausable.sol";
67

78
/// @title PegIn interface
89
/// @notice This interface is used to expose the required functions to provide the Flyover peg in service
9-
interface IPegIn is IPausable {
10+
interface IPegIn is IPausable, IERC5267 {
1011

1112
/// @notice The states of a peg in quote
1213
/// @dev The quote set to CALL_DONE when the callForUser function is called
@@ -160,6 +161,11 @@ interface IPegIn is IPausable {
160161
/// @return quoteHash The hash of the quote
161162
function hashPegInQuote(Quotes.PegInQuote calldata quote) external view returns (bytes32);
162163

164+
/// @notice This view is used to get the hash of a peg in quote using EIP712 specification
165+
/// @param quote The quote of the peg in
166+
/// @return hashStruct The hash struct to be combined with the domain separator
167+
function hashPegInQuoteEIP712(Quotes.PegInQuote calldata quote) external view returns (bytes32);
168+
163169
/// @notice This function is used to get the minimum peg in amount allowed by the protocol
164170
/// @return minPegIn The minimum peg in amount
165171
function getMinPegIn() external view returns (uint256);

src/interfaces/IPegOut.sol

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.25;
33

4+
import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol";
45
import {Quotes} from "../libraries/Quotes.sol";
56
import {IPausable} from "./IPausable.sol";
67

78
/// @title PegOut interface
89
/// @notice This interface is used to expose the required functions to provide the Flyover peg out service
9-
interface IPegOut is IPausable {
10+
interface IPegOut is IPausable, IERC5267 {
1011

1112
/// @notice Emitted when a peg out is refunded to the liquidity
1213
/// provider after successfully providing the service
@@ -124,6 +125,11 @@ interface IPegOut is IPausable {
124125
/// @param quote the quote to hash
125126
function hashPegOutQuote(Quotes.PegOutQuote calldata quote) external view returns (bytes32);
126127

128+
/// @notice This view is used to get the hash of a peg out quote using EIP712 specification
129+
/// @param quote The quote of the peg out
130+
/// @return hashStruct The hash struct to be combined with the domain separator
131+
function hashPegOutQuoteEIP712(Quotes.PegOutQuote calldata quote) external view returns (bytes32);
132+
127133
/// @notice This view is used to check if a quote has been completed. Completed means it was paid and refunded
128134
/// doesn't matter if the refund was to the liquidity provider (success) or to the user (failure)
129135
/// @param quoteHash the hash of the quote to check

src/libraries/Quotes.sol

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@ library Quotes {
4747

4848
uint256 public constant SAT_TO_WEI_CONVERSION = 10**10;
4949

50+
/// @notice The type hash of the PegInQuote struct for EIP712
51+
/// @dev Due to the number of fields present in the struct, we'll just use the hash of the quote and the
52+
/// address of the liquidity provider offering it to calculate the EIP712 hash. In this way we avoid issues
53+
/// with stack depth limits and future modifications to the type hash based on changes in the struct.
54+
/// @dev keccak256("PegInQuote(address liquidityProvider, bytes32 quoteHash)")
55+
bytes32 public constant PEG_IN_QUOTE_TYPE_HASH = 0x04b6a5fb6a85d659a648dd600fb93f123bec1a7d0ae71b7c33b1d850b3a6da05;
56+
57+
/// @notice The type hash of the PegOutQuote struct for EIP712
58+
/// @dev Due to the number of fields present in the struct, we'll just use the hash of the quote and the
59+
/// address of the liquidity provider offering it to calculate the EIP712 hash. In this way we avoid issues
60+
/// with stack depth limits and future modifications to the type hash based on changes in the struct.
61+
/// @dev keccak256("PegOutQuote(address liquidityProvider, bytes32 quoteHash)")
62+
// TODO validar en jira
63+
bytes32 public constant PEG_OUT_QUOTE_TYPE_HASH =
64+
0x91061380a398473c20ee77839e0df2d1d1cf5dcc79ae90e660f8c6973d17765f;
65+
5066
error AmountTooLow(uint256 value, uint256 target);
5167

5268
function checkAgreedAmount(
@@ -68,6 +84,34 @@ library Quotes {
6884
}
6985
}
7086

87+
/// @notice This function is used to get the hashStruct of a peg in quote using EIP712 specification
88+
/// @dev The hashStruct should be later combined with the domain separator to get the final hash
89+
/// @param quote The peg in quote to hash
90+
/// @return hashStruct The hash struct to be combined with the domain separator
91+
function hashPegInQuoteEIP712(
92+
PegInQuote calldata quote
93+
) external pure returns (bytes32) {
94+
return keccak256(abi.encode(
95+
PEG_IN_QUOTE_TYPE_HASH,
96+
quote.liquidityProviderRskAddress,
97+
keccak256(abi.encode(_encodePart1(quote), _encodePart2(quote)))
98+
));
99+
}
100+
101+
/// @notice This function is used to get the hashStruct of a peg out quote using EIP712 specification
102+
/// @dev The hashStruct should be later combined with the domain separator to get the final hash
103+
/// @param quote The peg out quote to hash
104+
/// @return hashStruct The hash struct to be combined with the domain separator
105+
function hashPegOutQuoteEIP712(
106+
PegOutQuote calldata quote
107+
) external pure returns (bytes32) {
108+
return keccak256(abi.encode(
109+
PEG_OUT_QUOTE_TYPE_HASH,
110+
quote.lpRskAddress,
111+
keccak256(abi.encode(_encodePegOutPart1(quote), _encodePegOutPart2(quote)))
112+
));
113+
}
114+
71115
function encodeQuote(
72116
PegInQuote calldata quote
73117
) external pure returns (bytes memory) {

src/libraries/SignatureValidator.sol

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,20 @@ library SignatureValidator {
1212
/**
1313
@dev Verfies signature against address
1414
@param addr The signing address
15-
@param quoteHash The hash of the signed data
15+
@param eip712Hash The EIP712 hash of the signed data, this contract expects
16+
it to be already prefixed with the EIP712 domain separator
1617
@param signature The signature containing v, r and s
1718
@return True if the signature is valid, false otherwise.
1819
*/
19-
function verify(address addr, bytes32 quoteHash, bytes memory signature) public pure returns (bool) {
20+
function verify(address addr, bytes32 eip712Hash, bytes memory signature) public pure returns (bool) {
2021

2122
if (addr == address(0)) {
2223
revert ZeroAddress();
2324
}
2425

2526
if (signature.length != 65) {
26-
revert IncorrectSignature(addr, quoteHash, signature);
27+
revert IncorrectSignature(addr, eip712Hash, signature);
2728
}
28-
29-
30-
bytes32 r;
31-
bytes32 s;
32-
uint8 v;
33-
34-
assembly {
35-
r := mload(add(signature, 0x20))
36-
s := mload(add(signature, 0x40))
37-
v := byte(0, mload(add(signature, 0x60)))
38-
}
39-
// TODO use EIP712 compatible format instead
40-
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
41-
bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, quoteHash));
42-
return prefixedHash.recover(v, r, s) == addr;
29+
return eip712Hash.recover(signature) == addr;
4330
}
4431
}

0 commit comments

Comments
 (0)