diff --git a/project.clj b/project.clj index cd092e4..450b536 100644 --- a/project.clj +++ b/project.clj @@ -5,8 +5,11 @@ :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.0"] [org.web3j/core "4.8.4"] - [org.web3j/web3j-evm "4.8.4"]] + [org.web3j/web3j-evm "4.8.4"] + [org.web3j/codegen "4.8.4"] + [org.web3j/web3j-sokt "0.2.1"]] :repositories [["jcenter" {:url "https://jcenter.bintray.com/"}] ["releases" {:url "https://repo.clojars.org" :creds :gpg}]] - :repl-options {:init-ns web3clj.core}) + :repl-options {:init-ns web3clj.core} + :java-source-paths ["src/java"]) diff --git a/resources/contracts/Inbox.sol b/resources/contracts/Inbox.sol new file mode 100644 index 0000000..c089250 --- /dev/null +++ b/resources/contracts/Inbox.sol @@ -0,0 +1,22 @@ +// compiler version pinning +pragma solidity ^0.4.17; + +// define a new contract +contract Inbox { + // storage variable which is stored on the blockchain + string public message; + + // constructor, invoked when we deploy contract to blockchain + function Inbox(string newMessage) public { + message = newMessage; + } + + function setMessage(string newMessage) public { + message = newMessage; + } + + // function-name() function-type return-type + function getMessage() public view returns (string) { + return message; + } +} \ No newline at end of file diff --git a/src/java/web3clj/solc/Inbox.java b/src/java/web3clj/solc/Inbox.java new file mode 100644 index 0000000..e2ff566 --- /dev/null +++ b/src/java/web3clj/solc/Inbox.java @@ -0,0 +1,118 @@ +package web3clj.solc; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import org.web3j.abi.FunctionEncoder; +import org.web3j.abi.TypeReference; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.Utf8String; +import org.web3j.crypto.Credentials; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.RemoteCall; +import org.web3j.protocol.core.RemoteFunctionCall; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.tx.Contract; +import org.web3j.tx.TransactionManager; +import org.web3j.tx.gas.ContractGasProvider; + +/** + *

Auto generated code. + *

Do not modify! + *

Please use the web3j command line tools, + * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the + * codegen module to update. + * + *

Generated with web3j version 4.8.4. + */ +@SuppressWarnings("rawtypes") +public class Inbox extends Contract { + public static final String BINARY = "608060405234801561001057600080fd5b50604051610514380380610514833981018060405281019080805182019291905050508060009080519060200190610049929190610050565b50506100f5565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061009157805160ff19168380011785556100bf565b828001600101855582156100bf579182015b828111156100be5782518255916020019190600101906100a3565b5b5090506100cc91906100d0565b5090565b6100f291905b808211156100ee5760008160009055506001016100d6565b5090565b90565b610410806101046000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063368b87721461005c578063ce6d41de146100c5578063e21f37ce14610155575b600080fd5b34801561006857600080fd5b506100c3600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506101e5565b005b3480156100d157600080fd5b506100da6101ff565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561011a5780820151818401526020810190506100ff565b50505050905090810190601f1680156101475780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561016157600080fd5b5061016a6102a1565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101aa57808201518184015260208101905061018f565b50505050905090810190601f1680156101d75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101fb92919061033f565b5050565b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102975780601f1061026c57610100808354040283529160200191610297565b820191906000526020600020905b81548152906001019060200180831161027a57829003601f168201915b5050505050905090565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103375780601f1061030c57610100808354040283529160200191610337565b820191906000526020600020905b81548152906001019060200180831161031a57829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061038057805160ff19168380011785556103ae565b828001600101855582156103ae579182015b828111156103ad578251825591602001919060010190610392565b5b5090506103bb91906103bf565b5090565b6103e191905b808211156103dd5760008160009055506001016103c5565b5090565b905600a165627a7a723058201a8692b28f48880175c7f0e650864d1e809dd193a465ffb1ed74696155cbc0740029"; + + public static final String FUNC_SETMESSAGE = "setMessage"; + + public static final String FUNC_GETMESSAGE = "getMessage"; + + public static final String FUNC_MESSAGE = "message"; + + @Deprecated + protected Inbox(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + protected Inbox(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, credentials, contractGasProvider); + } + + @Deprecated + protected Inbox(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + protected Inbox(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); + } + + public RemoteFunctionCall setMessage(String newMessage) { + final Function function = new Function( + FUNC_SETMESSAGE, + Arrays.asList(new org.web3j.abi.datatypes.Utf8String(newMessage)), + Collections.>emptyList()); + return executeRemoteCallTransaction(function); + } + + public RemoteFunctionCall getMessage() { + final Function function = new Function(FUNC_GETMESSAGE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + public RemoteFunctionCall message() { + final Function function = new Function(FUNC_MESSAGE, + Arrays.asList(), + Arrays.>asList(new TypeReference() {})); + return executeRemoteCallSingleValueReturn(function, String.class); + } + + @Deprecated + public static Inbox load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) { + return new Inbox(contractAddress, web3j, credentials, gasPrice, gasLimit); + } + + @Deprecated + public static Inbox load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) { + return new Inbox(contractAddress, web3j, transactionManager, gasPrice, gasLimit); + } + + public static Inbox load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) { + return new Inbox(contractAddress, web3j, credentials, contractGasProvider); + } + + public static Inbox load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) { + return new Inbox(contractAddress, web3j, transactionManager, contractGasProvider); + } + + public static RemoteCall deploy(Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider, String newMessage) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Utf8String(newMessage))); + return deployRemoteCall(Inbox.class, web3j, credentials, contractGasProvider, BINARY, encodedConstructor); + } + + public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider, String newMessage) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Utf8String(newMessage))); + return deployRemoteCall(Inbox.class, web3j, transactionManager, contractGasProvider, BINARY, encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit, String newMessage) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Utf8String(newMessage))); + return deployRemoteCall(Inbox.class, web3j, credentials, gasPrice, gasLimit, BINARY, encodedConstructor); + } + + @Deprecated + public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit, String newMessage) { + String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new org.web3j.abi.datatypes.Utf8String(newMessage))); + return deployRemoteCall(Inbox.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, encodedConstructor); + } +} diff --git a/src/web3clj/solc.clj b/src/web3clj/solc.clj new file mode 100644 index 0000000..49d1122 --- /dev/null +++ b/src/web3clj/solc.clj @@ -0,0 +1,119 @@ +(ns web3clj.solc + (:require + [clojure.java.io :as io] + [web3clj.core :as w3cljc] + [web3clj.crypto :as w3cljcrypto] + [web3clj.evm :as w3cljevm] + [web3clj.protocol :as w3cljproto] + [web3clj.utils :as wu] + [web3clj.utils.numeric :as wun]) + (:import + org.web3j.codegen.SolidityFunctionWrapperGenerator + (org.web3j.crypto + Credentials + WalletUtils) + (org.web3j.protocol + Web3j + Web3jService) + org.web3j.protocol.http.HttpService + (org.web3j.sokt + MainKt + SolcArguments + SolcInstance + SolcOutput + SolidityFile))) + + +(defn compile-contract + "Compile a given contract from a file and + return compiled information. Shape of returned + data : + {:abi + :bytecode + :ast }" + [file contract & {out :out solc-args :solc-args + :or {out "/tmp" + solc-args [SolcArguments/AST + SolcArguments/BIN + SolcArguments/ABI + SolcArguments/OVERWRITE + (.param (SolcArguments/OUTPUT_DIR) + (wu/func0 out))]}}] + (let [sol-file (SolidityFile. file) + solc (.getCompilerInstance sol-file ".web3j" false) + solc-args (into-array SolcArguments solc-args)] + (when (= 0 (.getExitCode (.execute solc solc-args))) + {:abi (slurp (str out "/" contract ".abi")) + :bytecode (slurp (str out "/" contract ".bin")) + :ast (slurp (str out "/" contract ".sol.ast"))}))) + + +(defn generate-solidity-wrappers + "Generate java wrappers from compiled solidity contract files. + Alternatively you can use the ~web3j generate solidity~ cli tool + Note: this does not seem to work with codegen 5.0.0, works with 4.8.4!" + [^String bytecode-file + ^String abi-file + ^String java-src-path + contract-name + package-name] + (let [gen-wrapper (SolidityFunctionWrapperGenerator. (io/file bytecode-file) + (io/file abi-file) + (io/file java-src-path) + contract-name + package-name + true ; useJavaNativeTypes + true ; useJavaPrimitiveTypes + 20)] ; address length in bytes, default is 20 + + (.generate gen-wrapper))) + + +(defn create-compiled-contract + "Compile solidity contract and spit it out to + files + Returns: dictionary with :abi and :bytecode keys" + [file contract-name out-path] + (let [{:keys [abi bytecode] :as out} (compile-contract file contract-name :out out-path) + file-name (str out-path "/" contract-name)] + (io/make-parents file-name) + (spit (str file-name ".abi") abi) + (spit (str file-name ".bytecode") bytecode) + out)) + + +(comment + (let [{:keys [abi bytecode]} (create-compiled-contract "resources/contracts/Inbox.sol" + "Inbox" + "resources/out")] + (println "ABI is " abi)) + (generate-solidity-wrappers "resources/out/Inbox.bytecode" + "resources/out/Inbox.abi" + "src/java" + "Inbox" + "web3clj.solc") + ;; At this point you will need to restart the repl for it + ;; to load this namespace. + + (import web3clj.solc.Inbox) + + ;; Deploy a smart contract + (def web3-ganache (Web3j/build (HttpService. "http://localhost:7545"))) + ;; These credentials determine the author of the contract deployment + (def pr-key "8b246c57e1eb7ccec7705ce7889144292c37e02bec747da104b8981fdfb9fcfe") + (def local-wallet (Credentials/create pr-key)) + + (def inbox-contract (.send (Inbox/deploy web3-ganache + local-wallet + ;; this comes from the ganache settings + (wun/->big-integer 20000000000) + ;; eventually we required 302737 of gas + (wun/->big-integer 2000000) + "hello world"))) + + ;; get the message from contract + (def contract-message (.send (.getMessage inbox-contract))) + + ;; update the message + ;; who decides the gas price and gas limit on these transactions? + (.send (.setMessage inbox-contract "bye for now"))) diff --git a/src/web3clj/utils.clj b/src/web3clj/utils.clj index 802dc54..ca0dccf 100644 --- a/src/web3clj/utils.clj +++ b/src/web3clj/utils.clj @@ -1,6 +1,7 @@ (ns web3clj.utils (:import [java.math BigDecimal] - [org.web3j.utils Convert Convert$Unit])) + [org.web3j.utils Convert Convert$Unit] + (kotlin.jvm.functions Function0))) (def WEI Convert$Unit/WEI) (def KWEI Convert$Unit/KWEI) @@ -54,3 +55,12 @@ (defn wei->kether [kether-amount] (<-wei kether-amount KETHER)) (defn wei->mether [mether-amount] (<-wei mether-amount METHER)) (defn wei->gether [gether-amount] (<-wei gether-amount GETHER)) + + +(defn func0 + "A wrapper around the Function0 interface" + [s] + (reify Function0 + (invoke + [this] + s)))