Разбираем смарт-контракт Binance Coin
В этой статье рассмотрим код токена Binance Coin (BNB), принадлежащий популярной бирже Binance.
Биржа Binance создала в свое время токен Binance Coin, который со временем обрел большую популярность. Токен BNB создан в блокчейне Ethereum на Solidity версии 0.4.8. Токен соответствует стандарту ERC20, плюс имеет дополнительные функции: функции заморозки и разморозки токенов, функцию сжигания токенов, а также может хранить на своем адресе токены и эфиры и позволяет создателю выводить их. Состоит из двух контрактов-классов. Код токена можно посмотреть на Etherscan.io или ниже с комментариями.
Внимание! Информация в этой статье была валидна до тех пор, пока токен BNB существовал в блокчейне Ethereum. В настоящее время токен благополучно мигрировал в собственный блокчейн биржи Бинанс «Binance Chain» и живет только там, к Ethereum он больше никакого отношения не имеет.
/** * Смарт-контракт токена Binance Coin (BNB) */ pragma solidity ^0.4.8; /** * Безопасные математические операции */ contract SafeMath { // умножение function safeMul(uint256 a, uint256 b) internal returns (uint256) { uint256 c = a * b; assert(a == 0 || c / a == b); return c; } //деление function safeDiv(uint256 a, uint256 b) internal returns (uint256) { assert(b > 0); uint256 c = a / b; assert(a == b * c + a % b); return c; } //вычитание function safeSub(uint256 a, uint256 b) internal returns (uint256) { assert(b <= a); return a - b; } //сложение function safeAdd(uint256 a, uint256 b) internal returns (uint256) { uint256 c = a + b; assert(c>=a && c>=b); return c; } function assert(bool assertion) internal { if (!assertion) { throw; } } } //Контракт BNB наследник контракта SafeMath contract BNB is SafeMath{ string public name; string public symbol; uint8 public decimals; uint256 public totalSupply; address public owner; /* Мэппинги */ mapping (address => uint256) public balanceOf; //балансы пользователей mapping (address => uint256) public freezeOf; // мэппинг замороженных токенов mapping (address => mapping (address => uint256)) public allowance; // мэппинг делегированных токенов /* Событие при успешном выполнении функции transfer */ event Transfer(address indexed from, address indexed to, uint256 value); /* Событие при выполнении функции сжигания токенов */ event Burn(address indexed from, uint256 value); /* Событие при выполнении функции заморозки токенов */ event Freeze(address indexed from, uint256 value); /* Событие при выполнении функции разморозки токенов */ event Unfreeze(address indexed from, uint256 value); /* Конструктор */ function BNB( uint256 initialSupply, string tokenName, uint8 decimalUnits, string tokenSymbol ) { balanceOf[msg.sender] = initialSupply; // Все токены принадлежат создателю totalSupply = initialSupply; // Устанавливается общая эмиссия токенов name = tokenName; // Устанавливается имя токена symbol = tokenSymbol; // Устанавливается символ токена decimals = decimalUnits; // Кол-во нулей owner = msg.sender; // Владелец == отправитель } /* Функция для отправки токенов */ function transfer(address _to, uint256 _value) { if (_to == 0x0) throw; // Запрет на передачу на адрес 0x0. Проверка что соответствует ETH-адресу if (_value <= 0) throw; if (balanceOf[msg.sender] < _value) throw; // Проверка что у отправителя <= кол-ву токенов if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Проверка на переполнение balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);// Вычитает токены у отправителя balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);//Прибавляет токены получателю Transfer(msg.sender, _to, _value);// Запускается событие Transfer } /* Функция для одобрения делегирования токенов */ function approve(address _spender, uint256 _value) returns (bool success) { if (_value <= 0) throw; allowance[msg.sender][_spender] = _value; return true; } /* Функция для отправки делегированных токенов */ function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { if (_to == 0x0) throw; if (_value <= 0) throw; if (balanceOf[_from] < _value) throw; if (balanceOf[_to] + _value < balanceOf[_to]) throw; if (_value > allowance[_from][msg.sender]) throw; balanceOf[_from] = SafeMath.safeSub(balanceOf[_from], _value); balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value); allowance[_from][msg.sender] = SafeMath.safeSub(allowance[_from][msg.sender], _value); Transfer(_from, _to, _value); return true; } /* Функция для сжигания токенов */ function burn(uint256 _value) returns (bool success) { if (balanceOf[msg.sender] < _value) throw;//проверка что на балансе есть нужное кол-во токенов if (_value <= 0) throw; balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);// вычитание totalSupply = SafeMath.safeSub(totalSupply,_value);// Новое значение totalSupply Burn(msg.sender, _value);// Запуск события Burn return true; } /* Функция заморозки токенов */ function freeze(uint256 _value) returns (bool success) { if (balanceOf[msg.sender] < _value) throw; //проверка что на балансе есть нужное кол-во токенов if (_value <= 0) throw; balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value); // Уменьшаем в мэппинге balanceOf freezeOf[msg.sender] = SafeMath.safeAdd(freezeOf[msg.sender], _value); // Прибавляем в мэппинг freezeOf Freeze(msg.sender, _value); return true; } /* Функция разморозки токенов */ function unfreeze(uint256 _value) returns (bool success) { if (freezeOf[msg.sender] < _value) throw; if (_value <= 0) throw; freezeOf[msg.sender] = SafeMath.safeSub(freezeOf[msg.sender], _value); balanceOf[msg.sender] = SafeMath.safeAdd(balanceOf[msg.sender], _value); Unfreeze(msg.sender, _value); return true; } //Позволяет создателю выводить хранящиеся на адрес контракта Эфиры и токены function withdrawEther(uint256 amount) { if(msg.sender != owner)throw; owner.transfer(amount); } // Функция payable позвлоляет контракту принимать и хранить эфиры function() payable { } }
Контракт SafeMath содержит безопасные математические операции от OpenZeppelin. Так как тип uint не умеет работать с отрицательными и дробными числами, в этих внутренних функций идет проверка на соответствие.
Контракт BNB наследует функции из контракта SafeMath. В современной реализации контракт SafeMath стал библиотекой и подключается как библиотека.
contract BNB is SafeMath{ string public name; string public symbol; uint8 public decimals; uint256 public totalSupply; address public owner;
Здесь все понятно. Определяются переменные
name — имя токена;
symbol — символ;
decimals — кол-во нулей;
totalSupply — общее кол-во токенов;
owner — адрес создателя (владельца) токена.
Далее создаются стандартные ERC20-мэппинги balanceOf и allowance, а также дополнительный мэппинг freezeOf для хранения баланса замороженных токенов в виде адрес=> количество.
mapping (address => uint256) public balanceOf; mapping (address => uint256) public freezeOf; mapping (address => mapping (address => uint256)) public allowance;
Далее определяются 4 события:
Transfer — запускается в случае успешного выполнения функции transfer;
Burn — запускается при сжигании токенов;
Freeze — запускается при заморозке токенов;
Unfreeze — запускается при разморозке токенов.
event Transfer(address indexed from, address indexed to, uint256 value); event Burn(address indexed from, uint256 value); event Freeze(address indexed from, uint256 value); event Unfreeze(address indexed from, uint256 value);
Конструктор BNB присваивает все создаваемые токены адресу создателя, устанавливает общее количество выпускаемых токенов, устанавливает имя, символ, кол-во нулей токена. А также присваивает переменной owner адрес создателя. Имя, символ, кол-во нулей и количество токенов не прописаны явно, а указываются при деплое как параметры конструктора.
function BNB( uint256 initialSupply, string tokenName, uint8 decimalUnits, string tokenSymbol ) { balanceOf[msg.sender] = initialSupply; totalSupply = initialSupply; name = tokenName; symbol = tokenSymbol; decimals = decimalUnits; owner = msg.sender; }
Функцию transfer подробно описывал в статье про стандарт erc20. В современных версиях Solidity конструкция if() throw упразднена, вместо нее используется require(), применительно к проверке адреса это будет выглядеть так:
require(_to != address(0));
Про функцию делегирования токенов approve также подробно писал в статье Что такое токены стандарта ERC20, здесь повторяться не буду.
Пользователь может уничтожить часть или все токены на балансе своего адреса с помощью функции Burn. Регулярно можно слышать что Binance сжигает часть своих токенов, делает она это как раз с помощью этой функции. Раньше для сжигания токенов их просто отправляли на адрес 0x0000000000000000000000000000000000000000, при проверке этого адреса в Etherscan.io можно увидеть огромное количество токенов и эфиров на балансе, которые будут лежать там мертвым грузом вечно, так как ни у кого нет возможности списать их оттуда.
Функция берет параметром количество сжигаемых токенов, останавливается если баланс меньше указанного кол-ва сжигаемых токенов, останавливается если количество меньше равно нулю. Далее с помощью математической функции safeSub из контракта SafeMath уменьшает баланс токенов на указанное количество сжигаемых токенов, потом обновляет общее количество токенов в переменной totalSupply. После запускает событие Burn и возвращает true.
function burn(uint256 _value) returns (bool success) { if (balanceOf[msg.sender] < _value) throw; if (_value <= 0) throw; balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value); totalSupply = SafeMath.safeSub(totalSupply,_value); Burn(msg.sender, _value); return true; }
Функции freeze и unfreeze позволяют заморозить и разморозить часть или все токены. Замороженные токены вычитаются из баланса пользователя balanceOf и записываются в мэппинг freezeOf. Замороженные токены нельзя переводить и сжигать. Разморозить токены можно с помощью функции unfreeze.
function freeze(uint256 _value) returns (bool success) { if (balanceOf[msg.sender] < _value) throw; if (_value <= 0) throw; balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value); freezeOf[msg.sender] = SafeMath.safeAdd(freezeOf[msg.sender], _value); Freeze(msg.sender, _value); return true; } function unfreeze(uint256 _value) returns (bool success) { if (freezeOf[msg.sender] < _value) throw; if (_value <= 0) throw; freezeOf[msg.sender] = SafeMath.safeSub(freezeOf[msg.sender], _value); balanceOf[msg.sender] = SafeMath.safeAdd(balanceOf[msg.sender], _value); Unfreeze(msg.sender, _value); return true; }
Наличие безымянной функции с модификатором payable позволяет хранить на адресе контракта эфиры и другие токены. С помощью функции withdrawEther создатель может перечислить на свой адрес эфиры и токены.
function() payable { } function withdrawEther(uint256 amount) { if(msg.sender != owner)throw; owner.transfer(amount); }
Весь код можно задеплоить в блокчейн со своим именем токена.