Skip to content

Commit

Permalink
💥 Add ERC-5267 Implementation (#129)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcaversaccio authored Jun 6, 2023
1 parent e0d25f1 commit cb37d13
Show file tree
Hide file tree
Showing 28 changed files with 922 additions and 339 deletions.
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/openzeppelin-contracts
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

0 comments on commit cb37d13

Please sign in to comment.