🔖

OpenSeaのコントラクトを読んでるよ

2022/04/23に公開約10,900字

情報

コントラクト

V1

https://etherscan.io/address/0x7be8076f4ea4a4ad08075c2508e481d6c946d12b

V2(こっちを読む)

https://etherscan.io/address/0x7f268357a8c2552623316e2562d90e642bb538e5

参考サイト

https://recruit.gmo.jp/engineer/jisedai/blog/opensea-research/
https://blog.suishow.net/2021/08/25/opensea-market-contract解説/
https://note.com/dkcrypto1/n/nd0aca44a50c5

メモったスクラップ

https://zenn.dev/mkurita/scraps/2851e7338e75d7

イメージ

継承関係

フロー(先に共有しておくよ)

(ある程度省略してある)

処理の詳細

まずOpenSeaの主要機能として

  • 作成(Create)
  • 出品(Sell)
  • 購入(Buy)
  • オファー(Make Offer)
  • キャンセル(Cancel)

があります。
一つずつ説明していく。(何か間違ってたら教えて)

作成(Mint)


出品したい画像でNFTを作成する機能です。

このコントラクト上でNFTを発行しているっぽい。

https://etherscan.io/address/0x495f947276749ce646f68ac8c248420045cb7b5e

出品(Sell)


作成したNFTをマーケットに出品します。

registerProxy()

まず初めにProxyRegistoryコントラクトのregister proxyを呼び出します。
ここで自分のアドレス専用のproxyをproxiesに登録します。
※初回のみ実行される

https://etherscan.io/address/0xa5409ec958C83C3f309868babACA7c86DCB077c1
function registerProxy()
public
returns (OwnableDelegateProxy proxy)
{
   // すでに作成されているかチェック
   require(proxies[msg.sender] == address(0));
   // OwnableDelegateProxyのproxyを作成
   proxy = new OwnableDelegateProxy(msg.sender, delegateProxyImplementation, abi.encodeWithSignature("initialize(address,address)", msg.sender, address(this)));
   // proxiesへ登録
   proxies[msg.sender] = proxy;
   return proxy;
}

setApprovalForAll()

このメソッドでregisterProxy()で作成したproxyに対しNFTの転送権限を与えます。
これによりproxyが自由にNFTを転送できるようになります。
※ERC721もしくはERC1155のコントラクトの処理


購入(Buy)

atomicMatch()

ここが一番中心の処理といってもいいかもしれません。
sellまたはbuyのcallDataをもとに送金の実行、NFTの移転を行います。
色々ありますが要点を絞ります。(一つ一つの詳細は別記事にしようかと)

CheckOut時に作成されるDataを見てみましょう。

Data
> address:
0x7f268357A8c2552623316e2562D90e642bB538E5 // OpenSeaのコントラクト
0x8fD4819eD56408a33eF6b0FE3d267579497FE011 // 購入者のアドレス
0x3E8f28BBDC3abBd7450518DD1E09430F1b8b009F // 出品者のアドレス
0x0000000000000000000000000000000000000000
0x495f947276749Ce646f68AC8c248420045cb7b5e // OpenSea Shared Storefrontというtracker
0x0000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000
0x7f268357A8c2552623316e2562D90e642bB538E5 // OpenSeaのコントラクト
0x3E8f28BBDC3abBd7450518DD1E09430F1b8b009F // 出品者のアドレス
0x0000000000000000000000000000000000000000
0x5b3256965e7C3cF26E11FCAf296DfC8807C01073 // 販売手数料の受け取りアドレス
0x495f947276749Ce646f68AC8c248420045cb7b5e // OpenSea Shared Storefrontというtracker
0x0000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000
0x7f268357A8c2552623316e2562D90e642bB538E5 // OpenSeaのコントラクト
0x3E8f28BBDC3abBd7450518DD1E09430F1b8b009F // 出品者のアドレス
0x0000000000000000000000000000000000000000
0x5b3256965e7C3cF26E11FCAf296DfC8807C01073 // 販売手数料の受け取りアドレス
0x495f947276749Ce646f68AC8c248420045cb7b5e // OpenSea Shared Storefrontというtracker
0x0000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000

> uints:
250 // 売買成立時にOpenSeaに支払う手数料(makerが払う)
0   // 売買成立時にOpenSeaに支払う手数料(takerが払う)
0
0
1000000000000000
0
1650789427
0
39084863006214373240324378108073879226763846619051693146992710516441163329964
250
0
0
0
1000000000000000
0
1645271808
1660906608
40016311087236705643380035686772935131414220092142398132607028984093621814475

> feeMethodsSidesKindsHowToCalls:
1
0
0
0
1
1
0
0

> calldataBuy:
0xf242432a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000008fd4819ed56408a33ef6b0fe3d267579497fe0113e8f28bbdc3abbd7450518dd1e09430f1b8b009f000000000000060000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000

> calldataSell:
0xf242432a0000000000000000000000003e8f28bbdc3abbd7450518dd1e09430f1b8b009f00000000000000000000000000000000000000000000000000000000000000003e8f28bbdc3abbd7450518dd1e09430f1b8b009f000000000000060000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000

> replacementPatternBuy:
0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

> replacementPatternSell:
0x000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

> staticExtradataBuy:
0x

>staticExtradataSell:
0x

> vs:
28
28

> rssMetadata:
0x4e45cfae11d66bd60d4bba5956dd96901d3f347a9d924c8805902decfc860efc
0x586f75e97f8952ae65547cad025a1ba57249f098a0f3b84446e79ba1d96e4ed4
0x4e45cfae11d66bd60d4bba5956dd96901d3f347a9d924c8805902decfc860efc
0x586f75e97f8952ae65547cad025a1ba57249f098a0f3b84446e79ba1d96e4ed4
0x0000000000000000000000000000000000000000000000000000000000000000

ぱっと見いろんなデータの羅列でよくわからないのでコントラクト内でどのように使われているのか見てみます。

まずどこにデータを渡しているかというと、atomicMatchに各々のデータを指定して渡しています。

return atomicMatch(
  Order(addrs[0], addrs[1], addrs[2], uints[0], uints[1], uints[2], uints[3], addrs[3], FeeMethod(feeMethodsSidesKindsHowToCalls[0]), SaleKindInterface.Side(feeMethodsSidesKindsHowToCalls[1]), SaleKindInterface.SaleKind(feeMethodsSidesKindsHowToCalls[2]), addrs[4], AuthenticatedProxy.HowToCall(feeMethodsSidesKindsHowToCalls[3]), calldataBuy, replacementPatternBuy, addrs[5], staticExtradataBuy, ERC20(addrs[6]), uints[4], uints[5], uints[6], uints[7], uints[8]),
  Sig(vs[0], rssMetadata[0], rssMetadata[1]),
  Order(addrs[7], addrs[8], addrs[9], uints[9], uints[10], uints[11], uints[12], addrs[10], FeeMethod(feeMethodsSidesKindsHowToCalls[4]), SaleKindInterface.Side(feeMethodsSidesKindsHowToCalls[5]), SaleKindInterface.SaleKind(feeMethodsSidesKindsHowToCalls[6]), addrs[11], AuthenticatedProxy.HowToCall(feeMethodsSidesKindsHowToCalls[7]), calldataSell, replacementPatternSell, addrs[12], staticExtradataSell, ERC20(addrs[13]), uints[13], uints[14], uints[15], uints[16], uints[17]),
  Sig(vs[1], rssMetadata[2], rssMetadata[3]),
  rssMetadata[4]
);

ここで登場するのがOrderとSigです。

Order

Order struct
/* An order on the exchange. */
struct Order {
    /* Exchange address, intended as a versioning mechanism. */
    address exchange; // メインのOpenSeaのコントラクトアドレス
    /* Order maker address. */
    address maker; // 買い手のアドレス
    /* Order taker address, if specified. */
    address taker; // 売り手のアドレス
    /* Maker relayer fee of the order, unused for taker order. */
    uint makerRelayerFee; 
    /* Taker relayer fee of the order, or maximum taker fee for a taker order. */
    uint takerRelayerFee;
    /* Maker protocol fee of the order, unused for taker order. */
    uint makerProtocolFee;
    /* Taker protocol fee of the order, or maximum taker fee for a taker order. */
    uint takerProtocolFee;
    /* Order fee recipient or zero address for taker order. */
    address feeRecipient;
    /* Fee method (protocol token or split fee). */
    FeeMethod feeMethod;
    /* Side (buy/sell). */
    SaleKindInterface.Side side;
    /* Kind of sale. */
    SaleKindInterface.SaleKind saleKind;
    /* Target. */
    address target;
    /* HowToCall. */
    AuthenticatedProxy.HowToCall howToCall;
    /* Calldata. */
    bytes calldata;
    /* Calldata replacement pattern, or an empty byte array for no replacement. */
    bytes replacementPattern;
    /* Static call target, zero-address for no static call. */
    address staticTarget;
    /* Static call extra data. */
    bytes staticExtradata;
    /* Token used to pay for the order, or the zero-address as a sentinel value for Ether. */
    address paymentToken;
    /* Base price of the order (in paymentTokens). */
    uint basePrice;
    /* Auction extra parameter - minimum bid increment for English auctions, starting/ending price difference. */
    uint extra;
    /* Listing timestamp. */
    uint listingTime;
    /* Expiration timestamp - 0 for no expiry. */
    uint expirationTime;
    /* Order salt, used to prevent duplicate hashes. */
    uint salt;
}

このOrderを使って処理を進めています。

各処理

コントラクトの実行者をもとに処理を分岐します

/* Ensure buy order validity and calculate hash if necessary. */
bytes32 buyHash;
/* もしatomicMatchの実行者が購入者だったら */
if (buy.maker == msg.sender) {
    // BUYのパラメータが正しいかチェック
    require(validateOrderParameters(buy));
// もしatomicMatchの実行者が他の人だったら
} else {
    // ハッシュを計算
    buyHash = _requireValidOrderWithNonce(buy, buySig);
}

/* Ensure sell order validity and calculate hash if necessary. */
bytes32 sellHash;
/* もしコントラクトの実行者が出品者だったら */
if (sell.maker == msg.sender) {
    require(validateOrderParameters(sell));
} else {
    sellHash = _requireValidOrderWithNonce(sell, sellSig);
}

オーダーが一致しているかのチェック

/* Must be matchable. */
// buyとsellのオーダーが一致しているかチェック
require(ordersCanMatch(buy, sell));

ここでregisterProxy()で作成した出品者側のproxieを取得します。
もしproxieが存在しなかった場合はコントラクトを停止します。

/* Retrieve delegateProxy contract. */
OwnableDelegateProxy delegateProxy = registry.proxies(sell.maker);

/* Proxy must exist. */
require(delegateProxy != address(0));

移転処理自体はAuthenticatedProxyに委任する為、取得したproxieをもとにauthenticateProxyを取得します。

/* Access the passthrough AuthenticatedProxy. */
AuthenticatedProxy proxy = AuthenticatedProxy(delegateProxy);

ここでハッシュをもとにそのオーダーが完了したか否かのフラグを立てています

/* Mark previously signed or approved orders as finalized. */
if (msg.sender != buy.maker) {
    // そのオーダーがキャンセルもしくは完了したかフラグを立てる
    cancelledOrFinalized[buyHash] = true;
}
if (msg.sender != sell.maker) {
    cancelledOrFinalized[sellHash] = true;
}

NFTの金額のお支払いをします。
executeFundsTransferの中で送金に失敗した場合はコントラクトを停止します。

/* Execute funds transfer and pay fees. */
uint price = executeFundsTransfer(buy, sell);

NFTを実際に移転します。

/* Execute specified call through proxy. */
require(proxy.proxy(sell.target, sell.howToCall, sell.calldata));

作成中

Discussion

ログインするとコメントできます