mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-25 19:15:10 +00:00
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:
parent
006c3fa3b4
commit
24c296c8a8
19 changed files with 323 additions and 239 deletions
|
|
@ -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.
|
Jobs are not used to get assets, because evaluating transactions is typically much faster.
|
||||||
|
|
||||||
Related files:
|
Related files:
|
||||||
- [src/asset.router.ts](src/asset.router.ts)
|
- [src/assets.router.ts](src/assets.router.ts)
|
||||||
Defines the main `/api/assets` endpoint.
|
Defines the main `/api/assets` endpoint.
|
||||||
- [src/fabric.ts](src/fabric.ts)
|
- [src/fabric.ts](src/fabric.ts)
|
||||||
All the sample code which interacts with the Fabric network via the Fabric SDK.
|
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.
|
Defines the `/api/jobs` endpoint for getting job status.
|
||||||
- [src/jobs.ts](src/jobs.ts)
|
- [src/jobs.ts](src/jobs.ts)
|
||||||
Job queue implementation details.
|
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.
|
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.
|
**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.
|
||||||
|
|
|
||||||
|
|
@ -5570,9 +5570,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/moment": {
|
"node_modules/moment": {
|
||||||
"version": "2.29.1",
|
"version": "2.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
|
||||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
|
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
|
|
@ -11993,9 +11993,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"moment": {
|
"moment": {
|
||||||
"version": "2.29.1",
|
"version": "2.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
|
||||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg=="
|
||||||
},
|
},
|
||||||
"moment-timezone": {
|
"moment-timezone": {
|
||||||
"version": "0.5.33",
|
"version": "0.5.33",
|
||||||
|
|
@ -13570,4 +13570,4 @@
|
||||||
"integrity": "sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg=="
|
"integrity": "sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ simplified, but realistic CA deployment illustrating key touch points with Kuber
|
||||||
|
|
||||||
### Future Enhancements:
|
### 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
|
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)
|
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.
|
and/or alternate signing chains backed by formal (e.g. letsencrypt, Thawte, Verisign, etc.) certificate authorities.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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
|
## Aggregating the Channel MSP
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ The peers have been configured so they implemented a essential failover/high-ava
|
||||||
|
|
||||||
Two important notes:
|
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.
|
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
|
## 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
|
## 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.
|
"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.
|
For this KIND cluster, this is configured by updating the cluster configuration, add the following yaml.
|
||||||
|
|
|
||||||
BIN
test-network-k8s/kubectl
Normal file
BIN
test-network-k8s/kubectl
Normal file
Binary file not shown.
|
|
@ -174,6 +174,11 @@ elif [ "${MODE}" == "chaincode" ]; then
|
||||||
elif [ "${MODE}" == "anchor" ]; then
|
elif [ "${MODE}" == "anchor" ]; then
|
||||||
update_anchor_peers $@
|
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
|
elif [ "${MODE}" == "rest-easy" ]; then
|
||||||
log "Launching fabric-rest-sample application:"
|
log "Launching fabric-rest-sample application:"
|
||||||
launch_rest_sample
|
launch_rest_sample
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ function pull_docker_images() {
|
||||||
docker pull ${FABRIC_CONTAINER_REGISTRY}/fabric-peer:$FABRIC_VERSION
|
docker pull ${FABRIC_CONTAINER_REGISTRY}/fabric-peer:$FABRIC_VERSION
|
||||||
docker pull ${FABRIC_CONTAINER_REGISTRY}/fabric-tools:$FABRIC_VERSION
|
docker pull ${FABRIC_CONTAINER_REGISTRY}/fabric-tools:$FABRIC_VERSION
|
||||||
docker pull ghcr.io/hyperledgendary/fabric-ccaas-asset-transfer-basic:latest
|
docker pull ghcr.io/hyperledgendary/fabric-ccaas-asset-transfer-basic:latest
|
||||||
|
docker pull couchdb:3.2.1
|
||||||
|
|
||||||
pop_fn
|
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 ${FABRIC_CONTAINER_REGISTRY}/fabric-tools:$FABRIC_VERSION
|
||||||
kind load docker-image ghcr.io/hyperledgendary/fabric-ccaas-asset-transfer-basic:latest
|
kind load docker-image ghcr.io/hyperledgendary/fabric-ccaas-asset-transfer-basic:latest
|
||||||
kind load docker-image couchdb:3.2.1
|
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() {
|
function apply_nginx_ingress() {
|
||||||
push_fn "Launching ingress controller"
|
push_fn "Launching ingress controller"
|
||||||
|
|
||||||
|
|
@ -181,8 +201,10 @@ function kind_init() {
|
||||||
launch_docker_registry
|
launch_docker_registry
|
||||||
|
|
||||||
if [ "${STAGE_DOCKER_IMAGES}" == true ]; then
|
if [ "${STAGE_DOCKER_IMAGES}" == true ]; then
|
||||||
pull_docker_images
|
pull_docker_images
|
||||||
load_docker_images
|
load_docker_images
|
||||||
|
pull_docker_images_for_rest_sample
|
||||||
|
load_docker_images_for_rest_sample
|
||||||
fi
|
fi
|
||||||
|
|
||||||
wait_for_cert_manager
|
wait_for_cert_manager
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ function yaml_ccp {
|
||||||
-e "s/\${CAPORT}/$3/" \
|
-e "s/\${CAPORT}/$3/" \
|
||||||
-e "s#\${PEERPEM}#$PP#" \
|
-e "s#\${PEERPEM}#$PP#" \
|
||||||
-e "s#\${CAPEM}#$CP#" \
|
-e "s#\${CAPEM}#$CP#" \
|
||||||
ccp-template.yaml | sed -e $'s/\\\\n/\\\n /g'
|
ccp-template.yaml | sed -e $'s/\\\\n/\\\n /g'
|
||||||
}
|
}
|
||||||
|
|
||||||
ORG=3
|
ORG=3
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ peers:
|
||||||
url: grpcs://localhost:${P0PORT}
|
url: grpcs://localhost:${P0PORT}
|
||||||
tlsCACerts:
|
tlsCACerts:
|
||||||
pem: |
|
pem: |
|
||||||
${PEERPEM}
|
${PEERPEM}
|
||||||
grpcOptions:
|
grpcOptions:
|
||||||
ssl-target-name-override: peer0.org${ORG}.example.com
|
ssl-target-name-override: peer0.org${ORG}.example.com
|
||||||
hostnameOverride: peer0.org${ORG}.example.com
|
hostnameOverride: peer0.org${ORG}.example.com
|
||||||
|
|
@ -28,7 +28,8 @@ certificateAuthorities:
|
||||||
url: https://localhost:${CAPORT}
|
url: https://localhost:${CAPORT}
|
||||||
caName: ca-org${ORG}
|
caName: ca-org${ORG}
|
||||||
tlsCACerts:
|
tlsCACerts:
|
||||||
pem: |
|
pem:
|
||||||
${CAPEM}
|
- |
|
||||||
|
${CAPEM}
|
||||||
httpOptions:
|
httpOptions:
|
||||||
verify: false
|
verify: false
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,32 @@
|
||||||
# the first stage
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
FROM gradle:jdk11 AS GRADLE_BUILD
|
# the first stage
|
||||||
|
FROM gradle:jdk11 AS GRADLE_BUILD
|
||||||
# copy the build.gradle and src code to the container
|
|
||||||
COPY src/ src/
|
# copy the build.gradle and src code to the container
|
||||||
COPY build.gradle ./
|
COPY src/ src/
|
||||||
|
COPY build.gradle ./
|
||||||
# Build and package our code
|
|
||||||
RUN gradle --no-daemon build shadowJar -x checkstyleMain -x checkstyleTest
|
# 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
|
# the second stage of our build just needs the compiled files
|
||||||
ARG CC_SERVER_PORT=9999
|
FROM openjdk:11-jre
|
||||||
|
ARG CC_SERVER_PORT=9999
|
||||||
# Setup tini to work better handle signals
|
|
||||||
ENV TINI_VERSION v0.19.0
|
# Setup tini to work better handle signals
|
||||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
|
ENV TINI_VERSION v0.19.0
|
||||||
RUN chmod +x /tini
|
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
|
||||||
|
RUN chmod +x /tini
|
||||||
RUN addgroup --system javauser && useradd -g javauser javauser
|
|
||||||
|
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 only the artifacts we need from the first stage and discard the rest
|
||||||
COPY --chown=javauser:javauser docker/docker-entrypoint.sh /docker-entrypoint.sh
|
COPY --chown=javauser:javauser --from=GRADLE_BUILD /home/gradle/build/libs/chaincode.jar /chaincode.jar
|
||||||
RUN chmod +x /docker-entrypoint.sh
|
COPY --chown=javauser:javauser docker/docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
ENV PORT $CC_SERVER_PORT
|
RUN chmod +x /docker-entrypoint.sh
|
||||||
EXPOSE $CC_SERVER_PORT
|
ENV PORT $CC_SERVER_PORT
|
||||||
|
EXPOSE $CC_SERVER_PORT
|
||||||
USER javauser
|
|
||||||
ENTRYPOINT [ "/tini", "--", "/docker-entrypoint.sh" ]
|
USER javauser
|
||||||
|
ENTRYPOINT [ "/tini", "--", "/docker-entrypoint.sh" ]
|
||||||
|
|
|
||||||
0
token-erc-20/chaincode-java/config/checkstyle/checkstyle.xml
Normal file → Executable file
0
token-erc-20/chaincode-java/config/checkstyle/checkstyle.xml
Normal file → Executable file
0
token-erc-20/chaincode-java/config/checkstyle/suppressions.xml
Normal file → Executable file
0
token-erc-20/chaincode-java/config/checkstyle/suppressions.xml
Normal file → Executable file
|
|
@ -10,7 +10,7 @@ package org.hyperledger.fabric.samples.erc20;
|
||||||
|
|
||||||
public enum ContractErrors {
|
public enum ContractErrors {
|
||||||
BALANCE_NOT_FOUND,
|
BALANCE_NOT_FOUND,
|
||||||
UNAUTHERIZED_SENDER,
|
UNAUTHORIZED_SENDER,
|
||||||
INVALID_AMOUNT,
|
INVALID_AMOUNT,
|
||||||
NOT_FOUND,
|
NOT_FOUND,
|
||||||
INVALID_TRANSFER,
|
INVALID_TRANSFER,
|
||||||
|
|
|
||||||
|
|
@ -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.INVALID_TRANSFER;
|
||||||
import static org.hyperledger.fabric.samples.erc20.ContractErrors.NOT_FOUND;
|
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.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 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.Context;
|
||||||
import org.hyperledger.fabric.contract.ContractInterface;
|
import org.hyperledger.fabric.contract.ContractInterface;
|
||||||
import org.hyperledger.fabric.contract.annotation.Contact;
|
import org.hyperledger.fabric.contract.annotation.Contact;
|
||||||
|
|
@ -54,6 +56,8 @@ import org.hyperledger.fabric.shim.ledger.CompositeKey;
|
||||||
@Default
|
@Default
|
||||||
public final class ERC20TokenContract implements ContractInterface {
|
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
|
* Mint creates new tokens and adds them to minter's account balance. This function triggers a
|
||||||
* Transfer event.
|
* Transfer event.
|
||||||
|
|
@ -69,18 +73,15 @@ public final class ERC20TokenContract implements ContractInterface {
|
||||||
String clientMSPID = ctx.getClientIdentity().getMSPID();
|
String clientMSPID = ctx.getClientIdentity().getMSPID();
|
||||||
ChaincodeStub stub = ctx.getStub();
|
ChaincodeStub stub = ctx.getStub();
|
||||||
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSPID.getValue())) {
|
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSPID.getValue())) {
|
||||||
|
|
||||||
throw new ChaincodeException(
|
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
|
// Get ID of submitting client identity
|
||||||
String minter = ctx.getClientIdentity().getId();
|
String minter = ctx.getClientIdentity().getId();
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
throw new ChaincodeException(
|
throw new ChaincodeException(
|
||||||
"Mint amount must be a positive integer", INVALID_AMOUNT.toString());
|
"Mint amount must be a positive integer", INVALID_AMOUNT.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), minter);
|
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), minter);
|
||||||
String currentBalanceStr = stub.getStringState(balanceKey.toString());
|
String currentBalanceStr = stub.getStringState(balanceKey.toString());
|
||||||
// If minter current balance doesn't yet exist, we'll create it with a current balance of 0
|
// 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 .
|
// Used safe math .
|
||||||
long updatedBalance = Math.addExact(currentBalance, amount);
|
long updatedBalance = Math.addExact(currentBalance, amount);
|
||||||
|
|
||||||
stub.putStringState(balanceKey.toString(), String.valueOf(updatedBalance));
|
stub.putStringState(balanceKey.toString(), String.valueOf(updatedBalance));
|
||||||
// Increase totalSupply
|
// Increase totalSupply
|
||||||
String totalSupplyStr = stub.getStringState(TOTAL_SUPPLY_KEY.getValue());
|
String totalSupplyStr = stub.getStringState(TOTAL_SUPPLY_KEY.getValue());
|
||||||
|
|
@ -102,7 +102,11 @@ public final class ERC20TokenContract implements ContractInterface {
|
||||||
totalSupply = Math.addExact(totalSupply, amount);
|
totalSupply = Math.addExact(totalSupply, amount);
|
||||||
stub.putStringState(TOTAL_SUPPLY_KEY.getValue(), String.valueOf(totalSupply));
|
stub.putStringState(TOTAL_SUPPLY_KEY.getValue(), String.valueOf(totalSupply));
|
||||||
Transfer transferEvent = new Transfer("0x0", minter, amount);
|
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();
|
ChaincodeStub stub = ctx.getStub();
|
||||||
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSPID.getValue())) {
|
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSPID.getValue())) {
|
||||||
throw new ChaincodeException(
|
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();
|
String minter = ctx.getClientIdentity().getId();
|
||||||
|
|
||||||
if (amount <= 0) {
|
if (amount <= 0) {
|
||||||
throw new ChaincodeException(
|
throw new ChaincodeException(
|
||||||
"Burn amount must be a positive integer", INVALID_AMOUNT.toString());
|
"Burn amount must be a positive integer", INVALID_AMOUNT.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), minter);
|
CompositeKey balanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), minter);
|
||||||
String currentBalanceStr = stub.getStringState(balanceKey.toString());
|
String currentBalanceStr = stub.getStringState(balanceKey.toString());
|
||||||
if (stringIsNullOrEmpty(currentBalanceStr)) {
|
if (stringIsNullOrEmpty(currentBalanceStr)) {
|
||||||
throw new ChaincodeException("The balance does not exist", BALANCE_NOT_FOUND.toString());
|
throw new ChaincodeException("The balance does not exist", BALANCE_NOT_FOUND.toString());
|
||||||
}
|
}
|
||||||
long currentBalance = Long.parseLong(currentBalanceStr);
|
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);
|
long updatedBalance = Math.subtractExact(currentBalance, amount);
|
||||||
|
|
||||||
stub.putStringState(balanceKey.toString(), String.valueOf(updatedBalance));
|
stub.putStringState(balanceKey.toString(), String.valueOf(updatedBalance));
|
||||||
|
|
||||||
// Decrease totalSupply
|
// Decrease totalSupply
|
||||||
String totalSupplyBytes = stub.getStringState(TOTAL_SUPPLY_KEY.getValue());
|
String totalSupplyBytes = stub.getStringState(TOTAL_SUPPLY_KEY.getValue());
|
||||||
if (stringIsNullOrEmpty(totalSupplyBytes)) {
|
if (stringIsNullOrEmpty(totalSupplyBytes)) {
|
||||||
|
|
@ -148,10 +153,13 @@ public final class ERC20TokenContract implements ContractInterface {
|
||||||
}
|
}
|
||||||
long totalSupply = Math.subtractExact(Long.parseLong(totalSupplyBytes), amount);
|
long totalSupply = Math.subtractExact(Long.parseLong(totalSupplyBytes), amount);
|
||||||
stub.putStringState(TOTAL_SUPPLY_KEY.getValue(), String.valueOf(totalSupply));
|
stub.putStringState(TOTAL_SUPPLY_KEY.getValue(), String.valueOf(totalSupply));
|
||||||
|
|
||||||
// Emit the Transfer event
|
// Emit the Transfer event
|
||||||
final Transfer transferEvent = new Transfer(minter, "0x0", amount);
|
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();
|
String from = ctx.getClientIdentity().getId();
|
||||||
this.transferHelper(ctx, from, to, value);
|
this.transferHelper(ctx, from, to, value);
|
||||||
final Transfer transferEvent = new Transfer(from, to, value);
|
final Transfer transferEvent = new Transfer(from, to, value);
|
||||||
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);
|
String errorMessage = String.format("Balance of the owner %s not exists", owner);
|
||||||
throw new ChaincodeException(errorMessage, NOT_FOUND.toString());
|
throw new ChaincodeException(errorMessage, NOT_FOUND.toString());
|
||||||
}
|
}
|
||||||
|
logger.info(String.format("%s has balance of %s tokens", owner, balance));
|
||||||
return Long.parseLong(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);
|
String errorMessage = String.format("The account %s does not exist", clientAccountID);
|
||||||
throw new ChaincodeException(errorMessage, NOT_FOUND.toString());
|
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)) {
|
if (stringIsNullOrEmpty(totalSupply)) {
|
||||||
throw new ChaincodeException("Total Supply not found", NOT_FOUND.toString());
|
throw new ChaincodeException("Total Supply not found", NOT_FOUND.toString());
|
||||||
}
|
}
|
||||||
|
logger.info(String.format("TotalSupply: %s tokens", totalSupply));
|
||||||
return Long.parseLong(totalSupply);
|
return Long.parseLong(totalSupply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,7 +265,11 @@ public final class ERC20TokenContract implements ContractInterface {
|
||||||
stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), owner, spender);
|
stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), owner, spender);
|
||||||
stub.putStringState(allowanceKey.toString(), String.valueOf(value));
|
stub.putStringState(allowanceKey.toString(), String.valueOf(value));
|
||||||
Approval approval = new Approval(owner, spender, 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 =
|
CompositeKey allowanceKey =
|
||||||
stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), owner, spender);
|
stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), owner, spender);
|
||||||
String allowanceBytes = stub.getStringState(allowanceKey.toString());
|
String allowanceBytes = stub.getStringState(allowanceKey.toString());
|
||||||
if (stringIsNullOrEmpty(allowanceBytes)) {
|
long allowance = 0;
|
||||||
String errorMessage =
|
if (!stringIsNullOrEmpty(allowanceBytes)) {
|
||||||
String.format("Spender account %s has no allowance from %s", spender, owner);
|
allowance = Long.parseLong(allowanceBytes);
|
||||||
throw new ChaincodeException(errorMessage);
|
|
||||||
}
|
}
|
||||||
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();
|
String spender = ctx.getClientIdentity().getId();
|
||||||
ChaincodeStub stub = ctx.getStub();
|
ChaincodeStub stub = ctx.getStub();
|
||||||
// Retrieve the allowance of the spender
|
// Retrieve the allowance of the spender
|
||||||
|
|
||||||
CompositeKey allowanceKey = stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), from, spender);
|
CompositeKey allowanceKey = stub.createCompositeKey(ALLOWANCE_PREFIX.getValue(), from, spender);
|
||||||
String currentAllowanceStr = stub.getStringState(allowanceKey.toString());
|
String currentAllowanceStr = stub.getStringState(allowanceKey.toString());
|
||||||
if (stringIsNullOrEmpty(currentAllowanceStr)) {
|
if (stringIsNullOrEmpty(currentAllowanceStr)) {
|
||||||
String errorMessage = String.format("Spender %s has no allowance from %s", spender, from);
|
String errorMessage = String.format("Spender %s has no allowance from %s", spender, from);
|
||||||
throw new ChaincodeException(errorMessage, NO_ALLOWANCE_FOUND.toString());
|
throw new ChaincodeException(errorMessage, NO_ALLOWANCE_FOUND.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
long currentAllowance = Long.parseLong(currentAllowanceStr);
|
long currentAllowance = Long.parseLong(currentAllowanceStr);
|
||||||
// Check if the transferred value is less than the allowance
|
// Check if the transferred value is less than the allowance
|
||||||
if (currentAllowance < value) {
|
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);
|
String.format("Spender %s does not have enough allowance to spend", spender);
|
||||||
throw new ChaincodeException(errorMessage, INSUFFICIENT_FUND.toString());
|
throw new ChaincodeException(errorMessage, INSUFFICIENT_FUND.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.transferHelper(ctx, from, to, value);
|
this.transferHelper(ctx, from, to, value);
|
||||||
// Decrease the allowance
|
// Decrease the allowance
|
||||||
long updatedAllowance = currentAllowance - value;
|
long updatedAllowance = currentAllowance - value;
|
||||||
stub.putStringState(allowanceKey.toString(), String.valueOf(updatedAllowance));
|
stub.putStringState(allowanceKey.toString(), String.valueOf(updatedAllowance));
|
||||||
final Transfer transferEvent = new Transfer(from, to, value);
|
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);
|
String errorMessage = String.format("Client account %s has no balance", from);
|
||||||
throw new ChaincodeException(errorMessage, INSUFFICIENT_FUND.toString());
|
throw new ChaincodeException(errorMessage, INSUFFICIENT_FUND.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
long fromCurrentBalance = Long.parseLong(fromCurrentBalanceStr);
|
long fromCurrentBalance = Long.parseLong(fromCurrentBalanceStr);
|
||||||
|
|
||||||
// Check if the sender has enough tokens to spend.
|
// Check if the sender has enough tokens to spend.
|
||||||
if (fromCurrentBalance < value) {
|
if (fromCurrentBalance < value) {
|
||||||
String errorMessage = String.format("Client account %s has insufficient funds", from);
|
String errorMessage = String.format("Client account %s has insufficient funds", from);
|
||||||
throw new ChaincodeException(errorMessage, INSUFFICIENT_FUND.toString());
|
throw new ChaincodeException(errorMessage, INSUFFICIENT_FUND.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the current balance of the recipient
|
// Retrieve the current balance of the recipient
|
||||||
CompositeKey toBalanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), to);
|
CompositeKey toBalanceKey = stub.createCompositeKey(BALANCE_PREFIX.getValue(), to);
|
||||||
String toCurrentBalanceStr = stub.getStringState(toBalanceKey.toString());
|
String toCurrentBalanceStr = stub.getStringState(toBalanceKey.toString());
|
||||||
|
|
||||||
long toCurrentBalance = 0;
|
long toCurrentBalance = 0;
|
||||||
// If recipient current balance doesn't yet exist, we'll create it with a
|
// If recipient current balance doesn't yet exist, we'll create it with a
|
||||||
// current balance of 0
|
// current balance of 0
|
||||||
if (!stringIsNullOrEmpty(toCurrentBalanceStr)) {
|
if (!stringIsNullOrEmpty(toCurrentBalanceStr)) {
|
||||||
toCurrentBalance = Long.parseLong(toCurrentBalanceStr.trim());
|
toCurrentBalance = Long.parseLong(toCurrentBalanceStr.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the balance
|
// Update the balance
|
||||||
long fromUpdatedBalance = Math.subtractExact(fromCurrentBalance, value);
|
long fromUpdatedBalance = Math.subtractExact(fromCurrentBalance, value);
|
||||||
long toUpdatedBalance = Math.addExact(toCurrentBalance, value);
|
long toUpdatedBalance = Math.addExact(toCurrentBalance, value);
|
||||||
|
|
||||||
stub.putStringState(fromBalanceKey.toString(), String.valueOf(fromUpdatedBalance));
|
stub.putStringState(fromBalanceKey.toString(), String.valueOf(fromUpdatedBalance));
|
||||||
|
|
||||||
stub.putStringState(toBalanceKey.toString(), String.valueOf(toUpdatedBalance));
|
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)
|
@Transaction(intent = Transaction.TYPE.EVALUATE)
|
||||||
public String TokenName(final Context ctx) {
|
public String TokenName(final Context ctx) {
|
||||||
|
|
||||||
String tokenName = ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue());
|
String tokenName = ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue());
|
||||||
|
|
||||||
if (stringIsNullOrEmpty(tokenName)) {
|
if (stringIsNullOrEmpty(tokenName)) {
|
||||||
throw new ChaincodeException("Token name not found", NOT_FOUND.toString());
|
throw new ChaincodeException("Token name not found", NOT_FOUND.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokenName;
|
return tokenName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,7 +439,6 @@ public final class ERC20TokenContract implements ContractInterface {
|
||||||
if (stringIsNullOrEmpty(tokenSymbol)) {
|
if (stringIsNullOrEmpty(tokenSymbol)) {
|
||||||
throw new ChaincodeException("Token symbol not found", NOT_FOUND.toString());
|
throw new ChaincodeException("Token symbol not found", NOT_FOUND.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokenSymbol;
|
return tokenSymbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -441,4 +457,13 @@ public final class ERC20TokenContract implements ContractInterface {
|
||||||
}
|
}
|
||||||
return Integer.parseInt(decimals);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,58 @@
|
||||||
package org.hyperledger.fabric.samples.erc20.model;
|
/*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
import com.owlike.genson.Genson;
|
*/
|
||||||
import com.owlike.genson.annotation.JsonProperty;
|
package org.hyperledger.fabric.samples.erc20.model;
|
||||||
import org.hyperledger.fabric.contract.annotation.DataType;
|
|
||||||
import org.hyperledger.fabric.contract.annotation.Property;
|
import com.owlike.genson.annotation.JsonProperty;
|
||||||
|
import org.hyperledger.fabric.contract.annotation.DataType;
|
||||||
@DataType()
|
import org.hyperledger.fabric.contract.annotation.Property;
|
||||||
public final class Approval {
|
|
||||||
|
@DataType()
|
||||||
@Property()
|
public final class Approval {
|
||||||
@JsonProperty("owner")
|
|
||||||
private String owner;
|
@Property()
|
||||||
|
@JsonProperty("owner")
|
||||||
@Property()
|
private String owner;
|
||||||
@JsonProperty("spender")
|
|
||||||
private String spender;
|
@Property()
|
||||||
|
@JsonProperty("spender")
|
||||||
@Property()
|
private String spender;
|
||||||
@JsonProperty("value")
|
|
||||||
private long value;
|
@Property()
|
||||||
|
@JsonProperty("value")
|
||||||
/** Default constructor */
|
private long value;
|
||||||
public Approval() {
|
|
||||||
super();
|
/** Default constructor */
|
||||||
}
|
public Approval() {
|
||||||
|
super();
|
||||||
/**
|
}
|
||||||
* Constructor of the class
|
|
||||||
*
|
/**
|
||||||
* @param owner token owner
|
* Constructor of the class
|
||||||
* @param spender approved spender of the token
|
*
|
||||||
* @param value amount approved as allowance
|
* @param owner token owner
|
||||||
*/
|
* @param spender approved spender of the token
|
||||||
public Approval(
|
* @param value amount approved as allowance
|
||||||
@JsonProperty("owner") final String owner,
|
*/
|
||||||
@JsonProperty("spender") final String spender,
|
public Approval(
|
||||||
@JsonProperty("value") final long value) {
|
@JsonProperty("owner") final String owner,
|
||||||
super();
|
@JsonProperty("spender") final String spender,
|
||||||
this.owner = owner;
|
@JsonProperty("value") final long value) {
|
||||||
this.spender = spender;
|
super();
|
||||||
this.value = value;
|
this.owner = owner;
|
||||||
}
|
this.spender = spender;
|
||||||
|
this.value = value;
|
||||||
public String getOwner() {
|
}
|
||||||
return owner;
|
|
||||||
}
|
public String getOwner() {
|
||||||
|
return owner;
|
||||||
public void setOwner(final String owner1) {
|
}
|
||||||
this.owner = owner1;
|
|
||||||
}
|
public String getSpender() {
|
||||||
|
return spender;
|
||||||
public String getSpender() {
|
}
|
||||||
return spender;
|
|
||||||
}
|
public long getValue() {
|
||||||
|
return value;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,73 +1,58 @@
|
||||||
package org.hyperledger.fabric.samples.erc20.model;
|
/*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
import com.owlike.genson.Genson;
|
*/
|
||||||
import com.owlike.genson.annotation.JsonProperty;
|
package org.hyperledger.fabric.samples.erc20.model;
|
||||||
import org.hyperledger.fabric.contract.annotation.DataType;
|
|
||||||
import org.hyperledger.fabric.contract.annotation.Property;
|
import com.owlike.genson.annotation.JsonProperty;
|
||||||
|
import org.hyperledger.fabric.contract.annotation.DataType;
|
||||||
@DataType()
|
import org.hyperledger.fabric.contract.annotation.Property;
|
||||||
public final class Transfer {
|
|
||||||
|
@DataType()
|
||||||
@Property()
|
public final class Transfer {
|
||||||
@JsonProperty("from")
|
|
||||||
private String from;
|
@Property()
|
||||||
|
@JsonProperty("from")
|
||||||
@Property()
|
private String from;
|
||||||
@JsonProperty("to")
|
|
||||||
private String to;
|
@Property()
|
||||||
|
@JsonProperty("to")
|
||||||
@Property()
|
private String to;
|
||||||
@JsonProperty("value")
|
|
||||||
private long value;
|
@Property()
|
||||||
|
@JsonProperty("value")
|
||||||
/** Default constructor */
|
private long value;
|
||||||
public Transfer() {
|
|
||||||
super();
|
/** Default constructor */
|
||||||
}
|
public Transfer() {
|
||||||
|
super();
|
||||||
/**
|
}
|
||||||
* Constructor of the class
|
|
||||||
*
|
/**
|
||||||
* @param from owner of the token
|
* Constructor of the class
|
||||||
* @param to token receiver
|
*
|
||||||
* @param value amount to be transferred
|
* @param from owner of the token
|
||||||
*/
|
* @param to token receiver
|
||||||
public Transfer(
|
* @param value amount to be transferred
|
||||||
@JsonProperty("from") final String from,
|
*/
|
||||||
@JsonProperty("to") final String to,
|
public Transfer(
|
||||||
@JsonProperty("value") final long value) {
|
@JsonProperty("from") final String from,
|
||||||
super();
|
@JsonProperty("to") final String to,
|
||||||
this.from = from;
|
@JsonProperty("value") final long value) {
|
||||||
this.to = to;
|
super();
|
||||||
this.value = value;
|
this.from = from;
|
||||||
}
|
this.to = to;
|
||||||
|
this.value = value;
|
||||||
public String getFrom() {
|
}
|
||||||
return from;
|
|
||||||
}
|
public String getFrom() {
|
||||||
|
return from;
|
||||||
public void setFrom(final String from1) {
|
}
|
||||||
this.from = from1;
|
|
||||||
}
|
public String getTo() {
|
||||||
|
return to;
|
||||||
public String getTo() {
|
}
|
||||||
return to;
|
|
||||||
}
|
public long getValue() {
|
||||||
|
return value;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ public final class ContractUtility {
|
||||||
private ContractUtility() {
|
private ContractUtility() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean stringIsNullOrEmpty(final String string) {
|
public static boolean stringIsNullOrEmpty(final String string) {
|
||||||
return string == null || string.isEmpty();
|
return string == null || string.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
package org.hyperledger.fabric.samples.erc20;
|
package org.hyperledger.fabric.samples.erc20;
|
||||||
|
|
||||||
import org.hyperledger.fabric.contract.ClientIdentity;
|
import org.hyperledger.fabric.contract.ClientIdentity;
|
||||||
|
|
@ -333,6 +336,37 @@ public class TokenERC20ContractTest {
|
||||||
.hasMessage("Transfer amount cannot be negative");
|
.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
|
@Test
|
||||||
public void invokeTokenBurnTest() {
|
public void invokeTokenBurnTest() {
|
||||||
|
|
||||||
|
|
@ -362,10 +396,10 @@ public class TokenERC20ContractTest {
|
||||||
ClientIdentity ci = mock(ClientIdentity.class);
|
ClientIdentity ci = mock(ClientIdentity.class);
|
||||||
when(ctx.getClientIdentity()).thenReturn(ci);
|
when(ctx.getClientIdentity()).thenReturn(ci);
|
||||||
when(ci.getMSPID()).thenReturn("Org2MSP");
|
when(ci.getMSPID()).thenReturn("Org2MSP");
|
||||||
when(ci.getId()).thenReturn(org1UserId);
|
when(ci.getId()).thenReturn(spender);
|
||||||
CompositeKey ck = mock(CompositeKey.class);
|
CompositeKey ck = mock(CompositeKey.class);
|
||||||
when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), org1UserId)).thenReturn(ck);
|
when(stub.createCompositeKey(BALANCE_PREFIX.getValue(), spender)).thenReturn(ck);
|
||||||
when(ck.toString()).thenReturn(BALANCE_PREFIX.getValue() + org1UserId);
|
when(ck.toString()).thenReturn(BALANCE_PREFIX.getValue() + spender);
|
||||||
when(stub.getStringState(ck.toString())).thenReturn(null);
|
when(stub.getStringState(ck.toString())).thenReturn(null);
|
||||||
when(ctx.getStub()).thenReturn(stub);
|
when(ctx.getStub()).thenReturn(stub);
|
||||||
when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn("1000");
|
when(stub.getStringState(TOTAL_SUPPLY_KEY.getValue())).thenReturn("1000");
|
||||||
|
|
@ -377,6 +411,30 @@ public class TokenERC20ContractTest {
|
||||||
.hasNoCause()
|
.hasNoCause()
|
||||||
.hasMessage("Client is not authorized to burn tokens");
|
.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
|
@Nested
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue