- Регистрация
- 12.06.2018
- Сообщения
- 1 568
- Реакции
- 1 094
- Баллы
- 113
Для доступа к блокчейну рядовой пользователь использует плагин для браузера Metamask, который делает следующие вещи:
• Обеспечивает подключение к нужной сети через свой удаленный узел.
• Позволяет создавать новые или импортировать существующие аккаунты для работы с блокчейном. Это дает возможность подписывать транзакции приватным ключом, который хранится локально у пользователя.
Использовать Metamask достаточно удобно, однако при попытке автоматизации с помощью ZennoPoster возникают дополнительные сложности - приходится взаимодействовать с браузерным плагином, что накладывает некоторые ограничения.
Исключим из нашей цепочки Metamask и будем работать напрямую с блокчейном с помощью Nethereum – удобной библиотеки для взаимодействия с блокчейном и смарт-контрактами. Таким образом повышается масштабируемость действий и скорость их выполнения.
В данной статье рассмотрим сеть Binance Smart Chain (BSC), которая поддерживает смарт-контракты и совместима с виртуальной машиной Ethereum (EVM), а соответственно поддерживает инструменты Ethereum и DApps.
1. Транзакции.
Транзакция — операция сохранения данных в блокчейне, в ходе которой происходит передача криптоактивов или другой информации между кошельками. Отправка транзакции происходит после её создания в кошельке и подписания цифровой подписью на основе закрытого ключа. Транзакция похожа на HTTP запрос. Сервер принимает этот запрос и вносит изменения в базу данных, которой по сути является блокчейн. Транзакция принимается сетью и цепочка блоков расширяется включёнными изменениями.
Для работы с конкретной сетью (в данном случае BSC) необходимы данные этой сети - адрес RPC сервера и Chain ID, а так же приватный ключ нашего кошелька.
BSC Mainnet:
Chain Id: 56
Json Rpc: https://bsc-dataseed.binance.org/
Для Testnet данные следующие:
Chain Id: 97
Json Rpc: https://data-seed-prebsc-1-s1.binance.org:8545/
Напишем код для транзакции в Testnet:
C#:
int binanceTestnetChainId = 97;
string binanceTestnetJsonRpc = "https://data-seed-prebsc-1-s1.binance.org:8545/";
string walletKey = "01388939eaae42aad053c8db7ffe320e7c9c182de72b8f89f9822125dff5496a";
string addressTo = "0x000000000000000000000000000000000000dEaD"; // адрес получателя
string data = "0x";
decimal amount = 0.001;
string hash = string.Empty;
var account = new Account(walletKey, binanceTestnetChainId);
var web3 = new Web3(account, binanceTestnetJsonRpc);
web3.TransactionManager.UseLegacyAsDefault = true;
var transaction = new TransactionInput();
transaction.From = account.Address;
transaction.To = addressTo;
transaction.Data = data;
transaction.Value = Web3.Convert.ToWei(amount).ToHexBigInteger();
transaction.Gas = await web3.TransactionManager.EstimateGasAsync(transaction);
var hash = await web3.TransactionManager.SendTransactionAsync(transaction);
В результате получаем хэш нашей транзакции - 0xb5a35a7105e3d83874a8671f330d7d38208c84bd0efe126fe8c2160b4db15ebd, детали которой можно посмотреть в обозревателе блоков BscScan.
После этого можно узнать баланс нашего кошелька:
C#:
public async Task GetBnbTestnetAccountBalance()
{
var account = new Account(walletKey, binanceTestnetChainId);
var web3 = new Web3(account, binanceTestnetJsonRpc);
var balance = await web3.Eth.GetBalance.SendRequestAsync("0x483879d01E943a404B97240D9Aa8B5b94E1a7C0B");
}
2. Работа со смарт-контрактами.
Смарт-контракты - это алгоритм определенных действий, интегрированный в код блокчейна. При соблюдении установленных договоренностей, которые в нем прописаны, выполняется автоматический запуск последовательности. Являются посредником между данными и внешним миром. Посредством HTTP RPC вызываются методы смарт-контракта и изменяется его состояние. Транзакции относятся к смарт-контрактам так же, как HTTP запросы к вёб серверу. Код смарт контракта исполняется на стековой витруальной машине Ethereum Virtual Machine (EVM). Смарт-контракт запускается путем компиляции в байт-код EVM. Код Solidity должен быть скомпилирован в байт-код перед развертыванием в сети Ethereum. Этот байт-код соответствует серии инструкций кода операции, которые интерпретирует EVM. EVM использует архитектуру на основе стека. Размер элемента данных в стеке составляет 32 байта (или 256 бит).
Соответственно мы будем манипулировать блоками размером в 32 байта.
Рассмотрим простой контракт:
Код:
contract C {
uint256 a;
function setA(uint256 _a) {
a = _a;
}
function getA() returns(uint256) {
return a;
}
}
Создадим транзакцию вызывающую метод setA(1) этого контракта:
Rinkeby Transaction Hash (Txhash) Details | Etherscan
Rinkeby (ETH) detailed transaction info for txhash 0x7db471e5792bbf38dc784a5b983ee6a7bbe3f1db85dd4daede9ee88ed88057a5. The transaction status, block confirmation, gas fee, Ether (ETH), and token transfer are shown.
Данные (всего 36 байт), переданные в транзакции следующие:
0xee919d500000000000000000000000000000000000000000000000000000000000000001
Смарт-контракт интерпретирует этот набор байт, как вызов метода и выполнит код для setA(1).
Эти данные можно разбить на 2 части:
первые 4 байта это селектор метода - 0xee919d5
оставшиеся данные это аргумент метода длинной в 32 байта - 00000000000000000000000000000000000000000000000000000000000000001
Селектор метода это kecccak256 хэш сигнатуры метода setA(uint256)
Смарт-контракт вызывает методы, обрабатывая входные данные структурируемым способом, для чего используется ABI (Application Binary Interface) - данные кодируются в соответствии с их типом, описанным в этой спецификации. ABI в нашем случае нужен, чтобы преобразовывать входные и выходные данные к нужному типу.
Если вызываемый метод меняет состояние блокчейна, он будет выполняться как транзакция и соответственно будет израсходован газ. Однако, если метод только получает информацию и ничего не изменяет то такой запрос (eth_call) будет выполнен бесплатно. Eth_call похож на HTTP GET запрос, который не изменяет состояние, а просто получает данные. В примере выше это метод getA().
Таким образом существует 2 способа взаимодействия с контрактами - Read и Write.
В обозревателе блоков BscScan можно увидеть все функции контракта, с принимаемыми и возвращаемыми значениями, разделённые по типу, правда только для проверенных контрактов.
Сделаем вызов этого метода. Для начала необходимо рассчитать селектор метода getA(). Как я писал выше, это будет kecccak256 хэш "getA()".
Для удобства можно сделать это опять же с использованием Nethereum:
C#:
var deserialize = new ABIJsonDeserialiser();
var abiFunctions = deserialize.DeserialiseContract(abi).Functions;
var hash = abiFunctions.Where(n => n.Name == functionName)
.Select(f => f.Sha3Signature).First();
Т.к. никаких входящих данных нет, то эти самые данные будут состоять только из хэша сигнатуры метода:
data = 0xd46300fd
и на выходе получим ожидаемый результат 0x0000000000000000000000000000000000000000000000000000000000000001
2.1 Read Contract.
Теперь можно применить полученные знания на практике для реальных контрактов.
Возьмём контракт 0x23567C7299702018B133ad63cE28685788ff3f67 и будем взаимодействовать с функцией "offers". Этот же запрос можно сделать в обозревателе блоков BscScan, но для автоматизации он не подходит в силу ограничения по количеству запросов.
Данная функция принимает единственное значение id, и возвращает информацию по NFT:
Сделаем всё то же самое из кода. Для сопоставления типов данных нам нужен ABI данного контракта, берём его из обозревателя блоков для данного контракта в подразделе "Code":
Метод чтения контракта будет выглядеть слудующим образом:
C#:
public async Task<string> ReadContract(string contractAddress, string abi, string functionName, int parameter)
{
var web3 = new Web3(jsonRpc);
web3.TransactionManager.UseLegacyAsDefault = true;
var contract = web3.Eth.GetContract(abi, contractAddress);
var function = contract.GetFunction(functionName);
string param = parameter.ToString("X");
var data = function.GetData(param);
var call = new CallInput(data, contractAddress);
var result = await function.CallAsync(call);
return result;
}
Передаём параметры (адрес контракта, abi, имя функции, параметр). Функция возвращает набор байт, которые необходимо преобразовать в нужный тип данных:
Код:
0x0000000000000000000000000000000000000000000000000000000000013548000000000000000000000000000000000000000000000002aef353bcddd60000000000000000000000000000965f527d9159dce6288a2219db51fc6eef120dd1000000000000000000000000d4220b0b196824c2f548a34c47d81737b0f6b5d6000000000000000000000000d87e639772276daa89a2c85f5bdd29021e72b1f2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000
Как мы уже знаем, данные представлены в блоках по 32 байта, для наглядности разобьём их по этим блокам и пропишем соответствие типам в порядке следования:
Код:
[0]: 0000000000000000000000000000000000000000000000000000000000013548 tokenId [uint256]
[1]: 000000000000000000000000000000000000000000000002aef353bcddd60000 price [uint256]
[2]: 000000000000000000000000965f527d9159dce6288a2219db51fc6eef120dd1 dealToken [address]
[3]: 000000000000000000000000d4220b0b196824c2f548a34c47d81737b0f6b5d6 nft [address]
[4]: 000000000000000000000000d87e639772276daa89a2c85f5bdd29021e72b1f2 user [address]
[5]: 0000000000000000000000000000000000000000000000000000000000000000 acceptUser [address]
[6]: 0000000000000000000000000000000000000000000000000000000000000002 status [uint8]
[7]: 0000000000000000000000000000000000000000000000000000000000000000 side [uint8]
После преобразования из Hex в Dec получаем нужные данные:
Код:
tokenId: 79176
price: 49500000000000000000
dealToken: 0x965F527D9159dCe6288a2219DB51fc6Eef120dD1
nft: 0xD4220B0B196824C2F548a34C47D81737b0F6B5D6
user: 0xd87e639772276dAa89A2c85F5bdD29021e72b1F2
acceptUser: 0x0000000000000000000000000000000000000000
status: 2
side: 0
2.2 Write Contract.
Напишем взаимодействие с функцией обмена токенов Pancakeswap. Для начала нам нужно собрать все необходимые для этого данные. Вручную инициируем обмен WBNB на BUSD. Во всплывающем окне метамаска скопируем адрес контракта, название функции и шестнадцатеричные данные:
Адрес контракта: 0x10ED43C718714eb63d5aA57B78B54704E256024E
Функция: SwapExactTokensForTokens
Параметры:
"amountIn": "uint256" - количество токена, который отдаем
"amountOutMin": "uint256" - на бирже задается в процентах, передается в виде минимального количества токенов, которые мы согласны получить
"path": "address[]" - адреса двух наших токенов (плюс тут ещё указаны промежуточные)
"to": "address" - наш кошелёк
"deadline": "uint256" - время выполнения
Шестрадцатеричные данные:
Код:
0x38ed173900000000000000000000000000000000000000000000000000a2d46a79ab0800000000000000000000000000000000000000000000000000bd07c2735b4f74db00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000075d68823ec5ad0b7e135960d7f8a23a400000000000000000000000000000000000000000000000000000000627ed3ee0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d00000000000000000000000023396cf899ca06c4472205fc903bdb4de249d6fc000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d56
Проверяем на bscscan - есть такое:
Рассмотрим передаваемые данные, разбив их на куски по 32 байта, причём первые 4 байта это селектор функции, к которой необходимо обратиться.
Код:
MethodID: 0x38ed1739
[0]: 00000000000000000000000000000000000000000000000000a2d46a79ab0800 amountIn
[1]: 000000000000000000000000000000000000000000000000bd07c2735b4f74db amountOutMin
[2]: 00000000000000000000000000000000000000000000000000000000000000a0 path - адрес начала массива (5ая строка)
[3]: 0000000000000000000000000000000075d68823ec5ad0b7e135960d7f8a23a4 to
[4]: 00000000000000000000000000000000000000000000000000000000627ed3ee deadline (unixtime)
[5]: 0000000000000000000000000000000000000000000000000000000000000004 начало массива пути к токенам (кол-во элементов)
[6]: 000000000000000000000000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c path1 - адрес токена WBNB
[7]: 0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d path2 - адрес токена USDC
[8]: 00000000000000000000000023396cf899ca06c4472205fc903bdb4de249d6fc path3 - адрес токена UST
[9]: 000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d56 path4 - адрес токена BUSD
Обратите внимание, как кодируются массивы. В том блоке, где должен располагаться массив пишется его адрес начала (смещение), и следом передаются все последующие данные. После этого начинается массив, причём в первой строке указыватеся количество элементов, а со следующего блока сами данные массива.
Теперь, чтобы работать со своими данными без использования метамаска, нужно просто подставить нужные значения, преобразовать их в соответствии с типом данных и сделать транзакцию (рассмотренную в первой части), где в поле data передать сформированные нами данные. В результате получим хэш нашей транзакции.
Мною были опущены некоторые самые базовые понятия при работе с блокчейном, например то, что касается газа. Надеюсь, что полученная информация поможет в реализации и автоматизации ваших проектов.
В приложении находятся 3 шаблона, в которых реализованы вышеописанные действия по транзакциям, чтению и записи контрактов. В папке dll находятся библиотеки, которые необходимо переместить в ExternalAssemblies вашей версии ZennoPoster.
- Номер конкурса статей
- Семнадцатый конкурс статей
- Тема статьи
- Другое







как и где можно использовать?