From 5f7146629562fc5b3be0f5b03b9226edd8f3fa37 Mon Sep 17 00:00:00 2001 From: fraVlaca <86831094+fraVlaca@users.noreply.github.com> Date: Wed, 11 May 2022 15:35:10 +0100 Subject: [PATCH] updated erc tokens samples (#731) Signed-off-by: fraVlaca --- .../chaincode-go/chaincode/contract.go | 325 +++++++++++++++++- .../chaincode-go/chaincode/token_contract.go | 261 +++++++++++++- .../samples/erc20/ContractConstants.java | 4 +- .../samples/erc20/ERC20TokenContract.java | 59 +++- .../samples/erc20/TokenERC20ContractTest.java | 31 +- .../chaincode-javascript/lib/tokenERC20.js | 104 +++++- .../test/tokenERC20.test.js | 15 +- .../chaincode-go/chaincode/erc721-contract.go | 172 ++++++++- .../chaincode/erc721-contract_test.go | 4 +- .../samples/erc721/ERC721TokenContract.java | 57 ++- .../erc721/ERC721TokenContractTest.java | 13 + .../chaincode-javascript/lib/tokenERC721.js | 62 +++- .../chaincode-go/chaincode/token_contract.go | 151 +++++++- 13 files changed, 1207 insertions(+), 51 deletions(-) diff --git a/token-erc-1155/chaincode-go/chaincode/contract.go b/token-erc-1155/chaincode-go/chaincode/contract.go index a86c9c9b..b6f690e5 100644 --- a/token-erc-1155/chaincode-go/chaincode/contract.go +++ b/token-erc-1155/chaincode-go/chaincode/contract.go @@ -23,6 +23,10 @@ const approvalPrefix = "account~operator" const minterMSPID = "Org1MSP" +// Define key names for options +const nameKey = "name" +const symbolKey = "symbol" + // SmartContract provides functions for transferring tokens between accounts type SmartContract struct { contractapi.Contract @@ -113,8 +117,17 @@ type ToID struct { // This function emits a TransferSingle event. func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens - err := authorizationHelper(ctx) + err = authorizationHelper(ctx) if err != nil { return err } @@ -140,12 +153,21 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, accoun // This function emits a TransferBatch event. func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + if len(ids) != len(amounts) { return fmt.Errorf("ids and amounts must have the same length") } // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens - err := authorizationHelper(ctx) + err = authorizationHelper(ctx) if err != nil { return err } @@ -160,7 +182,10 @@ func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, a amountToSend := make(map[uint64]uint64) // token id => amount for i := 0; i < len(amounts); i++ { - amountToSend[ids[i]] += amounts[i] + amountToSend[ids[i]], err = add(amountToSend[ids[i]], amounts[i]) + if err != nil { + return err + } } // Copy the map keys and sort it. This is necessary because iterating maps in Go is not deterministic @@ -184,12 +209,21 @@ func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, a // This function triggers a TransferSingle event. func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + if account == "0x0" { return fmt.Errorf("burn to the zero address") } // Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens - err := authorizationHelper(ctx) + err = authorizationHelper(ctx) if err != nil { return err } @@ -214,6 +248,15 @@ func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, accoun // This function emits a TransferBatch event. func (s *SmartContract) BurnBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + if account == "0x0" { return fmt.Errorf("burn to the zero address") } @@ -223,7 +266,7 @@ func (s *SmartContract) BurnBatch(ctx contractapi.TransactionContextInterface, a } // Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens - err := authorizationHelper(ctx) + err = authorizationHelper(ctx) if err != nil { return err } @@ -247,6 +290,16 @@ func (s *SmartContract) BurnBatch(ctx contractapi.TransactionContextInterface, a // recipient account must be a valid clientID as returned by the ClientID() function // This function triggers a TransferSingle event func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + if sender == recipient { return fmt.Errorf("transfer to self") } @@ -293,6 +346,16 @@ func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface // recipient account must be a valid clientID as returned by the ClientID() function // This function triggers a TransferBatch event func (s *SmartContract) BatchTransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, ids []uint64, amounts []uint64) error { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + if sender == recipient { return fmt.Errorf("transfer to self") } @@ -332,7 +395,10 @@ func (s *SmartContract) BatchTransferFrom(ctx contractapi.TransactionContextInte amountToSend := make(map[uint64]uint64) // token id => amount for i := 0; i < len(amounts); i++ { - amountToSend[ids[i]] += amounts[i] + amountToSend[ids[i]], err = add(amountToSend[ids[i]], amounts[i]) + if err != nil { + return err + } } // Copy the map keys and sort it. This is necessary because iterating maps in Go is not deterministic @@ -356,6 +422,15 @@ func (s *SmartContract) BatchTransferFrom(ctx contractapi.TransactionContextInte // This function triggers a TransferBatchMultiRecipient event func (s *SmartContract) BatchTransferFromMultiRecipient(ctx contractapi.TransactionContextInterface, sender string, recipients []string, ids []uint64, amounts []uint64) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + if len(recipients) != len(ids) || len(ids) != len(amounts) { return fmt.Errorf("recipients, ids, and amounts must have the same length") } @@ -393,7 +468,10 @@ func (s *SmartContract) BatchTransferFromMultiRecipient(ctx contractapi.Transact amountToSend := make(map[ToID]uint64) // (recipient, id ) => amount for i := 0; i < len(amounts); i++ { - amountToSend[ToID{recipients[i], ids[i]}] += amounts[i] + amountToSend[ToID{recipients[i], ids[i]}], err = add(amountToSend[ToID{recipients[i], ids[i]}], amounts[i]) + if err != nil { + return err + } } // Copy the map keys and sort it. This is necessary because iterating maps in Go is not deterministic @@ -425,6 +503,16 @@ func (s *SmartContract) IsApprovedForAll(ctx contractapi.TransactionContextInter // _isApprovedForAll returns true if operator is approved to transfer account's tokens. func _isApprovedForAll(ctx contractapi.TransactionContextInterface, account string, operator string) (bool, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return false, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{account, operator}) if err != nil { return false, fmt.Errorf("failed to create the composite key for prefix %s: %v", approvalPrefix, err) @@ -450,6 +538,16 @@ func _isApprovedForAll(ctx contractapi.TransactionContextInterface, account stri // SetApprovalForAll returns true if operator is approved to transfer account's tokens. func (s *SmartContract) SetApprovalForAll(ctx contractapi.TransactionContextInterface, operator string, approved bool) error { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity account, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -490,11 +588,31 @@ func (s *SmartContract) SetApprovalForAll(ctx contractapi.TransactionContextInte // BalanceOf returns the balance of the given account func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, account string, id uint64) (uint64, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + return balanceOfHelper(ctx, account, id) } // BalanceOfBatch returns the balance of multiple account/token pairs func (s *SmartContract) BalanceOfBatch(ctx contractapi.TransactionContextInterface, accounts []string, ids []uint64) ([]uint64, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return nil, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return nil, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + if len(accounts) != len(ids) { return nil, fmt.Errorf("accounts and ids must have the same length") } @@ -515,6 +633,15 @@ func (s *SmartContract) BalanceOfBatch(ctx contractapi.TransactionContextInterfa // ClientAccountBalance returns the balance of the requesting client's account func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextInterface, id uint64) (uint64, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity clientID, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -529,6 +656,15 @@ func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextI // Users can use this function to get their own account id, which they can then give to others as the payment address func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity clientAccountID, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -542,8 +678,17 @@ func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterf // This function triggers URI event for each token id func (s *SmartContract) SetURI(ctx contractapi.TransactionContextInterface, uri string) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens - err := authorizationHelper(ctx) + err = authorizationHelper(ctx) if err != nil { return err } @@ -563,6 +708,15 @@ func (s *SmartContract) SetURI(ctx contractapi.TransactionContextInterface, uri // URI returns the URI func (s *SmartContract) URI(ctx contractapi.TransactionContextInterface, id uint64) (string, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + uriBytes, err := ctx.GetStub().GetState(uriKey) if err != nil { return "", fmt.Errorf("failed to get uri: %v", err) @@ -577,8 +731,17 @@ func (s *SmartContract) URI(ctx contractapi.TransactionContextInterface, id uint func (s *SmartContract) BroadcastTokenExistance(ctx contractapi.TransactionContextInterface, id uint64) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens - err := authorizationHelper(ctx) + err = authorizationHelper(ctx) if err != nil { return err } @@ -594,6 +757,84 @@ func (s *SmartContract) BroadcastTokenExistance(ctx contractapi.TransactionConte return emitTransferSingle(ctx, transferSingleEvent) } +// Name returns a descriptive name for fungible tokens in this contract +// returns {String} Returns the name of the token + +func (s *SmartContract) Name(ctx contractapi.TransactionContextInterface) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + + bytes, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return "", fmt.Errorf("failed to get Name bytes: %s", err) + } + + return string(bytes), nil +} + +// Symbol returns an abbreviated name for fungible tokens in this contract. +// returns {String} Returns the symbol of the token + +func (s *SmartContract) Symbol(ctx contractapi.TransactionContextInterface) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + + bytes, err := ctx.GetStub().GetState(symbolKey) + if err != nil { + return "", fmt.Errorf("failed to get Symbol: %v", err) + } + + return string(bytes), nil +} + +// Set information for a token and intialize contract. +// param {String} name The name of the token +// param {String} symbol The symbol of the token +func (s *SmartContract) Initialize(ctx contractapi.TransactionContextInterface, name string, symbol string) (bool, error) { + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to intitialize contract + clientMSPID, err := ctx.GetClientIdentity().GetMSPID() + if err != nil { + return false, fmt.Errorf("failed to get MSPID: %v", err) + } else if clientMSPID != "Org1MSP" { + return false, fmt.Errorf("client is not authorized to initialize contract") + } + + //check contract options are not already set, client is not authorized to change them once intitialized + bytes, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return false, fmt.Errorf("failed to get Name: %v", err) + } else if bytes != nil { + return false, fmt.Errorf("contract options are already set, client is not authorized to change them") + } + + err = ctx.GetStub().PutState(nameKey, []byte(name)) + if err != nil { + return false, fmt.Errorf("failed to set token name: %v", err) + } + + err = ctx.GetStub().PutState(symbolKey, []byte(symbol)) + if err != nil { + return false, fmt.Errorf("failed to set symbol: %v", err) + } + + return true, nil +} + // Helper Functions // authorizationHelper checks minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens @@ -646,7 +887,10 @@ func addBalance(ctx contractapi.TransactionContextInterface, sender string, reci balance, _ = strconv.ParseUint(string(balanceBytes), 10, 64) } - balance += amount + balance, err = add(balance, amount) + if err != nil { + return err + } err = ctx.GetStub().PutState(balanceKey, []byte(strconv.FormatUint(uint64(balance), 10))) if err != nil { @@ -676,9 +920,13 @@ func setBalance(ctx contractapi.TransactionContextInterface, sender string, reci func removeBalance(ctx contractapi.TransactionContextInterface, sender string, ids []uint64, amounts []uint64) error { // Calculate the total amount of each token to withdraw necessaryFunds := make(map[uint64]uint64) // token id -> necessary amount + var err error for i := 0; i < len(amounts); i++ { - necessaryFunds[ids[i]] += amounts[i] + necessaryFunds[ids[i]], err = add(necessaryFunds[ids[i]], amounts[i]) + if err != nil { + return err + } } // Copy the map keys and sort it. This is necessary because iterating maps in Go is not deterministic @@ -708,7 +956,10 @@ func removeBalance(ctx contractapi.TransactionContextInterface, sender string, i } partBalAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64) - partialBalance += partBalAmount + partialBalance, err = add(partialBalance, partBalAmount) + if err != nil { + return err + } _, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(queryResponse.Key) if err != nil { @@ -730,7 +981,11 @@ func removeBalance(ctx contractapi.TransactionContextInterface, sender string, i return fmt.Errorf("sender has insufficient funds for token %v, needed funds: %v, available fund: %v", tokenId, neededAmount, partialBalance) } else if partialBalance > neededAmount { // Send the remainder back to the sender - remainder := partialBalance - neededAmount + remainder, err := sub(partialBalance, neededAmount) + if err != nil { + return err + } + if selfRecipientKeyNeedsToBeRemoved { // Set balance for the key that has the same address for sender and recipient err = setBalance(ctx, sender, sender, tokenId, remainder) @@ -821,7 +1076,10 @@ func balanceOfHelper(ctx contractapi.TransactionContextInterface, account string } balAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64) - balance += balAmount + balance, err = add(balance, balAmount) + if err != nil { + return 0, err + } } return balance, nil @@ -859,3 +1117,42 @@ func sortedKeysToID(m map[ToID]uint64) []ToID { }) return keys } + +//Checks that contract options have been already initialized +func checkInitialized(ctx contractapi.TransactionContextInterface) (bool, error) { + tokenName, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return false, fmt.Errorf("failed to get token name: %v", err) + } else if tokenName == nil { + return false, nil + } + return true, nil +} + +// add two number checking for overflow +func add(b uint64, q uint64) (uint64, error) { + + // Check overflow + var sum uint64 + sum = q + b + + if sum < q { + return 0, fmt.Errorf("Math: addition overflow occurred %d + %d", b, q) + } + + return sum, nil +} + +// sub two number checking for overflow +func sub(b uint64, q uint64) (uint64, error) { + + // Check overflow + var diff uint64 + diff = q - b + + if diff > q { + return 0, fmt.Errorf("Math: subtraction overflow occurred %d - %d", b, q) + } + + return diff, nil +} diff --git a/token-erc-20/chaincode-go/chaincode/token_contract.go b/token-erc-20/chaincode-go/chaincode/token_contract.go index e746ca8f..9e64f030 100644 --- a/token-erc-20/chaincode-go/chaincode/token_contract.go +++ b/token-erc-20/chaincode-go/chaincode/token_contract.go @@ -11,11 +11,16 @@ import ( ) // Define key names for options +const nameKey = "name" +const symbolKey = "symbol" +const decimalsKey = "decimals" const totalSupplyKey = "totalSupply" - // Define objectType names for prefix const allowancePrefix = "allowance" +// Define key names for options + + // SmartContract provides functions for transferring tokens between accounts type SmartContract struct { contractapi.Contract @@ -32,6 +37,15 @@ type event struct { // This function triggers a Transfer event func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount int) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens clientMSPID, err := ctx.GetClientIdentity().GetMSPID() if err != nil { @@ -65,7 +79,10 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount currentBalance, _ = strconv.Atoi(string(currentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer. } - updatedBalance := currentBalance + amount + updatedBalance,err := add(currentBalance, amount) + if err != nil { + return err + } err = ctx.GetStub().PutState(minter, []byte(strconv.Itoa(updatedBalance))) if err != nil { @@ -88,7 +105,11 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount } // Add the mint amount to the total supply and update the state - totalSupply += amount + totalSupply, err = add(totalSupply, amount) + if err != nil { + return err + } + err = ctx.GetStub().PutState(totalSupplyKey, []byte(strconv.Itoa(totalSupply))) if err != nil { return err @@ -114,6 +135,14 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount // This function triggers a Transfer event func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, amount int) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } // Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens clientMSPID, err := ctx.GetClientIdentity().GetMSPID() if err != nil { @@ -147,7 +176,10 @@ func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, amount currentBalance, _ = strconv.Atoi(string(currentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer. - updatedBalance := currentBalance - amount + updatedBalance, err := sub(currentBalance, amount) + if err != nil { + return err + } err = ctx.GetStub().PutState(minter, []byte(strconv.Itoa(updatedBalance))) if err != nil { @@ -168,7 +200,11 @@ func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, amount totalSupply, _ := strconv.Atoi(string(totalSupplyBytes)) // Error handling not needed since Itoa() was used when setting the totalSupply, guaranteeing it was an integer. // Subtract the burn amount to the total supply and update the state - totalSupply -= amount + totalSupply, err = sub(totalSupply, amount) + if err != nil { + return err + } + err = ctx.GetStub().PutState(totalSupplyKey, []byte(strconv.Itoa(totalSupply))) if err != nil { return err @@ -195,6 +231,15 @@ func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, amount // This function triggers a Transfer event func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, recipient string, amount int) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity clientID, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -222,6 +267,16 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, re // BalanceOf returns the balance of the given account func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, account string) (int, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + balanceBytes, err := ctx.GetStub().GetState(account) if err != nil { return 0, fmt.Errorf("failed to read from world state: %v", err) @@ -238,6 +293,15 @@ func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, a // ClientAccountBalance returns the balance of the requesting client's account func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextInterface) (int, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity clientID, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -262,6 +326,15 @@ func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextI // Users can use this function to get their own account id, which they can then give to others as the payment address func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity clientAccountID, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -274,6 +347,15 @@ func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterf // TotalSupply returns the total token supply func (s *SmartContract) TotalSupply(ctx contractapi.TransactionContextInterface) (int, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Retrieve total supply of tokens from state of smart contract totalSupplyBytes, err := ctx.GetStub().GetState(totalSupplyKey) if err != nil { @@ -299,6 +381,15 @@ func (s *SmartContract) TotalSupply(ctx contractapi.TransactionContextInterface) // This function triggers an Approval event func (s *SmartContract) Approve(ctx contractapi.TransactionContextInterface, spender string, value int) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity owner, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -336,6 +427,15 @@ func (s *SmartContract) Approve(ctx contractapi.TransactionContextInterface, spe // Allowance returns the amount still available for the spender to withdraw from the owner func (s *SmartContract) Allowance(ctx contractapi.TransactionContextInterface, owner string, spender string) (int, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Create allowanceKey allowanceKey, err := ctx.GetStub().CreateCompositeKey(allowancePrefix, []string{owner, spender}) if err != nil { @@ -366,6 +466,15 @@ func (s *SmartContract) Allowance(ctx contractapi.TransactionContextInterface, o // This function triggers a Transfer event func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface, from string, to string, value int) error { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity spender, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -399,7 +508,11 @@ func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface } // Decrease the allowance - updatedAllowance := currentAllowance - value + updatedAllowance, err := sub(currentAllowance, value) + if err != nil { + return err + } + err = ctx.GetStub().PutState(allowanceKey, []byte(strconv.Itoa(updatedAllowance))) if err != nil { return err @@ -421,6 +534,90 @@ func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface return nil } +// Name returns a descriptive name for fungible tokens in this contract +// returns {String} Returns the name of the token + +func (s *SmartContract) Name(ctx contractapi.TransactionContextInterface) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + + bytes, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return "", fmt.Errorf("failed to get Name bytes: %s", err) + } + + return string(bytes), nil +} + +// Symbol returns an abbreviated name for fungible tokens in this contract. +// returns {String} Returns the symbol of the token + +func (s *SmartContract) Symbol(ctx contractapi.TransactionContextInterface) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + + bytes, err := ctx.GetStub().GetState(symbolKey) + if err != nil { + return "", fmt.Errorf("failed to get Symbol: %v", err) + } + + return string(bytes), nil +} + +// Set information for a token and intialize contract. +// param {String} name The name of the token +// param {String} symbol The symbol of the token +// param {String} decimals The name of the token +func (s *SmartContract) Initialize(ctx contractapi.TransactionContextInterface, name string, symbol string, decimals string) (bool, error) { + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to intitialize contract + clientMSPID, err := ctx.GetClientIdentity().GetMSPID() + if err != nil { + return false, fmt.Errorf("failed to get MSPID: %v", err) + } else if clientMSPID != "Org1MSP" { + return false, fmt.Errorf("client is not authorized to initialize contract") + } + + //check contract options are not already set, client is not authorized to change them once intitialized + bytes, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return false, fmt.Errorf("failed to get Name: %v", err) + }else if bytes != nil { + return false, fmt.Errorf("contract options are already set, client is not authorized to change them") + } + + err = ctx.GetStub().PutState(nameKey, []byte(name)) + if err != nil { + return false, fmt.Errorf("failed to set token name: %v", err) + } + + err = ctx.GetStub().PutState(symbolKey, []byte(symbol)) + if err != nil { + return false, fmt.Errorf("failed to set symbol: %v", err) + } + + err = ctx.GetStub().PutState(decimalsKey, []byte(decimals)) + if err != nil { + return false, fmt.Errorf("failed to set token name: %v", err) + } + + return true, nil; +} + // Helper Functions // transferHelper is a helper function that transfers tokens from the "from" address to the "to" address @@ -463,8 +660,15 @@ func transferHelper(ctx contractapi.TransactionContextInterface, from string, to toCurrentBalance, _ = strconv.Atoi(string(toCurrentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer. } - fromUpdatedBalance := fromCurrentBalance - value - toUpdatedBalance := toCurrentBalance + value + fromUpdatedBalance, err := sub(fromCurrentBalance, value) + if err != nil { + return err + } + + toUpdatedBalance, err := add(toCurrentBalance, value) + if err != nil { + return err + } err = ctx.GetStub().PutState(from, []byte(strconv.Itoa(fromUpdatedBalance))) if err != nil { @@ -481,3 +685,44 @@ func transferHelper(ctx contractapi.TransactionContextInterface, from string, to return nil } + + +// add two number checking for overflow +func add(b int, q int) (int, error) { + + // Check overflow + var sum int + sum = q + b + + if (sum < q) == (b > 0 && q > 0) { + return 0, fmt.Errorf("Math: addition overflow occurred %d + %d", b, q) + } + + return sum, nil +} + +//Checks that contract options have been already initialized +func checkInitialized(ctx contractapi.TransactionContextInterface) (bool, error) { + tokenName, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return false, fmt.Errorf("failed to get token name: %v", err) + }else if (tokenName == nil){ + return false , nil + } + return true , nil +} + + +// sub two number checking for overflow +func sub(b int, q int) (int, error) { + + // Check overflow + var diff int + diff = q - b + + if (diff > q) == (b > 0 && q > 0) { + return 0, fmt.Errorf("Math: Subtraction overflow occurred %d - %d", b, q) + } + + return diff, nil +} diff --git a/token-erc-20/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc20/ContractConstants.java b/token-erc-20/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc20/ContractConstants.java index 9c283143..9877021f 100644 --- a/token-erc-20/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc20/ContractConstants.java +++ b/token-erc-20/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc20/ContractConstants.java @@ -9,8 +9,8 @@ public enum ContractConstants { BALANCE_PREFIX("balance"), ALLOWANCE_PREFIX("allowance"), NAME_KEY("name"), - SYMBOL_KEY("decimals"), - DECIMALS_KEY("symbolKey"), + SYMBOL_KEY("symbolKey"), + DECIMALS_KEY("decimals"), TOTAL_SUPPLY_KEY("totalSupply"), TRANSFER_EVENT("Transfer"), MINTER_ORG_MSPID("Org1MSP"), diff --git a/token-erc-20/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc20/ERC20TokenContract.java b/token-erc-20/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc20/ERC20TokenContract.java index a8ed75c6..2c3e5602 100644 --- a/token-erc-20/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc20/ERC20TokenContract.java +++ b/token-erc-20/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc20/ERC20TokenContract.java @@ -76,6 +76,10 @@ public final class ERC20TokenContract implements ContractInterface { throw new ChaincodeException( "Client is not authorized to mint new tokens", UNAUTHORIZED_SENDER.toString()); } + + //check contract options are already set first to execute the function + this.checkInitialized(ctx); + // Get ID of submitting client identity String minter = ctx.getClientIdentity().getId(); if (amount <= 0) { @@ -127,6 +131,10 @@ public final class ERC20TokenContract implements ContractInterface { throw new ChaincodeException( "Client is not authorized to burn tokens", UNAUTHORIZED_SENDER.toString()); } + + //check contract options are already set first to execute the function + this.checkInitialized(ctx); + String minter = ctx.getClientIdentity().getId(); if (amount <= 0) { throw new ChaincodeException( @@ -173,6 +181,8 @@ public final class ERC20TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public void Transfer(final Context ctx, final String to, final long value) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); String from = ctx.getClientIdentity().getId(); this.transferHelper(ctx, from, to, value); final Transfer transferEvent = new Transfer(from, to, value); @@ -188,6 +198,8 @@ public final class ERC20TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public long BalanceOf(final Context ctx, final String owner) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); ChaincodeStub stub = ctx.getStub(); CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), owner); String balance = stub.getStringState(balanceKey.toString()); @@ -207,6 +219,8 @@ public final class ERC20TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public long ClientAccountBalance(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); // Get ID of submitting client identity ChaincodeStub stub = ctx.getStub(); String clientAccountID = ctx.getClientIdentity().getId(); @@ -230,6 +244,8 @@ public final class ERC20TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public String ClientAccountID(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); // Get ID of submitting client identity return ctx.getClientIdentity().getId(); } @@ -242,6 +258,8 @@ public final class ERC20TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public long TotalSupply(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); String totalSupply = ctx.getStub().getStringState(TOTAL_SUPPLY_KEY.getValue()); if (stringIsNullOrEmpty(totalSupply)) { throw new ChaincodeException("Total Supply not found", NOT_FOUND.toString()); @@ -259,6 +277,8 @@ public final class ERC20TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public void Approve(final Context ctx, final String spender, final long value) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); ChaincodeStub stub = ctx.getStub(); String owner = ctx.getClientIdentity().getId(); CompositeKey allowanceKey = @@ -282,6 +302,8 @@ public final class ERC20TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public long Allowance(final Context ctx, final String owner, final String spender) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); ChaincodeStub stub = ctx.getStub(); CompositeKey allowanceKey = stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), owner, spender); @@ -308,6 +330,8 @@ public final class ERC20TokenContract implements ContractInterface { @Transaction(intent = Transaction.TYPE.SUBMIT) public void TransferFrom( final Context ctx, final String from, final String to, final long value) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); String spender = ctx.getClientIdentity().getId(); ChaincodeStub stub = ctx.getStub(); // Retrieve the allowance of the spender @@ -402,9 +426,23 @@ public final class ERC20TokenContract implements ContractInterface { * @param decimals The decimals of the token */ @Transaction(intent = Transaction.TYPE.SUBMIT) - public void SetOptions( + public void Initialize( final Context ctx, final String name, final String symbol, final String decimals) { ChaincodeStub stub = ctx.getStub(); + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to set Options for these tokens + String clientMSPID = ctx.getClientIdentity().getMSPID(); + if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSPID.getValue())) { + throw new ChaincodeException( + "Client is not authorized to initialize contract", UNAUTHORIZED_SENDER.toString()); + } + + //check contract options are not already set, client is not authorized to change them once intitialized + String tokenName = stub.getStringState(ContractConstants.NAME_KEY.getValue()); + if (!stringIsNullOrEmpty(tokenName)) { + throw new ChaincodeException("contract options are already set, client is not authorized to change them"); + } + stub.putStringState(NAME_KEY.getValue(), name); stub.putStringState(SYMBOL_KEY.getValue(), symbol); stub.putStringState(DECIMALS_KEY.getValue(), decimals); @@ -420,6 +458,8 @@ public final class ERC20TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public String TokenName(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); String tokenName = ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue()); if (stringIsNullOrEmpty(tokenName)) { throw new ChaincodeException("Token name not found", NOT_FOUND.toString()); @@ -435,6 +475,8 @@ public final class ERC20TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public String TokenSymbol(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); String tokenSymbol = ctx.getStub().getStringState(SYMBOL_KEY.getValue()); if (stringIsNullOrEmpty(tokenSymbol)) { throw new ChaincodeException("Token symbol not found", NOT_FOUND.toString()); @@ -451,6 +493,8 @@ public final class ERC20TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public int Decimals(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); String decimals = ctx.getStub().getStringState(DECIMALS_KEY.getValue()); if (stringIsNullOrEmpty(decimals)) { throw new ChaincodeException("Decimal not found", NOT_FOUND.toString()); @@ -466,4 +510,17 @@ public final class ERC20TokenContract implements ContractInterface { private byte[] marshal(final Object obj) { return new Genson().serialize(obj).getBytes(UTF_8); } + + /** + * Checks that contract options have been already initialized + * + * @param ctx the transaction context + * @return the number of decimals + */ + private void checkInitialized(final Context ctx) { + String tokenName = ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue()); + if (stringIsNullOrEmpty(tokenName)) { + throw new ChaincodeException("Contract options need to be set before calling any function, call Initialize() to initialize contract", NOT_FOUND.toString()); + } + } } diff --git a/token-erc-20/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc20/TokenERC20ContractTest.java b/token-erc-20/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc20/TokenERC20ContractTest.java index a38ac986..716a8513 100644 --- a/token-erc-20/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc20/TokenERC20ContractTest.java +++ b/token-erc-20/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc20/TokenERC20ContractTest.java @@ -58,7 +58,7 @@ public class TokenERC20ContractTest { assertThat(thrown) .isInstanceOf(ChaincodeException.class) .hasNoCause() - .hasMessage("Token name not found"); + .hasMessage("Contract options need to be set before calling any function, call Initialize() to initialize contract"); } @Test @@ -67,6 +67,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(stub.getStringState(SYMBOL_KEY.getValue())).thenReturn("ARBT"); String toknName = contract.TokenSymbol(ctx); assertThat(toknName).isEqualTo("ARBT"); @@ -78,6 +79,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(stub.getStringState(SYMBOL_KEY.getValue())).thenReturn(""); Throwable thrown = catchThrowable(() -> contract.TokenSymbol(ctx)); assertThat(thrown) @@ -92,6 +94,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(stub.getStringState(DECIMALS_KEY.getValue())).thenReturn("18"); long decimal = contract.Decimals(ctx); assertThat(decimal).isEqualTo(18); @@ -103,6 +106,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(stub.getStringState(DECIMALS_KEY.getValue())).thenReturn(""); Throwable thrown = catchThrowable(() -> contract.Decimals(ctx)); assertThat(thrown) @@ -117,6 +121,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn("222222222222"); long totalSupply = contract.TotalSupply(ctx); assertThat(totalSupply).isEqualTo(222222222222L); @@ -128,6 +133,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn(""); Throwable thrown = catchThrowable(() -> contract.TotalSupply(ctx)); assertThat(thrown) @@ -143,6 +149,7 @@ public class TokenERC20ContractTest { ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); when(ctx.getClientIdentity()).thenReturn(ci); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getId()).thenReturn(org1UserId); when(ctx.getStub()).thenReturn(stub); @@ -156,12 +163,15 @@ public class TokenERC20ContractTest { class TokenOperationsInvoke { @Test - public void invokeSetOptionsTest() { + public void invokeInitializeTest() { ERC20TokenContract contract = new ERC20TokenContract(); Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); - contract.SetOptions(ctx, "ARBTToken", "ARBT", "18"); + ClientIdentity ci = mock(ClientIdentity.class); + when(ctx.getClientIdentity()).thenReturn(ci); + when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); + contract.Initialize(ctx, "ARBTToken", "ARBT", "18"); verify(stub).putStringState(NAME_KEY.getValue(), "ARBTToken"); verify(stub).putStringState(SYMBOL_KEY.getValue(), "ARBT"); verify(stub).putStringState(DECIMALS_KEY.getValue(), "18"); @@ -173,6 +183,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); ClientIdentity ci = mock(ClientIdentity.class); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); @@ -192,6 +203,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); ClientIdentity ci = mock(ClientIdentity.class); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); @@ -213,6 +225,7 @@ public class TokenERC20ContractTest { ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); when(ctx.getClientIdentity()).thenReturn(ci); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getId()).thenReturn(org1UserId); when(ctx.getStub()).thenReturn(stub); @@ -234,6 +247,7 @@ public class TokenERC20ContractTest { ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); when(ctx.getClientIdentity()).thenReturn(ci); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ci.getMSPID()).thenReturn("Org2MSP"); when(ci.getId()).thenReturn(org1UserId); when(ctx.getStub()).thenReturn(stub); @@ -256,6 +270,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getId()).thenReturn(org1UserId); @@ -284,6 +299,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getId()).thenReturn(org1UserId); @@ -312,6 +328,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getId()).thenReturn(org1UserId); @@ -343,6 +360,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getId()).thenReturn(org1UserId); @@ -374,6 +392,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getId()).thenReturn(org1UserId); @@ -394,6 +413,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn("Org2MSP"); when(ci.getId()).thenReturn(spender); @@ -418,6 +438,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn("Org1MSP"); when(ci.getId()).thenReturn(org1UserId); @@ -446,6 +467,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getId()).thenReturn(org1UserId); @@ -465,6 +487,7 @@ public class TokenERC20ContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); ClientIdentity ci = mock(ClientIdentity.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(ctx.getClientIdentity()).thenReturn(ci); when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue()); when(ci.getId()).thenReturn(org1UserId); @@ -489,6 +512,7 @@ public class TokenERC20ContractTest { + " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US"; CompositeKey ckFromBalance = mock(CompositeKey.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(stub.createCompositeKey(BALANCE_PREFIX.toString(), org1UserId)) .thenReturn(ckFromBalance); when(ckFromBalance.toString()).thenReturn(BALANCE_PREFIX.getValue() + org1UserId); @@ -529,6 +553,7 @@ public class TokenERC20ContractTest { + " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US"; CompositeKey ckFromBalance = mock(CompositeKey.class); + when(stub.getStringState(NAME_KEY.getValue())).thenReturn("ARBTToken"); when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), org1UserId)) .thenReturn(ckFromBalance); when(ckFromBalance.toString()).thenReturn(BALANCE_PREFIX.getValue() + org1UserId); diff --git a/token-erc-20/chaincode-javascript/lib/tokenERC20.js b/token-erc-20/chaincode-javascript/lib/tokenERC20.js index 4f067306..7f52a76c 100644 --- a/token-erc-20/chaincode-javascript/lib/tokenERC20.js +++ b/token-erc-20/chaincode-javascript/lib/tokenERC20.js @@ -30,7 +30,12 @@ class TokenERC20Contract extends Contract { * @returns {String} Returns the name of the token */ async TokenName(ctx) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const nameBytes = await ctx.stub.getState(nameKey); + return nameBytes.toString(); } @@ -41,6 +46,10 @@ class TokenERC20Contract extends Contract { * @returns {String} Returns the symbol of the token */ async Symbol(ctx) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const symbolBytes = await ctx.stub.getState(symbolKey); return symbolBytes.toString(); } @@ -53,6 +62,10 @@ class TokenERC20Contract extends Contract { * @returns {Number} Returns the number of decimals */ async Decimals(ctx) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const decimalsBytes = await ctx.stub.getState(decimalsKey); const decimals = parseInt(decimalsBytes.toString()); return decimals; @@ -65,6 +78,10 @@ class TokenERC20Contract extends Contract { * @returns {Number} Returns the total token supply */ async TotalSupply(ctx) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const totalSupplyBytes = await ctx.stub.getState(totalSupplyKey); const totalSupply = parseInt(totalSupplyBytes.toString()); return totalSupply; @@ -78,6 +95,10 @@ class TokenERC20Contract extends Contract { * @returns {Number} Returns the account balance */ async BalanceOf(ctx, owner) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const balanceKey = ctx.stub.createCompositeKey(balancePrefix, [owner]); const balanceBytes = await ctx.stub.getState(balanceKey); @@ -99,6 +120,10 @@ class TokenERC20Contract extends Contract { * @returns {Boolean} Return whether the transfer was successful or not */ async Transfer(ctx, to, value) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const from = ctx.clientIdentity.getID(); const transferResp = await this._transfer(ctx, from, to, value); @@ -123,6 +148,10 @@ class TokenERC20Contract extends Contract { * @returns {Boolean} Return whether the transfer was successful or not */ async TransferFrom(ctx, from, to, value) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const spender = ctx.clientIdentity.getID(); // Retrieve the allowance of the spender @@ -149,7 +178,7 @@ class TokenERC20Contract extends Contract { } // Decrease the allowance - const updatedAllowance = currentAllowance - valueInt; + const updatedAllowance = this.sub(currentAllowance, valueInt); await ctx.stub.putState(allowanceKey, Buffer.from(updatedAllowance.toString())); console.log(`spender ${spender} allowance updated from ${currentAllowance} to ${updatedAllowance}`); @@ -202,8 +231,8 @@ class TokenERC20Contract extends Contract { } // Update the balance - const fromUpdatedBalance = fromCurrentBalance - valueInt; - const toUpdatedBalance = toCurrentBalance + valueInt; + const fromUpdatedBalance = this.sub(fromCurrentBalance, valueInt); + const toUpdatedBalance = this.add(toCurrentBalance, valueInt); await ctx.stub.putState(fromBalanceKey, Buffer.from(fromUpdatedBalance.toString())); await ctx.stub.putState(toBalanceKey, Buffer.from(toUpdatedBalance.toString())); @@ -223,6 +252,10 @@ class TokenERC20Contract extends Contract { * @returns {Boolean} Return whether the approval was successful or not */ async Approve(ctx, spender, value) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const owner = ctx.clientIdentity.getID(); const allowanceKey = ctx.stub.createCompositeKey(allowancePrefix, [owner, spender]); @@ -247,6 +280,10 @@ class TokenERC20Contract extends Contract { * @returns {Number} Return the amount of remaining tokens allowed to spent */ async Allowance(ctx, owner, spender) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const allowanceKey = ctx.stub.createCompositeKey(allowancePrefix, [owner, spender]); const allowanceBytes = await ctx.stub.getState(allowanceKey); @@ -269,7 +306,19 @@ class TokenERC20Contract extends Contract { * @param {String} decimals The decimals of the token * @param {String} totalSupply The totalSupply of the token */ - async SetOption(ctx, name, symbol, decimals) { + async Initialize(ctx, name, symbol, decimals) { + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to set Options for these tokens + const clientMSPID = ctx.clientIdentity.getMSPID(); + if (clientMSPID !== 'Org1MSP') { + throw new Error('client is not authorized to initialize contract'); + } + + //check contract options are not already set, client is not authorized to change them once intitialized + const nameBytes = await ctx.stub.getState(nameKey); + if (nameBytes !== undefined) { + throw new Error('contract options are already set, client is not authorized to change them'); + } + await ctx.stub.putState(nameKey, Buffer.from(name)); await ctx.stub.putState(symbolKey, Buffer.from(symbol)); await ctx.stub.putState(decimalsKey, Buffer.from(decimals)); @@ -287,6 +336,9 @@ class TokenERC20Contract extends Contract { */ async Mint(ctx, amount) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens const clientMSPID = ctx.clientIdentity.getMSPID(); if (clientMSPID !== 'Org1MSP') { @@ -311,7 +363,7 @@ class TokenERC20Contract extends Contract { } else { currentBalance = parseInt(currentBalanceBytes.toString()); } - const updatedBalance = currentBalance + amountInt; + const updatedBalance = this.add(currentBalance, amountInt); await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString())); @@ -324,7 +376,7 @@ class TokenERC20Contract extends Contract { } else { totalSupply = parseInt(totalSupplyBytes.toString()); } - totalSupply = totalSupply + amountInt; + totalSupply = this.add(totalSupply, amountInt); await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString())); // Emit the Transfer event @@ -344,6 +396,9 @@ class TokenERC20Contract extends Contract { */ async Burn(ctx, amount) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn tokens const clientMSPID = ctx.clientIdentity.getMSPID(); if (clientMSPID !== 'Org1MSP') { @@ -361,7 +416,7 @@ class TokenERC20Contract extends Contract { throw new Error('The balance does not exist'); } const currentBalance = parseInt(currentBalanceBytes.toString()); - const updatedBalance = currentBalance - amountInt; + const updatedBalance = this.sub(currentBalance, amountInt); await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString())); @@ -370,7 +425,7 @@ class TokenERC20Contract extends Contract { if (!totalSupplyBytes || totalSupplyBytes.length === 0) { throw new Error('totalSupply does not exist.'); } - const totalSupply = parseInt(totalSupplyBytes.toString()) - amountInt; + const totalSupply = this.sub(parseInt(totalSupplyBytes.toString()), amountInt); await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString())); // Emit the Transfer event @@ -388,6 +443,10 @@ class TokenERC20Contract extends Contract { * @returns {Number} Returns the account balance */ async ClientAccountBalance(ctx) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + // Get ID of submitting client identity const clientAccountID = ctx.clientIdentity.getID(); @@ -405,11 +464,40 @@ class TokenERC20Contract extends Contract { // In this implementation, the client account ID is the clientId itself. // Users can use this function to get their own account id, which they can then give to others as the payment address async ClientAccountID(ctx) { + + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + // Get ID of submitting client identity const clientAccountID = ctx.clientIdentity.getID(); return clientAccountID; } + //Checks that contract options have been already initialized + async CheckInitialized(ctx){ + const nameBytes = await ctx.stub.getState(nameKey); + if (nameBytes === undefined) { + throw new Error('contract options need to be set before calling any function, call Initialize() to initialize contract'); + } + } + + // add two number checking for overflow + add(a, b) { + let c = a + b; + if (a !== c - b || b !== c - a){ + throw new Error(`Math: addition overflow occurred ${a} + ${b}`); + } + return c; + } + + // add two number checking for overflow + sub(a, b) { + let c = a - b; + if (a !== c + b || b !== a - c){ + throw new Error(`Math: subtraction overflow occurred ${a} - ${b}`); + } + return c; + } } module.exports = TokenERC20Contract; diff --git a/token-erc-20/chaincode-javascript/test/tokenERC20.test.js b/token-erc-20/chaincode-javascript/test/tokenERC20.test.js index cf33ea81..dd7e37b3 100644 --- a/token-erc-20/chaincode-javascript/test/tokenERC20.test.js +++ b/token-erc-20/chaincode-javascript/test/tokenERC20.test.js @@ -26,7 +26,7 @@ describe('Chaincode', () => { let mockStub; let mockClientIdentity; - beforeEach('Sandbox creation', () => { + beforeEach('Sandbox creation', async () => { sandbox = sinon.createSandbox(); token = new TokenERC20Contract('token-erc20'); @@ -36,6 +36,8 @@ describe('Chaincode', () => { mockClientIdentity = sinon.createStubInstance(ClientIdentity); ctx.clientIdentity = mockClientIdentity; + await token.Initialize(ctx, 'some name', 'some symbol', '2'); + mockStub.putState.resolves('some state'); mockStub.setEvent.returns('set event'); @@ -198,13 +200,18 @@ describe('Chaincode', () => { }); }); - describe('#SetOption', () => { + describe('#Initialize', () => { it('should work', async () => { - const response = await token.SetOption(ctx, 'some name', 'some symbol', '2'); + //we consider that is been already initialize in the before each statement sinon.assert.calledWith(mockStub.putState, 'name', Buffer.from('some name')); sinon.assert.calledWith(mockStub.putState, 'symbol', Buffer.from('some symbol')); sinon.assert.calledWith(mockStub.putState, 'decimals', Buffer.from('2')); - expect(response).to.equals(true); + }); + + it('should failed if called a second time', async () => { + //we consider that is been already initialize in the before each statement + await expect(await token.Initialize(ctx, 'some name', 'some symbol', '2')) + .to.be.rejectedWith(Error, 'contract options are already set, client is not authorized to change them'); }); }); diff --git a/token-erc-721/chaincode-go/chaincode/erc721-contract.go b/token-erc-721/chaincode-go/chaincode/erc721-contract.go index 94257a13..eb3f07d1 100644 --- a/token-erc-721/chaincode-go/chaincode/erc721-contract.go +++ b/token-erc-721/chaincode-go/chaincode/erc721-contract.go @@ -60,6 +60,16 @@ func _nftExists(ctx contractapi.TransactionContextInterface, tokenId string) boo // param owner {String} An owner for whom to query the balance // returns {int} The number of non-fungible tokens owned by the owner, possibly zero func (c *TokenERC721Contract) BalanceOf(ctx contractapi.TransactionContextInterface, owner string) int { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + panic("failed to check if contract ia already initialized:"+ err.Error()) + } + if !initialized { + panic("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // There is a key record for every non-fungible token in the format of balancePrefix.owner.tokenId. // BalanceOf() queries for and counts all records matching balancePrefix.owner.* @@ -85,6 +95,16 @@ func (c *TokenERC721Contract) BalanceOf(ctx contractapi.TransactionContextInterf // param {String} tokenId The identifier for a non-fungible token // returns {String} Return the owner of the non-fungible token func (c *TokenERC721Contract) OwnerOf(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + nft, err := _readNFT(ctx, tokenId) if err != nil { return "", fmt.Errorf("could not process OwnerOf for tokenId: %w", err) @@ -98,6 +118,16 @@ func (c *TokenERC721Contract) OwnerOf(ctx contractapi.TransactionContextInterfac // param {String} tokenId the non-fungible token to approve // returns {Boolean} Return whether the approval was successful or not func (c *TokenERC721Contract) Approve(ctx contractapi.TransactionContextInterface, operator string, tokenId string) (bool, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return false, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + sender64, err := ctx.GetClientIdentity().GetID() if err != nil { return false, fmt.Errorf("failed to GetClientIdentity: %v", err) @@ -151,6 +181,16 @@ func (c *TokenERC721Contract) Approve(ctx contractapi.TransactionContextInterfac // param {Boolean} approved True if the operator is approved, false to revoke approval // returns {Boolean} Return whether the approval was successful or not func (c *TokenERC721Contract) SetApprovalForAll(ctx contractapi.TransactionContextInterface, operator string, approved bool) (bool, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return false, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + sender64, err := ctx.GetClientIdentity().GetID() if err != nil { return false, fmt.Errorf("failed to GetClientIdentity: %v", err) @@ -196,6 +236,16 @@ func (c *TokenERC721Contract) SetApprovalForAll(ctx contractapi.TransactionConte // param {String} operator The client that acts on behalf of the owner // returns {Boolean} Return true if the operator is an approved operator for the owner, false otherwise func (c *TokenERC721Contract) IsApprovedForAll(ctx contractapi.TransactionContextInterface, owner string, operator string) (bool, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return false,fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{owner, operator}) if err != nil { return false, fmt.Errorf("failed to CreateCompositeKey: %v", err) @@ -223,6 +273,16 @@ func (c *TokenERC721Contract) IsApprovedForAll(ctx contractapi.TransactionContex // param {String} tokenId the non-fungible token to find the approved client for // returns {Object} Return the approved client for this non-fungible token, or null if there is none func (c *TokenERC721Contract) GetApproved(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "false",fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "false", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + nft, err := _readNFT(ctx, tokenId) if err != nil { return "false", fmt.Errorf("failed GetApproved for tokenId : %v", err) @@ -239,6 +299,16 @@ func (c *TokenERC721Contract) GetApproved(ctx contractapi.TransactionContextInte func (c *TokenERC721Contract) TransferFrom(ctx contractapi.TransactionContextInterface, from string, to string, tokenId string) (bool, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return false, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + + // Get ID of submitting client identity sender64, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -336,6 +406,16 @@ func (c *TokenERC721Contract) TransferFrom(ctx contractapi.TransactionContextInt // returns {String} Returns the name of the token func (c *TokenERC721Contract) Name(ctx contractapi.TransactionContextInterface) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + bytes, err := ctx.GetStub().GetState(nameKey) if err != nil { return "", fmt.Errorf("failed to get Name bytes: %s", err) @@ -348,6 +428,16 @@ func (c *TokenERC721Contract) Name(ctx contractapi.TransactionContextInterface) // returns {String} Returns the symbol of the token func (c *TokenERC721Contract) Symbol(ctx contractapi.TransactionContextInterface) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + bytes, err := ctx.GetStub().GetState(symbolKey) if err != nil { return "", fmt.Errorf("failed to get Symbol: %v", err) @@ -361,6 +451,16 @@ func (c *TokenERC721Contract) Symbol(ctx contractapi.TransactionContextInterface // returns {String} Returns the URI of the token func (c *TokenERC721Contract) TokenURI(ctx contractapi.TransactionContextInterface, tokenId string) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + nft, err := _readNFT(ctx, tokenId) if err != nil { return "", fmt.Errorf("failed to get TokenURI: %v", err) @@ -376,6 +476,16 @@ func (c *TokenERC721Contract) TokenURI(ctx contractapi.TransactionContextInterfa // where each one of them has an assigned and queryable owner. func (c *TokenERC721Contract) TotalSupply(ctx contractapi.TransactionContextInterface) int { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + panic("failed to check if contract ia already initialized:"+ err.Error()) + } + if !initialized { + panic("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // There is a key record for every non-fungible token in the format of nftPrefix.tokenId. // TotalSupply() queries for and counts all records matching nftPrefix.* @@ -399,11 +509,11 @@ func (c *TokenERC721Contract) TotalSupply(ctx contractapi.TransactionContextInte } // ============== ERC721 enumeration extension =============== -// Set optional information for a token. +// Set information for a token and intialize contract. // param {String} name The name of the token // param {String} symbol The symbol of the token -func (c *TokenERC721Contract) SetOption(ctx contractapi.TransactionContextInterface, name string, symbol string) (bool, error) { +func (c *TokenERC721Contract) Initialize(ctx contractapi.TransactionContextInterface, name string, symbol string) (bool, error) { // Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the name and symbol clientMSPID, err := ctx.GetClientIdentity().GetMSPID() if err != nil { @@ -412,6 +522,13 @@ func (c *TokenERC721Contract) SetOption(ctx contractapi.TransactionContextInterf return false, fmt.Errorf("client is not authorized to set the name and symbol of the token") } + bytes, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return false, fmt.Errorf("failed to get Name: %v", err) + }else if bytes != nil { + return false, fmt.Errorf("contract options are already set, client is not authorized to change them") + } + err = ctx.GetStub().PutState(nameKey, []byte(name)) if err != nil { return false, fmt.Errorf("failed to PutState nameKey %s: %v", nameKey, err) @@ -432,6 +549,16 @@ func (c *TokenERC721Contract) SetOption(ctx contractapi.TransactionContextInterf func (c *TokenERC721Contract) MintWithTokenURI(ctx contractapi.TransactionContextInterface, tokenId string, tokenURI string) (*Nft, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return nil, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return nil, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + + // Check minter authorization - this sample assumes Org1 is the issuer with privilege to mint a new token clientMSPID, err := ctx.GetClientIdentity().GetMSPID() if err != nil { @@ -518,6 +645,16 @@ func (c *TokenERC721Contract) MintWithTokenURI(ctx contractapi.TransactionContex // param {String} tokenId Unique ID of a non-fungible token // returns {Boolean} Return whether the burn was successful or not func (c *TokenERC721Contract) Burn(ctx contractapi.TransactionContextInterface, tokenId string) (bool, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return false, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return false, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + owner64, err := ctx.GetClientIdentity().GetID() if err != nil { return false, fmt.Errorf("failed to GetClientIdentity owner64: %v", err) @@ -582,6 +719,16 @@ func (c *TokenERC721Contract) Burn(ctx contractapi.TransactionContextInterface, // ClientAccountBalance returns the balance of the requesting client's account. // returns {Number} Returns the account balance func (c *TokenERC721Contract) ClientAccountBalance(ctx contractapi.TransactionContextInterface) (int, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return 0, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return 0, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity clientAccountID64, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -603,6 +750,16 @@ func (c *TokenERC721Contract) ClientAccountBalance(ctx contractapi.TransactionCo // Users can use this function to get their own account id, which they can then give to others as the payment address func (c *TokenERC721Contract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity clientAccountID64, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -617,3 +774,14 @@ func (c *TokenERC721Contract) ClientAccountID(ctx contractapi.TransactionContext return clientAccount, nil } + +//Checks that contract options have been already initialized +func checkInitialized(ctx contractapi.TransactionContextInterface) (bool, error) { + tokenName, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return false, fmt.Errorf("failed to get token name: %v", err) + }else if (tokenName == nil){ + return false, nil + } + return true, nil +} diff --git a/token-erc-721/chaincode-go/chaincode/erc721-contract_test.go b/token-erc-721/chaincode-go/chaincode/erc721-contract_test.go index cbb88c67..7c8053b8 100644 --- a/token-erc-721/chaincode-go/chaincode/erc721-contract_test.go +++ b/token-erc-721/chaincode-go/chaincode/erc721-contract_test.go @@ -242,11 +242,11 @@ func TestTokenURI(t *testing.T) { assert.Equal(t, "https://example.com/nft101.json", tokenURI) } -func TestSetOption(t *testing.T) { +func TestInitialize(t *testing.T) { ctx, _ := setupStub() c := new(TokenERC721Contract) - option, _ := c.SetOption(ctx, "someName", "someSymbol") + option, _ := c.Initialize(ctx, "someName", "someSymbol") assert.Equal(t, true, option) } diff --git a/token-erc-721/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc721/ERC721TokenContract.java b/token-erc-721/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc721/ERC721TokenContract.java index b682a2a1..cc15d782 100644 --- a/token-erc-721/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc721/ERC721TokenContract.java +++ b/token-erc-721/chaincode-java/src/main/java/org/hyperledger/fabric/samples/erc721/ERC721TokenContract.java @@ -54,6 +54,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public long BalanceOf(final Context ctx, final String owner) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final ChaincodeStub stub = ctx.getStub(); final CompositeKey balanceKey = stub.createCompositeKey(ContractConstants.BALANCE.getValue(), owner); @@ -76,6 +78,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public String OwnerOf(final Context ctx, final String tokenId) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final NFT nft = this._readNft(ctx, tokenId); if (stringIsNullOrEmpty(nft.getOwner())) { final String errorMessage = String.format("No owner is assigned o the token %s", tokenId); @@ -94,6 +98,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public boolean IsApprovedForAll(final Context ctx, final String owner, final String operator) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final ChaincodeStub stub = ctx.getStub(); final CompositeKey approvalKey = stub.createCompositeKey(ContractConstants.APPROVAL.getValue(), owner, operator); @@ -116,6 +122,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public void Approve(final Context ctx, final String operator, final String tokenId) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final ChaincodeStub stub = ctx.getStub(); final String sender = ctx.getClientIdentity().getId(); NFT nft = this._readNft(ctx, tokenId); @@ -144,6 +152,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public void SetApprovalForAll(final Context ctx, final String operator, final boolean approved) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final String sender = ctx.getClientIdentity().getId(); final ChaincodeStub stub = ctx.getStub(); final Approval nftApproval = new Approval(sender, operator, approved); @@ -163,6 +173,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public String GetApproved(final Context ctx, final String tokenId) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final NFT nft = this._readNft(ctx, tokenId); return nft.getApproved(); } @@ -178,6 +190,8 @@ public class ERC721TokenContract implements ContractInterface { @Transaction(intent = Transaction.TYPE.SUBMIT) public void TransferFrom( final Context ctx, final String from, final String to, final String tokenId) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final String sender = ctx.getClientIdentity().getId(); final ChaincodeStub stub = ctx.getStub(); NFT nft = this._readNft(ctx, tokenId); @@ -235,6 +249,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public String Name(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); return ctx.getStub().getStringState(ContractConstants.NAMEKEY.getValue()); } @@ -246,6 +262,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public String Symbol(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); return ctx.getStub().getStringState(ContractConstants.SYMBOLKEY.getValue()); } @@ -258,6 +276,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public String TokenURI(final Context ctx, final String tokenId) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final NFT nft = this._readNft(ctx, tokenId); return nft.getTokenURI(); } @@ -275,6 +295,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public long TotalSupply(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final ChaincodeStub stub = ctx.getStub(); final CompositeKey nftKey = stub.createCompositeKey(ContractConstants.NFT.getValue()); final QueryResultsIterator iterator = stub.getStateByPartialCompositeKey(nftKey); @@ -297,16 +319,24 @@ public class ERC721TokenContract implements ContractInterface { * @param symbol The symbol of the token */ @Transaction(intent = Transaction.TYPE.SUBMIT) - public void SetOption(final Context ctx, final String name, final String symbol) { + public void Initialize(final Context ctx, final String name, final String symbol) { + + final ChaincodeStub stub = ctx.getStub(); final String clientMSPID = ctx.getClientIdentity().getMSPID(); // Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the // name and symbol if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSP.getValue())) { throw new ChaincodeException( - "Client is not authorized to set the name and symbol of the token"); + "Client is not authorized to initialize the contract (set the name and symbol of the token)"); } - final ChaincodeStub stub = ctx.getStub(); + + //check contract options are not already set, client is not authorized to change them once intitialized + String tokenName = stub.getStringState(ContractConstants.NAMEKEY.getValue()); + if (!stringIsNullOrEmpty(tokenName)) { + throw new ChaincodeException("contract options are already set, client is not authorized to change them"); + } + stub.putStringState(ContractConstants.NAMEKEY.getValue(), name); stub.putStringState(ContractConstants.SYMBOLKEY.getValue(), symbol); } @@ -321,6 +351,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public NFT MintWithTokenURI(final Context ctx, final String tokenId, final String tokenURI) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final String clientMSPID = ctx.getClientIdentity().getMSPID(); final ChaincodeStub stub = ctx.getStub(); // Check minter authorization this sample assumes Org1 is the issuer with privilege to mint a @@ -360,6 +392,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public void Burn(final Context ctx, final String tokenId) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); final ChaincodeStub stub = ctx.getStub(); final String owner = ctx.getClientIdentity().getId(); // Check if a caller is the owner of the non-fungible token @@ -390,6 +424,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public long ClientAccountBalance(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); return this.BalanceOf(ctx, ctx.getClientIdentity().getId()); } @@ -403,6 +439,8 @@ public class ERC721TokenContract implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.EVALUATE) public String ClientAccountID(final Context ctx) { + //check contract options are already set first to execute the function + this.checkInitialized(ctx); return ctx.getClientIdentity().getId(); } @@ -437,4 +475,17 @@ public class ERC721TokenContract implements ContractInterface { final String nft = stub.getStringState(nftKey.toString()); return ((stringIsNullOrEmpty(nft)) ? false : true); } + + /** + * Checks that contract options have been already initialized + * + * @param ctx the transaction context + * @return the number of decimals + */ + private void checkInitialized(final Context ctx) { + String tokenName = ctx.getStub().getStringState(ContractConstants.NAMEKEY.getValue()); + if (stringIsNullOrEmpty(tokenName)) { + throw new ChaincodeException("Contract options need to be set before calling any function, call Initialize() to initialize contract", ContractErrors.TOKEN_NOT_FOUND.toString()); + } + } } diff --git a/token-erc-721/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc721/ERC721TokenContractTest.java b/token-erc-721/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc721/ERC721TokenContractTest.java index 1ceb8c8f..218970d0 100644 --- a/token-erc-721/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc721/ERC721TokenContractTest.java +++ b/token-erc-721/chaincode-java/src/test/java/org/hyperledger/fabric/samples/erc721/ERC721TokenContractTest.java @@ -84,6 +84,7 @@ public class ERC721TokenContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); List list = new ArrayList<>(); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); list.add(new MockKeyValue("balance_Alice_101", "\u0000")); list.add(new MockKeyValue("balance_Alice_101", "\u0000")); when(ctx.getStub()).thenReturn(stub); @@ -103,6 +104,7 @@ public class ERC721TokenContractTest { NFT nft = new NFT("101", "Alicd", "http://test.com", ""); when(ctx.getStub()).thenReturn(stub); CompositeKey ck = mock(CompositeKey.class); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); when(stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck); when(stub.getStringState(ck.toString())).thenReturn(nft.toJSONString()); @@ -129,6 +131,7 @@ public class ERC721TokenContractTest { this.stub = mock(ChaincodeStub.class); when(this.ctx.getStub()).thenReturn(stub); contract = new ERC721TokenContract(); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); CompositeKey ck1 = mock(CompositeKey.class); when(ck1.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); when(this.stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck1); @@ -256,6 +259,7 @@ public class ERC721TokenContractTest { ChaincodeStub stub = mock(ChaincodeStub.class); NFT nft = new NFT("101", "Alice", "http://test.com", ""); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); CompositeKey ck = mock(CompositeKey.class); when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); when(stub.createCompositeKey(ContractConstants.NFT.getValue(), "101")).thenReturn(ck); @@ -281,6 +285,7 @@ public class ERC721TokenContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); ClientIdentity ci = null; ci = mock(ClientIdentity.class); when(ctx.getClientIdentity()).thenReturn(ci); @@ -306,6 +311,7 @@ public class ERC721TokenContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); ClientIdentity ci = null; ci = mock(ClientIdentity.class); when(ctx.getClientIdentity()).thenReturn(ci); @@ -327,6 +333,7 @@ public class ERC721TokenContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); ClientIdentity ci = null; ci = mock(ClientIdentity.class); when(ctx.getClientIdentity()).thenReturn(ci); @@ -362,6 +369,7 @@ public class ERC721TokenContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ANFT"); ERC721TokenContract contract = new ERC721TokenContract(); final String name = contract.Name(ctx); @@ -373,6 +381,7 @@ public class ERC721TokenContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); ERC721TokenContract contract = new ERC721TokenContract(); final NFT nft = new NFT("101", "Alice", "http://test.com", "Bob"); CompositeKey ck = mock(CompositeKey.class); @@ -388,6 +397,7 @@ public class ERC721TokenContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); List list = new ArrayList<>(); list.add( new MockKeyValue( @@ -417,6 +427,7 @@ public class ERC721TokenContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); final NFT nft = new NFT("101", "Alice", "DummyURI", ""); CompositeKey ck = mock(CompositeKey.class); when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); @@ -445,6 +456,7 @@ public class ERC721TokenContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); final NFT nft = new NFT("101", "Alice", "DummyURI", ""); CompositeKey ck = mock(CompositeKey.class); when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); @@ -471,6 +483,7 @@ public class ERC721TokenContractTest { Context ctx = mock(Context.class); ChaincodeStub stub = mock(ChaincodeStub.class); when(ctx.getStub()).thenReturn(stub); + when(stub.getStringState(ContractConstants.NAMEKEY.getValue())).thenReturn("ARBTToken"); final NFT nft = new NFT("101", "Alice", "DummyURI", ""); CompositeKey ck = mock(CompositeKey.class); when(ck.toString()).thenReturn(ContractConstants.NFT.getValue() + "101"); diff --git a/token-erc-721/chaincode-javascript/lib/tokenERC721.js b/token-erc-721/chaincode-javascript/lib/tokenERC721.js index 184d4eff..b87bf946 100644 --- a/token-erc-721/chaincode-javascript/lib/tokenERC721.js +++ b/token-erc-721/chaincode-javascript/lib/tokenERC721.js @@ -25,6 +25,9 @@ class TokenERC721Contract extends Contract { * @returns {Number} The number of non-fungible tokens owned by the owner, possibly zero */ async BalanceOf(ctx, owner) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + // There is a key record for every non-fungible token in the format of balancePrefix.owner.tokenId. // BalanceOf() queries for and counts all records matching balancePrefix.owner.* const iterator = await ctx.stub.getStateByPartialCompositeKey(balancePrefix, [owner]); @@ -47,6 +50,9 @@ class TokenERC721Contract extends Contract { * @returns {String} Return the owner of the non-fungible token */ async OwnerOf(ctx, tokenId) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const nft = await this._readNFT(ctx, tokenId); const owner = nft.owner; if (!owner) { @@ -67,6 +73,9 @@ class TokenERC721Contract extends Contract { * @returns {Boolean} Return whether the transfer was successful or not */ async TransferFrom(ctx, from, to, tokenId) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const sender = ctx.clientIdentity.getID(); const nft = await this._readNFT(ctx, tokenId); @@ -118,6 +127,9 @@ class TokenERC721Contract extends Contract { * @returns {Boolean} Return whether the approval was successful or not */ async Approve(ctx, approved, tokenId) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const sender = ctx.clientIdentity.getID(); const nft = await this._readNFT(ctx, tokenId); @@ -153,6 +165,9 @@ class TokenERC721Contract extends Contract { * @returns {Boolean} Return whether the approval was successful or not */ async SetApprovalForAll(ctx, operator, approved) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const sender = ctx.clientIdentity.getID(); const approval = { owner: sender, operator: operator, approved: approved }; @@ -174,6 +189,9 @@ class TokenERC721Contract extends Contract { * @returns {Object} Return the approved client for this non-fungible token, or null if there is none */ async GetApproved(ctx, tokenId) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const nft = await this._readNFT(ctx, tokenId); return nft.approved; } @@ -187,6 +205,9 @@ class TokenERC721Contract extends Contract { * @returns {Boolean} Return true if the operator is an approved operator for the owner, false otherwise */ async IsApprovedForAll(ctx, owner, operator) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const approvalKey = ctx.stub.createCompositeKey(approvalPrefix, [owner, operator]); const approvalBytes = await ctx.stub.getState(approvalKey); let approved; @@ -209,6 +230,9 @@ class TokenERC721Contract extends Contract { * @returns {String} Returns the name of the token */ async Name(ctx) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const nameAsBytes = await ctx.stub.getState(nameKey); return nameAsBytes.toString(); } @@ -220,6 +244,9 @@ class TokenERC721Contract extends Contract { * @returns {String} Returns the symbol of the token */ async Symbol(ctx) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const symbolAsBytes = await ctx.stub.getState(symbolKey); return symbolAsBytes.toString(); } @@ -232,6 +259,9 @@ class TokenERC721Contract extends Contract { * @returns {String} Returns the URI of the token */ async TokenURI(ctx, tokenId) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const nft = await this._readNFT(ctx, tokenId); return nft.tokenURI; } @@ -246,6 +276,9 @@ class TokenERC721Contract extends Contract { * where each one of them has an assigned and queryable owner. */ async TotalSupply(ctx) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + // There is a key record for every non-fungible token in the format of nftPrefix.tokenId. // TotalSupply() queries for and counts all records matching nftPrefix.* const iterator = await ctx.stub.getStateByPartialCompositeKey(nftPrefix, []); @@ -269,14 +302,20 @@ class TokenERC721Contract extends Contract { * @param {String} name The name of the token * @param {String} symbol The symbol of the token */ - async SetOption(ctx, name, symbol) { + async Initialize(ctx, name, symbol) { - // Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the name and symbol + // Check minter authorization - this sample assumes Org1 is the issuer with privilege to initialize contract (set the name and symbol) const clientMSPID = ctx.clientIdentity.getMSPID(); if (clientMSPID !== 'Org1MSP') { throw new Error('client is not authorized to set the name and symbol of the token'); } + //check contract options are not already set, client is not authorized to change them once intitialized + const nameBytes = await ctx.stub.getState(nameKey); + if (nameBytes !== undefined) { + throw new Error('contract options are already set, client is not authorized to change them'); + } + await ctx.stub.putState(nameKey, Buffer.from(name)); await ctx.stub.putState(symbolKey, Buffer.from(symbol)); return true; @@ -291,6 +330,8 @@ class TokenERC721Contract extends Contract { * @returns {Object} Return the non-fungible token object */ async MintWithTokenURI(ctx, tokenId, tokenURI) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); // Check minter authorization - this sample assumes Org1 is the issuer with privilege to mint a new token const clientMSPID = ctx.clientIdentity.getMSPID(); @@ -341,6 +382,9 @@ class TokenERC721Contract extends Contract { * @returns {Boolean} Return whether the burn was successful or not */ async Burn(ctx, tokenId) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + const owner = ctx.clientIdentity.getID(); // Check if a caller is the owner of the non-fungible token @@ -388,6 +432,9 @@ class TokenERC721Contract extends Contract { * @returns {Number} Returns the account balance */ async ClientAccountBalance(ctx) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + // Get ID of submitting client identity const clientAccountID = ctx.clientIdentity.getID(); return this.BalanceOf(ctx, clientAccountID); @@ -397,10 +444,21 @@ class TokenERC721Contract extends Contract { // In this implementation, the client account ID is the clientId itself. // Users can use this function to get their own account id, which they can then give to others as the payment address async ClientAccountID(ctx) { + //check contract options are already set first to execute the function + await this.CheckInitialized(ctx); + // Get ID of submitting client identity const clientAccountID = ctx.clientIdentity.getID(); return clientAccountID; } + + //Checks that contract options have been already initialized + async CheckIntitialized(ctx){ + const nameBytes = await ctx.stub.getState(nameKey); + if (nameBytes === undefined) { + throw new Error('contract options need to be set before calling any function, call Initialize() to initialize contract'); + } + } } module.exports = TokenERC721Contract; \ No newline at end of file diff --git a/token-utxo/chaincode-go/chaincode/token_contract.go b/token-utxo/chaincode-go/chaincode/token_contract.go index 963e11aa..9b98ae2f 100644 --- a/token-utxo/chaincode-go/chaincode/token_contract.go +++ b/token-utxo/chaincode-go/chaincode/token_contract.go @@ -20,9 +20,23 @@ type UTXO struct { Amount int `json:"amount"` } +// Define key names for options +const nameKey = "name" +const symbolKey = "symbol" +const totalSupplyKey = "totalSupply" + // Mint creates a new unspent transaction output (UTXO) owned by the minter func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount int) (*UTXO, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return nil, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return nil, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens clientMSPID, err := ctx.GetClientIdentity().GetMSPID() if err != nil { @@ -63,6 +77,15 @@ func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount // Transfer transfers UTXOs containing tokens from client to recipient(s) func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, utxoInputKeys []string, utxoOutputs []UTXO) ([]UTXO, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return nil, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return nil, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity clientID, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -100,7 +123,7 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, ut Amount: amount, } - totalInputAmount += amount + totalInputAmount,err = add(totalInputAmount, amount) utxoInputs[utxoInputKey] = utxoInput } @@ -115,7 +138,7 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, ut utxoOutputs[i].Key = fmt.Sprintf("%s.%d", txID, i) - totalOutputAmount += utxoOutput.Amount + totalOutputAmount,err = add(totalOutputAmount, utxoOutput.Amount) } // Validate total inputs equals total outputs @@ -158,6 +181,15 @@ func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, ut // ClientUTXOs returns all UTXOs owned by the calling client func (s *SmartContract) ClientUTXOs(ctx contractapi.TransactionContextInterface) ([]*UTXO, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return nil, fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return nil, fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity clientID, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -211,6 +243,15 @@ func (s *SmartContract) ClientUTXOs(ctx contractapi.TransactionContextInterface) // Users can use this function to get their own client id, which they can then give to others as the payment address func (s *SmartContract) ClientID(ctx contractapi.TransactionContextInterface) (string, error) { + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + // Get ID of submitting client identity clientID, err := ctx.GetClientIdentity().GetID() if err != nil { @@ -219,3 +260,109 @@ func (s *SmartContract) ClientID(ctx contractapi.TransactionContextInterface) (s return clientID, nil } + +// Name returns a descriptive name for fungible tokens in this contract +// returns {String} Returns the name of the token + +func (s *SmartContract) Name(ctx contractapi.TransactionContextInterface) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + + bytes, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return "", fmt.Errorf("failed to get Name bytes: %s", err) + } + + return string(bytes), nil +} + +// Symbol returns an abbreviated name for fungible tokens in this contract. +// returns {String} Returns the symbol of the token + +func (s *SmartContract) Symbol(ctx contractapi.TransactionContextInterface) (string, error) { + + //check if contract has been intilized first + initialized, err := checkInitialized(ctx) + if err != nil { + return "", fmt.Errorf("failed to check if contract ia already initialized: %v", err) + } + if !initialized { + return "", fmt.Errorf("Contract options need to be set before calling any function, call Initialize() to initialize contract") + } + + bytes, err := ctx.GetStub().GetState(symbolKey) + if err != nil { + return "", fmt.Errorf("failed to get Symbol: %v", err) + } + + return string(bytes), nil +} + +// Set information for a token and intialize contract. +// param {String} name The name of the token +// param {String} symbol The symbol of the token +func (s *SmartContract) Initialize(ctx contractapi.TransactionContextInterface, name string, symbol string) (bool, error) { + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to intitialize contract + clientMSPID, err := ctx.GetClientIdentity().GetMSPID() + if err != nil { + return false, fmt.Errorf("failed to get MSPID: %v", err) + } else if clientMSPID != "Org1MSP" { + return false, fmt.Errorf("client is not authorized to initialize contract") + } + + //check contract options are not already set, client is not authorized to change them once intitialized + bytes, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return false, fmt.Errorf("failed to get Name: %v", err) + }else if bytes != nil { + return false, fmt.Errorf("contract options are already set, client is not authorized to change them") + } + + err = ctx.GetStub().PutState(nameKey, []byte(name)) + if err != nil { + return false, fmt.Errorf("failed to set token name: %v", err) + } + + err = ctx.GetStub().PutState(symbolKey, []byte(symbol)) + if err != nil { + return false, fmt.Errorf("failed to set symbol: %v", err) + } + + log.Printf("name: %v, symbol: %v", name, symbol) + + return true, nil; +} + +//Checks that contract options have been already initialized +func checkInitialized(ctx contractapi.TransactionContextInterface) (bool, error) { + tokenName, err := ctx.GetStub().GetState(nameKey) + if err != nil { + return false, fmt.Errorf("failed to get token name: %v", err) + }else if (tokenName == nil){ + return false , nil + } + return true , nil +} + + +// add two number checking for overflow +func add(b int, q int) (int, error) { + + // Check overflow + var sum int + sum = q + b + + if (sum < q) == (b > 0 && q > 0) { + return 0, fmt.Errorf("Math: addition overflow occurred %d + %d", b, q) + } + + return sum, nil +}