multiDelegatecall - demonoved/A-trial-run-in-solidity GitHub Wiki

// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0
pragma solidity ^0.8.23;
//пример вызова нескольких функций с помощью одной транзакции
contract MultiDelegatecall{
    error DelegatecallFailed();
    function multiDelegatecall(
        bytes[] memory data
    ) external payable returns (bytes[] memory results){
        results = new bytes[] (data.length);
        for (uint i; i < data.length; i++){
            (bool ok, bytes memory res) = address(this).delegatecall(data[i]);
            if (!ok){
                revert DelegatecallFailed();
            }
            results[i] = res;
        }
    }
}
//alice -> multicall --- call ---> test(msg.sender = multicall)
//alice -> test --- delegatecall ---> test(msg.sender = alice)
contract TestMultiDelegatecall is MultiDelegatecall{
    event Log(address caller, string func, uint i);
    function func1(uint x, uint y) external{
        //msg.sender = alice
        emit Log(msg.sender, "func1", x + y);
    }
    function func2() external returns (uint){
        //msg.sender = alice
        emit Log(msg.sender, "func2", 2);
        return 111;
    }
    mapping (address => uint) public balanceOf;
    //Не безопасный код при использовании в сочетании с multidelegatecall
    //пользователь может минтить несколько раз по цене msg.value
    function mint() external payable{
        balanceOf[msg.sender] += msg.value;
    }
}
contract Helper {
    function getFunc1Data(uint x, uint y) external pure returns (bytes memory){
        return abi.encodeWithSelector(TestMultiDelegatecall.func1.selector, x, y);
    }
    function getFunc2Data()external pure returns (bytes memory){
        return abi.encodeWithSelector(TestMultiDelegatecall.func2.selector);
    }
    function getMintData() external pure returns (bytes memory){
        return abi.encodeWithSelector(TestMultiDelegatecall.mint.selector);
    }
}

Данный код, написанный на языке программирования Solidity для создания смарт-контрактов на блокчейне Ethereum, показывает реализацию паттерна, который позволяет вызывать несколько функций в одной транзакции. Вот разбор основных компонентов и их предназначение:

  1. MultiDelegatecall: Это базовый контракт, который имеет функцию multiDelegatecall, позволяющую выполнить несколько действий (функций), каждое из которых передается как элемент массива байт data. Он использует delegatecall для вызова каждой функции, которая должна быть уже определена в контракте (или его сабконтрактах). Используется в механизме управления контрактом, позволяя агрегировать вызовы и экономить на транзакциях.

  2. DelegatecallFailed: Это пользовательская ошибка, которая используется внутри multiDelegatecall для обработки ситуации, когда delegatecall не выполняется успешно.

  3. TestMultiDelegatecall: Это производный контракт, который наследует MultiDelegatecall и добавляет логику для трех функций: func1, func2, и mint.

    • func1 и func2 просто логируют вызов, указывая msg.sender (адрес, который инициировал транзакцию) и другие параметры вызова.
    • mint увеличивает баланс отправителя на msg.value, количество эфирных монет, отправленных вместе с вызовом функции. Это указывает на возможную уязвимость, поскольку с помощью multiDelegatecall этот контракт можно вызывать несколько раз за одну транзакцию, позволяя одному пользователю многократно увеличить баланс только за один раз отправив оплату (msg.value).
  4. Helper: Это вспомогательный контракт, который предоставляет удобный способ для генерации данных, необходимых для вызова функций func1, func2 и mint через multiDelegatecall. Функции getFunc1Data, getFunc2Data и getMintData возвращают закодированные для вызова данные, соответствующие селекторам функций (идентификаторам).

Использование delegatecall означает, что вызываемые функции (func1, func2 и mint) будут выполняться с контекстом состояния вызывающего контракта (TestMultiDelegatecall), но с сохранением msg.sender и msg.value из первоначального вызова, что позволяет тонко управлять правами и логикой выполнения функций в дочерних и родительских контрактах.

Такая техника может использоваться для различных целей, начиная от простого батчинга операций для экономии на газе (плата за транзакции в сети Ethereum) и заканчивая более сложными механизмами управления и рефакторинга логики смарт-контрактов. Однако она также увеличивает сложность контракта и потенциальные уязвимости, особенно в контексте безопасности делегированных вызовов.