diff --git a/token-erc-20/chaincode-java/pom.xml b/token-erc-20/chaincode-java/pom.xml
new file mode 100644
index 00000000..ff00fc1d
--- /dev/null
+++ b/token-erc-20/chaincode-java/pom.xml
@@ -0,0 +1,165 @@
+
+ 4.0.0
+ TokenERC20Contract
+ TokenERC20Contract
+ 1.0-SNAPSHOT
+
+
+
+ 1.8
+ UTF-8
+ UTF-8
+
+
+ 2.2.0
+
+
+ 1.0.13
+ 1.7.5
+
+
+ 5.4.2
+ 1.3.0-RC1
+
+
+
+
+
+ jitpack.io
+ https://www.jitpack.io
+
+
+ artifactory
+ https://hyperledger.jfrog.io/hyperledger/fabric-maven
+
+
+
+
+
+
+
+ org.hyperledger.fabric-chaincode-java
+ fabric-chaincode-shim
+ ${fabric-chaincode-java.version}
+ compile
+
+
+
+ org.hyperledger.fabric-chaincode-java
+ fabric-chaincode-protos
+ ${fabric-chaincode-java.version}
+ compile
+
+
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+ compile
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+ runtime
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.jupiter.version}
+ compile
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.jupiter.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.11.1
+
+
+
+
+ org.mockito
+ mockito-core
+ 2.23.0
+
+
+
+
+
+ org.json
+ json
+ 20180813
+
+
+
+
+ src
+
+
+
+ maven-surefire-plugin
+ 2.22.0
+
+
+ maven-compiler-plugin
+ 3.1
+
+ ${java.version}
+ ${java.version}
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.1.0
+
+
+ package
+
+ shade
+
+
+ chaincode
+
+
+ org.hyperledger.fabric.contract.ContractRouter
+
+
+
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/token-erc-20/chaincode-java/src/main/java/org/example/TokenERC20Contract.java b/token-erc-20/chaincode-java/src/main/java/org/example/TokenERC20Contract.java
new file mode 100644
index 00000000..20d23185
--- /dev/null
+++ b/token-erc-20/chaincode-java/src/main/java/org/example/TokenERC20Contract.java
@@ -0,0 +1,519 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.example;
+
+
+import org.hyperledger.fabric.contract.Context;
+import org.hyperledger.fabric.contract.ContractInterface;
+import org.hyperledger.fabric.contract.annotation.*;
+import org.hyperledger.fabric.shim.ChaincodeException;
+import org.hyperledger.fabric.shim.ChaincodeStub;
+import org.hyperledger.fabric.shim.ledger.CompositeKey;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import org.json.JSONObject;
+
+@Contract(name = "TokenERC20Contract", info = @Info(title = "TokenERC20Contract", description = "A java chaincode for erc20 token", version = "0.0.1-SNAPSHOT"))
+
+@Default
+public final class TokenERC20Contract implements ContractInterface {
+
+
+ final private String balancePrefix = "balance";
+ final private String allowancePrefix = "allowance";
+ final private String nameKey = "name";
+ final private String symbolKey = "symbol";
+ final private String decimalsKey = "decimals";
+ final private String totalSupplyKey = "totalSupply";
+
+ /**
+ * Return the name of the token - e.g. "MyToken". The original function name is
+ * `name` in ERC20 specification. However, 'name' conflicts with a parameter
+ * `name` in `Contract` class. As a work around, we use `TokenName` as an
+ * alternative function name.
+ *
+ * @param {Context} ctx the transaction context
+ * @returns {String} Returns the name of the token
+ */
+ @Transaction()
+ public String tokenName(final Context ctx) {
+
+ ChaincodeStub stub = ctx.getStub();
+ String tokenName = stub.getStringState(nameKey);
+ if (tokenName.isEmpty()) {
+
+ throw new ChaincodeException("Sorry ! Token name not found");
+ }
+
+ return tokenName;
+
+ }
+
+ /**
+ * Return the symbol of the token. E.g. “HIX”.
+ *
+ * @param {Context} ctx the transaction context
+ * @returns {String} Returns the symbol of the token
+ */
+ @Transaction()
+ public String tokenSymbol(final Context ctx) {
+ ChaincodeStub stub = ctx.getStub();
+ String tokenSymbol = stub.getStringState(symbolKey);
+ if (tokenSymbol.isEmpty()) {
+
+ throw new ChaincodeException("Sorry ! Token symbol not found");
+ }
+
+ return tokenSymbol;
+
+ }
+
+ /**
+ * Return the number of decimals the token uses e.g. 8, means to divide the
+ * token amount by 100000000 to get its user representation.
+ *
+ * @param {Context} ctx the transaction context
+ * @returns {Number} Returns the number of decimals
+ */
+ @Transaction()
+ public Integer decimals(final Context ctx) {
+
+ ChaincodeStub stub = ctx.getStub();
+ String decimals = stub.getStringState(decimalsKey);
+ if (decimals.isEmpty()) {
+
+ throw new ChaincodeException("Sorry ! Decimal not found");
+ }
+ return Integer.parseInt(decimals);
+ }
+
+ /**
+ * Return the total token supply.
+ *
+ * @param {Context} ctx the transaction context
+ * @returns {Number} Returns the total token supply
+ */
+ @Transaction()
+ public Long totalSupply(final Context ctx) {
+ ChaincodeStub stub = ctx.getStub();
+ String totalSupply = stub.getStringState(totalSupplyKey);
+ if (totalSupply.isEmpty()) {
+
+ throw new ChaincodeException("Sorry ! Total Supply not found");
+ }
+ return Long.parseLong(totalSupply);
+ }
+
+ /**
+ * BalanceOf returns the balance of the given account.
+ *
+ * @param {Context} ctx the transaction context
+ * @param {String} owner The owner from which the balance will be retrieved
+ * @returns {Number} Returns the account balance
+ */
+ @Transaction()
+ public long balanceOf(final Context ctx, final String owner) {
+
+ ChaincodeStub stub = ctx.getStub();
+ CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, owner);
+
+ String balance = stub.getStringState(balanceKey.toString().trim());
+ if (balance == null || balance.isEmpty() || balance.length() == 0) {
+ String errorMessage = String.format("Balance of the owner %s not exists", owner);
+ System.out.println(errorMessage);
+ throw new ChaincodeException(errorMessage);
+ }
+
+ return Long.parseLong(balance.toString());
+
+ }
+
+ /**
+ * Transfer transfers tokens from client account to recipient account. recipient
+ * account must be a valid clientID as returned by the ClientAccountID()
+ * function.
+ *
+ * @param {Context} ctx the transaction context
+ * @param {String} to The recipient
+ * @param {Integer} value The amount of token to be transferred
+ * @returns {Boolean} Return whether the transfer was successful or not
+ */
+ @Transaction()
+ public boolean transfer(final Context ctx, final String to, String _value) {
+
+ String from = ctx.getClientIdentity().getId();
+ long value = Long.parseLong(_value.trim());
+ boolean transferResp = this.doTransfer(ctx, from, to, value);
+
+ if (!transferResp) {
+ String errorMessage = String.format("Cannot transfer to and from same client account");
+ System.out.println(errorMessage);
+ throw new ChaincodeException(errorMessage);
+ }
+
+ ChaincodeStub stub = ctx.getStub();
+ JSONObject obj = new JSONObject();
+ obj.put("from", from);
+ obj.put("to", to);
+ obj.put("value", value);
+ stub.setEvent("Transfer", this.serialize(obj));
+ return true;
+
+ }
+
+ /**
+ * Transfer `value` amount of tokens from `from` to `to`.
+ *
+ * @param {Context} ctx the transaction context
+ * @param {String} from The sender
+ * @param {String} to The recipient
+ * @param {Integer} value The amount of token to be transferred
+ * @returns {Boolean} Return whether the transfer was successful or not
+ */
+ @Transaction()
+ public boolean transferFrom(Context ctx, final String from, final String to, String _value) {
+
+ String spender = ctx.getClientIdentity().getId();
+ ChaincodeStub stub = ctx.getStub();
+ // Retrieve the allowance of the spender
+ CompositeKey allowanceKey = stub.createCompositeKey(allowancePrefix, from, spender);
+ String currentAllowanceStr = stub.getStringState(allowanceKey.toString().trim());
+ if (currentAllowanceStr.isBlank() || currentAllowanceStr.length() == 0) {
+ String errorMessage = String.format("Spender %s has no allowance from %s", spender, from);
+ System.out.println(errorMessage);
+ throw new ChaincodeException(errorMessage);
+ }
+ long currentAllowance = Long.parseLong(currentAllowanceStr.toString());
+
+ // Convert value from string to int
+ Long valueInt = Long.parseLong(_value);
+
+ // Check if the transferred value is less than the allowance
+ if (currentAllowance < valueInt) {
+
+ String errorMessage = String.format("The spender does not have enough allowance to spend.");
+ System.out.println(errorMessage);
+ throw new ChaincodeException(errorMessage);
+
+ }
+
+ boolean transferResp = this.doTransfer(ctx, from, to, valueInt);
+
+ if (!transferResp) {
+ throw new ChaincodeException("Failed to transfer");
+ }
+
+ // Decrease the allowance
+ long updatedAllowance = currentAllowance - valueInt;
+ stub.putStringState(allowanceKey.toString().trim(), String.valueOf(updatedAllowance));
+ System.out.printf("spender %s allowance updated from %d to %d", spender, currentAllowance, updatedAllowance);
+
+ JSONObject obj = new JSONObject();
+ obj.put("from", from);
+ obj.put("to", to);
+ obj.put("value", valueInt);
+ stub.setEvent("Transfer", this.serialize(obj));
+ System.out.println("transferFrom ended successfully");
+
+ return true;
+ }
+
+ @Transaction()
+ private boolean doTransfer(final Context ctx, final String _from, final String _to, long _value) {
+
+ if (_from.equalsIgnoreCase(_to)) {
+ throw new ChaincodeException("cannot transfer to and from same client account");
+ }
+
+ if (_value < 0) { // transfer of 0 is allowed in ERC20, so just validate against negative amounts
+ throw new ChaincodeException("transfer amount cannot be negative");
+ }
+
+ ChaincodeStub stub = ctx.getStub();
+ // Retrieve the current balance of the sender
+ CompositeKey fromBalanceKey = stub.createCompositeKey(balancePrefix, _from.trim());
+ String fromCurrentBalance = stub.getStringState(fromBalanceKey.toString().trim());
+ if (fromCurrentBalance.isBlank() || fromCurrentBalance.length() == 0) {
+ String errorMessage = String.format("client account %s has no balance", _from);
+ throw new ChaincodeException(errorMessage);
+
+ }
+
+ long _fromCurrentBalance = Long.parseLong(fromCurrentBalance.toString().trim());
+
+ // Check if the sender has enough tokens to spend.
+ if (_fromCurrentBalance < _value) {
+ String errorMessage = String.format("client account %s has insufficient funds", _from);
+ throw new ChaincodeException(errorMessage);
+ }
+
+ // Retrieve the current balance of the recepient
+ CompositeKey toBalanceKey = stub.createCompositeKey(balancePrefix, _to);
+ String toCurrentBalance = stub.getStringState(toBalanceKey.toString().trim());
+
+ long _toCurrentBalance = 0;
+ // If recipient current balance doesn't yet exist, we'll create it with a
+ // current balance of 0
+ if (toCurrentBalance.isBlank() || toCurrentBalance.length() == 0) {
+ _toCurrentBalance = 0;
+ } else {
+ _toCurrentBalance = Long.parseLong(toCurrentBalance.trim());
+ }
+
+ // Update the balance
+ long fromUpdatedBalance = _fromCurrentBalance - _value;
+ long toUpdatedBalance = _toCurrentBalance + _value;
+
+ stub.putStringState(fromBalanceKey.toString().trim(), String.valueOf(fromUpdatedBalance));
+
+ stub.putStringState(toBalanceKey.toString().trim(), String.valueOf(toUpdatedBalance));
+
+ System.out.printf("client %s balance updated from %d to %d", _from, _fromCurrentBalance, fromUpdatedBalance);
+ System.out.printf("recipient %s balance updated from %d to %d", _to, _toCurrentBalance, toUpdatedBalance);
+
+ return true;
+ }
+
+ /**
+ * Allows `spender` to spend `value` amount of tokens from the owner.
+ *
+ * @param {Context} ctx the transaction context
+ * @param {String} spender The spender
+ * @param {Integer} value The amount of tokens to be approved for transfer
+ * @returns {Boolean} Return whether the approval was successful or not
+ */
+ @Transaction()
+ public boolean approve(final Context ctx, final String spender, final String value) {
+
+ String owner = ctx.getClientIdentity().getId();
+ ChaincodeStub stub = ctx.getStub();
+ CompositeKey allowanceKey = stub.createCompositeKey(allowancePrefix, owner, spender);
+ long valueInt = Long.parseLong(value);
+ stub.putStringState(allowanceKey.toString().trim(), String.valueOf(valueInt));
+ JSONObject obj = new JSONObject();
+ obj.put("owner", owner);
+ obj.put("spender", spender);
+ obj.put("value", valueInt);
+ stub.setEvent("Approval", this.serialize(obj));
+ System.out.println("Approve ended successfully");
+
+ return true;
+
+ }
+
+ /**
+ * Returns the amount of tokens which `spender` is allowed to withdraw from
+ * `owner`.
+ *
+ * @param {Context} ctx the transaction context
+ * @param {String} owner The owner of tokens
+ * @param {String} spender The spender who are able to transfer the tokens
+ * @returns {Number} Return the amount of remaining tokens allowed to spent
+ */
+
+ @Transaction()
+ public long allowance(final Context ctx, final String owner, final String spender) {
+
+ ChaincodeStub stub = ctx.getStub();
+
+ CompositeKey allowanceKey = stub.createCompositeKey(allowancePrefix, owner, spender);
+ String allowanceBytes = stub.getStringState(allowanceKey.toString().trim());
+
+ if (allowanceBytes.isBlank() || allowanceBytes.length() == 0) {
+
+ String errorMessage = String.format("spender account %s has no allowance from", spender, owner);
+ throw new ChaincodeException(errorMessage);
+ }
+
+ long allowance = Long.parseLong(allowanceBytes.toString().trim());
+ return allowance;
+ }
+
+ /**
+ * Set optional infomation for a token.
+ *
+ * @param {Context} ctx the transaction context
+ * @param {String} name The name of the token
+ * @param {String} symbol The symbol of the token
+ * @param {String} decimals The decimals of the token
+ * @param {String} totalSupply The totalSupply of the token
+ */
+ @Transaction()
+ public boolean setOptions(final Context ctx, final String name, final String symbol, final String decimals) {
+ ChaincodeStub stub = ctx.getStub();
+ stub.putStringState(nameKey, name);
+ stub.putStringState(symbolKey, symbol);
+ stub.putStringState(decimalsKey, decimals);
+
+ System.out.printf("name:%s, symbol: %s, decimals: %s", name, symbol, decimals);
+ return true;
+ }
+
+ /**
+ * Mint creates new tokens and adds them to minter's account balance
+ *
+ * @param {Context} ctx the transaction context
+ * @param {Integer} amount amount of tokens to be minted
+ * @returns {Object} The balance
+ */
+ @Transaction()
+ public boolean mint(final Context ctx, final String amount) {
+
+ // Check minter authorization - this sample assumes Org1 is the central banker
+ // with privilege to mint new tokens
+
+ String clientMSPID = ctx.getClientIdentity().getMSPID();
+ ChaincodeStub stub = ctx.getStub();
+ if (!clientMSPID.equalsIgnoreCase("Org1MSP")) {
+ throw new ChaincodeException("client is not authorized to mint new tokens");
+ }
+
+ // Get ID of submitting client identity
+ String minter = ctx.getClientIdentity().getId();
+ long amountInt = Long.parseLong(amount.trim());
+ if (amountInt <= 0) {
+ throw new ChaincodeException("mint amount must be a positive integer");
+ }
+
+ CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, minter);
+
+ String currentBalanceBytes = stub.getStringState(balanceKey.toString().trim());
+ // If minter current balance doesn't yet exist, we'll create it with a current
+ // balance of 0
+ long currentBalance = 0;
+
+ if (currentBalanceBytes.isBlank() || currentBalanceBytes.length() == 0) {
+
+ currentBalance = 0;
+
+ } else {
+
+ currentBalance = Long.parseLong(currentBalanceBytes.toString());
+
+ }
+ long updatedBalance = currentBalance + amountInt;
+
+ stub.putStringState(balanceKey.toString().trim(), String.valueOf(updatedBalance));
+
+ // Increase totalSupply
+ String totalSupplyBytes = stub.getStringState(totalSupplyKey.trim());
+ long totalSupply = 0;
+ if (totalSupplyBytes.isBlank() || totalSupplyBytes.length() == 0) {
+ System.out.println("Initialize the tokenSupply");
+ totalSupply = 0;
+
+ } else {
+
+ totalSupply = Long.parseLong(totalSupplyBytes.toString());
+ }
+
+ totalSupply = totalSupply + amountInt;
+ stub.putStringState(totalSupplyKey.trim(), String.valueOf(totalSupply));
+
+ JSONObject obj = new JSONObject();
+ obj.put("from", "0x0");
+ obj.put("to", minter);
+ obj.put("value", amountInt);
+ stub.setEvent("Transfer", this.serialize(obj));
+
+ // System.out.printf("minter account %s balance updated from %d to %d",minter,
+ // currentBalance ,updatedBalance);
+ return true;
+ }
+
+ /**
+ * Burn redeem tokens from minter's account balance
+ *
+ * @param {Context} ctx the transaction context
+ * @param {Integer} amount amount of tokens to be burned
+ * @returns {Object} The balance
+ */
+ @Transaction()
+ public boolean burn(final Context ctx, final String amount) {
+
+ // Check minter authorization - this sample assumes Org1 is the central banker
+ // with privilege to burn tokens
+ String clientMSPID = ctx.getClientIdentity().getMSPID();
+ ChaincodeStub stub = ctx.getStub();
+ if (!clientMSPID.equalsIgnoreCase("Org1MSP")) {
+ throw new ChaincodeException("client is not authorized to mint new tokens");
+ }
+
+ String minter = ctx.getClientIdentity().getId();
+
+ long amountInt = Long.parseLong(amount);
+
+ CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, minter);
+
+ String currentBalanceBytes = stub.getStringState(balanceKey.toString().trim());
+ if (currentBalanceBytes.isBlank() || currentBalanceBytes.length() == 0) {
+ throw new ChaincodeException("The balance does not exist");
+ }
+ long currentBalance = Long.valueOf(currentBalanceBytes.toString());
+ long updatedBalance = currentBalance - amountInt;
+
+ stub.putStringState(balanceKey.toString().trim(), String.valueOf(updatedBalance));
+
+ // Decrease totalSupply
+ String totalSupplyBytes = stub.getStringState(totalSupplyKey.toString().trim());
+ if (totalSupplyBytes.isBlank() || totalSupplyBytes.length() == 0) {
+ throw new ChaincodeException("totalSupply does not exist.");
+ }
+ long totalSupply = Long.parseLong(totalSupplyBytes.toString()) - amountInt;
+ stub.putStringState(totalSupplyKey.toString().trim(), String.valueOf(totalSupply));
+
+ // Emit the Transfer event
+
+ JSONObject obj = new JSONObject();
+ obj.put("from", minter);
+ obj.put("to", "0x0");
+ obj.put("value", amountInt);
+ stub.setEvent("Transfer", this.serialize(obj));
+ System.out.printf("minter account %s balance updated from %d to %d", minter, currentBalance, updatedBalance);
+ return true;
+ }
+
+ /**
+ * ClientAccountBalance returns the balance of the requesting client's account.
+ *
+ * @param {Context} ctx the transaction context
+ * @returns {Number} Returns the account balance
+ */
+
+ @Transaction()
+ public long getClientAccountBalance(final Context ctx) {
+ // Get ID of submitting client identity
+ ChaincodeStub stub = ctx.getStub();
+ String clientAccountID = ctx.getClientIdentity().getId();
+ CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, clientAccountID);
+ String balanceBytes = stub.getStringState(balanceKey.toString().trim());
+ if (balanceBytes.isBlank() || balanceBytes.length() == 0) {
+
+ String errorMessage = String.format("the account %s does not exist", clientAccountID);
+ throw new ChaincodeException(errorMessage);
+ }
+ long balance = Long.parseLong(balanceBytes.trim());
+
+ return balance;
+ }
+
+ // 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
+ @Transaction()
+ public String getClientAccountID(final Context ctx) {
+ // Get ID of submitting client identity
+ String clientAccountID = ctx.getClientIdentity().getId();
+ return clientAccountID;
+ }
+
+ private byte[] serialize(Object object) {
+ String jsonStr = new JSONObject(object).toString();
+ return jsonStr.getBytes(UTF_8);
+ }
+
+}
diff --git a/token-erc-20/chaincode-java/src/test/org/example/ChaincodeStubNaiveImpl.java b/token-erc-20/chaincode-java/src/test/org/example/ChaincodeStubNaiveImpl.java
new file mode 100644
index 00000000..60f73d83
--- /dev/null
+++ b/token-erc-20/chaincode-java/src/test/org/example/ChaincodeStubNaiveImpl.java
@@ -0,0 +1,337 @@
+package org.example;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+//import org.hyperledger.fabric.samples.fabcar.TestUtil;
+
+import org.hyperledger.fabric.protos.msp.Identities.SerializedIdentity;
+import org.hyperledger.fabric.protos.peer.ChaincodeEventPackage;
+import org.hyperledger.fabric.protos.peer.ProposalPackage;
+import org.hyperledger.fabric.shim.Chaincode;
+import org.hyperledger.fabric.shim.ChaincodeStub;
+import org.hyperledger.fabric.shim.ledger.CompositeKey;
+import org.hyperledger.fabric.shim.ledger.KeyModification;
+import org.hyperledger.fabric.shim.ledger.KeyValue;
+import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;
+import org.hyperledger.fabric.shim.ledger.QueryResultsIteratorWithMetadata;
+
+import com.google.protobuf.ByteString;
+import static java.nio.charset.StandardCharsets.UTF_8;
+public final class ChaincodeStubNaiveImpl implements ChaincodeStub {
+ private List args;
+ private List argsAsByte;
+ private final Map state;
+ private final Chaincode.Response resp;
+
+ public String certificate = "MIICXTCCAgSgAwIBAgIUeLy6uQnq8wwyElU/jCKRYz3tJiQwCgYIKoZIzj0EAwIw"
+ + "eTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh" + "biBGcmFuY2lzY28xGTAXBgNVBAoTEEludGVybmV0IFdpZGdldHMxDDAKBgNVBAsT"
+ + "A1dXVzEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTcwOTA4MDAxNTAwWhcNMTgw" + "OTA4MDAxNTAwWjBdMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xp"
+ + "bmExFDASBgNVBAoTC0h5cGVybGVkZ2VyMQ8wDQYDVQQLEwZGYWJyaWMxDjAMBgNV" + "BAMTBWFkbWluMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFq/90YMuH4tWugHa"
+ + "oyZtt4Mbwgv6CkBSDfYulVO1CVInw1i/k16DocQ/KSDTeTfgJxrX1Ree1tjpaodG" + "1wWyM6OBhTCBgjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4E"
+ + "FgQUhKs/VJ9IWJd+wer6sgsgtZmxZNwwHwYDVR0jBBgwFoAUIUd4i/sLTwYWvpVr" + "TApzcT8zv/kwIgYDVR0RBBswGYIXQW5pbHMtTWFjQm9vay1Qcm8ubG9jYWwwCgYI"
+ + "KoZIzj0EAwIDRwAwRAIgCoXaCdU8ZiRKkai0QiXJM/GL5fysLnmG2oZ6XOIdwtsC" + "IEmCsI8Mhrvx1doTbEOm7kmIrhQwUVDBNXCWX1t3kJVN";
+
+ public static final String CERT_WITH_ATTRS = "MIIB6TCCAY+gAwIBAgIUHkmY6fRP0ANTvzaBwKCkMZZPUnUwCgYIKoZIzj0EAwIw"
+ + "GzEZMBcGA1UEAxMQZmFicmljLWNhLXNlcnZlcjAeFw0xNzA5MDgwMzQyMDBaFw0x" + "ODA5MDgwMzQyMDBaMB4xHDAaBgNVBAMTE015VGVzdFVzZXJXaXRoQXR0cnMwWTAT"
+ + "BgcqhkjOPQIBBggqhkjOPQMBBwNCAATmB1r3CdWvOOP3opB3DjJnW3CnN8q1ydiR" + "dzmuA6A2rXKzPIltHvYbbSqISZJubsy8gVL6GYgYXNdu69RzzFF5o4GtMIGqMA4G"
+ + "A1UdDwEB/wQEAwICBDAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTYKLTAvJJK08OM" + "VGwIhjMQpo2DrjAfBgNVHSMEGDAWgBTEs/52DeLePPx1+65VhgTwu3/2ATAiBgNV"
+ + "HREEGzAZghdBbmlscy1NYWNCb29rLVByby5sb2NhbDAmBggqAwQFBgcIAQQaeyJh" + "dHRycyI6eyJhdHRyMSI6InZhbDEifX0wCgYIKoZIzj0EAwIDSAAwRQIhAPuEqWUp"
+ + "svTTvBqLR5JeQSctJuz3zaqGRqSs2iW+QB3FAiAIP0mGWKcgSGRMMBvaqaLytBYo" + "9v3hRt1r8j8vN0pMcg==";
+
+ public static final String CERT_WITH_DNS = "MIICGjCCAcCgAwIBAgIRAIPRwJHVLhHK47XK0BbFZJswCgYIKoZIzj0EAwIwczEL"
+ + "MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG" + "cmFuY2lzY28xGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh"
+ + "Lm9yZzIuZXhhbXBsZS5jb20wHhcNMTcwNjIzMTIzMzE5WhcNMjcwNjIxMTIzMzE5" + "WjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN"
+ + "U2FuIEZyYW5jaXNjbzEfMB0GA1UEAwwWVXNlcjFAb3JnMi5leGFtcGxlLmNvbTBZ" + "MBMGByqGSM49AgEGCCqGSM49AwEHA0IABBd9SsEiFH1/JIb3qMEPLR2dygokFVKW"
+ + "eINcB0Ni4TBRkfIWWUJeCANTUY11Pm/+5gs+fBTqBz8M2UzpJDVX7+2jTTBLMA4G" + "A1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAIKfUfvpGproH"
+ + "cwyFD+0sE3XfJzYNcif0jNwvgOUFZ4AFMAoGCCqGSM49BAMCA0gAMEUCIQC8NIMw" + "e4ym/QRwCJb5umbONNLSVQuEpnPsJrM/ssBPvgIgQpe2oYa3yO3USro9nBHjpM3L"
+ + "KsFQrpVnF8O6hoHOYZQ=";
+
+ public static final String CERT_MULTIPLE_ATTRIBUTES = "MIIChzCCAi6gAwIBAgIURilAHeqwLu/fNUv8eZoGPRh3H4IwCgYIKoZIzj0EAwIw"
+ + "czELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh" + "biBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT"
+ + "E2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTkwNzMxMTYxNzAwWhcNMjAwNzMwMTYy" + "MjAwWjAgMQ8wDQYDVQQLEwZjbGllbnQxDTALBgNVBAMTBHRlc3QwWTATBgcqhkjO"
+ + "PQIBBggqhkjOPQMBBwNCAAR2taQK8w7D3hr3gBxCz+8eV4KSv7pFQfNjDHMMe9J9" + "LJwcLpVTT5hYiLLRaqQonLBxBE3Ey0FneySvFuBScas3o4HyMIHvMA4GA1UdDwEB"
+ + "/wQEAwIHgDAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQi3mhXS/WzcjBniwAmPdYP" + "kHqVVzArBgNVHSMEJDAigCC7VXjmSEugjAB/A0S6vfMxLsUIgag9WVNwtwwebnRC"
+ + "7TCBggYIKgMEBQYHCAEEdnsiYXR0cnMiOnsiYXR0cjEiOiJ2YWwxIiwiZm9vIjoi" + "YmFyIiwiaGVsbG8iOiJ3b3JsZCIsImhmLkFmZmlsaWF0aW9uIjoiIiwiaGYuRW5y"
+ + "b2xsbWVudElEIjoidGVzdCIsImhmLlR5cGUiOiJjbGllbnQifX0wCgYIKoZIzj0E" + "AwIDRwAwRAIgQxEFvnZTEsf3CSZmp9IYsxcnEOtVYleOd86LAKtk1wICIH7XOPwW"
+ + "/RE4Z8WLZzFei/78Oezbx6obOvBxPMsVWRe5";
+
+ public ChaincodeStubNaiveImpl() {
+
+ args = new ArrayList<>();
+ args.add("func1");
+ args.add("param1");
+ args.add("param2");
+ state = new HashMap<>();
+ state.put("a", ByteString.copyFrom("asdf", StandardCharsets.UTF_8));
+ argsAsByte = null;
+ resp = new Chaincode.Response(404, "Wrong cc name", new byte[] {});
+
+ }
+
+ ChaincodeStubNaiveImpl(final List args) {
+ this.args = args;
+ state = new HashMap<>();
+ state.put("a", ByteString.copyFrom("asdf", StandardCharsets.UTF_8));
+
+ argsAsByte = null;
+
+ resp = new Chaincode.Response(404, "Wrong cc name", new byte[] {});
+ }
+
+ @Override
+ public List getArgs() {
+ if (argsAsByte == null) {
+ argsAsByte = args.stream().map(i -> i.getBytes()).collect(Collectors.toList());
+ }
+ return argsAsByte;
+ }
+
+ @Override
+ public List getStringArgs() {
+ return args;
+ }
+
+ @Override
+ public String getFunction() {
+ return args.get(0);
+ }
+
+ @Override
+ public List getParameters() {
+ return args.subList(1, args.size());
+ }
+
+ @Override
+ public String getTxId() {
+ return "tx0";
+ }
+
+ @Override
+ public String getChannelId() {
+ return "ch0";
+ }
+
+ @Override
+ public Chaincode.Response invokeChaincode(final String chaincodeName, final List args, final String channel) {
+ return resp;
+
+
+ }
+
+ public void putStringState(final String key, final String value) {
+ putState(key, value.getBytes(UTF_8));
+ }
+
+
+ public String getStringState(final String key) {
+ //return new String(getState(key), UTF_8);
+ if(state.get(key) == null)
+ return "";
+ else
+ return new String(getState(key), UTF_8);
+ }
+
+ @Override
+ public byte[] getState(final String key) {
+ return state.get(key).toByteArray();
+ }
+
+ @Override
+ public byte[] getStateValidationParameter(final String key) {
+ return new byte[0];
+ }
+
+ @Override
+ public void putState(final String key, final byte[] value) {
+ state.put(key, ByteString.copyFrom(value));
+
+ }
+
+ @Override
+ public void setStateValidationParameter(final String key, final byte[] value) {
+
+ }
+
+ @Override
+ public void delState(final String key) {
+ state.remove(key);
+ }
+
+ @Override
+ public QueryResultsIterator getStateByRange(final String startKey, final String endKey) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIteratorWithMetadata getStateByRangeWithPagination(final String startKey, final String endKey, final int pageSize,
+ final String bookmark) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIterator getStateByPartialCompositeKey(final String compositeKey) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIterator getStateByPartialCompositeKey(final String objectType, final String... attributes) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIterator getStateByPartialCompositeKey(final CompositeKey compositeKey) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIteratorWithMetadata getStateByPartialCompositeKeyWithPagination(final CompositeKey compositeKey, final int pageSize,
+ final String bookmark) {
+ return null;
+ }
+
+ @Override
+ public CompositeKey createCompositeKey(final String objectType, final String... attributes) {
+ final CompositeKey key = new CompositeKey(objectType, attributes);
+ return key;
+ }
+
+ @Override
+ public CompositeKey splitCompositeKey(final String compositeKey) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIterator getQueryResult(final String query) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIteratorWithMetadata getQueryResultWithPagination(final String query, final int pageSize, final String bookmark) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIterator getHistoryForKey(final String key) {
+ return null;
+ }
+
+ @Override
+ public byte[] getPrivateData(final String collection, final String key) {
+ return new byte[0];
+ }
+
+ @Override
+ public byte[] getPrivateDataHash(final String collection, final String key) {
+ return new byte[0];
+ }
+
+ @Override
+ public byte[] getPrivateDataValidationParameter(final String collection, final String key) {
+ return new byte[0];
+ }
+
+ @Override
+ public void putPrivateData(final String collection, final String key, final byte[] value) {
+
+ }
+
+ @Override
+ public void setPrivateDataValidationParameter(final String collection, final String key, final byte[] value) {
+
+ }
+
+ @Override
+ public void delPrivateData(final String collection, final String key) {
+
+ }
+
+ @Override
+ public QueryResultsIterator getPrivateDataByRange(final String collection, final String startKey, final String endKey) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIterator getPrivateDataByPartialCompositeKey(final String collection, final String compositeKey) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIterator getPrivateDataByPartialCompositeKey(final String collection, final CompositeKey compositeKey) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIterator getPrivateDataByPartialCompositeKey(final String collection, final String objectType, final String... attributes) {
+ return null;
+ }
+
+ @Override
+ public QueryResultsIterator getPrivateDataQueryResult(final String collection, final String query) {
+ return null;
+ }
+
+ @Override
+ public void setEvent(final String name, final byte[] payload) {
+
+ }
+
+ @Override
+ public ChaincodeEventPackage.ChaincodeEvent getEvent() {
+ return null;
+ }
+
+ @Override
+ public ProposalPackage.SignedProposal getSignedProposal() {
+ return null;
+ }
+
+ @Override
+ public Instant getTxTimestamp() {
+ return null;
+ }
+
+ @Override
+ public byte[] getCreator() {
+ return buildSerializedIdentity();
+ }
+
+ @Override
+ public Map getTransient() {
+ return null;
+ }
+
+ @Override
+ public byte[] getBinding() {
+ return new byte[0];
+ }
+
+ void setStringArgs(final List args) {
+ this.args = args;
+ this.argsAsByte = args.stream().map(i -> i.getBytes()).collect(Collectors.toList());
+ }
+
+ public byte[] buildSerializedIdentity() {
+ final SerializedIdentity.Builder identity = SerializedIdentity.newBuilder();
+ identity.setMspid("Org1MSP");
+ final byte[] decodedCert = Base64.getDecoder().decode(this.certificate);
+ identity.setIdBytes(ByteString.copyFrom(decodedCert));
+ final SerializedIdentity builtIdentity = identity.build();
+ return builtIdentity.toByteArray();
+ }
+
+ // Used by tests to control which serialized identity is returned by
+ // buildSerializedIdentity
+ public void setCertificate(final String certificateToTest) {
+ this.certificate = certificateToTest;
+ }
+
+ @Override
+ public String getMspId() {
+ return "Org1MSP";
+ }
+}
diff --git a/token-erc-20/chaincode-java/src/test/org/example/TokenERC20ContractTest.java b/token-erc-20/chaincode-java/src/test/org/example/TokenERC20ContractTest.java
new file mode 100644
index 00000000..89392560
--- /dev/null
+++ b/token-erc-20/chaincode-java/src/test/org/example/TokenERC20ContractTest.java
@@ -0,0 +1,341 @@
+
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.example;
+
+
+import org.hyperledger.fabric.contract.Context;
+
+import org.hyperledger.fabric.contract.ClientIdentity;
+
+import org.example.TokenERC20Contract;
+import org.hyperledger.fabric.shim.ChaincodeStub;
+import org.hyperledger.fabric.shim.ledger.CompositeKey;
+import org.hyperledger.fabric.shim.ChaincodeException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class TokenERC20ContractTest {
+
+ final private String balancePrefix = "balance";
+
+ final private String nameKey = "name";
+ final private String symbolKey = "symbol";
+ final private String decimalsKey = "decimals";
+ final private String totalSupplyKey = "totalSupply";
+
+ @Nested
+ class InvokeQueryERC20TokenOPtionsTransaction {
+
+ @Test
+ public void whenTokenNameExists() {
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState(nameKey)).thenReturn("ARBTToken");
+
+ String toknName = contract.tokenName(ctx);
+
+ assertThat(toknName).isEqualTo("ARBTToken");
+
+ }
+
+ @Test
+ public void whenTokenNameDoesNotExist() {
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState(nameKey)).thenReturn("");
+
+ Throwable thrown = catchThrowable(() -> {
+ contract.tokenName(ctx);
+ });
+
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
+ .hasMessage("Sorry ! Token name not found");
+ }
+
+ @Test
+ public void whenTokenSymbolExists() {
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState(symbolKey)).thenReturn("ARBT");
+
+ String toknName = contract.tokenSymbol(ctx);
+
+ assertThat(toknName).isEqualTo("ARBT");
+
+ }
+
+ @Test
+ public void whenTokenSymbolDoesNotExist() {
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState(symbolKey)).thenReturn("");
+
+ Throwable thrown = catchThrowable(() -> {
+ contract.tokenSymbol(ctx);
+ });
+
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
+ .hasMessage("Sorry ! Token symbol not found");
+ }
+
+ @Test
+ public void whenTokenDecimalExists() {
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState(decimalsKey)).thenReturn("18");
+
+ long decimal = contract.decimals(ctx);
+
+ assertThat(decimal).isEqualTo(18);
+
+ }
+
+ @Test
+ public void whenTokenDecimalNotExists() {
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState(decimalsKey)).thenReturn("");
+
+ Throwable thrown = catchThrowable(() -> {
+ contract.decimals(ctx);
+ });
+
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
+ .hasMessage("Sorry ! Decimal not found");
+ }
+
+ @Test
+ public void whenTokenTotalSupplyExists() {
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState(totalSupplyKey)).thenReturn("222222222222");
+
+ long totalSupply = contract.totalSupply(ctx);
+
+ assertThat(totalSupply).isEqualTo(222222222222L);
+
+ }
+
+ @Test
+ public void whenTokenTotalSupplyNotExists() {
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState(totalSupplyKey)).thenReturn("");
+
+ Throwable thrown = catchThrowable(() -> {
+ contract.totalSupply(ctx);
+ });
+
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
+ .hasMessage("Sorry ! Total Supply not found");
+ }
+
+ @Test
+ public void whenClientAccountIDTest() throws Exception {
+
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
+ final ClientIdentity identity = new ClientIdentity(stub);
+ assertThat(identity.getMSPID()).isEqualTo("Org1MSP");
+ when(ctx.getClientIdentity()).thenReturn(identity);
+ String id = contract.getClientAccountID(ctx);
+ String actualId = "x509::CN=admin, OU=Fabric, O=Hyperledger, ST=North Carolina, C=US::CN=example.com,"
+ + " OU=WWW, O=Internet Widgets, L=San Francisco, ST=California, C=US";
+ assertThat(id).isEqualTo(actualId);
+
+ }
+
+ }
+
+ @Nested
+ class TokenOperationsInvoke {
+
+ @Test
+ public void setOptionsTest() {
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ contract.setOptions(ctx, "ARBTToken", "ARBT", "18");
+ verify(stub).putStringState(nameKey, "ARBTToken");
+ verify(stub).putStringState(symbolKey, "ARBT");
+ verify(stub).putStringState(decimalsKey, "18");
+ }
+
+ @Test
+ public void whenOrgMintTokensTest() throws Exception {
+
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
+ final ClientIdentity identity = new ClientIdentity(stub);
+ when(ctx.getClientIdentity()).thenReturn(identity);
+
+ when(ctx.getStub()).thenReturn(stub);
+ boolean returnValue = contract.mint(ctx, "1000");
+ assertThat(returnValue).isEqualTo(true);
+ String totalSupply = stub.getStringState(totalSupplyKey.trim());
+ assertThat(totalSupply).isEqualTo("1000");
+ String minter = ctx.getClientIdentity().getId();
+ CompositeKey balanceKey = stub.createCompositeKey(balancePrefix, minter);
+ String updatedBalance = stub.getStringState(balanceKey.toString().trim());
+ assertThat(updatedBalance).isEqualTo("1000");
+
+ }
+
+ @Test
+ public void whenUserTransferTokenTest() throws Exception {
+
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
+ final ClientIdentity identity = new ClientIdentity(stub);
+ when(ctx.getClientIdentity()).thenReturn(identity);
+ when(ctx.getStub()).thenReturn(stub);
+ boolean returnValue = contract.mint(ctx, "1000");
+ String minter = ctx.getClientIdentity().getId();
+ String _to = "x509::CN=User1@org2.example.com, L=San Francisco, ST=California,"
+ + " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
+ boolean transferResult = contract.transfer(ctx, _to, "100");
+ CompositeKey toBalanceKey = stub.createCompositeKey(balancePrefix, _to);
+ String _toCurrentBalance = stub.getStringState(toBalanceKey.toString().trim());
+ Long totalSupply = contract.totalSupply(ctx);
+ Long fromBalance = contract.balanceOf(ctx, minter);
+ ((ChaincodeStubNaiveImpl) stub).setCertificate(ChaincodeStubNaiveImpl.CERT_WITH_DNS);
+ Long _toBalance = contract.balanceOf(ctx, _to);
+
+ assertThat(transferResult).isEqualTo(true);
+ assertThat(returnValue).isEqualTo(true);
+ assertThat(totalSupply).isEqualTo(1000);
+ assertThat(_toCurrentBalance).isEqualTo("100");
+ assertThat(fromBalance).isEqualTo(900);
+ assertThat(_toBalance).isEqualTo(100);
+
+ }
+
+ @Test
+ public void whenOrgBurnsTokenTest() throws Exception {
+
+ TokenERC20Contract contract = new TokenERC20Contract();
+ Context ctx = mock(Context.class);
+ final ChaincodeStub stub = new ChaincodeStubNaiveImpl();
+ final ClientIdentity identity = new ClientIdentity(stub);
+ when(ctx.getClientIdentity()).thenReturn(identity);
+ when(ctx.getStub()).thenReturn(stub);
+ boolean returnValue = contract.mint(ctx, "1000");
+ String minter = ctx.getClientIdentity().getId();
+ boolean burnResult = contract.burn(ctx, "100");
+ Long totalSupply = contract.totalSupply(ctx);
+ Long fromBalance = contract.balanceOf(ctx, minter);
+ assertThat(returnValue).isEqualTo(true);
+ assertThat(burnResult).isEqualTo(true);
+ assertThat(totalSupply).isEqualTo(900);
+ assertThat(fromBalance).isEqualTo(900);
+
+ }
+
+ }
+
+ @Nested
+ class InvokeERC20AllowanceTransactions {
+
+ private Context ctx = null;
+ private ChaincodeStub stub = null;
+ private ClientIdentity identity = null;
+ private TokenERC20Contract contract = null;
+
+ @BeforeEach
+ public void initialize() {
+ try {
+
+ this.ctx = mock(Context.class);
+ this.stub = new ChaincodeStubNaiveImpl();
+ this.identity = new ClientIdentity(stub);
+ when(ctx.getClientIdentity()).thenReturn(identity);
+ when(ctx.getStub()).thenReturn(stub);
+ contract = new TokenERC20Contract();
+ contract.mint(ctx, "1000");
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void approveForTokenAllowanceTest() {
+
+ String spender = "x509::CN=User1@org2.example.com, L=San Francisco, ST=California,"
+ + " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
+ boolean result = contract.approve(ctx, spender, "200");
+ assertThat(result).isEqualTo(true);
+ String owner = ctx.getClientIdentity().getId();
+ long allowance = contract.allowance(ctx, owner, spender);
+ assertThat(allowance).isEqualTo(200);
+
+ }
+
+ @Test
+ public void allowanceTransferFromTest() throws Exception {
+
+ /*
+ * ChaincodeStub localStub = new ChaincodeStubNaiveImpl();
+ * ((ChaincodeStubNaiveImpl)
+ * localStub).setCertificate(ChaincodeStubNaiveImpl.CERT_WITH_DNS); Context
+ * localCtx = mock(Context.class); ClientIdentity localidentity = new
+ * ClientIdentity(localStub);
+ * when(localCtx.getClientIdentity()).thenReturn(localidentity);
+ * when(localCtx.getStub()).thenReturn(localStub);
+ */
+
+ String spender = "x509::CN=User1@org2.example.com, L=San Francisco, ST=California,"
+ + " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
+ String to = "x509::CN=User2@org2.example.com, L=San Francisco, ST=California,"
+ + " C=US::CN=ca.org2.example.com, O=org2.example.com, L=San Francisco, ST=California, C=US";
+ boolean result = contract.approve(ctx, spender, "200");
+ String owner = ctx.getClientIdentity().getId();
+
+ ((ChaincodeStubNaiveImpl) stub).setCertificate(ChaincodeStubNaiveImpl.CERT_WITH_DNS);
+ identity = new ClientIdentity(stub);
+ when(ctx.getClientIdentity()).thenReturn(identity);
+ when(ctx.getStub()).thenReturn(stub);
+ boolean transferResult = contract.transferFrom(ctx, owner, to, "100");
+ long allowance = contract.allowance(ctx, owner, spender);
+
+ assertThat(result).isEqualTo(true);
+ assertThat(transferResult).isEqualTo(true);
+ assertThat(allowance).isEqualTo(100);
+
+ }
+
+ }
+
+}