Value и Data в транзакции
Основная "полезная нагрузка" транзакции содержится в двух полях: value и data. Транзакции могут иметь как value, так и data, только value, только data или ни value, ни data. Все четыре комбинации являются допустимыми.
- Транзакция содержащая только value - это платеж.
- Транзакция содержащая только data - это вызов.
- Транзакция содержащая и value, и data является и платежом и вызовом.
- Транзакция, не содержащая ни vale, ни data - вероятно просто пустая трата газа.
Давайте попробуем все эти комбинации. Сначала мы зададим адреса источника и назначения из нашего кошелька, просто чтобы демонстрацию было легче читать:
src = web3.eth.accounts[0];
dst = web3.eth.accounts[1];
Наша первая транзакция содержит только value (платеж) и не содержит никакой полезной нагрузки в виде data:
web3.eth.sendTransaction({from: src, to: dst, value: web3.utils.toWei(0.01, "ether"), data: ""});
Наш кошелек показывает экран подтверждения с указанием value для отправки, но без data.
В следующем примере указывается как value, так и полезная нагрузка data:
web3.eth.sendTransaction({from: src, to: dst, value: web3.utils.toWei(0.01, "ether"), data: "0x1234"});
Наш кошелек показывает экран подтверждения с указанием value для отправки, а также data.
Следующая транзакция включает data, но указывает value как ноль:
web3.eth.sendTransaction({from: src, to: dst, value: 0, data: "0x1234"});
Наш кошелек показывает экран подтверждения с указанием нулевой value и data.
Наконец, последняя транзакция не содержит ни value для отправки, ни полезной нагрузки в виде data:
web3.eth.sendTransaction({from: src, to: dst, value: 0, data: ""}));
Передача стоимости в EOA и контракты
Когда вы создаете транзакцию Ethereum, содержащую value, это эквивалент платежа. Такие транзакции ведут себя по-разному в зависимости от того, является ли адрес назначения контрактом или нет.
Для адресов EOA, а точнее для любого адреса, который не отмечен на блокчейне как контракт, Ethereum зафиксирует изменение состояния, добавив отправленное вами value к balance адреса. Если адрес не был замечен ранее, он будет добавлен во внутреннее представление состояния клиента, а его balance будет инициализирован значением вашего платежа.
Если адрес назначения (to) является контрактом, то EVM выполнит контракт и попытается вызвать функцию, названную в полезной нагрузке data вашей транзакции. Если в вашей транзакции нет data, EVM вызовет fallback функцию и, если эта функция является payable, выполнит ее, чтобы определить, что делать дальше. Если в функции fallback нет кода, то эффектом от транзакции будет увеличение balance контракта, в точности как при платеже на кошелек. Если falback функции нет или она не payable, то транзакция будет отменена.
Контракт может отклонять входящие платежи, выбрасывая исключение непосредственно при вызове функции или в соответствии с условиями, закодированными в функции. Если функция завершается успешно, то состояние контракта обновляется, чтобы отразить увеличение баланса ether контракта.
Передача полезной нагрзуки в EOA или контракт
Когда ваша транзакция содержит data, то она, скорее всего, адресована на адрес контракта. Это не означает, что вы не можете отправить data на EOA - это вполне допустимо в протоколе Ethereum. Однако в этом случае интерпретация данных зависит от кошелька, который вы используете для доступа к EOA. Протокол Ethereum их игнорирует. Большинство кошельков также игнорируют любые данные, полученные при транзакции к EOA, которую они контролируют. В будущем, возможно, появятся стандарты, которые позволят кошелькам интерпретировать данные так же, как это делают контракты, что позволит транзакциям вызывать функции, работающие внутри пользовательских кошельков. Критическое различие заключается в том, что любая интерпретация полезной нагрузки данных со стороны EOA не подчиняется правилам консенсуса Ethereum, в отличие от исполнения контракта.
Пока предположим, что ваша транзакция доставляет данные по адресу контракта. В этом случае данные будут интерпретированы EVM как вызов контракта. Большинство контрактов используют эти данные более конкретно, как вызов функции, вызывая именованную функцию и передавая ей любые закодированные аргументы.
Полезная нагрузка data, отправляемая в ABI-совместимый контракт (можно предположить, что все контракты являются таковыми), представляет собой шестнадцатеричную кодировку:
Селектор функций
Первые 4 байта хэша Keccak-256 интерфейса функции. Это позволяет контракту однозначно определить, какую функцию вы хотите вызвать.
Аргументы функции
Аргументы функции, закодированные в соответствии с правилами для различных элементарных типов, определенных в спецификации ABI.
В [solidity_faucet_example] мы определили функцию для снятия денег:
function withdraw(uint withdraw_amount) public {
Интерфейс функции определяется как строка, содержащая имя функции, за которым следуют типы данных каждого из ее аргументов, заключенные в круглые скобки и разделенные запятыми. Имя функции здесь - withdraw, и она принимает один аргумент, который является uint (это псевдоним uint256), поэтому интерфейсом функции withdraw будет:
withdraw(uint256)
Давайте вычислим хэш Keccak-256 этой строки:
> web3.utils.sha3("withdraw(uint256)");
'0x2e1a7d4d13322e7b96f9a57413e1525c250fb7a9021cf91d1540d5b69f16a49f'
Первые 4 байта хэша - 0x2e1a7d4d. Это наше значение "function selector", которое сообщит контракту, какую функцию мы хотим вызвать.
Далее вычислим значение, которое будет передано в качестве аргумента withdraw_amount. Мы хотим снять 0.01 ether. Закодируем это в шестнадцатеричную последовательность big-endian беззнакового 256-битного целого числа, обозначаемого в wei:
> withdraw_amount = web3.utils.toWei(0.01, "ether");
'10000000000000000'
> withdraw_amount_hex = web3.utils.toHex(withdraw_amount);
'0x2386f26fc10000'
Теперь мы добавляем селектор функции к сумме (заполненной до 32 байт):
2e1a7d4d000000000000000000000000000000000000000000000000002386f26fc10000
Это data для нашей транзакции, вызывающей функцию withdraw и запрашивающей 0.01 ether в качестве суммы withdraw_amount.