This EIP standardizes the passing of unstructured call data to functions to enable future extensibility.
Motivation
The purpose of having extra data in a method is to allow further extensions to existing method interfaces.
It is it useful to make methods extendable. Any methods complying with this EIP, such as overloaded transfer and vote could use string reasons as the extra data. Existing EIPs that have exported methods compliant with this EIP can be extended for behaviors such as using the extra data to prove endorsement, as a salt, as a nonce, or as a commitment for a reveal/commit scheme. Finally, data can be passed forward to callbacks.
There are two ways to achieve extensibility for existing functions. Each comes with their set of challenges:
Add a new method
What will the method name be?
What will the parameters be?
How many use-cases does a given method signature support?
Does this support off-chain signatures?
Use one or more existing parameters, or add one or more new ones
Should existing parameters be repurposed, or should more be added?
How many parameters should be used?
What are their sizes and types?
Standardizing how methods can be extended helps to answer these questions.
Finally, this EIP aims to achieve maximum backward and future compatibility. Many EIPs already partially support this EIP, such as EIP-721 and EIP-1155. This EIP supports many use cases, from commit-reveal schemes (EIP-5732), to adding digital signatures alongside with a method call. Other implementers and EIPs should be able to depend on the compatibility granted by this EIP so that all compliant method interfaces are eligible for future new behaviors.
Specification
The key words “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 and RFC 8174.
When used in this EIP, the term bytes MUST be interpreted as the dynamically-sized byte array in Solidity data types.
Unlike many other ERCs which is compliant at the contract level, this ERC’s specification specify compliance at method level.
Any method with a bytes as this method’s last parameter is an eligible method. It looks like this function methodName(type1 value1, type2 value2, ... bytes data).
A compliant method MUST be an eligible method and MUST also designate that last bytes field in its method parameter for behaviors extensions.
If an eligible method has an overloaded sibling method that
has the exact same method name and exact same preceding parameters
except for not having the last bytes parameter, the behavior
of the compliant method MUST be identical to
its overloaded sibling method when last bytes is an empty array.
Examples of compliant and non-compliant methods
Here is a compliant method methodName1 in a Foo contract
contractFoo{// @dev This method allows extension behavior via `_data` field;
functionmethodName1(uint256_param1,address_param2,bytescalldata_data);functionfirstNonRelatedMethod(uint256someValue);functionsecondNonRelatedMethod(uint256someValue);}
Here is a compliant method methodName2 in a Bar contract which is an overloaded method for another methodName2.
contractFoo{// @dev This is a sibling method to `methodName2(uint256 _param1, address _param2, bytes calldata _data);`
functionmethodName2(uint256_param1,address_param2);// @dev This method allows extension behavior via `_data` field;
// When passed in an empty array for `_data` field, this method
// MUST behave IDENTICAL to
// its overloaded sibling `methodName2(uint256 _param1, address _param2);`
functionmethodName2(uint256_param1,address_param2,bytescalldata_data);functionfirstNonRelatedMethod(uint256someValue);functionsecondNonRelatedMethod(uint256someValue);}
Here is a non-compliant method methodName1 because it do not allow extending behavior
contractFoo{// @dev This method DO NOT allow extension behavior via `_data` field;
functionmethodName1(uint256_param1,address_param2,bytescalldata_data);functionfirstNonRelatedMethod(uint256someValue);functionsecondNonRelatedMethod(uint256someValue);}
Here is a non-compliant method
methodName2(uint256 _param1, address _param2, bytes calldata _data);
because it behaves differently
to its overloaded sibling method
methodName2(uint256 _param1, address _param2); when _data is empty array.
contractFoo{// @dev This is a sibling method to `methodName2(uint256 _param1, address _param2, bytes calldata _data);`
functionmethodName2(uint256_param1,address_param2);// @dev This method allows extension behavior via `_data` field;
// When passed in an empty array for `_data` field, this method
// behave DIFFERENTLY to
// its overloaded sibling `methodName2(uint256 _param1, address _param2);`
functionmethodName2(uint256_param1,address_param2,bytescalldata_data);functionfirstNonRelatedMethod(uint256someValue);functionsecondNonRelatedMethod(uint256someValue);}
Rationale
Using the dynamically-sized bytes type allows for maximum flexibility by enabling payloads of arbitrary types.
Having the bytes specified as the last parameter makes this EIP compatible with the calldata layout of solidity.
Backwards Compatibility
Many existing EIPs already have compliant methods as part of their specification. All contracts compliant with those EIPs are either fully or partially compliant with this EIP.
Here is an incomplete list:
In EIP-721, the following method is already compliant:
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; is already compliant
In EIP-1155, the following methods are already compliant
In EIP-777, the following methods are already compliant
function burn(uint256 amount, bytes calldata data) external;
function send(address to, uint256 amount, bytes calldata data) external;
However, not all functions that have a bytes as the last parameter are compliant. The following functions are not compliant without an overload since their last parameter is involved in functionality:
In EIP-2535, the following methods is not compliant:
function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external;
Either of the following can be done to create a compliance.
An overload MUST be created: function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata, bytes calldata _data) external; which adds a new _data after all parameters of original method.
The use of bytes memory _calldata MUST be relaxed to allow for extending behaviors.
In EIP-1271, the following method is not compliant:
function isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4 magicValue);
Either of the following can be done to create a compliance:
An new overload MUST be created: function isValidSignature(bytes32 _hash, bytes memory _signature, bytes calldata _data) public view returns (bytes4 magicValue); which adds a new _data after all parameters of original method.
The use of bytes memory _signature MUST be relaxed to allow for extending behaviors.
Security Considerations
If using the extra data for extended behavior, such as supplying signature for onchain verification, or supplying commitments in a commit-reveal scheme, best practices should be followed for those particular extended behaviors.
Compliant contracts must also take into consideration that the data parameter will be publicly revealed when submitted into the mempool or included in a block, so one must consider the risk of replay and transaction ordering attacks. Unencrypted personally identifiable information must never be included in the data parameter.