Skip to content

Commit

Permalink
Merge branch 'ethereum:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
mod authored Nov 29, 2024
2 parents 8f0680e + 8aed1a5 commit d0bb9a0
Show file tree
Hide file tree
Showing 9 changed files with 3,352 additions and 4 deletions.
10 changes: 7 additions & 3 deletions ERCS/erc-7579.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ interface IExecution {
* @param mode The encoded execution mode of the transaction.
* @param executionCalldata The encoded execution call data.
*
* @return returnData An array with the returned data of each executed subcall
*
* MUST ensure adequate authorization control: i.e. onlyExecutorModule
* If a mode is requested that is not supported by the Account, it MUST revert
*/
Expand All @@ -91,9 +93,10 @@ The account MAY also implement the following function in accordance with ERC-433
function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external;
```

If an account chooses to implement `executeUserOp`, this method SHOULD ensure the account executes `userOp.calldata` except 4 most significant bytes, which are reserved for `executeUserOp.selector` as per ERC-4337. Thus the `userOp.callData[4:]` should represent the calldata for a valid call to the account. It is RECOMMENDED that the account executes a `delegatecall` in order to preserve the original `msg.sender` to the account.
If an account chooses to implement `executeUserOp`, this method SHOULD ensure the account executes `userOp.calldata` except 4 most significant bytes, which are reserved for `executeUserOp.selector` as per ERC-4337. Thus the `userOp.callData[4:]` should represent the calldata for a valid call to the account. It is RECOMMENDED that the account executes a `delegatecall` in order to preserve the original `msg.sender` to the account.

Example:

```
(bool success, bytes memory innerCallRet) = address(this).delegatecall(userOp.callData[4:]);
```
Expand Down Expand Up @@ -228,7 +231,7 @@ If the smart account has a fallback handler installed, it:
- MUST use `call` or `staticcall` to invoke the fallback handler
- MUST utilize [ERC-2771](./eip-2771.md) to add the original `msg.sender` to the `calldata` sent to the fallback handler
- MUST route to fallback handlers based on the function selector of the calldata
- MAY implement authorization control, which SHOULD be done via hooks
- MAY implement authorization control, which SHOULD be done via hooks

If the account adds features via fallback, these should be considered the same as if the account was implementing those features natively.
ERC-165 support (see below) is one example of such an approach. Note, that it is only RECOMMENDED to implement view functions via fallback where this can lead to greater extensibility. It is NOT RECOMMENDED to implement core account logic via a fallback.
Expand Down Expand Up @@ -280,6 +283,7 @@ interface IModule {

Note: A single module that is of multiple types MAY decide to pass `moduleTypeId` inside `data` to `onInstall` and/or `onUninstall` methods, so those methods are able to properly handle installation/uninstallation for various types.
Example:

```solidity
// Module.sol
function onInstall(bytes calldata data) external {
Expand Down Expand Up @@ -367,7 +371,7 @@ Our approach has been twofold:

### Extensions

While we want to be minimal, we also want to allow for innovation and opinionated features. Some of these features might also need to be standardized (for similar reasons as the core interfaces) even if not all smart accounts will implement them. To ensure that this is possible, we suggest for future standardization efforts to be done as extensions to this standard. This means that the core interfaces will not change, but that new interfaces can be added as extensions. These should be proposed as separate ERCs, for example with the title `[FEATURE] Extension for ERC-7579`.
While we want to be minimal, we also want to allow for innovation and opinionated features. Some of these features might also need to be standardized (for similar reasons as the core interfaces) even if not all smart accounts will implement them. To ensure that this is possible, we suggest for future standardization efforts to be done as extensions to this standard. This means that the core interfaces will not change, but that new interfaces can be added as extensions. These should be proposed as separate ERCs, for example with the title "[FEATURE] Extension for [ERC-7579](./eip-7579.md)".

### Specification

Expand Down
198 changes: 198 additions & 0 deletions ERCS/erc-7694.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
---
eip: 7694
title: Solana Storage Router
description: Cross-chain storage router protocol incorporating storage router for Solana
author: Avneet Singh (@sshmatrix), 0xc0de4c0ffee (@0xc0de4c0ffee)
discussions-to: https://ethereum-magicians.org/t/erc-7694-solana-storage-router/19706
status: Draft
type: Standards Track
category: ERC
created: 2024-04-18
requires: 3668, 7700
---

## Abstract

The following standard is an extension to the cross-chain storage router protocol introducing the storage router for Solana blockchain. With this specification, any Ethereum L1 contract can defer a call to Solana blockchain as part of its core functionality, provided that the client is equipped to handle Solana transactions. It was previously possible to defer write and storage operations to other Ethereum L1 contracts, L2 contracts and off-chain databases, and this document extends that functionality to include alternative L1 chains. The data stored on Solana must be translated to [EIP-3668](./eip-3668)-compliant format by an appropriate HTTP gateway where it can be retrieved by generic Ethereum contracts. This standard allows Ethereum to utilise a broader range of cross-chain blockspaces.

## Motivation

Cross-Chain Storage Router Protocol (CCIP-Store) introduced in [EIP-7700](./eip-7700), describes three external routers for routing storage to L1 contracts, L2s and databases. This document extends that specification by introducing a fourth storage router targeting Solana as the storage provider.

L2s and databases both have centralising catalysts in their stack. For L2s, this centralising agent is the shared security with Ethereum mainnet. In case of databases, the centralising agent is trivial; it is the physical server hosting the database. In light of this, a storage provider that relies on its own independent consensus mechanism is preferred. This specification instructs how the clients should treat storage calls made to the Solana router.

Solana is a low cost L1 solution that is supported alongside Ethereum by multiple wallet providers. There are several chain-agnostic protocols on Ethereum which could benefit from direct access to Solana blockspace; ENS is one such example where it can serve users of Solana via its chain-agnostic properties while also using Solana's own native storage. This development will encourage more cross-chain functionalities between Ethereum and Solana at core.

![Fig.1 CCIP-Store and CCIP-Read Workflows](../assets/eip-7694/images/Schema.svg)

## Specification

A Solana storage router `StorageRoutedToSolana()` requires the hex-encoded `programId` and the manager `account` on the Solana blockchain. `programId` is equivalent to a contract address on Solana while `account` is the manager wallet on Solana handling storage on behalf of `msg.sender`.

```solidity
// Revert handling Solana storage router
error StorageRoutedToSolana(
bytes32 programId,
bytes32 account
);
// Generic function in a contract
function setValue(
bytes32 node,
bytes32 key,
bytes32 value
) external {
// Get metadata from on-chain sources
(
bytes32 programId, // Program (= contract) address on Solana; hex-encoded
bytes32 account // Manager account on Solana; hex-encoded
) = getMetadata(node); // Arbitrary code
// programId = 0x37868885bbaf236c5d2e7a38952f709e796a1c99d6c9d142a1a41755d7660de3
// account = 0xe853e0dcc1e57656bd760325679ea960d958a0a704274a5a12330208ba0f428f
// Route storage call to Solana router
revert StorageRoutedToSolana(
programId,
account
);
};
```

Since Solana natively uses `base58` encoding in its virtual machine setup, `programId` values that are hex-encoded on EVM must be `base58`-decoded for usage on SVM. Clients implementing the Solana router must call the Solana `programId` using a Solana wallet that is connected to `account` using the `base58`-decoded (and casted to appropriate data type) calldata that it originally received.

```js
/* Pseudo-code to write to Solana program (= contract) */
// Decode all 'bytes32' types in EVM to 'PubKey' type in SVM
const [programId, account, node, key, value] = E2SVM(
[programId, account, node, key, value],
["bytes32", "bytes32", "bytes32", "bytes32", "bytes32"]
);
// Instantiate program interface on Solana
const program = new program(programId, rpcProvider);
// Connect to Solana wallet
const wallet = useWallet();
// Call the Solana program using connected wallet with initial calldata
// [!] Only approved manager in the Solana program should call
if (wallet.publicKey === account) {
await program(wallet).setValue(node, key, value);
}
```

In the above example, EVM-specific `bytes32`-type variables `programId`, `account`, `node`, `key` and `value` must all be converted to SVM-specific `PubKey` data type. The equivalent `setValue()` function in the Solana program is of the form

```rust
// Example function in Solana program
pub fn setValue(
ctx: Context,
node: PubKey,
key: PubKey,
value: PubKey
) -> ProgramResult {
// Code to verify PROGRAM_ID and rent exemption status
...
// Code for de-serialising, updating and re-serialising the data
...
// Store serialised data in account
// [!] Stored data must be mapped by node & account
...
}
```

Since EVM and SVM have differing architectures, it is important to define precise data type castings from EVM to SVM. Some pre-existing custom but popular data types in SVM already equate to common EVM data types such as `PubKey` and `bytes32` respectively. This specification requires the following implementation of bijective EVM to SVM type casting:

| EVM | SVM |
| :-------: | :---------------: |
| `uint8` | `u8` |
| `uint16` | `u16` |
| `uint32` | `u32` |
| `uint64` | `u64` |
| `uint128` | `u128` |
| `uint256` | `u256`|
| `bytes1` | `bytes: [u8; 1]` |
| `bytes2` | `bytes: [u8; 2]` |
| `bytes4` | `bytes: [u8; 4]` |
| `bytes8` | `bytes: [u8; 8]` |
| `bytes16` | `bytes: [u8; 16]` |
| `bytes32` | `PubKey` |
| `bytes` | `bytes: Vec<u8>` |
| `string` | `String` |
| `address` | `bytes: [u8; 20]` |

> `u256` is not available natively in SVM but is routinely implemented via `u256` crate in Rust
Using this strategy, most - if not all - current use-cases of `StorageRoutedToSolana()` are accounted for.

Finally, in order to read the cross-chain data stored on Solana in an arbitrary Ethereum contract, it must be translated back into EVM tongue by an [EIP-3668](./eip-3668)-compliant HTTP gateway. The arguments for a generic call to the gateway URL must be specified in the `/`-delimited nested format as described in [EIP-7700](./eip-7700). The core of such a gateway must follow

```js
/* Pseudo-code of an ERC-3668-compliant HTTP gateway tunneling Solana content to Ethereum */
// CCIP-Read call by contract to a known gateway URL; gatewayUrl = 'https://read.solana.namesys.xyz/<programId>/<node>/<key>/'
const [programId, node, key] = parseQuery(path); // Parse query parameters from path; path = '/<programId>/<node>/<key>/'
// Decode 'bytes32' types in EVM to 'PubKey' type in SVM
const [programId, node, key] = E2SVM(
[programId, node, key],
["bytes32", "bytes32", "bytes32"]
);
// Instantiate program interface on Solana
const program = new program(programId, rpcProvider);
// Call the Solana program to read in cross-chain data
const value = await program.getValue(node, key);
if (value !== "NOT_FOUND") {
// Decode 'PubKey' type in SVM to 'bytes32' type in EVM
const value = S2EVM(value, "PubKey");
} else {
// Null value
const value = "0x0";
}
// Compile CCIP-Read-compatible payload
const data = abi.encode(["bytes"], [value]);
// Create HTTP gateway emitting value in format 'data: ...'
emitERC3668(data);
```

In the above example, the generic `getValue()` function in the Solana program is of the form

```rust
// Example getValue() function in Solana program
pub fn getValue<'a>(
ctx: Context,
node: Pubkey,
key: Pubkey,
account: &AccountInfo<'a>, // Lifetime-bound parameter
) -> Result<Pubkey, ProgramError> {
// Validate that the account belongs to the correct program ID
...
// Retrieve the data from the account
let data = &account.data.borrow();
// De-serialise the data from the account
...
// Look up the value by node and key
match deserialised.get(&node, &key) {
Some(value) => {
msg!("VALUE: {:?}", value);
Ok(value)
},
None => {
msg!("NOT_FOUND");
Err(ProgramError::InvalidArgument)
}
}
}
```

## Rationale

`StorageRoutedToSolana()` works in a similar fashion to `StorageRoutedToL2()` in CCIP-Store in the sense that the client needs to be pointed to a certain contract on another chain by the revert event. Other than that, the only technical difference is casting between EVM and SVM data types.

![Fig.2 Solana Call Lifecycle](../assets/eip-7694/images/Solana.svg)

## Backwards Compatibility

None.

## Security Considerations

None.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
2 changes: 1 addition & 1 deletion ERCS/erc-7722.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
eip: 7722
title: Opaque Token
description: A token specification designed to enhance privacy by concealing balance information.
author: Ivica Aračić (@ivica7), SWIAT
author: Ivica Aračić (@ivica7), Ante Bešlić (@ethSplit), Mirko Katanić (@mkatanic), SWIAT
discussions-to: https://ethereum-magicians.org/t/erc-7722-opaque-token/20249
status: Draft
type: Standards Track
Expand Down
Loading

0 comments on commit d0bb9a0

Please sign in to comment.