18 sierpnia 2022

Standardy w Ethereum #1

Co to jest token?

Definicja tokenu jest bardzo rozmyta. Na potrzeby tego wpisu przyjmijmy, że token:

  1. Jest to pewien zapis w blockchain
  2. Token podlega zasadą m.in. :
    • Kto, kiedy i komu może przekazać tokeny
    • Ile tokenów można wygenerować
    • Kto, ile tokenów posiada
    • Jaki jest czas życia tokenu
  3. Za kontrolę przestrzegania zasad odpowiada smart contract
  4. Aby wykonywać operacje na tokenach, wywołujemy funkcje w smart contract
  5. Aby wykonać operacje na tokenach, musimy zapłacić gas fee
ETH logo

To jak działa dany token, zależy od programisty kontraktu, może on oddać pełną kontrolę nad tokenem jego właścicielowi, ale równie dobrze, może pozostawić sobie prawo odebrania tokenów dowolnej osobie albo każdemu zabierać po jednym tokenie dzienni.
Aby zweryfikować, jakim zasadą podlega dany token należy przeprowadzić analizę kodu smart contractu

Dlaczego potrzebujemy standardów/interfejsów dla tokenów

Stosowanie standardów ułatwia rozwój ekosystemu, dzięki nim programiści pracujący nad aplikacją nie muszą pisać funkcjonalności dla każdego tokenu osobno, zamiast tego implementują obsługę standardu, co pozwala obsługiwać wszystkie tokeny w tym standardzie. Standardowe interfejsy ułatwiają interakcje z obcymi kontraktami niezależnie czy ta interakcja dotyczy tokenów.

Dobrym przykładem obrazującym użyteczności standardów są portfele kryptowalut, zarówno sprzętowe, jak i programowe. Wiele z nich obsługuje kilka kryptowalut i setki tokenów.


ERC-20 Token Standard (opens new window)

Intencja

Ideą dla tego standardu jest reprezentowanie cyfrowego żetonu. Celem jest, tylko ustalenie wspólnych jednorodnych mechanizmów do operacji na tym żetonie podobnie jak ma to miejsce w przypadku fizycznych obiektów. Poszczególne żetony nie są rozróżnialne, podobnie jak ma to miejsce w przypadku pieniędzy. Jeśli posiadamy 1 sztukę o danym symbolu, to jest ona nie, rozróżnialna z dowolną inną sztuką o tym samym symbolu.

Funkcjonalności

  • Sprawdzenie ilości tokenów w posiadaniu danego adresu
  • Zwraca Podstawowe informacje o tokenie:
    • Nazwę
    • Symbol
    • Miejsce dziesiętne, do którego token jest podzielny
    • Łączną podaż tokenów w całej sieci Etherium
  • Transfer tokenów z jednego adresu do innego
  • Przydzielenie pewnego limitu dla obcego adresu, aby ten mógł wypłacić środki do wysokości limitu

Interfejs

function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)

Implementacja


ERC-165 Interface Detection (opens new window)

Intencja

Jeśli chcemy komunikować się automatycznie z wieloma kontraktami/tokenami, to pojawia się potrzeba automatycznego sprawdzania jakie interfejsy dany kontrakt obsługuje. W standardzie ERC-165 mamy tylko jedną funkcję, pozwala ona sprawdzić które interfejsy są obsługiwane. Wartość liczbowa identyfikująca standard to wynik operacji xor wszystkich 4 bajtowych skrótów sygnatur funkcji obecnych w standardzie.

Przykład wyliczanie interfaceID dla przykładowego interfejsy z dwoma funkcjami:

interfaceID = bytes4(keccak256('function_1(bytes4)')) ^ bytes4(keccak256('function_2(bytes4)'))

Funkcjonalności

  • Sprawdzenie, czy standard ze skrótem interfaceID jest obsługiwany przez ten kontrakt
  • Opłata maksymalna wynosi 30,000 gas

Interfejs

interface ERC165 {
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

Implementacja


ERC-721 Non-Fungible Token (opens new window)

Intencja

W przeciwieństwie do ERC-20, te tokeny są rozróżnialne i niepodzielne. Możemy je bardziej porównać do certyfikatu własności niż do pieniędzy. Tokeny ERC-721 są nazywane NFT

Spełnia standardy

Funkcjonalności

  • Każdy token posiada swój unikalny identyfikator tokenId
  • Możemy poznać ile NFT posiada dany adres oraz kto posiada konkretny tokenId
  • Możliwość przekazywanie NFT od jednego właściciela do innego
  • Dany tokenId, może mieć przypisanych wiele adresów uprawnionych do dokonania transferu, niekoniecznie będzie to tylko właściciel
  • (opcjonalnie ERC721Metadata) Pozwala opublikować pełną listę tokenów w posiadaniu kontraktu
  • (opcjonalnie ERC721Enumerable) Zwraca: nazwę, symbol i URI tokenu

Interfejs

interface ERC721 /* is ERC165 */ {
    function balanceOf(address _owner) external view returns (uint256);
    function ownerOf(uint256 _tokenId) external view returns (address);
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function approve(address _approved, uint256 _tokenId) external payable;
    function setApprovalForAll(address _operator, bool _approved) external;
    function getApproved(uint256 _tokenId) external view returns (address);
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
    
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
}
interface ERC721TokenReceiver {
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
}

interface ERC721Metadata /* is ERC721 */ {
    function name() external view returns (string _name);
    function symbol() external view returns (string _symbol);
    function tokenURI(uint256 _tokenId) external view returns (string);
}

interface ERC721Enumerable /* is ERC721 */ {
    function totalSupply() external view returns (uint256);
    function tokenByIndex(uint256 _index) external view returns (uint256);
    function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}

Implementacja

Linki


ERC-777 Token Standard (opens new window)

Intencja

Rozszerza ERC-20 i jest z nim wstecznie kompatybilny. Już standard ERC-20 pozwalał nadać innemu adresowi prawo do dokonania transferu własnych środków do pewnego limitu, ERC-777 rozszerz te możliwości, wprowadza pojęcie operatora, czyli konto, które może mieć nadane przywileje do wykonania określonych zadań. Użycie operatora pozwala delegować odpowiedzialność opłacenie gas na odbiorcę. Zamiast przelać odbiorcy X Eth i zapłacić za gas, możemy ustanowić odbiorcę operatorem i pozwolić mu wypłacić X Eth z naszych środków, wówczas ma on pełną kontrolę nad opłatami dla sieci.

Kolejnym aspektem jest zabezpieczenie przed pomyłkowym wysłaniem tokenów do innego kontraktu, który ich nie obsługuje, aby uniknąć takiej sytuacji, każdy kontrakt rejestruje się w bazie obsługiwanych tokenów i tylko wówczas może przyjąć dany token.

Spełnia standardy

Funkcjonalności

  • Ustanowienie operatora który może mieć prawo do transferu lub spalenia środków
  • Ustanowienie domyślnego operatora który może transferować lub spalić środki dowolnego posiadacza tokenów
  • Prowadzenie rejestru adresów zdolnych do obsługi naszego tokenu i zablokowanie transferów do tych nie zarejestrowanych

Interfejs

interface ERC777Token {
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function totalSupply() external view returns (uint256);
    function balanceOf(address holder) external view returns (uint256);
    function granularity() external view returns (uint256);

    function defaultOperators() external view returns (address[] memory);
    function isOperatorFor operator, holder) external view returns (bool);
    function authorizeOperator(address operator) external;
    function revokeOperator(address operator) external;

    function send(to, amount, data) external;
    function operatorSend(from, to, amount, data, operatorData) external;

    function burn(uint256 amount, bytes calldata data) external;
    function operatorBurn(from, amount, data, operatorData) external;

    event Sent(operator, from, to, amount, data, operatorData);
    event Minted( operator, to, amount, data, operatorData);
    event Burned(operator, from, amount, data,  operatorData);
    event AuthorizedOperator(operator,holder);
    event RevokedOperator(operator, holder);
}

interface ERC777TokensSender {
    function tokensToSend(operator, from, to, amount, userData, operatorData) external;
}

interface ERC777TokensRecipient {
    function tokensReceived(operator, from, to, amount, data, operatorData) external;
}

Implementacja

Linki


ERC-1155 Multi Token (opens new window)

Intencja

Zarówno tokeny ERC-20, jak i ERC-777 zakładają, że jeden rodzaj tokenu jest obsługiwany przez jeden kontrakt. Jeśli chcemy, obsługiwać nowy token to potrzebujemy nowego kontraktu. ERC-1155 zmienia sytuacje, teraz jeden kontrakt może obsługiwać wiele różnych tokenów.

Spełnia standardy

Funkcjonalności

  • Obsługa wielu rodzajów tokenów o różnych charakterystykach
  • Sprawdzanie stanu dla danego adresu dla każdego tokenu
  • Obsługa bezpiecznego transferu (zabezpieczenie przed wysłaniem na niewłaściwy adres)

Interfejsy

interface ERC1155 /* is ERC165 */ {
    function safeTransferFrom(_from, _to, _id, _value, _data) external;
    function safeBatchTransferFrom(_from, _to,  _ids, _values, _data) external;
    function balanceOf(_owner, _id) external view returns (uint256);
    function balanceOfBatch(_owners,  _ids) external view returns (uint256[] memory);
    function setApprovalForAll(_operator, _approved) external;
    function isApprovedForAll(_owner, _operator) external view returns (bool);
    
    event TransferSingle(_operator, _from, indexed _to, _id, _value);
    event TransferBatch(_operator, _from, indexed _to, _ids, _values);
    event ApprovalForAll(_owner, _operator, _approved);
    event URI(_value, indexed _id);
}
interface ERC1155TokenReceiver {
    function onERC1155Received(_operator, _from, _id, _value, _data) external returns(bytes4);
    function onERC1155BatchReceived(_operator, _from,  _ids, _values, _data) external returns(bytes4);       
}

interface ERC1155Metadata_URI {
    function uri(uint256 _id) external view returns (string memory);
}

Implementacja

Linki


ERC-1820 Pseudo-introspection Registry Contract (opens new window)

Intencja

Ten standard wprowadza drobne poprawki do ERC-820, rozwiązujące problemy z kompatybilnością z ERC-165 wprowadzone przez aktualizację Solidity v. 0.5.

Ten standard rozwiązuje ten sam problem, co ERC-165 tylko w inny sposób i dodaje nowe funkcje. W przypadku ERC-165 mogliśmy sprawdzić interfejsy obsługiwane tylko przez inne smart kontrakty, teraz możemy sprawdzić interfejsy obsługiwane zarówno przez kontrakty, jak i regularne konta.

Wprowadza pojęcie manager adresu, czyli innego adresu, który może rejestrować interfejsy w imieniu obcego adresu. Początkowo każdy adres, jest swoim własnym menagerem, dopóki nie deleguje tej roli. Przykładem zastosowania

Spełnia standardy

Funkcjonalności

  • Odpowiada na pytanie, czy dany kontrakt obsługuje dany interfejs
  • Pozwala rejestrować obsługiwane interfejsy
  • Kompatybilny z ERC-165

Interfejs

interface ERC1820ImplementerInterface {
    function canImplementInterfaceForAddress(interfaceHash, addr) external view returns(bytes32);
}
contract ERC1820Registry {
    function setManager(address account, address newManager) external;
    function getManager(address account) external view returns (address);
    function setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) external;
    function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address);
    function interfaceHash(string calldata interfaceName) external pure returns (bytes32);
    function updateERC165Cache(address account, bytes4 interfaceId) external;
    function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);
    function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);
    
    event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);
    event ManagerChanged(address indexed account, address indexed newManager);
}

Implementacja


ERC-1363 Payable Token (opens new window)

Intencja

Rozszerzenie ERC-20 polegające na wywołanie callbacków przy transferach i zatwierdzaniu uprawnionych do transferów. Istotne jest, że wywołanie callback odbywa się w tej samej transakcji co transfer.

Spełnia standardy

Funkcjonalności

  • Transfer tokenów i wywołanie onTransferReceived u odbiorcy
  • Delegacja uprawnienia do dokonania transferu w czyimś imieniu i wywołanie onApprovalReceived u odbiorcy

Interfejs

contract ERC1363 is ERC20, ERC165 {
  function transferAndCall(address _to, uint256 _value) public returns (bool);
  function transferAndCall(address _to, uint256 _value, bytes _data) public returns (bool); 
  function transferFromAndCall(address _from, address _to, uint256 _value) public returns (bool); 
  function transferFromAndCall(address _from, address _to, uint256 _value, bytes _data) public returns (bool); 
  function approveAndCall(address _spender, uint256 _value) public returns (bool);
  function approveAndCall(address _spender, uint256 _value, bytes _data) public returns (bool); 
}
contract ERC1363Receiver {
  function onTransferReceived(address _operator, address _from, uint256 _value, bytes _data) external returns(bytes4);
}

contract ERC1363Spender {
  function onApprovalReceived(address _owner, uint256 _value, bytes _data) external returns (bytes4);
}

Implementacja

Linki


Linki