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.