Loading...
Loading...
abi.encodeWithSignature, которая почти всегда используется вместе с функцией call.abi.encodeWithSignature делает всё за вас!abi.encodeWithSignature часто применяется совместно с функцией call для выполнения низкоуровневых вызовов функций других контрактов.// Общий шаблон:bytes memory calldata = abi.encodeWithSignature("functionName(types)", parameters);(bool success, bytes memory returnData) = targetContract.call(calldata);contract AlexFunctionCalls {
// Базовый пример: вызов функции transfer токена function demonstrateSignature() public pure returns (bytes memory) { // Кодируем вызов функции по её сигнатуре bytes memory encoded = abi.encodeWithSignature( "transfer(address,uint256)", // Сигнатура функции (БЕЗ пробелов!) 0x1234567890123456789012345678901234567890, // Адрес получателя 1000 // Количество токенов );
// Результат содержит: // Первые 4 байта: 0xa9059cbb (селектор функции transfer) // Остальные байты: закодированные параметры
return encoded; }
// ✅ Практический пример: реальный вызов функции токена с call function callTokenTransfer(address tokenContract, address to, uint256 amount) external returns (bool success) {
// ШАГ 1: Создаем calldata с помощью abi.encodeWithSignature bytes memory calldata = abi.encodeWithSignature( "transfer(address,uint256)", to, amount );
// ШАГ 2: Отправляем calldata в контракт токена через call (success, ) = tokenContract.call(calldata);
// ШАГ 3: Проверяем успешность выполнения require(success, "Token transfer failed");
return success; }
// ✅ Пример с обработкой возвращаемых данных function callTokenBalanceOf(address tokenContract, address user) external view returns (uint256 balance) {
// ШАГ 1: Кодируем вызов функции balanceOf bytes memory calldata = abi.encodeWithSignature( "balanceOf(address)", user );
// ШАГ 2: Вызываем функцию чтения (используем staticcall для view функций) (bool success, bytes memory returnData) = tokenContract.staticcall(calldata);
require(success, "Balance check failed");
// ШАГ 3: Декодируем возвращенные данные balance = abi.decode(returnData, (uint256));
return balance; }
// ✅ Пример с множественными параметрами function callTokenApprove(address tokenContract, address spender, uint256 amount) external returns (bool success) {
// Создаем calldata для функции approve bytes memory calldata = abi.encodeWithSignature( "approve(address,uint256)", spender, amount );
// Выполняем вызов (success, ) = tokenContract.call(calldata); require(success, "Approve failed");
return success; }
// ✅ Более сложный пример с массивами и call function callBatchTransfer( address tokenContract, address[] memory recipients, uint256[] memory amounts ) external returns (bool success) {
// ШАГ 1: Кодируем вызов функции с массивами bytes memory calldata = abi.encodeWithSignature( "batchTransfer(address[],uint256[])", // Функция массовой отправки recipients, // Массив получателей amounts // Массив сумм );
// ШАГ 2: Выполняем вызов (success, ) = tokenContract.call(calldata); require(success, "Batch transfer failed");
return success; }
// ✅ Демонстрация для понимания (только кодирование без call) function demonstrateEncoding() public pure returns (bytes memory) { // Этот пример показывает только создание calldata // В реальности этот результат будет использован с call return abi.encodeWithSignature( "transfer(address,uint256)", 0x1234567890123456789012345678901234567890, 1000 ); }
// Пример с разными типами параметров function encodeComplexTypes() public pure returns (bytes memory) { return abi.encodeWithSignature( "createUser(string,uint256,bool,bytes32)", "Alex", // string 25, // uint256 true, // bool keccak256("some_data") // bytes32 ); }}abi.encodeWithSignature важно понимать, какой тип call функции использовать:// Для функций, которые изменяют состояние (transfer, approve, mint, etc.)function callStateChangingFunction(address target) external { bytes memory calldata = abi.encodeWithSignature( "transfer(address,uint256)", msg.sender, 100 );
(bool success, bytes memory returnData) = target.call(calldata); require(success, "Call failed");
// Если функция возвращает данные, их можно декодировать: bool result = abi.decode(returnData, (bool));}// Для view/pure функций (balanceOf, name, symbol, etc.)function callViewFunction(address target, address user) external view returns (uint256) { bytes memory calldata = abi.encodeWithSignature( "balanceOf(address)", user );
(bool success, bytes memory returnData) = target.staticcall(calldata); require(success, "Static call failed");
return abi.decode(returnData, (uint256));}// Используется в proxy контрактах и библиотекахfunction callWithDelegateCall(address implementation) external { bytes memory calldata = abi.encodeWithSignature( "upgradeTo(address)", implementation );
(bool success, ) = implementation.delegatecall(calldata); require(success, "Delegate call failed");}Тип call | Когда использовать | Изменения состояния | Контекст выполнения |
|---|---|---|---|
call | Функции, изменяющие состояние целевого контракта | ✅ Разрешены | Целевой контракт |
staticcall | view/pure функции, только чтение данных | ❌ Запрещены | Целевой контракт |
delegatecall | Выполнение кода в контексте вызывающего контракта (proxy/libs) | ✅ В своем контракте | Текущий контракт |
contract PracticalCallExamples {
// ✅ Правильно: call для функций изменения состояния function transferTokens(address token, address to, uint256 amount) external { bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", to, amount); (bool success, ) = token.call(data); require(success, "Transfer failed"); }
// ✅ Правильно: staticcall для view функций function getTokenBalance(address token, address user) external view returns (uint256) { bytes memory data = abi.encodeWithSignature("balanceOf(address)", user); (bool success, bytes memory result) = token.staticcall(data); require(success, "Balance check failed"); return abi.decode(result, (uint256)); }
// ❌ Неправильно: staticcall для функций изменения состояния function wrongExample(address token, address to, uint256 amount) external view { bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", to, amount); // Этот вызов провалится, так как transfer изменяет состояние, а staticcall это запрещает token.staticcall(data); // ОШИБКА! }}keccak256("transfer(address,uint256)")[:4]contract CommonMistakes { function wrongSignatures() public pure { // ❌ НЕПРАВИЛЬНО: есть пробелы в сигнатуре // abi.encodeWithSignature("transfer(address, uint256)", to, amount);
// ❌ НЕПРАВИЛЬНО: неточные типы // abi.encodeWithSignature("transfer(address,uint)", to, amount); // uint вместо uint256
// ❌ НЕПРАВИЛЬНО: имена параметров в сигнатуре // abi.encodeWithSignature("transfer(address to, uint256 amount)", to, amount);
// ✅ ПРАВИЛЬНО: точная сигнатура без пробелов abi.encodeWithSignature("transfer(address,uint256)", address(0), 100); }}Результат abi.encodeWithSignature("transfer(address,uint256)", recipient, 1000):
┌─────────────┬──────────────────────────────────────────────────────────┐│ 4 байта │ Остальные байты (кратно 32) │├─────────────┼──────────────────────────────────────────────────────────┤│ 0xa9059cbb │ 000...000[recipient_address]000...0003e8 ││ (селектор) │ (параметры: address + uint256) │└─────────────┴──────────────────────────────────────────────────────────┘.call() вместо прямого вызоваtransfer(address,uint256), а хеш сигнатуры будет keccak256("transfer(address,uint256)") = 0xa9059cbb12345678.... Первые 4 байта этого хеша – 0xa9059cbb и будет селектор функции.function transferFrom( address sender, address recipient, uint256 amount) public virtual override returns (bool) { // логика функции... return true;}sender, recipient, amount)public)virtual, override)returns (bool))transferFromaddress, address, uint256transferFrom(address,address,uint256)// В Solidity:bytes4 calculated = bytes4(keccak256("transferFrom(address,address,uint256)"));// Результат: 0x23b872ddПолный хеш: 0x23b872dd7302113369cda2901243429419bec145408fa8b352b3dd92b66c680bСелектор: 0x23b872ddcontract SelectorExample { function getTransferFromSelector() public pure returns (bytes4) { return bytes4(keccak256("transferFrom(address,address,uint256)")); // Вернет: 0x23b872dd }
function checkSelector() public pure returns (bool) { // Проверяем, что наш расчет правильный bytes4 calculated = bytes4(keccak256("transferFrom(address,address,uint256)")); bytes4 builtin = this.transferFrom.selector; return calculated == builtin; // true }}❌ Неправильно | ✅ Правильно | Пояснение |
|---|---|---|
transferFrom(address sender, address recipient, uint256 amount) | transferFrom(address,address,uint256) | Не должно быть имен параметров |
transferFrom (address,address,uint256) | transferFrom(address,address,uint256) | Нет пробела после имени функции |
transferFrom(address, address, uint256) | transferFrom(address,address,uint256) | Нет пробелов после запятых |
transferFrom(address,address,uint) | transferFrom(address,address,uint256) | uint должен быть uint256 |
public transferFrom(address,address,uint256) | transferFrom(address,address,uint256) | Без модификаторов видимости |
Функция | Сигнатура функции | Хеш сигнатуры (keccak256) | Селектор (первые 4 байта) |
|---|---|---|---|
transfer(address,uint256) | "transfer(address,uint256)" | 0xa9059cbb12345678... | 0xa9059cbb |
approve(address,uint256) | "approve(address,uint256)" | 0x095ea7b312345678... | 0x095ea7b3 |
balanceOf(address) | "balanceOf(address)" | 0x70a0823112345678... | 0x70a08231 |
transferFrom(address,address,uint256) | "transferFrom(address,address,uint256)" | 0x23b872dd12345678... | 0x23b872dd |
totalSupply() | "totalSupply()" | 0x18160ddd12345678... | 0x18160ddd |
name() | "name()" | 0x06fdde0312345678... | 0x06fdde03 |
symbol() | "symbol()" | 0x95d89b4112345678... | 0x95d89b41 |
decimals() | "decimals()" | 0x313ce56712345678... | 0x313ce567 |
contract TokenExample { function transfer(address to, uint256 amount) public { /* ... */ } function approve(address spender, uint256 amount) public { /* ... */ } function balanceOf(address owner) public view returns (uint256) { /* ... */ }}transfer():"transfer(address,uint256)"keccak256("transfer(address,uint256)") = 0xa9059cbb12345678...0xa9059cbb0xa9059cbb + закодированные параметрыtransfer)approve)balanceOf)0xa9059cbb, она знает — выполнить функцию transfer.transfer(address,uint256) → 0xa9059cbbapprove(address,uint256) → 0x095ea7b3balanceOf(address) → 0x70a08231transferFrom(address,address,uint256) → 0x23b872ddtransfer(address,uint256) имеет селектор 0xa9059cbb в любом ERC-20 токене.0xa9059cbb + параметры0xa9059cbbtransfer() в MetaMask, кошелек отправляет в блокчейн calldata, которая начинается с селектора 0xa9059cbb, а затем идут закодированные параметры.contract TokenManager { mapping(address => bool) public authorizedTokens;
modifier onlyAuthorizedToken(address token) { require(authorizedTokens[token], "Token not authorized"); _; }
// Универсальная функция перевода любого токена function transferTokenFor( address token, address from, address to, uint256 amount ) external onlyAuthorizedToken(token) { // Используем abi.encodeWithSignature + call для вызова transferFrom bytes memory calldata = abi.encodeWithSignature( "transferFrom(address,address,uint256)", from, to, amount );
(bool success, ) = token.call(calldata); require(success, "Transfer failed"); }
// Проверка баланса любого токена function getTokenBalance(address token, address user) external view returns (uint256) { bytes memory calldata = abi.encodeWithSignature( "balanceOf(address)", user );
(bool success, bytes memory result) = token.staticcall(calldata); require(success, "Balance check failed");
return abi.decode(result, (uint256)); }}contract DeFiRouter { // Структура для описания вызова struct CallData { address target; // Адрес контракта string signature; // Сигнатура функции bytes parameters; // Закодированные параметры }
// Батчевое выполнение множественных вызовов function executeBatch(CallData[] memory calls) external { for (uint i = 0; i < calls.length; i++) { CallData memory currentCall = calls[i];
// Создаем calldata из сигнатуры и параметров bytes memory calldata = abi.encodeWithSignature( currentCall.signature, currentCall.parameters );
(bool success, ) = currentCall.target.call(calldata); require(success, string(abi.encodePacked("Call ", i, " failed"))); } }
// Swap токенов через Uniswap-подобный DEX function swapTokens( address dexContract, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutMin ) external { // Сначала делаем approve bytes memory approveCalldata = abi.encodeWithSignature( "approve(address,uint256)", dexContract, amountIn ); (bool approveSuccess, ) = tokenIn.call(approveCalldata); require(approveSuccess, "Approve failed");
// Затем делаем swap bytes memory swapCalldata = abi.encodeWithSignature( "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)", amountIn, amountOutMin, _getPath(tokenIn, tokenOut), msg.sender, block.timestamp + 300 ); (bool swapSuccess, ) = dexContract.call(swapCalldata); require(swapSuccess, "Swap failed"); }
function _getPath(address tokenA, address tokenB) private pure returns (address[] memory) { address[] memory path = new address[](2); path[0] = tokenA; path[1] = tokenB; return path; }}contract MultiCallAggregator { struct Call { address target; bytes callData; }
struct Result { bool success; bytes returnData; }
// Агрегация множественных вызовов в одну транзакцию function aggregate(Call[] memory calls) external view returns ( uint256 blockNumber, Result[] memory results ) { blockNumber = block.number; results = new Result[](calls.length);
for (uint256 i = 0; i < calls.length; i++) { (bool success, bytes memory returnData) = calls[i].target.staticcall(calls[i].callData); results[i] = Result(success, returnData); } }
// Удобная функция для создания calldata function createCall(address target, string memory signature, bytes memory params) external pure returns (Call memory) { return Call({ target: target, callData: abi.encodeWithSignature(signature, params) }); }
// Пример использования для получения балансов нескольких токенов function getMultipleBalances( address[] memory tokens, address user ) external view returns (uint256[] memory balances) { Call[] memory calls = new Call[](tokens.length);
// Подготавливаем все вызовы for (uint i = 0; i < tokens.length; i++) { calls[i] = Call({ target: tokens[i], callData: abi.encodeWithSignature("balanceOf(address)", user) }); }
// Выполняем все вызовы одновременно (, Result[] memory results) = this.aggregate(calls);
// Декодируем результаты balances = new uint256[](tokens.length); for (uint i = 0; i < results.length; i++) { if (results[i].success) { balances[i] = abi.decode(results[i].returnData, (uint256)); } } }}

