updated erc tokens samples (#731)

Signed-off-by: fraVlaca <ocsenarf@outlook.com>
This commit is contained in:
fraVlaca 2022-05-11 15:35:10 +01:00 committed by GitHub
parent 2d32a8e7e3
commit 5f71466295
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1207 additions and 51 deletions

View file

@ -23,6 +23,10 @@ const approvalPrefix = "account~operator"
const minterMSPID = "Org1MSP"
// Define key names for options
const nameKey = "name"
const symbolKey = "symbol"
// SmartContract provides functions for transferring tokens between accounts
type SmartContract struct {
contractapi.Contract
@ -113,8 +117,17 @@ type ToID struct {
// This function emits a TransferSingle event.
func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
err = authorizationHelper(ctx)
if err != nil {
return err
}
@ -140,12 +153,21 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, accoun
// This function emits a TransferBatch event.
func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
if len(ids) != len(amounts) {
return fmt.Errorf("ids and amounts must have the same length")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
err = authorizationHelper(ctx)
if err != nil {
return err
}
@ -160,7 +182,10 @@ func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, a
amountToSend := make(map[uint64]uint64) // token id => amount
for i := 0; i < len(amounts); i++ {
amountToSend[ids[i]] += amounts[i]
amountToSend[ids[i]], err = add(amountToSend[ids[i]], amounts[i])
if err != nil {
return err
}
}
// Copy the map keys and sort it. This is necessary because iterating maps in Go is not deterministic
@ -184,12 +209,21 @@ func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, a
// This function triggers a TransferSingle event.
func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
if account == "0x0" {
return fmt.Errorf("burn to the zero address")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens
err := authorizationHelper(ctx)
err = authorizationHelper(ctx)
if err != nil {
return err
}
@ -214,6 +248,15 @@ func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, accoun
// This function emits a TransferBatch event.
func (s *SmartContract) BurnBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
if account == "0x0" {
return fmt.Errorf("burn to the zero address")
}
@ -223,7 +266,7 @@ func (s *SmartContract) BurnBatch(ctx contractapi.TransactionContextInterface, a
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens
err := authorizationHelper(ctx)
err = authorizationHelper(ctx)
if err != nil {
return err
}
@ -247,6 +290,16 @@ func (s *SmartContract) BurnBatch(ctx contractapi.TransactionContextInterface, a
// recipient account must be a valid clientID as returned by the ClientID() function
// This function triggers a TransferSingle event
func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
if sender == recipient {
return fmt.Errorf("transfer to self")
}
@ -293,6 +346,16 @@ func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface
// recipient account must be a valid clientID as returned by the ClientID() function
// This function triggers a TransferBatch event
func (s *SmartContract) BatchTransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, ids []uint64, amounts []uint64) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
if sender == recipient {
return fmt.Errorf("transfer to self")
}
@ -332,7 +395,10 @@ func (s *SmartContract) BatchTransferFrom(ctx contractapi.TransactionContextInte
amountToSend := make(map[uint64]uint64) // token id => amount
for i := 0; i < len(amounts); i++ {
amountToSend[ids[i]] += amounts[i]
amountToSend[ids[i]], err = add(amountToSend[ids[i]], amounts[i])
if err != nil {
return err
}
}
// Copy the map keys and sort it. This is necessary because iterating maps in Go is not deterministic
@ -356,6 +422,15 @@ func (s *SmartContract) BatchTransferFrom(ctx contractapi.TransactionContextInte
// This function triggers a TransferBatchMultiRecipient event
func (s *SmartContract) BatchTransferFromMultiRecipient(ctx contractapi.TransactionContextInterface, sender string, recipients []string, ids []uint64, amounts []uint64) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
if len(recipients) != len(ids) || len(ids) != len(amounts) {
return fmt.Errorf("recipients, ids, and amounts must have the same length")
}
@ -393,7 +468,10 @@ func (s *SmartContract) BatchTransferFromMultiRecipient(ctx contractapi.Transact
amountToSend := make(map[ToID]uint64) // (recipient, id ) => amount
for i := 0; i < len(amounts); i++ {
amountToSend[ToID{recipients[i], ids[i]}] += amounts[i]
amountToSend[ToID{recipients[i], ids[i]}], err = add(amountToSend[ToID{recipients[i], ids[i]}], amounts[i])
if err != nil {
return err
}
}
// Copy the map keys and sort it. This is necessary because iterating maps in Go is not deterministic
@ -425,6 +503,16 @@ func (s *SmartContract) IsApprovedForAll(ctx contractapi.TransactionContextInter
// _isApprovedForAll returns true if operator is approved to transfer account's tokens.
func _isApprovedForAll(ctx contractapi.TransactionContextInterface, account string, operator string) (bool, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return false, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{account, operator})
if err != nil {
return false, fmt.Errorf("failed to create the composite key for prefix %s: %v", approvalPrefix, err)
@ -450,6 +538,16 @@ func _isApprovedForAll(ctx contractapi.TransactionContextInterface, account stri
// SetApprovalForAll returns true if operator is approved to transfer account's tokens.
func (s *SmartContract) SetApprovalForAll(ctx contractapi.TransactionContextInterface, operator string, approved bool) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
account, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -490,11 +588,31 @@ func (s *SmartContract) SetApprovalForAll(ctx contractapi.TransactionContextInte
// BalanceOf returns the balance of the given account
func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, account string, id uint64) (uint64, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
return balanceOfHelper(ctx, account, id)
}
// BalanceOfBatch returns the balance of multiple account/token pairs
func (s *SmartContract) BalanceOfBatch(ctx contractapi.TransactionContextInterface, accounts []string, ids []uint64) ([]uint64, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return nil, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return nil, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
if len(accounts) != len(ids) {
return nil, fmt.Errorf("accounts and ids must have the same length")
}
@ -515,6 +633,15 @@ func (s *SmartContract) BalanceOfBatch(ctx contractapi.TransactionContextInterfa
// ClientAccountBalance returns the balance of the requesting client's account
func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextInterface, id uint64) (uint64, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -529,6 +656,15 @@ func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextI
// Users can use this function to get their own account id, which they can then give to others as the payment address
func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
clientAccountID, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -542,8 +678,17 @@ func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterf
// This function triggers URI event for each token id
func (s *SmartContract) SetURI(ctx contractapi.TransactionContextInterface, uri string) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
err = authorizationHelper(ctx)
if err != nil {
return err
}
@ -563,6 +708,15 @@ func (s *SmartContract) SetURI(ctx contractapi.TransactionContextInterface, uri
// URI returns the URI
func (s *SmartContract) URI(ctx contractapi.TransactionContextInterface, id uint64) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
uriBytes, err := ctx.GetStub().GetState(uriKey)
if err != nil {
return "", fmt.Errorf("failed to get uri: %v", err)
@ -577,8 +731,17 @@ func (s *SmartContract) URI(ctx contractapi.TransactionContextInterface, id uint
func (s *SmartContract) BroadcastTokenExistance(ctx contractapi.TransactionContextInterface, id uint64) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
err = authorizationHelper(ctx)
if err != nil {
return err
}
@ -594,6 +757,84 @@ func (s *SmartContract) BroadcastTokenExistance(ctx contractapi.TransactionConte
return emitTransferSingle(ctx, transferSingleEvent)
}
// Name returns a descriptive name for fungible tokens in this contract
// returns {String} Returns the name of the token
func (s *SmartContract) Name(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
bytes, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return "", fmt.Errorf("failed to get Name bytes: %s", err)
}
return string(bytes), nil
}
// Symbol returns an abbreviated name for fungible tokens in this contract.
// returns {String} Returns the symbol of the token
func (s *SmartContract) Symbol(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
bytes, err := ctx.GetStub().GetState(symbolKey)
if err != nil {
return "", fmt.Errorf("failed to get Symbol: %v", err)
}
return string(bytes), nil
}
// Set information for a token and intialize contract.
// param {String} name The name of the token
// param {String} symbol The symbol of the token
func (s *SmartContract) Initialize(ctx contractapi.TransactionContextInterface, name string, symbol string) (bool, error) {
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to intitialize contract
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return false, fmt.Errorf("failed to get MSPID: %v", err)
} else if clientMSPID != "Org1MSP" {
return false, fmt.Errorf("client is not authorized to initialize contract")
}
//check contract options are not already set, client is not authorized to change them once intitialized
bytes, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return false, fmt.Errorf("failed to get Name: %v", err)
} else if bytes != nil {
return false, fmt.Errorf("contract options are already set, client is not authorized to change them")
}
err = ctx.GetStub().PutState(nameKey, []byte(name))
if err != nil {
return false, fmt.Errorf("failed to set token name: %v", err)
}
err = ctx.GetStub().PutState(symbolKey, []byte(symbol))
if err != nil {
return false, fmt.Errorf("failed to set symbol: %v", err)
}
return true, nil
}
// Helper Functions
// authorizationHelper checks minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
@ -646,7 +887,10 @@ func addBalance(ctx contractapi.TransactionContextInterface, sender string, reci
balance, _ = strconv.ParseUint(string(balanceBytes), 10, 64)
}
balance += amount
balance, err = add(balance, amount)
if err != nil {
return err
}
err = ctx.GetStub().PutState(balanceKey, []byte(strconv.FormatUint(uint64(balance), 10)))
if err != nil {
@ -676,9 +920,13 @@ func setBalance(ctx contractapi.TransactionContextInterface, sender string, reci
func removeBalance(ctx contractapi.TransactionContextInterface, sender string, ids []uint64, amounts []uint64) error {
// Calculate the total amount of each token to withdraw
necessaryFunds := make(map[uint64]uint64) // token id -> necessary amount
var err error
for i := 0; i < len(amounts); i++ {
necessaryFunds[ids[i]] += amounts[i]
necessaryFunds[ids[i]], err = add(necessaryFunds[ids[i]], amounts[i])
if err != nil {
return err
}
}
// Copy the map keys and sort it. This is necessary because iterating maps in Go is not deterministic
@ -708,7 +956,10 @@ func removeBalance(ctx contractapi.TransactionContextInterface, sender string, i
}
partBalAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64)
partialBalance += partBalAmount
partialBalance, err = add(partialBalance, partBalAmount)
if err != nil {
return err
}
_, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(queryResponse.Key)
if err != nil {
@ -730,7 +981,11 @@ func removeBalance(ctx contractapi.TransactionContextInterface, sender string, i
return fmt.Errorf("sender has insufficient funds for token %v, needed funds: %v, available fund: %v", tokenId, neededAmount, partialBalance)
} else if partialBalance > neededAmount {
// Send the remainder back to the sender
remainder := partialBalance - neededAmount
remainder, err := sub(partialBalance, neededAmount)
if err != nil {
return err
}
if selfRecipientKeyNeedsToBeRemoved {
// Set balance for the key that has the same address for sender and recipient
err = setBalance(ctx, sender, sender, tokenId, remainder)
@ -821,7 +1076,10 @@ func balanceOfHelper(ctx contractapi.TransactionContextInterface, account string
}
balAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64)
balance += balAmount
balance, err = add(balance, balAmount)
if err != nil {
return 0, err
}
}
return balance, nil
@ -859,3 +1117,42 @@ func sortedKeysToID(m map[ToID]uint64) []ToID {
})
return keys
}
//Checks that contract options have been already initialized
func checkInitialized(ctx contractapi.TransactionContextInterface) (bool, error) {
tokenName, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return false, fmt.Errorf("failed to get token name: %v", err)
} else if tokenName == nil {
return false, nil
}
return true, nil
}
// add two number checking for overflow
func add(b uint64, q uint64) (uint64, error) {
// Check overflow
var sum uint64
sum = q + b
if sum < q {
return 0, fmt.Errorf("Math: addition overflow occurred %d + %d", b, q)
}
return sum, nil
}
// sub two number checking for overflow
func sub(b uint64, q uint64) (uint64, error) {
// Check overflow
var diff uint64
diff = q - b
if diff > q {
return 0, fmt.Errorf("Math: subtraction overflow occurred %d - %d", b, q)
}
return diff, nil
}

View file

@ -11,11 +11,16 @@ import (
)
// Define key names for options
const nameKey = "name"
const symbolKey = "symbol"
const decimalsKey = "decimals"
const totalSupplyKey = "totalSupply"
// Define objectType names for prefix
const allowancePrefix = "allowance"
// Define key names for options
// SmartContract provides functions for transferring tokens between accounts
type SmartContract struct {
contractapi.Contract
@ -32,6 +37,15 @@ type event struct {
// This function triggers a Transfer event
func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount int) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
@ -65,7 +79,10 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount
currentBalance, _ = strconv.Atoi(string(currentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer.
}
updatedBalance := currentBalance + amount
updatedBalance,err := add(currentBalance, amount)
if err != nil {
return err
}
err = ctx.GetStub().PutState(minter, []byte(strconv.Itoa(updatedBalance)))
if err != nil {
@ -88,7 +105,11 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount
}
// Add the mint amount to the total supply and update the state
totalSupply += amount
totalSupply, err = add(totalSupply, amount)
if err != nil {
return err
}
err = ctx.GetStub().PutState(totalSupplyKey, []byte(strconv.Itoa(totalSupply)))
if err != nil {
return err
@ -114,6 +135,14 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount
// This function triggers a Transfer event
func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, amount int) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
@ -147,7 +176,10 @@ func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, amount
currentBalance, _ = strconv.Atoi(string(currentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer.
updatedBalance := currentBalance - amount
updatedBalance, err := sub(currentBalance, amount)
if err != nil {
return err
}
err = ctx.GetStub().PutState(minter, []byte(strconv.Itoa(updatedBalance)))
if err != nil {
@ -168,7 +200,11 @@ func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, amount
totalSupply, _ := strconv.Atoi(string(totalSupplyBytes)) // Error handling not needed since Itoa() was used when setting the totalSupply, guaranteeing it was an integer.
// Subtract the burn amount to the total supply and update the state
totalSupply -= amount
totalSupply, err = sub(totalSupply, amount)
if err != nil {
return err
}
err = ctx.GetStub().PutState(totalSupplyKey, []byte(strconv.Itoa(totalSupply)))
if err != nil {
return err
@ -195,6 +231,15 @@ func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, amount
// This function triggers a Transfer event
func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, recipient string, amount int) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -222,6 +267,16 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, re
// BalanceOf returns the balance of the given account
func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, account string) (int, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
balanceBytes, err := ctx.GetStub().GetState(account)
if err != nil {
return 0, fmt.Errorf("failed to read from world state: %v", err)
@ -238,6 +293,15 @@ func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, a
// ClientAccountBalance returns the balance of the requesting client's account
func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextInterface) (int, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -262,6 +326,15 @@ func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextI
// Users can use this function to get their own account id, which they can then give to others as the payment address
func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
clientAccountID, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -274,6 +347,15 @@ func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterf
// TotalSupply returns the total token supply
func (s *SmartContract) TotalSupply(ctx contractapi.TransactionContextInterface) (int, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Retrieve total supply of tokens from state of smart contract
totalSupplyBytes, err := ctx.GetStub().GetState(totalSupplyKey)
if err != nil {
@ -299,6 +381,15 @@ func (s *SmartContract) TotalSupply(ctx contractapi.TransactionContextInterface)
// This function triggers an Approval event
func (s *SmartContract) Approve(ctx contractapi.TransactionContextInterface, spender string, value int) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
owner, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -336,6 +427,15 @@ func (s *SmartContract) Approve(ctx contractapi.TransactionContextInterface, spe
// Allowance returns the amount still available for the spender to withdraw from the owner
func (s *SmartContract) Allowance(ctx contractapi.TransactionContextInterface, owner string, spender string) (int, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Create allowanceKey
allowanceKey, err := ctx.GetStub().CreateCompositeKey(allowancePrefix, []string{owner, spender})
if err != nil {
@ -366,6 +466,15 @@ func (s *SmartContract) Allowance(ctx contractapi.TransactionContextInterface, o
// This function triggers a Transfer event
func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface, from string, to string, value int) error {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
spender, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -399,7 +508,11 @@ func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface
}
// Decrease the allowance
updatedAllowance := currentAllowance - value
updatedAllowance, err := sub(currentAllowance, value)
if err != nil {
return err
}
err = ctx.GetStub().PutState(allowanceKey, []byte(strconv.Itoa(updatedAllowance)))
if err != nil {
return err
@ -421,6 +534,90 @@ func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface
return nil
}
// Name returns a descriptive name for fungible tokens in this contract
// returns {String} Returns the name of the token
func (s *SmartContract) Name(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
bytes, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return "", fmt.Errorf("failed to get Name bytes: %s", err)
}
return string(bytes), nil
}
// Symbol returns an abbreviated name for fungible tokens in this contract.
// returns {String} Returns the symbol of the token
func (s *SmartContract) Symbol(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
bytes, err := ctx.GetStub().GetState(symbolKey)
if err != nil {
return "", fmt.Errorf("failed to get Symbol: %v", err)
}
return string(bytes), nil
}
// Set information for a token and intialize contract.
// param {String} name The name of the token
// param {String} symbol The symbol of the token
// param {String} decimals The name of the token
func (s *SmartContract) Initialize(ctx contractapi.TransactionContextInterface, name string, symbol string, decimals string) (bool, error) {
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to intitialize contract
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return false, fmt.Errorf("failed to get MSPID: %v", err)
} else if clientMSPID != "Org1MSP" {
return false, fmt.Errorf("client is not authorized to initialize contract")
}
//check contract options are not already set, client is not authorized to change them once intitialized
bytes, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return false, fmt.Errorf("failed to get Name: %v", err)
}else if bytes != nil {
return false, fmt.Errorf("contract options are already set, client is not authorized to change them")
}
err = ctx.GetStub().PutState(nameKey, []byte(name))
if err != nil {
return false, fmt.Errorf("failed to set token name: %v", err)
}
err = ctx.GetStub().PutState(symbolKey, []byte(symbol))
if err != nil {
return false, fmt.Errorf("failed to set symbol: %v", err)
}
err = ctx.GetStub().PutState(decimalsKey, []byte(decimals))
if err != nil {
return false, fmt.Errorf("failed to set token name: %v", err)
}
return true, nil;
}
// Helper Functions
// transferHelper is a helper function that transfers tokens from the "from" address to the "to" address
@ -463,8 +660,15 @@ func transferHelper(ctx contractapi.TransactionContextInterface, from string, to
toCurrentBalance, _ = strconv.Atoi(string(toCurrentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer.
}
fromUpdatedBalance := fromCurrentBalance - value
toUpdatedBalance := toCurrentBalance + value
fromUpdatedBalance, err := sub(fromCurrentBalance, value)
if err != nil {
return err
}
toUpdatedBalance, err := add(toCurrentBalance, value)
if err != nil {
return err
}
err = ctx.GetStub().PutState(from, []byte(strconv.Itoa(fromUpdatedBalance)))
if err != nil {
@ -481,3 +685,44 @@ func transferHelper(ctx contractapi.TransactionContextInterface, from string, to
return nil
}
// add two number checking for overflow
func add(b int, q int) (int, error) {
// Check overflow
var sum int
sum = q + b
if (sum < q) == (b > 0 && q > 0) {
return 0, fmt.Errorf("Math: addition overflow occurred %d + %d", b, q)
}
return sum, nil
}
//Checks that contract options have been already initialized
func checkInitialized(ctx contractapi.TransactionContextInterface) (bool, error) {
tokenName, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return false, fmt.Errorf("failed to get token name: %v", err)
}else if (tokenName == nil){
return false , nil
}
return true , nil
}
// sub two number checking for overflow
func sub(b int, q int) (int, error) {
// Check overflow
var diff int
diff = q - b
if (diff > q) == (b > 0 && q > 0) {
return 0, fmt.Errorf("Math: Subtraction overflow occurred %d - %d", b, q)
}
return diff, nil
}

View file

@ -9,8 +9,8 @@ public enum ContractConstants {
BALANCE_PREFIX("balance"),
ALLOWANCE_PREFIX("allowance"),
NAME_KEY("name"),
SYMBOL_KEY("decimals"),
DECIMALS_KEY("symbolKey"),
SYMBOL_KEY("symbolKey"),
DECIMALS_KEY("decimals"),
TOTAL_SUPPLY_KEY("totalSupply"),
TRANSFER_EVENT("Transfer"),
MINTER_ORG_MSPID("Org1MSP"),

View file

@ -76,6 +76,10 @@ public final class ERC20TokenContract implements ContractInterface {
throw new ChaincodeException(
"Client is not authorized to mint new tokens", UNAUTHORIZED_SENDER.toString());
}
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
// Get ID of submitting client identity
String minter = ctx.getClientIdentity().getId();
if (amount <= 0) {
@ -127,6 +131,10 @@ public final class ERC20TokenContract implements ContractInterface {
throw new ChaincodeException(
"Client is not authorized to burn tokens", UNAUTHORIZED_SENDER.toString());
}
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
String minter = ctx.getClientIdentity().getId();
if (amount <= 0) {
throw new ChaincodeException(
@ -173,6 +181,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void Transfer(final Context ctx, final String to, final long value) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
String from = ctx.getClientIdentity().getId();
this.transferHelper(ctx, from, to, value);
final Transfer transferEvent = new Transfer(from, to, value);
@ -188,6 +198,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public long BalanceOf(final Context ctx, final String owner) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
ChaincodeStub stub = ctx.getStub();
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), owner);
String balance = stub.getStringState(balanceKey.toString());
@ -207,6 +219,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public long ClientAccountBalance(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
// Get ID of submitting client identity
ChaincodeStub stub = ctx.getStub();
String clientAccountID = ctx.getClientIdentity().getId();
@ -230,6 +244,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String ClientAccountID(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
// Get ID of submitting client identity
return ctx.getClientIdentity().getId();
}
@ -242,6 +258,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public long TotalSupply(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
String totalSupply = ctx.getStub().getStringState(TOTAL_SUPPLY_KEY.getValue());
if (stringIsNullOrEmpty(totalSupply)) {
throw new ChaincodeException("Total Supply not found", NOT_FOUND.toString());
@ -259,6 +277,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void Approve(final Context ctx, final String spender, final long value) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
ChaincodeStub stub = ctx.getStub();
String owner = ctx.getClientIdentity().getId();
CompositeKey allowanceKey =
@ -282,6 +302,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public long Allowance(final Context ctx, final String owner, final String spender) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
ChaincodeStub stub = ctx.getStub();
CompositeKey allowanceKey =
stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), owner, spender);
@ -308,6 +330,8 @@ public final class ERC20TokenContract implements ContractInterface {
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void TransferFrom(
final Context ctx, final String from, final String to, final long value) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
String spender = ctx.getClientIdentity().getId();
ChaincodeStub stub = ctx.getStub();
// Retrieve the allowance of the spender
@ -402,9 +426,23 @@ public final class ERC20TokenContract implements ContractInterface {
* @param decimals The decimals of the token
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void SetOptions(
public void Initialize(
final Context ctx, final String name, final String symbol, final String decimals) {
ChaincodeStub stub = ctx.getStub();
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to set Options for these tokens
String clientMSPID = ctx.getClientIdentity().getMSPID();
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSPID.getValue())) {
throw new ChaincodeException(
"Client is not authorized to initialize contract", UNAUTHORIZED_SENDER.toString());
}
//check contract options are not already set, client is not authorized to change them once intitialized
String tokenName = stub.getStringState(ContractConstants.NAME_KEY.getValue());
if (!stringIsNullOrEmpty(tokenName)) {
throw new ChaincodeException("contract options are already set, client is not authorized to change them");
}
stub.putStringState(NAME_KEY.getValue(), name);
stub.putStringState(SYMBOL_KEY.getValue(), symbol);
stub.putStringState(DECIMALS_KEY.getValue(), decimals);
@ -420,6 +458,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String TokenName(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
String tokenName = ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue());
if (stringIsNullOrEmpty(tokenName)) {
throw new ChaincodeException("Token name not found", NOT_FOUND.toString());
@ -435,6 +475,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String TokenSymbol(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
String tokenSymbol = ctx.getStub().getStringState(SYMBOL_KEY.getValue());
if (stringIsNullOrEmpty(tokenSymbol)) {
throw new ChaincodeException("Token symbol not found", NOT_FOUND.toString());
@ -451,6 +493,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public int Decimals(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
String decimals = ctx.getStub().getStringState(DECIMALS_KEY.getValue());
if (stringIsNullOrEmpty(decimals)) {
throw new ChaincodeException("Decimal not found", NOT_FOUND.toString());
@ -466,4 +510,17 @@ public final class ERC20TokenContract implements ContractInterface {
private byte[] marshal(final Object obj) {
return new Genson().serialize(obj).getBytes(UTF_8);
}
/**
* Checks that contract options have been already initialized
*
* @param ctx the transaction context
* @return the number of decimals
*/
private void checkInitialized(final Context ctx) {
String tokenName = ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue());
if (stringIsNullOrEmpty(tokenName)) {
throw new ChaincodeException("Contract options need to be set before calling any function, call Initialize() to initialize contract", NOT_FOUND.toString());
}
}
}

View file

@ -58,7 +58,7 @@ public class TokenERC20ContractTest {
assertThat(thrown)
.isInstanceOf(ChaincodeException.class)
.hasNoCause()
.hasMessage("Token name not found");
.hasMessage("Contract options need to be set before calling any function, call Initialize() to initialize contract");
}
@Test
@ -67,6 +67,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(SYMBOL_KEY.getValue())).thenReturn("ARBT");
String toknName = contract.TokenSymbol(ctx);
assertThat(toknName).isEqualTo("ARBT");
@ -78,6 +79,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(SYMBOL_KEY.getValue())).thenReturn("");
Throwable thrown = catchThrowable(() -> contract.TokenSymbol(ctx));
assertThat(thrown)
@ -92,6 +94,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(DECIMALS_KEY.getValue())).thenReturn("18");
long decimal = contract.Decimals(ctx);
assertThat(decimal).isEqualTo(18);
@ -103,6 +106,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(DECIMALS_KEY.getValue())).thenReturn("");
Throwable thrown = catchThrowable(() -> contract.Decimals(ctx));
assertThat(thrown)
@ -117,6 +121,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn("222222222222");
long totalSupply = contract.TotalSupply(ctx);
assertThat(totalSupply).isEqualTo(222222222222L);
@ -128,6 +133,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn("");
Throwable thrown = catchThrowable(() -> contract.TotalSupply(ctx));
assertThat(thrown)
@ -143,6 +149,7 @@ public class TokenERC20ContractTest {
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId);
when(ctx.getStub()).thenReturn(stub);
@ -156,12 +163,15 @@ public class TokenERC20ContractTest {
class TokenOperationsInvoke {
@Test
public void invokeSetOptionsTest() {
public void invokeInitializeTest() {
ERC20TokenContract contract = new ERC20TokenContract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
contract.SetOptions(ctx, "ARBTToken", "ARBT", "18");
ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
contract.Initialize(ctx, "ARBTToken", "ARBT", "18");
verify(stub).putStringState(NAME_KEY.getValue(), "ARBTToken");
verify(stub).putStringState(SYMBOL_KEY.getValue(), "ARBT");
verify(stub).putStringState(DECIMALS_KEY.getValue(), "18");
@ -173,6 +183,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
@ -192,6 +203,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
@ -213,6 +225,7 @@ public class TokenERC20ContractTest {
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId);
when(ctx.getStub()).thenReturn(stub);
@ -234,6 +247,7 @@ public class TokenERC20ContractTest {
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ci.getMSPID()).thenReturn("Org2MSP");
when(ci.getId()).thenReturn(org1UserId);
when(ctx.getStub()).thenReturn(stub);
@ -256,6 +270,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId);
@ -284,6 +299,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId);
@ -312,6 +328,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId);
@ -343,6 +360,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId);
@ -374,6 +392,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId);
@ -394,6 +413,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn("Org2MSP");
when(ci.getId()).thenReturn(spender);
@ -418,6 +438,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn("Org1MSP");
when(ci.getId()).thenReturn(org1UserId);
@ -446,6 +467,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId);
@ -465,6 +487,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId);
@ -489,6 +512,7 @@ public class TokenERC20ContractTest {
+ " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
CompositeKey ckFromBalance = mock(CompositeKey.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.createCompositeKey(BALANCE_PREFIX.toString(), org1UserId))
.thenReturn(ckFromBalance);
when(ckFromBalance.toString()).thenReturn(BALANCE_PREFIX.getValue() + org1UserId);
@ -529,6 +553,7 @@ public class TokenERC20ContractTest {
+ " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
CompositeKey ckFromBalance = mock(CompositeKey.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), org1UserId))
.thenReturn(ckFromBalance);
when(ckFromBalance.toString()).thenReturn(BALANCE_PREFIX.getValue() + org1UserId);

View file

@ -30,7 +30,12 @@ class TokenERC20Contract extends Contract {
* @returns {String} Returns the name of the token
*/
async TokenName(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const nameBytes = await ctx.stub.getState(nameKey);
return nameBytes.toString();
}
@ -41,6 +46,10 @@ class TokenERC20Contract extends Contract {
* @returns {String} Returns the symbol of the token
*/
async Symbol(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const symbolBytes = await ctx.stub.getState(symbolKey);
return symbolBytes.toString();
}
@ -53,6 +62,10 @@ class TokenERC20Contract extends Contract {
* @returns {Number} Returns the number of decimals
*/
async Decimals(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const decimalsBytes = await ctx.stub.getState(decimalsKey);
const decimals = parseInt(decimalsBytes.toString());
return decimals;
@ -65,6 +78,10 @@ class TokenERC20Contract extends Contract {
* @returns {Number} Returns the total token supply
*/
async TotalSupply(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const totalSupplyBytes = await ctx.stub.getState(totalSupplyKey);
const totalSupply = parseInt(totalSupplyBytes.toString());
return totalSupply;
@ -78,6 +95,10 @@ class TokenERC20Contract extends Contract {
* @returns {Number} Returns the account balance
*/
async BalanceOf(ctx, owner) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const balanceKey = ctx.stub.createCompositeKey(balancePrefix, [owner]);
const balanceBytes = await ctx.stub.getState(balanceKey);
@ -99,6 +120,10 @@ class TokenERC20Contract extends Contract {
* @returns {Boolean} Return whether the transfer was successful or not
*/
async Transfer(ctx, to, value) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const from = ctx.clientIdentity.getID();
const transferResp = await this._transfer(ctx, from, to, value);
@ -123,6 +148,10 @@ class TokenERC20Contract extends Contract {
* @returns {Boolean} Return whether the transfer was successful or not
*/
async TransferFrom(ctx, from, to, value) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const spender = ctx.clientIdentity.getID();
// Retrieve the allowance of the spender
@ -149,7 +178,7 @@ class TokenERC20Contract extends Contract {
}
// Decrease the allowance
const updatedAllowance = currentAllowance - valueInt;
const updatedAllowance = this.sub(currentAllowance, valueInt);
await ctx.stub.putState(allowanceKey, Buffer.from(updatedAllowance.toString()));
console.log(`spender ${spender} allowance updated from ${currentAllowance} to ${updatedAllowance}`);
@ -202,8 +231,8 @@ class TokenERC20Contract extends Contract {
}
// Update the balance
const fromUpdatedBalance = fromCurrentBalance - valueInt;
const toUpdatedBalance = toCurrentBalance + valueInt;
const fromUpdatedBalance = this.sub(fromCurrentBalance, valueInt);
const toUpdatedBalance = this.add(toCurrentBalance, valueInt);
await ctx.stub.putState(fromBalanceKey, Buffer.from(fromUpdatedBalance.toString()));
await ctx.stub.putState(toBalanceKey, Buffer.from(toUpdatedBalance.toString()));
@ -223,6 +252,10 @@ class TokenERC20Contract extends Contract {
* @returns {Boolean} Return whether the approval was successful or not
*/
async Approve(ctx, spender, value) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const owner = ctx.clientIdentity.getID();
const allowanceKey = ctx.stub.createCompositeKey(allowancePrefix, [owner, spender]);
@ -247,6 +280,10 @@ class TokenERC20Contract extends Contract {
* @returns {Number} Return the amount of remaining tokens allowed to spent
*/
async Allowance(ctx, owner, spender) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const allowanceKey = ctx.stub.createCompositeKey(allowancePrefix, [owner, spender]);
const allowanceBytes = await ctx.stub.getState(allowanceKey);
@ -269,7 +306,19 @@ class TokenERC20Contract extends Contract {
* @param {String} decimals The decimals of the token
* @param {String} totalSupply The totalSupply of the token
*/
async SetOption(ctx, name, symbol, decimals) {
async Initialize(ctx, name, symbol, decimals) {
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to set Options for these tokens
const clientMSPID = ctx.clientIdentity.getMSPID();
if (clientMSPID !== 'Org1MSP') {
throw new Error('client is not authorized to initialize contract');
}
//check contract options are not already set, client is not authorized to change them once intitialized
const nameBytes = await ctx.stub.getState(nameKey);
if (nameBytes !== undefined) {
throw new Error('contract options are already set, client is not authorized to change them');
}
await ctx.stub.putState(nameKey, Buffer.from(name));
await ctx.stub.putState(symbolKey, Buffer.from(symbol));
await ctx.stub.putState(decimalsKey, Buffer.from(decimals));
@ -287,6 +336,9 @@ class TokenERC20Contract extends Contract {
*/
async Mint(ctx, amount) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
const clientMSPID = ctx.clientIdentity.getMSPID();
if (clientMSPID !== 'Org1MSP') {
@ -311,7 +363,7 @@ class TokenERC20Contract extends Contract {
} else {
currentBalance = parseInt(currentBalanceBytes.toString());
}
const updatedBalance = currentBalance + amountInt;
const updatedBalance = this.add(currentBalance, amountInt);
await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString()));
@ -324,7 +376,7 @@ class TokenERC20Contract extends Contract {
} else {
totalSupply = parseInt(totalSupplyBytes.toString());
}
totalSupply = totalSupply + amountInt;
totalSupply = this.add(totalSupply, amountInt);
await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString()));
// Emit the Transfer event
@ -344,6 +396,9 @@ class TokenERC20Contract extends Contract {
*/
async Burn(ctx, amount) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn tokens
const clientMSPID = ctx.clientIdentity.getMSPID();
if (clientMSPID !== 'Org1MSP') {
@ -361,7 +416,7 @@ class TokenERC20Contract extends Contract {
throw new Error('The balance does not exist');
}
const currentBalance = parseInt(currentBalanceBytes.toString());
const updatedBalance = currentBalance - amountInt;
const updatedBalance = this.sub(currentBalance, amountInt);
await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString()));
@ -370,7 +425,7 @@ class TokenERC20Contract extends Contract {
if (!totalSupplyBytes || totalSupplyBytes.length === 0) {
throw new Error('totalSupply does not exist.');
}
const totalSupply = parseInt(totalSupplyBytes.toString()) - amountInt;
const totalSupply = this.sub(parseInt(totalSupplyBytes.toString()), amountInt);
await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString()));
// Emit the Transfer event
@ -388,6 +443,10 @@ class TokenERC20Contract extends Contract {
* @returns {Number} Returns the account balance
*/
async ClientAccountBalance(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Get ID of submitting client identity
const clientAccountID = ctx.clientIdentity.getID();
@ -405,11 +464,40 @@ class TokenERC20Contract extends Contract {
// In this implementation, the client account ID is the clientId itself.
// Users can use this function to get their own account id, which they can then give to others as the payment address
async ClientAccountID(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Get ID of submitting client identity
const clientAccountID = ctx.clientIdentity.getID();
return clientAccountID;
}
//Checks that contract options have been already initialized
async CheckInitialized(ctx){
const nameBytes = await ctx.stub.getState(nameKey);
if (nameBytes === undefined) {
throw new Error('contract options need to be set before calling any function, call Initialize() to initialize contract');
}
}
// add two number checking for overflow
add(a, b) {
let c = a + b;
if (a !== c - b || b !== c - a){
throw new Error(`Math: addition overflow occurred ${a} + ${b}`);
}
return c;
}
// add two number checking for overflow
sub(a, b) {
let c = a - b;
if (a !== c + b || b !== a - c){
throw new Error(`Math: subtraction overflow occurred ${a} - ${b}`);
}
return c;
}
}
module.exports = TokenERC20Contract;

View file

@ -26,7 +26,7 @@ describe('Chaincode', () => {
let mockStub;
let mockClientIdentity;
beforeEach('Sandbox creation', () => {
beforeEach('Sandbox creation', async () => {
sandbox = sinon.createSandbox();
token = new TokenERC20Contract('token-erc20');
@ -36,6 +36,8 @@ describe('Chaincode', () => {
mockClientIdentity = sinon.createStubInstance(ClientIdentity);
ctx.clientIdentity = mockClientIdentity;
await token.Initialize(ctx, 'some name', 'some symbol', '2');
mockStub.putState.resolves('some state');
mockStub.setEvent.returns('set event');
@ -198,13 +200,18 @@ describe('Chaincode', () => {
});
});
describe('#SetOption', () => {
describe('#Initialize', () => {
it('should work', async () => {
const response = await token.SetOption(ctx, 'some name', 'some symbol', '2');
//we consider that is been already initialize in the before each statement
sinon.assert.calledWith(mockStub.putState, 'name', Buffer.from('some name'));
sinon.assert.calledWith(mockStub.putState, 'symbol', Buffer.from('some symbol'));
sinon.assert.calledWith(mockStub.putState, 'decimals', Buffer.from('2'));
expect(response).to.equals(true);
});
it('should failed if called a second time', async () => {
//we consider that is been already initialize in the before each statement
await expect(await token.Initialize(ctx, 'some name', 'some symbol', '2'))
.to.be.rejectedWith(Error, 'contract options are already set, client is not authorized to change them');
});
});

View file

@ -60,6 +60,16 @@ func _nftExists(ctx contractapi.TransactionContextInterface, tokenId string) boo
// param owner {String} An owner for whom to query the balance
// returns {int} The number of non-fungible tokens owned by the owner, possibly zero
func (c *TokenERC721Contract) BalanceOf(ctx contractapi.TransactionContextInterface, owner string) int {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
panic("failed to check if contract ia already initialized:"+ err.Error())
}
if !initialized {
panic("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// There is a key record for every non-fungible token in the format of balancePrefix.owner.tokenId.
// BalanceOf() queries for and counts all records matching balancePrefix.owner.*
@ -85,6 +95,16 @@ func (c *TokenERC721Contract) BalanceOf(ctx contractapi.TransactionContextInterf
// param {String} tokenId The identifier for a non-fungible token
// returns {String} Return the owner of the non-fungible token
func (c *TokenERC721Contract) OwnerOf(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
nft, err := _readNFT(ctx, tokenId)
if err != nil {
return "", fmt.Errorf("could not process OwnerOf for tokenId: %w", err)
@ -98,6 +118,16 @@ func (c *TokenERC721Contract) OwnerOf(ctx contractapi.TransactionContextInterfac
// param {String} tokenId the non-fungible token to approve
// returns {Boolean} Return whether the approval was successful or not
func (c *TokenERC721Contract) Approve(ctx contractapi.TransactionContextInterface, operator string, tokenId string) (bool, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return false, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
sender64, err := ctx.GetClientIdentity().GetID()
if err != nil {
return false, fmt.Errorf("failed to GetClientIdentity: %v", err)
@ -151,6 +181,16 @@ func (c *TokenERC721Contract) Approve(ctx contractapi.TransactionContextInterfac
// param {Boolean} approved True if the operator is approved, false to revoke approval
// returns {Boolean} Return whether the approval was successful or not
func (c *TokenERC721Contract) SetApprovalForAll(ctx contractapi.TransactionContextInterface, operator string, approved bool) (bool, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return false, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
sender64, err := ctx.GetClientIdentity().GetID()
if err != nil {
return false, fmt.Errorf("failed to GetClientIdentity: %v", err)
@ -196,6 +236,16 @@ func (c *TokenERC721Contract) SetApprovalForAll(ctx contractapi.TransactionConte
// param {String} operator The client that acts on behalf of the owner
// returns {Boolean} Return true if the operator is an approved operator for the owner, false otherwise
func (c *TokenERC721Contract) IsApprovedForAll(ctx contractapi.TransactionContextInterface, owner string, operator string) (bool, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return false,fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{owner, operator})
if err != nil {
return false, fmt.Errorf("failed to CreateCompositeKey: %v", err)
@ -223,6 +273,16 @@ func (c *TokenERC721Contract) IsApprovedForAll(ctx contractapi.TransactionContex
// param {String} tokenId the non-fungible token to find the approved client for
// returns {Object} Return the approved client for this non-fungible token, or null if there is none
func (c *TokenERC721Contract) GetApproved(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "false",fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "false", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
nft, err := _readNFT(ctx, tokenId)
if err != nil {
return "false", fmt.Errorf("failed GetApproved for tokenId : %v", err)
@ -239,6 +299,16 @@ func (c *TokenERC721Contract) GetApproved(ctx contractapi.TransactionContextInte
func (c *TokenERC721Contract) TransferFrom(ctx contractapi.TransactionContextInterface, from string, to string, tokenId string) (bool, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return false, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
sender64, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -336,6 +406,16 @@ func (c *TokenERC721Contract) TransferFrom(ctx contractapi.TransactionContextInt
// returns {String} Returns the name of the token
func (c *TokenERC721Contract) Name(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
bytes, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return "", fmt.Errorf("failed to get Name bytes: %s", err)
@ -348,6 +428,16 @@ func (c *TokenERC721Contract) Name(ctx contractapi.TransactionContextInterface)
// returns {String} Returns the symbol of the token
func (c *TokenERC721Contract) Symbol(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
bytes, err := ctx.GetStub().GetState(symbolKey)
if err != nil {
return "", fmt.Errorf("failed to get Symbol: %v", err)
@ -361,6 +451,16 @@ func (c *TokenERC721Contract) Symbol(ctx contractapi.TransactionContextInterface
// returns {String} Returns the URI of the token
func (c *TokenERC721Contract) TokenURI(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
nft, err := _readNFT(ctx, tokenId)
if err != nil {
return "", fmt.Errorf("failed to get TokenURI: %v", err)
@ -376,6 +476,16 @@ func (c *TokenERC721Contract) TokenURI(ctx contractapi.TransactionContextInterfa
// where each one of them has an assigned and queryable owner.
func (c *TokenERC721Contract) TotalSupply(ctx contractapi.TransactionContextInterface) int {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
panic("failed to check if contract ia already initialized:"+ err.Error())
}
if !initialized {
panic("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// There is a key record for every non-fungible token in the format of nftPrefix.tokenId.
// TotalSupply() queries for and counts all records matching nftPrefix.*
@ -399,11 +509,11 @@ func (c *TokenERC721Contract) TotalSupply(ctx contractapi.TransactionContextInte
}
// ============== ERC721 enumeration extension ===============
// Set optional information for a token.
// Set information for a token and intialize contract.
// param {String} name The name of the token
// param {String} symbol The symbol of the token
func (c *TokenERC721Contract) SetOption(ctx contractapi.TransactionContextInterface, name string, symbol string) (bool, error) {
func (c *TokenERC721Contract) Initialize(ctx contractapi.TransactionContextInterface, name string, symbol string) (bool, error) {
// Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the name and symbol
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
@ -412,6 +522,13 @@ func (c *TokenERC721Contract) SetOption(ctx contractapi.TransactionContextInterf
return false, fmt.Errorf("client is not authorized to set the name and symbol of the token")
}
bytes, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return false, fmt.Errorf("failed to get Name: %v", err)
}else if bytes != nil {
return false, fmt.Errorf("contract options are already set, client is not authorized to change them")
}
err = ctx.GetStub().PutState(nameKey, []byte(name))
if err != nil {
return false, fmt.Errorf("failed to PutState nameKey %s: %v", nameKey, err)
@ -432,6 +549,16 @@ func (c *TokenERC721Contract) SetOption(ctx contractapi.TransactionContextInterf
func (c *TokenERC721Contract) MintWithTokenURI(ctx contractapi.TransactionContextInterface, tokenId string, tokenURI string) (*Nft, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return nil, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return nil, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Check minter authorization - this sample assumes Org1 is the issuer with privilege to mint a new token
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
@ -518,6 +645,16 @@ func (c *TokenERC721Contract) MintWithTokenURI(ctx contractapi.TransactionContex
// param {String} tokenId Unique ID of a non-fungible token
// returns {Boolean} Return whether the burn was successful or not
func (c *TokenERC721Contract) Burn(ctx contractapi.TransactionContextInterface, tokenId string) (bool, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return false, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
owner64, err := ctx.GetClientIdentity().GetID()
if err != nil {
return false, fmt.Errorf("failed to GetClientIdentity owner64: %v", err)
@ -582,6 +719,16 @@ func (c *TokenERC721Contract) Burn(ctx contractapi.TransactionContextInterface,
// ClientAccountBalance returns the balance of the requesting client's account.
// returns {Number} Returns the account balance
func (c *TokenERC721Contract) ClientAccountBalance(ctx contractapi.TransactionContextInterface) (int, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
clientAccountID64, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -603,6 +750,16 @@ func (c *TokenERC721Contract) ClientAccountBalance(ctx contractapi.TransactionCo
// Users can use this function to get their own account id, which they can then give to others as the payment address
func (c *TokenERC721Contract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
clientAccountID64, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -617,3 +774,14 @@ func (c *TokenERC721Contract) ClientAccountID(ctx contractapi.TransactionContext
return clientAccount, nil
}
//Checks that contract options have been already initialized
func checkInitialized(ctx contractapi.TransactionContextInterface) (bool, error) {
tokenName, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return false, fmt.Errorf("failed to get token name: %v", err)
}else if (tokenName == nil){
return false, nil
}
return true, nil
}

View file

@ -242,11 +242,11 @@ func TestTokenURI(t *testing.T) {
assert.Equal(t, "https://example.com/nft101.json", tokenURI)
}
func TestSetOption(t *testing.T) {
func TestInitialize(t *testing.T) {
ctx, _ := setupStub()
c := new(TokenERC721Contract)
option, _ := c.SetOption(ctx, "someName", "someSymbol")
option, _ := c.Initialize(ctx, "someName", "someSymbol")
assert.Equal(t, true, option)
}

View file

@ -54,6 +54,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public long BalanceOf(final Context ctx, final String owner) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final ChaincodeStub stub = ctx.getStub();
final CompositeKey balanceKey =
stub.createCompositeKey(ContractConstants.BALANCE.getValue(), owner);
@ -76,6 +78,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String OwnerOf(final Context ctx, final String tokenId) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final NFT nft = this._readNft(ctx, tokenId);
if (stringIsNullOrEmpty(nft.getOwner())) {
final String errorMessage = String.format("No owner is assigned o the token %s", tokenId);
@ -94,6 +98,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public boolean IsApprovedForAll(final Context ctx, final String owner, final String operator) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final ChaincodeStub stub = ctx.getStub();
final CompositeKey approvalKey =
stub.createCompositeKey(ContractConstants.APPROVAL.getValue(), owner, operator);
@ -116,6 +122,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void Approve(final Context ctx, final String operator, final String tokenId) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final ChaincodeStub stub = ctx.getStub();
final String sender = ctx.getClientIdentity().getId();
NFT nft = this._readNft(ctx, tokenId);
@ -144,6 +152,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void SetApprovalForAll(final Context ctx, final String operator, final boolean approved) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final String sender = ctx.getClientIdentity().getId();
final ChaincodeStub stub = ctx.getStub();
final Approval nftApproval = new Approval(sender, operator, approved);
@ -163,6 +173,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String GetApproved(final Context ctx, final String tokenId) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final NFT nft = this._readNft(ctx, tokenId);
return nft.getApproved();
}
@ -178,6 +190,8 @@ public class ERC721TokenContract implements ContractInterface {
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void TransferFrom(
final Context ctx, final String from, final String to, final String tokenId) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final String sender = ctx.getClientIdentity().getId();
final ChaincodeStub stub = ctx.getStub();
NFT nft = this._readNft(ctx, tokenId);
@ -235,6 +249,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String Name(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
return ctx.getStub().getStringState(ContractConstants.NAMEKEY.getValue());
}
@ -246,6 +262,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String Symbol(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
return ctx.getStub().getStringState(ContractConstants.SYMBOLKEY.getValue());
}
@ -258,6 +276,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String TokenURI(final Context ctx, final String tokenId) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final NFT nft = this._readNft(ctx, tokenId);
return nft.getTokenURI();
}
@ -275,6 +295,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public long TotalSupply(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final ChaincodeStub stub = ctx.getStub();
final CompositeKey nftKey = stub.createCompositeKey(ContractConstants.NFT.getValue());
final QueryResultsIterator<KeyValue> iterator = stub.getStateByPartialCompositeKey(nftKey);
@ -297,16 +319,24 @@ public class ERC721TokenContract implements ContractInterface {
* @param symbol The symbol of the token
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void SetOption(final Context ctx, final String name, final String symbol) {
public void Initialize(final Context ctx, final String name, final String symbol) {
final ChaincodeStub stub = ctx.getStub();
final String clientMSPID = ctx.getClientIdentity().getMSPID();
// Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the
// name and symbol
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSP.getValue())) {
throw new ChaincodeException(
"Client is not authorized to set the name and symbol of the token");
"Client is not authorized to initialize the contract (set the name and symbol of the token)");
}
final ChaincodeStub stub = ctx.getStub();
//check contract options are not already set, client is not authorized to change them once intitialized
String tokenName = stub.getStringState(ContractConstants.NAMEKEY.getValue());
if (!stringIsNullOrEmpty(tokenName)) {
throw new ChaincodeException("contract options are already set, client is not authorized to change them");
}
stub.putStringState(ContractConstants.NAMEKEY.getValue(), name);
stub.putStringState(ContractConstants.SYMBOLKEY.getValue(), symbol);
}
@ -321,6 +351,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public NFT MintWithTokenURI(final Context ctx, final String tokenId, final String tokenURI) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final String clientMSPID = ctx.getClientIdentity().getMSPID();
final ChaincodeStub stub = ctx.getStub();
// Check minter authorization this sample assumes Org1 is the issuer with privilege to mint a
@ -360,6 +392,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void Burn(final Context ctx, final String tokenId) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
final ChaincodeStub stub = ctx.getStub();
final String owner = ctx.getClientIdentity().getId();
// Check if a caller is the owner of the non-fungible token
@ -390,6 +424,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public long ClientAccountBalance(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
return this.BalanceOf(ctx, ctx.getClientIdentity().getId());
}
@ -403,6 +439,8 @@ public class ERC721TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String ClientAccountID(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
return ctx.getClientIdentity().getId();
}
@ -437,4 +475,17 @@ public class ERC721TokenContract implements ContractInterface {
final String nft = stub.getStringState(nftKey.toString());
return ((stringIsNullOrEmpty(nft)) ? false : true);
}
/**
* Checks that contract options have been already initialized
*
* @param ctx the transaction context
* @return the number of decimals
*/
private void checkInitialized(final Context ctx) {
String tokenName = ctx.getStub().getStringState(ContractConstants.NAMEKEY.getValue());
if (stringIsNullOrEmpty(tokenName)) {
throw new ChaincodeException("Contract options need to be set before calling any function, call Initialize() to initialize contract", ContractErrors.TOKEN_NOT_FOUND.toString());
}
}
}

View file

@ -84,6 +84,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
List<KeyValue> list = new ArrayList<>();
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
list.add(new MockKeyValue("balance_Alice_101", "\u0000"));
list.add(new MockKeyValue("balance_Alice_101", "\u0000"));
when(ctx.getStub()).thenReturn(stub);
@ -103,6 +104,7 @@ public class ERC721TokenContractTest {
NFT nft = new NFT("101", "Alicd", "http://test.com", "");
when(ctx.getStub()).thenReturn(stub);
CompositeKey ck = mock(CompositeKey.class);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");
when(stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck);
when(stub.getStringState(ck.toString())).thenReturn(nft.toJSONString());
@ -129,6 +131,7 @@ public class ERC721TokenContractTest {
this.stub = mock(ChaincodeStub.class);
when(this.ctx.getStub()).thenReturn(stub);
contract = new ERC721TokenContract();
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
CompositeKey ck1 = mock(CompositeKey.class);
when(ck1.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");
when(this.stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck1);
@ -256,6 +259,7 @@ public class ERC721TokenContractTest {
ChaincodeStub stub = mock(ChaincodeStub.class);
NFT nft = new NFT("101", "Alice", "http://test.com", "");
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
CompositeKey ck = mock(CompositeKey.class);
when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");
when(stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck);
@ -281,6 +285,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
ClientIdentity ci = null;
ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
@ -306,6 +311,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
ClientIdentity ci = null;
ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
@ -327,6 +333,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
ClientIdentity ci = null;
ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
@ -362,6 +369,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ANFT");
ERC721TokenContract contract = new ERC721TokenContract();
final String name = contract.Name(ctx);
@ -373,6 +381,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
ERC721TokenContract contract = new ERC721TokenContract();
final NFT nft = new NFT("101", "Alice", "http://test.com", "Bob");
CompositeKey ck = mock(CompositeKey.class);
@ -388,6 +397,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
List<KeyValue> list = new ArrayList<>();
list.add(
new MockKeyValue(
@ -417,6 +427,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
final NFT nft = new NFT("101", "Alice", "DummyURI", "");
CompositeKey ck = mock(CompositeKey.class);
when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");
@ -445,6 +456,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
final NFT nft = new NFT("101", "Alice", "DummyURI", "");
CompositeKey ck = mock(CompositeKey.class);
when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");
@ -471,6 +483,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
final NFT nft = new NFT("101", "Alice", "DummyURI", "");
CompositeKey ck = mock(CompositeKey.class);
when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");

View file

@ -25,6 +25,9 @@ class TokenERC721Contract extends Contract {
* @returns {Number} The number of non-fungible tokens owned by the owner, possibly zero
*/
async BalanceOf(ctx, owner) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// There is a key record for every non-fungible token in the format of balancePrefix.owner.tokenId.
// BalanceOf() queries for and counts all records matching balancePrefix.owner.*
const iterator = await ctx.stub.getStateByPartialCompositeKey(balancePrefix, [owner]);
@ -47,6 +50,9 @@ class TokenERC721Contract extends Contract {
* @returns {String} Return the owner of the non-fungible token
*/
async OwnerOf(ctx, tokenId) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const nft = await this._readNFT(ctx, tokenId);
const owner = nft.owner;
if (!owner) {
@ -67,6 +73,9 @@ class TokenERC721Contract extends Contract {
* @returns {Boolean} Return whether the transfer was successful or not
*/
async TransferFrom(ctx, from, to, tokenId) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const sender = ctx.clientIdentity.getID();
const nft = await this._readNFT(ctx, tokenId);
@ -118,6 +127,9 @@ class TokenERC721Contract extends Contract {
* @returns {Boolean} Return whether the approval was successful or not
*/
async Approve(ctx, approved, tokenId) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const sender = ctx.clientIdentity.getID();
const nft = await this._readNFT(ctx, tokenId);
@ -153,6 +165,9 @@ class TokenERC721Contract extends Contract {
* @returns {Boolean} Return whether the approval was successful or not
*/
async SetApprovalForAll(ctx, operator, approved) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const sender = ctx.clientIdentity.getID();
const approval = { owner: sender, operator: operator, approved: approved };
@ -174,6 +189,9 @@ class TokenERC721Contract extends Contract {
* @returns {Object} Return the approved client for this non-fungible token, or null if there is none
*/
async GetApproved(ctx, tokenId) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const nft = await this._readNFT(ctx, tokenId);
return nft.approved;
}
@ -187,6 +205,9 @@ class TokenERC721Contract extends Contract {
* @returns {Boolean} Return true if the operator is an approved operator for the owner, false otherwise
*/
async IsApprovedForAll(ctx, owner, operator) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const approvalKey = ctx.stub.createCompositeKey(approvalPrefix, [owner, operator]);
const approvalBytes = await ctx.stub.getState(approvalKey);
let approved;
@ -209,6 +230,9 @@ class TokenERC721Contract extends Contract {
* @returns {String} Returns the name of the token
*/
async Name(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const nameAsBytes = await ctx.stub.getState(nameKey);
return nameAsBytes.toString();
}
@ -220,6 +244,9 @@ class TokenERC721Contract extends Contract {
* @returns {String} Returns the symbol of the token
*/
async Symbol(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const symbolAsBytes = await ctx.stub.getState(symbolKey);
return symbolAsBytes.toString();
}
@ -232,6 +259,9 @@ class TokenERC721Contract extends Contract {
* @returns {String} Returns the URI of the token
*/
async TokenURI(ctx, tokenId) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const nft = await this._readNFT(ctx, tokenId);
return nft.tokenURI;
}
@ -246,6 +276,9 @@ class TokenERC721Contract extends Contract {
* where each one of them has an assigned and queryable owner.
*/
async TotalSupply(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// There is a key record for every non-fungible token in the format of nftPrefix.tokenId.
// TotalSupply() queries for and counts all records matching nftPrefix.*
const iterator = await ctx.stub.getStateByPartialCompositeKey(nftPrefix, []);
@ -269,14 +302,20 @@ class TokenERC721Contract extends Contract {
* @param {String} name The name of the token
* @param {String} symbol The symbol of the token
*/
async SetOption(ctx, name, symbol) {
async Initialize(ctx, name, symbol) {
// Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the name and symbol
// Check minter authorization - this sample assumes Org1 is the issuer with privilege to initialize contract (set the name and symbol)
const clientMSPID = ctx.clientIdentity.getMSPID();
if (clientMSPID !== 'Org1MSP') {
throw new Error('client is not authorized to set the name and symbol of the token');
}
//check contract options are not already set, client is not authorized to change them once intitialized
const nameBytes = await ctx.stub.getState(nameKey);
if (nameBytes !== undefined) {
throw new Error('contract options are already set, client is not authorized to change them');
}
await ctx.stub.putState(nameKey, Buffer.from(name));
await ctx.stub.putState(symbolKey, Buffer.from(symbol));
return true;
@ -291,6 +330,8 @@ class TokenERC721Contract extends Contract {
* @returns {Object} Return the non-fungible token object
*/
async MintWithTokenURI(ctx, tokenId, tokenURI) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Check minter authorization - this sample assumes Org1 is the issuer with privilege to mint a new token
const clientMSPID = ctx.clientIdentity.getMSPID();
@ -341,6 +382,9 @@ class TokenERC721Contract extends Contract {
* @returns {Boolean} Return whether the burn was successful or not
*/
async Burn(ctx, tokenId) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const owner = ctx.clientIdentity.getID();
// Check if a caller is the owner of the non-fungible token
@ -388,6 +432,9 @@ class TokenERC721Contract extends Contract {
* @returns {Number} Returns the account balance
*/
async ClientAccountBalance(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Get ID of submitting client identity
const clientAccountID = ctx.clientIdentity.getID();
return this.BalanceOf(ctx, clientAccountID);
@ -397,10 +444,21 @@ class TokenERC721Contract extends Contract {
// In this implementation, the client account ID is the clientId itself.
// Users can use this function to get their own account id, which they can then give to others as the payment address
async ClientAccountID(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Get ID of submitting client identity
const clientAccountID = ctx.clientIdentity.getID();
return clientAccountID;
}
//Checks that contract options have been already initialized
async CheckIntitialized(ctx){
const nameBytes = await ctx.stub.getState(nameKey);
if (nameBytes === undefined) {
throw new Error('contract options need to be set before calling any function, call Initialize() to initialize contract');
}
}
}
module.exports = TokenERC721Contract;

View file

@ -20,9 +20,23 @@ type UTXO struct {
Amount int `json:"amount"`
}
// Define key names for options
const nameKey = "name"
const symbolKey = "symbol"
const totalSupplyKey = "totalSupply"
// Mint creates a new unspent transaction output (UTXO) owned by the minter
func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount int) (*UTXO, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return nil, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return nil, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
@ -63,6 +77,15 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount
// Transfer transfers UTXOs containing tokens from client to recipient(s)
func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, utxoInputKeys []string, utxoOutputs []UTXO) ([]UTXO, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return nil, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return nil, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -100,7 +123,7 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, ut
Amount: amount,
}
totalInputAmount += amount
totalInputAmount,err = add(totalInputAmount, amount)
utxoInputs[utxoInputKey] = utxoInput
}
@ -115,7 +138,7 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, ut
utxoOutputs[i].Key = fmt.Sprintf("%s.%d", txID, i)
totalOutputAmount += utxoOutput.Amount
totalOutputAmount,err = add(totalOutputAmount, utxoOutput.Amount)
}
// Validate total inputs equals total outputs
@ -158,6 +181,15 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, ut
// ClientUTXOs returns all UTXOs owned by the calling client
func (s *SmartContract) ClientUTXOs(ctx contractapi.TransactionContextInterface) ([]*UTXO, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return nil, fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return nil, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -211,6 +243,15 @@ func (s *SmartContract) ClientUTXOs(ctx contractapi.TransactionContextInterface)
// Users can use this function to get their own client id, which they can then give to others as the payment address
func (s *SmartContract) ClientID(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
// Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
@ -219,3 +260,109 @@ func (s *SmartContract) ClientID(ctx contractapi.TransactionContextInterface) (s
return clientID, nil
}
// Name returns a descriptive name for fungible tokens in this contract
// returns {String} Returns the name of the token
func (s *SmartContract) Name(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
bytes, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return "", fmt.Errorf("failed to get Name bytes: %s", err)
}
return string(bytes), nil
}
// Symbol returns an abbreviated name for fungible tokens in this contract.
// returns {String} Returns the symbol of the token
func (s *SmartContract) Symbol(ctx contractapi.TransactionContextInterface) (string, error) {
//check if contract has been intilized first
initialized, err := checkInitialized(ctx)
if err != nil {
return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err)
}
if !initialized {
return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract")
}
bytes, err := ctx.GetStub().GetState(symbolKey)
if err != nil {
return "", fmt.Errorf("failed to get Symbol: %v", err)
}
return string(bytes), nil
}
// Set information for a token and intialize contract.
// param {String} name The name of the token
// param {String} symbol The symbol of the token
func (s *SmartContract) Initialize(ctx contractapi.TransactionContextInterface, name string, symbol string) (bool, error) {
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to intitialize contract
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return false, fmt.Errorf("failed to get MSPID: %v", err)
} else if clientMSPID != "Org1MSP" {
return false, fmt.Errorf("client is not authorized to initialize contract")
}
//check contract options are not already set, client is not authorized to change them once intitialized
bytes, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return false, fmt.Errorf("failed to get Name: %v", err)
}else if bytes != nil {
return false, fmt.Errorf("contract options are already set, client is not authorized to change them")
}
err = ctx.GetStub().PutState(nameKey, []byte(name))
if err != nil {
return false, fmt.Errorf("failed to set token name: %v", err)
}
err = ctx.GetStub().PutState(symbolKey, []byte(symbol))
if err != nil {
return false, fmt.Errorf("failed to set symbol: %v", err)
}
log.Printf("name: %v, symbol: %v", name, symbol)
return true, nil;
}
//Checks that contract options have been already initialized
func checkInitialized(ctx contractapi.TransactionContextInterface) (bool, error) {
tokenName, err := ctx.GetStub().GetState(nameKey)
if err != nil {
return false, fmt.Errorf("failed to get token name: %v", err)
}else if (tokenName == nil){
return false , nil
}
return true , nil
}
// add two number checking for overflow
func add(b int, q int) (int, error) {
// Check overflow
var sum int
sum = q + b
if (sum < q) == (b > 0 && q > 0) {
return 0, fmt.Errorf("Math: addition overflow occurred %d + %d", b, q)
}
return sum, nil
}