Перемещение структуры в массив, который содержит сам массив

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

pragma solidity ^0.4.18;

contract StructArrayInitWrong {
  struct Room {
    address[] players;       
  }  
  Room[] rooms;

  function createRoom() public {
    address[] adr;
    adr.push(msg.sender);
    Room memory room = Room(adr);   
    rooms.push(room);
  }

  function getRoomsLength() view returns (uint) {
    return rooms.length;
  }
}

Странно то, что roomsдлина массива увеличивается на два каждый раз , когда createRoomвызывается в этом контракте. Можете ли вы объяснить это поведение? Спасибо.

Ответы (1)

Проблема в этой строке

address[] adr;

Есть "предупреждение" о неинициализированном хранилище. Здесь происходит нечто большее, чем можно предположить из мягкого предупреждения. Я действительно не понимаю, почему это не серьезная ошибка, поэтому разработчик ее не принимает.

Из-за того, что хранилище не было инициализировано, компилятор не знал, куда поместить динамический массив adr[], поэтому поместил его в первый слот . Вы правильно прочитали. Это топает rooms[]. Ой!

Так как место, занятое этим слотом, также является динамическим массивом, оно также использует первое слово для описания длины массива. Таким образом, мы получаем наблюдаемое странное поведение... нажимаем на один массив, потом на другой и пуф! - оба массива имеют длину 2.

Это не единственный случай, когда хранение, объявленное внутри функций, а не в обычном месте (снаружи), приводит к потенциально катастрофическим перезаписям данных. См. здесь другой пример: Оплошность компилятора Solc? Неверное объявление сопоставления перезаписывает хранилище

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

Может быть, кто-то еще вмешается с лучшей эвристикой. Я мог бы предложить сформировать две привычки

  • Всегда размещайте переменные состояния в одном и том же месте, вверху, вне функций, чтобы избежать такого рода неожиданной перезаписи.
  • Никогда не настраивайте/устанавливайте переменную, которая ранее была прочитана из индексированного хранилища, потому что это может неожиданно перезаписать хранилище. См. здесь: http://vessenes.com/solidity-frustrations-references-and-mapping/

Вот код с одним изменением и работает как положено:

pragma solidity ^0.4.18;

contract StructArrayInitWrong {

  struct Room {
    address[] players;       
  }  
  Room[] rooms;
  address[] adr; // <=== if we're going to store this, then let's store this.

  function createRoom() public {
                                    // <=== note gaping hole
    adr.push(msg.sender);
    Room memory room = Room(adr);   
    rooms.push(room);
  }

  function getRoomsLength() public view returns (uint) {
    return rooms.length;
  }
}

Надеюсь, это поможет.

Спасибо за подробный ответ! Я не хотел хранить массив в хранилище, поэтому я изменил переменную memoryтак: address[] memory adr = new address[](1); adr[0] = msg.sender;. Кажется, это тоже работает.