Вызов функции смарт-контракта BSC для совершения покупки с помощью Python
Я пытаюсь программно воспроизвести действие покупки соответствии с тем, что в этом контракте NFT на торговой площадке в0x54ac76f9afe0764e6a8ed6c4179730e6c768f01cуказано
Моя цель - иметь возможность воссоздать транзакцию, подобную следующей, с хешем 0xa792fa25a9fcf9346cff4e37a580c96d27d019d646e815dcc8680496fd611adf
Первым делом я вызвал этот хеш в Python с помощью web3, который вернул
AttributeDict({'blockHash': HexBytes('0x778481abab6360ff6a13c6159743d9289d4edcee64c8fa2d067857fd3d0b673f'), 'blockNumber': 13411584, 'from': '0x292788A70a8e885FF6b0D9A95A4c75366a82930a', 'gas': 328806, 'gasPrice': 5000000000, 'hash': HexBytes('0xa792fa25a9fcf9346cff4e37a580c96d27d019d646e815dcc8680496fd611adf'), 'input': '0xe8e8e872000000000000000000000000b417cc1330076d05a13406310a4ae774f8b5c38300000000000000000000000098eb46cbf76b19824105dfbcfa80ea8ed020c6f4000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c000000000000000000000000000000000000000000000000000013fdffc2431400000000000000000000000000000000000000000000000002df69673c8180000000000000000000000000000000000000000000000000000000000061b5f67900000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004183c3caefa779647a11d7f6bb55eaa15c08ca2feadd339793fc57d69b094e5f2b1515391276afca3828f7b3d7f22a45c48a6a947f6edeff815d85e6370c93a4721b00000000000000000000000000000000000000000000000000000000000000', 'nonce': 977, 'to': '0x54ac76f9afe0764e6a8Ed6c4179730E6c768F01C', 'transactionIndex': 103, 'value': 0, 'type': '0x0', 'v': 148, 'r': HexBytes('0x980378216da1477e8a5bd65ba5d865814719bfe50a70d53b36a754d36cab9319'), 's': HexBytes('0x69d01eaa1d7711c9f8c30e9976db3fe37bd39c877f9cdded1bfe50255eae4179')})
Чтобы лучше понять, что было внутри ввода, я декодировал ту часть, которая дала
function called: matchTransaction
arguments: {
"addresses": [
"0xb417CC1330076D05A13406310A4ae774F8b5c383",
"0x98eb46CbF76B19824105DfBCfa80EA8ED020c6f4",
"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
],
"values": [
21981638574868,
207000000000000000,
1639315065
],
"signature": "0x83c3caefa779647a11d7f6bb55eaa15c08ca2feadd339793fc57d69b094e5f2b1515391276afca3828f7b3d7f22a45c48a6a947f6edeff815d85e6370c93a4721b"
}
Оттуда у нас есть ABI
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"contractAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"},{"indexed":false,"internalType":"address","name":"paymentToken","type":"address"},{"indexed":false,"internalType":"address","name":"seller","type":"address"},{"indexed":false,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"MatchTransaction","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"feeToAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_messageHash","type":"bytes32"}],"name":"getEthSignedMessageHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"_nftAddress","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"address","name":"_paymentErc20","type":"address"},{"internalType":"uint256","name":"_price","type":"uint256"},{"internalType":"uint256","name":"_saltNonce","type":"uint256"}],"name":"getMessageHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address[2]","name":"addresses","type":"address[2]"},{"internalType":"uint256[3]","name":"values","type":"uint256[3]"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"ignoreSignature","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[3]","name":"addresses","type":"address[3]"},{"internalType":"uint256[3]","name":"values","type":"uint256[3]"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"matchTransaction","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"paymentTokens","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_ethSignedMessageHash","type":"bytes32"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"recoverSigner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address[]","name":"_removedPaymentTokens","type":"address[]"}],"name":"removePaymentTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_feeToAddress","type":"address"}],"name":"setFeeToAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_paymentTokens","type":"address[]"}],"name":"setPaymentTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_transactionFee","type":"uint256"}],"name":"setTransactionFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"splitSignature","outputs":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"transactionFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"usedSignatures","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]
Немного изучив функции рынка, мы можем примерно увидеть, что такое matchTransaction, в файле marketplace.sol.
function matchTransaction(
address[3] calldata addresses,
uint256[3] calldata values,
bytes calldata signature
) external returns (bool) {
// address[3] [seller_address,nft_address,payment_token_address]
// uint256[3] [token_id,price,salt_nonce]
// bytes seller_signature
require(
paymentTokens[addresses[2]] == true,
"Marketplace: invalid payment method"
);
require(
!usedSignatures[signature],
"Marketplace: signature used. please send another transaction with new signature"
);
bytes32 criteriaMessageHash = getMessageHash(
addresses[1],
values[0],
addresses[2],
values[1],
values[2]
);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(
criteriaMessageHash
);
require(
recoverSigner(ethSignedMessageHash, signature) == addresses[0],
"Marketplace: invalid seller signature"
);
// check current ownership
IERC721 nft = IERC721(addresses[1]);
require(
nft.ownerOf(values[0]) == addresses[0],
"Marketplace: seller is not owner of this item now"
);
// Check payment approval and buyer balance
IERC20 paymentContract = IERC20(addresses[2]);
require(
paymentContract.balanceOf(_msgSender()) >= values[1],
"Marketplace: buyer doesn't have enough token to buy this item"
);
require(
paymentContract.allowance(_msgSender(), address(this)) >= values[1],
"Marketplace: buyer doesn't approve marketplace to spend payment amount"
);
// We divide by 10000 to support decimal value such as 4.25% => 425 / 10000
uint256 fee = transactionFee.mul(values[1]).div(10000);
uint256 payToSellerAmount = values[1].sub(fee);
// transfer money to seller
paymentContract.safeTransferFrom(
_msgSender(),
addresses[0],
payToSellerAmount
);
// transfer fee to address
if (fee > 0) {
paymentContract.safeTransferFrom(_msgSender(), feeToAddress, fee);
}
// transfer item to buyer
nft.safeTransferFrom(addresses[0], _msgSender(), values[0]);
usedSignatures[signature] = true;
// emit sale event
emitEvent(addresses, values);
return true;
}
А также
// Events
event MatchTransaction(
uint256 indexed tokenId,
address contractAddress,
uint256 price,
address paymentToken,
address seller,
address buyer,
uint256 fee
);
и
emit MatchTransaction(
values[0],
addresses[1],
values[1],
addresses[2],
addresses[0],
_msgSender(),
transactionFee
);
Хорошо, во-первых, я не понимаю разницы между событием и функцией. Я предполагаю, что мне нужно вызвать функцию, но способ ее определения и способ ее вызова в последнем фрагменте отличается от того, что я декодировал, поэтому обо всем по порядку
import json
from web3 import Web3
from web3.middleware import geth_poa_middleware
bsc='https://bsc-dataseed.binance.org/'
w3=Web3(Web3.HTTPProvider(bsc))
w3.middleware_onion.inject(geth_poa_middleware, layer=0)
marketplace_contract_address='0x54ac76f9afe0764e6a8Ed6c4179730E6c768F01C'
contract_object=w3.eth.contract(address=marketplace_contract_address,abi=abi)
myAddress='0xsjfjjehdhshejdjsjej.....'
nonce=w3.eth.getTransactionCount(myAddress)
current_owner='0x46b1589e796c1102f68e2889a3315e42e600a901'
nft_contract='0x98eb46cbf76b19824105dfbcfa80ea8ed020c6f4'
tokenId=21981638574868
wbnb_contract='0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'
bnb_price=w3.toWei(0.207,'ether')
salt_nonce=1639315065 #This value is in the website and can be scraped
tx={'nonce':nonce,'from':myAddress,'gas':500000,'gasPrice':5000000000,'to':marketplace_contract_address}
purchase=contract_object.functions.MatchTransaction(here comes the main doubt).buildTransaction(tx)
sign_tx=w3.eth.account.signTransaction(purchase,privatekey)
w3.eth.send_raw_transaction(sign_tx.rawTransaction())
Итак, пока все правильно? Я читал документы и смотрел несколько видеороликов, но все складывается только небольшими частями, поэтому, вероятно, не все так, как должно. Я что-то забыл при создании переменной? Мне нужно включить в
tx
изменить
to
и
from
адреса? В зависимости от того, что находится внутри входных данных, он может быть уже включен туда
И теперь у меня возникает главное сомнение по поводу всего этого процесса. Как мне перейти к входным данным при вызове функции matchTransaction? Если моя расшифровка верна, должно быть так
input_data={'addresses':[current_owner,nft_contract,wbnb_contract],'values':[tokenId,bnb_price,salt_nonce],'signature':value_to_be_scraped}
purchase=contract_object.functions.MatchTransaction(input_data).buildTransaction(tx)
Но глядя на
marketplace.sol
возможно, вам нужно ввести данные без такого словаря, как это сделано в последнем фрагменте кода оттуда
purchase=contract_object.functions.MatchTransaction(tokenId,nft_contract,bnb_price,wbnb_contract,current_owner,value_to_be_scraped,fee).buildTransaction(tx)
Итак, это главное сомнение можно разделить на 3 части.
1 - Почему входные данные различаются в событии matchTransaction, функции matchTransaction и emit matchTransaction. В чем разница между этими тремя?
2 - Как правильно называть? Я предполагаю, что это функция, поскольку код указывает на нее, но, как я уже сказал, незнание разницы между собой усложняет мне задачу.
3 - Предполагая, что мне нужно воспроизвести аргументы функции, нужно ли мне передавать их в dict, как полученные мной декодированные данные?
Спасибо за любые разъяснения, я знаю, что разместил здесь довольно много текста.