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" const minterMSPID = "Org1MSP"
// Define key names for options
const nameKey = "name"
const symbolKey = "symbol"
// SmartContract provides functions for transferring tokens between accounts // SmartContract provides functions for transferring tokens between accounts
type SmartContract struct { type SmartContract struct {
contractapi.Contract contractapi.Contract
@ -113,8 +117,17 @@ type ToID struct {
// This function emits a TransferSingle event. // This function emits a TransferSingle event.
func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error { 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 // 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 { if err != nil {
return err return err
} }
@ -140,12 +153,21 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, accoun
// This function emits a TransferBatch event. // This function emits a TransferBatch event.
func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error { 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) { if len(ids) != len(amounts) {
return fmt.Errorf("ids and amounts must have the same length") 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 // 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 { if err != nil {
return err return err
} }
@ -160,7 +182,10 @@ func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, a
amountToSend := make(map[uint64]uint64) // token id => amount amountToSend := make(map[uint64]uint64) // token id => amount
for i := 0; i < len(amounts); i++ { 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 // 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. // This function triggers a TransferSingle event.
func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error { 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" { if account == "0x0" {
return fmt.Errorf("burn to the zero address") 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 // 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 { if err != nil {
return err return err
} }
@ -214,6 +248,15 @@ func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, accoun
// This function emits a TransferBatch event. // This function emits a TransferBatch event.
func (s *SmartContract) BurnBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error { 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" { if account == "0x0" {
return fmt.Errorf("burn to the zero address") 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 // 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 { if err != nil {
return err 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 // recipient account must be a valid clientID as returned by the ClientID() function
// This function triggers a TransferSingle event // This function triggers a TransferSingle event
func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error { 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 { if sender == recipient {
return fmt.Errorf("transfer to self") 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 // recipient account must be a valid clientID as returned by the ClientID() function
// This function triggers a TransferBatch event // This function triggers a TransferBatch event
func (s *SmartContract) BatchTransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, ids []uint64, amounts []uint64) error { 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 { if sender == recipient {
return fmt.Errorf("transfer to self") 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 amountToSend := make(map[uint64]uint64) // token id => amount
for i := 0; i < len(amounts); i++ { 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 // 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 // This function triggers a TransferBatchMultiRecipient event
func (s *SmartContract) BatchTransferFromMultiRecipient(ctx contractapi.TransactionContextInterface, sender string, recipients []string, ids []uint64, amounts []uint64) error { 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) { if len(recipients) != len(ids) || len(ids) != len(amounts) {
return fmt.Errorf("recipients, ids, and amounts must have the same length") 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 amountToSend := make(map[ToID]uint64) // (recipient, id ) => amount
for i := 0; i < len(amounts); i++ { 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 // 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. // _isApprovedForAll returns true if operator is approved to transfer account's tokens.
func _isApprovedForAll(ctx contractapi.TransactionContextInterface, account string, operator string) (bool, error) { 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}) approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{account, operator})
if err != nil { if err != nil {
return false, fmt.Errorf("failed to create the composite key for prefix %s: %v", approvalPrefix, err) 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. // SetApprovalForAll returns true if operator is approved to transfer account's tokens.
func (s *SmartContract) SetApprovalForAll(ctx contractapi.TransactionContextInterface, operator string, approved bool) error { 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 // Get ID of submitting client identity
account, err := ctx.GetClientIdentity().GetID() account, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
@ -490,11 +588,31 @@ func (s *SmartContract) SetApprovalForAll(ctx contractapi.TransactionContextInte
// BalanceOf returns the balance of the given account // BalanceOf returns the balance of the given account
func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, account string, id uint64) (uint64, error) { 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) return balanceOfHelper(ctx, account, id)
} }
// BalanceOfBatch returns the balance of multiple account/token pairs // BalanceOfBatch returns the balance of multiple account/token pairs
func (s *SmartContract) BalanceOfBatch(ctx contractapi.TransactionContextInterface, accounts []string, ids []uint64) ([]uint64, error) { 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) { if len(accounts) != len(ids) {
return nil, fmt.Errorf("accounts and ids must have the same length") 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 // ClientAccountBalance returns the balance of the requesting client's account
func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextInterface, id uint64) (uint64, error) { 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 // Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID() clientID, err := ctx.GetClientIdentity().GetID()
if err != nil { 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 // 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) { 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 // Get ID of submitting client identity
clientAccountID, err := ctx.GetClientIdentity().GetID() clientAccountID, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
@ -542,8 +678,17 @@ func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterf
// This function triggers URI event for each token id // This function triggers URI event for each token id
func (s *SmartContract) SetURI(ctx contractapi.TransactionContextInterface, uri string) error { 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 // 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 { if err != nil {
return err return err
} }
@ -563,6 +708,15 @@ func (s *SmartContract) SetURI(ctx contractapi.TransactionContextInterface, uri
// URI returns the URI // URI returns the URI
func (s *SmartContract) URI(ctx contractapi.TransactionContextInterface, id uint64) (string, error) { 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) uriBytes, err := ctx.GetStub().GetState(uriKey)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to get uri: %v", err) 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 { 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 // 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 { if err != nil {
return err return err
} }
@ -594,6 +757,84 @@ func (s *SmartContract) BroadcastTokenExistance(ctx contractapi.TransactionConte
return emitTransferSingle(ctx, transferSingleEvent) 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 // Helper Functions
// authorizationHelper checks minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens // 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, _ = 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))) err = ctx.GetStub().PutState(balanceKey, []byte(strconv.FormatUint(uint64(balance), 10)))
if err != nil { 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 { func removeBalance(ctx contractapi.TransactionContextInterface, sender string, ids []uint64, amounts []uint64) error {
// Calculate the total amount of each token to withdraw // Calculate the total amount of each token to withdraw
necessaryFunds := make(map[uint64]uint64) // token id -> necessary amount necessaryFunds := make(map[uint64]uint64) // token id -> necessary amount
var err error
for i := 0; i < len(amounts); i++ { 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 // 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) 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) _, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(queryResponse.Key)
if err != nil { 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) return fmt.Errorf("sender has insufficient funds for token %v, needed funds: %v, available fund: %v", tokenId, neededAmount, partialBalance)
} else if partialBalance > neededAmount { } else if partialBalance > neededAmount {
// Send the remainder back to the sender // Send the remainder back to the sender
remainder := partialBalance - neededAmount remainder, err := sub(partialBalance, neededAmount)
if err != nil {
return err
}
if selfRecipientKeyNeedsToBeRemoved { if selfRecipientKeyNeedsToBeRemoved {
// Set balance for the key that has the same address for sender and recipient // Set balance for the key that has the same address for sender and recipient
err = setBalance(ctx, sender, sender, tokenId, remainder) 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) balAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64)
balance += balAmount balance, err = add(balance, balAmount)
if err != nil {
return 0, err
}
} }
return balance, nil return balance, nil
@ -859,3 +1117,42 @@ func sortedKeysToID(m map[ToID]uint64) []ToID {
}) })
return keys 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 // Define key names for options
const nameKey = "name"
const symbolKey = "symbol"
const decimalsKey = "decimals"
const totalSupplyKey = "totalSupply" const totalSupplyKey = "totalSupply"
// Define objectType names for prefix // Define objectType names for prefix
const allowancePrefix = "allowance" const allowancePrefix = "allowance"
// Define key names for options
// SmartContract provides functions for transferring tokens between accounts // SmartContract provides functions for transferring tokens between accounts
type SmartContract struct { type SmartContract struct {
contractapi.Contract contractapi.Contract
@ -32,6 +37,15 @@ type event struct {
// This function triggers a Transfer event // This function triggers a Transfer event
func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount int) error { 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 // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
clientMSPID, err := ctx.GetClientIdentity().GetMSPID() clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil { 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. 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))) err = ctx.GetStub().PutState(minter, []byte(strconv.Itoa(updatedBalance)))
if err != nil { 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 // 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))) err = ctx.GetStub().PutState(totalSupplyKey, []byte(strconv.Itoa(totalSupply)))
if err != nil { if err != nil {
return err return err
@ -114,6 +135,14 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount
// This function triggers a Transfer event // This function triggers a Transfer event
func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, amount int) error { 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 // Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens
clientMSPID, err := ctx.GetClientIdentity().GetMSPID() clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil { 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. 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))) err = ctx.GetStub().PutState(minter, []byte(strconv.Itoa(updatedBalance)))
if err != nil { 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. 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 // 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))) err = ctx.GetStub().PutState(totalSupplyKey, []byte(strconv.Itoa(totalSupply)))
if err != nil { if err != nil {
return err return err
@ -195,6 +231,15 @@ func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, amount
// This function triggers a Transfer event // This function triggers a Transfer event
func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, recipient string, amount int) error { 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 // Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID() clientID, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
@ -222,6 +267,16 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, re
// BalanceOf returns the balance of the given account // BalanceOf returns the balance of the given account
func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, account string) (int, error) { 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) balanceBytes, err := ctx.GetStub().GetState(account)
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to read from world state: %v", err) 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 // ClientAccountBalance returns the balance of the requesting client's account
func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextInterface) (int, error) { 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 // Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID() clientID, err := ctx.GetClientIdentity().GetID()
if err != nil { 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 // 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) { 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 // Get ID of submitting client identity
clientAccountID, err := ctx.GetClientIdentity().GetID() clientAccountID, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
@ -274,6 +347,15 @@ func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterf
// TotalSupply returns the total token supply // TotalSupply returns the total token supply
func (s *SmartContract) TotalSupply(ctx contractapi.TransactionContextInterface) (int, error) { 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 // Retrieve total supply of tokens from state of smart contract
totalSupplyBytes, err := ctx.GetStub().GetState(totalSupplyKey) totalSupplyBytes, err := ctx.GetStub().GetState(totalSupplyKey)
if err != nil { if err != nil {
@ -299,6 +381,15 @@ func (s *SmartContract) TotalSupply(ctx contractapi.TransactionContextInterface)
// This function triggers an Approval event // This function triggers an Approval event
func (s *SmartContract) Approve(ctx contractapi.TransactionContextInterface, spender string, value int) error { 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 // Get ID of submitting client identity
owner, err := ctx.GetClientIdentity().GetID() owner, err := ctx.GetClientIdentity().GetID()
if err != nil { 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 // 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) { 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 // Create allowanceKey
allowanceKey, err := ctx.GetStub().CreateCompositeKey(allowancePrefix, []string{owner, spender}) allowanceKey, err := ctx.GetStub().CreateCompositeKey(allowancePrefix, []string{owner, spender})
if err != nil { if err != nil {
@ -366,6 +466,15 @@ func (s *SmartContract) Allowance(ctx contractapi.TransactionContextInterface, o
// This function triggers a Transfer event // This function triggers a Transfer event
func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface, from string, to string, value int) error { 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 // Get ID of submitting client identity
spender, err := ctx.GetClientIdentity().GetID() spender, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
@ -399,7 +508,11 @@ func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface
} }
// Decrease the allowance // 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))) err = ctx.GetStub().PutState(allowanceKey, []byte(strconv.Itoa(updatedAllowance)))
if err != nil { if err != nil {
return err return err
@ -421,6 +534,90 @@ func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface
return nil 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 // Helper Functions
// transferHelper is a helper function that transfers tokens from the "from" address to the "to" address // 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. 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 fromUpdatedBalance, err := sub(fromCurrentBalance, value)
toUpdatedBalance := toCurrentBalance + 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))) err = ctx.GetStub().PutState(from, []byte(strconv.Itoa(fromUpdatedBalance)))
if err != nil { if err != nil {
@ -481,3 +685,44 @@ func transferHelper(ctx contractapi.TransactionContextInterface, from string, to
return nil 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"), BALANCE_PREFIX("balance"),
ALLOWANCE_PREFIX("allowance"), ALLOWANCE_PREFIX("allowance"),
NAME_KEY("name"), NAME_KEY("name"),
SYMBOL_KEY("decimals"), SYMBOL_KEY("symbolKey"),
DECIMALS_KEY("symbolKey"), DECIMALS_KEY("decimals"),
TOTAL_SUPPLY_KEY("totalSupply"), TOTAL_SUPPLY_KEY("totalSupply"),
TRANSFER_EVENT("Transfer"), TRANSFER_EVENT("Transfer"),
MINTER_ORG_MSPID("Org1MSP"), MINTER_ORG_MSPID("Org1MSP"),

View file

@ -76,6 +76,10 @@ public final class ERC20TokenContract implements ContractInterface {
throw new ChaincodeException( throw new ChaincodeException(
"Client is not authorized to mint new tokens", UNAUTHORIZED_SENDER.toString()); "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 // Get ID of submitting client identity
String minter = ctx.getClientIdentity().getId(); String minter = ctx.getClientIdentity().getId();
if (amount <= 0) { if (amount <= 0) {
@ -127,6 +131,10 @@ public final class ERC20TokenContract implements ContractInterface {
throw new ChaincodeException( throw new ChaincodeException(
"Client is not authorized to burn tokens", UNAUTHORIZED_SENDER.toString()); "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(); String minter = ctx.getClientIdentity().getId();
if (amount <= 0) { if (amount <= 0) {
throw new ChaincodeException( throw new ChaincodeException(
@ -173,6 +181,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.SUBMIT) @Transaction(intent = Transaction.TYPE.SUBMIT)
public void Transfer(final Context ctx, final String to, final long value) { 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(); String from = ctx.getClientIdentity().getId();
this.transferHelper(ctx, from, to, value); this.transferHelper(ctx, from, to, value);
final Transfer transferEvent = new Transfer(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) @Transaction(intent = Transaction.TYPE.EVALUATE)
public long BalanceOf(final Context ctx, final String owner) { 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(); ChaincodeStub stub = ctx.getStub();
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), owner); CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), owner);
String balance = stub.getStringState(balanceKey.toString()); String balance = stub.getStringState(balanceKey.toString());
@ -207,6 +219,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public long ClientAccountBalance(final Context ctx) { 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 // Get ID of submitting client identity
ChaincodeStub stub = ctx.getStub(); ChaincodeStub stub = ctx.getStub();
String clientAccountID = ctx.getClientIdentity().getId(); String clientAccountID = ctx.getClientIdentity().getId();
@ -230,6 +244,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public String ClientAccountID(final Context ctx) { 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 // Get ID of submitting client identity
return ctx.getClientIdentity().getId(); return ctx.getClientIdentity().getId();
} }
@ -242,6 +258,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public long TotalSupply(final Context ctx) { 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()); String totalSupply = ctx.getStub().getStringState(TOTAL_SUPPLY_KEY.getValue());
if (stringIsNullOrEmpty(totalSupply)) { if (stringIsNullOrEmpty(totalSupply)) {
throw new ChaincodeException("Total Supply not found", NOT_FOUND.toString()); 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) @Transaction(intent = Transaction.TYPE.SUBMIT)
public void Approve(final Context ctx, final String spender, final long value) { 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(); ChaincodeStub stub = ctx.getStub();
String owner = ctx.getClientIdentity().getId(); String owner = ctx.getClientIdentity().getId();
CompositeKey allowanceKey = CompositeKey allowanceKey =
@ -282,6 +302,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.SUBMIT) @Transaction(intent = Transaction.TYPE.SUBMIT)
public long Allowance(final Context ctx, final String owner, final String spender) { 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(); ChaincodeStub stub = ctx.getStub();
CompositeKey allowanceKey = CompositeKey allowanceKey =
stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), owner, spender); stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), owner, spender);
@ -308,6 +330,8 @@ public final class ERC20TokenContract implements ContractInterface {
@Transaction(intent = Transaction.TYPE.SUBMIT) @Transaction(intent = Transaction.TYPE.SUBMIT)
public void TransferFrom( public void TransferFrom(
final Context ctx, final String from, final String to, final long value) { 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(); String spender = ctx.getClientIdentity().getId();
ChaincodeStub stub = ctx.getStub(); ChaincodeStub stub = ctx.getStub();
// Retrieve the allowance of the spender // Retrieve the allowance of the spender
@ -402,9 +426,23 @@ public final class ERC20TokenContract implements ContractInterface {
* @param decimals The decimals of the token * @param decimals The decimals of the token
*/ */
@Transaction(intent = Transaction.TYPE.SUBMIT) @Transaction(intent = Transaction.TYPE.SUBMIT)
public void SetOptions( public void Initialize(
final Context ctx, final String name, final String symbol, final String decimals) { final Context ctx, final String name, final String symbol, final String decimals) {
ChaincodeStub stub = ctx.getStub(); 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(NAME_KEY.getValue(), name);
stub.putStringState(SYMBOL_KEY.getValue(), symbol); stub.putStringState(SYMBOL_KEY.getValue(), symbol);
stub.putStringState(DECIMALS_KEY.getValue(), decimals); stub.putStringState(DECIMALS_KEY.getValue(), decimals);
@ -420,6 +458,8 @@ public final class ERC20TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public String TokenName(final Context ctx) { 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()); String tokenName = ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue());
if (stringIsNullOrEmpty(tokenName)) { if (stringIsNullOrEmpty(tokenName)) {
throw new ChaincodeException("Token name not found", NOT_FOUND.toString()); 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) @Transaction(intent = Transaction.TYPE.EVALUATE)
public String TokenSymbol(final Context ctx) { 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()); String tokenSymbol = ctx.getStub().getStringState(SYMBOL_KEY.getValue());
if (stringIsNullOrEmpty(tokenSymbol)) { if (stringIsNullOrEmpty(tokenSymbol)) {
throw new ChaincodeException("Token symbol not found", NOT_FOUND.toString()); 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) @Transaction(intent = Transaction.TYPE.EVALUATE)
public int Decimals(final Context ctx) { 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()); String decimals = ctx.getStub().getStringState(DECIMALS_KEY.getValue());
if (stringIsNullOrEmpty(decimals)) { if (stringIsNullOrEmpty(decimals)) {
throw new ChaincodeException("Decimal not found", NOT_FOUND.toString()); 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) { private byte[] marshal(final Object obj) {
return new Genson().serialize(obj).getBytes(UTF_8); 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) assertThat(thrown)
.isInstanceOf(ChaincodeException.class) .isInstanceOf(ChaincodeException.class)
.hasNoCause() .hasNoCause()
.hasMessage("Token name not found"); .hasMessage("Contract options need to be set before calling any function, call Initialize() to initialize contract");
} }
@Test @Test
@ -67,6 +67,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(SYMBOL_KEY.getValue())).thenReturn("ARBT"); when(stub.getStringState(SYMBOL_KEY.getValue())).thenReturn("ARBT");
String toknName = contract.TokenSymbol(ctx); String toknName = contract.TokenSymbol(ctx);
assertThat(toknName).isEqualTo("ARBT"); assertThat(toknName).isEqualTo("ARBT");
@ -78,6 +79,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(SYMBOL_KEY.getValue())).thenReturn(""); when(stub.getStringState(SYMBOL_KEY.getValue())).thenReturn("");
Throwable thrown = catchThrowable(() -> contract.TokenSymbol(ctx)); Throwable thrown = catchThrowable(() -> contract.TokenSymbol(ctx));
assertThat(thrown) assertThat(thrown)
@ -92,6 +94,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(DECIMALS_KEY.getValue())).thenReturn("18"); when(stub.getStringState(DECIMALS_KEY.getValue())).thenReturn("18");
long decimal = contract.Decimals(ctx); long decimal = contract.Decimals(ctx);
assertThat(decimal).isEqualTo(18); assertThat(decimal).isEqualTo(18);
@ -103,6 +106,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(DECIMALS_KEY.getValue())).thenReturn(""); when(stub.getStringState(DECIMALS_KEY.getValue())).thenReturn("");
Throwable thrown = catchThrowable(() -> contract.Decimals(ctx)); Throwable thrown = catchThrowable(() -> contract.Decimals(ctx));
assertThat(thrown) assertThat(thrown)
@ -117,6 +121,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn("222222222222"); when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn("222222222222");
long totalSupply = contract.TotalSupply(ctx); long totalSupply = contract.TotalSupply(ctx);
assertThat(totalSupply).isEqualTo(222222222222L); assertThat(totalSupply).isEqualTo(222222222222L);
@ -128,6 +133,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn(""); when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn("");
Throwable thrown = catchThrowable(() -> contract.TotalSupply(ctx)); Throwable thrown = catchThrowable(() -> contract.TotalSupply(ctx));
assertThat(thrown) assertThat(thrown)
@ -143,6 +149,7 @@ public class TokenERC20ContractTest {
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId); when(ci.getId()).thenReturn(org1UserId);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
@ -156,12 +163,15 @@ public class TokenERC20ContractTest {
class TokenOperationsInvoke { class TokenOperationsInvoke {
@Test @Test
public void invokeSetOptionsTest() { public void invokeInitializeTest() {
ERC20TokenContract contract = new ERC20TokenContract(); ERC20TokenContract contract = new ERC20TokenContract();
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); 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(NAME_KEY.getValue(), "ARBTToken");
verify(stub).putStringState(SYMBOL_KEY.getValue(), "ARBT"); verify(stub).putStringState(SYMBOL_KEY.getValue(), "ARBT");
verify(stub).putStringState(DECIMALS_KEY.getValue(), "18"); verify(stub).putStringState(DECIMALS_KEY.getValue(), "18");
@ -173,6 +183,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
@ -192,6 +203,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
@ -213,6 +225,7 @@ public class TokenERC20ContractTest {
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId); when(ci.getId()).thenReturn(org1UserId);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
@ -234,6 +247,7 @@ public class TokenERC20ContractTest {
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ci.getMSPID()).thenReturn("Org2MSP"); when(ci.getMSPID()).thenReturn("Org2MSP");
when(ci.getId()).thenReturn(org1UserId); when(ci.getId()).thenReturn(org1UserId);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
@ -256,6 +270,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId); when(ci.getId()).thenReturn(org1UserId);
@ -284,6 +299,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId); when(ci.getId()).thenReturn(org1UserId);
@ -312,6 +328,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId); when(ci.getId()).thenReturn(org1UserId);
@ -343,6 +360,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId); when(ci.getId()).thenReturn(org1UserId);
@ -374,6 +392,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId); when(ci.getId()).thenReturn(org1UserId);
@ -394,6 +413,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn("Org2MSP"); when(ci.getMSPID()).thenReturn("Org2MSP");
when(ci.getId()).thenReturn(spender); when(ci.getId()).thenReturn(spender);
@ -418,6 +438,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn("Org1MSP"); when(ci.getMSPID()).thenReturn("Org1MSP");
when(ci.getId()).thenReturn(org1UserId); when(ci.getId()).thenReturn(org1UserId);
@ -446,6 +467,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId); when(ci.getId()).thenReturn(org1UserId);
@ -465,6 +487,7 @@ public class TokenERC20ContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class); ClientIdentity ci = mock(ClientIdentity.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId); 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"; + " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
CompositeKey ckFromBalance = mock(CompositeKey.class); CompositeKey ckFromBalance = mock(CompositeKey.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.createCompositeKey(BALANCE_PREFIX.toString(), org1UserId)) when(stub.createCompositeKey(BALANCE_PREFIX.toString(), org1UserId))
.thenReturn(ckFromBalance); .thenReturn(ckFromBalance);
when(ckFromBalance.toString()).thenReturn(BALANCE_PREFIX.getValue() + org1UserId); 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"; + " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
CompositeKey ckFromBalance = mock(CompositeKey.class); CompositeKey ckFromBalance = mock(CompositeKey.class);
when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken");
when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), org1UserId)) when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), org1UserId))
.thenReturn(ckFromBalance); .thenReturn(ckFromBalance);
when(ckFromBalance.toString()).thenReturn(BALANCE_PREFIX.getValue() + org1UserId); 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 * @returns {String} Returns the name of the token
*/ */
async TokenName(ctx) { 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); const nameBytes = await ctx.stub.getState(nameKey);
return nameBytes.toString(); return nameBytes.toString();
} }
@ -41,6 +46,10 @@ class TokenERC20Contract extends Contract {
* @returns {String} Returns the symbol of the token * @returns {String} Returns the symbol of the token
*/ */
async Symbol(ctx) { 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); const symbolBytes = await ctx.stub.getState(symbolKey);
return symbolBytes.toString(); return symbolBytes.toString();
} }
@ -53,6 +62,10 @@ class TokenERC20Contract extends Contract {
* @returns {Number} Returns the number of decimals * @returns {Number} Returns the number of decimals
*/ */
async Decimals(ctx) { 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 decimalsBytes = await ctx.stub.getState(decimalsKey);
const decimals = parseInt(decimalsBytes.toString()); const decimals = parseInt(decimalsBytes.toString());
return decimals; return decimals;
@ -65,6 +78,10 @@ class TokenERC20Contract extends Contract {
* @returns {Number} Returns the total token supply * @returns {Number} Returns the total token supply
*/ */
async TotalSupply(ctx) { 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 totalSupplyBytes = await ctx.stub.getState(totalSupplyKey);
const totalSupply = parseInt(totalSupplyBytes.toString()); const totalSupply = parseInt(totalSupplyBytes.toString());
return totalSupply; return totalSupply;
@ -78,6 +95,10 @@ class TokenERC20Contract extends Contract {
* @returns {Number} Returns the account balance * @returns {Number} Returns the account balance
*/ */
async BalanceOf(ctx, owner) { 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 balanceKey = ctx.stub.createCompositeKey(balancePrefix, [owner]);
const balanceBytes = await ctx.stub.getState(balanceKey); 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 * @returns {Boolean} Return whether the transfer was successful or not
*/ */
async Transfer(ctx, to, value) { 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 from = ctx.clientIdentity.getID();
const transferResp = await this._transfer(ctx, from, to, value); 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 * @returns {Boolean} Return whether the transfer was successful or not
*/ */
async TransferFrom(ctx, from, to, value) { 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(); const spender = ctx.clientIdentity.getID();
// Retrieve the allowance of the spender // Retrieve the allowance of the spender
@ -149,7 +178,7 @@ class TokenERC20Contract extends Contract {
} }
// Decrease the allowance // Decrease the allowance
const updatedAllowance = currentAllowance - valueInt; const updatedAllowance = this.sub(currentAllowance, valueInt);
await ctx.stub.putState(allowanceKey, Buffer.from(updatedAllowance.toString())); await ctx.stub.putState(allowanceKey, Buffer.from(updatedAllowance.toString()));
console.log(`spender ${spender} allowance updated from ${currentAllowance} to ${updatedAllowance}`); console.log(`spender ${spender} allowance updated from ${currentAllowance} to ${updatedAllowance}`);
@ -202,8 +231,8 @@ class TokenERC20Contract extends Contract {
} }
// Update the balance // Update the balance
const fromUpdatedBalance = fromCurrentBalance - valueInt; const fromUpdatedBalance = this.sub(fromCurrentBalance, valueInt);
const toUpdatedBalance = toCurrentBalance + valueInt; const toUpdatedBalance = this.add(toCurrentBalance, valueInt);
await ctx.stub.putState(fromBalanceKey, Buffer.from(fromUpdatedBalance.toString())); await ctx.stub.putState(fromBalanceKey, Buffer.from(fromUpdatedBalance.toString()));
await ctx.stub.putState(toBalanceKey, Buffer.from(toUpdatedBalance.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 * @returns {Boolean} Return whether the approval was successful or not
*/ */
async Approve(ctx, spender, value) { 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 owner = ctx.clientIdentity.getID();
const allowanceKey = ctx.stub.createCompositeKey(allowancePrefix, [owner, spender]); 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 * @returns {Number} Return the amount of remaining tokens allowed to spent
*/ */
async Allowance(ctx, owner, spender) { 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 allowanceKey = ctx.stub.createCompositeKey(allowancePrefix, [owner, spender]);
const allowanceBytes = await ctx.stub.getState(allowanceKey); 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} decimals The decimals of the token
* @param {String} totalSupply The totalSupply 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(nameKey, Buffer.from(name));
await ctx.stub.putState(symbolKey, Buffer.from(symbol)); await ctx.stub.putState(symbolKey, Buffer.from(symbol));
await ctx.stub.putState(decimalsKey, Buffer.from(decimals)); await ctx.stub.putState(decimalsKey, Buffer.from(decimals));
@ -287,6 +336,9 @@ class TokenERC20Contract extends Contract {
*/ */
async Mint(ctx, amount) { 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 // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
const clientMSPID = ctx.clientIdentity.getMSPID(); const clientMSPID = ctx.clientIdentity.getMSPID();
if (clientMSPID !== 'Org1MSP') { if (clientMSPID !== 'Org1MSP') {
@ -311,7 +363,7 @@ class TokenERC20Contract extends Contract {
} else { } else {
currentBalance = parseInt(currentBalanceBytes.toString()); currentBalance = parseInt(currentBalanceBytes.toString());
} }
const updatedBalance = currentBalance + amountInt; const updatedBalance = this.add(currentBalance, amountInt);
await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString())); await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString()));
@ -324,7 +376,7 @@ class TokenERC20Contract extends Contract {
} else { } else {
totalSupply = parseInt(totalSupplyBytes.toString()); totalSupply = parseInt(totalSupplyBytes.toString());
} }
totalSupply = totalSupply + amountInt; totalSupply = this.add(totalSupply, amountInt);
await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString())); await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString()));
// Emit the Transfer event // Emit the Transfer event
@ -344,6 +396,9 @@ class TokenERC20Contract extends Contract {
*/ */
async Burn(ctx, amount) { 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 // Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn tokens
const clientMSPID = ctx.clientIdentity.getMSPID(); const clientMSPID = ctx.clientIdentity.getMSPID();
if (clientMSPID !== 'Org1MSP') { if (clientMSPID !== 'Org1MSP') {
@ -361,7 +416,7 @@ class TokenERC20Contract extends Contract {
throw new Error('The balance does not exist'); throw new Error('The balance does not exist');
} }
const currentBalance = parseInt(currentBalanceBytes.toString()); const currentBalance = parseInt(currentBalanceBytes.toString());
const updatedBalance = currentBalance - amountInt; const updatedBalance = this.sub(currentBalance, amountInt);
await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString())); await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString()));
@ -370,7 +425,7 @@ class TokenERC20Contract extends Contract {
if (!totalSupplyBytes || totalSupplyBytes.length === 0) { if (!totalSupplyBytes || totalSupplyBytes.length === 0) {
throw new Error('totalSupply does not exist.'); 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())); await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString()));
// Emit the Transfer event // Emit the Transfer event
@ -388,6 +443,10 @@ class TokenERC20Contract extends Contract {
* @returns {Number} Returns the account balance * @returns {Number} Returns the account balance
*/ */
async ClientAccountBalance(ctx) { async ClientAccountBalance(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Get ID of submitting client identity // Get ID of submitting client identity
const clientAccountID = ctx.clientIdentity.getID(); const clientAccountID = ctx.clientIdentity.getID();
@ -405,11 +464,40 @@ class TokenERC20Contract extends Contract {
// In this implementation, the client account ID is the clientId itself. // 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 // 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) { async ClientAccountID(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Get ID of submitting client identity // Get ID of submitting client identity
const clientAccountID = ctx.clientIdentity.getID(); const clientAccountID = ctx.clientIdentity.getID();
return clientAccountID; 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; module.exports = TokenERC20Contract;

View file

@ -26,7 +26,7 @@ describe('Chaincode', () => {
let mockStub; let mockStub;
let mockClientIdentity; let mockClientIdentity;
beforeEach('Sandbox creation', () => { beforeEach('Sandbox creation', async () => {
sandbox = sinon.createSandbox(); sandbox = sinon.createSandbox();
token = new TokenERC20Contract('token-erc20'); token = new TokenERC20Contract('token-erc20');
@ -36,6 +36,8 @@ describe('Chaincode', () => {
mockClientIdentity = sinon.createStubInstance(ClientIdentity); mockClientIdentity = sinon.createStubInstance(ClientIdentity);
ctx.clientIdentity = mockClientIdentity; ctx.clientIdentity = mockClientIdentity;
await token.Initialize(ctx, 'some name', 'some symbol', '2');
mockStub.putState.resolves('some state'); mockStub.putState.resolves('some state');
mockStub.setEvent.returns('set event'); mockStub.setEvent.returns('set event');
@ -198,13 +200,18 @@ describe('Chaincode', () => {
}); });
}); });
describe('#SetOption', () => { describe('#Initialize', () => {
it('should work', async () => { 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, 'name', Buffer.from('some name'));
sinon.assert.calledWith(mockStub.putState, 'symbol', Buffer.from('some symbol')); sinon.assert.calledWith(mockStub.putState, 'symbol', Buffer.from('some symbol'));
sinon.assert.calledWith(mockStub.putState, 'decimals', Buffer.from('2')); 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 // 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 // returns {int} The number of non-fungible tokens owned by the owner, possibly zero
func (c *TokenERC721Contract) BalanceOf(ctx contractapi.TransactionContextInterface, owner string) int { 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. // 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.* // 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 // param {String} tokenId The identifier for a non-fungible token
// returns {String} Return the owner of the non-fungible token // returns {String} Return the owner of the non-fungible token
func (c *TokenERC721Contract) OwnerOf(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) { 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) nft, err := _readNFT(ctx, tokenId)
if err != nil { if err != nil {
return "", fmt.Errorf("could not process OwnerOf for tokenId: %w", err) 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 // param {String} tokenId the non-fungible token to approve
// returns {Boolean} Return whether the approval was successful or not // returns {Boolean} Return whether the approval was successful or not
func (c *TokenERC721Contract) Approve(ctx contractapi.TransactionContextInterface, operator string, tokenId string) (bool, error) { 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() sender64, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
return false, fmt.Errorf("failed to GetClientIdentity: %v", err) 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 // param {Boolean} approved True if the operator is approved, false to revoke approval
// returns {Boolean} Return whether the approval was successful or not // returns {Boolean} Return whether the approval was successful or not
func (c *TokenERC721Contract) SetApprovalForAll(ctx contractapi.TransactionContextInterface, operator string, approved bool) (bool, error) { 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() sender64, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
return false, fmt.Errorf("failed to GetClientIdentity: %v", err) 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 // 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 // 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) { 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}) approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{owner, operator})
if err != nil { if err != nil {
return false, fmt.Errorf("failed to CreateCompositeKey: %v", err) 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 // 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 // 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) { 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) nft, err := _readNFT(ctx, tokenId)
if err != nil { if err != nil {
return "false", fmt.Errorf("failed GetApproved for tokenId : %v", err) 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) { 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 // Get ID of submitting client identity
sender64, err := ctx.GetClientIdentity().GetID() sender64, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
@ -336,6 +406,16 @@ func (c *TokenERC721Contract) TransferFrom(ctx contractapi.TransactionContextInt
// returns {String} Returns the name of the token // returns {String} Returns the name of the token
func (c *TokenERC721Contract) Name(ctx contractapi.TransactionContextInterface) (string, error) { 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) bytes, err := ctx.GetStub().GetState(nameKey)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to get Name bytes: %s", err) 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 // returns {String} Returns the symbol of the token
func (c *TokenERC721Contract) Symbol(ctx contractapi.TransactionContextInterface) (string, error) { 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) bytes, err := ctx.GetStub().GetState(symbolKey)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to get Symbol: %v", err) 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 // returns {String} Returns the URI of the token
func (c *TokenERC721Contract) TokenURI(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) { 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) nft, err := _readNFT(ctx, tokenId)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to get TokenURI: %v", err) 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. // where each one of them has an assigned and queryable owner.
func (c *TokenERC721Contract) TotalSupply(ctx contractapi.TransactionContextInterface) int { 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. // 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.* // TotalSupply() queries for and counts all records matching nftPrefix.*
@ -399,11 +509,11 @@ func (c *TokenERC721Contract) TotalSupply(ctx contractapi.TransactionContextInte
} }
// ============== ERC721 enumeration extension =============== // ============== 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} name The name of the token
// param {String} symbol The symbol 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 // Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the name and symbol
clientMSPID, err := ctx.GetClientIdentity().GetMSPID() clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil { 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") 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)) err = ctx.GetStub().PutState(nameKey, []byte(name))
if err != nil { if err != nil {
return false, fmt.Errorf("failed to PutState nameKey %s: %v", nameKey, err) 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) { 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 // Check minter authorization - this sample assumes Org1 is the issuer with privilege to mint a new token
clientMSPID, err := ctx.GetClientIdentity().GetMSPID() clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil { if err != nil {
@ -518,6 +645,16 @@ func (c *TokenERC721Contract) MintWithTokenURI(ctx contractapi.TransactionContex
// param {String} tokenId Unique ID of a non-fungible token // param {String} tokenId Unique ID of a non-fungible token
// returns {Boolean} Return whether the burn was successful or not // returns {Boolean} Return whether the burn was successful or not
func (c *TokenERC721Contract) Burn(ctx contractapi.TransactionContextInterface, tokenId string) (bool, error) { 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() owner64, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
return false, fmt.Errorf("failed to GetClientIdentity owner64: %v", err) 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. // ClientAccountBalance returns the balance of the requesting client's account.
// returns {Number} Returns the account balance // returns {Number} Returns the account balance
func (c *TokenERC721Contract) ClientAccountBalance(ctx contractapi.TransactionContextInterface) (int, error) { 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 // Get ID of submitting client identity
clientAccountID64, err := ctx.GetClientIdentity().GetID() clientAccountID64, err := ctx.GetClientIdentity().GetID()
if err != nil { 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 // 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) { 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 // Get ID of submitting client identity
clientAccountID64, err := ctx.GetClientIdentity().GetID() clientAccountID64, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
@ -617,3 +774,14 @@ func (c *TokenERC721Contract) ClientAccountID(ctx contractapi.TransactionContext
return clientAccount, nil 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) assert.Equal(t, "https://example.com/nft101.json", tokenURI)
} }
func TestSetOption(t *testing.T) { func TestInitialize(t *testing.T) {
ctx, _ := setupStub() ctx, _ := setupStub()
c := new(TokenERC721Contract) c := new(TokenERC721Contract)
option, _ := c.SetOption(ctx, "someName", "someSymbol") option, _ := c.Initialize(ctx, "someName", "someSymbol")
assert.Equal(t, true, option) assert.Equal(t, true, option)
} }

View file

@ -54,6 +54,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public long BalanceOf(final Context ctx, final String owner) { 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 ChaincodeStub stub = ctx.getStub();
final CompositeKey balanceKey = final CompositeKey balanceKey =
stub.createCompositeKey(ContractConstants.BALANCE.getValue(), owner); stub.createCompositeKey(ContractConstants.BALANCE.getValue(), owner);
@ -76,6 +78,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public String OwnerOf(final Context ctx, final String tokenId) { 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); final NFT nft = this._readNft(ctx, tokenId);
if (stringIsNullOrEmpty(nft.getOwner())) { if (stringIsNullOrEmpty(nft.getOwner())) {
final String errorMessage = String.format("No owner is assigned o the token %s", tokenId); 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) @Transaction(intent = Transaction.TYPE.EVALUATE)
public boolean IsApprovedForAll(final Context ctx, final String owner, final String operator) { 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 ChaincodeStub stub = ctx.getStub();
final CompositeKey approvalKey = final CompositeKey approvalKey =
stub.createCompositeKey(ContractConstants.APPROVAL.getValue(), owner, operator); stub.createCompositeKey(ContractConstants.APPROVAL.getValue(), owner, operator);
@ -116,6 +122,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.SUBMIT) @Transaction(intent = Transaction.TYPE.SUBMIT)
public void Approve(final Context ctx, final String operator, final String tokenId) { 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 ChaincodeStub stub = ctx.getStub();
final String sender = ctx.getClientIdentity().getId(); final String sender = ctx.getClientIdentity().getId();
NFT nft = this._readNft(ctx, tokenId); NFT nft = this._readNft(ctx, tokenId);
@ -144,6 +152,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.SUBMIT) @Transaction(intent = Transaction.TYPE.SUBMIT)
public void SetApprovalForAll(final Context ctx, final String operator, final boolean approved) { 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 String sender = ctx.getClientIdentity().getId();
final ChaincodeStub stub = ctx.getStub(); final ChaincodeStub stub = ctx.getStub();
final Approval nftApproval = new Approval(sender, operator, approved); final Approval nftApproval = new Approval(sender, operator, approved);
@ -163,6 +173,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public String GetApproved(final Context ctx, final String tokenId) { 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); final NFT nft = this._readNft(ctx, tokenId);
return nft.getApproved(); return nft.getApproved();
} }
@ -178,6 +190,8 @@ public class ERC721TokenContract implements ContractInterface {
@Transaction(intent = Transaction.TYPE.SUBMIT) @Transaction(intent = Transaction.TYPE.SUBMIT)
public void TransferFrom( public void TransferFrom(
final Context ctx, final String from, final String to, final String tokenId) { 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 String sender = ctx.getClientIdentity().getId();
final ChaincodeStub stub = ctx.getStub(); final ChaincodeStub stub = ctx.getStub();
NFT nft = this._readNft(ctx, tokenId); NFT nft = this._readNft(ctx, tokenId);
@ -235,6 +249,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public String Name(final Context ctx) { 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()); return ctx.getStub().getStringState(ContractConstants.NAMEKEY.getValue());
} }
@ -246,6 +262,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public String Symbol(final Context ctx) { 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()); return ctx.getStub().getStringState(ContractConstants.SYMBOLKEY.getValue());
} }
@ -258,6 +276,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public String TokenURI(final Context ctx, final String tokenId) { 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); final NFT nft = this._readNft(ctx, tokenId);
return nft.getTokenURI(); return nft.getTokenURI();
} }
@ -275,6 +295,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public long TotalSupply(final Context ctx) { 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 ChaincodeStub stub = ctx.getStub();
final CompositeKey nftKey = stub.createCompositeKey(ContractConstants.NFT.getValue()); final CompositeKey nftKey = stub.createCompositeKey(ContractConstants.NFT.getValue());
final QueryResultsIterator<KeyValue> iterator = stub.getStateByPartialCompositeKey(nftKey); final QueryResultsIterator<KeyValue> iterator = stub.getStateByPartialCompositeKey(nftKey);
@ -297,16 +319,24 @@ public class ERC721TokenContract implements ContractInterface {
* @param symbol The symbol of the token * @param symbol The symbol of the token
*/ */
@Transaction(intent = Transaction.TYPE.SUBMIT) @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(); final String clientMSPID = ctx.getClientIdentity().getMSPID();
// Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the // Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the
// name and symbol // name and symbol
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSP.getValue())) { if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSP.getValue())) {
throw new ChaincodeException( 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.NAMEKEY.getValue(), name);
stub.putStringState(ContractConstants.SYMBOLKEY.getValue(), symbol); stub.putStringState(ContractConstants.SYMBOLKEY.getValue(), symbol);
} }
@ -321,6 +351,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.SUBMIT) @Transaction(intent = Transaction.TYPE.SUBMIT)
public NFT MintWithTokenURI(final Context ctx, final String tokenId, final String tokenURI) { 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 String clientMSPID = ctx.getClientIdentity().getMSPID();
final ChaincodeStub stub = ctx.getStub(); final ChaincodeStub stub = ctx.getStub();
// Check minter authorization this sample assumes Org1 is the issuer with privilege to mint a // 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) @Transaction(intent = Transaction.TYPE.SUBMIT)
public void Burn(final Context ctx, final String tokenId) { 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 ChaincodeStub stub = ctx.getStub();
final String owner = ctx.getClientIdentity().getId(); final String owner = ctx.getClientIdentity().getId();
// Check if a caller is the owner of the non-fungible token // 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) @Transaction(intent = Transaction.TYPE.EVALUATE)
public long ClientAccountBalance(final Context ctx) { 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()); return this.BalanceOf(ctx, ctx.getClientIdentity().getId());
} }
@ -403,6 +439,8 @@ public class ERC721TokenContract implements ContractInterface {
*/ */
@Transaction(intent = Transaction.TYPE.EVALUATE) @Transaction(intent = Transaction.TYPE.EVALUATE)
public String ClientAccountID(final Context ctx) { public String ClientAccountID(final Context ctx) {
//check contract options are already set first to execute the function
this.checkInitialized(ctx);
return ctx.getClientIdentity().getId(); return ctx.getClientIdentity().getId();
} }
@ -437,4 +475,17 @@ public class ERC721TokenContract implements ContractInterface {
final String nft = stub.getStringState(nftKey.toString()); final String nft = stub.getStringState(nftKey.toString());
return ((stringIsNullOrEmpty(nft)) ? false : true); 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); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
List<KeyValue> list = new ArrayList<>(); 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"));
list.add(new MockKeyValue("balance_Alice_101", "\u0000")); list.add(new MockKeyValue("balance_Alice_101", "\u0000"));
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
@ -103,6 +104,7 @@ public class ERC721TokenContractTest {
NFT nft = new NFT("101", "Alicd", "http://test.com", ""); NFT nft = new NFT("101", "Alicd", "http://test.com", "");
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
CompositeKey ck = mock(CompositeKey.class); CompositeKey ck = mock(CompositeKey.class);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");
when(stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck); when(stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck);
when(stub.getStringState(ck.toString())).thenReturn(nft.toJSONString()); when(stub.getStringState(ck.toString())).thenReturn(nft.toJSONString());
@ -129,6 +131,7 @@ public class ERC721TokenContractTest {
this.stub = mock(ChaincodeStub.class); this.stub = mock(ChaincodeStub.class);
when(this.ctx.getStub()).thenReturn(stub); when(this.ctx.getStub()).thenReturn(stub);
contract = new ERC721TokenContract(); contract = new ERC721TokenContract();
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
CompositeKey ck1 = mock(CompositeKey.class); CompositeKey ck1 = mock(CompositeKey.class);
when(ck1.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); when(ck1.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");
when(this.stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck1); when(this.stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck1);
@ -256,6 +259,7 @@ public class ERC721TokenContractTest {
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
NFT nft = new NFT("101", "Alice", "http://test.com", ""); NFT nft = new NFT("101", "Alice", "http://test.com", "");
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
CompositeKey ck = mock(CompositeKey.class); CompositeKey ck = mock(CompositeKey.class);
when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");
when(stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck); when(stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck);
@ -281,6 +285,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
ClientIdentity ci = null; ClientIdentity ci = null;
ci = mock(ClientIdentity.class); ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
@ -306,6 +311,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
ClientIdentity ci = null; ClientIdentity ci = null;
ci = mock(ClientIdentity.class); ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
@ -327,6 +333,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
ClientIdentity ci = null; ClientIdentity ci = null;
ci = mock(ClientIdentity.class); ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci); when(ctx.getClientIdentity()).thenReturn(ci);
@ -362,6 +369,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ANFT"); when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ANFT");
ERC721TokenContract contract = new ERC721TokenContract(); ERC721TokenContract contract = new ERC721TokenContract();
final String name = contract.Name(ctx); final String name = contract.Name(ctx);
@ -373,6 +381,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
ERC721TokenContract contract = new ERC721TokenContract(); ERC721TokenContract contract = new ERC721TokenContract();
final NFT nft = new NFT("101", "Alice", "http://test.com", "Bob"); final NFT nft = new NFT("101", "Alice", "http://test.com", "Bob");
CompositeKey ck = mock(CompositeKey.class); CompositeKey ck = mock(CompositeKey.class);
@ -388,6 +397,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
List<KeyValue> list = new ArrayList<>(); List<KeyValue> list = new ArrayList<>();
list.add( list.add(
new MockKeyValue( new MockKeyValue(
@ -417,6 +427,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
final NFT nft = new NFT("101", "Alice", "DummyURI", ""); final NFT nft = new NFT("101", "Alice", "DummyURI", "");
CompositeKey ck = mock(CompositeKey.class); CompositeKey ck = mock(CompositeKey.class);
when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");
@ -445,6 +456,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
final NFT nft = new NFT("101", "Alice", "DummyURI", ""); final NFT nft = new NFT("101", "Alice", "DummyURI", "");
CompositeKey ck = mock(CompositeKey.class); CompositeKey ck = mock(CompositeKey.class);
when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101");
@ -471,6 +483,7 @@ public class ERC721TokenContractTest {
Context ctx = mock(Context.class); Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class); ChaincodeStub stub = mock(ChaincodeStub.class);
when(ctx.getStub()).thenReturn(stub); when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken");
final NFT nft = new NFT("101", "Alice", "DummyURI", ""); final NFT nft = new NFT("101", "Alice", "DummyURI", "");
CompositeKey ck = mock(CompositeKey.class); CompositeKey ck = mock(CompositeKey.class);
when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); 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 * @returns {Number} The number of non-fungible tokens owned by the owner, possibly zero
*/ */
async BalanceOf(ctx, owner) { 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. // 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.* // BalanceOf() queries for and counts all records matching balancePrefix.owner.*
const iterator = await ctx.stub.getStateByPartialCompositeKey(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 * @returns {String} Return the owner of the non-fungible token
*/ */
async OwnerOf(ctx, tokenId) { 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 nft = await this._readNFT(ctx, tokenId);
const owner = nft.owner; const owner = nft.owner;
if (!owner) { if (!owner) {
@ -67,6 +73,9 @@ class TokenERC721Contract extends Contract {
* @returns {Boolean} Return whether the transfer was successful or not * @returns {Boolean} Return whether the transfer was successful or not
*/ */
async TransferFrom(ctx, from, to, tokenId) { 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 sender = ctx.clientIdentity.getID();
const nft = await this._readNFT(ctx, tokenId); 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 * @returns {Boolean} Return whether the approval was successful or not
*/ */
async Approve(ctx, approved, tokenId) { 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 sender = ctx.clientIdentity.getID();
const nft = await this._readNFT(ctx, tokenId); 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 * @returns {Boolean} Return whether the approval was successful or not
*/ */
async SetApprovalForAll(ctx, operator, approved) { 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 sender = ctx.clientIdentity.getID();
const approval = { owner: sender, operator: operator, approved: approved }; 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 * @returns {Object} Return the approved client for this non-fungible token, or null if there is none
*/ */
async GetApproved(ctx, tokenId) { 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); const nft = await this._readNFT(ctx, tokenId);
return nft.approved; 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 * @returns {Boolean} Return true if the operator is an approved operator for the owner, false otherwise
*/ */
async IsApprovedForAll(ctx, owner, operator) { 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 approvalKey = ctx.stub.createCompositeKey(approvalPrefix, [owner, operator]);
const approvalBytes = await ctx.stub.getState(approvalKey); const approvalBytes = await ctx.stub.getState(approvalKey);
let approved; let approved;
@ -209,6 +230,9 @@ class TokenERC721Contract extends Contract {
* @returns {String} Returns the name of the token * @returns {String} Returns the name of the token
*/ */
async Name(ctx) { 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); const nameAsBytes = await ctx.stub.getState(nameKey);
return nameAsBytes.toString(); return nameAsBytes.toString();
} }
@ -220,6 +244,9 @@ class TokenERC721Contract extends Contract {
* @returns {String} Returns the symbol of the token * @returns {String} Returns the symbol of the token
*/ */
async Symbol(ctx) { 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); const symbolAsBytes = await ctx.stub.getState(symbolKey);
return symbolAsBytes.toString(); return symbolAsBytes.toString();
} }
@ -232,6 +259,9 @@ class TokenERC721Contract extends Contract {
* @returns {String} Returns the URI of the token * @returns {String} Returns the URI of the token
*/ */
async TokenURI(ctx, tokenId) { 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); const nft = await this._readNFT(ctx, tokenId);
return nft.tokenURI; return nft.tokenURI;
} }
@ -246,6 +276,9 @@ class TokenERC721Contract extends Contract {
* where each one of them has an assigned and queryable owner. * where each one of them has an assigned and queryable owner.
*/ */
async TotalSupply(ctx) { 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. // 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.* // TotalSupply() queries for and counts all records matching nftPrefix.*
const iterator = await ctx.stub.getStateByPartialCompositeKey(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} name The name of the token
* @param {String} symbol The symbol 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(); const clientMSPID = ctx.clientIdentity.getMSPID();
if (clientMSPID !== 'Org1MSP') { if (clientMSPID !== 'Org1MSP') {
throw new Error('client is not authorized to set the name and symbol of the token'); 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(nameKey, Buffer.from(name));
await ctx.stub.putState(symbolKey, Buffer.from(symbol)); await ctx.stub.putState(symbolKey, Buffer.from(symbol));
return true; return true;
@ -291,6 +330,8 @@ class TokenERC721Contract extends Contract {
* @returns {Object} Return the non-fungible token object * @returns {Object} Return the non-fungible token object
*/ */
async MintWithTokenURI(ctx, tokenId, tokenURI) { 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 // Check minter authorization - this sample assumes Org1 is the issuer with privilege to mint a new token
const clientMSPID = ctx.clientIdentity.getMSPID(); const clientMSPID = ctx.clientIdentity.getMSPID();
@ -341,6 +382,9 @@ class TokenERC721Contract extends Contract {
* @returns {Boolean} Return whether the burn was successful or not * @returns {Boolean} Return whether the burn was successful or not
*/ */
async Burn(ctx, tokenId) { async Burn(ctx, tokenId) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
const owner = ctx.clientIdentity.getID(); const owner = ctx.clientIdentity.getID();
// Check if a caller is the owner of the non-fungible token // 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 * @returns {Number} Returns the account balance
*/ */
async ClientAccountBalance(ctx) { async ClientAccountBalance(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Get ID of submitting client identity // Get ID of submitting client identity
const clientAccountID = ctx.clientIdentity.getID(); const clientAccountID = ctx.clientIdentity.getID();
return this.BalanceOf(ctx, clientAccountID); return this.BalanceOf(ctx, clientAccountID);
@ -397,10 +444,21 @@ class TokenERC721Contract extends Contract {
// In this implementation, the client account ID is the clientId itself. // 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 // 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) { async ClientAccountID(ctx) {
//check contract options are already set first to execute the function
await this.CheckInitialized(ctx);
// Get ID of submitting client identity // Get ID of submitting client identity
const clientAccountID = ctx.clientIdentity.getID(); const clientAccountID = ctx.clientIdentity.getID();
return clientAccountID; 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; module.exports = TokenERC721Contract;

View file

@ -20,9 +20,23 @@ type UTXO struct {
Amount int `json:"amount"` 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 // Mint creates a new unspent transaction output (UTXO) owned by the minter
func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount int) (*UTXO, error) { 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 // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
clientMSPID, err := ctx.GetClientIdentity().GetMSPID() clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil { 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) // Transfer transfers UTXOs containing tokens from client to recipient(s)
func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, utxoInputKeys []string, utxoOutputs []UTXO) ([]UTXO, error) { 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 // Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID() clientID, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
@ -100,7 +123,7 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, ut
Amount: amount, Amount: amount,
} }
totalInputAmount += amount totalInputAmount,err = add(totalInputAmount, amount)
utxoInputs[utxoInputKey] = utxoInput utxoInputs[utxoInputKey] = utxoInput
} }
@ -115,7 +138,7 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, ut
utxoOutputs[i].Key = fmt.Sprintf("%s.%d", txID, i) utxoOutputs[i].Key = fmt.Sprintf("%s.%d", txID, i)
totalOutputAmount += utxoOutput.Amount totalOutputAmount,err = add(totalOutputAmount, utxoOutput.Amount)
} }
// Validate total inputs equals total outputs // 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 // ClientUTXOs returns all UTXOs owned by the calling client
func (s *SmartContract) ClientUTXOs(ctx contractapi.TransactionContextInterface) ([]*UTXO, error) { 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 // Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID() clientID, err := ctx.GetClientIdentity().GetID()
if err != nil { 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 // 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) { 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 // Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID() clientID, err := ctx.GetClientIdentity().GetID()
if err != nil { if err != nil {
@ -219,3 +260,109 @@ func (s *SmartContract) ClientID(ctx contractapi.TransactionContextInterface) (s
return clientID, nil 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
}