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

Data Interfaces & EXTSLOAD #7593

Open
dominicletz opened this issue Oct 31, 2019 · 0 comments

Comments

@dominicletz
Copy link

@dominicletz dominicletz commented Oct 31, 2019

Abstract

The Solidity ABI as is (https://solidity.readthedocs.io/en/develop/abi-spec.html) does not allow creating ABI specs for storage data, instead only function calls to contracts are standardized.

When off-chain applications want to consume data from smart contracts, they have to use the existing ABI function calls defined in the corresponding contracts. This means that these off-chain consumers must have a) a full & up-to-date EVM and b) download the contracts storage data locally to execute and fetch the result of the ABI calls -- or that these consumers must fallback to use a centralized gateway that can execute an ABI call for them such as with the "eth_call" RPC API. A work-around to this has been to hard-code in off-chain clients the actual memory layout of the compiled smart contract storage and to access variables directly, validating the storage against the merkle tree. This works with careful engineering but is very brittle since the actual memory slot position of smart contract variables can change easily when there are compiler changes, addition/removal of smart contract variables or variable re-ordering. Also this mechanism can not be expressed in a common interface such such as for ENS defined contracts as storage slot calculations need to be made on a per contract basis

For example we want to enable small connected devices like an ESP32 or a Linkit 7697 with ~300kb to read ENS data. Fetching specific storage data and checking them is possible on such small devices thanks to the RPC eth_getProof trustless but it this does not help currently when interacting with Solidity interfaces.

Example

interface ContractAddress {
    function addr(bytes32 node) constant returns (address);
}
contract Resolver {
    event AddrChanged(bytes32 indexed node, address a);

    address owner;
    mapping(bytes32=>address) addresses;

    modifier only_owner() {
        if(msg.sender != owner) throw;
        _
    }

    function Resolver() {
        owner = msg.sender;
    }

    function addr(bytes32 node) constant returns(address) {
        return addresses[node];
    }

    function setAddr(bytes32 node, address addr) only_owner {
        addresses[node] = addr;
        AddrChanged(node, addr);
    }

    function supportsInterface(bytes4 interfaceID) constant returns (bool) {
        return interfaceID == 0x3b3b57de || interfaceID == 0x01ffc9a7;
    }

    function() {
        throw;
    }
}

The example is the ENS resolver sample from EIP-137. Here the function addr(byte32) is resolving the node to address mapping. Even though it is a simple map lookup in this case, a client can not rely on that based on the interface. It is impossible to use these Resolver interfaces without a full EVM rendering it inaccessible for small devices.

Motivation

We're suggesting to solve this dilemma for EVM free devices by adding interface syntax that can be used to create data interfaces.

A new keyword fixed(@N) is proposed that can be used to define fixed slot position in interfaces.

interface ContractAddressMap {
    public fixed(@5) mapping(bytes32=>address) addr;
}

This new keyword would ensure that all Contracts that use this interface inherit the specified storage member at the position defined. With this in place an EVM free client could now calculate the slot positions for any byte32 entry they want to check and fetch only the required data and proofs with eth_getProof.

Specification #1

The introduction of fixed(@N) can be done today based on existing EVM infrastructure. The solidity compiler would ensure that the base storage slot for the specified data type becomes N. This is specifically important for maps which don't store any actual data at N but used it as base for the offset calculations.

Conflicts

There is a potential for having location conflicts from this when multiple data interfaces are to be implemented. An external registry of data interfaces could help orchestrate this to avoid conflicts.

Specification #2

With possible introduction of the new opcode EXTSLOAD from EIP-2330 the ability to read random external contract storage would also come to smart contracts.

For that case this issue suggest syntax additions for solidity exposing the new EXTSLOAD opcode and allowing to access storage of an external contracts when the fixed(@N) keyword has been used.

Referring to above example here is corresponding contract function that uses this resolver as a data interface:

function resolve(address node, byte32 what) returns(address) {
  Resolver r = Resolver(node);
  address ret = r.addr[what];
  if (ret == address(0)) revert("Not existing!");
  return ret
}

The call r.addr[what] here should let the Solidity compiler generate the normal map access logic but using the EXTSLOAD <address> <slot> instructions to read from the r contract storage instead of the local contract storage.

Backwards Compatibility

This is backwards compatible as it only introduces new syntax.

Forward Compatibility

The introduction of data interfaces would mean that the concrete coding of the exposed data types becomes part of the contract. While for simple types such as integers and byte8..32 a non-issue it might become a burden for advanced types. mappings, arrays, dynamic strings and dynamic bytes if they would ever change their storage encoding logic could break data interfaces. In such case it would become necessary to version the the data layout. This should be elaborated more when the need arises, but new language extensions making the data layout version part of the interface could be considered such as::

public fixed(@5, map_v1) mapping(bytes32=>address) addr;

In this case map_v1 would indicate that the version 1 encoding of mappings is used for this type. Which could allow a new contract on a new compiler to stay compatible with an old data interface.

@Marenz Marenz added the feature label Oct 31, 2019
@dominicletz dominicletz changed the title Data Interfaces & `SLOAD2` Data Interfaces & EXTSLOAD Nov 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.