github.com/s7techlab/cckit@v0.10.5/examples/erc20/README.md (about)

     1  # How to create ERC20 token on Hyperledger Fabric
     2  
     3  As well as Ethereum blockchain,  Hyperledger Fabric platform (HLF) can be used for token creation, implemented as
     4  smart contract (chaincode in HLF terminology), that holds user balances. Unlike Ethereum, HLF chaincodes can't work
     5  with user addresses as a holder key, thus we will use combination of Membership Service Provider (MSP) Identifier 
     6  and user certificate identifier. Below is an simple example of how to create a token as Golang chaincode on the 
     7  Hyperledger Fabric platform using CCKit chaincode library.
     8  
     9  ## What is ERC20 token standard
    10  
    11  The [ERC20 token standard](https://github.com/ethereum/eips/issues/20) came about as an attempt to standardize token 
    12  smart contracts in Ethereum, it describes the functions  and events that an Ethereum token contract has to implement. 
    13  Most of the major tokens on the Ethereum blockchain  are ERC20-compliant. ERC-20 has many benefits, including unifying 
    14  token wallets and ability for exchanges to list more tokens by providing nothing more than the address of the token’s contract.
    15  
    16  ```solidity
    17  // ----------------------------------------------------------------------------
    18  // ERC Token Standard #20 Interface
    19  // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md
    20  // ----------------------------------------------------------------------------
    21  contract ERC20Interface {
    22      function totalSupply() public constant returns (uint);
    23      function balanceOf(address tokenOwner) public constant returns (uint balance);
    24      function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    25      function transfer(address to, uint tokens) public returns (bool success);
    26      function approve(address spender, uint tokens) public returns (bool success);
    27      function transferFrom(address from, address to, uint tokens) public returns (bool success);
    28  
    29      event Transfer(address indexed from, address indexed to, uint tokens);
    30      event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
    31  }
    32  ```
    33  
    34  ## ERC20 implementation basics
    35  
    36  Essentially, an Ethereum token contract is a smart contract that holds a map of account addresses and their balances.
    37  The balance is a value that is defined by the contract creator - in can be fungible physical objects, another monetary value.
    38  The unit of this balance is commonly called a token.
    39  
    40  ERC20 functions do:
    41  
    42  * `balanceOf` : returns the token balance of an owner identifier (account address in case of Ethereum)
    43  
    44  * `transfer` : transfers an amount to an owner identifier of our choosing
    45  
    46  * `approve` : sets an amount of tokens a specified owner identifier is allowed to spend on our behalf
    47  
    48  * `allowance` : check how much an owner identifier is allowed to spend on our behalf
    49  
    50  * `transferFrom` : specify an owner identifier to transfer from if we are allowed by that owner identifier to spend some tokens.
    51  
    52  
    53  ## Owner identifier in Hyperledger Fabric
    54  
    55  In the Hyperledger Fabric network, all actors have an identity known to other participants. The default [Membership Service 
    56  Provider](https://hyperledger-fabric.readthedocs.io/en/release-1.3/msp.html) implementation uses X.509 certificates as identities, adopting a traditional Public Key Infrastructure (PKI) 
    57  hierarchical model.
    58  
    59  Using information about creator of a proposal and asset ownership the chaincode should be able implement chaincode-level 
    60  access control mechanisms checking  is actor can initiate transactions that update the asset. The corresponding chaincode 
    61  logic has to be able to store this "ownership" information associated with the asset and evaluate it with respect to the 
    62  proposal creator.
    63  
    64  In HLF network as unique owner identifier (token balance holder) we can use combination of MSP Identifier and user 
    65  identity identifier. Identity identifier - is concatenation of `Subject` and `Issuer` parts of X.509 certificate. 
    66  This ID is guaranteed to be unique within the MSP.
    67  
    68  ```go
    69  func (c *clientIdentityImpl) GetID() (string, error) {
    70  	// The leading "x509::" distinquishes this as an X509 certificate, and
    71  	// the subject and issuer DNs uniquely identify the X509 certificate.
    72  	// The resulting ID will remain the same if the certificate is renewed.
    73  	id := fmt.Sprintf("x509::%s::%s", getDN(&c.cert.Subject), getDN(&c.cert.Issuer))
    74  	return base64.StdEncoding.EncodeToString([]byte(id)), nil
    75  }
    76  ````
    77  [Client identity chaincode library](https://github.com/hyperledger/fabric/tree/master/core/chaincode/lib/cid) 
    78  allows to write chaincode which makes access control decisions based on the identity of the client 
    79  (i.e. the invoker of the chaincode). 
    80                                       
    81  In particular, you may make access control decisions based on either or both of the following associated with the client:
    82                                       
    83   * the client identity's MSP (Membership Service Provider) ID
    84   * an attribute associated with the client identity
    85  
    86  CCkit contains [identity](https://github.com/s7techlab/cckit/tree/master/identity) package with structures and functions 
    87  can that be used for implementing access control in chaincode. 
    88  
    89  ## Getting started with example
    90  
    91  In our example we use CCKit router for managing smart contract functions. Before you begin, be sure to get `CCkit`:
    92  
    93  `git clone git@github.com:s7techlab/cckit.git`
    94  
    95  and get dependencies using `go mod` command:
    96  
    97  `go mod vendor`
    98  
    99  ERC20 example is located in [examples/erc20](https://github.com/s7techlab/cckit/tree/master/examples/erc20) directory.
   100  
   101  
   102  
   103  ## Defining token smart contract functions
   104  
   105  First, we need to define chaincode functions. In our example we use [router](https://github.com/s7techlab/cckit/tree/master/router) 
   106  package from CCkit, that allows us to define chaincode methods and their parameters in consistent way. 
   107  
   108  At first we define `init` function (smart contract constructor) with arguments `symbol`, `name` and `totalSupply`. 
   109  After that we define chaincode methods, implementing ERC20 interface, adopted to HLF owner identifiers 
   110  (pair of MSP Id and certificate ID). All querying method are prefixed with `query`, all writing to state methods are prefixed with
   111  `invoke`.
   112  
   113  As a result we use [default chaincode](https://github.com/s7techlab/cckit/blob/master/router/chaincode.go) structure, 
   114  that delegates `Init` and `Invoke` handling to router.
   115  
   116  ```go
   117  func NewErc20FixedSupply() *router.Chaincode {
   118  	
   119  	r := router.New(`erc20fixedSupply`).Use(p.StrictKnown).
   120          // Chaincode init function, initiates token smart contract with token symbol, name and totalSupply
   121          Init(invokeInitFixedSupply, p.String(`symbol`), p.String(`name`), p.Int(`totalSupply`)).
   122          // Get token symbol
   123          Query(`symbol`, querySymbol).
   124          // Get token name
   125          Query(`name`, queryName).
   126          // Get the total token supply
   127          Query(`totalSupply`, queryTotalSupply).
   128          //  get account balance
   129          Query(`balanceOf`, queryBalanceOf, p.String(`mspId`), p.String(`certId`)).
   130          //Send value amount of tokens
   131          Invoke(`transfer`, invokeTransfer, p.String(`toMspId`), p.String(`toCertId`), p.Int(`amount`)).
   132          // Allow spender to withdraw from your account, multiple times, up to the _value amount.
   133          // If this function is called again it overwrites the current allowance with _valu
   134          Invoke(`approve`, invokeApprove, p.String(`spenderMspId`), p.String(`spenderCertId`), p.Int(`amount`)).
   135          //    Returns the amount which _spender is still allowed to withdraw from _owner]
   136          Invoke(`allowance`, queryAllowance, p.String(`ownerMspId`), p.String(`ownerCertId`),
   137              p.String(`spenderMspId`), p.String(`spenderCertId`)).
   138          // Send amount of tokens from owner account to another
   139          Invoke(`transferFrom`, invokeTransferFrom, p.String(`fromMspId`), p.String(`fromCertId`),
   140              p.String(`toMspId`), p.String(`toCertId`), p.Int(`amount`))
   141  
   142  	return router.NewChaincode(r)
   143  }
   144  ````
   145  
   146  ## Chaincode initialization (constructor)
   147  
   148  Chaincode `init` function (token constructor) performs the following actions:
   149  
   150  * puts to chaincode state information about chaincode owner, using 
   151    [owner](https://github.com/s7techlab/cckit/tree/master/extensions/owner) extension from CCkit
   152  * puts to chaincode state token configuration - token symbol, name and total supply
   153  * sets chaincode owner balance with total supply
   154  
   155  ```go
   156  const SymbolKey = `symbol`
   157  const NameKey = `name`
   158  const TotalSupplyKey = `totalSupply`
   159  
   160  
   161  func invokeInitFixedSupply(c router.Context) (interface{}, error) {
   162  	ownerIdentity, err := owner.SetFromCreator(c)
   163  	if err != nil {
   164  		return nil, errors.Wrap(err, `set chaincode owner`)
   165  	}
   166  
   167  	// save token configuration in state
   168  	if err := c.State().Insert(SymbolKey, c.ArgString(`symbol`)); err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	if err := c.State().Insert(NameKey, c.ArgString(`name`)); err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	if err := c.State().Insert(TotalSupplyKey, c.ArgInt(`totalSupply`)); err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	// set token owner initial balance
   181  	if err := setBalance(c, ownerIdentity.GetMSPID(), ownerIdentity.GetID(), c.ArgInt(`totalSupply`)); err != nil {
   182  		return nil, errors.Wrap(err, `set owner initial balance`)
   183  	}
   184  
   185  	return ownerIdentity, nil
   186  }
   187  ```
   188  
   189  
   190  ## Defining events structure types
   191  
   192  We use [Id](https://github.com/s7techlab/cckit/blob/master/identity/entry.go) structure from 
   193  [identity](https://github.com/s7techlab/cckit/tree/master/identity) package:
   194  ```go
   195  // Id structure defines short id representation
   196  type Id struct {
   197  	MSP  string
   198  	Cert string
   199  }
   200  ```
   201  
   202  And define structures for `Transfer` and `Approve` event:
   203  
   204  ```go
   205  type (
   206  	Transfer struct {
   207  		From   identity.Id
   208  		To     identity.Id
   209  		Amount int
   210  	}
   211  
   212  	Approve struct {
   213  		From    identity.Id
   214  		Spender identity.Id
   215  		Amount  int
   216  	}
   217  )
   218  ```
   219  
   220  ## Implementing token smart contract functions
   221  
   222  Querying function is quite simple - it's just read value from chaincode state:
   223  
   224  ```go
   225  const SymbolKey = `symbol`
   226  
   227  func querySymbol(c r.Context) (interface{}, error) {
   228  	return c.State().Get(SymbolKey)
   229  }
   230  ```
   231  
   232  Some of changing state functions are more complicated. For example in function `invokeTransfer` we do:
   233  
   234  * receive function invoker certificate (via tx `GetCreator()` function)
   235  * check transfer destination 
   236  * get current invoker (payer) balance
   237  * check balance to transfer `amount` of tokens
   238  * get recipient balance
   239  * update payer and recipient balances in chaincode state
   240  
   241  ```go
   242  func invokeTransfer(c r.Context) (interface{}, error) {
   243  	// transfer target
   244  	toMspId := c.ArgString(`toMspId`)
   245  	toCertId := c.ArgString(`toCertId`)
   246  
   247  	//transfer amount
   248  	amount := c.ArgInt(`amount`)
   249  
   250  	// get informartion about tx creator
   251  	invoker, err := identity.FromStub(c.Stub())
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	// Disallow to transfer token to same account
   257  	if invoker.GetMSPID() == toMspId && invoker.GetID() == toCertId {
   258  		return nil, ErrForbiddenToTransferToSameAccount
   259  	}
   260  
   261  	// get information about invoker balance from state
   262  	invokerBalance, err := getBalance(c, invoker.GetMSPID(), invoker.GetID())
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	// Check the funds sufficiency
   268  	if invokerBalance-amount < 0 {
   269  		return nil, ErrNotEnoughFunds
   270  	}
   271  
   272  	// Get information about recipient balance from state
   273  	recipientBalance, err := getBalance(c, toMspId, toCertId)
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  
   278  	// Update payer and recipient balance
   279  	setBalance(c, invoker.GetMSPID(), invoker.GetID(), invokerBalance-amount)
   280  	setBalance(c, toMspId, toCertId, recipientBalance+amount)
   281  
   282  	// Trigger event with name "transfer" and payload - serialized to json Transfer structure
   283  	c.SetEvent(`transfer`, &Transfer{
   284  		From: identity.Id{
   285  			MSP:  invoker.GetMSPID(),
   286  			Cert: invoker.GetID(), 
   287  		},
   288  		To: identity.Id{
   289  			MSP:  toMspId,
   290  			Cert: toCertId,
   291  		},
   292  		Amount: amount,
   293  	})
   294  
   295  	// return current invoker balance
   296  	return invokerBalance - amount, nil
   297  }
   298  
   299  // setBalance puts balance value to state
   300  func setBalance(c r.Context, mspId, certId string, balance int) error {
   301  	return c.State().Put(balanceKey(mspId, certId), balance)
   302  }
   303  
   304  // balanceKey creates composite key for store balance value in state
   305  func balanceKey(ownerMspId, ownerCertId string) []string {
   306  	return []string{BalancePrefix, ownerMspId, ownerCertId}
   307  }
   308  ```
   309  
   310  
   311  ## Testing 
   312  
   313  Also, we can fast test our chaincode via CCkit [MockStub](https://github.com/s7techlab/cckit/tree/master/testing).  
   314  
   315  To start testing we init chaincode via MockStub with test parameters:
   316  
   317  ```go
   318  var _ = Describe(`ERC-20`, func() {
   319  
   320  	const TokenSymbol = `HLF`
   321  	const TokenName = `HLFCoin`
   322  	const TotalSupply = 10000
   323  	const Decimals = 3
   324  
   325  	//Create chaincode mock
   326  	erc20fs := testcc.NewMockStub(`erc20`, NewErc20FixedSupply())
   327  
   328  	// load actor certificates
   329  	actors, err := identity.ActorsFromPemFile(`SOME_MSP`, map[string]string{
   330  		`token_owner`:     `s7techlab.pem`,
   331  		`account_holder1`: `victor-nosov.pem`,
   332  		//`accoubt_holder2`: `victor-nosov.pem`
   333  	}, examplecert.Content)
   334  	if err != nil {
   335  		panic(err)
   336  	}
   337  
   338  	BeforeSuite(func() {
   339  		// init token haincode
   340  		expectcc.ResponseOk(erc20fs.From(actors[`token_owner`]).Init(TokenSymbol, TokenName, TotalSupply, Decimals))
   341  	})
   342  ```
   343  
   344  After we can check all token operations:
   345  
   346  ```go
   347  Describe("ERC-20 transfer", func() {
   348      It("Disallow to transfer token to same account", func() {
   349          expectcc.ResponseError(
   350              erc20fs.From(actors[`token_owner`]).Invoke(
   351                  `transfer`, actors[`token_owner`].GetMSPID(), actors[`token_owner`].GetID(), 100),
   352              ErrForbiddenToTransferToSameAccount)
   353      })
   354  
   355      It("Disallow token holder with zero balance to transfer tokens", func() {
   356          expectcc.ResponseError(
   357              erc20fs.From(actors[`account_holder1`]).Invoke(
   358                  `transfer`, actors[`token_owner`].GetMSPID(), actors[`token_owner`].GetID(), 100),
   359              ErrNotEnoughFunds)
   360      })
   361  
   362      It("Allow token holder with non zero balance to transfer tokens", func() {
   363          expectcc.PayloadInt(
   364              erc20fs.From(actors[`token_owner`]).Invoke(
   365                  `transfer`, actors[`account_holder1`].GetMSPID(), actors[`account_holder1`].GetID(), 100),
   366              TotalSupply-100)
   367  
   368          expectcc.PayloadInt(
   369              erc20fs.Query(
   370                  `balanceOf`, actors[`token_owner`].GetMSPID(), actors[`token_owner`].GetID()), TotalSupply-100)
   371  
   372          expectcc.PayloadInt(
   373              erc20fs.Query(
   374                  `balanceOf`, actors[`account_holder1`].GetMSPID(), actors[`account_holder1`].GetID()), 100)
   375      })
   376  })
   377  ```