uint256 percentage = 30;
function splitAmountToOwnerAndSeller(uint256 amount) internal
view returns (uint256 amountForSender, uint256 amountForOwner)
{ uint256 ownerPercentage = percentage;
amountForSender = (amount * (100 - ownerPercentage)) / 100;
amountForOwner = (amount * ownerPercentage) / 100;
}
(1) Спасибо пользователю CPlusPlusDeveloper с Reddit за замечание, что это не совсем так. С момента внедрения EIP-2929 первая операция SLOAD стоит 2100 газа, но после первого чтения данные кэшируются и считаются "тёплыми", поэтому повторное чтение стоит уже 100 газа. Тем не менее, всё равно выгодно загружать и считывать такую переменную из памяти, особенно если её читают более двух раз.
Это особенно важно при работе с циклами, в которых регулярно происходит чтение из состояния (state). Всегда приводите переменные состояния к memory перед входом в цикл.
Примечания:
Эта функция вычисляет, сколько эфира должно быть отправлено владельцу и продавцу товара. Владелец получает процент, заданный переменной percentage, хранящейся в хранилище (storage). Если разобрать, как это работает, то становится ясно, что переменная percentage считывается из хранилища дважды. В некоторых языках программирования это не проблема, но если понимать, как данные хранятся в блокчейне, то становится очевидным, что чтение переменной amount — это операция в оперативной памяти (memory), а чтение percentage — операция из хранилища (storage). Это разные инструкции на уровне ассемблера.
Рассмотрим пример:
Solidity — это компилируемый язык, в котором каждая операция преобразуется в низкоуровневую инструкцию (opcode), понятную и исполняемую виртуальной машиной Ethereum (EVM). Все операции вашего кода выполняются на каждом компьютере сети, поэтому за каждую операцию взимается "газ", чтобы предотвратить спам и, что ещё важнее, бесконечные циклы. В Solidity понимание машинных операций и их стоимости буквально позволяет экономить деньги.
Итак, код будет выглядеть следующим образом:
Поскольку вы выполняете это дважды, вы потратите 1600 газа только на чтение этой переменной. Чтобы избежать таких затрат, можно сначала сохранить значение из хранилища в оперативную память (memory), а затем считывать его оттуда — это гораздо дешевле (около 3 газа). То есть можно один раз считать из хранилища и записать в память (SLOAD + MSTORE) — это 803 газа, а затем дважды считать из памяти (MLOAD + MLOAD) — ещё 6 газа. В итоге транзакция обойдётся почти вдвое дешевле — около 809 газа вместо 1600.
Каждый раз, когда вы читаете переменную percentage, вы получаете данные из базы данных блокчейна (то есть из сети компьютеров, где каждый должен подтвердить эти данные). Это осуществляется с помощью инструкции SLOAD, которая, согласно Ethereum Yellow Paper, стоит 800 газа за выполнение.
Ассемблер EVM
Если вы знакомы с таким языком, как JavaScript, то, как правило, не задумываетесь о том, как хранятся переменные, за исключением их области видимости. Но при создании программ для распределённой системы, такой как блокчейн, приходится мыслить иначе.
Разработка на блокчейне
uint256 percentage = 30;
function splitAmountToOwnerAndSeller(uint256 amount)
internal
view
returns (uint256 amountForSender, uint256 amountForOwner)
{
amountForSender = (amount * (100 - percentage)) / 100;
amountForOwner = (amount * percentage) / 100;
}