Skip to content

Commit 80cbc52

Browse files
authored
Merge 74f1943 into d50e424
2 parents d50e424 + 74f1943 commit 80cbc52

28 files changed

+995
-0
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@
1111
path = lib/openzeppelin-contracts-upgradeable
1212
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
1313
branch = v4.8.3
14+
[submodule "lib/solmate"]
15+
path = lib/solmate
16+
url = https://github.com/transmissions11/solmate

lib/solmate

Submodule solmate added at bfc9c25

remappings.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ ds-test/=lib/forge-std/lib/ds-test/src/
33
forge-std/=lib/forge-std/src/
44
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
55
openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
6+
7+
@solmate/=lib/solmate/src/

src/ERC2330.sol

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity ^0.8.0;
3+
4+
import {IERC2330} from "./interfaces/IERC2330.sol";
5+
6+
/// @dev Gas-optimized extsload getters to allow anyone to read storage from this contract.
7+
/// Enables the benefit of https://eips.ethereum.org/EIPS/eip-2330 without requiring changes to the execution layer.
8+
contract ERC2330 is IERC2330 {
9+
/* EXTERNAL */
10+
11+
/// @inheritdoc IERC2330
12+
function extsload(bytes32 slot) external view returns (bytes32 value) {
13+
/// @solidity memory-safe-assembly
14+
assembly {
15+
value := sload(slot)
16+
}
17+
}
18+
19+
/// @inheritdoc IERC2330
20+
function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes memory value) {
21+
value = new bytes(32 * nSlots);
22+
23+
/// @solidity memory-safe-assembly
24+
assembly {
25+
for { let i := 0 } lt(i, nSlots) { i := add(i, 1) } {
26+
mstore(add(value, mul(add(i, 1), 32)), sload(add(startSlot, i)))
27+
}
28+
}
29+
}
30+
}

src/ERC3156xFlashBorrower.sol

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity ^0.8.0;
3+
4+
import {IERC3156xFlashLender} from "./interfaces/IERC3156xFlashLender.sol";
5+
import {IERC3156xFlashBorrower} from "./interfaces/IERC3156xFlashBorrower.sol";
6+
7+
import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol";
8+
9+
import {FLASH_BORROWER_SUCCESS_HASH} from "./ERC3156xFlashLender.sol";
10+
11+
contract ERC3156xFlashBorrower is IERC3156xFlashBorrower {
12+
using SafeTransferLib for ERC20;
13+
14+
IERC3156xFlashLender private immutable _LENDER;
15+
16+
constructor(IERC3156xFlashLender lender) {
17+
_LENDER = lender;
18+
}
19+
20+
/* PUBLIC */
21+
22+
/// @inheritdoc IERC3156xFlashBorrower
23+
function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data)
24+
public
25+
virtual
26+
returns (bytes32 successHash, bytes memory returnData)
27+
{
28+
_checkFlashLoan(initiator);
29+
30+
(successHash, returnData) = _onFlashLoan(initiator, token, amount, fee, data);
31+
32+
ERC20(token).safeApprove(address(_LENDER), amount + fee);
33+
}
34+
35+
/* INTERNAL */
36+
37+
function _checkFlashLoan(address initiator) internal view virtual {
38+
if (msg.sender != address(_LENDER)) revert UnauthorizedLender();
39+
if (initiator != address(this)) revert UnauthorizedInitiator();
40+
}
41+
42+
function _flashLoan(address token, uint256 amount, bytes calldata data) internal virtual returns (bytes memory) {
43+
return _LENDER.flashLoan(this, token, amount, data);
44+
}
45+
46+
function _onFlashLoan(address, address, uint256, uint256, bytes calldata)
47+
public
48+
virtual
49+
returns (bytes32, bytes memory)
50+
{
51+
return (FLASH_BORROWER_SUCCESS_HASH, bytes(""));
52+
}
53+
}

src/ERC3156xFlashLender.sol

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity ^0.8.0;
3+
4+
import {IERC3156xFlashLender} from "./interfaces/IERC3156xFlashLender.sol";
5+
import {IERC3156xFlashBorrower} from "./interfaces/IERC3156xFlashBorrower.sol";
6+
7+
import {SafeTransferLib, ERC20} from "@solmate/utils/SafeTransferLib.sol";
8+
9+
/// @dev The expected success hash returned by the FlashBorrower.
10+
bytes32 constant FLASH_BORROWER_SUCCESS_HASH = keccak256("ERC3156xFlashBorrower.onFlashLoan");
11+
12+
contract ERC3156xFlashLender is IERC3156xFlashLender {
13+
using SafeTransferLib for ERC20;
14+
15+
/* PUBLIC */
16+
17+
/// @inheritdoc IERC3156xFlashLender
18+
function maxFlashLoan(address token) public view virtual returns (uint256) {
19+
return ERC20(token).balanceOf(address(this));
20+
}
21+
22+
/// @inheritdoc IERC3156xFlashLender
23+
function flashFee(address, uint256) public pure virtual returns (uint256) {
24+
return 0;
25+
}
26+
27+
/// @inheritdoc IERC3156xFlashLender
28+
function flashLoan(IERC3156xFlashBorrower receiver, address token, uint256 amount, bytes calldata data)
29+
public
30+
virtual
31+
returns (bytes memory returnData)
32+
{
33+
uint256 max = maxFlashLoan(token);
34+
if (amount > max) revert FlashLoanTooLarge(max);
35+
36+
ERC20(token).safeTransfer(address(receiver), amount);
37+
38+
uint256 fee = flashFee(token, amount);
39+
40+
bytes32 successHash;
41+
(successHash, returnData) = receiver.onFlashLoan(msg.sender, token, amount, fee, data);
42+
if (successHash != FLASH_BORROWER_SUCCESS_HASH) revert InvalidSuccessHash(successHash);
43+
44+
_accrueFee(token, amount, fee);
45+
46+
ERC20(token).safeTransferFrom(address(receiver), address(this), amount + fee);
47+
}
48+
49+
/* INTERNAL */
50+
51+
function _accrueFee(address token, uint256 amount, uint256 fee) internal virtual {}
52+
}

src/ERC712.sol

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity ^0.8.0;
3+
4+
import {IERC712} from "./interfaces/IERC712.sol";
5+
6+
/// @dev The prefix used for EIP-712 signature.
7+
string constant ERC712_MSG_PREFIX = "\x19\x01";
8+
9+
/// @dev The domain typehash used for the EIP-712 signature.
10+
bytes32 constant ERC712_DOMAIN_TYPEHASH =
11+
keccak256("ERC712Domain(string name,uint256 chainId,address verifyingContract)");
12+
13+
/// @dev The highest valid value for s in an ECDSA signature pair (0 < s < secp256k1n ÷ 2 + 1).
14+
uint256 constant MAX_VALID_ECDSA_S = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0;
15+
16+
/// @notice ERC712 helpers.
17+
/// @dev Maintains cross-chain replay protection in the event of a fork.
18+
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ERC712.sol
19+
contract ERC712 is IERC712 {
20+
/// @dev The reference chainid. Used to check whether the chain forked and offer replay protection.
21+
uint256 private immutable _CACHED_CHAIN_ID;
22+
23+
/// @dev The cached domain separator to use if chainid didnt change.
24+
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
25+
26+
/// @dev The name used for EIP-712 signature.
27+
bytes32 private immutable _NAMEHASH;
28+
29+
/// @dev The nonce used inside by signers to offer signature replay protection.
30+
mapping(address => uint256) private _nonces;
31+
32+
constructor(string memory name) {
33+
_NAMEHASH = keccak256(bytes(name));
34+
35+
_CACHED_CHAIN_ID = block.chainid;
36+
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator();
37+
}
38+
39+
/* PUBLIC */
40+
41+
/// @inheritdoc IERC712
42+
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
43+
return block.chainid == _CACHED_CHAIN_ID ? _CACHED_DOMAIN_SEPARATOR : _buildDomainSeparator();
44+
}
45+
46+
/// @inheritdoc IERC712
47+
function nonce(address user) public view virtual returns (uint256) {
48+
return _nonces[user];
49+
}
50+
51+
/* INTERNAL */
52+
53+
/// @dev Verifies a signature components against the provided data hash, nonce, deadline and signer.
54+
/// @param signature The signature to verify.
55+
/// @param dataHash The ERC712 message hash the signature should correspond to.
56+
/// @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.
57+
/// @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.
58+
/// @param signer The expected signature's signer.
59+
function _verify(
60+
Signature calldata signature,
61+
bytes32 dataHash,
62+
uint256 signedNonce,
63+
uint256 deadline,
64+
address signer
65+
) internal virtual {
66+
if (block.timestamp > deadline) revert SignatureExpired();
67+
if (uint256(signature.s) > MAX_VALID_ECDSA_S) revert InvalidValueS();
68+
// v ∈ {27, 28} (source: https://ethereum.github.io/yellowpaper/paper.pdf #308)
69+
if (signature.v != 27 && signature.v != 28) revert InvalidValueV();
70+
71+
bytes32 digest = _hashTypedData(dataHash);
72+
address recovered = ecrecover(digest, signature.v, signature.r, signature.s);
73+
74+
if (recovered == address(0) || signer != recovered) revert InvalidSignature(recovered);
75+
76+
uint256 usedNonce = _useNonce(signer);
77+
if (signedNonce != usedNonce) revert InvalidNonce(usedNonce);
78+
}
79+
80+
/// @dev Increments and returns the nonce that should have been used in the corresponding signature.
81+
function _useNonce(address signer) internal virtual returns (uint256 usedNonce) {
82+
usedNonce = _nonces[signer]++;
83+
84+
emit NonceUsed(msg.sender, signer, usedNonce);
85+
}
86+
87+
/* PRIVATE */
88+
89+
/// @notice Builds a domain separator using the current chainId and contract address.
90+
function _buildDomainSeparator() private view returns (bytes32) {
91+
return keccak256(abi.encode(ERC712_DOMAIN_TYPEHASH, _NAMEHASH, block.chainid, address(this)));
92+
}
93+
94+
/// @notice Creates an EIP-712 typed data hash
95+
function _hashTypedData(bytes32 dataHash) private view returns (bytes32) {
96+
return keccak256(abi.encodePacked(ERC712_MSG_PREFIX, DOMAIN_SEPARATOR(), dataHash));
97+
}
98+
}

src/access/Ownable.sol

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity ^0.8.0;
3+
4+
import {IOwnable} from "../interfaces/access/IOwnable.sol";
5+
6+
/// @notice Gas-optimized Ownable helpers.
7+
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
8+
contract Ownable is IOwnable {
9+
address private _owner;
10+
11+
/// @dev Initializes the contract setting the deployer as the initial owner.
12+
constructor(address initialOwner) {
13+
_transferOwnership(initialOwner);
14+
}
15+
16+
/// @dev Throws if called by any account other than the owner.
17+
modifier onlyOwner() {
18+
_checkOwner();
19+
20+
_;
21+
}
22+
23+
/* PUBLIC */
24+
25+
/// @inheritdoc IOwnable
26+
function owner() public view virtual returns (address) {
27+
return _owner;
28+
}
29+
30+
/// @inheritdoc IOwnable
31+
function transferOwnership(address newOwner) public virtual onlyOwner {
32+
_transferOwnership(newOwner);
33+
}
34+
35+
/* INTERNAL */
36+
37+
/// @dev Throws if the sender is not the owner.
38+
function _checkOwner() internal view virtual {
39+
address currentOwner = owner();
40+
41+
if (currentOwner != msg.sender) revert OwnershipRequired(currentOwner);
42+
}
43+
44+
/// @dev Transfers ownership of the contract to a new account (`newOwner`). Internal function without access restriction.
45+
function _transferOwnership(address newOwner) internal virtual {
46+
address oldOwner = owner();
47+
48+
_owner = newOwner;
49+
50+
emit OwnershipTransferred(oldOwner, newOwner);
51+
}
52+
}

src/access/Ownable2Step.sol

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity ^0.8.0;
3+
4+
import {IOwnable2Step} from "../interfaces/access/IOwnable2Step.sol";
5+
6+
import {Ownable} from "./Ownable.sol";
7+
8+
/// @notice Gas-optimized Ownable2Step helpers.
9+
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol
10+
contract Ownable2Step is IOwnable2Step, Ownable {
11+
address private _pendingOwner;
12+
13+
/// @dev Initializes the contract setting the deployer as the initial owner.
14+
constructor(address initialOwner) Ownable(initialOwner) {}
15+
16+
/* PUBLIC */
17+
18+
/// @inheritdoc IOwnable2Step
19+
function pendingOwner() public view virtual returns (address) {
20+
return _pendingOwner;
21+
}
22+
23+
/// @inheritdoc IOwnable2Step
24+
function acceptOwnership() public virtual {
25+
address sender = msg.sender;
26+
27+
address pending = pendingOwner();
28+
if (pending != sender) revert PendingOwnershipRequired(pending);
29+
30+
_transferOwnership(sender);
31+
}
32+
33+
/// @inheritdoc IOwnable2Step
34+
function transferOwnership(address newOwner) public virtual override(IOwnable2Step, Ownable) onlyOwner {
35+
_pendingOwner = newOwner;
36+
37+
emit OwnershipTransferStarted(owner(), newOwner);
38+
}
39+
40+
/* INTERNAL */
41+
42+
/// @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. Internal function without access restriction.
43+
function _transferOwnership(address newOwner) internal virtual override {
44+
delete _pendingOwner;
45+
46+
super._transferOwnership(newOwner);
47+
}
48+
}

src/interfaces/IERC2330.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity ^0.8.0;
3+
4+
interface IERC2330 {
5+
/* FUNCTIONS */
6+
7+
/// @dev Returns the 32-bytes value stored in this contract, at the given storage slot.
8+
function extsload(bytes32 slot) external view returns (bytes32 value);
9+
10+
/// @dev Returns the `nSlots` 32-bytes values stored in this contract, starting from the given start slot.
11+
function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes memory value);
12+
}

0 commit comments

Comments
 (0)