From a751897a742f83e0b356fa31b47ce45edec39660 Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 22 Jun 2023 13:14:28 -0300 Subject: [PATCH 1/3] Add EIP: Namespaced Storage Layout Merged by EIP-Bot. --- EIPS/eip-7201.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 EIPS/eip-7201.md diff --git a/EIPS/eip-7201.md b/EIPS/eip-7201.md new file mode 100644 index 0000000000000..b3f113a7517e2 --- /dev/null +++ b/EIPS/eip-7201.md @@ -0,0 +1,89 @@ +--- +eip: 7201 +title: Namespaced Storage Layout +description: A formula for the storage location of structs in the namespaced storage pattern. +author: Francisco Giordano (@frangio), Hadrien Croubois (@Amxx), Ernesto García (@ernestognw), Eric Lau (@ericglau) +discussions-to: https://ethereum-magicians.org/t/eip-7201-namespaced-storage-layout/14796 +status: Draft +type: Standards Track +category: ERC +created: 2023-06-20 +--- + +## Abstract + +We define a formula to derivate a location in storage, that is suitable for structs of arbitrary size, from an identifier. The formula is chosen to be safe against collisions with the standard Solidity storage layout. We define a convention to document this location in Solidity source code. + +## Motivation + +The default Solidity storage layout is linear: all state variables are assigned sequential locations starting from storage slot 0. This is sufficient for most contracts. However, various design patterns used in smart contract development can benefit from a non-linear storage layout. One example is a modular design where using `DELEGATECALL` a contract executes code from multiple contracts, all of which share the same storage space, and which have to carefully coordinate on how to use it. Another example is upgradeable contracts, where it can be difficult to add state variables in an upgrade given that they may affect the assigned storage location for the preexisting variables. + +In a non-linear storage layout, instead of being assigned sequential locations starting from slot 0, state variables are spread out across the storage space, usually at pseudorandom locations obtained by hashing. Each value may be placed in an entirely different location, but more frequently values that are used together are put in a Solidity struct and co-located in storage. + +These storage usage patterns are invisible to the Solidity compiler because they are not represented as Solidity state variables. Smart contract tools like static analyzers or blockchain explorers often need to know the storage location of contract data. Standardizing the location for non-linear storage layouts will allow these tools to correctly interpret contracts where these design patterns are used. + +## Specification + +A _namespace_ consists of a set of variables that are placed contiguous in the storage layout of a contract. It should be implemented as a struct. + +A _namespace id_ is a string that uniquely identifies a namespace in a contract. It should not contain any whitespace characters. + +The storage location for a namespace is defined as `ns_loc(id: string) = keccak256(uint256(keccak256(id)) - 1)`. + +A Solidity contract using namespaced storage can annotate a struct with the NatSpec tag `@custom:storage-location erc7201:` to identify it as a namespace with id ``. + +## Rationale + +A requirement for the location is that it shouldn't overlap with any storage location that would be used by the standard Solidity layout. This is in case namespaced storage is used alongside standard linear storage, either deliberately or accidentally. + +First, note that a namespace may be larger than a single storage slot, so a variable in a namespace will be placed in a slot `ns_loc(id) + k`. If we assume collision resistance of Keccak-256, the chosen `ns_loc` function has the desired property with very high probability, because the cases in which a Solidity variable receives a location of the form `keccak256(x)` are: + +1. Arrays: + 1. If the array is at the top level and is variable number `n` in the layout, the location of the `k`th item in the array will be `keccak256(n) + k`, but `n` will be a number much smaller than `uint256(keccak256(id)) - 1`. + 2. If the array is within another array or mapping, it will be in some location `keccak256(x) + j`, and the `k`th item will be at `keccak256(keccak256(x) + j) + k`. For this to equal `ns_loc(x) + k` we would need `j = -1`, but `j` will always be a positive number in standard Solidity layout. +2. Mappings: The value for key `q` in a mapping will be at location `keccak256(h(q) . x)` where `x` is the location of the mapping itself, and `h` is as defined in the Solidity documentation (section "Layout of State Variables in Storage"). Note that `h(q)` can be any number of bytes. If it is a non-zero number of bytes, it is distinct from any `ns_loc(id) + k`. If it is zero bytes, it can be that `ns_loc(id) = keccak256(x)` if `x = keccak256(id) - 1`, but we know that `x` is the location of the mapping and (as mentioned for arrays above) a variable will be at `keccak256(y) + j` for a positive number `j`. + +### Naming + +This pattern has sometimes been referred to as "diamond storage". This causes it to be conflated with the "diamond proxy pattern", even though they can be used independently of each other. This EIP has chosen to use a different name to clearly differentiate it from the proxy pattern. + +## Backwards Compatibility + +No backward compatibility issues found. + +## Reference Implementation + +```solidity +pragma solidity ^0.8.19; + +contract Example { + /// @custom:storage-location erc7201:example.main + struct MainStorage { + uint256 x; + uint256 y; + } + + bytes32 private immutable MAIN_STORAGE_LOCATION = + keccak256(bytes.concat(uint256(keccak256("example.main")) - 1)); + + function _getMainStorage() private pure returns (MainStorage storage $) { + assembly { + $.slot := MAIN_STORAGE_LOCATION + } + } + + function _getXTimesY() internal view returns (uint256) { + MainStorage storage $ = _getMainStorage(); + return $.x * $.y; + } +} +``` + + +## Security Considerations + +Needs discussion. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). From 8b64ef0bb1f5c3f315f1cc53a24bc00389db0554 Mon Sep 17 00:00:00 2001 From: Eduard Fina <67655321+eduardfina@users.noreply.github.com> Date: Thu, 22 Jun 2023 18:15:29 +0200 Subject: [PATCH 2/3] Add EIP: ERC-20 with transaction validation step. Merged by EIP-Bot. --- EIPS/eip-7144.md | 345 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 EIPS/eip-7144.md diff --git a/EIPS/eip-7144.md b/EIPS/eip-7144.md new file mode 100644 index 0000000000000..f8cefe732dc9f --- /dev/null +++ b/EIPS/eip-7144.md @@ -0,0 +1,345 @@ +--- +eip: 7144 +title: ERC-20 with transaction validation step. +description: A new validation step for transfer and approve calls, achieving a security step in case of stolen wallet. +author: Eduard López i Fina (@eduardfina) +discussions-to: https://ethereum-magicians.org/t/erc721-with-a-validation-step/14071 +status: Draft +type: Standards Track +category: ERC +created: 2023-05-07 +requires: 20 +--- + +## Abstract + +This standard is an extension of [ERC-20](./eip-20.md). It defines new validation functionality to avoid wallet draining: every `transfer` or `approve` will be locked waiting for validation. + +## Motivation + +The power of the blockchain is at the same time its weakness: giving the user full responsibility for their data. + +Many cases of Token theft currently exist, and current Token anti-theft schemes, such as transferring Tokens to cold wallets, make Tokens inconvenient to use. + +Having a validation step before every `transfer` and `approve` would give Smart Contract developers the opportunity to create secure Token anti-theft schemes. + +An implementation example would be a system where a validator address is responsible for validating all Smart Contract transactions. + +This address would be connected to a dApp where the user could see the validation requests of his Tokens and accept the correct ones. + +Giving this address only the power to validate transactions would make a much more secure system where to steal a Token the thief would have to have both the user's address and the validator address simultaneously. + +## Specification + +The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. + +[ERC-20](./eip-20.md) compliant contracts MAY implement this EIP. + +All the operations that change the ownership of Tokens, like a `transfer`/`transferFrom`, SHALL create a `TransferValidation` pending to be validated and emit a `ValidateTransfer`, and SHALL NOT transfer the Tokens. + +All the operations that enable an approval to manage a Token, like an `approve`, SHALL create an `ApprovalValidation` pending to be validated and emit a `ValidateApproval`, and SHALL NOT enable an approval. + +When the transfer is called by an approved account and not the owner, it MUST be executed directly without the need for validation. This is in order to adapt to all current projects that require approve to directly move your Tokens. + +When validating a `TransferValidation` or `ApprovalValidation` the valid field MUST be set to true and MUST NOT be validated again. + +The operations that validate a `TransferValidation` SHALL change the ownership of the Tokens. + +The operations that validate an `ApprovalValidation` SHALL enable the approval. + +### Contract Interface + +```solidity +interface IERC7144 { + + struct TransferValidation { + // The address of the owner. + address from; + // The address of the receiver. + address to; + // The token amount. + uint256 amount; + // Whether is a valid transfer. + bool valid; + } + + struct ApprovalValidation { + // The address of the owner. + address owner; + // The spender address. + address spender; + // The token amount approved. + uint256 amount; + // Whether is a valid approval. + bool valid; + } + + /** + * @dev Emitted when a new transfer validation has been requested. + */ + event ValidateTransfer(address indexed from, address indexed to, uint256 amount, uint256 indexed transferValidationId); + + /** + * @dev Emitted when a new approval validation has been requested. + */ + event ValidateApproval(address indexed owner, address indexed spender, uint256 amount, uint256 indexed approvalValidationId); + + /** + * @dev Returns true if this contract is a validator ERC20. + */ + function isValidatorContract() external view returns (bool); + + /** + * @dev Returns the transfer validation struct using the transfer ID. + * + */ + function transferValidation(uint256 transferId) external view returns (TransferValidation memory); + + /** + * @dev Returns the approval validation struct using the approval ID. + * + */ + function approvalValidation(uint256 approvalId) external view returns (ApprovalValidation memory); + + /** + * @dev Return the total amount of transfer validations created. + * + */ + function totalTransferValidations() external view returns (uint256); + + /** + * @dev Return the total amount of transfer validations created. + * + */ + function totalApprovalValidations() external view returns (uint256); +} + ``` + +The `isValidatorContract()` function MUST be implemented as `public`. + +The `transferValidation(uint256 transferId)` function MAY be implemented as `public` or `external`. + +The `approvalValidation(uint256 approveId)` function MAY be implemented as `public` or `external`. + +The `totalTransferValidations()` function MAY be implemented as `pure` or `view`. + +The `totalApprovalValidations()` function MAY be implemented as `pure` or `view`. + +## Rationale + +### Universality + +The standard only defines the validation functions, but not how they should be used. It defines the validations as internal and lets the user decide how to manage them. + +An example could be to have an address validator connected to a dApp so that users could manage their validations. + +This validator could be used for all Tokens or only for some users. + +It could also be used as a wrapped Smart Contract for existing ERC-20, allowing 1/1 conversion with existing Tokens. + +### Extensibility + +This standard only defines the validation function, but does not define the system with which it has to be validated. A third-party protocol can define how it wants to call these functions as it wishes. + +## Backwards Compatibility + +This standard is an extension of [ERC-20](./eip-20.md), compatible with all the operations except `transfer`/`transferFrom`/`approve`. + +This operations will be overridden to create a validation petition instead of transfer the Tokens or enable an approval. + +## Reference Implementation + +```solidity +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./IERC7144.sol"; + +/** + * @dev Implementation of ERC7144 + */ +contract ERC7144 is IERC7144, ERC20 { + + // Mapping from transfer ID to transfer validation + mapping(uint256 => TransferValidation) private _transferValidations; + + // Mapping from approval ID to approval validation + mapping(uint256 => ApprovalValidation) private _approvalValidations; + + // Total number of transfer validations + uint256 private _totalTransferValidations; + + // Total number of approval validations + uint256 private _totalApprovalValidations; + + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){ + } + + /** + * @dev Returns true if this contract is a validator ERC721. + */ + function isValidatorContract() public pure returns (bool) { + return true; + } + + /** + * @dev Returns the transfer validation struct using the transfer ID. + * + */ + function transferValidation(uint256 transferId) public view override returns (TransferValidation memory) { + require(transferId < _totalTransferValidations, "ERC7144: invalid transfer ID"); + TransferValidation memory v = _transferValidation(transferId); + + return v; + } + + /** + * @dev Returns the approval validation struct using the approval ID. + * + */ + function approvalValidation(uint256 approvalId) public view override returns (ApprovalValidation memory) { + require(approvalId < _totalApprovalValidations, "ERC7144: invalid approval ID"); + ApprovalValidation memory v = _approvalValidation(approvalId); + + return v; + } + + /** + * @dev Return the total amount of transfer validations created. + * + */ + function totalTransferValidations() public view override returns (uint256) { + return _totalTransferValidations; + } + + /** + * @dev Return the total amount of approval validations created. + * + */ + function totalApprovalValidations() public view override returns (uint256) { + return _totalApprovalValidations; + } + + /** + * @dev Returns the transfer validation of the `transferId`. Does NOT revert if transfer doesn't exist + */ + function _transferValidation(uint256 transferId) internal view virtual returns (TransferValidation memory) { + return _transferValidations[transferId]; + } + + /** + * @dev Returns the approval validation of the `approvalId`. Does NOT revert if transfer doesn't exist + */ + function _approvalValidation(uint256 approvalId) internal view virtual returns (ApprovalValidation memory) { + return _approvalValidations[approvalId]; + } + + /** + * @dev Validate the transfer using the transfer ID. + * + */ + function _validateTransfer(uint256 transferId) internal virtual { + TransferValidation memory v = transferValidation(transferId); + require(!v.valid, "ERC721V: the transfer is already validated"); + + super._transfer(v.from, v.to, v.amount); + + _transferValidations[transferId].valid = true; + } + + /** + * @dev Validate the approval using the approval ID. + * + */ + function _validateApproval(uint256 approvalId) internal virtual { + ApprovalValidation memory v = approvalValidation(approvalId); + require(!v.valid, "ERC7144: the approval is already validated"); + + super._approve(v.owner, v.spender, v.amount); + + _approvalValidations[approvalId].valid = true; + } + + /** + * @dev Create a transfer petition of `tokenId` from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * + * Emits a {ValidateTransfer} event. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual override { + require(from != address(0), "ERC7144: transfer from the zero address"); + require(to != address(0), "ERC7144: transfer to the zero address"); + + if(_msgSender() == from) { + TransferValidation memory v; + + v.from = from; + v.to = to; + v.amount = amount; + + _transferValidations[_totalTransferValidations] = v; + + emit ValidateTransfer(from, to, amount, _totalTransferValidations); + + _totalTransferValidations++; + } else { + super._transfer(from, to, amount); + } + } + + /** + * @dev Create an approval petition from `owner` to operate the `amount` + * + * Emits an {ValidateApproval} event. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual override { + require(owner != address(0), "ERC7144: approve from the zero address"); + require(spender != address(0), "ERC7144: approve to the zero address"); + + ApprovalValidation memory v; + + v.owner = owner; + v.spender = spender; + v.amount = amount; + + _approvalValidations[_totalApprovalValidations] = v; + + emit ValidateApproval(v.owner, spender, amount, _totalApprovalValidations); + + _totalApprovalValidations++; + } +} +``` + +## Security Considerations + +As is defined in the Specification the operations that change the ownership of Tokens or enable an approval to manage the Tokens SHALL create a `TransferValidation` or an `ApprovalValidation` pending to be validated and SHALL NOT transfer the Tokens or enable an approval. + +With this premise in mind, the operations in charge of validating a `TransferValidation` or an `ApprovalValidation` must be protected with the maximum security required by the applied system. + +For example, a valid system would be one where there is a validator address in charge of validating the transactions. + +To give another example, a system where each user could choose his validator address would also be correct. + +In any case, the importance of security resides in the fact that no address can validate a `TransferValidation` or an `ApprovalValidation` without the permission of the chosen system. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). From 98d0e738a1fd2bdeeddf41ace756ee16870a3f25 Mon Sep 17 00:00:00 2001 From: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:25:04 -0400 Subject: [PATCH 3/3] CI: Bump eipw to 0.4.7 Merged by EIP-Bot. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 933bf83cba3de..03bd033f3f4d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,7 +112,7 @@ jobs: - name: Checkout EIP Repository uses: actions/checkout@47fbe2df0ad0e27efb67a70beac3555f192b062f - - uses: ethereum/eipw-action@70379db401cbb3197454d8e02589af42d4ac5d21 + - uses: ethereum/eipw-action@2d1d689c81a07f93d9b091df0f3ef6a7c0de1492 id: eipw with: token: ${{ secrets.GITHUB_TOKEN }}