Протокол для продаваемых смарт-контрактов

В Ethereum нет встроенной концепции владения смарт-контрактом.
Несмотря на то, что создание и развертывание смарт-контракта осуществляется учетной записью - будь то внешняя учетная запись (EOA) или другой контракт - являющейся Создатель смарт-контракта не дает учетной записи каких-либо особых привилегий по отношению к контракту, который они развернули.

В большинстве случаев использования смарт-контрактов требуется, чтобы кто-то владел контрактами. Этому «владельцу» предоставляются привилегии и обязанности в отношении смарт-контракта.

В контракте краудсейла им может быть поручено управлять всем процессом и приостанавливать краудсейл, если что-то пойдет не так.

В приложении Lottery / Ruffle Dapp им может быть поручено провести розыгрыш номеров.

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

Обычным шаблоном, используемым многими смарт-контрактами, является установка владельца учетной записи, развертывающей контракт, следующим образом:

pragma solidity 0.4.19;
contract MyContract {
  address owner;
  function MyContract(){
    owner = msg.sender;
  }
}

Затем добавляем модификатор:

modifier onlyOwner {
  require(msg.sender == owner);
  _;    
}

И, наконец, использование этого модификатора для принудительного выполнения критических операций может выполняться только владельцем контракта:

// Suicide the contract and transfer funds to the owner
// Only available to the owner, for obvious reasons.
function destroyContract() public onlyOwner {
  selfdestruct(owner);
}

Проблема с изменением прав собственности на договор

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

  • Человек, разместивший контракт, сделал это от имени другого лица.
    Разработчик или консультант, выполняющий контрактную работу для компании.
  • Компания хочет ликвидировать / продать свои активы, в том числе смарт-контракты
    , которые могут иметь или не иметь эфирный баланс.
  • Владелец смарт-контракта хочет отдать его, подарить или просто перевернуть с целью получения прибыли

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

function changeOwner(address _newOwner)public onlyOwner {
  ownerCandidate = _newOwner;
}
function acceptOwnership()public {
  require(msg.sender == ownerCandidate);  
  owner = ownerCandidate;
}

Теперь в упомянутых выше ситуациях есть несколько общих проблем, которые не решаются этими не очень широко используемыми функциями changeOwner() и acceptOwnership():

  • Как покупатель контракта может быть уверен, что после оплаты права собственности по контракту продавец фактически выполнит соответствующую changeOwner() функцию?
  • Это может случиться и наоборот. Как продавец контракта может быть уверен, что ему заплатят, если он сначала уступит право собственности?
  • Как покупатель контракта может быть уверен, что текущий владелец контракта не изменит его (ну, это данные), прежде чем уступить свое право собственности?

Протокол продаваемого контракта

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

Подробности протокола можно прочитать - и обсудить - в соответствующем EIP.

В следующих абзацах я рассмотрю пример реализации, который доступен в моем репозитории Github.

Обработка собственности

Обработка права собственности на контракт довольно проста. Как обычно, мы устанавливаем для владельца развернутого контракта значение msg.sender при инициализации:

function Sellable() public {
        owner = msg.sender;
        Transfer(now,address(0),owner,0);
    }

Затем мы добавляем модификатор onlyOwner, который будет использоваться для каждой функции, которую мы хотим сделать исполняемой только лицом, в настоящее время владеющим контрактом:

modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

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

Выставление договора на продажу

Владелец контракта может выставить его на продажу, вызвав следующую функцию:

function initiateSale(uint _price, address _to) onlyOwner public {
        require(_to != address(this) && _to != owner);
        require(!selling);
        
        selling = true;
        
        // Set the target buyer, if specified.
        sellingTo = _to;
        
        askingPrice = _price;
    }

initiateSale() принимает два параметра:

  • uint _price: цена, по которой владелец хочет продать контракт.
  • адрес _to: необязательный и соответствует тому, кому владелец хочет продать контракт.

При выставлении контракта на продажу у собственника есть два варианта: он может выбрать покупателя, в этом случае продажа была согласована заранее. Или они могут просто «объявить», что контракт выставлен на продажу, и тот, кто первым потребует его (и заплатит свою цену), получит его.

Кроме того, запрашиваемая цена может быть установлена ​​на 0. Это означает, что владельцу контракта разрешено дарить, дарить или отдавать контракт.

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

Наконец, есть функция cancelPurchase(), которая позволяет владельцу отменить продажу до того, как кто-то ее завершит.

function cancelSale() onlyOwner public {
        require(selling);
        
        // Reset sale variables
        resetSale();
    }

Покупка контракта

Как только контракт выставлен на продажу, все, что требуется для завершения продажи, - это для покупателя (если он был указан) или кого-либо (если не указан конкретный покупатель) вызвать следующую функцию:

function completeSale() public payable {
        require(selling);
        require(msg.sender != owner);
        require(msg.sender == sellingTo || sellingTo == address(0));
        require(msg.value == askingPrice);
        
        // Swap ownership
        address prevOwner = owner;
        address newOwner = msg.sender;
        uint salePrice = askingPrice;
        
        owner = newOwner;
        
        // Transaction cleanup
        resetSale();
        
        prevOwner.transfer(salePrice);
        
        Transfer(now,prevOwner,newOwner,salePrice);
    }

Функция completeSale() - это функция payable, которая требует отправки эфира. Отправляемая сумма должна быть точно такой же, которую установил владелец в качестве запрашиваемой цены.

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

Пример использования

Вот очень простой пример того, как можно использовать этот базовый контракт:

contract Kitty is Sellable {
    
    string public name;
    uint public kittyValue = 0;
    
    function Kitty(string _name) public {
        name = _name;
    }
    
    function findNewOwner() public onlyOwner {
        kittyValue = kittyValue + 1 ether;   
        super.initiateSale(kittyValue,address(0));
    }
    
    function renameKitty(string newName) ifNotLocked public onlyOwner {
        name = newName;
    }
    
    function buyKitty() public payable {
        require(msg.value == kittyValue);
        super.completeSale();
    }
}

У нас есть контракт, который представляет собой CryptoKitty 😺. Владелец может findNewOwner() выставить его на продажу. Каждый раз, когда котенок покупается, его стоимость увеличивается на 1 эфир. Владелец котенка может изменить его имя до тех пор, пока он не продается, путем реализации модификатора ifNotLocked в renameKitty.

Вот и все!

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