mookim

mookim

mookim.eth

ユニチェーンメインネットのジェネシスを推測する方法は?

何を持っていますか?#

何を達成したいですか?#

動作するユニチェーン RPC ノード。この文書では、正しい genesis-l2.json ファイルを推測する方法にのみ焦点を当て、ブロック 0 が正しいブロックハッシュ =0x3425162ddf41a0a1f0106d67b71828c9a9577e6ddeb94e4f33d2cde1fdc3befeを持つようにします。

ステップ#

1. ブロックデータを修正する#

まず、ブロックを取得して差分を確認しましょう。

テストネットのジェネシスをダウンロードし、genesis-l2.json.bakという名前を付けます。

import os
os.environ["RPC"] = "https://mainnet-readonly.unichain.org"
from simplebase import *

b=eth_getBlockByNumber(RPC, 0, True)
g=json.load(open("genesis-l2.json.bak"))

>>> pprint({i:j for i,j in b.items() if i in g and b[i]!=g[i]})
{'nonce': '0x0000000000000000', 'timestamp': '0x67291fc7'}

上記の 2 つの差分を修正し、config.chainIdも 130(メインネットの chainId)に変更する必要があることに注意してください。

2. 既知のコントラクトコードとデータを修正する#

次に、allocを修正し、alloc 構造体を観察します:

    "420000000000000000000000000000000000002f": {
      "code": "0x60806040526004...",
      "storage": {
        "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x0000000000000000000000004200000000000000000000000000000000000018"
      },
      "balance": "0x0"
    },

キーは 0x プレフィックスなしのコントラクトアドレスで、値にはコード、ストレージ辞書、残高、ノンスがあります。
残高とノンスは異ならないと推測するので、ストレージとコードを照会し、不一致を見つけて置き換えます:

import os
os.environ["RPC"] = "https://mainnet-readonly.unichain.org"
from simplebase import *

def cached_simple_rpccall(rpc, method, params, cacheprefix=""):
    cachekey = hashlib.sha256(f"{rpc}_{method}_{json.dumps(params)}".encode()).hexdigest()
    cachefile = f"__pycache__/cached_simple_rpccall{cacheprefix}_{cachekey}"
    if os.path.isfile(cachefile):
        return json.load(open(cachefile))
    res = simple_rpccall(rpc, method, params)
    open(cachefile, "w").write(json.dumps(res))
    return res

if 1:
    g = json.load(open("genesis-l2.json.bak"))
    known = set()
    for addr,v in g["alloc"].items():
        addr = "0x"+addr
        cache = "-".join([v[i] for i in sorted(v.keys()) if i!="storage"])
        if "storage" in v:
            cache += json.dumps(v["storage"])
        if cache in known:
            continue
        known.add(cache)
        #print(v.keys()) code,balance,storage,nonce
        if "code" in v:
            #realcode = eth_getCode(RPC, addr)
            realcode = cached_simple_rpccall(RPC, "eth_getCode", [addr, "0x0"])
            if v["code"]==realcode:
                print("ok code", addr)
            else:
                print("ERROR code:", addr, len(v["code"]), "=>", len(realcode))
                g["alloc"][addr[2:]]["code"] = realcode
                if cache in known:
                    known.remove(cache)
        if "storage" in v:
            for s_slot, s_value in v["storage"].items():
                #realvalue = eth_getStorageAt(RPC, addr, s_slot, height=0)
                realvalue = int(cached_simple_rpccall(RPC, "eth_getStorageAt", [addr, s_slot, "0x0"]), 16)
                if realvalue==int(s_value, 16):
                    print("ok storage", addr, s_slot)
                else:
                    print("ERROR storage:", addr, s_slot, int(s_value, 16), "=>", realvalue)
                    g["alloc"][addr[2:]]["storage"][s_slot] = "%064x"%(realvalue)
                    if cache in known:
                        known.remove(cache)
        
    print(len(known))
    open("genesis-l2.json", "w").write(json.dumps(g))

テストネットとメインネットの両方が opstack を使用しているため、ほとんどのコントラクトは同じアドレス、コード、ストレージを持つはずです。したがって、キャッシュメカニズムを採用し、テストネットのジェネシスで同じ値を持つ多くのコントラクトがある場合、メインネットでも同じ値を持つ可能性が高いです。

3. 欠落しているコントラクトを見つける#

上記のコードを実行した後、テストネットに存在するがメインネットにはコードやストレージがない 2 つのコントラクトが見つかりました。したがって、これらの 2 つのキーは削除する必要があります:

  • 0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f
  • 0x1f98431c8ad98523631ae4a59f267346ea31f984

これらのアドレスを検索すると、それぞれ UniV2Factory と UniV3Factory であることがわかります。

エクスプローラーの検証済みコントラクトページ( https://unichain.blockscout.com/verified-contracts )を見て、ユニークなコントラクトアドレスを持つ UniV2Factory コントラクトを見つけました:https://unichain.blockscout.com/address/0x1F98400000000000000000000000000000000002?tab=contract

したがって、隣接するコントラクトアドレスを確認し、これらのコントラクトも事前にデプロイされていることを見つけました:

0x1F98400000000000000000000000000000000002
0x1F98400000000000000000000000000000000003
0x1F98400000000000000000000000000000000004

後者の 2 つは検証されていませんが、発生したイベントから、UniV3 および UniV4 コントラクトであると推測できます。したがって、これらの 3 つのアドレスを genesis-l2.json.bak に追加しましょう。

ストレージスロット番号は、コントラクトソースコードの分析、メインネットデプロイのデバッグトレース、または単にスロット 0〜10 の eth_getStorageAt 呼び出しから推測できます。

    "1f98400000000000000000000000000000000002": {
      "code": "0x",
      "storage": {
        "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000009b64f6e1d60032f5515bd167346cfcd2162ee73a"
      },
      "balance": "0x0"
    },
    "1f98400000000000000000000000000000000003": {
      "code": "0x",
      "storage": {
        "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000009b64f6e1d60032f5515bd167346cfcd2162ee73a",
        "0x083fc81be30b6287dea23aa60f8ffaf268f507cdeac82ed9644e506b59c54ff0": "0x0000000000000000000000000000000000000000000000000000000000000001",
        "0x72dffa9b822156d9cf4b0090fa0b656bcb9cc2b2c60eb6acfc20a34f54b31743": "0x000000000000000000000000000000000000000000000000000000000000003c",
        "0x8cc740d51daa94ff54f33bd779c2d20149f524c340519b49181be5a08615f829": "0x00000000000000000000000000000000000000000000000000000000000000c8",
        "0xfb8cf1d12598d1a039dd1d106665851a96aadf67d0d9ed76fceea282119208b7": "0x000000000000000000000000000000000000000000000000000000000000000a"
      },
      "balance": "0x0"
    },
    "1f98400000000000000000000000000000000004": {
      "code": "0x",
      "storage": {
        "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000009b64f6e1d60032f5515bd167346cfcd2162ee73a"
      },
      "balance": "0x0"
    },

詳細な値は重要ではなく、上記のコードは RPC からそれらを取得し、正しい値に置き換えます。

これらのステップを経ても、まだ間違ったブロックハッシュが得られています。

4. eth_getProof でストレージの正確性を確認する#

ジェネシスブロックの詳細を知るにはどうすればよいでしょうか? debug_traceBlock RPC メソッドは、ジェネシスブロックをサポートしています。

利用可能なすべての RPC メソッドを調査すると、このeth_getProofが見つかります。これにより、accountProof、balance、codeHash、nonce、storageHash、storageProof を出力できます。

すべての既知のアドレスを反復して eth_getProof を呼び出すことで、すべての既知のアドレスが正しい balance、codeHash、nonce、storageHash を持っているが、accountProof が間違っていることがわかります。これは、ジェネシスからまだ欠落しているコントラクトがあることを意味します。

5. 欠落しているコントラクトを探し続ける#

ブロックスカウトが提供するすべての API を調査すると、このコントラクトのリストを取得が見つかります。これにより、すべてのコントラクトアドレスを返すことができます:

https://unichain.blockscout.com/api?module=contract&action=listcontracts&offset=10000

この出力をフィルタリングして、ブロック番号 0 を使用して eth_getCode を呼び出すことで、4 つの欠落したアドレスを見つけます:

  • 0x4300c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30001
  • 0x4300c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30002
  • 0x4300c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30003
  • 0x4300c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30004

最初の 200 スロットを見つけるために単純な eth_getStorageAt を使用すると、最初の 3 つの正しいストレージ値を成功裏に見つけますが、最後のものはまだ間違っています。最初の 200 スロットがすべてゼロであってもです。

g = json.load(open("api?module=contract&action=listcontracts&offset=10000"))
for addr in ["0x4300c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30001", "0x4300c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30002", "0x4300c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30003", "0x4300c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30004"]:
    if not addr.startswith("0x"):
        addr = "0x"+addr
    addr = addr.lower()
    truth = cached_simple_rpccall(RPC, "eth_getProof", [addr, [], "0x0"])
    our = cached_simple_rpccall(OUR_PRC, "eth_getProof", [addr, [], "0x0"], cacheprefix="test2")
    #[i==our['accountProof'][idx] for idx,i in enumerate(truth['accountProof'])]
    #if not all([truth['balance']==our['balance'], truth['codeHash']==our['codeHash'], truth['nonce']==our['nonce'], truth['storageHash']==our['storageHash']]):
    if 1:
        print(truth['balance'], truth['codeHash'], truth["nonce"], truth["storageHash"])
        print(addr, truth['balance']==our['balance'], truth['codeHash']==our['codeHash'], truth['nonce']==our['nonce'], truth['storageHash']==our['storageHash'], )

今、eth_getProof を使用してスロットを指定し、このコントラクトが実際にストレージを持っていることを確認します。空のコントラクトはストレージプルーフを返さないからです。

5. コントラクトの逆アセンブル#

0x4300c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30004をデコンパイルするために dedaub を使用し、手動でフォーマットします:

// Decompiled by library.dedaub.com
// 2024.11.17 14:07 UTC
// Compiled using the solidity compiler version 0.8.26


// ストレージ命令の使用から推測されたデータ構造と変数
uint256 _receive; // STORAGE[0x0]
mapping (address => uint256) storage1; // STORAGE[0x1]
mapping (address => uint256) _earnedFees; // STORAGE[0x2]
mapping (address => struct_313) _balanceOf; // STORAGE[0x3]

struct struct_313 { address addr; uint256 amount; };

// イベント
Withdrawn(address, address, uint256);

function 0x396cb597(address varg0) public nonPayable { 
    require(msg.data.length - 4 >= 32);
    return _balanceOf[varg0].addr;
}

function balanceOf(address account) public nonPayable { 
    require(msg.data.length - 4 >= 32);
    return _balanceOf[account].amount;
}

function 0xc13886a4(address varg0, address varg1) public nonPayable { 
    require(_balanceOf[varg0].addr == msg.sender, Unauthorized());
    _balanceOf[varg0].addr = varg1;
    emit 0xf0476b1621059e9b7a94b31f4699ab07974f87c8e31db4cb9ad1f9892eb9b169(varg0, _balanceOf[varg0].addr, varg1);
}

function 0xc6743555(address contract1, address contract2, address owner2, uint256 amt) public nonPayable { 
    require(!_balanceOf[contract2].addr);
    _balanceOf[contract2].addr = owner2;
    _balanceOf[contract2].amount = 0;
    emit 0xf0476b1621059e9b7a94b31f4699ab07974f87c8e31db4cb9ad1f9892eb9b169(contract2, 0, owner2);
    0x6de(amt, contract2, contract1);
      //require(msg.sender == _balanceOf[contract1].addr, Unauthorized());
}

function recipients(address varg0) public nonPayable { 
    return _balanceOf[varg0].addr, _balanceOf[varg0].amount;
}

function earnedFees(address addr) public nonPayable { 
    return _earnedFees[addr] + (_balanceOf[addr].amount * (_receive - storage1[addr])) /1e30;
}

function receive() public payable { 
    _receive += msg.value * 1e26;
}

function 0x6de(uint256 varg0, uint256 varg1, uint256 varg2) private { 
    require(msg.sender == _balanceOf[address(varg2)].addr, Unauthorized());
    require(address(varg1));
    require(0 - varg0);
    require(_balanceOf[address(varg2)].amount >= varg0, InsufficientAllocation());
    updateState(varg2);
    updateState(varg1);
    v0 = _SafeSub(_balanceOf[address(varg2)].amount, varg0);
    _balanceOf[address(varg2)].amount = v0;
    v1 = _SafeAdd(_balanceOf[address(varg1)].amount, varg0);
    _balanceOf[address(varg1)].amount = v1;
    emit 0x4c6e7131fb69f3c2cc88b05b76a7aa4809429fefa284dda9cf14884d25e3742b(msg.sender, address(varg2), address(varg1), varg0);
    return ;
}

function updateState(addr) private { 
    _earnedFees[addr] += (_receive - storage1[addr])* _balanceOf[addr].amount /1e30 ;
    storage1[addr] = _receive;
}

function 0x976(address varg0) private { 
    v0 = _SafeSub(_receive, storage1[varg0]);
    v1 = _SafeMul(_balanceOf[varg0].amount, v0);
    v2 = _SafeDiv(v1, 10 ** 30);
    return v2;
}

function transferAllocation(address varg0, address varg1, uint256 amt) public nonPayable { 
    require(_balanceOf[varg1].addr);
    
    //0x6de(uint256 varg0, uint256 varg1, uint256 varg2) 0x6de(varg2, varg1, varg0);
    require(msg.sender == _balanceOf[varg0].addr, Unauthorized());
    require(_balanceOf[varg0].amount >= amt, InsufficientAllocation());
    updateState(varg0);
    updateState(varg1);
    _balanceOf[varg0].amount -= amt;
    _balanceOf[address(varg1)].amount += amt;
    emit 0x4c6e7131fb69f3c2cc88b05b76a7aa4809429fefa284dda9cf14884d25e3742b(msg.sender, varg0, address(varg1), amt);
}

function withdrawFees(address account_) public nonPayable { 
    updateState(msg.sender);
    if (_earnedFees[msg.sender]) {
        _earnedFees[msg.sender] = 0;
        v0, /* uint256 */ v1 = account_.call().value(_earnedFees[msg.sender]).gas(msg.gas);
        require(v0, WithdrawalFailed());
    }
    emit Withdrawn(msg.sender, account_, _earnedFees[msg.sender]);
    return _earnedFees[msg.sender];
}

// 注意: 関数セレクタは元のソリディティコードには存在しません。
// しかし、完全性のために表示します。

function __function_selector__() private { 
    MEM[64] = 128;
    if (msg.data.length < 4) {
        require(!msg.data.length);
        receive();
    } else if (0xc13886a4 > msg.data[0] >> 224) {
        if (0x24e2c06 == msg.data[0] >> 224) {
            transferAllocation(address,address,uint256);
        } else if (0x164e68de == msg.data[0] >> 224) {
            withdrawFees(address);
        } else if (0x396cb597 == msg.data[0] >> 224) {
            0x396cb597();
        } else {
            require(0x70a08231 == msg.data[0] >> 224);
            balanceOf(address);
        }
    } else if (0xc13886a4 == msg.data[0] >> 224) {
        0xc13886a4();
    } else if (0xc6743555 == msg.data[0] >> 224) {
        0xc6743555();
    } else if (0xeb820312 == msg.data[0] >> 224) {
        recipients(address);
    } else {
        require(0xfeb7219d == msg.data[0] >> 224);
        earnedFees(address);
    }
}

機能は収益分配であると推測できます。コントラクトアドレス => オーナーアドレスのマッピングがあり、オーナーは重みを移転でき、総重みは 10000 です。

6. スロットを推測する#

ストレージプルーフはマークルツリーであり、各リーフノードは sha3 (slot) と rlp エンコードされた値であり、プレフィックスは上記のブランチノードによって参照されます。したがって、eth_getProof を使用していくつかのプレフィックスを照会して完全なツリーを取得できます。プルーフの最初の項目は常にルートであり、それをデコードすると、16 の可能な子の中に 4 つの子があることがわかります。たとえば、スロット 2 のストレージプルーフを照会することで、子プレフィックス 4 のプルーフを取得できます。sha3(toarg(2))=405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。