Как избежать использования tx.origin, когда он нам действительно нужен?

Было рекомендовано избегать использования tx.origin.

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


Предположим, у нас есть два смарт-контракта: Sc1 и Sc2, оба из которых созданы одним и тем же владельцем . Сначала он развертывает Sc1, который поддерживает баланс токенов. Затем владелец создает еще один смарт-контракт Sc2, развертывает его и позволяет пользователям взаимодействовать с Sc2 и передавать некоторые свои токены из Sc1 в Sc2 (предположим, что у некоторых пользователей есть токены в Sc1). Но только владельцы токенов должны иметь возможность сделать это, и никто другой (даже владелец контракта ).

Как вы можете видеть ниже, я должен использовать tx.origin. Я знаю, что если владелец контракта создаст другой недействительный контракт и заставит пользователя (владельца токена) вызвать его, то он сможет уменьшить свой token_bsalance в Sc1. Ниже приведен код для вышеуказанного случая (атака исключена).

   contract Sc1{
      mapping (address => uint) token_balance;
      mapping (address => bool) valid_caller_contracts;// keeps track 
                                                      //of those contract that can call this contract
      address owner;
      function Sc1(){
        owner = msg.sender;
      }
       // only owner must be able to register. 
      function register_valid_contract() external {
          require(tx.origin==owner);
          valid_caller_contracts[msg.sender] = true;
      }
      function decrement_token(uint val) external{
        require(token_balance[tx.origin] >= val);
        token_balance[tx.origin] -=val;
      }
   }

   contract Sc2{
     mapping (address => uint) token_balance2;
     address owner;
     function SC2(){
        owner = msg.sender;
      }
      function register() external{
       Sc1 c1=Sc1(0x22);//Let's assum the address of Sc1 after it's 
                        //deployed is 0x22
       c1.register_valid_contract();
      }
      // only those who have tokens in Sc1 must be able to tranfer 
      //tokens to Sc2.
      function fetch_tokens(uint val) external{
       Sc1 c1=Sc1(0x22);//Let's assum the address of Sc1 after it's 
                        //deployed is 0x22
      c1.decrement_token(val);
      token_balance2[msg.sender] += val;
      }
    }

Вопрос : Можем ли мы в приведенном выше случае избежать использования tx.origin?

Ответы (1)

Серьезная проблема для контракта в зависимости от заключается tx.originв том, что ваш контракт не будет корректно работать с кошельками с мультиподписью (или с любым другим смарт-контрактом).

Решение проблемы с register_valid_contractзаключается в том, чтобы передать адрес контракта в качестве параметра, а владелец должен вызвать функцию в качестве дополнительного шага.

function register_valid_contract(address sc2) external {
    require(msg.sender == owner);
    valid_caller_contracts[sc2] = true;
}

Другой подход заключается в том, что SC1 использует заводские шаблоны для создания SC2 и регистрирует их сразу при создании.


Поскольку decrement_tokenвы можете передать msg.sender в качестве параметра, но вы должны убедиться, что отправитель является действительным контрактом.

function decrement_token(address from, uint val) external{
    require(valid_caller_contracts[msg.sender]);
    require(token_balance[from] >= val);
    token_balance[from] -=val;
}

В этом примере, если вы определяете доверительные отношения между SC1 и SC2, например, если SC1 создает SC2, тогда SC1 может проверить, что адрес отправителя — SC2, и верить, что параметры будут правильными.

Большое спасибо за ответ. Просто хотел упомянуть, что в предложенном вами решении (хотя оно и правильное) стороны должны взаимодействовать напрямую с SC1, а мне было интересно, могут ли они сделать это через SC2. Еще раз спасибо!
Проблема в том, что вам нужно вызвать SC1 из SC2, а SC1 ничего не знает о SC2 (это может быть злоумышленник). Одним из решений является то, что владелец отправляет подпись с сообщением, SC1 может восстановить адрес с помощью ECRECOVER и подтвердить, что он принадлежит владельцу. Другое решение состоит в том, чтобы третий контракт создавал как SC1, так и SC2 и регистрировал их взаимно при создании.
Спасибо за ваш комментарий. Что касается первого решения (если вы имели в виду, что отправитель отправляет сообщение и его подпись в SC2, то и SC1, и SC2 проверяют подпись), вы не думаете, что будет повторная атака? Потому что сообщение отправителя и подпись можно скопировать и отправить из другого контракта в SC1. В этом случае SC1 всегда их проверяет.
Вы можете включить в подпись дополнительную информацию, адрес SC2, метку времени, чтобы SC1 мог убедиться, что SC2 является вызывающим абонентом, а дата подписания близка к транзакции.