-
Notifications
You must be signed in to change notification settings - Fork 1
Add ERC712
, ERC2330
, ERC3156x
, Ownable
& Ownable2Step
#114
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
Draft
Rubilmax
wants to merge
13
commits into
main
Choose a base branch
from
feat/eip712-ownable
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+995
−0
Draft
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
261216a
feat(eip712): add eip712 base contract
Rubilmax ed7192d
feat(ownable): add ownable helpers
Rubilmax 400caba
test(ownable): added Ownable tests
Rubilmax 5c96ca4
test(eip712): added eip712 tests
Rubilmax f5a807f
refactor(erc712): rename eip to erc
Rubilmax 558489e
feat(erc3156x): added flash lender/borrower erc
Rubilmax a90238c
forge install: solmate
Rubilmax c221ee3
fix(erc3156): make it compile
Rubilmax 60035db
test(erc712): fix erc712 tests
Rubilmax 5a7ae7d
test(erc3156x): test lender/borrower
Rubilmax a826a08
feat(erc2330): add erc2330 alternative
Rubilmax 97080d1
refacgtor(remappings): update solmate remapping
Rubilmax 74f1943
fix(imports): use . instead of src
Rubilmax File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.0; | ||
|
||
import {IERC2330} from "./interfaces/IERC2330.sol"; | ||
|
||
/// @dev Gas-optimized extsload getters to allow anyone to read storage from this contract. | ||
/// Enables the benefit of https://eips.ethereum.org/EIPS/eip-2330 without requiring changes to the execution layer. | ||
contract ERC2330 is IERC2330 { | ||
/* EXTERNAL */ | ||
|
||
/// @inheritdoc IERC2330 | ||
function extsload(bytes32 slot) external view returns (bytes32 value) { | ||
/// @solidity memory-safe-assembly | ||
assembly { | ||
value := sload(slot) | ||
} | ||
} | ||
|
||
/// @inheritdoc IERC2330 | ||
function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes memory value) { | ||
value = new bytes(32 * nSlots); | ||
|
||
/// @solidity memory-safe-assembly | ||
assembly { | ||
for { let i := 0 } lt(i, nSlots) { i := add(i, 1) } { | ||
mstore(add(value, mul(add(i, 1), 32)), sload(add(startSlot, i))) | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.0; | ||
|
||
import {IERC3156xFlashLender} from "./interfaces/IERC3156xFlashLender.sol"; | ||
import {IERC3156xFlashBorrower} from "./interfaces/IERC3156xFlashBorrower.sol"; | ||
|
||
import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol"; | ||
|
||
import {FLASH_BORROWER_SUCCESS_HASH} from "./ERC3156xFlashLender.sol"; | ||
|
||
contract ERC3156xFlashBorrower is IERC3156xFlashBorrower { | ||
using SafeTransferLib for ERC20; | ||
|
||
IERC3156xFlashLender private immutable _LENDER; | ||
|
||
constructor(IERC3156xFlashLender lender) { | ||
_LENDER = lender; | ||
} | ||
|
||
/* PUBLIC */ | ||
|
||
/// @inheritdoc IERC3156xFlashBorrower | ||
function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) | ||
public | ||
virtual | ||
returns (bytes32 successHash, bytes memory returnData) | ||
{ | ||
_checkFlashLoan(initiator); | ||
|
||
(successHash, returnData) = _onFlashLoan(initiator, token, amount, fee, data); | ||
|
||
ERC20(token).safeApprove(address(_LENDER), amount + fee); | ||
} | ||
|
||
/* INTERNAL */ | ||
|
||
function _checkFlashLoan(address initiator) internal view virtual { | ||
if (msg.sender != address(_LENDER)) revert UnauthorizedLender(); | ||
if (initiator != address(this)) revert UnauthorizedInitiator(); | ||
} | ||
|
||
function _flashLoan(address token, uint256 amount, bytes calldata data) internal virtual returns (bytes memory) { | ||
return _LENDER.flashLoan(this, token, amount, data); | ||
} | ||
|
||
function _onFlashLoan(address, address, uint256, uint256, bytes calldata) | ||
public | ||
virtual | ||
returns (bytes32, bytes memory) | ||
{ | ||
return (FLASH_BORROWER_SUCCESS_HASH, bytes("")); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.0; | ||
|
||
import {IERC3156xFlashLender} from "./interfaces/IERC3156xFlashLender.sol"; | ||
import {IERC3156xFlashBorrower} from "./interfaces/IERC3156xFlashBorrower.sol"; | ||
|
||
import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol"; | ||
|
||
/// @dev The expected success hash returned by the FlashBorrower. | ||
bytes32 constant FLASH_BORROWER_SUCCESS_HASH = keccak256("ERC3156xFlashBorrower.onFlashLoan"); | ||
|
||
contract ERC3156xFlashLender is IERC3156xFlashLender { | ||
using SafeTransferLib for ERC20; | ||
|
||
/* PUBLIC */ | ||
|
||
/// @inheritdoc IERC3156xFlashLender | ||
function maxFlashLoan(address token) public view virtual returns (uint256) { | ||
return ERC20(token).balanceOf(address(this)); | ||
} | ||
|
||
/// @inheritdoc IERC3156xFlashLender | ||
function flashFee(address, uint256) public pure virtual returns (uint256) { | ||
return 0; | ||
} | ||
|
||
/// @inheritdoc IERC3156xFlashLender | ||
function flashLoan(IERC3156xFlashBorrower receiver, address token, uint256 amount, bytes calldata data) | ||
public | ||
virtual | ||
returns (bytes memory returnData) | ||
{ | ||
uint256 max = maxFlashLoan(token); | ||
if (amount > max) revert FlashLoanTooLarge(max); | ||
|
||
ERC20(token).safeTransfer(address(receiver), amount); | ||
|
||
uint256 fee = flashFee(token, amount); | ||
|
||
bytes32 successHash; | ||
(successHash, returnData) = receiver.onFlashLoan(msg.sender, token, amount, fee, data); | ||
if (successHash != FLASH_BORROWER_SUCCESS_HASH) revert InvalidSuccessHash(successHash); | ||
|
||
_accrueFee(token, amount, fee); | ||
|
||
ERC20(token).safeTransferFrom(address(receiver), address(this), amount + fee); | ||
} | ||
|
||
/* INTERNAL */ | ||
|
||
function _accrueFee(address token, uint256 amount, uint256 fee) internal virtual {} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.0; | ||
|
||
import {IERC712} from "./interfaces/IERC712.sol"; | ||
|
||
/// @dev The prefix used for EIP-712 signature. | ||
string constant ERC712_MSG_PREFIX = "\x19\x01"; | ||
|
||
/// @dev The domain typehash used for the EIP-712 signature. | ||
bytes32 constant ERC712_DOMAIN_TYPEHASH = | ||
keccak256("ERC712Domain(string name,uint256 chainId,address verifyingContract)"); | ||
|
||
/// @dev The highest valid value for s in an ECDSA signature pair (0 < s < secp256k1n ÷ 2 + 1). | ||
uint256 constant MAX_VALID_ECDSA_S = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0; | ||
|
||
/// @notice ERC712 helpers. | ||
/// @dev Maintains cross-chain replay protection in the event of a fork. | ||
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ERC712.sol | ||
contract ERC712 is IERC712 { | ||
/// @dev The reference chainid. Used to check whether the chain forked and offer replay protection. | ||
uint256 private immutable _CACHED_CHAIN_ID; | ||
|
||
/// @dev The cached domain separator to use if chainid didnt change. | ||
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; | ||
|
||
/// @dev The name used for EIP-712 signature. | ||
bytes32 private immutable _NAMEHASH; | ||
|
||
/// @dev The nonce used inside by signers to offer signature replay protection. | ||
mapping(address => uint256) private _nonces; | ||
|
||
constructor(string memory name) { | ||
_NAMEHASH = keccak256(bytes(name)); | ||
|
||
_CACHED_CHAIN_ID = block.chainid; | ||
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(); | ||
} | ||
|
||
/* PUBLIC */ | ||
|
||
/// @inheritdoc IERC712 | ||
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { | ||
return block.chainid == _CACHED_CHAIN_ID ? _CACHED_DOMAIN_SEPARATOR : _buildDomainSeparator(); | ||
} | ||
|
||
/// @inheritdoc IERC712 | ||
function nonce(address user) public view virtual returns (uint256) { | ||
return _nonces[user]; | ||
} | ||
|
||
/* INTERNAL */ | ||
|
||
/// @dev Verifies a signature components against the provided data hash, nonce, deadline and signer. | ||
/// @param signature The signature to verify. | ||
/// @param dataHash The ERC712 message hash the signature should correspond to. | ||
/// @param signedNonce The nonce used along with the provided signature. Must not be an end-user input and must be proven to be signed by the signer. | ||
/// @param deadline The signature's maximum valid timestamp. Must not be an end-user input and must be proven to be signed by the signer. | ||
/// @param signer The expected signature's signer. | ||
function _verify( | ||
Signature calldata signature, | ||
bytes32 dataHash, | ||
uint256 signedNonce, | ||
uint256 deadline, | ||
address signer | ||
) internal virtual { | ||
if (block.timestamp > deadline) revert SignatureExpired(); | ||
if (uint256(signature.s) > MAX_VALID_ECDSA_S) revert InvalidValueS(); | ||
// v ∈ {27, 28} (source: https://ethereum.github.io/yellowpaper/paper.pdf #308) | ||
if (signature.v != 27 && signature.v != 28) revert InvalidValueV(); | ||
|
||
bytes32 digest = _hashTypedData(dataHash); | ||
address recovered = ecrecover(digest, signature.v, signature.r, signature.s); | ||
|
||
if (recovered == address(0) || signer != recovered) revert InvalidSignature(recovered); | ||
|
||
uint256 usedNonce = _useNonce(signer); | ||
if (signedNonce != usedNonce) revert InvalidNonce(usedNonce); | ||
} | ||
|
||
/// @dev Increments and returns the nonce that should have been used in the corresponding signature. | ||
function _useNonce(address signer) internal virtual returns (uint256 usedNonce) { | ||
usedNonce = _nonces[signer]++; | ||
|
||
emit NonceUsed(msg.sender, signer, usedNonce); | ||
} | ||
|
||
/* PRIVATE */ | ||
|
||
/// @notice Builds a domain separator using the current chainId and contract address. | ||
function _buildDomainSeparator() private view returns (bytes32) { | ||
return keccak256(abi.encode(ERC712_DOMAIN_TYPEHASH, _NAMEHASH, block.chainid, address(this))); | ||
} | ||
|
||
/// @notice Creates an EIP-712 typed data hash | ||
function _hashTypedData(bytes32 dataHash) private view returns (bytes32) { | ||
return keccak256(abi.encodePacked(ERC712_MSG_PREFIX, DOMAIN_SEPARATOR(), dataHash)); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.0; | ||
|
||
import {IOwnable} from "../interfaces/access/IOwnable.sol"; | ||
|
||
/// @notice Gas-optimized Ownable helpers. | ||
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol | ||
contract Ownable is IOwnable { | ||
address private _owner; | ||
|
||
/// @dev Initializes the contract setting the deployer as the initial owner. | ||
constructor(address initialOwner) { | ||
_transferOwnership(initialOwner); | ||
} | ||
|
||
/// @dev Throws if called by any account other than the owner. | ||
modifier onlyOwner() { | ||
_checkOwner(); | ||
|
||
_; | ||
} | ||
|
||
/* PUBLIC */ | ||
|
||
/// @inheritdoc IOwnable | ||
function owner() public view virtual returns (address) { | ||
return _owner; | ||
} | ||
|
||
/// @inheritdoc IOwnable | ||
function transferOwnership(address newOwner) public virtual onlyOwner { | ||
_transferOwnership(newOwner); | ||
} | ||
|
||
/* INTERNAL */ | ||
|
||
/// @dev Throws if the sender is not the owner. | ||
function _checkOwner() internal view virtual { | ||
address currentOwner = owner(); | ||
|
||
if (currentOwner != msg.sender) revert OwnershipRequired(currentOwner); | ||
} | ||
|
||
/// @dev Transfers ownership of the contract to a new account (`newOwner`). Internal function without access restriction. | ||
function _transferOwnership(address newOwner) internal virtual { | ||
address oldOwner = owner(); | ||
|
||
_owner = newOwner; | ||
|
||
emit OwnershipTransferred(oldOwner, newOwner); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.0; | ||
|
||
import {IOwnable2Step} from "../interfaces/access/IOwnable2Step.sol"; | ||
|
||
import {Ownable} from "./Ownable.sol"; | ||
|
||
/// @notice Gas-optimized Ownable2Step helpers. | ||
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol | ||
contract Ownable2Step is IOwnable2Step, Ownable { | ||
address private _pendingOwner; | ||
|
||
/// @dev Initializes the contract setting the deployer as the initial owner. | ||
constructor(address initialOwner) Ownable(initialOwner) {} | ||
|
||
/* PUBLIC */ | ||
|
||
/// @inheritdoc IOwnable2Step | ||
function pendingOwner() public view virtual returns (address) { | ||
return _pendingOwner; | ||
} | ||
|
||
/// @inheritdoc IOwnable2Step | ||
function acceptOwnership() public virtual { | ||
address sender = msg.sender; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to cache the sender because msg.sender is actually cheaper than using memory. |
||
|
||
address pending = pendingOwner(); | ||
if (pending != sender) revert PendingOwnershipRequired(pending); | ||
|
||
_transferOwnership(sender); | ||
} | ||
|
||
/// @inheritdoc IOwnable2Step | ||
function transferOwnership(address newOwner) public virtual override(IOwnable2Step, Ownable) onlyOwner { | ||
_pendingOwner = newOwner; | ||
|
||
emit OwnershipTransferStarted(owner(), newOwner); | ||
} | ||
|
||
/* INTERNAL */ | ||
|
||
/// @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. Internal function without access restriction. | ||
function _transferOwnership(address newOwner) internal virtual override { | ||
delete _pendingOwner; | ||
|
||
super._transferOwnership(newOwner); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.0; | ||
|
||
interface IERC2330 { | ||
/* FUNCTIONS */ | ||
|
||
/// @dev Returns the 32-bytes value stored in this contract, at the given storage slot. | ||
function extsload(bytes32 slot) external view returns (bytes32 value); | ||
|
||
/// @dev Returns the `nSlots` 32-bytes values stored in this contract, starting from the given start slot. | ||
function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes memory value); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity ^0.8.0; | ||
|
||
/// @dev Interface of the ERC3156x FlashBorrower, inspired by https://eips.ethereum.org/EIPS/eip-3156. | ||
/// The FlashLender's `flashLoan` function now returns the FlashBorrower's return data. | ||
interface IERC3156xFlashBorrower { | ||
/* ERRORS */ | ||
|
||
/// @dev Thrown when the caller of the FlashBorrower's callback is not authorized. | ||
error UnauthorizedLender(); | ||
|
||
/// @dev Thrown when the intiiator of the flash loan is not authorized. | ||
error UnauthorizedInitiator(); | ||
|
||
/* FUNCTIONS */ | ||
|
||
/// @dev Receive a flash loan. | ||
/// @param initiator The initiator of the loan. | ||
/// @param token The loan currency. | ||
/// @param amount The amount of tokens lent. | ||
/// @param fee The additional amount of tokens to repay. | ||
/// @param data Arbitrary data structure, intended to contain user-defined parameters. | ||
/// @return The keccak256 hash of "IERC3156xFlashBorrower.onFlashLoan" and any additional arbitrary data. | ||
function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) | ||
external | ||
returns (bytes32, bytes memory); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be appropriate to set these contracts as abstract?