Бегом по SeaPort
Сегодня мы будем рассматривать данную транзакцию ТЫК. Это транзакция продажи MAYC через контракт SeaPort. В ней мы можем наблюдать вызов функции 0xfb0f3ee1 (function fulfillBasicOrder) (45 контракт на контракте ТЫК)
Вот структура входных значений
Исходя из байткода данной транзакции (ТЫК) можно определить, как и какими значениями была наполнена данная структура
Вот байткод, переданный на вход транзакции (могут быть неточности):
signature - 0xfb0f3ee1
1) 0000000000000000000000000000000000000000000000000000000000000020 // offset структуры (типо через сколько байтов начинается структуруа(20, так как в самом начале))
2) 0000000000000000000000000000000000000000000000000000000000000000 - address considerationToken
3) 0000000000000000000000000000000000000000000000000000000000000000 - uint256 considerationIdentifier
4) 000000000000000000000000000000000000000000000000ed4f67667b2e0000 - uint256 considerationAmount // сколько денег получит сам продавец с учетом комиссий создателя и опенси
5) 000000000000000000000000473373949ffdd88bb3e6e48dff81ae2fa0858a9d - address payable offerer // адресс продавца
6) 000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00 - address zone
7) 00000000000000000000000060e4d786628fea6478f785a6d7e704777c86a7c6 - address offerToken // контракт нфт коллекции
8) 00000000000000000000000000000000000000000000000000000000000073aa - uint256 offerIdentifier // номер нфт в коллекции
9) 0000000000000000000000000000000000000000000000000000000000000001 - uint256 offerAmount // количество передаваемых нфт
10) 0000000000000000000000000000000000000000000000000000000000000000 - BasicOrderType basicOrderType (BasicOrderType это enum) // enum выставляется на ETH_TO_ERC721_FULL_OPEN (передано нулевое значение)
11) 0000000000000000000000000000000000000000000000000000000062a8ec90 - uint256 startTime // дата создания ордера (timestamp)
12) 0000000000000000000000000000000000000000000000000000000062af3fc9 - uint256 endTime // дата закрытия ордера (timestamp)
13) 0000000000000000000000000000000000000000000000000000000000000000 - bytes32 zoneHash
14) 00000000000000000000000000000000000000000000000000f1da4507a36d2a - uint256 salt // какая-то фича криптографическая
15) 0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000 - bytes32 offererConduitKey // используется для генерации conduitKey
16) 0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000 - bytes32 fulfillerConduitKey
17) 0000000000000000000000000000000000000000000000000000000000000002 - uint256 totalOriginalAdditionalRecipients // количесвто человек, который получат комиссию со сделки, в нашем случае 2 (опенси и креатор)
18) 0000000000000000000000000000000000000000000000000000000000000240 // offset по массиву структур (0x240 именно через столько байт начинается массив (20 строка))
19) 00000000000000000000000000000000000000000000000000000000000002e0 // offset до следующего пункта структуры, а именно до сигнатуры, до нее 736 байт (25 строка)
20) 0000000000000000000000000000000000000000000000000000000000000002 // размер массива структур AdditionalRecipient
21) 000000000000000000000000000000000000000000000000063eb89da4ed0000 // объем перечисляемых комиссий на опенси (в wei)
22) 0000000000000000000000005b3256965e7c3cf26e11fcaf296dfc8807c01073 // сторонний кошелек OpenSea, где они собирают комиссии
23) 000000000000000000000000000000000000000000000000063eb89da4ed0000 // объем перечисляемых комиссий для Creator (в wei)
24) 000000000000000000000000aae7ac476b117bccafe2f05f582906be44bc8ff1 // сторонний кошелек, на который начисляют Creators fee
25) 0000000000000000000000000000000000000000000000000000000000000041 // сигнатура
26) 513ff435df6256dd9ebcc3f805c55eec75a26e10985a73b1c8aebefc8296d61f // сигнатура
27) 4eeb7f5d34b3aa9b54b4681e66b0410b6cf2600c10070721826b18df17007b4c // сигнатура
28) 1b00000000000000000000000000000000000000000000000000000000000000 // сигнатура
Сама функция _validateAndFulfillBasicOrder выглядит следующим образом и возвращает bool значение, которое означает что, что сделка была успешной
В самом начале мы объявляем три переменные:
После у нас идет блок ассемблерного кода, который основываясь на поле basicOrderType нашей calldata будет определять значения для наших трех только что определенных переменных (делается это путем масок)
Мы создаем блок инициализации и начинаем проводить в нем операции
В начале мы определяем временную переменную correctPayableStatus типа bool, которая будет сигнализировать о статусе оплаты
Далее асемблерный код делает проверку, была ли уже произведена оплата (то есть были ли вместе с транзакцией отправлены средства(по большей части это проверка для оплаты в ERC20)). Если оплата не была произведена, то транзакцию выкидывает в ошибку
Тут мы объявляем еще переменные, которые в будущем будут заполнены значениями из нашей calldata
Создаем новый блок инициализации
Объявляем временную переменную, которая определяет наш способ оплаты
После идет большой блок ассемблерного кода, в котором мы определяем значения для ранее заданных переменных, а именно:
- В offerTypeIsAdditionalRecipientsType записываем результат проверки, чтобы понимать, продаем мы или покупаем (0 - покупаем, 1 - продаем)
- В additionalRecipientsToken записываем результат проверки, если route > 3, то записываем 0xc4, иначе записываем 0x24
- В receivedItemType записываем результат от проверки, если route > 2, то ReceiveItemType равен (route - 2), если route равен 2, то ReceiveItemType равен ERC20 (1), в противном случае ReceiveItemType равен Eth (0)
- В offeredItemType записываем результат от проверок, если route > 3, то offeredItemType равен ERC20 (1), если route равен 2 или 3, то offeredItemType равен route, если route равен 0 или 1, то offeredItemType равняется route + 2
- После всего этого мы вызываем внутренюю функцию _prepareBasicFulfillmentFromCalldata (это большая функция, на разбор которой нужна отдельная статья, поэтому я просто кратко опишу ее), которая основываясь на входные данные (мы в нее передмаем все ранее созданные переменные) подготавливает нам fulfillment с ручным доступом к сalldata и памяти. Она вычисляет хэш заказа, генерирует event OrderFulfilled и подтверждает валидность транзакции (если кратко, то тут просто объявляется начало транзакции и что она готова к выполнению)
Теперь мы создаем переменную conduitKey, которая будет использоваться в transfer функции
Теперь мы снова ныряем в блок ассемблерного кода, который основываясь на один из входных параметров (offererConduitKey) и route генерирует нам conduitKey
Тут у нас начинается основной этап с передачей токенов между аккаунтами (основываясь на route)
If-else конструкция в данном случае определяет, в каком токене происходит оплата, if это native token сети, в остальных случаях соответсвенно else
В обоих случаях все очень просто, везде проверяет route и опираясь на эту проверку, идет вызов функции с трансфером в конкретных токенах (из особенностей, так это то что в else еще используется переменная accumulator, которая выполняет функцию буферки
В самом конце мы просто объявляем о конце сделки при помощи _clearReentrancyGuard() и передаем true в изначальную транзакцию, которая и вызвала всю эту громадную функцию, тем самым сигнализируя об успешной сделке!