Использование вызова делегата высокого уровня в обновляемых контрактах, начиная с Византии.

Начиная с Byzantium, мы можем реализовать обновляемые прокси-контракты намного проще с помощью инструкций по returndatacopyсборке returndatasize. Это означает, что нам больше не нужно регистрировать возвращаемые типы и размеры, как при использовании EtherRouter .

Самый надежный способ структурировать прокси-контракт, который мы знаем, — это прокси-сервер Zeppelin , где он создается delegatecallна ассемблере. Тем не менее, похоже, что это также работает при выполнении delegatecallвызова Solidity высокого уровня, где резервная функция прокси-контракта вместо этого выглядит следующим образом:

function () public {

    bool callSuccess = upgradableContractAddress.delegatecall(msg.data);

    if (callSuccess) {
        assembly {
            returndatacopy(0x0, 0x0, returndatasize)
            return(0x0, returndatasize)
        }
    } else {
        revert();
    }
}

Этот подход (см . весь прокси здесь ) немного более лаконичен и требует меньше знаний ассемблера для понимания. Мои минимальные тесты для этого подхода, кажется, работают.

Итак, в каких ситуациях этот высокоуровневый подход не будет работать?

И если их нет, насколько вероятно, что скомпилированный байт-код для высокоуровневого вызова делегата будет меняться между версиями Solidity, нарушая этот подход для этих версий?

Возможно, вы захотите внести свой вклад в обсуждение на github.com/ethereum/EIPs/pull/897.
спасибо за этот отличный пример кода. У меня нет ответа на ваш вопрос, но вы не могли бы мне помочь? Я хотел бы использовать этот метод, но меня интересует только одна конкретная функция. При использовании delegatecall я бы указал функцию явно или она будет включена в msg.data? если я сделаю это явно, будет ли это выглядеть так? gist.github.com/okwme/a86e32a843bc15453002c5a0229da021
Похоже, Антон Буков добавил поддержку высокоуровневого вызова делегата в Address.sol OpenZeppelin .

Ответы (2)

Одна проблема заключается в том, что вы копируете свои данные в память, начиная с адреса 0. Это будет работать для возвращаемых размеров менее 64 байт, но в этот момент начнется перезапись другой памяти.

Вместо этого вы должны сделать что-то вроде

let m := mload(0x40)
returndatacopy(m, 0, returndatasize)
return(0, returndatasize)
Почему код @willjgriff перестает работать, если размер возвращаемого значения превышает 64 байта?
Это потому, что он переопределит указатель свободной памяти?
Да, и дальнейшая запись уничтожит любую другую выделенную память. См. docs.soliditylang.org/en/v0.8.7/internals/layout_in_memory.html .

Чтобы смягчить проблему, упомянутую @Tjaden Hess, сделайте то , что делает OpenZeppelin:

function functionDelegateCall(
    address target,
    bytes memory data,
    string memory errorMessage
) internal returns (bytes memory) {
    (bool success, bytes memory returndata) = target.delegatecall(data);
    if (success) {
        return returndata;
    } else {
        if (returndata.length > 0) {
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }

Если вызов не завершается ошибкой, верните данные, как обычно в Solidity. В противном случае выполните возврат через сборку, чтобы указать причину возврата.

Совет для профессионалов: посмотрите мою реализацию этого в PRBProxy .