From 628c77bca315aa277cc51886c3743379208d350a Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Wed, 8 Nov 2017 21:10:25 -0800 Subject: [PATCH 01/16] Support for generating Java source from Truffle .json contract files. --- .../codegen/FunctionWrapperGenerator.java | 69 + .../codegen/SolidityFunctionWrapper.java | 53 +- .../SolidityFunctionWrapperGenerator.java | 61 +- .../TruffleJsonFunctionWrapperGenerator.java | 480 ++++++ .../web3j/codegen/ContractJsonParseTest.java | 68 + .../SolidityFunctionWrapperGeneratorTest.java | 1 - ...uffleJsonFunctionWrapperGeneratorTest.java | 101 ++ .../resources/truffle/MetaCoin/ConvertLib.sol | 8 + .../resources/truffle/MetaCoin/MetaCoin.sol | 34 + .../MetaCoin/build/contracts/ConvertLib.json | 281 ++++ .../MetaCoin/build/contracts/MetaCoin.json | 1431 +++++++++++++++++ .../main/java/org/web3j/console/Runner.java | 4 + .../core/methods/response/AbiDefinition.java | 40 + core/src/main/java/org/web3j/tx/Contract.java | 30 + .../main/java/org/web3j/utils/Strings.java | 4 + 15 files changed, 2612 insertions(+), 53 deletions(-) create mode 100644 codegen/src/main/java/org/web3j/codegen/FunctionWrapperGenerator.java create mode 100644 codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java create mode 100644 codegen/src/test/java/org/web3j/codegen/ContractJsonParseTest.java create mode 100644 codegen/src/test/java/org/web3j/codegen/TruffleJsonFunctionWrapperGeneratorTest.java create mode 100644 codegen/src/test/resources/truffle/MetaCoin/ConvertLib.sol create mode 100644 codegen/src/test/resources/truffle/MetaCoin/MetaCoin.sol create mode 100644 codegen/src/test/resources/truffle/MetaCoin/build/contracts/ConvertLib.json create mode 100644 codegen/src/test/resources/truffle/MetaCoin/build/contracts/MetaCoin.json diff --git a/codegen/src/main/java/org/web3j/codegen/FunctionWrapperGenerator.java b/codegen/src/main/java/org/web3j/codegen/FunctionWrapperGenerator.java new file mode 100644 index 0000000000..4314f2e2a6 --- /dev/null +++ b/codegen/src/main/java/org/web3j/codegen/FunctionWrapperGenerator.java @@ -0,0 +1,69 @@ +package org.web3j.codegen; + +import java.io.File; + +import static org.web3j.codegen.Console.exitError; + +/** + * Abstract base class for the concrete function wrapper generators. + */ +abstract class FunctionWrapperGenerator { + + static final String JAVA_TYPES_ARG = "--javaTypes"; + static final String SOLIDITY_TYPES_ARG = "--solidityTypes"; + + final File destinationDirLocation; + final String basePackageName; + final boolean useJavaNativeTypes; + + FunctionWrapperGenerator( + String destinationDirLocation, + String basePackageName, + boolean useJavaNativeTypes) { + + this.destinationDirLocation = new File(destinationDirLocation); + this.basePackageName = basePackageName; + this.useJavaNativeTypes = useJavaNativeTypes; + } + + static boolean useJavaNativeTypes(String argVal, String usageString) { + boolean useJavaNativeTypes = true; + if (SOLIDITY_TYPES_ARG.equals(argVal)) { + useJavaNativeTypes = false; + } else if (JAVA_TYPES_ARG.equals(argVal)) { + useJavaNativeTypes = true; + } else { + exitError(usageString); + } + return useJavaNativeTypes; + } + + static String parsePositionalArg(String[] args, int idx) { + if (args != null && args.length > idx) { + return args[idx]; + } else { + return ""; + } + } + + static String parseParameterArgument(String[] args, String... parameters) { + for (String parameter : parameters) { + for (int i = 0; i < args.length; i++) { + if (args[i].equals(parameter) + && i + 1 < args.length) { + String parameterValue = args[i + 1]; + if (!parameterValue.startsWith("-")) { + return parameterValue; + } + } + } + } + return ""; + } + + static String getFileNameNoExtension(String fileName) { + String[] splitName = fileName.split("\\.(?=[^.]*$)"); + return splitName[0]; + } + +} diff --git a/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java b/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java index dd5ac96c81..1021ca6bb5 100644 --- a/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java +++ b/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java @@ -5,7 +5,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; import javax.lang.model.element.Modifier; @@ -81,10 +83,21 @@ public SolidityFunctionWrapper(boolean useNativeJavaTypes) { this.useNativeJavaTypes = useNativeJavaTypes; } + @SuppressWarnings("unchecked") public void generateJavaFiles( String contractName, String bin, String abi, String destinationDir, String basePackageName) throws IOException, ClassNotFoundException { + generateJavaFiles(contractName, bin, + loadContractDefinition(abi), + destinationDir, basePackageName, + null); + } + + void generateJavaFiles( + String contractName, String bin, List abi, String destinationDir, + String basePackageName, Map addresses) + throws IOException, ClassNotFoundException { String className = Strings.capitaliseFirstLetter(contractName); TypeSpec.Builder classBuilder = createClassBuilder(className, bin); @@ -92,13 +105,51 @@ public void generateJavaFiles( classBuilder.addMethod(buildConstructor(Credentials.class, CREDENTIALS)); classBuilder.addMethod(buildConstructor(TransactionManager.class, TRANSACTION_MANAGER)); classBuilder.addMethods( - buildFunctionDefinitions(className, classBuilder, loadContractDefinition(abi))); + buildFunctionDefinitions(className, classBuilder, abi)); classBuilder.addMethod(buildLoad(className, Credentials.class, CREDENTIALS)); classBuilder.addMethod(buildLoad(className, TransactionManager.class, TRANSACTION_MANAGER)); + addAddressesSupport(classBuilder, addresses); + write(basePackageName, classBuilder.build(), destinationDir); } + private void addAddressesSupport(TypeSpec.Builder classBuilder, Map addresses) { + if (addresses != null) { + + ClassName stringType = ClassName.get(String.class); + ClassName mapType = ClassName.get(HashMap.class); + TypeName mapStringString = ParameterizedTypeName.get(mapType, stringType, stringType); + FieldSpec addressesStaticField = FieldSpec + .builder(mapStringString, "_addresses", + Modifier.PROTECTED, Modifier.STATIC, Modifier.FINAL) + .build(); + classBuilder.addField(addressesStaticField); + + final CodeBlock.Builder staticInit = CodeBlock.builder(); + staticInit.addStatement("_addresses = new HashMap<>()"); + addresses.forEach((k, v) -> + staticInit.addStatement(String.format("_addresses.put(\"%1s\", \"%2s\")", k, v)) + ); + classBuilder.addStaticBlock(staticInit.build()); + + // See org.web3j.tx.Contract#getStaticDeployedAddress(String) + MethodSpec getAddress = MethodSpec + .methodBuilder("getStaticDeployedAddress") + .addModifiers(Modifier.PROTECTED) + .returns(stringType) + .addParameter(stringType, "networkId") + .addCode(CodeBlock + .builder() + .addStatement("return _addresses.get(networkId)") + .build()) + .build(); + classBuilder.addMethod(getAddress); + + } + } + + private TypeSpec.Builder createClassBuilder(String className, String binary) { String javadoc = CODEGEN_WARNING + getWeb3jVersion(); diff --git a/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapperGenerator.java b/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapperGenerator.java index cd0ec1d95d..7716578ecc 100644 --- a/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapperGenerator.java +++ b/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapperGenerator.java @@ -18,7 +18,7 @@ /** * Java wrapper source code generator for Solidity ABI format. */ -public class SolidityFunctionWrapperGenerator { +public class SolidityFunctionWrapperGenerator extends FunctionWrapperGenerator { private static final String USAGE = "solidity generate " + "[--javaTypes|--solidityTypes] " @@ -26,14 +26,8 @@ public class SolidityFunctionWrapperGenerator { + "-p|--package " + "-o|--output "; - static final String JAVA_TYPES_ARG = "--javaTypes"; - static final String SOLIDITY_TYPES_ARG = "--solidityTypes"; - - private String binaryFileLocation; - private String absFileLocation; - private File destinationDirLocation; - private String basePackageName; - private boolean useJavaNativeTypes; + private final String binaryFileLocation; + private final String absFileLocation; private SolidityFunctionWrapperGenerator( String binaryFileLocation, @@ -42,11 +36,9 @@ private SolidityFunctionWrapperGenerator( String basePackageName, boolean useJavaNativeTypes) { + super(destinationDirLocation, basePackageName, useJavaNativeTypes); this.binaryFileLocation = binaryFileLocation; this.absFileLocation = absFileLocation; - this.destinationDirLocation = new File(destinationDirLocation); - this.basePackageName = basePackageName; - this.useJavaNativeTypes = useJavaNativeTypes; } public static void run(String[] args) throws Exception { @@ -72,14 +64,7 @@ public static void main(String[] args) throws Exception { exitError(USAGE); } - boolean useJavaNativeTypes = true; - if (fullArgs[0].equals(SOLIDITY_TYPES_ARG)) { - useJavaNativeTypes = false; - } else if (fullArgs[0].equals(JAVA_TYPES_ARG)) { - useJavaNativeTypes = true; - } else { - exitError(USAGE); - } + boolean useJavaNativeTypes = useJavaNativeTypes(fullArgs[0], USAGE); String binaryFileLocation = parsePositionalArg(fullArgs, 1); String absFileLocation = parsePositionalArg(fullArgs, 2); @@ -102,27 +87,11 @@ public static void main(String[] args) throws Exception { .generate(); } - private static String parsePositionalArg(String[] args, int idx) { - if (args != null && args.length > idx) { - return args[idx]; - } else { - return ""; - } - } - - private static String parseParameterArgument(String[] args, String... parameters) { - for (String parameter : parameters) { - for (int i = 0; i < args.length; i++) { - if (args[i].equals(parameter) - && i + 1 < args.length) { - String parameterValue = args[i + 1]; - if (!parameterValue.startsWith("-")) { - return parameterValue; - } - } - } - } - return ""; + static List loadContractDefinition(File absFile) + throws IOException { + ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper(); + AbiDefinition[] abiDefinition = objectMapper.readValue(absFile, AbiDefinition[].class); + return Arrays.asList(abiDefinition); } private void generate() throws IOException, ClassNotFoundException { @@ -157,15 +126,5 @@ private void generate() throws IOException, ClassNotFoundException { } } - private static List loadContractDefinition(File absFile) - throws IOException { - ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper(); - AbiDefinition[] abiDefinition = objectMapper.readValue(absFile, AbiDefinition[].class); - return Arrays.asList(abiDefinition); - } - static String getFileNameNoExtension(String fileName) { - String[] splitName = fileName.split("\\.(?=[^.]*$)"); - return splitName[0]; - } } \ No newline at end of file diff --git a/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java b/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java new file mode 100644 index 0000000000..bbeba6d832 --- /dev/null +++ b/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java @@ -0,0 +1,480 @@ +package org.web3j.codegen; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.web3j.protocol.ObjectMapperFactory; +import org.web3j.protocol.core.methods.response.AbiDefinition; +import org.web3j.utils.Strings; + +import static org.web3j.codegen.Console.exitError; +import static org.web3j.utils.Collection.tail; + +/** + * Java wrapper source code generator for Truffle JSON format. Truffle embeds the Solidity ABI + * formatted JSON in its own format. That format also gives access to the binary code. It also + * contains information about deployment addresses. This should make integration with Truffle + * easier. + */ +public class TruffleJsonFunctionWrapperGenerator extends FunctionWrapperGenerator { + + private static final String USAGE = "truffle generate " + + "[--javaTypes|--solidityTypes] " + + ".json " + + "-p|--package " + + "-o|--output "; + + + private String jsonFileLocation; + + private TruffleJsonFunctionWrapperGenerator( + String jsonFileLocation, + String destinationDirLocation, + String basePackageName, + boolean useJavaNativeTypes) { + + super(destinationDirLocation, basePackageName, useJavaNativeTypes); + this.jsonFileLocation = jsonFileLocation; + } + + public static void run(String[] args) throws Exception { + if (args.length < 1 || !"generate".equals(args[0])) { + exitError(USAGE); + } else { + main(tail(args)); + } + } + + public static void main(String[] args) throws Exception { + + String[] fullArgs; + if (args.length == 5) { + fullArgs = new String[args.length + 1]; + fullArgs[0] = JAVA_TYPES_ARG; + System.arraycopy(args, 0, fullArgs, 1, args.length); + } else { + fullArgs = args; + } + + if (fullArgs.length != 6) { + exitError(USAGE); + } + + boolean useJavaNativeTypes = useJavaNativeTypes(fullArgs[0], USAGE); + + String jsonFileLocation = parsePositionalArg(fullArgs, 1); + String destinationDirLocation = parseParameterArgument(fullArgs, "-o", "--outputDir"); + String basePackageName = parseParameterArgument(fullArgs, "-p", "--package"); + + if (Strings.isEmpty(jsonFileLocation) + || Strings.isEmpty(destinationDirLocation) + || Strings.isEmpty(basePackageName)) { + exitError(USAGE); + } + + new TruffleJsonFunctionWrapperGenerator( + jsonFileLocation, + destinationDirLocation, + basePackageName, + useJavaNativeTypes) + .generate(); + } + + static Contract loadContractDefinition(File jsonFile) + throws IOException { + ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper(); + return objectMapper.readValue(jsonFile, Contract.class); + } + + @SuppressWarnings("unchecked") + private void generate() throws IOException, ClassNotFoundException { + + File truffleJsonFile = new File(jsonFileLocation); + if (!truffleJsonFile.exists() || !truffleJsonFile.canRead()) { + exitError("Invalid input json file specified: " + jsonFileLocation); + } + + String fileName = truffleJsonFile.getName(); + String contractName = getFileNameNoExtension(fileName); + + Contract c = loadContractDefinition(truffleJsonFile); + if (c == null) { + exitError("Unable to parse input json file"); + } else { + String className = Strings.capitaliseFirstLetter(contractName); + System.out.printf("Generating " + basePackageName + "." + className + " ... "); + Map addresses; + if (c.networks != null && !c.networks.isEmpty()) { + addresses = c.networks.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, e -> e.getValue().getAddress() + )); + } else { + addresses = Collections.EMPTY_MAP; + } + new SolidityFunctionWrapper(useJavaNativeTypes) + .generateJavaFiles(contractName, + c.bytecode, + c.abi, + destinationDirLocation.toString(), + basePackageName, + addresses); + System.out.println("File written to " + destinationDirLocation.toString() + "\n"); + } + } + + /** + * Truffle Contract

Describes a contract exported by and consumable by Truffle, which may + * include information about deployed instances on networks.

+ */ + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonPropertyOrder({ + "contractName", + "abi", + "bytecode", + "deployedBytecode", + "sourceMap", + "deployedSourceMap", + "source", + "sourcePath", + "ast", + "compiler", + "networks", + "schemaVersion", + "updatedAt" + }) + public static class Contract { + + @JsonProperty("contractName") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "^[a-zA-Z_][a-zA-Z0-9_]*$") + public String contractName; + @JsonProperty(value = "abi", required = true) + public List abi; + @JsonProperty("bytecode") + @JsonFormat(shape = JsonFormat.Shape.STRING, + pattern = "^0x0$|^0x([a-fA-F0-9]{2}|__.{38})+$") + public String bytecode; + @JsonProperty("deployedBytecode") + @JsonFormat(shape = JsonFormat.Shape.STRING, + pattern = "^0x0$|^0x([a-fA-F0-9]{2}|__.{38})+$") + public String deployedBytecode; + @JsonProperty("sourceMap") + public String sourceMap; + @JsonProperty("deployedSourceMap") + public String deployedSourceMap; + @JsonProperty("source") + public String source; + @JsonProperty("sourcePath") + public String sourcePath; + @JsonProperty("ast") + public JsonNode ast; + @JsonProperty("compiler") + public Compiler compiler; + @JsonProperty("networks") + public Map networks; + @JsonProperty("schemaVersion") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "[0-9]+\\.[0-9]+\\.[0-9]+") + public String schemaVersion; + @JsonProperty("updatedAt") + @JsonFormat(shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "GMT") + public Date updatedAt; + + /** + * No args constructor for use in serialization. + */ + public Contract() { + } + + /** + * Default constructor. + */ + public Contract(String contractName, List abi, String bytecode, + String deployedBytecode, + String sourceMap, String deployedSourceMap, String source, String sourcePath, + JsonNode ast, + Compiler compiler, Map networks, String schemaVersion, + Date updatedAt) { + super(); + this.contractName = contractName; + this.abi = abi; + this.bytecode = bytecode; + this.deployedBytecode = deployedBytecode; + this.sourceMap = sourceMap; + this.deployedSourceMap = deployedSourceMap; + this.source = source; + this.sourcePath = sourcePath; + this.ast = ast; + this.compiler = compiler; + this.networks = networks; + this.schemaVersion = schemaVersion; + this.updatedAt = updatedAt; + } + + public Contract withContractName(String contractName) { + this.contractName = contractName; + return this; + } + + public Contract withAbi(List abi) { + this.abi = abi; + return this; + } + + public Contract withBytecode(String bytecode) { + this.bytecode = bytecode; + return this; + } + + public Contract withDeployedBytecode(String deployedBytecode) { + this.deployedBytecode = deployedBytecode; + return this; + } + + public Contract withSourceMap(String sourceMap) { + this.sourceMap = sourceMap; + return this; + } + + public Contract withDeployedSourceMap(String deployedSourceMap) { + this.deployedSourceMap = deployedSourceMap; + return this; + } + + public Contract withSource(String source) { + this.source = source; + return this; + } + + public Contract withSourcePath(String sourcePath) { + this.sourcePath = sourcePath; + return this; + } + + public Contract withAst(JsonNode ast) { + this.ast = ast; + return this; + } + + public Contract withCompiler(Compiler compiler) { + this.compiler = compiler; + return this; + } + + public Contract withNetworks(Map networks) { + this.networks = networks; + return this; + } + + public Contract withSchemaVersion(String schemaVersion) { + this.schemaVersion = schemaVersion; + return this; + } + + public Contract withUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + return this; + } + + public String getContractName() { + return contractName; + } + + public List getAbi() { + return abi; + } + + public String getBytecode() { + return bytecode; + } + + public String getDeployedBytecode() { + return deployedBytecode; + } + + public String getSourceMap() { + return sourceMap; + } + + public String getDeployedSourceMap() { + return deployedSourceMap; + } + + public String getSource() { + return source; + } + + public String getSourcePath() { + return sourcePath; + } + + public JsonNode getAst() { + return ast; + } + + public Compiler getCompiler() { + return compiler; + } + + public Map getNetworks() { + return networks; + } + + public String getSchemaVersion() { + return schemaVersion; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public NetworkInfo getNetwork(String networkId) { + return networks.get(networkId); + } + + public String getAddress(String networkId) { + NetworkInfo network = getNetwork(networkId); + return network == null ? null : network.getAddress(); + } + + /** + * Convenience method to get the deployed address of the contract. + * + * @param network the contract's address on this Ethereum network + * @return the contract's address or null if there isn't one known. + */ + public String getAddress(Network network) { + return getAddress(Long.toString(network.id)); + } + + enum Network { + olympic(0), mainnet(1), morden(2), ropsten(3), rinkeby(4), kovan(42); + + public final long id; + + Network(long id) { + this.id = id; + } + } + + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonPropertyOrder({ + "name", + "version" + }) + public static class Compiler { + + @JsonProperty("name") + public String name; + @JsonProperty("version") + public String version; + @JsonIgnore + private Map additionalProperties = new HashMap(); + + /** + * No args constructor for use in serialization. + */ + public Compiler() { + } + + /** + * Default constructor. + */ + public Compiler(String name, String version) { + super(); + this.name = name; + this.version = version; + } + + public Compiler withName(String name) { + this.name = name; + return this; + } + + public Compiler withVersion(String version) { + this.version = version; + return this; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, JsonNode value) { + this.additionalProperties.put(name, value); + } + + public Compiler withAdditionalProperty(String name, JsonNode value) { + this.additionalProperties.put(name, value); + return this; + } + + } + + + // For now we just ignore "events" + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonPropertyOrder({ + "events", + "links", + "address" + }) + public static class NetworkInfo { + + @JsonProperty("events") + public Map events; + @JsonProperty("links") + public Map links; + @JsonProperty("address") + public String address; + + /** + * No args constructor for use in serialization. + */ + public NetworkInfo() { + } + + public NetworkInfo(Map events, Map links, + String address) { + super(); + this.events = events; + this.links = links; + this.address = address; + } + + public NetworkInfo withAddress(String address) { + this.address = address; + return this; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + +} \ No newline at end of file diff --git a/codegen/src/test/java/org/web3j/codegen/ContractJsonParseTest.java b/codegen/src/test/java/org/web3j/codegen/ContractJsonParseTest.java new file mode 100644 index 0000000000..e345c78496 --- /dev/null +++ b/codegen/src/test/java/org/web3j/codegen/ContractJsonParseTest.java @@ -0,0 +1,68 @@ +package org.web3j.codegen; + +import java.io.File; +import java.net.URL; + +import org.junit.Before; +import org.junit.Test; + +import org.web3j.protocol.core.methods.response.AbiDefinition; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.web3j.codegen.TruffleJsonFunctionWrapperGenerator.Contract; +import static org.web3j.codegen.TruffleJsonFunctionWrapperGenerator.loadContractDefinition; + +/** + * Test that we can parse Truffle Contract from JSON file. + */ +public class ContractJsonParseTest { + + static final String BUILD_CONTRACTS = "build" + File.separator + "contracts"; + private String contractBaseDir; + + static String jsonFileLocation(String baseDir, + String contractName, String inputFileName) { + return baseDir + File.separator + contractName + File.separator + BUILD_CONTRACTS + + File.separator + inputFileName + ".json"; + } + + @SuppressWarnings("SameParameterValue") + static Contract parseContractJson(String baseDir, + String contractName, String inputFileName) + throws Exception { + + String fileLocation = jsonFileLocation(baseDir, contractName, inputFileName); + return loadContractDefinition(new File(fileLocation)); + } + + @Before + public void setUp() throws Exception { + URL url = SolidityFunctionWrapperGeneratorTest.class.getClass().getResource("/truffle"); + contractBaseDir = url.getPath(); + } + + @Test + public void testParseMetaCoin() throws Exception { + Contract mc = parseContractJson(contractBaseDir, "MetaCoin", "MetaCoin"); + + assertEquals("Unexpected contract name", "MetaCoin", mc.getContractName()); + } + + @Test + public void testParseConvertLib() throws Exception { + Contract mc = parseContractJson(contractBaseDir, "MetaCoin", "ConvertLib"); + + assertEquals("Unexpected contract name", "ConvertLib", mc.getContractName()); + assertEquals("Unexpected number of functions", 1, mc.abi.size()); + AbiDefinition abi = mc.abi.get(0); + assertEquals("Unexpected function name", "convert", abi.getName()); + assertTrue("Expected function to be 'constant'", abi.isConstant()); + assertFalse("Expected function to not be 'payable'", abi.isPayable()); + assertEquals("Expected abi to represent a function", "function", abi.getType()); + assertEquals("Expected the 'pure' for the state mutability setting", "pure", + abi.getStateMutability()); + + } +} \ No newline at end of file diff --git a/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java b/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java index d899c903cb..966a192ea9 100644 --- a/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java +++ b/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.net.URL; import java.util.Arrays; -import java.util.Collections; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; diff --git a/codegen/src/test/java/org/web3j/codegen/TruffleJsonFunctionWrapperGeneratorTest.java b/codegen/src/test/java/org/web3j/codegen/TruffleJsonFunctionWrapperGeneratorTest.java new file mode 100644 index 0000000000..008d36dd67 --- /dev/null +++ b/codegen/src/test/java/org/web3j/codegen/TruffleJsonFunctionWrapperGeneratorTest.java @@ -0,0 +1,101 @@ +package org.web3j.codegen; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; + +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +import org.junit.Before; +import org.junit.Test; + +import org.web3j.TempFileProvider; +import org.web3j.utils.Strings; + +import static org.junit.Assert.assertTrue; +import static org.web3j.codegen.FunctionWrapperGenerator.JAVA_TYPES_ARG; +import static org.web3j.codegen.FunctionWrapperGenerator.SOLIDITY_TYPES_ARG; + +public class TruffleJsonFunctionWrapperGeneratorTest extends TempFileProvider { + + private static final String PackageName = "org.web3j.unittests.truffle.java"; + + private String contractBaseDir; + + private static void verifyGeneratedCode(String sourceFile) throws IOException { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + + try (StandardJavaFileManager fileManager = + compiler.getStandardFileManager(diagnostics, null, null)) { + Iterable compilationUnits = fileManager + .getJavaFileObjectsFromStrings(Collections.singletonList(sourceFile)); + JavaCompiler.CompilationTask task = compiler.getTask( + null, fileManager, diagnostics, null, null, compilationUnits); + assertTrue("Generated contract contains compile time error", task.call()); + } + } + + @Before + public void setUp() throws Exception { + super.setUp(); + + URL url = SolidityFunctionWrapperGeneratorTest.class.getClass().getResource("/truffle"); + contractBaseDir = url.getPath(); + } + + @Test + public void testLibGeneration() throws Exception { + testCodeGenerationJvmTypes("MetaCoin", "ConvertLib"); + testCodeGenerationSolidtyTypes("MetaCoin", "ConvertLib"); + } + + @Test + public void testContractGeneration() throws Exception { + testCodeGenerationJvmTypes("MetaCoin", "MetaCoin"); + testCodeGenerationSolidtyTypes("MetaCoin", "MetaCoin"); + } + + @SuppressWarnings("SameParameterValue") + private void testCodeGenerationJvmTypes( + String contractName, String inputFileName) throws Exception { + + testCodeGeneration( + contractName, inputFileName, PackageName, JAVA_TYPES_ARG); + + } + + @SuppressWarnings("SameParameterValue") + private void testCodeGenerationSolidtyTypes( + String contractName, String inputFileName) throws Exception { + + testCodeGeneration( + contractName, inputFileName, PackageName, SOLIDITY_TYPES_ARG); + + } + + private void testCodeGeneration( + String contractName, String inputFileName, String packageName, String types) + throws Exception { + + TruffleJsonFunctionWrapperGenerator.main(Arrays.asList( + types, + ContractJsonParseTest + .jsonFileLocation(contractBaseDir, contractName, inputFileName), + "-p", packageName, + "-o", tempDirPath + ).toArray(new String[0])); + + verifyGeneratedCode(tempDirPath + File.separator + + packageName.replace('.', File.separatorChar) + File.separator + + Strings.capitaliseFirstLetter(inputFileName) + ".java"); + } + + +} \ No newline at end of file diff --git a/codegen/src/test/resources/truffle/MetaCoin/ConvertLib.sol b/codegen/src/test/resources/truffle/MetaCoin/ConvertLib.sol new file mode 100644 index 0000000000..99f004dfe2 --- /dev/null +++ b/codegen/src/test/resources/truffle/MetaCoin/ConvertLib.sol @@ -0,0 +1,8 @@ +pragma solidity ^0.4.2; + +library ConvertLib{ + function convert(uint amount,uint conversionRate) pure public returns (uint convertedAmount) + { + return amount * conversionRate; + } +} diff --git a/codegen/src/test/resources/truffle/MetaCoin/MetaCoin.sol b/codegen/src/test/resources/truffle/MetaCoin/MetaCoin.sol new file mode 100644 index 0000000000..2658d0466f --- /dev/null +++ b/codegen/src/test/resources/truffle/MetaCoin/MetaCoin.sol @@ -0,0 +1,34 @@ +pragma solidity ^0.4.2; + +import "./ConvertLib.sol"; + +// This is just a simple example of a coin-like contract. +// It is not standards compatible and cannot be expected to talk to other +// coin/token contracts. If you want to create a standards-compliant +// token, see: https://github.com/ConsenSys/Tokens. Cheers! + +contract MetaCoin { + mapping (address => uint) balances; + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + + function MetaCoin() public { + balances[tx.origin] = 10000; + } + + function sendCoin(address receiver, uint amount) public returns(bool sufficient) { + if (balances[msg.sender] < amount) return false; + balances[msg.sender] -= amount; + balances[receiver] += amount; + Transfer(msg.sender, receiver, amount); + return true; + } + + function getBalanceInEth(address addr) view public returns(uint){ + return ConvertLib.convert(getBalance(addr),2); + } + + function getBalance(address addr) view public returns(uint) { + return balances[addr]; + } +} diff --git a/codegen/src/test/resources/truffle/MetaCoin/build/contracts/ConvertLib.json b/codegen/src/test/resources/truffle/MetaCoin/build/contracts/ConvertLib.json new file mode 100644 index 0000000000..a54254200b --- /dev/null +++ b/codegen/src/test/resources/truffle/MetaCoin/build/contracts/ConvertLib.json @@ -0,0 +1,281 @@ +{ + "contractName": "ConvertLib", + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "amount", + "type": "uint256" + }, + { + "name": "conversionRate", + "type": "uint256" + } + ], + "name": "convert", + "outputs": [ + { + "name": "convertedAmount", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + } + ], + "bytecode": "0x6060604052341561000f57600080fd5b60b08061001d6000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806396e4ee3d146044575b600080fd5b606160048080359060200190919080359060200190919050506077565b6040518082815260200191505060405180910390f35b60008183029050929150505600a165627a7a72305820fc2416f7929b800f6d4af1a1e6d2a8d7d8ac81998f8c072a99314c7bf5c2a5f80029", + "deployedBytecode": "0x606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806396e4ee3d146044575b600080fd5b606160048080359060200190919080359060200190919050506077565b6040518082815260200191505060405180910390f35b60008183029050929150505600a165627a7a72305820fc2416f7929b800f6d4af1a1e6d2a8d7d8ac81998f8c072a99314c7bf5c2a5f80029", + "sourceMap": "25:155:0:-;;;;;;;;;;;;;;;;;", + "deployedSourceMap": "25:155:0:-;;;;;;;;;;;;;;;;;;;;;;;;46:132;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;117:20;160:14;151:6;:23;144:30;;46:132;;;;:::o", + "source": "pragma solidity ^0.4.2;\n\nlibrary ConvertLib{\n\tfunction convert(uint amount,uint conversionRate) pure public returns (uint convertedAmount)\n\t{\n\t\treturn amount * conversionRate;\n\t}\n}\n", + "sourcePath": "/Users/ezra/Developer/blockchain/truffle_webpack/contracts/ConvertLib.sol", + "ast": { + "attributes": { + "absolutePath": "/Users/ezra/Developer/blockchain/truffle_webpack/contracts/ConvertLib.sol", + "exportedSymbols": { + "ConvertLib": [ + 16 + ] + } + }, + "children": [ + { + "attributes": { + "literals": [ + "solidity", + "^", + "0.4", + ".2" + ] + }, + "id": 1, + "name": "PragmaDirective", + "src": "0:23:0" + }, + { + "attributes": { + "baseContracts": [ + null + ], + "contractDependencies": [ + null + ], + "contractKind": "library", + "documentation": null, + "fullyImplemented": true, + "linearizedBaseContracts": [ + 16 + ], + "name": "ConvertLib", + "scope": 17 + }, + "children": [ + { + "attributes": { + "constant": true, + "implemented": true, + "isConstructor": false, + "modifiers": [ + null + ], + "name": "convert", + "payable": false, + "scope": 16, + "stateMutability": "pure", + "superFunction": null, + "visibility": "public" + }, + "children": [ + { + "children": [ + { + "attributes": { + "constant": false, + "name": "amount", + "scope": 15, + "stateVariable": false, + "storageLocation": "default", + "type": "uint256", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint", + "type": "uint256" + }, + "id": 2, + "name": "ElementaryTypeName", + "src": "63:4:0" + } + ], + "id": 3, + "name": "VariableDeclaration", + "src": "63:11:0" + }, + { + "attributes": { + "constant": false, + "name": "conversionRate", + "scope": 15, + "stateVariable": false, + "storageLocation": "default", + "type": "uint256", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint", + "type": "uint256" + }, + "id": 4, + "name": "ElementaryTypeName", + "src": "75:4:0" + } + ], + "id": 5, + "name": "VariableDeclaration", + "src": "75:19:0" + } + ], + "id": 6, + "name": "ParameterList", + "src": "62:33:0" + }, + { + "children": [ + { + "attributes": { + "constant": false, + "name": "convertedAmount", + "scope": 15, + "stateVariable": false, + "storageLocation": "default", + "type": "uint256", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint", + "type": "uint256" + }, + "id": 7, + "name": "ElementaryTypeName", + "src": "117:4:0" + } + ], + "id": 8, + "name": "VariableDeclaration", + "src": "117:20:0" + } + ], + "id": 9, + "name": "ParameterList", + "src": "116:22:0" + }, + { + "children": [ + { + "attributes": { + "functionReturnParameters": 9 + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "operator": "*", + "type": "uint256" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 3, + "type": "uint256", + "value": "amount" + }, + "id": 10, + "name": "Identifier", + "src": "151:6:0" + }, + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 5, + "type": "uint256", + "value": "conversionRate" + }, + "id": 11, + "name": "Identifier", + "src": "160:14:0" + } + ], + "id": 12, + "name": "BinaryOperation", + "src": "151:23:0" + } + ], + "id": 13, + "name": "Return", + "src": "144:30:0" + } + ], + "id": 14, + "name": "Block", + "src": "140:38:0" + } + ], + "id": 15, + "name": "FunctionDefinition", + "src": "46:132:0" + } + ], + "id": 16, + "name": "ContractDefinition", + "src": "25:155:0" + } + ], + "id": 17, + "name": "SourceUnit", + "src": "0:181:0" + }, + "compiler": { + "name": "solc", + "version": "0.4.18+commit.9cf6e910.Emscripten.clang" + }, + "networks": { + "4": { + "events": {}, + "links": {}, + "address": "0x14d00a701a2f0d4d65eebaf191f4f60bbaa1da36" + }, + "4447": { + "events": {}, + "links": {}, + "address": "0xcfeb869f69431e42cdb54a4f4f105c19c080a601" + } + }, + "schemaVersion": "1.0.1", + "updatedAt": "2017-11-07T20:44:02.301Z" +} \ No newline at end of file diff --git a/codegen/src/test/resources/truffle/MetaCoin/build/contracts/MetaCoin.json b/codegen/src/test/resources/truffle/MetaCoin/build/contracts/MetaCoin.json new file mode 100644 index 0000000000..8bed99e71e --- /dev/null +++ b/codegen/src/test/resources/truffle/MetaCoin/build/contracts/MetaCoin.json @@ -0,0 +1,1431 @@ +{ + "contractName": "MetaCoin", + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "addr", + "type": "address" + } + ], + "name": "getBalanceInEth", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "receiver", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "sendCoin", + "outputs": [ + { + "name": "sufficient", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "addr", + "type": "address" + } + ], + "name": "getBalance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } + ], + "bytecode": "0x6060604052341561000f57600080fd5b6127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506103c5806100636000396000f300606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680637bd703e81461005c57806390b98a11146100a9578063f8b2cb4f14610103575b600080fd5b341561006757600080fd5b610093600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610150565b6040518082815260200191505060405180910390f35b34156100b457600080fd5b6100e9600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101f8565b604051808215151515815260200191505060405180910390f35b341561010e57600080fd5b61013a600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610351565b6040518082815260200191505060405180910390f35b600073__ConvertLib____________________________6396e4ee3d61017584610351565b60026000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808381526020018281526020019250505060206040518083038186803b15156101d657600080fd5b6102c65a03f415156101e757600080fd5b505050604051805190509050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610249576000905061034b565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490509190505600a165627a7a72305820f791829bf0c920a6d43123cac9b9894757ddc838c17efbad6d56773d6e3dcf4c0029", + "deployedBytecode": "0x606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680637bd703e81461005c57806390b98a11146100a9578063f8b2cb4f14610103575b600080fd5b341561006757600080fd5b610093600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610150565b6040518082815260200191505060405180910390f35b34156100b457600080fd5b6100e9600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101f8565b604051808215151515815260200191505060405180910390f35b341561010e57600080fd5b61013a600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610351565b6040518082815260200191505060405180910390f35b600073__ConvertLib____________________________6396e4ee3d61017584610351565b60026000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808381526020018281526020019250505060206040518083038186803b15156101d657600080fd5b6102c65a03f415156101e757600080fd5b505050604051805190509050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610249576000905061034b565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490509190505600a165627a7a72305820f791829bf0c920a6d43123cac9b9894757ddc838c17efbad6d56773d6e3dcf4c0029", + "sourceMap": "315:675:1:-;;;452:62;;;;;;;;505:5;483:8;:19;492:9;483:19;;;;;;;;;;;;;;;:27;;;;315:675;;;;;;", + "deployedSourceMap": "315:675:1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;779:117;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;517:259;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;899:89;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;779:117;838:4;854:10;:18;873:16;884:4;873:10;:16::i;:::-;890:1;854:38;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;847:45;;779:117;;;:::o;517:259::-;581:15;629:6;606:8;:20;615:10;606:20;;;;;;;;;;;;;;;;:29;602:47;;;644:5;637:12;;;;602:47;677:6;653:8;:20;662:10;653:20;;;;;;;;;;;;;;;;:30;;;;;;;;;;;709:6;687:8;:18;696:8;687:18;;;;;;;;;;;;;;;;:28;;;;;;;;;;;740:8;719:38;;728:10;719:38;;;750:6;719:38;;;;;;;;;;;;;;;;;;768:4;761:11;;517:259;;;;;:::o;899:89::-;953:4;970:8;:14;979:4;970:14;;;;;;;;;;;;;;;;963:21;;899:89;;;:::o", + "source": "pragma solidity ^0.4.2;\n\nimport \"./ConvertLib.sol\";\n\n// This is just a simple example of a coin-like contract.\n// It is not standards compatible and cannot be expected to talk to other\n// coin/token contracts. If you want to create a standards-compliant\n// token, see: https://github.com/ConsenSys/Tokens. Cheers!\n\ncontract MetaCoin {\n\tmapping (address => uint) balances;\n\n\tevent Transfer(address indexed _from, address indexed _to, uint256 _value);\n\n\tfunction MetaCoin() public {\n\t\tbalances[tx.origin] = 10000;\n\t}\n\n\tfunction sendCoin(address receiver, uint amount) public returns(bool sufficient) {\n\t\tif (balances[msg.sender] < amount) return false;\n\t\tbalances[msg.sender] -= amount;\n\t\tbalances[receiver] += amount;\n\t\tTransfer(msg.sender, receiver, amount);\n\t\treturn true;\n\t}\n\n\tfunction getBalanceInEth(address addr) view public returns(uint){\n\t\treturn ConvertLib.convert(getBalance(addr),2);\n\t}\n\n\tfunction getBalance(address addr) view public returns(uint) {\n\t\treturn balances[addr];\n\t}\n}\n", + "sourcePath": "/Users/ezra/Developer/blockchain/truffle_webpack/contracts/MetaCoin.sol", + "ast": { + "attributes": { + "absolutePath": "/Users/ezra/Developer/blockchain/truffle_webpack/contracts/MetaCoin.sol", + "exportedSymbols": { + "MetaCoin": [ + 112 + ] + } + }, + "children": [ + { + "attributes": { + "literals": [ + "solidity", + "^", + "0.4", + ".2" + ] + }, + "id": 18, + "name": "PragmaDirective", + "src": "0:23:1" + }, + { + "attributes": { + "SourceUnit": 17, + "absolutePath": "/Users/ezra/Developer/blockchain/truffle_webpack/contracts/ConvertLib.sol", + "file": "./ConvertLib.sol", + "scope": 113, + "symbolAliases": [ + null + ], + "unitAlias": "" + }, + "id": 19, + "name": "ImportDirective", + "src": "25:26:1" + }, + { + "attributes": { + "baseContracts": [ + null + ], + "contractDependencies": [ + null + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "linearizedBaseContracts": [ + 112 + ], + "name": "MetaCoin", + "scope": 113 + }, + "children": [ + { + "attributes": { + "constant": false, + "name": "balances", + "scope": 112, + "stateVariable": true, + "storageLocation": "default", + "type": "mapping(address => uint256)", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "type": "mapping(address => uint256)" + }, + "children": [ + { + "attributes": { + "name": "address", + "type": "address" + }, + "id": 20, + "name": "ElementaryTypeName", + "src": "345:7:1" + }, + { + "attributes": { + "name": "uint", + "type": "uint256" + }, + "id": 21, + "name": "ElementaryTypeName", + "src": "356:4:1" + } + ], + "id": 22, + "name": "Mapping", + "src": "336:25:1" + } + ], + "id": 23, + "name": "VariableDeclaration", + "src": "336:34:1" + }, + { + "attributes": { + "anonymous": false, + "name": "Transfer" + }, + "children": [ + { + "children": [ + { + "attributes": { + "constant": false, + "indexed": true, + "name": "_from", + "scope": 31, + "stateVariable": false, + "storageLocation": "default", + "type": "address", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "address", + "type": "address" + }, + "id": 24, + "name": "ElementaryTypeName", + "src": "389:7:1" + } + ], + "id": 25, + "name": "VariableDeclaration", + "src": "389:21:1" + }, + { + "attributes": { + "constant": false, + "indexed": true, + "name": "_to", + "scope": 31, + "stateVariable": false, + "storageLocation": "default", + "type": "address", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "address", + "type": "address" + }, + "id": 26, + "name": "ElementaryTypeName", + "src": "412:7:1" + } + ], + "id": 27, + "name": "VariableDeclaration", + "src": "412:19:1" + }, + { + "attributes": { + "constant": false, + "indexed": false, + "name": "_value", + "scope": 31, + "stateVariable": false, + "storageLocation": "default", + "type": "uint256", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint256", + "type": "uint256" + }, + "id": 28, + "name": "ElementaryTypeName", + "src": "433:7:1" + } + ], + "id": 29, + "name": "VariableDeclaration", + "src": "433:14:1" + } + ], + "id": 30, + "name": "ParameterList", + "src": "388:60:1" + } + ], + "id": 31, + "name": "EventDefinition", + "src": "374:75:1" + }, + { + "attributes": { + "constant": false, + "implemented": true, + "isConstructor": true, + "modifiers": [ + null + ], + "name": "MetaCoin", + "payable": false, + "scope": 112, + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + "children": [ + { + "attributes": { + "parameters": [ + null + ] + }, + "children": [], + "id": 32, + "name": "ParameterList", + "src": "469:2:1" + }, + { + "attributes": { + "parameters": [ + null + ] + }, + "children": [], + "id": 33, + "name": "ParameterList", + "src": "479:0:1" + }, + { + "children": [ + { + "children": [ + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "operator": "=", + "type": "uint256" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "type": "uint256" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 23, + "type": "mapping(address => uint256)", + "value": "balances" + }, + "id": 34, + "name": "Identifier", + "src": "483:8:1" + }, + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "member_name": "origin", + "referencedDeclaration": null, + "type": "address" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 191, + "type": "tx", + "value": "tx" + }, + "id": 35, + "name": "Identifier", + "src": "492:2:1" + } + ], + "id": 36, + "name": "MemberAccess", + "src": "492:9:1" + } + ], + "id": 37, + "name": "IndexAccess", + "src": "483:19:1" + }, + { + "attributes": { + "argumentTypes": null, + "hexvalue": "3130303030", + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "subdenomination": null, + "token": "number", + "type": "int_const 10000", + "value": "10000" + }, + "id": 38, + "name": "Literal", + "src": "505:5:1" + } + ], + "id": 39, + "name": "Assignment", + "src": "483:27:1" + } + ], + "id": 40, + "name": "ExpressionStatement", + "src": "483:27:1" + } + ], + "id": 41, + "name": "Block", + "src": "479:35:1" + } + ], + "id": 42, + "name": "FunctionDefinition", + "src": "452:62:1" + }, + { + "attributes": { + "constant": false, + "implemented": true, + "isConstructor": false, + "modifiers": [ + null + ], + "name": "sendCoin", + "payable": false, + "scope": 112, + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + "children": [ + { + "children": [ + { + "attributes": { + "constant": false, + "name": "receiver", + "scope": 83, + "stateVariable": false, + "storageLocation": "default", + "type": "address", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "address", + "type": "address" + }, + "id": 43, + "name": "ElementaryTypeName", + "src": "535:7:1" + } + ], + "id": 44, + "name": "VariableDeclaration", + "src": "535:16:1" + }, + { + "attributes": { + "constant": false, + "name": "amount", + "scope": 83, + "stateVariable": false, + "storageLocation": "default", + "type": "uint256", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint", + "type": "uint256" + }, + "id": 45, + "name": "ElementaryTypeName", + "src": "553:4:1" + } + ], + "id": 46, + "name": "VariableDeclaration", + "src": "553:11:1" + } + ], + "id": 47, + "name": "ParameterList", + "src": "534:31:1" + }, + { + "children": [ + { + "attributes": { + "constant": false, + "name": "sufficient", + "scope": 83, + "stateVariable": false, + "storageLocation": "default", + "type": "bool", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "bool", + "type": "bool" + }, + "id": 48, + "name": "ElementaryTypeName", + "src": "581:4:1" + } + ], + "id": 49, + "name": "VariableDeclaration", + "src": "581:15:1" + } + ], + "id": 50, + "name": "ParameterList", + "src": "580:17:1" + }, + { + "children": [ + { + "attributes": { + "falseBody": null + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "operator": "<", + "type": "bool" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "type": "uint256" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 23, + "type": "mapping(address => uint256)", + "value": "balances" + }, + "id": 51, + "name": "Identifier", + "src": "606:8:1" + }, + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "member_name": "sender", + "referencedDeclaration": null, + "type": "address" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 181, + "type": "msg", + "value": "msg" + }, + "id": 52, + "name": "Identifier", + "src": "615:3:1" + } + ], + "id": 53, + "name": "MemberAccess", + "src": "615:10:1" + } + ], + "id": 54, + "name": "IndexAccess", + "src": "606:20:1" + }, + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 46, + "type": "uint256", + "value": "amount" + }, + "id": 55, + "name": "Identifier", + "src": "629:6:1" + } + ], + "id": 56, + "name": "BinaryOperation", + "src": "606:29:1" + }, + { + "attributes": { + "functionReturnParameters": 50 + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "hexvalue": "66616c7365", + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "subdenomination": null, + "token": "bool", + "type": "bool", + "value": "false" + }, + "id": 57, + "name": "Literal", + "src": "644:5:1" + } + ], + "id": 58, + "name": "Return", + "src": "637:12:1" + } + ], + "id": 59, + "name": "IfStatement", + "src": "602:47:1" + }, + { + "children": [ + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "operator": "-=", + "type": "uint256" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "type": "uint256" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 23, + "type": "mapping(address => uint256)", + "value": "balances" + }, + "id": 60, + "name": "Identifier", + "src": "653:8:1" + }, + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "member_name": "sender", + "referencedDeclaration": null, + "type": "address" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 181, + "type": "msg", + "value": "msg" + }, + "id": 61, + "name": "Identifier", + "src": "662:3:1" + } + ], + "id": 62, + "name": "MemberAccess", + "src": "662:10:1" + } + ], + "id": 63, + "name": "IndexAccess", + "src": "653:20:1" + }, + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 46, + "type": "uint256", + "value": "amount" + }, + "id": 64, + "name": "Identifier", + "src": "677:6:1" + } + ], + "id": 65, + "name": "Assignment", + "src": "653:30:1" + } + ], + "id": 66, + "name": "ExpressionStatement", + "src": "653:30:1" + }, + { + "children": [ + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "operator": "+=", + "type": "uint256" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "type": "uint256" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 23, + "type": "mapping(address => uint256)", + "value": "balances" + }, + "id": 67, + "name": "Identifier", + "src": "687:8:1" + }, + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 44, + "type": "address", + "value": "receiver" + }, + "id": 68, + "name": "Identifier", + "src": "696:8:1" + } + ], + "id": 69, + "name": "IndexAccess", + "src": "687:18:1" + }, + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 46, + "type": "uint256", + "value": "amount" + }, + "id": 70, + "name": "Identifier", + "src": "709:6:1" + } + ], + "id": 71, + "name": "Assignment", + "src": "687:28:1" + } + ], + "id": 72, + "name": "ExpressionStatement", + "src": "687:28:1" + }, + { + "children": [ + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": false, + "isPure": false, + "isStructConstructorCall": false, + "lValueRequested": false, + "names": [ + null + ], + "type": "tuple()", + "type_conversion": false + }, + "children": [ + { + "attributes": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 31, + "type": "function (address,address,uint256)", + "value": "Transfer" + }, + "id": 73, + "name": "Identifier", + "src": "719:8:1" + }, + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "member_name": "sender", + "referencedDeclaration": null, + "type": "address" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 181, + "type": "msg", + "value": "msg" + }, + "id": 74, + "name": "Identifier", + "src": "728:3:1" + } + ], + "id": 75, + "name": "MemberAccess", + "src": "728:10:1" + }, + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 44, + "type": "address", + "value": "receiver" + }, + "id": 76, + "name": "Identifier", + "src": "740:8:1" + }, + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 46, + "type": "uint256", + "value": "amount" + }, + "id": 77, + "name": "Identifier", + "src": "750:6:1" + } + ], + "id": 78, + "name": "FunctionCall", + "src": "719:38:1" + } + ], + "id": 79, + "name": "ExpressionStatement", + "src": "719:38:1" + }, + { + "attributes": { + "functionReturnParameters": 50 + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "hexvalue": "74727565", + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "subdenomination": null, + "token": "bool", + "type": "bool", + "value": "true" + }, + "id": 80, + "name": "Literal", + "src": "768:4:1" + } + ], + "id": 81, + "name": "Return", + "src": "761:11:1" + } + ], + "id": 82, + "name": "Block", + "src": "598:178:1" + } + ], + "id": 83, + "name": "FunctionDefinition", + "src": "517:259:1" + }, + { + "attributes": { + "constant": true, + "implemented": true, + "isConstructor": false, + "modifiers": [ + null + ], + "name": "getBalanceInEth", + "payable": false, + "scope": 112, + "stateMutability": "view", + "superFunction": null, + "visibility": "public" + }, + "children": [ + { + "children": [ + { + "attributes": { + "constant": false, + "name": "addr", + "scope": 99, + "stateVariable": false, + "storageLocation": "default", + "type": "address", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "address", + "type": "address" + }, + "id": 84, + "name": "ElementaryTypeName", + "src": "804:7:1" + } + ], + "id": 85, + "name": "VariableDeclaration", + "src": "804:12:1" + } + ], + "id": 86, + "name": "ParameterList", + "src": "803:14:1" + }, + { + "children": [ + { + "attributes": { + "constant": false, + "name": "", + "scope": 99, + "stateVariable": false, + "storageLocation": "default", + "type": "uint256", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint", + "type": "uint256" + }, + "id": 87, + "name": "ElementaryTypeName", + "src": "838:4:1" + } + ], + "id": 88, + "name": "VariableDeclaration", + "src": "838:4:1" + } + ], + "id": 89, + "name": "ParameterList", + "src": "837:6:1" + }, + { + "children": [ + { + "attributes": { + "functionReturnParameters": 89 + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": false, + "isPure": false, + "isStructConstructorCall": false, + "lValueRequested": false, + "names": [ + null + ], + "type": "uint256", + "type_conversion": false + }, + "children": [ + { + "attributes": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_rational_2_by_1", + "typeString": "int_const 2" + } + ], + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "member_name": "convert", + "referencedDeclaration": 15, + "type": "function (uint256,uint256) pure returns (uint256)" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 16, + "type": "type(library ConvertLib)", + "value": "ConvertLib" + }, + "id": 90, + "name": "Identifier", + "src": "854:10:1" + } + ], + "id": 91, + "name": "MemberAccess", + "src": "854:18:1" + }, + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": false, + "isPure": false, + "isStructConstructorCall": false, + "lValueRequested": false, + "names": [ + null + ], + "type": "uint256", + "type_conversion": false + }, + "children": [ + { + "attributes": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 111, + "type": "function (address) view returns (uint256)", + "value": "getBalance" + }, + "id": 92, + "name": "Identifier", + "src": "873:10:1" + }, + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 85, + "type": "address", + "value": "addr" + }, + "id": 93, + "name": "Identifier", + "src": "884:4:1" + } + ], + "id": 94, + "name": "FunctionCall", + "src": "873:16:1" + }, + { + "attributes": { + "argumentTypes": null, + "hexvalue": "32", + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "subdenomination": null, + "token": "number", + "type": "int_const 2", + "value": "2" + }, + "id": 95, + "name": "Literal", + "src": "890:1:1" + } + ], + "id": 96, + "name": "FunctionCall", + "src": "854:38:1" + } + ], + "id": 97, + "name": "Return", + "src": "847:45:1" + } + ], + "id": 98, + "name": "Block", + "src": "843:53:1" + } + ], + "id": 99, + "name": "FunctionDefinition", + "src": "779:117:1" + }, + { + "attributes": { + "constant": true, + "implemented": true, + "isConstructor": false, + "modifiers": [ + null + ], + "name": "getBalance", + "payable": false, + "scope": 112, + "stateMutability": "view", + "superFunction": null, + "visibility": "public" + }, + "children": [ + { + "children": [ + { + "attributes": { + "constant": false, + "name": "addr", + "scope": 111, + "stateVariable": false, + "storageLocation": "default", + "type": "address", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "address", + "type": "address" + }, + "id": 100, + "name": "ElementaryTypeName", + "src": "919:7:1" + } + ], + "id": 101, + "name": "VariableDeclaration", + "src": "919:12:1" + } + ], + "id": 102, + "name": "ParameterList", + "src": "918:14:1" + }, + { + "children": [ + { + "attributes": { + "constant": false, + "name": "", + "scope": 111, + "stateVariable": false, + "storageLocation": "default", + "type": "uint256", + "value": null, + "visibility": "internal" + }, + "children": [ + { + "attributes": { + "name": "uint", + "type": "uint256" + }, + "id": 103, + "name": "ElementaryTypeName", + "src": "953:4:1" + } + ], + "id": 104, + "name": "VariableDeclaration", + "src": "953:4:1" + } + ], + "id": 105, + "name": "ParameterList", + "src": "952:6:1" + }, + { + "children": [ + { + "attributes": { + "functionReturnParameters": 105 + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "type": "uint256" + }, + "children": [ + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 23, + "type": "mapping(address => uint256)", + "value": "balances" + }, + "id": 106, + "name": "Identifier", + "src": "970:8:1" + }, + { + "attributes": { + "argumentTypes": null, + "overloadedDeclarations": [ + null + ], + "referencedDeclaration": 101, + "type": "address", + "value": "addr" + }, + "id": 107, + "name": "Identifier", + "src": "979:4:1" + } + ], + "id": 108, + "name": "IndexAccess", + "src": "970:14:1" + } + ], + "id": 109, + "name": "Return", + "src": "963:21:1" + } + ], + "id": 110, + "name": "Block", + "src": "959:29:1" + } + ], + "id": 111, + "name": "FunctionDefinition", + "src": "899:89:1" + } + ], + "id": 112, + "name": "ContractDefinition", + "src": "315:675:1" + } + ], + "id": 113, + "name": "SourceUnit", + "src": "0:991:1" + }, + "compiler": { + "name": "solc", + "version": "0.4.18+commit.9cf6e910.Emscripten.clang" + }, + "networks": { + "4": { + "events": {}, + "links": { + "ConvertLib": "0x14d00a701a2f0d4d65eebaf191f4f60bbaa1da36" + }, + "address": "0xaea9d31a4aeda9e510f7d85559261c16ea0b6b8b" + }, + "4447": { + "events": {}, + "links": { + "ConvertLib": "0xcfeb869f69431e42cdb54a4f4f105c19c080a601" + }, + "address": "0x254dffcd3277c0b1660f6d42efbb754edababc2b" + } + }, + "schemaVersion": "1.0.1", + "updatedAt": "2017-11-07T20:44:02.307Z" +} \ No newline at end of file diff --git a/console/src/main/java/org/web3j/console/Runner.java b/console/src/main/java/org/web3j/console/Runner.java index 5a3f95119d..f52c83bf8c 100644 --- a/console/src/main/java/org/web3j/console/Runner.java +++ b/console/src/main/java/org/web3j/console/Runner.java @@ -2,6 +2,7 @@ import org.web3j.codegen.Console; import org.web3j.codegen.SolidityFunctionWrapperGenerator; +import org.web3j.codegen.TruffleJsonFunctionWrapperGenerator; import org.web3j.utils.Version; import static org.web3j.utils.Collection.tail; @@ -36,6 +37,9 @@ public static void main(String[] args) throws Exception { case "solidity": SolidityFunctionWrapperGenerator.run(tail(args)); break; + case "truffle": + TruffleJsonFunctionWrapperGenerator.run(tail(args)); + break; case "version": Console.exitSuccess("Version: " + Version.getVersion() + "\n" + "Build timestamp: " + Version.getTimestamp()); diff --git a/core/src/main/java/org/web3j/protocol/core/methods/response/AbiDefinition.java b/core/src/main/java/org/web3j/protocol/core/methods/response/AbiDefinition.java index ecdab20767..91d7e44c53 100644 --- a/core/src/main/java/org/web3j/protocol/core/methods/response/AbiDefinition.java +++ b/core/src/main/java/org/web3j/protocol/core/methods/response/AbiDefinition.java @@ -16,19 +16,44 @@ public class AbiDefinition { private String type; private boolean payable; + /** + * The stateMutability function modifier. + *

this does not factor into the #hashCode() or #equals() logic + * since multiple functions with the same signature that only differ in mutability are not + * allowed in Solidity.

+ *

+ * Valid values are: + *

    + *
  • pure
  • + *
  • view
  • + *
  • nonpayable
  • + *
  • payable
  • + *
+ *

+ */ + private String stateMutability; + public AbiDefinition() { } public AbiDefinition(boolean constant, List inputs, String name, List outputs, String type, boolean payable) { + this(constant, inputs, name, outputs, type, payable, null); + } + + public AbiDefinition(boolean constant, List inputs, String name, + List outputs, String type, boolean payable, + String stateMutability) { this.constant = constant; this.inputs = inputs; this.name = name; this.outputs = outputs; this.type = type; this.payable = payable; + this.stateMutability = stateMutability; } + public boolean isConstant() { return constant; } @@ -77,6 +102,14 @@ public void setPayable(boolean payable) { this.payable = payable; } + public String getStateMutability() { + return stateMutability; + } + + public void setStateMutability(String stateMutability) { + this.stateMutability = stateMutability; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -106,6 +139,11 @@ public boolean equals(Object o) { ? !getOutputs().equals(that.getOutputs()) : that.getOutputs() != null) { return false; } + if (getStateMutability() != null + ? !getStateMutability().equals(that.getStateMutability()) + : that.getStateMutability() != null) { + return false; + } return getType() != null ? getType().equals(that.getType()) : that.getType() == null; } @@ -118,6 +156,7 @@ public int hashCode() { result = 31 * result + (getOutputs() != null ? getOutputs().hashCode() : 0); result = 31 * result + (getType() != null ? getType().hashCode() : 0); result = 31 * result + (isPayable() ? 1 : 0); + result = 31 * result + (getStateMutability() != null ? getStateMutability().hashCode() : 0); return result; } @@ -172,6 +211,7 @@ public boolean equals(Object o) { if (isIndexed() != namedType.isIndexed()) { return false; } + if (getName() != null ? !getName().equals(namedType.getName()) : namedType.getName() != null) { return false; diff --git a/core/src/main/java/org/web3j/tx/Contract.java b/core/src/main/java/org/web3j/tx/Contract.java index ee9af948d7..8f6f4e4818 100644 --- a/core/src/main/java/org/web3j/tx/Contract.java +++ b/core/src/main/java/org/web3j/tx/Contract.java @@ -4,7 +4,9 @@ import java.lang.reflect.Constructor; import java.math.BigInteger; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import org.web3j.abi.EventEncoder; @@ -41,6 +43,7 @@ public abstract class Contract extends ManagedTransaction { private final BigInteger gasPrice; private final BigInteger gasLimit; private TransactionReceipt transactionReceipt; + private Map deployedAddresses; protected Contract(String contractBinary, String contractAddress, Web3j web3j, TransactionManager transactionManager, @@ -369,4 +372,31 @@ protected List extractEventParameters( return values; } + + /** + * Subclasses should implement this method to return pre-existing addresses for deployed + * contracts. + * + * @param networkId the network id, for example "1" for the main-net, "3" for ropsten, etc. + * @return the deployed address of the contract, if known, and null otherwise. + */ + protected String getStaticDeployedAddress(String networkId) { + return null; + } + + public final void setDeployedAddress(String networkId, String address) { + if (deployedAddresses == null) { + deployedAddresses = new HashMap<>(); + } + deployedAddresses.put(networkId, address); + } + + public final String getDeployedAddress(String networkId) { + String addr = null; + if (deployedAddresses != null) { + addr = deployedAddresses.get(networkId); + } + return addr == null ? getStaticDeployedAddress(networkId) : addr; + } + } diff --git a/utils/src/main/java/org/web3j/utils/Strings.java b/utils/src/main/java/org/web3j/utils/Strings.java index 37a1ada2c6..7d4376e4d9 100644 --- a/utils/src/main/java/org/web3j/utils/Strings.java +++ b/utils/src/main/java/org/web3j/utils/Strings.java @@ -47,4 +47,8 @@ public static String zeros(int n) { public static String repeat(char value, int n) { return new String(new char[n]).replace("\0", String.valueOf(value)); } + + public static boolean isEmpty(String s) { + return s == null || s.length() == 0; + } } From ecfd6e954fc8e1fe8a8fa49caed88db2bb71738c Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Wed, 8 Nov 2017 23:04:44 -0800 Subject: [PATCH 02/16] Appeasing the coverage police --- .../TruffleJsonFunctionWrapperGenerator.java | 130 +----------------- .../test/java/org/web3j/tx/ContractTest.java | 7 + gradle/wrapper/gradle-wrapper.jar | Bin 53556 -> 54727 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 26 ++-- gradlew.bat | 6 - .../java/org/web3j/utils/StringsTest.java | 14 +- 7 files changed, 43 insertions(+), 143 deletions(-) diff --git a/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java b/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java index bbeba6d832..851774c123 100644 --- a/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java +++ b/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java @@ -130,8 +130,8 @@ private void generate() throws IOException, ClassNotFoundException { } new SolidityFunctionWrapper(useJavaNativeTypes) .generateJavaFiles(contractName, - c.bytecode, - c.abi, + c.getBytecode(), + c.getAbi(), destinationDirLocation.toString(), basePackageName, addresses); @@ -226,72 +226,7 @@ public Contract(String contractName, List abi, String bytecode, this.schemaVersion = schemaVersion; this.updatedAt = updatedAt; } - - public Contract withContractName(String contractName) { - this.contractName = contractName; - return this; - } - - public Contract withAbi(List abi) { - this.abi = abi; - return this; - } - - public Contract withBytecode(String bytecode) { - this.bytecode = bytecode; - return this; - } - - public Contract withDeployedBytecode(String deployedBytecode) { - this.deployedBytecode = deployedBytecode; - return this; - } - - public Contract withSourceMap(String sourceMap) { - this.sourceMap = sourceMap; - return this; - } - - public Contract withDeployedSourceMap(String deployedSourceMap) { - this.deployedSourceMap = deployedSourceMap; - return this; - } - - public Contract withSource(String source) { - this.source = source; - return this; - } - - public Contract withSourcePath(String sourcePath) { - this.sourcePath = sourcePath; - return this; - } - - public Contract withAst(JsonNode ast) { - this.ast = ast; - return this; - } - - public Contract withCompiler(Compiler compiler) { - this.compiler = compiler; - return this; - } - - public Contract withNetworks(Map networks) { - this.networks = networks; - return this; - } - - public Contract withSchemaVersion(String schemaVersion) { - this.schemaVersion = schemaVersion; - return this; - } - - public Contract withUpdatedAt(Date updatedAt) { - this.updatedAt = updatedAt; - return this; - } - + public String getContractName() { return contractName; } @@ -303,47 +238,7 @@ public List getAbi() { public String getBytecode() { return bytecode; } - - public String getDeployedBytecode() { - return deployedBytecode; - } - - public String getSourceMap() { - return sourceMap; - } - - public String getDeployedSourceMap() { - return deployedSourceMap; - } - - public String getSource() { - return source; - } - - public String getSourcePath() { - return sourcePath; - } - - public JsonNode getAst() { - return ast; - } - - public Compiler getCompiler() { - return compiler; - } - - public Map getNetworks() { - return networks; - } - - public String getSchemaVersion() { - return schemaVersion; - } - - public Date getUpdatedAt() { - return updatedAt; - } - + public NetworkInfo getNetwork(String networkId) { return networks.get(networkId); } @@ -404,16 +299,6 @@ public Compiler(String name, String version) { this.version = version; } - public Compiler withName(String name) { - this.name = name; - return this; - } - - public Compiler withVersion(String version) { - this.version = version; - return this; - } - @JsonAnyGetter public Map getAdditionalProperties() { return this.additionalProperties; @@ -462,12 +347,7 @@ public NetworkInfo(Map events, Map links, this.links = links; this.address = address; } - - public NetworkInfo withAddress(String address) { - this.address = address; - return this; - } - + public String getAddress() { return address; } diff --git a/core/src/test/java/org/web3j/tx/ContractTest.java b/core/src/test/java/org/web3j/tx/ContractTest.java index 7ab91e74bc..d2184eed53 100644 --- a/core/src/test/java/org/web3j/tx/ContractTest.java +++ b/core/src/test/java/org/web3j/tx/ContractTest.java @@ -37,6 +37,7 @@ import org.web3j.utils.Async; import org.web3j.utils.Numeric; +import static junit.framework.TestCase.assertNotNull; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertFalse; @@ -254,6 +255,12 @@ public void testInvalidTransactionResponse() throws Throwable { testErrorScenario(); } + @Test + public void testSetGetAddresses() throws Exception { + assertNull(contract.getDeployedAddress("1")); + contract.setDeployedAddress("1", "0x000000000000add0e00000000000"); + assertNotNull(contract.getDeployedAddress("1")); + } @Test(expected = RuntimeException.class) public void testInvalidTransactionReceipt() throws Throwable { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ca78035ef0501d802d4fc55381ef2d5c3ce0ec6e..27768f1bbac3ce2d055b20d521f12da78d331e8e 100644 GIT binary patch delta 28785 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, is("")); + assertThat(toCsv(Collections.emptyList()), is("")); assertThat(toCsv(Collections.singletonList("a")), is("a")); assertThat(toCsv(Arrays.asList("a", "b", "c")), is("a, b, c")); } @@ -58,4 +61,13 @@ public void testZeros() { assertThat(zeros(0), is("")); assertThat(zeros(3), is("000")); } + + @SuppressWarnings("ConstantConditions") + @Test + public void testEmptyString() { + assertTrue(isEmpty(null)); + assertTrue(isEmpty("")); + assertFalse(isEmpty("hello world")); + } + } From 1c400b9f4759f61ecc94568eb579403950ade18e Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Wed, 8 Nov 2017 23:25:59 -0800 Subject: [PATCH 03/16] Don't reinvent wheels (esp not ones from java.lang.* !) --- utils/src/main/java/org/web3j/utils/Strings.java | 13 +------------ .../src/test/java/org/web3j/utils/StringsTest.java | 8 +------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/utils/src/main/java/org/web3j/utils/Strings.java b/utils/src/main/java/org/web3j/utils/Strings.java index 7d4376e4d9..6234ad3863 100644 --- a/utils/src/main/java/org/web3j/utils/Strings.java +++ b/utils/src/main/java/org/web3j/utils/Strings.java @@ -10,18 +10,7 @@ public class Strings { private Strings() {} public static String toCsv(List src) { - return join(src, ", "); - } - - public static String join(List src, String delimiter) { - String result = ""; - for (int i = 0; i < src.size(); i++) { - result += src.get(i); - if (i + 1 < src.size()) { - result += delimiter; - } - } - return result; + return src == null ? null : String.join(", ", src.toArray(new String[0])); } public static String capitaliseFirstLetter(String string) { diff --git a/utils/src/test/java/org/web3j/utils/StringsTest.java b/utils/src/test/java/org/web3j/utils/StringsTest.java index 1e76855acf..d34079c632 100644 --- a/utils/src/test/java/org/web3j/utils/StringsTest.java +++ b/utils/src/test/java/org/web3j/utils/StringsTest.java @@ -11,7 +11,6 @@ import static org.junit.Assert.assertTrue; import static org.web3j.utils.Strings.capitaliseFirstLetter; import static org.web3j.utils.Strings.isEmpty; -import static org.web3j.utils.Strings.join; import static org.web3j.utils.Strings.lowercaseFirstLetter; import static org.web3j.utils.Strings.repeat; import static org.web3j.utils.Strings.toCsv; @@ -26,12 +25,7 @@ public void testToCsv() { assertThat(toCsv(Collections.singletonList("a")), is("a")); assertThat(toCsv(Arrays.asList("a", "b", "c")), is("a, b, c")); } - - @Test - public void testJoin() { - assertThat(join(Arrays.asList("a", "b"), "|"), is("a|b")); - } - + @Test public void testCapitaliseFirstLetter() { assertThat(capitaliseFirstLetter(""), is("")); From 68da5d8a0782570155aad374cb8d43f5f254c1dc Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Wed, 8 Nov 2017 23:26:19 -0800 Subject: [PATCH 04/16] formatting for readability --- .../java/org/web3j/codegen/SolidityFunctionWrapper.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java b/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java index fc7d2b9467..98bcd5fc23 100644 --- a/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java +++ b/codegen/src/main/java/org/web3j/codegen/SolidityFunctionWrapper.java @@ -139,10 +139,11 @@ private void addAddressesSupport(TypeSpec.Builder classBuilder, Map Date: Wed, 8 Nov 2017 23:35:56 -0800 Subject: [PATCH 05/16] one more code path --- core/src/test/java/org/web3j/tx/ContractTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/test/java/org/web3j/tx/ContractTest.java b/core/src/test/java/org/web3j/tx/ContractTest.java index d2184eed53..97a67ecf51 100644 --- a/core/src/test/java/org/web3j/tx/ContractTest.java +++ b/core/src/test/java/org/web3j/tx/ContractTest.java @@ -260,6 +260,8 @@ public void testSetGetAddresses() throws Exception { assertNull(contract.getDeployedAddress("1")); contract.setDeployedAddress("1", "0x000000000000add0e00000000000"); assertNotNull(contract.getDeployedAddress("1")); + contract.setDeployedAddress("2", "0x000000000000add0e00000000000"); + assertNotNull(contract.getDeployedAddress("2")); } @Test(expected = RuntimeException.class) From 96f45cac2f761fac3077030006d76fcebc13d8d0 Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Thu, 9 Nov 2017 13:59:27 -0800 Subject: [PATCH 06/16] despite the current gradle settings, we should target JDK 1.6 to support Android devs --- utils/src/test/java/org/web3j/utils/StringsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/src/test/java/org/web3j/utils/StringsTest.java b/utils/src/test/java/org/web3j/utils/StringsTest.java index d34079c632..d42216c406 100644 --- a/utils/src/test/java/org/web3j/utils/StringsTest.java +++ b/utils/src/test/java/org/web3j/utils/StringsTest.java @@ -21,7 +21,7 @@ public class StringsTest { @Test public void testToCsv() { - assertThat(toCsv(Collections.emptyList()), is("")); + assertThat(toCsv(Collections.emptyList()), is("")); assertThat(toCsv(Collections.singletonList("a")), is("a")); assertThat(toCsv(Arrays.asList("a", "b", "c")), is("a, b, c")); } From d558bd2618351b601aa6c93787c460e067092f7a Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Thu, 9 Nov 2017 14:02:14 -0800 Subject: [PATCH 07/16] updated docs for the new truffle command-line codegen option --- .../TruffleJsonFunctionWrapperGenerator.java | 3 +++ docs/source/index.rst | 2 ++ docs/source/smart_contracts.rst | 15 ++++++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java b/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java index 851774c123..545b289f2f 100644 --- a/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java +++ b/codegen/src/main/java/org/web3j/codegen/TruffleJsonFunctionWrapperGenerator.java @@ -258,6 +258,9 @@ public String getAddress(Network network) { return getAddress(Long.toString(network.id)); } + /* + * c.f., org.web3j.tx.ChainId + */ enum Network { olympic(0), mainnet(1), morden(2), ropsten(3), rinkeby(4), kovan(42); diff --git a/docs/source/index.rst b/docs/source/index.rst index 582b9bf189..290dbb82b9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,6 +23,8 @@ Features - Ethereum wallet support - Auto-generation of Java smart contract wrappers to create, deploy, transact with and call smart contracts from native Java code + - from solc output; or + - from Truffle `.json` contract files. - Reactive-functional API for working with filters - Support for Parity's `Personal `__, and Geth's diff --git a/docs/source/smart_contracts.rst b/docs/source/smart_contracts.rst index f4f23b9c5a..93e34af7b0 100644 --- a/docs/source/smart_contracts.rst +++ b/docs/source/smart_contracts.rst @@ -160,7 +160,7 @@ In versions prior to 3.x of web3j, the generated smart contract wrappers used na types. From web3j 3.x onwards, Java types are created by default. You can create Solidity types using the *--solidityTypes* command line argument. -You can also generate the wrappers by calling then Java class directly: +You can also generate the wrappers by calling the Java class directly: .. code-block:: bash @@ -180,6 +180,19 @@ The smart contract wrappers support all common operations for working with smart Any method calls that requires an underlying JSON-RPC call to take place will return a Future to avoid blocking. +web3j also supports the generation of Java smart contract function wrappers directly from `Truffle `_ via the :doc:`command_line` utility. + +.. code-block:: bash + + $ web3j truffle generate [--javaTypes|--solidityTypes] /path/to/.json -o /path/to/src/main/java -p com.your.organisation.name + +And this also can be invoked by calling the Java class: + +.. code-block:: bash + + org.web3j.codegen.TruffleJsonFunctionWrapperGenerator /path/to/.json -o /path/to/src/main/java -p com.your.organisation.name + +A wrapper generated this way ia "enhanced" to expose the per-network deployed address of the contract. These addresses are from the truffle deployment at the time the wrapper is generared. .. _construction-and-deployment: From 515a8e10e330d1a906befe4fc7580a76eb357375 Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Thu, 9 Nov 2017 14:11:28 -0800 Subject: [PATCH 08/16] use the full gradle enchilada --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 92165eede8..590f0e81da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip From be54b80133bbbcf9cdce0158c607061d79f87f8f Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Thu, 9 Nov 2017 14:20:45 -0800 Subject: [PATCH 09/16] update main README.rst to talk about truffle json contract file support --- README.rst | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 83d4b48435..2550bb8aea 100644 --- a/README.rst +++ b/README.rst @@ -44,6 +44,8 @@ Features - Ethereum wallet support - Auto-generation of Java smart contract wrappers to create, deploy, transact with and call smart contracts from native Java code + - from solc output; or + - from Truffle `.json` contract files. - Reactive-functional API for working with filters - Support for Parity's `Personal `__, and Geth's @@ -251,7 +253,24 @@ Now you can create and deploy your smart contract: GAS_PRICE, GAS_LIMIT, , ..., ).send(); // constructor params -Or use an existing contract: +Alternatively, if you use `Truffle `_, you can make use of its `.json` output files: + +.. code-block:: bash + + # Inside your Truffle project + $ truffle compile + $ truffle deploy + +Then generate the wrapper code using web3j's `Command line tools`_: + +.. code-block:: bash + + $ cd /path/to/your/webj/java/project + $ web3j truffle generate /path/to/.json -o /path/to/src/main/java -p com.your.organisation.name + +Whether using `Truffle` or `solc` directly, either way you get a ready-to-use Java wrapper for your contract. + +So, to use an existing contract: .. code-block:: java From 2e309d95d42d8a35fa723a853b6cdb8aa2783d91 Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Thu, 9 Nov 2017 16:01:38 -0800 Subject: [PATCH 10/16] needed more indenting to create sublist --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 2550bb8aea..78038ce6dd 100644 --- a/README.rst +++ b/README.rst @@ -44,8 +44,8 @@ Features - Ethereum wallet support - Auto-generation of Java smart contract wrappers to create, deploy, transact with and call smart contracts from native Java code - - from solc output; or - - from Truffle `.json` contract files. + - from solc output; or + - from Truffle `.json` contract files. - Reactive-functional API for working with filters - Support for Parity's `Personal `__, and Geth's From 8585b0f9a05238e5f8ef040a2ba09af03e23a1ff Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Thu, 9 Nov 2017 16:02:26 -0800 Subject: [PATCH 11/16] needed more indenting to create sublist --- docs/source/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 290dbb82b9..104ec84f6f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,8 +23,8 @@ Features - Ethereum wallet support - Auto-generation of Java smart contract wrappers to create, deploy, transact with and call smart contracts from native Java code - - from solc output; or - - from Truffle `.json` contract files. + - from solc output; or + - from Truffle `.json` contract files. - Reactive-functional API for working with filters - Support for Parity's `Personal `__, and Geth's From d0bb36dff5fb477d46ee05548053cab83fb8085a Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Thu, 9 Nov 2017 16:03:15 -0800 Subject: [PATCH 12/16] indenting to create sublist --- docs/source/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 104ec84f6f..290dbb82b9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,8 +23,8 @@ Features - Ethereum wallet support - Auto-generation of Java smart contract wrappers to create, deploy, transact with and call smart contracts from native Java code - - from solc output; or - - from Truffle `.json` contract files. + - from solc output; or + - from Truffle `.json` contract files. - Reactive-functional API for working with filters - Support for Parity's `Personal `__, and Geth's From 5c751a85010d7bd8fca0a7327d1eddd0c79c94a6 Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Thu, 9 Nov 2017 16:03:37 -0800 Subject: [PATCH 13/16] needed more indenting to create sublist --- docs/source/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 290dbb82b9..104ec84f6f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,8 +23,8 @@ Features - Ethereum wallet support - Auto-generation of Java smart contract wrappers to create, deploy, transact with and call smart contracts from native Java code - - from solc output; or - - from Truffle `.json` contract files. + - from solc output; or + - from Truffle `.json` contract files. - Reactive-functional API for working with filters - Support for Parity's `Personal `__, and Geth's From 310a50401940ec175fa443266c62a7144897d111 Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Thu, 9 Nov 2017 16:05:23 -0800 Subject: [PATCH 14/16] typo: webj -> web3j --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 78038ce6dd..8309fbaa5c 100644 --- a/README.rst +++ b/README.rst @@ -265,7 +265,7 @@ Then generate the wrapper code using web3j's `Command line tools`_: .. code-block:: bash - $ cd /path/to/your/webj/java/project + $ cd /path/to/your/web3j/java/project $ web3j truffle generate /path/to/.json -o /path/to/src/main/java -p com.your.organisation.name Whether using `Truffle` or `solc` directly, either way you get a ready-to-use Java wrapper for your contract. From 9aeaa239b183f5cc998adf194cb5eb3f2753db9b Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Fri, 10 Nov 2017 10:06:34 -0800 Subject: [PATCH 15/16] conor wants this method --- utils/src/main/java/org/web3j/utils/Strings.java | 7 ++++++- utils/src/test/java/org/web3j/utils/StringsTest.java | 11 ++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/utils/src/main/java/org/web3j/utils/Strings.java b/utils/src/main/java/org/web3j/utils/Strings.java index 6234ad3863..d07256e0cc 100644 --- a/utils/src/main/java/org/web3j/utils/Strings.java +++ b/utils/src/main/java/org/web3j/utils/Strings.java @@ -10,7 +10,12 @@ public class Strings { private Strings() {} public static String toCsv(List src) { - return src == null ? null : String.join(", ", src.toArray(new String[0])); +// return src == null ? null : String.join(", ", src.toArray(new String[0])); + return join(src, ","); + } + + public static String join(List src, String delimiter) { + return src == null ? null : String.join(delimiter, src.toArray(new String[0])); } public static String capitaliseFirstLetter(String string) { diff --git a/utils/src/test/java/org/web3j/utils/StringsTest.java b/utils/src/test/java/org/web3j/utils/StringsTest.java index d42216c406..1ace414d87 100644 --- a/utils/src/test/java/org/web3j/utils/StringsTest.java +++ b/utils/src/test/java/org/web3j/utils/StringsTest.java @@ -7,10 +7,12 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.web3j.utils.Strings.capitaliseFirstLetter; import static org.web3j.utils.Strings.isEmpty; +import static org.web3j.utils.Strings.join; import static org.web3j.utils.Strings.lowercaseFirstLetter; import static org.web3j.utils.Strings.repeat; import static org.web3j.utils.Strings.toCsv; @@ -25,7 +27,14 @@ public void testToCsv() { assertThat(toCsv(Collections.singletonList("a")), is("a")); assertThat(toCsv(Arrays.asList("a", "b", "c")), is("a, b, c")); } - + + @Test + public void testJoin() { + assertThat(join(Arrays.asList("a", "b"), "|"), is("a|b")); + assertNull(join(null, "|")); + assertThat(join(Collections.singletonList("a"), "|"), is("a")); + } + @Test public void testCapitaliseFirstLetter() { assertThat(capitaliseFirstLetter(""), is("")); From 4ac04708ce5ac036793b0297097611bac03e16f0 Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Fri, 10 Nov 2017 11:09:02 -0800 Subject: [PATCH 16/16] minimalist "toCsv" comma and space between values --- utils/src/main/java/org/web3j/utils/Strings.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/src/main/java/org/web3j/utils/Strings.java b/utils/src/main/java/org/web3j/utils/Strings.java index d07256e0cc..bcba0a84fe 100644 --- a/utils/src/main/java/org/web3j/utils/Strings.java +++ b/utils/src/main/java/org/web3j/utils/Strings.java @@ -10,8 +10,8 @@ public class Strings { private Strings() {} public static String toCsv(List src) { -// return src == null ? null : String.join(", ", src.toArray(new String[0])); - return join(src, ","); + // return src == null ? null : String.join(", ", src.toArray(new String[0])); + return join(src, ", "); } public static String join(List src, String delimiter) {