[ERC5018] ファイルをコントラクトに保存する仕組みを理解しよう！

# はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、スマートコントラクト内でファイルやディレクトリの書き込みと読み取りができる仕組みを提案している**ERC5018**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-5018

他にも様々なEIPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

# 概要

**ERC5018**は、コントラクト内でファイルやディレクトリを扱うためのAPIを標準化する提案です。
従来のファイルシステムと同様のインターフェースをEVM上で実現することを目的としています。
この標準によって、任意のサイズのバイナリオブジェクト（画像、HTMLファイル、アセットなど）を読み書きする基本機能が提供されます。
特に、オブジェクトが1回のトランザクションで収まりきらない場合には、チャンク（分割された一部分）単位で読み書きすることも可能です。

従来のようにファイルを一括して処理するのではなく、大きなデータを複数の処理に分割して扱える柔軟な仕組みが提供されています。

# 動機

EVMベースのブロックチェーン上で扱われるバイナリオブジェクト（画像、HTML、スクリプトなど）を、他のDAppsと再利用できるようにしています。
異なるDApps間でコンテンツをシームレスに共有・活用するためには、統一されたインターフェースが必要です。

加えて、**ERC5018**は**ERC4804**と連携する形で設計されています。

:::note info
**ERC4804**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/837931ce60ca844aee13

**ERC4804**では、HTTPのようなURI（例えばhttps://example.eth/.well-known/resource のような形式）を用いてブロックチェーン上のリソースにアクセスできる仕組みが導入されました。
**ERC4804**と組み合わせることで、例えばHTMLやSVGのようなWebコンテンツが相対パスを使って他のリソースを参照している場合でも、ローカル環境とオンチェーン上での構成が一致するようになります。

# 仕様

**ERC5018**は、EVMベースのブロックチェーン上でディレクトリとファイルを管理するための標準的なインターフェースを定義しています。
大きなファイルの分割管理や相対パスを活かしたWeb3リソースの参照に対応しており、オンチェーン上でファイルシステム的な操作を可能にします。

このAPI仕様を使うことで、従来のWeb2におけるファイル管理と同じような構造でEVM上でもデータを柔軟に扱えるようになります。
特に以下のようなユースケースに適しています。

- オンチェーンHTMLやSVG、画像、スクリプトの保存・配信
- IPFSやArweaveに依存しないオンチェーンWebホスティング
- ストレージ分割を活かした大容量データの管理
- 相対パスとHTTPスタイルURIによるWeb3アクセス（**ERC4804**との連携）

## ディレクトリ構造とファイル操作

**ERC5018**では、ファイル名は `bytes` 型で扱われ、バイナリデータとして記録・取得が可能です。
基本的なファイル操作として以下の関数が定義されています。

### `write`

```js
function write(bytes memory name, bytes memory data) external payable
```

書き込み権限のあるアカウントによって、指定されたファイル名にバイナリデータを書き込む関数。
既に存在する場合は上書きされます。

### `read`

```js
function read(bytes memory name) external view returns (bytes memory data, bool exist)
```

指定されたファイル名のバイナリデータと、そのファイルが存在するかどうかの`bool`値を返す関数。

### `fallback`

```js
fallback(bytes calldata prefixedName) external returns (bytes memory data)
```

`/` で始まる名前に対してフォールバック的にファイルを取得する関数。
外部からの直接アクセスなどに対応するための関数です。

### `size`

```js
function size(bytes memory name) external view returns (uint256 size, uint256 chunks)
```

指定ファイルの全体サイズ（バイト数）と、チャンク数を返す関数。

### `remove`

```js
function remove(bytes memory name) external returns (uint256 numOfChunksRemoved)
```

指定ファイルを削除する関数。
削除されたチャンク数を返し、存在しない場合は0が返ります。
実行には書き込み権限が必要です。

## チャンク単位の操作

大きなファイルはチャンク（分割データ）として扱うことができ、個別の読み書き・削除が可能です。

### `countChunks`

```js
function countChunks(bytes memory name) external view returns (uint256 numOfChunks);
```

指定ファイルのチャンク数を取得する関数。

### `writeChunk`

```js
function writeChunk(bytes memory name, uint256 chunkId, bytes memory chunkData) external payable;
```

ファイルの指定チャンクにデータを書き込む関数。
`chunkId` が現在のチャンク数を超えると失敗し、追記または置換のみに対応します。

### `readChunk`

```js
function readChunk(bytes memory name, uint256 chunkId) external view returns (bytes memory chunkData, bool exist);
```

指定チャンクのデータと、そのチャンクが存在するかを返す関数。

### `chunkSize`

```js
function chunkSize(bytes memory name, uint256 chunkId) external view returns (uint256 chunkSize, bool exist);
```

チャンクのサイズと存在しているかを返す関数。

### `removeChunk`

```js
function removeChunk(bytes memory name, uint256 chunkId) external returns (bool exist);
```

チャンク単位で削除を行う関数。
成功すると `true` を、存在しない場合は `false` を返します。
書き込み権限が必要です。

### `truncate`

```js
function truncate(bytes memory name, uint256 chunkId) external returns (uint256 numOfChunksRemoved);
```

指定チャンクID以降の全チャンクを削除する関数。
`chunkId = 0` の場合、ファイル全体を削除する `remove()` と同様の動作になります。

## その他のユーティリティ関数

### `getChunkHash`

```js
function getChunkHash(bytes memory name, uint256 chunkId) external view returns (bytes32);
```

指定チャンクのデータに対して `keccak256` によるハッシュ値を返す関数。
データの整合性検証や変更検知に利用できます。

# 補足

## 単一トランザクションでは収まらない大容量データの課題

ブロックチェーンにWebコンテンツ（HTML、画像、スクリプトなど）をアップロードする時、ファイルサイズが大きすぎて1回のトランザクションで全てを送信できないことがあります。
EVMでは、トランザクションの `calldata` サイズに制限があります。

この問題を解決するため、**ERC5018**ではファイルを複数のチャンク（部分データ）に分けてアップロードできるようになっています。
これにより、ファイル全体を数回のトランザクションに分けて送ることが可能になります。

## チャンクベースの書き込みと読み込みの統合

チャンク単位で書き込まれたデータも、最終的には `read` 関数でファイル全体を一括取得できる設計になっています。
以下の例でその挙動を確認できます。

### 動作例

```js
read("hello.txt") => ""         // 初期状態。ファイルは空。
writeChunk("hello.txt", 0, "abc") // チャンク0に"abc"を書き込み
read("hello.txt") => "abc"

writeChunk("hello.txt", 1, "efg") // チャンク1に"efg"を書き込み
read("hello.txt") => "abcefg"

writeChunk("hello.txt", 0, "aaa") // チャンク0の内容を"aaa"に上書き
read("hello.txt") => "aaaefg"

writeChunk("hello.txt", 3, "hij") // チャンク2が存在しないため失敗（appendでもreplaceでもない）
→ 書き込み失敗
```

このように、`writeChunk` による部分的な追記・上書き操作が可能であり、`read` によって結合された全体の内容を取得できます。
データの整合性と連続性が維持される仕組みです。

## `write`関数の動作と`writeChunk`との関係

`write` 関数は、ファイル全体を一括して書き込む関数です。
内部的には次の2つの処理で構成されていると考えることができます。

- チャンクID 0 に `write` で渡されたデータを `writeChunk` で書き込む  
- チャンクID 1 以降を `truncate` によって削除する

この実装方法により、すでに存在していたファイルの中身が新しいデータで置き換えられます。
これにより、チャンクベースの一部上書きとは異なる「**全体上書き**」が可能になります。

## 大容量Webオブジェクト対応の利点

この仕様により、以下のような大容量オンチェーンデータの管理が現実的になります。

- HTMLページ（**ERC4804**によりWeb3 URIでアクセス可能）
- SVG、PNG、JPGなどの画像
- 複数ファイルで構成されるWebアプリケーションのアセット群

特に `read` 関数によって、チャンク単位でアップロードされたデータを1回のトランザクションで一括取得できるため、**ERC4804**で提案されているHTTPのようなURIでアクセスする時にもユーザー体験が損なわれません。

# 引用

Qi Zhou (@qizhou), "ERC-5018: Filesystem-like Interface for Contracts [DRAFT]," Ethereum Improvement Proposals, no. 5018, April 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5018.

# 最後に

今回は「スマートコントラクト内でファイルやディレクトリの書き込みと読み取りができる仕組みを提案している**ERC5018**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4