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 ERC-5267 Implementation #129

Merged
merged 14 commits into from
Jun 6, 2023
486 changes: 247 additions & 239 deletions .gas-snapshot

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# 🕓 Changelog

## `0.0.2` (Unreleased)
## `0.0.2` (07-06-2023)

### 💥 New Features

- **General**
- All 🐍 snekmate contracts now contain an _Ethereum Natural Language Specification Format_ (NatSpec) `custom` field `@custom:contract-name`. The underlying rationale is that the block explorers plan to use `@custom:contract-name` as contract name and `@title` as fallback. ([#124](https://github.com/pcaversaccio/snekmate/pull/124))
- **Extensions**
- [`ERC4626`](https://github.com/pcaversaccio/snekmate/blob/v0.0.2/src/extensions/ERC4626.vy): Implements additionally the interface [`IERC5267`](https://github.com/pcaversaccio/snekmate/blob/v0.0.2/src/utils/interfaces/IERC5267.vy). ([#129](https://github.com/pcaversaccio/snekmate/pull/129))
- **Tokens**
- [`ERC20`](https://github.com/pcaversaccio/snekmate/blob/v0.0.2/src/tokens/ERC20.vy): Implements additionally the interface [`IERC5267`](https://github.com/pcaversaccio/snekmate/blob/v0.0.2/src/utils/interfaces/IERC5267.vy). ([#129](https://github.com/pcaversaccio/snekmate/pull/129))
- [`ERC721`](https://github.com/pcaversaccio/snekmate/blob/v0.0.2/src/tokens/ERC721.vy): Implements additionally the interface [`IERC5267`](https://github.com/pcaversaccio/snekmate/blob/v0.0.2/src/utils/interfaces/IERC5267.vy). ([#129](https://github.com/pcaversaccio/snekmate/pull/129))
- **Utility Functions**
- [`EIP712DomainSeparator`](https://github.com/pcaversaccio/snekmate/blob/v0.0.2/src/utils/EIP712DomainSeparator.vy): Implements additionally the interface [`IERC5267`](https://github.com/pcaversaccio/snekmate/blob/v0.0.2/src/utils/interfaces/IERC5267.vy). ([#129](https://github.com/pcaversaccio/snekmate/pull/129))
- [`Math`](https://github.com/pcaversaccio/snekmate/blob/v0.0.2/src/utils/Math.vy): Add `wad_ln` and `wad_exp` to the standard mathematical utility functions. ([#91](https://github.com/pcaversaccio/snekmate/pull/91))

### ♻️ Refactoring
Expand Down
2 changes: 1 addition & 1 deletion lib/create-util
2 changes: 1 addition & 1 deletion lib/forge-std
Submodule forge-std updated 1 files
+0 −1 src/StdCheats.sol
2 changes: 1 addition & 1 deletion lib/solady
Submodule solady updated 46 files
+42 −42 .gas-snapshot
+39 −0 .github/workflows/ci-woke.yml
+9 −0 .gitignore
+26 −14 README.md
+ audits/ackee-blockchain-solady-report.pdf
+17 −0 ext/woke/EIP712Mock.sol
+149 −0 ext/woke/ERC1155Mock.sol
+121 −0 ext/woke/ERC20Mock.sol
+86 −0 ext/woke/ERC721Mock.sol
+21 −0 ext/woke/MerkleProofMock.sol
+44 −0 ext/woke/NoETHMock.sol
+58 −0 ext/woke/SignatureCheckerMock.sol
+0 −0 ext/woke/__init__.py
+84 −0 ext/woke/test_eip712.py
+65 −0 ext/woke/test_eip712_fuzz.py
+443 −0 ext/woke/test_erc1155.py
+537 −0 ext/woke/test_erc1155_fuzz.py
+242 −0 ext/woke/test_erc20.py
+365 −0 ext/woke/test_erc721_fuzz.py
+55 −0 ext/woke/test_merkle_proof.py
+124 −0 ext/woke/test_merkle_proof_fuzz.py
+178 −0 ext/woke/test_signature_checker_fuzz.py
+89 −0 ext/woke/utils.py
+17 −0 ext/woke/weird/Approval.sol
+17 −0 ext/woke/weird/ApprovalToZero.sol
+29 −0 ext/woke/weird/BlockList.sol
+62 −0 ext/woke/weird/Bytes32Metadata.sol
+89 −0 ext/woke/weird/DaiPermit.sol
+62 −0 ext/woke/weird/ERC20.sol
+12 −0 ext/woke/weird/HighDecimals.sol
+12 −0 ext/woke/weird/LowDecimals.sol
+58 −0 ext/woke/weird/MissingReturns.sol
+56 −0 ext/woke/weird/NoRevert.sol
+36 −0 ext/woke/weird/Pausable.sol
+140 −0 ext/woke/weird/Proxied.sol
+31 −0 ext/woke/weird/Reentrant.sol
+61 −0 ext/woke/weird/ReturnsFalse.sol
+17 −0 ext/woke/weird/RevertToZero.sol
+17 −0 ext/woke/weird/RevertZero.sol
+34 −0 ext/woke/weird/TransferFee.sol
+87 −0 ext/woke/weird/Uint96.sol
+61 −0 ext/woke/weird/Upgradable.sol
+28 −0 ext/woke/woke-via-ir.toml
+27 −0 ext/woke/woke.toml
+1 −1 package.json
+16 −16 src/tokens/ERC1155.sol
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "snekmate",
"version": "0.0.1",
"version": "0.0.2-rc.1",
"description": "State-of-the-art, highly opinionated, hyper-optimised, and secure Vyper smart contract building blocks.",
"author": "Pascal Marco Caversaccio <pascal.caversaccio@hotmail.ch>",
"license": "AGPL-3.0",
Expand Down Expand Up @@ -39,7 +39,7 @@
"@openzeppelin/merkle-tree": "^1.0.4",
"eslint": "^8.42.0",
"eslint-config-prettier": "^8.8.0",
"ethers": "^6.4.1",
"ethers": "^6.4.2",
"keccak256": "^1.0.6",
"merkletreejs": "^0.3.10",
"prettier": "^2.8.8",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "snekmate"
version = "0.0.1"
version = "0.0.2rc1"
description = "State-of-the-art, highly opinionated, hyper-optimised, and secure Vyper smart contract building blocks."
readme = {file = "README.md", content-type = "text/markdown"}
requires-python = ">=3.6"
Expand Down
84 changes: 67 additions & 17 deletions src/extensions/ERC4626.vy
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
been added for convenience:
- `permit` (`external` function),
- `nonces` (`external` `view` function),
- `DOMAIN_SEPARATOR` (`external` `view` function).
- `DOMAIN_SEPARATOR` (`external` `view` function),
- `eip712Domain` (`external` `view` function).
The `permit` function implements approvals via
EIP-712 secp256k1 signatures:
https://eips.ethereum.org/EIPS/eip-2612.
In addition, this contract also implements the EIP-5267
function `eip712Domain`:
https://eips.ethereum.org/EIPS/eip-5267.
The implementation is inspired by OpenZeppelin's
implementation here:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol,
Expand Down Expand Up @@ -74,10 +78,20 @@ from vyper.interfaces import ERC4626
implements: ERC4626


# @dev We import and implement the `IERC5267` interface,
# which is written using standard Vyper syntax.
from ..utils.interfaces.IERC5267 import IERC5267
implements: IERC5267


# @dev Constant used as part of the ECDSA recovery function.
_MALLEABILITY_THRESHOLD: constant(bytes32) = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0


# @dev The 32-byte type hash for the EIP-712 domain separator.
_TYPE_HASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")


# @dev The 32-byte type hash of the `permit` function.
_PERMIT_TYPE_HASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")

Expand Down Expand Up @@ -117,19 +131,25 @@ asset: public(immutable(ERC20))


# @dev Caches the domain separator as an `immutable`
# value, but also stores the corresponding chain id
# value, but also stores the corresponding chain ID
# to invalidate the cached domain separator if the
# chain id changes.
# chain ID changes.
_CACHED_DOMAIN_SEPARATOR: immutable(bytes32)
_CACHED_CHAIN_ID: immutable(uint256)


# @dev Caches `self` to `immutable` storage to avoid
# potential issues if a vanilla contract is used in
# a `delegatecall` context.
_CACHED_SELF: immutable(address)
_CACHED_DOMAIN_SEPARATOR: immutable(bytes32)


# @dev `immutable` variables to store the name,
# version, and type hash during contract creation.
# @dev `immutable` variables to store the (hashed)
# name and (hashed) version during contract creation.
_NAME: immutable(String[50])
_HASHED_NAME: immutable(bytes32)
_VERSION: immutable(String[20])
_HASHED_VERSION: immutable(bytes32)
_TYPE_HASH: immutable(bytes32)


# @dev An offset in the decimal representation between
Expand Down Expand Up @@ -206,6 +226,12 @@ event Withdraw:
shares: uint256


# @dev May be emitted to signal that the domain could
# have changed.
event EIP712DomainChanged:
pass


@external
@payable
def __init__(name_: String[25], symbol_: String[5], asset_: ERC20, decimals_offset_: uint8, name_eip712_: String[50], version_eip712_: String[20]):
Expand Down Expand Up @@ -247,15 +273,13 @@ def __init__(name_: String[25], symbol_: String[5], asset_: ERC20, decimals_offs
# to prevent a theoretically possible overflow.
decimals = _UNDERLYING_DECIMALS + _DECIMALS_OFFSET

hashed_name: bytes32 = keccak256(convert(name_eip712_, Bytes[50]))
hashed_version: bytes32 = keccak256(convert(version_eip712_, Bytes[20]))
type_hash: bytes32 = keccak256(convert("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)", Bytes[82]))
_HASHED_NAME = hashed_name
_HASHED_VERSION = hashed_version
_TYPE_HASH = type_hash
_NAME = name_eip712_
_VERSION = version_eip712_
_HASHED_NAME = keccak256(name_eip712_)
_HASHED_VERSION = keccak256(version_eip712_)
_CACHED_DOMAIN_SEPARATOR = self._build_domain_separator()
_CACHED_CHAIN_ID = chain.id
_CACHED_SELF = self
_CACHED_DOMAIN_SEPARATOR = self._build_domain_separator(type_hash, hashed_name, hashed_version)


@external
Expand Down Expand Up @@ -348,6 +372,32 @@ def DOMAIN_SEPARATOR() -> bytes32:
return self._domain_separator_v4()


@external
@view
def eip712Domain() -> (bytes1, String[50], String[20], uint256, address, bytes32, DynArray[uint256, 128]):
"""
@dev Returns the fields and values that describe the domain
separator used by this contract for EIP-712 signatures.
@notice The bits in the 1-byte bit map are read from the least
significant to the most significant, and fields are indexed
in the order that is specified by EIP-712, identical to the
order in which they are listed in the function type.
@return bytes1 The 1-byte bit map where bit `i` is set to 1
if and only if domain field `i` is present (`0 ≤ i ≤ 4`).
@return String The maximum 50-character user-readable string name
of the signing domain, i.e. the name of the dApp or protocol.
@return String The maximum 20-character current main version of
the signing domain. Signatures from different versions are
not compatible.
@return uint256 The 32-byte EIP-155 chain ID.
@return address The 20-byte address of the verifying contract.
@return bytes32 The 32-byte disambiguation salt for the protocol.
@return DynArray The 32-byte array of EIP-712 extensions.
"""
# Note that `\x0f` equals `01111`.
return (convert(b"\x0f", bytes1), _NAME, _VERSION, chain.id, self, empty(bytes32), empty(DynArray[uint256, 128]))


@external
@view
def totalAssets() -> uint256:
Expand Down Expand Up @@ -704,18 +754,18 @@ def _domain_separator_v4() -> bytes32:
if (self == _CACHED_SELF and chain.id == _CACHED_CHAIN_ID):
return _CACHED_DOMAIN_SEPARATOR
else:
return self._build_domain_separator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION)
return self._build_domain_separator()


@internal
@view
def _build_domain_separator(type_hash: bytes32, name_hash: bytes32, version_hash: bytes32) -> bytes32:
def _build_domain_separator() -> bytes32:
"""
@dev Sourced from {EIP712DomainSeparator-_build_domain_separator}.
@notice See {EIP712DomainSeparator-_build_domain_separator}
for the function docstring.
"""
return keccak256(_abi_encode(type_hash, name_hash, version_hash, chain.id, self))
return keccak256(_abi_encode(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, chain.id, self))


@internal
Expand Down
82 changes: 66 additions & 16 deletions src/tokens/ERC20.vy
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- `permit` (`external` function),
- `nonces` (`external` `view` function),
- `DOMAIN_SEPARATOR` (`external` `view` function),
- `eip712Domain` (`external` `view` function),
- `owner` (`external` `view` function),
- `transfer_ownership` (`external` function),
- `renounce_ownership` (`external` function),
Expand All @@ -30,6 +31,9 @@
The `permit` function implements approvals via
EIP-712 secp256k1 signatures:
https://eips.ethereum.org/EIPS/eip-2612.
In addition, this contract also implements the EIP-5267
function `eip712Domain`:
https://eips.ethereum.org/EIPS/eip-5267.
The implementation is inspired by OpenZeppelin's
implementation here:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol,
Expand Down Expand Up @@ -68,6 +72,12 @@ import interfaces.IERC20Permit as IERC20Permit
implements: IERC20Permit


# @dev We import and implement the `IERC5267` interface,
# which is written using standard Vyper syntax.
from ..utils.interfaces.IERC5267 import IERC5267
implements: IERC5267


# @dev Returns the decimals places of the token.
# The default value is 18.
# @notice If you declare a variable as `public`,
Expand All @@ -85,6 +95,10 @@ decimals: public(constant(uint8)) = 18
_MALLEABILITY_THRESHOLD: constant(bytes32) = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0


# @dev The 32-byte type hash for the EIP-712 domain separator.
_TYPE_HASH: constant(bytes32) = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")


# @dev The 32-byte type hash of the `permit` function.
_PERMIT_TYPE_HASH: constant(bytes32) = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")

Expand All @@ -102,19 +116,25 @@ symbol: public(immutable(String[5]))


# @dev Caches the domain separator as an `immutable`
# value, but also stores the corresponding chain id
# value, but also stores the corresponding chain ID
# to invalidate the cached domain separator if the
# chain id changes.
# chain ID changes.
_CACHED_DOMAIN_SEPARATOR: immutable(bytes32)
_CACHED_CHAIN_ID: immutable(uint256)


# @dev Caches `self` to `immutable` storage to avoid
# potential issues if a vanilla contract is used in
# a `delegatecall` context.
_CACHED_SELF: immutable(address)
_CACHED_DOMAIN_SEPARATOR: immutable(bytes32)


# @dev `immutable` variables to store the name,
# version, and type hash during contract creation.
# @dev `immutable` variables to store the (hashed)
# name and (hashed) version during contract creation.
_NAME: immutable(String[50])
_HASHED_NAME: immutable(bytes32)
_VERSION: immutable(String[20])
_HASHED_VERSION: immutable(bytes32)
_TYPE_HASH: immutable(bytes32)


# @dev Returns the amount of tokens owned by an `address`.
Expand Down Expand Up @@ -166,6 +186,12 @@ event Approval:
amount: uint256


# @dev May be emitted to signal that the domain could
# have changed.
event EIP712DomainChanged:
pass


# @dev Emitted when the ownership is transferred
# from `previous_owner` to `new_owner`.
event OwnershipTransferred:
Expand Down Expand Up @@ -217,15 +243,13 @@ def __init__(name_: String[25], symbol_: String[5], initial_supply_: uint256, na
log Transfer(empty(address), msg.sender, initial_supply)
self._after_token_transfer(empty(address), msg.sender, initial_supply)

hashed_name: bytes32 = keccak256(convert(name_eip712_, Bytes[50]))
hashed_version: bytes32 = keccak256(convert(version_eip712_, Bytes[20]))
type_hash: bytes32 = keccak256(convert("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)", Bytes[82]))
_HASHED_NAME = hashed_name
_HASHED_VERSION = hashed_version
_TYPE_HASH = type_hash
_NAME = name_eip712_
_VERSION = version_eip712_
_HASHED_NAME = keccak256(name_eip712_)
_HASHED_VERSION = keccak256(version_eip712_)
_CACHED_DOMAIN_SEPARATOR = self._build_domain_separator()
_CACHED_CHAIN_ID = chain.id
_CACHED_SELF = self
_CACHED_DOMAIN_SEPARATOR = self._build_domain_separator(type_hash, hashed_name, hashed_version)


@external
Expand Down Expand Up @@ -450,6 +474,32 @@ def DOMAIN_SEPARATOR() -> bytes32:
return self._domain_separator_v4()


@external
@view
def eip712Domain() -> (bytes1, String[50], String[20], uint256, address, bytes32, DynArray[uint256, 128]):
"""
@dev Returns the fields and values that describe the domain
separator used by this contract for EIP-712 signatures.
@notice The bits in the 1-byte bit map are read from the least
significant to the most significant, and fields are indexed
in the order that is specified by EIP-712, identical to the
order in which they are listed in the function type.
@return bytes1 The 1-byte bit map where bit `i` is set to 1
if and only if domain field `i` is present (`0 ≤ i ≤ 4`).
@return String The maximum 50-character user-readable string name
of the signing domain, i.e. the name of the dApp or protocol.
@return String The maximum 20-character current main version of
the signing domain. Signatures from different versions are
not compatible.
@return uint256 The 32-byte EIP-155 chain ID.
@return address The 20-byte address of the verifying contract.
@return bytes32 The 32-byte disambiguation salt for the protocol.
@return DynArray The 32-byte array of EIP-712 extensions.
"""
# Note that `\x0f` equals `01111`.
return (convert(b"\x0f", bytes1), _NAME, _VERSION, chain.id, self, empty(bytes32), empty(DynArray[uint256, 128]))


@external
def transfer_ownership(new_owner: address):
"""
Expand Down Expand Up @@ -692,18 +742,18 @@ def _domain_separator_v4() -> bytes32:
if (self == _CACHED_SELF and chain.id == _CACHED_CHAIN_ID):
return _CACHED_DOMAIN_SEPARATOR
else:
return self._build_domain_separator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION)
return self._build_domain_separator()


@internal
@view
def _build_domain_separator(type_hash: bytes32, name_hash: bytes32, version_hash: bytes32) -> bytes32:
def _build_domain_separator() -> bytes32:
"""
@dev Sourced from {EIP712DomainSeparator-_build_domain_separator}.
@notice See {EIP712DomainSeparator-_build_domain_separator}
for the function docstring.
"""
return keccak256(_abi_encode(type_hash, name_hash, version_hash, chain.id, self))
return keccak256(_abi_encode(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, chain.id, self))


@internal
Expand Down
Loading