Протокол для продаваемых смарт-контрактов
В 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.