はじめに

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

今回は、様々なコンテンツの投稿内容とタグを受け取り、イベントとして発行する仕組みを提案しているERC3722についてまとめていきます!

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

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

概要

Posterは、非常にシンプルな分散型ソーシャルメディア向けスマートコントラクトです。 2つの文字列(content: 投稿内容、tag: タグ)を受け取り、投稿者のアドレス(msg.sender)とともにイベントとして発行します。 さらに、1回のpost()呼び出しで複数の投稿や操作をまとめて送信できる、Twitter風の標準的なJSON形式を提案しています。 アプリケーションの状態は、イベントを収集するインデクサ(ブロックチェーン上のイベントを集めて整理するプログラム)を使ってオフチェーン(ブロックチェーン外)で構築する想定です。

動機

Posterは分散型ソーシャルメディアの基盤として利用することを目的としています。 シングルトンファクトリ(ひとつのコントラクトを任意のEVM互換ネットワークに同じアドレスで配備できる仕組み)を使うことで、ほとんどのEVM互換ネットワーク上に同一アドレスでデプロイできます。 これにより、どのEthereumアカウントからでも各ネットワーク上のPosterコントラクトに投稿でき、分散型SNSの汎用的な土台を提供します。

仕様

Posterコントラクトは、非常にシンプルに投稿機能だけを提供します。

js
event NewPost(address indexed user, string content, string indexed tag);

投稿内容を記録するイベント。 user(投稿者のアドレス)とtag(投稿に付与するラベル)はindexed指定されており、オフチェーンの検索・絞り込みが効率よく行えます。 contentには投稿本文の文字列をそのまま格納します。

js
function post(string calldata content, string calldata tag) public

投稿用の関数。 呼び出し時に本文とタグを受け取り、NewPostイベントを発行します。 引数のcalldataは、コントラクト呼び出し時に渡されたデータ領域を指し、ガスコストを抑えながら安全に読み込むために使用されます。

ABI

json
[
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "user",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "string",
          "name": "content",
          "type": "string"
        },
        {
          "indexed": true,
          "internalType": "string",
          "name": "tag",
          "type": "string"
        }
      ],
      "name": "NewPost",
      "type": "event"
    },
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "content",
          "type": "string"
        },
        {
          "internalType": "string",
          "name": "tag",
          "type": "string"
        }
      ],
      "name": "post",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
]

ABI(Application Binary Interface)は、コントラクトと外部アプリケーションがやり取りする時の入出力仕様をJSONで定義したものです。 Posterコントラクトでは以下を含みます。

NewPost

  • "anonymous": false
    • ログにイベント名を含める設定
  • "inputs"
    • 各フィールドの型(address/string)、名前(user/content/tag)、およびindexedの有無
  • "name": "NewPost"
    • イベント名
  • "type": "event"
    • 項目種別

post

  • "inputs"
    • 関数が受け取る引数の型と名前
  • "name": "post"
    • 関数名
  • "outputs": []
    • 戻り値はなし
  • "stateMutability": "nonpayable"
    • Etherを受け取らない関数であることを示す
  • "type": "function"
    • 項目種別

このABIを利用することで、ウォレットやライブラリは自動的に関数呼び出しやログ解析を行えます。

Twitter風投稿の標準JSON形式

json
{
  "content": [
    {
      "type": "microblog",
      "text": "this is the first post in a thread"
    },
    {
      "type": "microblog",
      "text": "this is the second post in a thread",
      "replyTo": "this[0]"
    },
    {
      "type": "microblog",
      "text": "this is a reply to some other post",
      "replyTo": "some_post_id"
    },
    {
      "type": "microblog",
      "text": "this is a post with an image",
      "image": "ipfs://ipfs_hash"
    },
    {
      "type": "microblog",
      "text": "this post replaces a previously posted post",
      "edit": "some_post_id"
    },
    {
      "type": "delete",
      "target": "some_post_id"
    },
    {
      "type": "like",
      "target": "some_post_id"
    },
    {
      "type": "repost",
      "target": "some_post_id"
    },
    {
      "type": "follow",
      "target": "some_account"
    },
    {
      "type": "unfollow",
      "target": "some_account"
    },
    {
      "type": "block",
      "target": "some_account"
    },
    {
      "type": "report",
      "target": "some_account or some_post_id"
    },
    {
      "type": "permissions",
      "account": "<account_to_set_permissions>",
      "permissions": {
        "post": true,
        "delete": true,
        "like": true,
        "follow": true,
        "block": true,
        "report": true,
        "permissions": true
      }
    },
    {
      "type": "microblog",
      "text": "This is a post from an account with permissions to post on behalf of another account.",
      "from": "<from_address>"
    }
  ]
}

オフチェーンのインデクサでアプリケーション状態を構築するために、複数操作をまとめて送信できるJSONフォーマットを提案しています。

  • ルートキーcontentに操作オブジェクトの配列を指定
  • 各オブジェクトは必ずtypeを持ち、追加のフィールドで詳細を指定します

投稿(microblog)

  • text
    • 本文文字列
  • replyTo
    • 同一配列内の「this[index]」や外部投稿IDを参照
  • image
    • 画像URI(例: ipfs://...)
  • edit
    • 編集対象の投稿ID

投稿以外の操作

  • delete, like, repost, follow, unfollow, block, report

各種操作はtargetフィールドで対象の投稿IDまたはアカウントを指定

権限設定(permissions)

  • account
    • 操作権限を設定するアカウント
  • permissions
    • postやdeleteなど各操作の可否を真偽値で指定

代理投稿(microblog+from)

  • from
    • 別アカウントとして投稿する場合の送信元を指定

この配列を時系列順にインデクサで適用することで、スレッド表示やいいね数、フォロー関係などを再現してTwitterのようなUIを実現できます。

補足

投稿IDを発行しない

コントラクトに状態管理を追加すると複雑化するため省略しました。 多くのインデクサではトランザクションハッシュ+ログインデックスからIDを生成する一般的なパターンがあるため、オフチェーンでIDを付与できます。

contentを文字列(string)で扱う**

JSONやCBORなどのバイナリ形式より可読性を重視し、Etherscanなど既存のインターフェース上で人が直接内容を確認しやすいようにしました。 よりコンパクトなエンコーディングを選ぶと可読性が損なわれるため、この方式を採用しています。

投稿内容を必ずイベントで発行する

イベントを発行しなければガスコストは削減できるものの、インデクサ側での投稿取得や検索の要件が大幅に増加します。 ガス増加分を許容することで、オフチェーンでの効率的な状態構築を優先しました。

セキュリティ

コントラクトレイヤーの安全性

実装が非常にシンプルなため、コントラクト内部に特別な脆弱性は想定されません。

アプリケーションレイヤーでの検証

fromフィールドで投稿元がmsg.senderと異なる場合、必ず該当アドレスによるpermissions投稿があり許可されているかを確認し、未承認の投稿は無効とみなしてください。

データのサニタイズ

投稿内容やタグを表示・保存する前に、XSSや不正データ挿入を防ぐために適切なサニタイズ(入力の検証・無害化)を行ってください。

引用

Auryn Macmillan (@auryn-macmillan), "ERC-3722: Poster [DRAFT]," Ethereum Improvement Proposals, no. 3722, July 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3722.

最後に

今回は「様々なコンテンツの投稿内容とタグを受け取り、イベントとして発行する仕組みを提案しているERC3722に」についてまとめてきました! いかがだったでしょうか?

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

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