Java erc20 token standard chaincode implementation Signed-off-by: renjithpta <renjithkn@gmail.com>

Signed-off-by: renjithpta <renjithkn@gmail.com>
This commit is contained in:
renjithpta 2022-04-19 07:27:10 +00:00
parent 006c3fa3b4
commit 24c296c8a8
19 changed files with 323 additions and 239 deletions

View file

@ -58,7 +58,7 @@ The `202 ACCEPTED` response includes a `jobId` which can be used with the `/api/
Jobs are not used to get assets, because evaluating transactions is typically much faster.
Related files:
- [src/asset.router.ts](src/asset.router.ts)
- [src/assets.router.ts](src/assets.router.ts)
Defines the main `/api/assets` endpoint.
- [src/fabric.ts](src/fabric.ts)
All the sample code which interacts with the Fabric network via the Fabric SDK.
@ -66,7 +66,7 @@ Related files:
Defines the `/api/jobs` endpoint for getting job status.
- [src/jobs.ts](src/jobs.ts)
Job queue implementation details.
- [src/transactions.router.ts]()
- [src/transactions.router.ts](src/transactions.router.ts)
Defines the `/api/transactions` endpoint for getting transaction status.
**Note:** If you are not specifically interested in REST APIs, you should only need to look at the files in the [Fabric network connections](#fabric-network-connections) and [Error handling](#error-handling) sections above.

View file

@ -5570,9 +5570,9 @@
"dev": true
},
"node_modules/moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
"version": "2.29.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==",
"engines": {
"node": "*"
}
@ -11993,9 +11993,9 @@
"dev": true
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
"version": "2.29.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg=="
},
"moment-timezone": {
"version": "0.5.33",
@ -13570,4 +13570,4 @@
"integrity": "sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg=="
}
}
}
}

View file

@ -72,7 +72,7 @@ simplified, but realistic CA deployment illustrating key touch points with Kuber
### Future Enhancements:
- **_Bring your own Certificates_** : It would be nice to boostrap the network using a single, top-level signing authority,
- **_Bring your own Certificates_** : It would be nice to bootstrap the network using a single, top-level signing authority,
rather than generating self-signed certificates when the system is bootstrapped. Ideally this will be realized by
introducing an [Intermediate CA](https://hyperledger-fabric-ca.readthedocs.io/en/latest/deployguide/ca-deploy-topology.html#when-would-i-want-an-intermediate-ca)
and/or alternate signing chains backed by formal (e.g. letsencrypt, Thawte, Verisign, etc.) certificate authorities.

View file

@ -33,7 +33,7 @@ In order to construct a Fabric channel, the following steps must be performed:
4. Network orderers are joined to the channel using the channel participation API.
5. Network peers are joioned to the channel.
5. Network peers are joined to the channel.
## Aggregating the Channel MSP

View file

@ -4,7 +4,7 @@ The peers have been configured so they implemented a essential failover/high-ava
Two important notes:
1. The word 'gateway' in the k8s definitions is being used in a generic way. It is not tied to the concept of the 'Fabric Gateway' component. However using the 'Fabric-Gateway' with the udpated SDKs, make connecting to Fabric even easier. There is a single connection, that can easily be handled with core k8s abilities. Attempting the approach described below with the older SDKs is not recommended.
1. The word 'gateway' in the k8s definitions is being used in a generic way. It is not tied to the concept of the 'Fabric Gateway' component. However using the 'Fabric-Gateway' with the updated SDKs, make connecting to Fabric even easier. There is a single connection, that can easily be handled with core k8s abilities. Attempting the approach described below with the older SDKs is not recommended.
2. Long Lived gRPC connections. Remember that the connections between components in Fabric are long-lived gRPC connections. From a client application's perspective that means the connection will be load-balanced when initially connected, but unless the connection breaks, it will not be 're-load-balanced'. It's important to keep this in mind.
## Peer Gateway Services
@ -39,7 +39,7 @@ The selector is `org: org2` that is defined in the specification of the Peer's D
```
## Kube Proxy Configuration
The proxy configuration is set to be `ipvs`. This gives a lot more scope for different load balancing algorthms.
The proxy configuration is set to be `ipvs`. This gives a lot more scope for different load balancing algorithms.
"Round Robin" is the default configuration (as used in this test network). For more information check this [deep dive](https://kubernetes.io/blog/2018/07/09/ipvs-based-in-cluster-load-balancing-deep-dive) on the Kubernetes blog.
For this KIND cluster, this is configured by updating the cluster configuration, add the following yaml.

BIN
test-network-k8s/kubectl Normal file

Binary file not shown.

View file

@ -174,6 +174,11 @@ elif [ "${MODE}" == "chaincode" ]; then
elif [ "${MODE}" == "anchor" ]; then
update_anchor_peers $@
elif [ "${MODE}" == "load-images-for-rest-easy" ]; then
log "Loading images for fabric-rest-sample to KIND:"
load_docker_images_for_rest_sample
log "🏁 - Images loaded."
elif [ "${MODE}" == "rest-easy" ]; then
log "Launching fabric-rest-sample application:"
launch_rest_sample

View file

@ -13,6 +13,7 @@ function pull_docker_images() {
docker pull ${FABRIC_CONTAINER_REGISTRY}/fabric-peer:$FABRIC_VERSION
docker pull ${FABRIC_CONTAINER_REGISTRY}/fabric-tools:$FABRIC_VERSION
docker pull ghcr.io/hyperledgendary/fabric-ccaas-asset-transfer-basic:latest
docker pull couchdb:3.2.1
pop_fn
}
@ -26,10 +27,29 @@ function load_docker_images() {
kind load docker-image ${FABRIC_CONTAINER_REGISTRY}/fabric-tools:$FABRIC_VERSION
kind load docker-image ghcr.io/hyperledgendary/fabric-ccaas-asset-transfer-basic:latest
kind load docker-image couchdb:3.2.1
pop_fn
pop_fn
}
function pull_docker_images_for_rest_sample() {
push_fn "Pulling docker images for fabric-rest-sample"
docker pull ghcr.io/hyperledger/fabric-rest-sample:latest
docker pull redis:6.2.5
pop_fn
}
function load_docker_images_for_rest_sample() {
push_fn "Loading docker images for fabric-rest-sample to KIND control plane"
kind load docker-image ghcr.io/hyperledgendary/fabric-ccaas-asset-transfer-basic:latest
kind load docker-image redis:6.2.5
pop_fn
}
function apply_nginx_ingress() {
push_fn "Launching ingress controller"
@ -181,8 +201,10 @@ function kind_init() {
launch_docker_registry
if [ "${STAGE_DOCKER_IMAGES}" == true ]; then
pull_docker_images
load_docker_images
pull_docker_images
load_docker_images
pull_docker_images_for_rest_sample
load_docker_images_for_rest_sample
fi
wait_for_cert_manager

View file

@ -23,7 +23,7 @@ function yaml_ccp {
-e "s/\${CAPORT}/$3/" \
-e "s#\${PEERPEM}#$PP#" \
-e "s#\${CAPEM}#$CP#" \
ccp-template.yaml | sed -e $'s/\\\\n/\\\n /g'
ccp-template.yaml | sed -e $'s/\\\\n/\\\n /g'
}
ORG=3

View file

@ -19,7 +19,7 @@ peers:
url: grpcs://localhost:${P0PORT}
tlsCACerts:
pem: |
${PEERPEM}
${PEERPEM}
grpcOptions:
ssl-target-name-override: peer0.org${ORG}.example.com
hostnameOverride: peer0.org${ORG}.example.com
@ -28,7 +28,8 @@ certificateAuthorities:
url: https://localhost:${CAPORT}
caName: ca-org${ORG}
tlsCACerts:
pem: |
${CAPEM}
pem:
- |
${CAPEM}
httpOptions:
verify: false

View file

@ -1,31 +1,32 @@
# the first stage
FROM gradle:jdk11 AS GRADLE_BUILD
# copy the build.gradle and src code to the container
COPY src/ src/
COPY build.gradle ./
# Build and package our code
RUN gradle --no-daemon build shadowJar -x checkstyleMain -x checkstyleTest
# the second stage of our build just needs the compiled files
FROM openjdk:11-jre
ARG CC_SERVER_PORT=9999
# Setup tini to work better handle signals
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
RUN addgroup --system javauser && useradd -g javauser javauser
# copy only the artifacts we need from the first stage and discard the rest
COPY --chown=javauser:javauser --from=GRADLE_BUILD /home/gradle/build/libs/chaincode.jar /chaincode.jar
COPY --chown=javauser:javauser docker/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENV PORT $CC_SERVER_PORT
EXPOSE $CC_SERVER_PORT
USER javauser
ENTRYPOINT [ "/tini", "--", "/docker-entrypoint.sh" ]
# SPDX-License-Identifier: Apache-2.0
# the first stage
FROM gradle:jdk11 AS GRADLE_BUILD
# copy the build.gradle and src code to the container
COPY src/ src/
COPY build.gradle ./
# Build and package our code
RUN gradle --no-daemon build shadowJar -x checkstyleMain -x checkstyleTest
# the second stage of our build just needs the compiled files
FROM openjdk:11-jre
ARG CC_SERVER_PORT=9999
# Setup tini to work better handle signals
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
RUN addgroup --system javauser && useradd -g javauser javauser
# copy only the artifacts we need from the first stage and discard the rest
COPY --chown=javauser:javauser --from=GRADLE_BUILD /home/gradle/build/libs/chaincode.jar /chaincode.jar
COPY --chown=javauser:javauser docker/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENV PORT $CC_SERVER_PORT
EXPOSE $CC_SERVER_PORT
USER javauser
ENTRYPOINT [ "/tini", "--", "/docker-entrypoint.sh" ]

View file

View file

View file

@ -10,7 +10,7 @@ package org.hyperledger.fabric.samples.erc20;
public enum ContractErrors {
BALANCE_NOT_FOUND,
UNAUTHERIZED_SENDER,
UNAUTHORIZED_SENDER,
INVALID_AMOUNT,
NOT_FOUND,
INVALID_TRANSFER,

View file

@ -18,9 +18,11 @@ import static org.hyperledger.fabric.samples.erc20.ContractErrors.INVALID_AMOUNT
import static org.hyperledger.fabric.samples.erc20.ContractErrors.INVALID_TRANSFER;
import static org.hyperledger.fabric.samples.erc20.ContractErrors.NOT_FOUND;
import static org.hyperledger.fabric.samples.erc20.ContractErrors.NO_ALLOWANCE_FOUND;
import static org.hyperledger.fabric.samples.erc20.ContractErrors.UNAUTHERIZED_SENDER;
import static org.hyperledger.fabric.samples.erc20.ContractErrors.UNAUTHORIZED_SENDER;
import static org.hyperledger.fabric.samples.erc20.utils.ContractUtility.stringIsNullOrEmpty;
import com.owlike.genson.Genson;
import org.hyperledger.fabric.Logger;
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.Contact;
@ -54,6 +56,8 @@ import org.hyperledger.fabric.shim.ledger.CompositeKey;
@Default
public final class ERC20TokenContract implements ContractInterface {
final Logger logger = Logger.getLogger(ERC20TokenContract.class);
/**
* Mint creates new tokens and adds them to minter's account balance. This function triggers a
* Transfer event.
@ -69,18 +73,15 @@ public final class ERC20TokenContract implements ContractInterface {
String clientMSPID = ctx.getClientIdentity().getMSPID();
ChaincodeStub stub = ctx.getStub();
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSPID.getValue())) {
throw new ChaincodeException(
"Client is not authorized to mint new tokens", UNAUTHERIZED_SENDER.toString());
"Client is not authorized to mint new tokens", UNAUTHORIZED_SENDER.toString());
}
// Get ID of submitting client identity
String minter = ctx.getClientIdentity().getId();
if (amount <= 0) {
throw new ChaincodeException(
"Mint amount must be a positive integer", INVALID_AMOUNT.toString());
}
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), minter);
String currentBalanceStr = stub.getStringState(balanceKey.toString());
// If minter current balance doesn't yet exist, we'll create it with a current balance of 0
@ -90,7 +91,6 @@ public final class ERC20TokenContract implements ContractInterface {
}
// Used safe math .
long updatedBalance = Math.addExact(currentBalance, amount);
stub.putStringState(balanceKey.toString(), String.valueOf(updatedBalance));
// Increase totalSupply
String totalSupplyStr = stub.getStringState(TOTAL_SUPPLY_KEY.getValue());
@ -102,7 +102,11 @@ public final class ERC20TokenContract implements ContractInterface {
totalSupply = Math.addExact(totalSupply, amount);
stub.putStringState(TOTAL_SUPPLY_KEY.getValue(), String.valueOf(totalSupply));
Transfer transferEvent = new Transfer("0x0", minter, amount);
stub.setEvent(TRANSFER_EVENT.getValue(), transferEvent.toJSONString().getBytes(UTF_8));
stub.setEvent(TRANSFER_EVENT.getValue(), this.marshal(transferEvent));
logger.info(
String.format(
"minter account %s balance updated from %d to %d",
minter, currentBalance, updatedBalance));
}
/**
@ -121,26 +125,27 @@ public final class ERC20TokenContract implements ContractInterface {
ChaincodeStub stub = ctx.getStub();
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSPID.getValue())) {
throw new ChaincodeException(
"Client is not authorized to burn tokens", UNAUTHERIZED_SENDER.toString());
"Client is not authorized to burn tokens", UNAUTHORIZED_SENDER.toString());
}
String minter = ctx.getClientIdentity().getId();
if (amount <= 0) {
throw new ChaincodeException(
"Burn amount must be a positive integer", INVALID_AMOUNT.toString());
}
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), minter);
String currentBalanceStr = stub.getStringState(balanceKey.toString());
if (stringIsNullOrEmpty(currentBalanceStr)) {
throw new ChaincodeException("The balance does not exist", BALANCE_NOT_FOUND.toString());
}
long currentBalance = Long.parseLong(currentBalanceStr);
// Check if the sender has enough tokens to burn.
if (currentBalance < amount) {
String errorMessage = String.format("Client account %s has insufficient funds", minter);
throw new ChaincodeException(errorMessage, INSUFFICIENT_FUND.toString());
}
long updatedBalance = Math.subtractExact(currentBalance, amount);
stub.putStringState(balanceKey.toString(), String.valueOf(updatedBalance));
// Decrease totalSupply
String totalSupplyBytes = stub.getStringState(TOTAL_SUPPLY_KEY.getValue());
if (stringIsNullOrEmpty(totalSupplyBytes)) {
@ -148,10 +153,13 @@ public final class ERC20TokenContract implements ContractInterface {
}
long totalSupply = Math.subtractExact(Long.parseLong(totalSupplyBytes), amount);
stub.putStringState(TOTAL_SUPPLY_KEY.getValue(), String.valueOf(totalSupply));
// Emit the Transfer event
final Transfer transferEvent = new Transfer(minter, "0x0", amount);
stub.setEvent(TRANSFER_EVENT.getValue(), transferEvent.toJSONString().getBytes(UTF_8));
stub.setEvent(TRANSFER_EVENT.getValue(), this.marshal(transferEvent));
logger.info(
String.format(
"minter account %s balance updated from %d to %d",
minter, currentBalance, updatedBalance));
}
/**
@ -168,7 +176,7 @@ public final class ERC20TokenContract implements ContractInterface {
String from = ctx.getClientIdentity().getId();
this.transferHelper(ctx, from, to, value);
final Transfer transferEvent = new Transfer(from, to, value);
ctx.getStub().setEvent(TRANSFER_EVENT.getValue(), transferEvent.toJSONString().getBytes(UTF_8));
ctx.getStub().setEvent(TRANSFER_EVENT.getValue(), this.marshal(transferEvent));
}
/**
@ -187,6 +195,7 @@ public final class ERC20TokenContract implements ContractInterface {
String errorMessage = String.format("Balance of the owner %s not exists", owner);
throw new ChaincodeException(errorMessage, NOT_FOUND.toString());
}
logger.info(String.format("%s has balance of %s tokens", owner, balance));
return Long.parseLong(balance);
}
@ -207,7 +216,9 @@ public final class ERC20TokenContract implements ContractInterface {
String errorMessage = String.format("The account %s does not exist", clientAccountID);
throw new ChaincodeException(errorMessage, NOT_FOUND.toString());
}
return Long.parseLong(balanceBytes);
long balance = Long.parseLong(balanceBytes);
logger.info(String.format("%s has balance of %d tokens", clientAccountID, balance));
return balance;
}
/**
@ -235,6 +246,7 @@ public final class ERC20TokenContract implements ContractInterface {
if (stringIsNullOrEmpty(totalSupply)) {
throw new ChaincodeException("Total Supply not found", NOT_FOUND.toString());
}
logger.info(String.format("TotalSupply: %s tokens", totalSupply));
return Long.parseLong(totalSupply);
}
@ -253,7 +265,11 @@ public final class ERC20TokenContract implements ContractInterface {
stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), owner, spender);
stub.putStringState(allowanceKey.toString(), String.valueOf(value));
Approval approval = new Approval(owner, spender, value);
stub.setEvent(APPROVAL.getValue(), approval.toJSONString().getBytes(UTF_8));
stub.setEvent(APPROVAL.getValue(), this.marshal(approval));
logger.info(
String.format(
"client %s approved a withdrawal allowance of %d for spender %s",
owner, value, spender));
}
/**
@ -270,12 +286,15 @@ public final class ERC20TokenContract implements ContractInterface {
CompositeKey allowanceKey =
stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), owner, spender);
String allowanceBytes = stub.getStringState(allowanceKey.toString());
if (stringIsNullOrEmpty(allowanceBytes)) {
String errorMessage =
String.format("Spender account %s has no allowance from %s", spender, owner);
throw new ChaincodeException(errorMessage);
long allowance = 0;
if (!stringIsNullOrEmpty(allowanceBytes)) {
allowance = Long.parseLong(allowanceBytes);
}
return Long.parseLong(allowanceBytes);
logger.info(
String.format(
"The allowance left for spender %s to withdraw from owner %s: %d",
spender, owner, allowance));
return allowance;
}
/**
@ -292,14 +311,12 @@ public final class ERC20TokenContract implements ContractInterface {
String spender = ctx.getClientIdentity().getId();
ChaincodeStub stub = ctx.getStub();
// Retrieve the allowance of the spender
CompositeKey allowanceKey = stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), from, spender);
String currentAllowanceStr = stub.getStringState(allowanceKey.toString());
if (stringIsNullOrEmpty(currentAllowanceStr)) {
String errorMessage = String.format("Spender %s has no allowance from %s", spender, from);
throw new ChaincodeException(errorMessage, NO_ALLOWANCE_FOUND.toString());
}
long currentAllowance = Long.parseLong(currentAllowanceStr);
// Check if the transferred value is less than the allowance
if (currentAllowance < value) {
@ -307,13 +324,16 @@ public final class ERC20TokenContract implements ContractInterface {
String.format("Spender %s does not have enough allowance to spend", spender);
throw new ChaincodeException(errorMessage, INSUFFICIENT_FUND.toString());
}
this.transferHelper(ctx, from, to, value);
// Decrease the allowance
long updatedAllowance = currentAllowance - value;
stub.putStringState(allowanceKey.toString(), String.valueOf(updatedAllowance));
final Transfer transferEvent = new Transfer(from, to, value);
stub.setEvent(TRANSFER_EVENT.getValue(), transferEvent.toJSONString().getBytes(UTF_8));
stub.setEvent(TRANSFER_EVENT.getValue(), marshal(transferEvent));
logger.info(
String.format(
"spender %s allowance updated from %d to %d",
spender, currentAllowance, updatedAllowance));
}
/**
@ -344,33 +364,33 @@ public final class ERC20TokenContract implements ContractInterface {
String errorMessage = String.format("Client account %s has no balance", from);
throw new ChaincodeException(errorMessage, INSUFFICIENT_FUND.toString());
}
long fromCurrentBalance = Long.parseLong(fromCurrentBalanceStr);
// 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, INSUFFICIENT_FUND.toString());
}
// Retrieve the current balance of the recipient
CompositeKey toBalanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), to);
String toCurrentBalanceStr = stub.getStringState(toBalanceKey.toString());
long toCurrentBalance = 0;
// If recipient current balance doesn't yet exist, we'll create it with a
// current balance of 0
if (!stringIsNullOrEmpty(toCurrentBalanceStr)) {
toCurrentBalance = Long.parseLong(toCurrentBalanceStr.trim());
}
// Update the balance
long fromUpdatedBalance = Math.subtractExact(fromCurrentBalance, value);
long toUpdatedBalance = Math.addExact(toCurrentBalance, value);
stub.putStringState(fromBalanceKey.toString(), String.valueOf(fromUpdatedBalance));
stub.putStringState(toBalanceKey.toString(), String.valueOf(toUpdatedBalance));
logger.info(
String.format(
"client %s balance updated from %d to %d",
from, fromCurrentBalance, fromUpdatedBalance));
logger.info(
String.format(
"recipient %s balance updated from %d to %d", to, toCurrentBalance, toUpdatedBalance));
}
/**
@ -400,13 +420,10 @@ public final class ERC20TokenContract implements ContractInterface {
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String TokenName(final Context ctx) {
String tokenName = ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue());
if (stringIsNullOrEmpty(tokenName)) {
throw new ChaincodeException("Token name not found", NOT_FOUND.toString());
}
return tokenName;
}
@ -422,7 +439,6 @@ public final class ERC20TokenContract implements ContractInterface {
if (stringIsNullOrEmpty(tokenSymbol)) {
throw new ChaincodeException("Token symbol not found", NOT_FOUND.toString());
}
return tokenSymbol;
}
@ -441,4 +457,13 @@ public final class ERC20TokenContract implements ContractInterface {
}
return Integer.parseInt(decimals);
}
/**
* marshal the event data
*
* @param obj the object to marshal.
* @return marshalled object.
*/
private byte[] marshal(final Object obj) {
return new Genson().serialize(obj).getBytes(UTF_8);
}
}

View file

@ -1,72 +1,58 @@
package org.hyperledger.fabric.samples.erc20.model;
import com.owlike.genson.Genson;
import com.owlike.genson.annotation.JsonProperty;
import org.hyperledger.fabric.contract.annotation.DataType;
import org.hyperledger.fabric.contract.annotation.Property;
@DataType()
public final class Approval {
@Property()
@JsonProperty("owner")
private String owner;
@Property()
@JsonProperty("spender")
private String spender;
@Property()
@JsonProperty("value")
private long value;
/** Default constructor */
public Approval() {
super();
}
/**
* Constructor of the class
*
* @param owner token owner
* @param spender approved spender of the token
* @param value amount approved as allowance
*/
public Approval(
@JsonProperty("owner") final String owner,
@JsonProperty("spender") final String spender,
@JsonProperty("value") final long value) {
super();
this.owner = owner;
this.spender = spender;
this.value = value;
}
public String getOwner() {
return owner;
}
public void setOwner(final String owner1) {
this.owner = owner1;
}
public String getSpender() {
return spender;
}
public void setSpender(final String spender1) {
this.spender = spender1;
}
public long getValue() {
return value;
}
public void setValue(final long value1) {
this.value = value1;
}
public String toJSONString() {
return new Genson().serialize(this);
}
}
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc20.model;
import com.owlike.genson.annotation.JsonProperty;
import org.hyperledger.fabric.contract.annotation.DataType;
import org.hyperledger.fabric.contract.annotation.Property;
@DataType()
public final class Approval {
@Property()
@JsonProperty("owner")
private String owner;
@Property()
@JsonProperty("spender")
private String spender;
@Property()
@JsonProperty("value")
private long value;
/** Default constructor */
public Approval() {
super();
}
/**
* Constructor of the class
*
* @param owner token owner
* @param spender approved spender of the token
* @param value amount approved as allowance
*/
public Approval(
@JsonProperty("owner") final String owner,
@JsonProperty("spender") final String spender,
@JsonProperty("value") final long value) {
super();
this.owner = owner;
this.spender = spender;
this.value = value;
}
public String getOwner() {
return owner;
}
public String getSpender() {
return spender;
}
public long getValue() {
return value;
}
}

View file

@ -1,73 +1,58 @@
package org.hyperledger.fabric.samples.erc20.model;
import com.owlike.genson.Genson;
import com.owlike.genson.annotation.JsonProperty;
import org.hyperledger.fabric.contract.annotation.DataType;
import org.hyperledger.fabric.contract.annotation.Property;
@DataType()
public final class Transfer {
@Property()
@JsonProperty("from")
private String from;
@Property()
@JsonProperty("to")
private String to;
@Property()
@JsonProperty("value")
private long value;
/** Default constructor */
public Transfer() {
super();
}
/**
* Constructor of the class
*
* @param from owner of the token
* @param to token receiver
* @param value amount to be transferred
*/
public Transfer(
@JsonProperty("from") final String from,
@JsonProperty("to") final String to,
@JsonProperty("value") final long value) {
super();
this.from = from;
this.to = to;
this.value = value;
}
public String getFrom() {
return from;
}
public void setFrom(final String from1) {
this.from = from1;
}
public String getTo() {
return to;
}
public void setTo(final String to1) {
this.to = to1;
}
public long getValue() {
return value;
}
public void setValue(final long value1) {
this.value = value1;
}
/** @return String JSON */
public String toJSONString() {
return new Genson().serialize(this);
}
}
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc20.model;
import com.owlike.genson.annotation.JsonProperty;
import org.hyperledger.fabric.contract.annotation.DataType;
import org.hyperledger.fabric.contract.annotation.Property;
@DataType()
public final class Transfer {
@Property()
@JsonProperty("from")
private String from;
@Property()
@JsonProperty("to")
private String to;
@Property()
@JsonProperty("value")
private long value;
/** Default constructor */
public Transfer() {
super();
}
/**
* Constructor of the class
*
* @param from owner of the token
* @param to token receiver
* @param value amount to be transferred
*/
public Transfer(
@JsonProperty("from") final String from,
@JsonProperty("to") final String to,
@JsonProperty("value") final long value) {
super();
this.from = from;
this.to = to;
this.value = value;
}
public String getFrom() {
return from;
}
public String getTo() {
return to;
}
public long getValue() {
return value;
}
}

View file

@ -12,6 +12,7 @@ public final class ContractUtility {
private ContractUtility() {
}
public static boolean stringIsNullOrEmpty(final String string) {
return string == null || string.isEmpty();
}

View file

@ -1,3 +1,6 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc20;
import org.hyperledger.fabric.contract.ClientIdentity;
@ -333,6 +336,37 @@ public class TokenERC20ContractTest {
.hasMessage("Transfer amount cannot be negative");
}
@Test
public void whenTokenTransferSameId() {
ERC20TokenContract contract = new ERC20TokenContract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn(MINTER_ORG_MSPID.getValue());
when(ci.getId()).thenReturn(org1UserId);
when(ctx.getStub()).thenReturn(stub);
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";
CompositeKey ckFrom = mock(CompositeKey.class);
when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), org1UserId)).thenReturn(ckFrom);
when(ckFrom.toString()).thenReturn(BALANCE_PREFIX.getValue() + org1UserId);
when(stub.getStringState(ckFrom.toString())).thenReturn("1000");
CompositeKey ckTo = mock(CompositeKey.class);
when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), to)).thenReturn(ckTo);
when(ckTo.toString()).thenReturn(BALANCE_PREFIX.getValue() + to);
when(stub.getStringState(ckTo.toString())).thenReturn(null);
Throwable thrown = catchThrowable(() -> contract.Transfer(ctx, org1UserId, 10));
assertThat(thrown)
.isInstanceOf(ChaincodeException.class)
.hasNoCause()
.hasMessage("Cannot transfer to and from same client account");
}
@Test
public void invokeTokenBurnTest() {
@ -362,10 +396,10 @@ public class TokenERC20ContractTest {
ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn("Org2MSP");
when(ci.getId()).thenReturn(org1UserId);
when(ci.getId()).thenReturn(spender);
CompositeKey ck = mock(CompositeKey.class);
when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), org1UserId)).thenReturn(ck);
when(ck.toString()).thenReturn(BALANCE_PREFIX.getValue() + org1UserId);
when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), spender)).thenReturn(ck);
when(ck.toString()).thenReturn(BALANCE_PREFIX.getValue() + spender);
when(stub.getStringState(ck.toString())).thenReturn(null);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn("1000");
@ -377,6 +411,30 @@ public class TokenERC20ContractTest {
.hasNoCause()
.hasMessage("Client is not authorized to burn tokens");
}
@Test
public void whenTokenBurnNegativeAmountTest() {
ERC20TokenContract contract = new ERC20TokenContract();
Context ctx = mock(Context.class);
ChaincodeStub stub = mock(ChaincodeStub.class);
ClientIdentity ci = mock(ClientIdentity.class);
when(ctx.getClientIdentity()).thenReturn(ci);
when(ci.getMSPID()).thenReturn("Org1MSP");
when(ci.getId()).thenReturn(org1UserId);
CompositeKey ck = mock(CompositeKey.class);
when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), org1UserId)).thenReturn(ck);
when(ck.toString()).thenReturn(BALANCE_PREFIX.getValue() + org1UserId);
when(stub.getStringState(ck.toString())).thenReturn(null);
when(ctx.getStub()).thenReturn(stub);
when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn("1000");
when(stub.getStringState(ck.toString())).thenReturn("1000");
Throwable thrown = catchThrowable(() -> contract.Burn(ctx, -100));
assertThat(thrown)
.isInstanceOf(ChaincodeException.class)
.hasNoCause()
.hasMessage("Burn amount must be a positive integer");
}
}
@Nested