Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add permit with standard signature validation to ERC20Bridged #2

Draft
wants to merge 5 commits into
base: l2/lisk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions contracts/BridgingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

pragma solidity 0.8.10;

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {AccessControlEnumerable} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol";

/// @author psirex
/// @notice Contains administrative methods to retrieve and control the state of the bridging
contract BridgingManager is AccessControl {
contract BridgingManager is AccessControlEnumerable {
/// @dev Stores the state of the bridging
/// @param isInitialized Shows whether the contract is initialized or not
/// @param isDepositsEnabled Stores the state of the deposits
Expand Down
35 changes: 35 additions & 0 deletions contracts/lisk/stubs/AccountStub.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2022 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.10;

import "@openzeppelin/contracts/interfaces/IERC1271.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract AccountStub is IERC1271 {
using ECDSA for bytes32;

address public owner;

// bytes4(keccak256("isValidSignature(bytes,bytes)")
bytes4 internal constant MAGICVALUE = 0x1626ba7e;
bytes4 internal constant INVALID_SIGNATURE = 0xffffffff;

constructor(address _owner) {
owner = _owner;
}

function isValidSignature(bytes32 _messageHash, bytes memory _signature)
public
view
override
returns (bytes4 magicValue)
{
address signer = _messageHash.recover(_signature);
if (signer == owner) {
return MAGICVALUE;
} else {
return INVALID_SIGNATURE;
}
}
}
13 changes: 10 additions & 3 deletions contracts/token/ERC20Bridged.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ pragma solidity 0.8.10;

import {IERC20Bridged} from "./interfaces/IERC20Bridged.sol";

import {ERC20Core} from "./ERC20Core.sol";
import {ERC20Permit} from "./ERC20Permit.sol";
import {ERC20Metadata} from "./ERC20Metadata.sol";

/// @author psirex
/// @notice Extends the ERC20 functionality that allows the bridge to mint/burn tokens
contract ERC20Bridged is IERC20Bridged, ERC20Core, ERC20Metadata {
contract ERC20Bridged is IERC20Bridged, ERC20Permit, ERC20Metadata {
/// @inheritdoc IERC20Bridged
address public immutable bridge;

Expand All @@ -23,7 +23,14 @@ contract ERC20Bridged is IERC20Bridged, ERC20Core, ERC20Metadata {
string memory symbol_,
uint8 decimals_,
address bridge_
) ERC20Metadata(name_, symbol_, decimals_) {
)
ERC20Permit(name_)
ERC20Metadata(
name_,
symbol_,
decimals_
)
{
bridge = bridge_;
}

Expand Down
87 changes: 87 additions & 0 deletions contracts/token/ERC20Permit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: 2022 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.10;

import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";

import {ERC20Core} from "./ERC20Core.sol";
import {Nonces} from "./utils/Nonces.sol";

/**
* @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
contract ERC20Permit is Initializable, ERC20Core, IERC20Permit, EIP712, Nonces {
// solhint-disable-next-line var-name-mixedcase
bytes32 private constant _PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

/**
* @dev Permit deadline has expired.
*/
error ERC2612ExpiredSignature(uint256 deadline);

/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC20 token name.
*/
constructor(string memory name) EIP712(name, "1") {}

/**
* @dev See {IERC20Permit-permit}.
*
* Method is compatible with EOA signatures and EIP-1271 (Smart Contract Wallet) signatures
* At the time of developing this contract OpenZeppelin issue related to this (https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2845)
* was still open since they require EIP-2612 specification change to support EIP-1271 signatures before supporting it
*/
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
public
virtual
{
if (block.timestamp > deadline) {
revert ERC2612ExpiredSignature(deadline);
}

bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

bytes32 hash = _hashTypedDataV4(structHash);

require(
SignatureChecker.isValidSignatureNow(owner, hash, abi.encodePacked(r, s, v)),
"ERC20Permit: invalid signature"
);

_approve(owner, spender, value);
}

/**
* @dev See {IERC20Permit-nonces}.
*/
function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}

/**
* @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
return _domainSeparatorV4();
}

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
61 changes: 61 additions & 0 deletions contracts/token/utils/Nonces.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2022 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.10;

import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";

/**
* @dev Provides tracking nonces for addresses. Nonces will only increment.
*/
abstract contract Nonces is Initializable {
function __Nonces_init() internal onlyInitializing {}

function __Nonces_init_unchained() internal onlyInitializing {}

/**
* @dev The nonce used for an `account` is not the expected current nonce.
*/
error InvalidAccountNonce(address account, uint256 currentNonce);

mapping(address => uint256) private _nonces;

/**
* @dev Returns an the next unused nonce for an address.
*/
function nonces(address owner) public view virtual returns (uint256) {
return _nonces[owner];
}

/**
* @dev Consumes a nonce.
*
* Returns the current value and increments nonce.
*/
function _useNonce(address owner) internal virtual returns (uint256) {
// For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
// decremented or reset. This guarantees that the nonce never overflows.
unchecked {
// It is important to do x++ and not ++x here.
return _nonces[owner]++;
}
}

/**
* @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
*/
function _useCheckedNonce(address owner, uint256 nonce) internal virtual returns (uint256) {
uint256 current = _useNonce(owner);
if (nonce != current) {
revert InvalidAccountNonce(owner, current);
}
return current;
}

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
5 changes: 5 additions & 0 deletions funding.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"opRetro": {
"projectId": "0x7747e26c3538f93bc67d9e13f343464996624e96fe549e3be484e848a1f61732"
}
}
Loading