erc1155 java chaincode implementation Signed-off-by: renjithpta <renjithkn@gmail.com>

Signed-off-by: renjithpta <renjithkn@gmail.com>
This commit is contained in:
renjithpta 2022-06-07 07:02:05 +00:00
parent 8ca50df4ff
commit 84326edb19
29 changed files with 3731 additions and 0 deletions

View file

@ -0,0 +1,31 @@
#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

@ -0,0 +1,91 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
plugins {
id 'com.github.johnrengelman.shadow' version '5.1.0'
id 'application'
id 'checkstyle'
id 'jacoco'
}
group 'org.hyperledger.fabric.samples'
version '1.0-SNAPSHOT'
dependencies {
implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.4.1'
implementation 'org.json:json:+'
implementation 'com.owlike:genson:1.5'
testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.4.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
testImplementation 'org.assertj:assertj-core:3.11.1'
testImplementation 'org.mockito:mockito-core:2.23.4'
testRuntimeOnly("net.bytebuddy:byte-buddy:1.10.6")
}
repositories {
mavenCentral()
maven {
url 'https://jitpack.io'
}
}
application {
mainClass = 'org.hyperledger.fabric.contract.ContractRouter'
}
checkstyle {
toolVersion '8.21'
configFile file("config/checkstyle/checkstyle.xml")
}
checkstyleMain {
source ='src/main/java'
}
checkstyleTest {
source ='src/test/java'
}
jacocoTestReport {
dependsOn test
}
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.8
}
}
}
finalizedBy jacocoTestReport
}
test {
useJUnitPlatform()
testLogging {
showStandardStreams = true
}
testLogging {
events "passed", "skipped", "failed"
}
}
mainClassName = 'org.hyperledger.fabric.contract.ContractRouter'
shadowJar {
baseName = 'chaincode'
version = null
classifier = null
manifest {
attributes 'Main-Class': 'org.hyperledger.fabric.contract.ContractRouter'
}
}
check.dependsOn jacocoTestCoverageVerification
installDist.dependsOn check

View file

@ -0,0 +1,174 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that matches the Eclipse formatter
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sourceforge.net (or in your downloaded distribution).
Most Checks are configurable, be sure to consult the documentation.
To completely disable a check, just comment it out or delete it from the file.
Finally, it is worth reading the documentation.
-->
<module name="Checker">
<!--
If you set the basedir property below, then all reported file
names will be relative to the specified directory. See
https://checkstyle.org/5.x/config.html#Checker
<property name="basedir" value="${basedir}"/>
-->
<property name="fileExtensions" value="java, properties, xml"/>
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions.xml"/>
<property name="optional" value="false"/>
</module>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- Checks that a package-info.java file exists for each package. -->
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html#JavadocPackage -->
<!-- <module name="JavadocPackage"/> -->
<!-- Checks whether files end with a new line. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile"/>
<!-- Checks that property files contain the same keys. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html#Translation -->
<module name="Translation"/>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
<module name="FileLength"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
<module name="FileTabCharacter"/>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<!-- Checks for Headers -->
<!-- See http://checkstyle.sourceforge.net/config_header.html -->
<!-- <module name="Header"> -->
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
<!-- <property name="fileExtensions" value="java"/> -->
<!-- </module> -->
<module name="TreeWalker">
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html -->
<!-- <module name="JavadocMethod"/> -->
<!-- <module name="JavadocType"/> -->
<!-- <module name="JavadocVariable"/> -->
<!-- <module name="JavadocStyle"/> -->
<!-- <module name="MissingJavadocMethod"/> -->
<!-- Checks for Naming Conventions. -->
<!-- See http://checkstyle.sourceforge.net/config_naming.html -->
<module name="ConstantName"/>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName"/>
<module name="PackageName"/>
<module name="StaticVariableName"/>
<module name="TypeName"/>
<!-- Checks for imports -->
<!-- See http://checkstyle.sourceforge.net/config_import.html -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<module name="UnusedImports">
<property name="processJavadoc" value="false"/>
</module>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
<module name="MethodLength"/>
<module name="ParameterNumber"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
<module name="EmptyForIteratorPad"/>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<!-- Modifier Checks -->
<!-- See http://checkstyle.sourceforge.net/config_modifiers.html -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sourceforge.net/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock"/>
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<!-- Checks for common coding problems -->
<!-- See http://checkstyle.sourceforge.net/config_coding.html -->
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="HiddenField">
<property name="ignoreSetter" value="true" />
<property name="ignoreConstructorParameter" value="true"/>
</module>
<module name="IllegalInstantiation"/>
<module name="InnerAssignment"/>
<module name="MissingSwitchDefault"/>
<module name="MultipleVariableDeclarations"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<!-- Checks for class design -->
<!-- See http://checkstyle.sourceforge.net/config_design.html -->
<module name="DesignForExtension"/>
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<module name="VisibilityModifier">
<property name="allowPublicFinalFields" value="true"/>
</module>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="FinalParameters"/>
<module name="TodoComment"/>
<module name="UpperEll"/>
</module>
</module>

View file

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
<suppress files="ERC721TokenContract.java"
checks="ParameterNumber" />
<suppress files="NFT.java" checks="HiddenFiled" />
<suppress files="Transfer.java" checks="HiddenFiled" />
<suppress files="Approval.java" checks="HiddenFiled" />
</suppressions>

View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
#
# SPDX-License-Identifier: Apache-2.0
#
set -euo pipefail
: ${CORE_PEER_TLS_ENABLED:="false"}
: ${DEBUG:="false"}
if [ "${DEBUG,,}" = "true" ]; then
exec java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar /chaincode.jar
elif [ "${CORE_PEER_TLS_ENABLED,,}" = "true" ]; then
exec java -jar /chaincode.jar # todo
else
exec java -jar /chaincode.jar
fi

Binary file not shown.

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
token-erc-1155/chaincode-java/gradlew vendored Executable file
View file

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

100
token-erc-1155/chaincode-java/gradlew.bat vendored Executable file
View file

@ -0,0 +1,100 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1,5 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
rootProject.name = 'erc1155'

View file

@ -0,0 +1,31 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155;
public enum ContractConstants {
URI_KEY("uri"),
BALANCE_PREFIX("account~tokenId~sender"),
APPROVAL_PREFIX("account~operator"),
NAME_KEY("name"),
SYMBOL_KEY("symbol"),
APPROVE_FOR_ALL("ApproveForAll"),
TRANSFER("Transfer"),
MINTER_ORG_MSP("Org1MSP"),
ZERO_ADDRESS("0x0"),
TRANSFER_SINGLE_EVENT("TransferSingle"),
TRANSFER_BATCH_EVENT("TransferBatch"),
TRANSFER_BATCH_MULTI_RECIPIENT_EVENT("TransferBatchMultiRecipient"),
APPROVE_FOR_ALL_EVENT("ApprovalForAll");
private final String value;
ContractConstants(final String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}

View file

@ -0,0 +1,19 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155;
public enum ContractErrors {
TOKEN_NOT_FOUND,
TOKEN_ALREADY_EXITS,
NO_OWNER_ASSIGNED,
UNAUTHORIZED_SENDER,
INVALID_TOKEN_OWNER,
NOT_FOUND,
INVALID_ADDRESS,
INVALID_AMOUNT,
INVALID_ARGUMENTS,
INSUFFICIENT_FUND,
INVALID_URI,
INVALID_INPUT
}

View file

@ -0,0 +1,993 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155;
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;
import org.hyperledger.fabric.contract.annotation.Contract;
import org.hyperledger.fabric.contract.annotation.Default;
import org.hyperledger.fabric.contract.annotation.Info;
import org.hyperledger.fabric.contract.annotation.License;
import org.hyperledger.fabric.contract.annotation.Transaction;
import org.hyperledger.fabric.samples.erc1155.models.ApprovalForAll;
import org.hyperledger.fabric.samples.erc1155.models.ToID;
import org.hyperledger.fabric.samples.erc1155.models.TransferBatch;
import org.hyperledger.fabric.samples.erc1155.models.TransferBatchMultiRecipient;
import org.hyperledger.fabric.samples.erc1155.models.TransferSingle;
import org.hyperledger.fabric.shim.ChaincodeException;
import org.hyperledger.fabric.shim.ChaincodeStub;
import org.hyperledger.fabric.shim.ledger.CompositeKey;
import org.hyperledger.fabric.shim.ledger.KeyValue;
import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.fabric.samples.erc1155.utils.ContractUtility.stringIsNullOrEmpty;
@Contract(
name = "erc1155token",
info =
@Info(
title = "ERC1155Token Contract",
description = "The erc1155 multi token standard implementation",
version = "0.0.1-SNAPSHOT",
license =
@License(
name = "Apache 2.0 License",
url = "http://www.apache.org/licenses/LICENSE-2.0.html"),
contact =
@Contact(
email = "renjith.narayanan@gmail.com",
name = "Renjith Narayanan",
url = "https://hyperledger.example.com")))
@Default
public class ERC1155Contract implements ContractInterface {
final Logger logger = Logger.getLogger(ERC1155Contract.class);
/**
* Mint a new non-fungible token
*
* @param ctx the transaction context
* @param tokenId Unique ID of the non-fungible token to be minted
* @param tokenURI URI containing metadata of the minted non-fungible token
* @return Return the non-fungible token object
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void Mint(final Context ctx, final String account, final long id, final long amount) {
// check if contract has been initialized first
this.checkInitialized(ctx);
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to
// mint new tokens
this.authorizationHelper(ctx);
// Get ID of submitting client identity
String operator = ctx.getClientIdentity().getId();
// Mint tokens
this.mintHelper(ctx, operator, account, id, amount);
// Emit TransferSingle event
TransferSingle transferSingleEvent =
new TransferSingle(
operator, ContractConstants.ZERO_ADDRESS.getValue(), account, id, amount);
this.emitTransferSingle(ctx, transferSingleEvent);
}
/**
* MintBatch creates amount tokens for each token type id and assigns them to account This
* function emits a TransferBatch event
*
* @param ctx the transaction context
* @param tokenId Unique ID of the non-fungible token to be minted
* @param tokenURI URI containing metadata of the minted non-fungible token
* @return Return the non-fungible token object
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void MintBatch(
final Context ctx, final String account, final long[] ids, final long[] amounts) {
// check if contract has been initialized first
this.checkInitialized(ctx);
if (ids.length != amounts.length) {
throw new ChaincodeException(
"ids and amounts must have the same length", ContractErrors.INVALID_ARGUMENTS.toString());
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to
// mint new tokens
this.authorizationHelper(ctx);
// Get ID of submitting client identity
String operator = ctx.getClientIdentity().getId();
// Group amount by token id because we can only send token to a recipient only one time in a
// block. This prevents key conflicts
Map<Long, Long> amountToSend = new HashMap<>(); // token id => amount
for (int i = 0; i < amounts.length; i++) {
if (amountToSend.containsKey(ids[i])) {
amountToSend.put(ids[i], Math.addExact(amountToSend.get(ids[i]).longValue(), amounts[i]));
} else {
amountToSend.put(ids[i], amounts[i]);
}
}
// Copy the map keys and sort it. This is necessary because iterating maps in Go is not
// deterministic
List<Long> amountToSendKeys = this.sortedKeys(amountToSend);
// Mint tokens
for (long id : amountToSendKeys) {
long amount = amountToSend.get(id);
this.mintHelper(ctx, operator, account, id, amount);
}
// Emit TransferBatch event
TransferBatch transferBatchEvent =
new TransferBatch(
operator, ContractConstants.ZERO_ADDRESS.getValue(), account, ids, amounts);
this.emitTransferBatch(ctx, transferBatchEvent);
}
/**
* Burn destroys amount tokens of token type id from account. This function triggers a
* TransferSingle event.
*
* @param ctx the transaction context
* @param account the account from where to burn token
* @param id token id
* @param amount amount of tokens to burn for the given token id.
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void Burn(final Context ctx, final String account, final long id, final long amount) {
// check if contract has been initialized first
this.checkInitialized(ctx);
if (account.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
throw new ChaincodeException(
"burn to the zero address", ContractErrors.INVALID_ADDRESS.toString());
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to
// burn new tokens
this.authorizationHelper(ctx);
// Get ID of submitting client identity
String operator = ctx.getClientIdentity().getId();
// Burn tokens
this.removeBalance(ctx, account, new long[] {id}, new long[] {amount});
TransferSingle transferSingleEvent =
new TransferSingle(
operator, account, ContractConstants.ZERO_ADDRESS.getValue(), id, amount);
this.emitTransferSingle(ctx, transferSingleEvent);
}
/**
* Batched version of Burn.
*
* @param ctx the transaction context
* @param account the account from where to burn token
* @param ids array of token ids
* @param amounts array of amount to burn corresponds to the token ids
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void BurnBatch(
final Context ctx, final String account, final long[] ids, final long[] amounts) {
// check if contract has been initialized first
this.checkInitialized(ctx);
if (account.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
throw new ChaincodeException(
"burn to the zero address", ContractErrors.INVALID_ADDRESS.toString());
}
if (ids.length != amounts.length) {
throw new ChaincodeException(
"ids and amounts must have the same length", ContractErrors.INVALID_INPUT.toString());
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to
// burn new tokens
this.authorizationHelper(ctx);
// Get ID of submitting client identity
String operator = ctx.getClientIdentity().getId();
this.removeBalance(ctx, account, ids, amounts);
TransferBatch transferBatchEvent =
new TransferBatch(
operator, account, ContractConstants.ZERO_ADDRESS.getValue(), ids, amounts);
this.emitTransferBatch(ctx, transferBatchEvent);
}
/**
* TransferFrom transfers tokens from sender account to recipient account. recipient account must
* be a valid clientID as returned by the ClientID() function This function triggers a
* TransferSingle event
*
* @param ctx
* @param sender
* @param recipient
* @param id
* @param amount
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void TransferFrom(
final Context ctx,
final String sender,
final String recipient,
final long id,
final long amount) {
// check if contract has been initialized first
this.checkInitialized(ctx);
if (sender.equalsIgnoreCase(recipient)) {
throw new ChaincodeException("transfer to self", ContractErrors.INVALID_INPUT.toString());
}
if (recipient.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
throw new ChaincodeException(
"transfer to the zero address", ContractErrors.INVALID_ADDRESS.toString());
}
// Get ID of submitting client identity
String operator = ctx.getClientIdentity().getId();
// Check whether operator is owner or approved
if (!operator.equalsIgnoreCase(sender)) {
boolean approved = _isApprovedForAll(ctx, sender, operator);
if (!approved) {
throw new ChaincodeException(
"caller is not owner nor is approved", ContractErrors.UNAUTHORIZED_SENDER.toString());
}
}
// Withdraw the funds from the sender address
this.removeBalance(ctx, sender, new long[] {id}, new long[] {amount});
// Deposit the fund to the recipient address
this.addBalance(ctx, sender, recipient, id, amount);
// Emit TransferSingle event
TransferSingle transferSingleEvent =
new TransferSingle(operator, sender, recipient, id, amount);
this.emitTransferSingle(ctx, transferSingleEvent);
}
/**
* BatchTransferFrom transfers multiple tokens from sender account to recipient account recipient
* account must be a valid clientID as returned by the ClientID() function This function triggers
* a TransferBatch event
*
* @param ctx the transaction context
* @param sender the sender account id
* @param recipient account id of the recipient
* @param ids token ids
* @param amounts amount to transfer for different token ids
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void BatchTransferFrom(
final Context ctx,
final String sender,
final String recipient,
final long[] ids,
final long[] amounts) {
this.checkInitialized(ctx); // check if contract has been initialized first
if (sender.equalsIgnoreCase(recipient)) {
throw new ChaincodeException("transfer to self", ContractErrors.INVALID_INPUT.toString());
}
if (ids.length != amounts.length) {
throw new ChaincodeException(
"ids and amounts must have the same length", ContractErrors.INVALID_INPUT.toString());
}
String operator = ctx.getClientIdentity().getId(); // Get ID of submitting client identity
// Check whether operator is owner or approved
if (!operator.equalsIgnoreCase(sender)) {
boolean approved = _isApprovedForAll(ctx, sender, operator);
if (!approved) {
throw new ChaincodeException(
"caller is not owner nor is approved", ContractErrors.UNAUTHORIZED_SENDER.toString());
}
}
if (recipient.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
throw new ChaincodeException(
"transfer to the zero address", ContractErrors.INVALID_ADDRESS.toString());
}
this.removeBalance(ctx, sender, ids, amounts); // Withdraw the funds from the sender address
// Group amount by token id because we can only send token to a recipient only one time in a
// block. This prevents key conflicts
Map<Long, Long> amountToSend = new HashMap<>(); // token id => amount
for (int i = 0; i < amounts.length; i++) {
amountToSend.put(ids[i], amountToSend.getOrDefault(ids[i], 0L) + amounts[i]);
}
// Copy the map keys and sort it. This is necessary because iterating maps in Go is not
// deterministic
List<Long> amountToSendKeys = sortedKeys(amountToSend);
// Deposit the funds to the recipient address
for (Long id : amountToSendKeys) {
long amount = amountToSend.get(id);
this.addBalance(ctx, sender, recipient, id, amount);
}
TransferBatch transferBatchEvent = new TransferBatch(operator, sender, recipient, ids, amounts);
this.emitTransferBatch(ctx, transferBatchEvent);
}
/**
* BatchTransferFromMultiRecipient transfers multiple tokens from sender account to multiple
* recipient accounts recipient account must be a valid clientID as returned by the ClientID()
* function This function triggers a TransferBatchMultiRecipient event
*
* @param ctx the transaction context
* @param sender account id of the sender
* @param recipients account id of the all the recipients
* @param ids all the token Ids
* @param amounts amount of token for each token id in the id array.
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void BatchTransferFromMultiRecipient(
final Context ctx,
final String sender,
final String[] recipients,
final long[] ids,
final long[] amounts) {
// check if contract has been initialized first
this.checkInitialized(ctx);
if ((recipients.length != ids.length) || (ids.length != amounts.length)) {
throw new ChaincodeException(
"recipients, ids, and amounts must have the same length",
ContractErrors.INVALID_INPUT.toString());
}
for (String recipient : recipients) {
if (sender.equalsIgnoreCase(recipient)) {
throw new ChaincodeException("transfer to self", ContractErrors.INVALID_ADDRESS.toString());
}
}
// Get ID of submitting client identity
String operator = ctx.getClientIdentity().getId();
// Check whether operator is owner or approved
if (!operator.equalsIgnoreCase(sender)) {
boolean approved = _isApprovedForAll(ctx, sender, operator);
if (!approved) {
throw new ChaincodeException(
"caller is not owner nor is approved", ContractErrors.INVALID_ADDRESS.toString());
}
}
// Withdraw the funds from the sender address
this.removeBalance(ctx, sender, ids, amounts);
// Group amount by (recipient, id ) pair because we can only send token to a recipient only one
// time in a block. This prevents key conflicts
Map<ToID, Long> amountToSend = new HashMap<>(); // (recipient, id ) => amount
for (int i = 0; i < amounts.length; i++) {
ToID key = new ToID(recipients[i], ids[i]);
amountToSend.put(key, amountToSend.getOrDefault(key, 0L) + amounts[i]);
}
// Copy the map keys and sort it. This is necessary because iterating maps in Go is not
// deterministic
List<ToID> amountToSendKeys = sortedKeysToID(amountToSend);
// Deposit the funds to the recipient addresses
for (ToID amountoSendKey : amountToSendKeys) {
if (amountoSendKey.getTo().equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
throw new ChaincodeException(
"transfer to the zero address", ContractErrors.INVALID_ADDRESS.toString());
}
long amount = amountToSend.get(amountoSendKey);
this.addBalance(ctx, sender, amountoSendKey.getTo(), amountoSendKey.getId(), amount);
}
// Emit TransferBatchMultiRecipient event
TransferBatchMultiRecipient transferBatchMultiRecipientEvent =
new TransferBatchMultiRecipient(operator, sender, recipients, ids, amounts);
this.emitTransferBatchMultiRecipient(ctx, transferBatchMultiRecipientEvent);
}
/**
* IsApprovedForAll returns true if operator is approved to transfer account's tokens.
*
* @param ctx the transaction context
* @param account account id from where token transferred
* @param operator the operator of the account
* @return
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public boolean IsApprovedForAll(final Context ctx, final String account, final String operator) {
return this._isApprovedForAll(ctx, account, operator);
}
/**
* Helper function for IsApprovedForAll
*
* @param ctx the transaction context
* @param account account id from where token transferred
* @param operator the operator of the account
* @return
*/
private boolean _isApprovedForAll(
final Context ctx, final String account, final String operator) {
// check if contract has been initialized first
this.checkInitialized(ctx);
CompositeKey approvalKey =
ctx.getStub()
.createCompositeKey(ContractConstants.APPROVAL_PREFIX.getValue(), account, operator);
String approved = ctx.getStub().getStringState(approvalKey.toString());
if (stringIsNullOrEmpty(approved)) {
return false;
}
return new Boolean(approved);
}
/**
* SetApprovalForAll returns true if operator is approved to transfer account's tokens. Emits an
* ApprovalForAll event.
*
* @param ctx the transaction context
* @param operator get the permission to transfer account's tokens.
* @param approved
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void SetApprovalForAll(final Context ctx, final String operator, final boolean approved) {
// check if contract has been initialized first
this.checkInitialized(ctx);
// Get ID of submitting client identity
String account = ctx.getClientIdentity().getId();
if (account.equalsIgnoreCase(operator)) {
throw new ChaincodeException(
"setting approval status for self", ContractErrors.INVALID_INPUT.toString());
}
CompositeKey approvalKey =
ctx.getStub()
.createCompositeKey(ContractConstants.APPROVAL_PREFIX.getValue(), account, operator);
ctx.getStub().putStringState(approvalKey.toString(), String.valueOf(approved));
ApprovalForAll approvalForAllEvent = new ApprovalForAll(account, operator, approved);
ctx.getStub()
.setEvent(
ContractConstants.APPROVE_FOR_ALL_EVENT.getValue(), this.marshal(approvalForAllEvent));
}
/**
* BalanceOf returns the balance of the given account
*
* @param ctx the transaction context
* @param account the account id
* @param id the given token id
* @return
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public long BalanceOf(final Context ctx, final String account, final long id) {
// check if contract has been initialized first
this.checkInitialized(ctx);
return balanceOfHelper(ctx, account, id);
}
/**
* BalanceOfBatch returns the balance of multiple account/token pairs
*
* @param ctx the transaction context
* @param accounts the account ids
* @param ids the given token ids
* @return
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public long[] BalanceOfBatch(final Context ctx, final String[] accounts, final long[] ids) {
// check if contract has been initialized first
this.checkInitialized(ctx);
if (accounts.length != ids.length) {
throw new ChaincodeException(
"ids and amounts must have the same length", ContractErrors.INVALID_INPUT.toString());
}
long[] balances = new long[accounts.length];
for (int i = 0; i < accounts.length; i++) {
balances[i] = balanceOfHelper(ctx, accounts[i], ids[i]);
}
return balances;
}
/**
* ClientAccountBalance returns the balance of the requesting client's account
*
* @param ctx the transaction context
* @param id token id
* @return
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public long ClientAccountBalance(final Context ctx, final long id) {
// check if contract has been initialized first
this.checkInitialized(ctx);
// Get ID of submitting client identity
String clientID = ctx.getClientIdentity().getId();
return balanceOfHelper(ctx, clientID, id);
}
/**
* 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.
*
* @param ctx the transaction context
* @return
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String ClientAccountID(final Context ctx) {
// check if contract has been initialized first
this.checkInitialized(ctx);
// Get ID of submitting client identity
String clientAccountID = ctx.getClientIdentity().getId();
return clientAccountID;
}
/**
* SetURI set the URI value. This function triggers URI event for each token id .This
* implementation returns the same URI for all token types. It relies on the token type ID
* substitution mechanism .
*
* @param ctx the transaction context
* @param uri the token URI
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void SetURI(final Context ctx, final String uri) {
// check if contract has been initialized first
this.checkInitialized(ctx);
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to
// mint new tokens
this.authorizationHelper(ctx);
if (uri.indexOf("{id}") < 0) {
throw new ChaincodeException(
"failed to set uri, uri should contain '{id}'", ContractErrors.INVALID_URI.toString());
}
ctx.getStub().putStringState(ContractConstants.URI_KEY.getValue(), uri);
}
/**
* Returns the URI.This implementation returns the same URI for *all* token types. It relies on
* the token type ID substitution mechanism. Clients calling this function must replace the
* `\{id\}` substring with the actual token type ID.
*
* @param ctx the transaction context
* @param id tokenid
* @return
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String URI(final Context ctx, final long id) {
// check if contract has been initialized first
final ChaincodeStub stub = ctx.getStub();
String uri = stub.getStringState(ContractConstants.URI_KEY.getValue());
if (stringIsNullOrEmpty(uri)) {
throw new ChaincodeException("no uri is set", ContractErrors.NOT_FOUND.toString());
}
return uri;
}
/**
* @param ctx
* @param id
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public void BroadcastTokenExistance(final Context ctx, final long id) {
// check if contract has been initialized first
this.checkInitialized(ctx);
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to
// mint new tokens
this.authorizationHelper(ctx);
String operator = ctx.getClientIdentity().getId();
TransferSingle transferSingleEvent =
new TransferSingle(
operator,
ContractConstants.ZERO_ADDRESS.getValue(),
ContractConstants.ZERO_ADDRESS.getValue(),
id,
0);
this.emitTransferSingle(ctx, transferSingleEvent);
}
/**
* Returns a descriptive name for a collection of non-fungible tokens in this contract
*
* @param ctx the transaction context
* @return name of the token.
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String Name(final Context ctx) {
// check contract options are already set first to execute the function
this.checkInitialized(ctx);
return ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue());
}
/**
* Returns an abbreviated symbol for non-fungible tokens in this contract.
*
* @param ctx the transaction context
* @return token symbol.
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String Symbol(final Context ctx) {
// check contract options are already set first to execute the function
this.checkInitialized(ctx);
return ctx.getStub().getStringState(ContractConstants.SYMBOL_KEY.getValue());
}
/**
* Initialize the token with name and symbol.
*
* @param ctx the transaction context
* @param name token name
* @param symbol token symbol
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void Initialize(final Context ctx, final String name, final String symbol) {
final ChaincodeStub stub = ctx.getStub();
final String clientMSPID = ctx.getClientIdentity().getMSPID();
// Check minter authorization - this sample assumes Org1 is the issuer with privilege to set the
// name and symbol
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSP.getValue())) {
throw new ChaincodeException(
"Client is not authorized to initialize the contract",
ContractErrors.UNAUTHORIZED_SENDER.toString());
}
// check contract options are not already set, client is not authorized to change them once
// initialized
String tokenName = stub.getStringState(ContractConstants.NAME_KEY.getValue());
if (!stringIsNullOrEmpty(tokenName)) {
throw new ChaincodeException(
"contract options are already set, client is not authorized to change them",
ContractErrors.UNAUTHORIZED_SENDER.toString());
}
stub.putStringState(ContractConstants.NAME_KEY.getValue(), name);
stub.putStringState(ContractConstants.SYMBOL_KEY.getValue(), symbol);
}
/**
* This helper function checks minter authorization - this sample assumes Org1 is the central
* banker with privilege to mint new tokens Throw exception if not initialized.
*
* @param ctx the transaction context
* @return
*/
private void authorizationHelper(final Context ctx) {
String clientMSPID = ctx.getClientIdentity().getMSPID();
if (!clientMSPID.equalsIgnoreCase(ContractConstants.MINTER_ORG_MSP.getValue())) {
throw new ChaincodeException(
"Client is not authorized to set the name and symbol of the token",
ContractErrors.UNAUTHORIZED_SENDER.toString());
}
}
/**
* Update the balance of account for the given token id.
*
* @param ctx
* @param account
* @param id
* @return
*/
private long balanceOfHelper(final Context ctx, final String account, final long id) {
if (account.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
throw new ChaincodeException(
"balance query for the zero address", ContractErrors.INVALID_ADDRESS.toString());
}
// Convert id to string
String idString = String.valueOf(id);
long balance = 0;
final QueryResultsIterator<KeyValue> balanceIterator =
ctx.getStub()
.getStateByPartialCompositeKey(
ContractConstants.BALANCE_PREFIX.getValue(), account, idString);
for (KeyValue keyVal : balanceIterator) {
long balAmount = Long.parseLong(keyVal.getStringValue());
balance = Math.addExact(balance, balAmount);
}
return balance;
}
/**
* Helper function to add balance of recipient account
*
* @param ctx the transaction context
* @param sender the sender account id
* @param recipient the token recipient account id
* @param id the token id
* @param amount the amount of token
*/
private void addBalance(
final Context ctx,
final String sender,
final String recipient,
final long id,
final long amount) {
ChaincodeStub stub = ctx.getStub();
String idString = String.valueOf(id); // Convert id to string
CompositeKey balanceKey =
stub.createCompositeKey(
ContractConstants.BALANCE_PREFIX.getValue(), recipient, idString, sender);
String balanceStr = stub.getStringState(balanceKey.toString());
long balance = 0;
if (!stringIsNullOrEmpty(balanceStr)) {
balance = Long.parseLong(balanceStr);
}
balance = Math.addExact(balance, amount);
stub.putStringState(balanceKey.toString(), String.valueOf(balance));
logger.info(String.format("account %s balance updated to %d", recipient, balance));
}
/**
* Remove Balance of the sender for each given token id and amounts.
*
* @param ctx the transaction context
* @param sender the owner of the token
* @param ids each token ids
* @param amounts the amount of the token to transfered for each id
*/
private void removeBalance(
final Context ctx, final String sender, final long[] ids, final long[] amounts) {
// Calculate the total amount of each token to withdraw
Map<Long, Long> necessaryFunds = new HashMap<Long, Long>(); // token id -> necessary amount
for (int i = 0; i < amounts.length; i++) {
necessaryFunds.put(ids[i], necessaryFunds.getOrDefault(i, 0L) + amounts[i]);
}
ChaincodeStub stub = ctx.getStub();
// Copy the map keys and sort it. This is necessary because iterating maps in Go is not
// deterministic
List<Long> necessaryFundsKeys = sortedKeys(necessaryFunds);
// Check whether the sender has the necessary funds and withdraw them from the account
for (long tokenId : necessaryFundsKeys) {
long neededAmount = necessaryFunds.get(tokenId);
String idString = String.valueOf(tokenId);
long partialBalance = 0;
boolean selfRecipientKeyNeedsToBeRemoved = false;
String selfRecipientKey = "";
final QueryResultsIterator<KeyValue> balanceResulutIterator =
ctx.getStub()
.getStateByPartialCompositeKey(
ContractConstants.BALANCE_PREFIX.getValue(), sender, idString);
Iterator<KeyValue> balanceIterator = balanceResulutIterator.iterator();
while (balanceIterator.hasNext() && partialBalance < neededAmount) {
KeyValue queryResponse = balanceIterator.next();
long partBalAmount = Long.valueOf(queryResponse.getStringValue());
partialBalance = Math.addExact(partialBalance, partBalAmount);
CompositeKey compositeKeyParts = stub.splitCompositeKey(queryResponse.getKey());
if (compositeKeyParts.getAttributes().contains(sender)) {
selfRecipientKeyNeedsToBeRemoved = true;
selfRecipientKey = queryResponse.getKey();
} else {
ctx.getStub().delState(queryResponse.getKey());
}
}
if (partialBalance < neededAmount) {
String error =
String.format(
"sender has insufficient funds for token %s, needed funds: %d, available fund: %d",
tokenId, neededAmount, partialBalance);
throw new ChaincodeException(error, ContractErrors.INSUFFICIENT_FUND.toString());
} else if (partialBalance > neededAmount) {
// Send the remainder back to the sender
long remainder = Math.subtractExact(partialBalance, neededAmount);
if (selfRecipientKeyNeedsToBeRemoved) {
// Set balance for the key that has the same address for sender and recipient
this.setBalance(ctx, sender, sender, tokenId, remainder);
} else {
this.addBalance(ctx, sender, sender, tokenId, remainder);
}
} else {
// Delete self recipient key
stub.delState(selfRecipientKey);
}
}
}
/**
* Helper function for token mint call.
*
* @param ctx the transaction context
* @param operator the approved operator of the token
* @param account account id updated with token
* @param id the token id
* @param amount quantity of token to be minted
*/
private void mintHelper(
final Context ctx,
final String operator,
final String account,
final long id,
final long amount) {
if (account.equalsIgnoreCase(ContractConstants.ZERO_ADDRESS.getValue())) {
throw new ChaincodeException(
"mint to the zero address", ContractErrors.INVALID_ADDRESS.toString());
}
if (amount <= 0) {
throw new ChaincodeException(
"Mint amount must be a positive integer", ContractErrors.INVALID_AMOUNT.toString());
}
this.addBalance(ctx, operator, account, id, amount);
}
private void setBalance(
final Context ctx,
final String sender,
final String recipient,
final long id,
final long amount) {
// Convert id to string
String idString = String.valueOf(id);
CompositeKey balanceKey =
ctx.getStub()
.createCompositeKey(
ContractConstants.BALANCE_PREFIX.getValue(), recipient, idString, sender);
ctx.getStub().putStringState(balanceKey.toString(), String.valueOf(amount));
}
/**
* Emit when single recipient token transfer occur.
*
* @param ctx
* @param transferSingleEvent
*/
private void emitTransferSingle(final Context ctx, final TransferSingle transferSingleEvent) {
ctx.getStub()
.setEvent(
ContractConstants.TRANSFER_SINGLE_EVENT.getValue(), this.marshal(transferSingleEvent));
}
/**
* Emit transfer batch event.
*
* @param ctx the transaction context
* @param transferBatchEvent BatchEvent details
*/
private void emitTransferBatch(final Context ctx, final TransferBatch transferBatchEvent) {
ctx.getStub()
.setEvent(
ContractConstants.TRANSFER_BATCH_EVENT.getValue(), this.marshal(transferBatchEvent));
}
/**
* EmitTransferBatchMultiRecipient when multi recipient token batch transfer occurs.
*
* @param ctx the transaction context
* @param transferBatchMutipleRecipientEvent event details
*/
private void emitTransferBatchMultiRecipient(
final Context ctx, final TransferBatchMultiRecipient transferBatchMutipleRecipientEvent) {
ctx.getStub()
.setEvent(
ContractConstants.TRANSFER_BATCH_MULTI_RECIPIENT_EVENT.getValue(),
this.marshal(transferBatchMutipleRecipientEvent));
}
/**
* Returns the sorted array from the keys of map
*
* @param map the keys of map data to be sorted
* @return
*/
private List<Long> sortedKeys(final Map<Long, Long> map) {
List<Long> sortedKeys = new ArrayList<>();
sortedKeys.addAll(map.keySet());
Collections.sort(sortedKeys);
return sortedKeys;
}
/**
* Returns the sorted slice ([]ToID) copied from the keys of map[ToID]uint64
*
* @param the keys of map data to be sorted
* @return
*/
private List<ToID> sortedKeysToID(final Map<ToID, Long> map) {
List<ToID> sortedKeys = new ArrayList<>();
sortedKeys.addAll(map.keySet());
Collections.sort(
sortedKeys,
new Comparator<ToID>() {
public int compare(final ToID toId1, final ToID toId2) {
if (toId1.getId() != toId2.getId()) {
return toId1.getTo().compareTo(toId2.getTo());
}
return new Long(toId1.getId()).compareTo(toId2.getId());
}
});
return sortedKeys;
}
/**
* Checks that contract options have been already initialized Throw exception if not initialized.
*
* @param ctx the transaction context
* @return
*/
private void checkInitialized(final Context ctx) {
String tokenName = ctx.getStub().getStringState(ContractConstants.NAME_KEY.getValue());
if (stringIsNullOrEmpty(tokenName)) {
throw new ChaincodeException(
"Contract options need to be set before calling any function, call Initialize() to initialize contract",
ContractErrors.TOKEN_NOT_FOUND.toString());
}
}
/**
* 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

@ -0,0 +1,68 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155.models;
import com.owlike.genson.annotation.JsonProperty;
import org.hyperledger.fabric.contract.annotation.Property;
public class ApprovalForAll {
@Property()
@JsonProperty("owner")
private String owner;
@Property()
@JsonProperty("operator")
private String operator;
@Property()
@JsonProperty("approved")
private boolean approved;
/** Default constructor */
public ApprovalForAll() {
super();
}
/**
* Constructor of the class
*
* @param owner
* @param operator
* @param approved
*/
public ApprovalForAll(final String owner, final String operator, final boolean approved) {
super();
this.owner = owner;
this.operator = operator;
this.approved = approved;
}
/**
* getter function for the owner
*
* @return the token owner account
*/
public String getOwner() {
return owner;
}
/**
* getter function for operator
*
* @return the token approved operator
*/
public String getOperator() {
return operator;
}
/**
* getter function for the approved
*
* @return is operator is approved one or not
*/
public boolean getApproved() {
return approved;
}
}

View file

@ -0,0 +1,68 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155.models;
import com.owlike.genson.annotation.JsonProperty;
import org.hyperledger.fabric.contract.annotation.Property;
public abstract class Event {
@Property()
@JsonProperty("operator")
private String operator;
@Property()
@JsonProperty("from")
private String from;
@Property()
@JsonProperty("to")
private String to;
/** Default constructor */
public Event() {
super();
}
/**
* Constructor of the class
*
* @param operator
* @param from
* @param to
*/
public Event(final String operator, final String from, final String to) {
super();
this.operator = operator;
this.from = from;
this.to = to;
}
/**
* getter function for the operator
*
* @return the token operator
*/
public String getOperator() {
return operator;
}
/**
* getter function for the from
*
* @return the sender account
*/
public String getFrom() {
return from;
}
/**
* getter function for the to
*
* @return the recipient account
*/
public String getTo() {
return to;
}
}

View file

@ -0,0 +1,98 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155.models;
import org.hyperledger.fabric.contract.annotation.Property;
import com.owlike.genson.annotation.JsonProperty;
public class ToID {
@Property()
@JsonProperty("id")
private long id;
@Property()
@JsonProperty("to")
private String to;
/** Default constructor */
public ToID() {
super();
}
/**
* Constructor of the class
*
* @param id tokenId
* @param to the recipient account
*/
public ToID(final String to, final long id) {
super();
this.to = to;
this.id = id;
}
/**
* The getter function for the Id
*
* @return the tokenId
*/
public long getId() {
return id;
}
/**
* The getter function for the To.
*
* @return the recipient account id
*/
public String getTo() {
return to;
}
/**
*
* @param obj
* @return
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ToID other = (ToID) obj;
if (id != other.id) {
return false;
}
if (to == null) {
if (other.to != null) {
return false;
}
} else if (!to.equalsIgnoreCase(other.to)) {
return false;
}
return true;
}
/**
*
* @return the hashcode
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
result = prime * result + ((to == null) ? 0 : to.hashCode());
return result;
}
}

View file

@ -0,0 +1,61 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155.models;
import com.owlike.genson.annotation.JsonProperty;
import org.hyperledger.fabric.contract.annotation.Property;
public class TransferBatch extends Event {
@Property()
@JsonProperty("ids")
private long[] ids;
@Property()
@JsonProperty("values")
private long[] values;
/** Default constructor */
public TransferBatch() {
super();
}
/**
* Constructor of the class
*
* @param operator
* @param from
* @param to
* @param ids
* @param values
*/
public TransferBatch(
final String operator,
final String from,
final String to,
final long[] ids,
final long[] values) {
super(operator, from, to);
this.ids = ids;
this.values = values;
}
/**
* Getter function for Ids
*
* @return all token Ids
*/
public long[] getIds() {
return ids;
}
/**
* Getter function for the values
*
* @return get the amount or value of all the tokens
*/
public long[] getValues() {
return values;
}
}

View file

@ -0,0 +1,103 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155.models;
import com.owlike.genson.annotation.JsonProperty;
import org.hyperledger.fabric.contract.annotation.Property;
public class TransferBatchMultiRecipient {
@Property()
@JsonProperty("operator")
private String operator;
@Property()
@JsonProperty("from")
private String from;
@Property()
@JsonProperty("to")
private String[] to;
@Property()
@JsonProperty("ids")
private long[] ids;
@Property()
@JsonProperty("values")
private long[] values;
/** Default constructor */
public TransferBatchMultiRecipient() {
super();
}
/**
* Constructor of the class
*
* @param operator
* @param from
* @param to
* @param ids
* @param values
*/
public TransferBatchMultiRecipient(
final String operator,
final String from,
final String[] to,
final long[] ids,
final long[] values) {
super();
this.operator = operator;
this.from = from;
this.to = to;
this.ids = ids;
this.values = values;
}
/**
* getter function for from account
*
* @return get the from account
*/
public String getFrom() {
return from;
}
/**
* getter function for Ids
*
* @return get all token ids
*/
public long[] getIds() {
return ids;
}
/**
* getter function for the operator
*
* @return gett all aproved token operators
*/
public String getOperator() {
return operator;
}
/**
* getter function for recipients
*
* @return all the recipients
*/
public String[] getTo() {
return to;
}
/**
* getter function for all the values
*
* @return get all the token values or ammount
*/
public long[] getValues() {
return values;
}
}

View file

@ -0,0 +1,57 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155.models;
import org.hyperledger.fabric.contract.annotation.Property;
import com.owlike.genson.annotation.JsonProperty;
public class TransferSingle extends Event {
@Property()
@JsonProperty("id")
private long id;
@Property()
@JsonProperty("value")
private long value;
/** Default constructor */
public TransferSingle() {
super();
}
/**
* Constructor of the class
*
* @param operator
* @param from
* @param to
* @param id
* @param value
*/
public TransferSingle(final String operator, final String from, final String to, final long id, final long value) {
super(operator, from, to);
this.id = id;
this.value = value;
}
/**
* gettter function for id
*
* @return get the token id
*/
public long getId() {
return id;
}
/**
* getter function for value
*
* @return the value or amount of the token id
*/
public long getValue() {
return value;
}
}

View file

@ -0,0 +1,53 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155.models;
import com.owlike.genson.annotation.JsonProperty;
import org.hyperledger.fabric.contract.annotation.Property;
public class URI {
@Property()
@JsonProperty("id")
private long id;
@Property()
@JsonProperty("value")
private long value;
/** Default constructor */
public URI() {
super();
}
/**
* Constructor of the class
*
* @param id
* @param value
*/
public URI(final long id, final long value) {
super();
this.id = id;
this.value = value;
}
/**
* getter function for the id
*
* @return token id
*/
public long getId() {
return id;
}
/**
* getter function for the value
*
* @return
*/
public long getValue() {
return value;
}
}

View file

@ -0,0 +1,14 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155.utils;
public final class ContractUtility {
private ContractUtility() {
}
public static boolean stringIsNullOrEmpty(final String string) {
return string == null || string.isEmpty();
}
}

View file

@ -0,0 +1,151 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155;
import org.hyperledger.fabric.contract.ClientIdentity;
import org.hyperledger.fabric.shim.ChaincodeStub;
import org.hyperledger.fabric.shim.ledger.CompositeKey;
import org.hyperledger.fabric.shim.ledger.KeyValue;
import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static org.hyperledger.fabric.samples.erc1155.Constants.TOKEN_NAME;
import static org.hyperledger.fabric.samples.erc1155.Constants.TOKEN_SYMBOL;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class CommonUtils {
/** Default constructor */
public CommonUtils() {
super();
// TODO Auto-generated constructor stub
}
/**
* Set token name and symbol
*
* @param stub
*/
protected void setTokenNameAndSysmbol(final ChaincodeStub stub) {
when(stub.getStringState(ContractConstants.NAME_KEY.getValue())).thenReturn(TOKEN_NAME);
when(stub.getStringState(ContractConstants.SYMBOL_KEY.getValue())).thenReturn(TOKEN_SYMBOL);
}
/**
* Set MSPID
*
* @param ci
*/
protected void setOrg1MspId(final ClientIdentity ci) {
when(ci.getMSPID()).thenReturn("Org1MSP");
}
/**
* Set org2 msp id
*
* @param ci
*/
protected void setOrg2MspId(final ClientIdentity ci) {
when(ci.getMSPID()).thenReturn("Org2MSP");
}
/**
* Create composite key
*
* @param stub
* @param returnValue
* @param firstKey
* @param keys
* @return
*/
protected CompositeKey createCompositeKey(
final ChaincodeStub stub,
final String returnValue,
final ContractConstants firstKey,
final String... keys) {
CompositeKey ck = mock(CompositeKey.class);
StringBuilder keyCombined = new StringBuilder();
keyCombined.append(ContractConstants.BALANCE_PREFIX.getValue());
for (String key : keys) {
keyCombined.append(key);
}
when(stub.createCompositeKey(firstKey.getValue(), keys)).thenReturn(ck);
when(ck.toString()).thenReturn(keyCombined.toString());
when(stub.getStringState(ck.toString())).thenReturn(returnValue);
return ck;
}
/**
* Set iterator for the key
*
* @param stub
* @param firstKey
* @param keys
* @param values
*/
protected void setResultsIterator(
final ChaincodeStub stub,
final ContractConstants firstKey,
final String[] keys,
final MockKeyValue... values) {
List<KeyValue> list = new ArrayList<KeyValue>();
for (MockKeyValue value : values) {
list.add(value);
}
when(stub.getStateByPartialCompositeKey(firstKey.getValue(), keys))
.thenReturn(new MockAssetResultsIterator(list));
}
final class MockKeyValue implements KeyValue {
private final String key;
private final String value;
MockKeyValue(final String key, final String value) {
super();
this.key = key;
this.value = value;
}
@Override
public String getKey() {
return this.key;
}
@Override
public String getStringValue() {
return this.value;
}
@Override
public byte[] getValue() {
return this.value.getBytes();
}
}
final class MockAssetResultsIterator implements QueryResultsIterator<KeyValue> {
private final List<KeyValue> assetList;
MockAssetResultsIterator(final List<KeyValue> list) {
super();
this.assetList = list;
}
@Override
public Iterator<KeyValue> iterator() {
return assetList.iterator();
}
@Override
public void close() throws Exception {
// do nothing
}
}
}

View file

@ -0,0 +1,25 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.erc1155;
public final class Constants {
/** Default constructor */
private Constants() {
super();
}
public static final String ORG1_USER_ID =
"x509::CN=User0@org1.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";
public static final String ORG2_USER_ID =
"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";
public static final String ORG3_USER_ID =
"x509::CN=User1@org3.example.com, L=San Francisco, ST=California,"
+ " C=US::CN=ca.org3.example.com, O=org3.example.com, L=San Francisco, ST=California, C=US";
public static final String TOKEN_NAME = "AirlineToke";
public static final String TOKEN_SYMBOL = "ART";
}

View file

View file

0
token-erc-721/chaincode-java/gradle/wrapper/gradle-wrapper.jar vendored Normal file → Executable file
View file

View file