mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 07:25:10 +00:00
733 lines
24 KiB
Go
733 lines
24 KiB
Go
package chaincode
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
|
|
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// event provides an organized struct for emitting events
|
|
type event struct {
|
|
From string `json:"from"`
|
|
To string `json:"to"`
|
|
Value int `json:"value"`
|
|
}
|
|
|
|
// Mint creates new tokens and adds them to minter's account balance
|
|
// 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 is already initialized: %v", err)
|
|
}
|
|
if !initialized {
|
|
return errors.New("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 {
|
|
return fmt.Errorf("failed to get MSPID: %v", err)
|
|
}
|
|
if clientMSPID != "Org1MSP" {
|
|
return errors.New("client is not authorized to mint new tokens")
|
|
}
|
|
|
|
// Get ID of submitting client identity
|
|
minter, err := ctx.GetClientIdentity().GetID()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get client id: %v", err)
|
|
}
|
|
|
|
if amount <= 0 {
|
|
return errors.New("mint amount must be a positive integer")
|
|
}
|
|
|
|
currentBalanceBytes, err := ctx.GetStub().GetState(minter)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read minter account %s from world state: %v", minter, err)
|
|
}
|
|
|
|
var currentBalance int
|
|
|
|
// If minter current balance doesn't yet exist, we'll create it with a current balance of 0
|
|
if currentBalanceBytes == nil {
|
|
currentBalance = 0
|
|
} else {
|
|
currentBalance, _ = strconv.Atoi(string(currentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer.
|
|
}
|
|
|
|
updatedBalance, err := add(currentBalance, amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ctx.GetStub().PutState(minter, []byte(strconv.Itoa(updatedBalance)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the totalSupply
|
|
totalSupplyBytes, err := ctx.GetStub().GetState(totalSupplyKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to retrieve total token supply: %v", err)
|
|
}
|
|
|
|
var totalSupply int
|
|
|
|
// If no tokens have been minted, initialize the totalSupply
|
|
if totalSupplyBytes == nil {
|
|
totalSupply = 0
|
|
} else {
|
|
totalSupply, _ = strconv.Atoi(string(totalSupplyBytes)) // Error handling not needed since Itoa() was used when setting the totalSupply, guaranteeing it was an integer.
|
|
}
|
|
|
|
// Add the mint amount to the total supply and update the state
|
|
totalSupply, err = add(totalSupply, amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ctx.GetStub().PutState(totalSupplyKey, []byte(strconv.Itoa(totalSupply)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Emit the Transfer event
|
|
transferEvent := event{"0x0", minter, amount}
|
|
transferEventJSON, err := json.Marshal(transferEvent)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
|
|
}
|
|
err = ctx.GetStub().SetEvent("Transfer", transferEventJSON)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set event: %v", err)
|
|
}
|
|
|
|
log.Printf("minter account %s balance updated from %d to %d", minter, currentBalance, updatedBalance)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Burn redeems tokens the minter's account balance
|
|
// 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 is already initialized: %v", err)
|
|
}
|
|
if !initialized {
|
|
return errors.New("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 {
|
|
return fmt.Errorf("failed to get MSPID: %v", err)
|
|
}
|
|
if clientMSPID != "Org1MSP" {
|
|
return errors.New("client is not authorized to mint new tokens")
|
|
}
|
|
|
|
// Get ID of submitting client identity
|
|
minter, err := ctx.GetClientIdentity().GetID()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get client id: %v", err)
|
|
}
|
|
|
|
if amount <= 0 {
|
|
return errors.New("burn amount must be a positive integer")
|
|
}
|
|
|
|
currentBalanceBytes, err := ctx.GetStub().GetState(minter)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read minter account %s from world state: %v", minter, err)
|
|
}
|
|
|
|
var currentBalance int
|
|
|
|
// Check if minter current balance exists
|
|
if currentBalanceBytes == nil {
|
|
return errors.New("The balance does not exist")
|
|
}
|
|
|
|
currentBalance, _ = strconv.Atoi(string(currentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer.
|
|
|
|
updatedBalance, err := sub(currentBalance, amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ctx.GetStub().PutState(minter, []byte(strconv.Itoa(updatedBalance)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the totalSupply
|
|
totalSupplyBytes, err := ctx.GetStub().GetState(totalSupplyKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to retrieve total token supply: %v", err)
|
|
}
|
|
|
|
// If no tokens have been minted, throw error
|
|
if totalSupplyBytes == nil {
|
|
return errors.New("totalSupply does not exist")
|
|
}
|
|
|
|
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, err = sub(totalSupply, amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ctx.GetStub().PutState(totalSupplyKey, []byte(strconv.Itoa(totalSupply)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Emit the Transfer event
|
|
transferEvent := event{minter, "0x0", amount}
|
|
transferEventJSON, err := json.Marshal(transferEvent)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
|
|
}
|
|
err = ctx.GetStub().SetEvent("Transfer", transferEventJSON)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set event: %v", err)
|
|
}
|
|
|
|
log.Printf("minter account %s balance updated from %d to %d", minter, currentBalance, updatedBalance)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Transfer transfers tokens from client account to recipient account
|
|
// recipient account must be a valid clientID as returned by the ClientID() function
|
|
// 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 is 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 {
|
|
return fmt.Errorf("failed to get client id: %v", err)
|
|
}
|
|
|
|
err = transferHelper(ctx, clientID, recipient, amount)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to transfer: %v", err)
|
|
}
|
|
|
|
// Emit the Transfer event
|
|
transferEvent := event{clientID, recipient, amount}
|
|
transferEventJSON, err := json.Marshal(transferEvent)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
|
|
}
|
|
err = ctx.GetStub().SetEvent("Transfer", transferEventJSON)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set event: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 is 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)
|
|
}
|
|
if balanceBytes == nil {
|
|
return 0, fmt.Errorf("the account %s does not exist", account)
|
|
}
|
|
|
|
balance, _ := strconv.Atoi(string(balanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer.
|
|
|
|
return balance, nil
|
|
}
|
|
|
|
// 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 is 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 {
|
|
return 0, fmt.Errorf("failed to get client id: %v", err)
|
|
}
|
|
|
|
balanceBytes, err := ctx.GetStub().GetState(clientID)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to read from world state: %v", err)
|
|
}
|
|
if balanceBytes == nil {
|
|
return 0, fmt.Errorf("the account %s does not exist", clientID)
|
|
}
|
|
|
|
balance, _ := strconv.Atoi(string(balanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer.
|
|
|
|
return balance, nil
|
|
}
|
|
|
|
// ClientAccountID returns the id of the requesting client's account
|
|
// 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
|
|
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 is 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 {
|
|
return "", fmt.Errorf("failed to get client id: %v", err)
|
|
}
|
|
|
|
return clientAccountID, nil
|
|
}
|
|
|
|
// 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 is 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 {
|
|
return 0, fmt.Errorf("failed to retrieve total token supply: %v", err)
|
|
}
|
|
|
|
var totalSupply int
|
|
|
|
// If no tokens have been minted, return 0
|
|
if totalSupplyBytes == nil {
|
|
totalSupply = 0
|
|
} else {
|
|
totalSupply, _ = strconv.Atoi(string(totalSupplyBytes)) // Error handling not needed since Itoa() was used when setting the totalSupply, guaranteeing it was an integer.
|
|
}
|
|
|
|
log.Printf("TotalSupply: %d tokens", totalSupply)
|
|
|
|
return totalSupply, nil
|
|
}
|
|
|
|
// Approve allows the spender to withdraw from the calling client's token account
|
|
// The spender can withdraw multiple times if necessary, up to the value amount
|
|
// 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 is 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 {
|
|
return fmt.Errorf("failed to get client id: %v", err)
|
|
}
|
|
|
|
// Create allowanceKey
|
|
allowanceKey, err := ctx.GetStub().CreateCompositeKey(allowancePrefix, []string{owner, spender})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create the composite key for prefix %s: %v", allowancePrefix, err)
|
|
}
|
|
|
|
// Update the state of the smart contract by adding the allowanceKey and value
|
|
err = ctx.GetStub().PutState(allowanceKey, []byte(strconv.Itoa(value)))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update state of smart contract for key %s: %v", allowanceKey, err)
|
|
}
|
|
|
|
// Emit the Approval event
|
|
approvalEvent := event{owner, spender, value}
|
|
approvalEventJSON, err := json.Marshal(approvalEvent)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
|
|
}
|
|
err = ctx.GetStub().SetEvent("Approval", approvalEventJSON)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set event: %v", err)
|
|
}
|
|
|
|
log.Printf("client %s approved a withdrawal allowance of %d for spender %s", owner, value, spender)
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 is 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 {
|
|
return 0, fmt.Errorf("failed to create the composite key for prefix %s: %v", allowancePrefix, err)
|
|
}
|
|
|
|
// Read the allowance amount from the world state
|
|
allowanceBytes, err := ctx.GetStub().GetState(allowanceKey)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to read allowance for %s from world state: %v", allowanceKey, err)
|
|
}
|
|
|
|
var allowance int
|
|
|
|
// If no current allowance, set allowance to 0
|
|
if allowanceBytes == nil {
|
|
allowance = 0
|
|
} else {
|
|
allowance, err = strconv.Atoi(string(allowanceBytes)) // Error handling not needed since Itoa() was used when setting the totalSupply, guaranteeing it was an integer.
|
|
}
|
|
|
|
log.Printf("The allowance left for spender %s to withdraw from owner %s: %d", spender, owner, allowance)
|
|
|
|
return allowance, nil
|
|
}
|
|
|
|
// TransferFrom transfers the value amount from the "from" address to the "to" address
|
|
// 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 is 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 {
|
|
return fmt.Errorf("failed to get client id: %v", err)
|
|
}
|
|
|
|
// Create allowanceKey
|
|
allowanceKey, err := ctx.GetStub().CreateCompositeKey(allowancePrefix, []string{from, spender})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create the composite key for prefix %s: %v", allowancePrefix, err)
|
|
}
|
|
|
|
// Retrieve the allowance of the spender
|
|
currentAllowanceBytes, err := ctx.GetStub().GetState(allowanceKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to retrieve the allowance for %s from world state: %v", allowanceKey, err)
|
|
}
|
|
|
|
var currentAllowance int
|
|
currentAllowance, _ = strconv.Atoi(string(currentAllowanceBytes)) // Error handling not needed since Itoa() was used when setting the totalSupply, guaranteeing it was an integer.
|
|
|
|
// Check if transferred value is less than allowance
|
|
if currentAllowance < value {
|
|
return fmt.Errorf("spender does not have enough allowance for transfer")
|
|
}
|
|
|
|
// Initiate the transfer
|
|
err = transferHelper(ctx, from, to, value)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to transfer: %v", err)
|
|
}
|
|
|
|
// Decrease the allowance
|
|
updatedAllowance, err := sub(currentAllowance, value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ctx.GetStub().PutState(allowanceKey, []byte(strconv.Itoa(updatedAllowance)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Emit the Transfer event
|
|
transferEvent := event{from, to, value}
|
|
transferEventJSON, err := json.Marshal(transferEvent)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
|
|
}
|
|
err = ctx.GetStub().SetEvent("Transfer", transferEventJSON)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set event: %v", err)
|
|
}
|
|
|
|
log.Printf("spender %s allowance updated from %d to %d", spender, currentAllowance, updatedAllowance)
|
|
|
|
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 is 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 is 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 decimals used for the token operations
|
|
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)
|
|
}
|
|
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)
|
|
}
|
|
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
|
|
// Dependant functions include Transfer and TransferFrom
|
|
func transferHelper(ctx contractapi.TransactionContextInterface, from string, to string, value int) error {
|
|
|
|
if from == to {
|
|
return fmt.Errorf("cannot transfer to and from same client account")
|
|
}
|
|
|
|
if value < 0 { // transfer of 0 is allowed in ERC-20, so just validate against negative amounts
|
|
return fmt.Errorf("transfer amount cannot be negative")
|
|
}
|
|
|
|
fromCurrentBalanceBytes, err := ctx.GetStub().GetState(from)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read client account %s from world state: %v", from, err)
|
|
}
|
|
|
|
if fromCurrentBalanceBytes == nil {
|
|
return fmt.Errorf("client account %s has no balance", from)
|
|
}
|
|
|
|
fromCurrentBalance, _ := strconv.Atoi(string(fromCurrentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer.
|
|
|
|
if fromCurrentBalance < value {
|
|
return fmt.Errorf("client account %s has insufficient funds", from)
|
|
}
|
|
|
|
toCurrentBalanceBytes, err := ctx.GetStub().GetState(to)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read recipient account %s from world state: %v", to, err)
|
|
}
|
|
|
|
var toCurrentBalance int
|
|
// If recipient current balance doesn't yet exist, we'll create it with a current balance of 0
|
|
if toCurrentBalanceBytes == nil {
|
|
toCurrentBalance = 0
|
|
} else {
|
|
toCurrentBalance, _ = strconv.Atoi(string(toCurrentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer.
|
|
}
|
|
|
|
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 {
|
|
return err
|
|
}
|
|
|
|
err = ctx.GetStub().PutState(to, []byte(strconv.Itoa(toUpdatedBalance)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("client %s balance updated from %d to %d", from, fromCurrentBalance, fromUpdatedBalance)
|
|
log.Printf("recipient %s balance updated from %d to %d", to, toCurrentBalance, toUpdatedBalance)
|
|
|
|
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 || sum < b) == (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)
|
|
}
|
|
|
|
if tokenName == nil {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// sub two number checking for overflow
|
|
func sub(b int, q int) (int, error) {
|
|
|
|
// sub two number checking
|
|
if q <= 0 {
|
|
return 0, fmt.Errorf("Error: the subtraction number is %d, it should be greater than 0", q)
|
|
}
|
|
if b < q {
|
|
return 0, fmt.Errorf("Error: the number %d is not enough to be subtracted by %d", b, q)
|
|
}
|
|
var diff int
|
|
diff = b - q
|
|
|
|
return diff, nil
|
|
}
|