From f24690694df95df696da74d51c790aedab83a8fc Mon Sep 17 00:00:00 2001 From: "Craig B. Ludington" Date: Thu, 12 Oct 2017 09:16:16 -0500 Subject: [PATCH] Upgrade for solc 0.4.16 and TestRPC v4.1.3 * Solc ** The format of the JSON emitted by solc changed between version 0.4.1 and 0.4.16. The keys for contracts used to be the contract name but now they include the file name. Old: "SimpleToken", new: "path/to/SimpleToken:SimpleToken". ** cloth.contracts/compile-solidity accepts the newer version of solc JSON output ** The new version of compile-solidity accepts solc 0.4.1 output as well as 0.4.16 output. ** There's an optional compile-solidity argument, `ignore-warnings?` that treats the compilation as successful when there are warnings but solc exit status is zero. If solc returns a non-zero status, compile-solidity fails either way. * TestRPC ** The default `testrpc` gasPrice is very large (20000000000) which caused many test failures. Some failed due to out-of-gas errors. Others failed because they explicitly tested for gasPrice of 1. ** Starting TestRPC with `--gasPrice 1` allows the tests to pass. --- README.md | 6 +++++ src/cloth/contracts.cljc | 54 +++++++++++++++++++++++++++----------- test/cloth/Constructed.sol | 3 ++- test/cloth/SimpleToken.sol | 20 +++++++------- test/cloth/core_test.cljc | 2 +- 5 files changed, 58 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c556149..7d3b9f5 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,10 @@ The `cloth.contracts` namespace allows you to compile solidity code and create c (defcontract simple-token "test/cloth/SimpleToken.sol") +;; The contract name argument must be the skewer-case version of the Solidity contract name. +;; In the example above, the use of `simple-token` means there's a contract named `SimpleToken` in SimpleToken.sol file. +;; Any solc compiler warnings are treated as an error; an optional `ignore-warnings?` argument to `defcontract` over-rides the default behavior. + ;; For simplicity sake the rest of the examples use Clojure @ syntax for dereferencing Promises ;; Deploy the solidity code to the blockchain @@ -199,6 +203,8 @@ testrpc -b 1 Run `lein test` or `lein test-refresh`. +Tests assume gasPrice is 1, so use `testrpc -g 1 -b 1` + ### Clojurescript tests We use [doo](https://github.com/bensu/doo) diff --git a/src/cloth/contracts.cljc b/src/cloth/contracts.cljc index c4a14ed..23d6585 100644 --- a/src/cloth/contracts.cljc +++ b/src/cloth/contracts.cljc @@ -16,11 +16,17 @@ #?(:clj - (defn compile-solidity [file] - (let [result (shell/sh "solc" "--combined-json" "abi,bin" file)] - (if (not (c/blank? (:err result))) - (throw (ex-info (:err result) {:solidity file :exit (:exit result)})) - (json/parse-string (:out result) true))))) + (defn compile-solidity + ([file ignore-warnings?] + (let [result (shell/sh "solc" "--combined-json" "abi,bin" file) + failed? (if ignore-warnings? + (not (zero? (:exit result))) + (not (c/blank? (:err result))))] + (if failed? + (throw (ex-info (:err result) {:solidity file :exit (:exit result)})) + (json/parse-string (:out result) true)))) + ([file] + (compile-solidity file false)))) #?(:clj (defn abi->args [f] @@ -69,7 +75,28 @@ ] (str fncall const returns))) -;; (!) +#?(:clj + (defn compile-contract [contract file ignore-warnings?] + "Compile a Solidity file and return a map of functions, constructor, events, and the name for a deploy function. +Tries to be compatible with older versions of solc that didn't include Solidity file name in the output of \"solc --combined-json=abi,bin\"." + (let [{:keys [contracts]} (compile-solidity file ignore-warnings?) + solidity-name (c/capitalize (c/camel (name contract))) ;; convert, e.g. "simple-token" -> "SimpleToken" + {json-abi :abi binary :bin} (or (contracts (keyword solidity-name)) ;; old version of solc + (contracts (keyword (str file ":" solidity-name))) ;; newer version of solc + (throw (ex-info (str "Contract \"" solidity-name "\" not found in file \"" file "\".") + {:clojure-name contract + :solidity-name solidity-name}))) + abi (json/parse-string json-abi true) + functions (filter #(= (:type %) "function") abi) + constructor (first (filter #(= (:type %) "constructor") abi)) + events (filter #(= (:type %) "event") abi) + deploy-name (symbol (str "deploy-" (c/kebab (name solidity-name)) "!"))] + {:functions functions + :constructor constructor + :events events + :binary binary + :deploy-name deploy-name}))) + #?(:clj (defmacro defcontract "Compiles solidity and creates a set of functions in the current namespace: @@ -82,16 +109,13 @@ (add-device!! \"0x0sdsfafs...\" {}) + Parameters: + contract - the name of the contract converted to Clojure-style skewer-case (e.g. \"SimpleToken\" becomes simple-token) + file - the Solidity file pathname (a string) + ignore-warnings? - optional, compiler warnings are OK but a compiler error will raise an exception " - [contract file] - (let [compiled (compile-solidity file) - contract-key (keyword (c/capitalize (c/camel (name contract)))) - binary (get-in compiled [:contracts contract-key :bin]) - abi (json/parse-string (get-in compiled [:contracts contract-key :abi]) true) - functions (filter #(= (:type %) "function") abi) - constructor (first (filter #(= (:type %) "constructor") abi)) - events (filter #(= (:type %) "event") abi) - deploy-name (symbol (str "deploy-" (c/kebab (name contract)) "!"))] + [contract file & [ignore-warnings?]] + (let [{:keys [functions constructor events binary deploy-name]} (compile-contract contract file ignore-warnings?)] `(do (defn ~deploy-name [ & args# ] (deploy-contract ~binary ~constructor args#)) diff --git a/test/cloth/Constructed.sol b/test/cloth/Constructed.sol index 43e9bee..126ed14 100644 --- a/test/cloth/Constructed.sol +++ b/test/cloth/Constructed.sol @@ -1,4 +1,5 @@ -pragma solidity ^0.4.1; +pragma solidity ^0.4.16; + contract Constructed { string public status; diff --git a/test/cloth/SimpleToken.sol b/test/cloth/SimpleToken.sol index f3db421..9ed6d71 100644 --- a/test/cloth/SimpleToken.sol +++ b/test/cloth/SimpleToken.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.1; +pragma solidity ^0.4.16; contract SimpleToken { uint32 public circulation; address public issuer; @@ -9,7 +9,7 @@ contract SimpleToken { bytes32 public ipfs; event Issued(address indexed recipient, uint32 amount); - event Message(address indexed shouter, string message); + event Message(address indexed shouter, string _message); event Transferred(address indexed sender, address indexed recipient, uint32 amount); function SimpleToken() { @@ -21,17 +21,17 @@ contract SimpleToken { modifier authorizedCustomer(address customer) { if (authorized[customer] != 0 ) _ ; } modifier unAuthorizedCustomer(address customer) { if (authorized[customer] == 0 ) _ ; } - function customer(address customer) constant + function customer(address _customer) constant returns(uint authorizedTime, uint32 balance) { - return (authorized[customer], balances[customer]); + return (authorized[_customer], balances[_customer]); } - function authorize(address customer) public + function authorize(address _customer) public onlyIssuer - unAuthorizedCustomer(customer) + unAuthorizedCustomer(_customer) returns(bool success) { - authorized[customer] = block.timestamp; - customers.push(customer); + authorized[_customer] = block.timestamp; + customers.push(_customer); return true; } @@ -45,9 +45,9 @@ contract SimpleToken { } function setMessage(string _message) - public returns(string message) { + public returns(string) { message = _message; - Message(msg.sender,message); + Message(msg.sender,_message); return message; } diff --git a/test/cloth/core_test.cljc b/test/cloth/core_test.cljc index 10f93cc..58008b2 100644 --- a/test/cloth/core_test.cljc +++ b/test/cloth/core_test.cljc @@ -158,7 +158,7 @@ (deftest sign-with-signer-proxy-test (let [keypair (keys/create-keypair)] (reset! core/global-signer keypair) - (c/defcontract proxy "test/cloth/Proxy.sol") + (c/defcontract proxy "test/cloth/Proxy.sol" :ignore-compiler-warnings) (let [recipient (:address (keys/create-keypair))] #?(:cljs