diff --git a/Dockerfile b/Dockerfile index ce2947a..83164d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,12 @@ RUN lein uberjar FROM clojure WORKDIR /root/ +RUN apt-get update +RUN apt-get -y install xvfb +RUN apt-get -y install wkhtmltopdf + COPY --from=builder /usr/src/app/target/uberjar/commiteth.jar . +COPY html2png.sh . CMD [""] ENTRYPOINT ["/usr/bin/java", "-Duser.timezone=UTC", "-Dconf=config-test.edn", "-jar", "/root/commiteth.jar"] diff --git a/Jenkinsfile b/Jenkinsfile index 219d449..4b24c0e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -36,4 +36,4 @@ def dockerreponame = "statusim/openbounty-app" // slackSend color: 'bad', message: REPO + ":" + BRANCH_NAME + ' failed to build. ' + env.BUILD_URL throw e } -} \ No newline at end of file +} diff --git a/README.md b/README.md index de5fd30..ced4f88 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Status Open Bounty +[![Riot Chat Badge](https://img.shields.io/badge/join%20%23openbounty-riot-green.svg)](https://chat.status.im/#/room/#openbounty:status.im) Allows you to set bounties for Github issues, paid out in Ether or any ERC-20 token. @@ -71,6 +72,7 @@ eth-account | Ethereum account ID for the bot eth-password | Ethereum account password for the bot eth-rpc-url | RPC URL to Ethereum node, e.g. Geth. Either local or remote eth-wallet-file | Location of wallet file. If Geth is run with the parameters as given below, it will reside under `$HOME/.ropsten/keystore` +offline-signing | Specifies whether to sign transactions locally before sending. Default is true. Set to false when connecting to local Geth node that unlocks accounts tokenreg-base-format | Should be set to `:status` github-client-id | Related to OAuth. Copied from GitHub account Settings->Developer settings->OAuth Apps github-client-secret | Related to OAuth. Copied from GitHub account Settings->Developer settings->OAuth Apps @@ -91,14 +93,22 @@ Follow the steps [here](https://developer.github.com/apps/building-github-apps/c ## Running -### Geth -Launch a local geth node with the bot account unlocked: +### Ethereum node +There are two options for connecting to an Ethereum node: either run a local node with an unlocked account, or connect to a remote Geth node or Infura. We will be connecting to Ropsten which is a test Ethereum network. + +#### Local + +In order to launch a local geth node with the bot account unlocked issue the following command: ``` #!/bin/bash geth --fast --testnet --cache=1024 --datadir=$HOME/.ropsten --verbosity 4 --port 50100 --ipcpath ~/.ropsten/geth.ipc --rpc --rpcaddr 127.0.0.1 --rpcport 8545 --rpcapi db,eth,net,web3,personal --rpccorsdomain "https://wallet.ethereum.org" --unlock "0xYOUR_ADDR" --password <(echo "YOUR_PASSPHRASE") ``` +#### Remote +Register at [Infura](https://infura.io/signup). You will receive an email with provider URLs. Paste an URL for the Ropsten network into `config.edn` under `:eth-rpc-url` key, and set `:offline-signing` to true. + + ### CSS auto-compilation Launch the following command in a separate shell: diff --git a/env/dev/resources/config.edn b/env/dev/resources/config.edn index 571a6ae..1f8248f 100644 --- a/env/dev/resources/config.edn +++ b/env/dev/resources/config.edn @@ -14,6 +14,9 @@ :eth-rpc-url "http://localhost:8545" :eth-wallet-file "/some/location" + ;; Specifies whether to sign transactions before sending. Should be false for local Geth, true for remote Infura + :offline-signing true + ;; address of token registry to be used ;; this is the default value for ropsten :tokenreg-addr "0x7d127a3e3b5e72cd8f15e7dee650abe4fcced2b9" diff --git a/html2png.sh b/html2png.sh new file mode 100755 index 0000000..781d8d5 --- /dev/null +++ b/html2png.sh @@ -0,0 +1,3 @@ +#!/bin/bash -eu +# need to run wkhtmltoimage in quiet mode because it misuses stdout +xvfb-run -a -s "-screen 0 640x480x16" wkhtmltoimage -q "$@" 2>/dev/null diff --git a/src/clj/commiteth/eth/core.clj b/src/clj/commiteth/eth/core.clj index c4c8fa5..00daaeb 100644 --- a/src/clj/commiteth/eth/core.clj +++ b/src/clj/commiteth/eth/core.clj @@ -7,14 +7,65 @@ [clojure.tools.logging :as log] [clojure.string :as str] [pandect.core :as pandect] - [commiteth.util.util :refer [json-api-request]])) + [commiteth.util.util :refer [json-api-request]]) + (:import [org.web3j + protocol.Web3j + protocol.http.HttpService + protocol.core.DefaultBlockParameterName + protocol.core.methods.response.EthGetTransactionCount + protocol.core.methods.request.RawTransaction + utils.Numeric + crypto.Credentials + crypto.TransactionEncoder + crypto.WalletUtils])) (defn eth-rpc-url [] (env :eth-rpc-url "http://localhost:8545")) (defn eth-account [] (:eth-account env)) (defn eth-password [] (:eth-password env)) (defn gas-estimate-factor [] (env :gas-estimate-factor 1.0)) -(defn auto-gas-price? [] (env :auto-gas-price? false)) +(defn auto-gas-price? [] (env :auto-gas-price false)) +(defn offline-signing? [] (env :offline-signing true)) +(defn wallet-file-path [] + (env :eth-wallet-file)) + +(defn wallet-password [] + (env :eth-password)) + +(defn creds [] + (let [password (wallet-password) + file-path (wallet-file-path)] + (if (and password file-path) + (WalletUtils/loadCredentials + password + file-path) + (throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn" + {:password password :file-path file-path}))))) + +(defn create-web3j [] + (Web3j/build (HttpService. (eth-rpc-url)))) + +(defn get-signed-tx [gas-price gas-limit to data] + "Create a sign a raw transaction. + 'From' argument is not needed as it's already + encoded in credentials. + See https://web3j.readthedocs.io/en/latest/transactions.html#offline-transaction-signing" + (let [web3j (create-web3j) + nonce (.. (.ethGetTransactionCount web3j + (env :eth-account) + DefaultBlockParameterName/LATEST) + sendAsync + get + getTransactionCount) + tx (RawTransaction/createTransaction + nonce + gas-price + gas-limit + to + data) + signed (TransactionEncoder/signMessage tx (creds)) + hex-string (Numeric/toHexString signed)] + hex-string)) (defn eth-gasstation-gas-price [] (let [data (json-api-request "https://ethgasstation.info/json/ethgasAPI.json") @@ -40,6 +91,21 @@ (gas-price-from-config))) (gas-price-from-config))) +(defn safe-read-str [s] + (if (nil? s) + (do + (log/error "JSON response is nil") + nil) + (try + (json/read-str s :key-fn keyword) + (catch Exception ex + (do (log/error "Exception when parsing json string:" + s + "message:" + ex) + + nil))))) + (defn eth-rpc [method params] (let [request-id (rand-int 4096) @@ -49,8 +115,8 @@ :id request-id}) options {:headers {"content-type" "application/json"} :body body} - response (:body @(post (eth-rpc-url) options)) - result (json/read-str response :key-fn keyword)] + response @(post (eth-rpc-url) options) + result (safe-read-str (:body response))] (log/debug body "\n" result) (if (= (:id result) request-id) @@ -128,54 +194,16 @@ [account digits] (hex->eth (get-balance-hex account) digits)) -(defn send-transaction - "Send transaction using default commiteth bot account." - [from to value & [params]] - (let [args (merge params - {:from from - :value value} - (when-not (nil? (gas-price)) - {:gasPrice (integer->hex (gas-price))}) - (when-not (contains? params :gas) - {:gas - (estimate-gas from to value params)}))] - (log/debug "args:" args) - (eth-rpc - "personal_sendTransaction" - [(if-not (nil? to) - (merge args {:to to}) - args) - (eth-password)]))) - -(defn send-transaction-using-from-account - "Send transaction using account address in parameter from. Assumes - account has been unlocked." - [from to value & [params]] - (let [args (merge params - {:from from - :value value} - (when-not (nil? (gas-price)) - {:gasPrice (integer->hex (gas-price))}) - (when-not (contains? params :gas) - {:gas - (estimate-gas from to value params)}))] - (log/debug "args:" args) - (eth-rpc - "eth_sendTransaction" - [(if-not (nil? to) - (merge args {:to to}) - args)]))) - -(defn get-transaction-receipt - [hash] - (eth-rpc "eth_getTransactionReceipt" [hash])) - (defn- format-param [param] (if (number? param) (format "%064x" param) (clojure.string/replace (format "%64s" (subs param 2)) " " "0"))) +(defn get-transaction-receipt + [hash] + (eth-rpc "eth_getTransactionReceipt" [hash])) + (defn format-call-params [method-id & params] (let [params (join (map format-param params))] @@ -189,33 +217,30 @@ (defn execute [from contract method-id gas-limit & params] (let [data (apply format-call-params method-id params) - value (format "0x%x" 0)] - (send-transaction from contract value (merge - {:data data} - (when gas-limit - {:gas gas-limit}))))) - -(defn execute-using-addr - [from-addr from-passphrase contract method-id & params] - (eth-rpc "personal_unlockAccount" [from-addr from-passphrase 30]) - (let [data (apply format-call-params method-id params) - value (format "0x%x" 0)] - (send-transaction-using-from-account from-addr - contract - value - {:data data}))) - -(defn transfer-eth - "Transfer amount-wei of ETH from from-addr to to-addr." - [from-addr from-passphrase to-addr amount-wei] - (eth-rpc "personal_unlockAccount" [from-addr from-passphrase 30]) - (let [data "0x" - value (integer->hex amount-wei)] - (send-transaction-using-from-account from-addr - to-addr - value - {:data data}))) - + gas-price (gas-price) + value (format "0x%x" 0) + params (cond-> {:data data + :from from + :value value} + gas-price + (merge {:gasPrice (integer->hex gas-price)}) + contract + (merge {:to contract})) + gas (if gas-limit gas-limit + (estimate-gas from contract value params)) + params (if (offline-signing?) + (get-signed-tx (biginteger gas-price) + (hex->big-integer gas) + contract + data) + (assoc params :gas gas))] + (if (offline-signing?) + (eth-rpc + "eth_sendRawTransaction" + [params]) + (eth-rpc + "personal_sendTransaction" + [params (eth-password)])))) (defn hex-ch->num [ch] diff --git a/src/clj/commiteth/eth/multisig_wallet.clj b/src/clj/commiteth/eth/multisig_wallet.clj index f5393ab..48a42a9 100644 --- a/src/clj/commiteth/eth/multisig_wallet.clj +++ b/src/clj/commiteth/eth/multisig_wallet.clj @@ -1,8 +1,7 @@ (ns commiteth.eth.multisig-wallet - (:require [commiteth.eth.core :as eth] - [commiteth.config :refer [env]] - [commiteth.eth.web3j + (:require [commiteth.eth.core :as eth :refer [create-web3j creds]] + [commiteth.config :refer [env]] [clojure.tools.logging :as log] [commiteth.eth.token-data :as token-data]) (:import [org.web3j @@ -191,30 +190,6 @@ [tla (token-balance bounty-addr tla)]))) addrs))) {}))) -(defn transfer-tokens - "Transfer mount of given ERC20 token from from-addr to - to-addr. Connected geth needs to have keys for the account and - passphrase needs to be supplied. Returns transaction ID." - [from-addr from-passphrase token to-addr amount] - (let [token-addr (get-token-address token)] - (eth/execute-using-addr from-addr - from-passphrase - token-addr - (method-ids :transfer) - to-addr - amount))) - -(defn get-owners - "Return vector of multisig owner addresses." - [bounty-addr] - (let [bounty-contract (load-bounty-contract bounty-addr) - owner-addresses (-> bounty-contract - (.getOwners) - .get)] - (if owner-addresses - (mapv #(.toString %) (.getValue owner-addresses)) - []))) - (defn uint256 [x] (org.web3j.abi.datatypes.generated.Uint256. x)) diff --git a/src/clj/commiteth/eth/token_data.clj b/src/clj/commiteth/eth/token_data.clj index 06703f3..680953c 100644 --- a/src/clj/commiteth/eth/token_data.clj +++ b/src/clj/commiteth/eth/token_data.clj @@ -15,7 +15,7 @@ (defn update-data [] (let [test-data (env :testnet-token-data) token-data - (if (and (env :on-testnet true) + (if (and #_(env :on-testnet true) test-data) test-data (into {} diff --git a/src/clj/commiteth/eth/token_registry.clj b/src/clj/commiteth/eth/token_registry.clj index 2d60cc9..9135f8d 100644 --- a/src/clj/commiteth/eth/token_registry.clj +++ b/src/clj/commiteth/eth/token_registry.clj @@ -1,6 +1,5 @@ (ns commiteth.eth.token-registry - (:require [commiteth.eth.core :as eth] - [commiteth.eth.web3j + (:require [commiteth.eth.core :as eth :refer [create-web3j creds]] [commiteth.config :refer [env]] [clojure.tools.logging :as log]) diff --git a/src/clj/commiteth/eth/web3j.clj b/src/clj/commiteth/eth/web3j.clj deleted file mode 100644 index 2bd33df..0000000 --- a/src/clj/commiteth/eth/web3j.clj +++ /dev/null @@ -1,28 +0,0 @@ -(ns commiteth.eth.web3j - (:require [commiteth.eth.core :as eth] - [commiteth.config :refer [env]]) - (:import [org.web3j - protocol.Web3j - protocol.http.HttpService - crypto.Credentials - crypto.WalletUtils])) - - -(defn wallet-file-path [] - (env :eth-wallet-file)) - -(defn wallet-password [] - (env :eth-password)) - -(defn creds [] - (let [password (wallet-password) - file-path (wallet-file-path)] - (if (and password file-path) - (WalletUtils/loadCredentials - password - file-path) - (throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn" - {:password password :file-path file-path}))))) - -(defn create-web3j [] - (Web3j/build (HttpService. (eth/eth-rpc-url)))) diff --git a/src/clj/commiteth/github/core.clj b/src/clj/commiteth/github/core.clj index f6b4ea9..93a4f56 100644 --- a/src/clj/commiteth/github/core.clj +++ b/src/clj/commiteth/github/core.clj @@ -3,6 +3,7 @@ [core :as tentacles] [repos :as repos] [oauth :as oauth] + [search :as search] [users :as users] [repos :as repos] [issues :as issues] @@ -27,6 +28,8 @@ (defn on-testnet? [] (env :on-testnet)) (defn webhook-secret [] (env :webhook-secret)) + + (defn authorize-url [scope] (let [params (codec/form-encode {:client_id (client-id) :redirect_uri (redirect-uri) @@ -360,3 +363,32 @@ (let [[owner repo] (str/split full-repo #"/")] (log/debug "creating bounty label" (str owner "/" repo) token) (issues/create-label owner repo "bounty" "fafad2" (auth-params token)))) + +(defn get-labeled-issues-for-repos [repos auth-params] + "Find all issues with a bounty label in provided repos" + (let [get-last-part (fn get-last-part [s] + (subs s (inc (str/last-index-of s "/")))) + get-issue-info (fn get-issue-info [r issue] + (hash-map :owner r + :repo (get-last-part (:repository_url issue)) + :id (:id issue) + :number (:number issue) + :title (:title issue) + :url (:html_url issue) + :created_at (:created_at issue) + :closed_at (:closed_at issue))) + issues (for [r repos] + (loop [repo-issues [] i 1] + (let [params (into auth-params + {:sort "created" :order "desc" :page i}) + issues-page (-> (search/search-issues + nil + {:label "bounty" :user r :type "issue"} + params) + :items)] + (if (first issues-page) + (recur (into repo-issues + (map (partial get-issue-info r) issues-page)) + (inc i)) + repo-issues))))] + (apply concat issues))) diff --git a/src/clj/commiteth/routes/redirect.clj b/src/clj/commiteth/routes/redirect.clj index 01018fa..6aaaf2e 100644 --- a/src/clj/commiteth/routes/redirect.clj +++ b/src/clj/commiteth/routes/redirect.clj @@ -39,6 +39,7 @@ body (keywordize-keys (codec/form-decode (:body resp))) scope (:scope body) access-token (:access_token body)] + (log/info "access-token:" access-token) (log/debug "github sign-in callback, response body:" body) (if (:error body) ;; Why does Mist browser sends two redirects at the same time? The latter results in 401 error. diff --git a/src/clj/commiteth/routes/services.clj b/src/clj/commiteth/routes/services.clj index d813962..435b172 100644 --- a/src/clj/commiteth/routes/services.clj +++ b/src/clj/commiteth/routes/services.clj @@ -146,26 +146,40 @@ (defn prettify-bounty-items [bounty-items] - (let [renames {:user_name :display-name - :user_avatar_url :avatar-url - :issue_title :issue-title - :type :item-type - :repo_name :repo-name - :repo_owner :repo-owner - :issue_number :issue-number - :value_usd :value-usd - :claim_count :claim-count - :balance_eth :balance-eth - :user_has_address :user-has-address}] + (let [format-float (fn [bounty balance] + (try + (format "%.2f" (double balance)) + (catch Throwable ex + (log/error (str (:repo-owner bounty) + "/" + (:repo-name bounty) + "/" + (:issue-number bounty)) + "Failed to convert token value:" balance) + "0.00"))) + update-token-values (fn [bounty] + (->> bounty + :tokens + (map (fn [[tla balance]] + [tla (format-float bounty balance)])) + (into {}) + (assoc bounty :tokens))) + renames {:user_name :display-name + :user_avatar_url :avatar-url + :issue_title :issue-title + :type :item-type + :repo_name :repo-name + :repo_owner :repo-owner + :issue_number :issue-number + :value_usd :value-usd + :claim_count :claim-count + :balance_eth :balance-eth + :user_has_address :user-has-address}] (map #(-> % (rename-keys renames) (update :value-usd usd-decimal->str) (update :balance-eth eth-decimal->str) - (update :tokens (fn [tokens] - (into {} - (map (fn [[tla balance]] - [tla (format "%.2f" balance)]) - tokens))))) + update-token-values) bounty-items))) (defn activity-feed [] diff --git a/src/clj/commiteth/scheduler.clj b/src/clj/commiteth/scheduler.clj index 0c35df3..f9c25dd 100644 --- a/src/clj/commiteth/scheduler.clj +++ b/src/clj/commiteth/scheduler.clj @@ -15,43 +15,50 @@ [clj-time.periodic :refer [periodic-seq]] [chime :refer [chime-at]])) + (defn update-issue-contract-address "For each pending deployment: gets transaction receipt, updates db state (contract-address, comment-id) and posts github comment" [] + (log/info "In update-issue-contract-address") (doseq [{issue-id :issue_id transaction-hash :transaction_hash} (issues/list-pending-deployments)] - (log/debug "pending deployment:" transaction-hash) - (when-let [receipt (eth/get-transaction-receipt transaction-hash)] - (log/info "update-issue-contract-address: transaction receipt for issue #" - issue-id ": " receipt) - (if-let [contract-address (multisig/find-created-multisig-address receipt)] - (let [issue (issues/update-contract-address issue-id contract-address) - {owner :owner - repo :repo - comment-id :comment_id - issue-number :issue_number} issue - balance-eth-str (eth/get-balance-eth contract-address 6) - balance-eth (read-string balance-eth-str)] - (log/info "Updating comment image") - (bounties/update-bounty-comment-image issue-id - owner - repo - issue-number - contract-address - balance-eth - balance-eth-str - {}) - (log/info "Updating comment") - (github/update-comment owner - repo - comment-id - issue-number - contract-address - balance-eth - balance-eth-str - {})) - (log/error "Failed to find contract address in tx logs"))))) + (log/info "pending deployment:" transaction-hash) + (try + (when-let [receipt (eth/get-transaction-receipt transaction-hash)] + (log/info "update-issue-contract-address: transaction receipt for issue #" + issue-id ": " receipt) + (if-let [contract-address (multisig/find-created-multisig-address receipt)] + (let [issue (issues/update-contract-address issue-id contract-address) + {owner :owner + repo :repo + comment-id :comment_id + issue-number :issue_number} issue + balance-eth-str (eth/get-balance-eth contract-address 6) + balance-eth (read-string balance-eth-str)] + (log/info "Updating comment image") + (bounties/update-bounty-comment-image issue-id + owner + repo + issue-number + contract-address + balance-eth + balance-eth-str + {}) + (log/info "Updating comment") + (github/update-comment owner + repo + comment-id + issue-number + contract-address + balance-eth + balance-eth-str + {})) + (log/error "Failed to find contract address in tx logs"))) + (catch Throwable ex + (do (log/error "update-issue-contract-address exception:" ex) + (clojure.stacktrace/print-stack-trace ex))))) + (log/info "Exit update-issue-contract-address")) (defn deploy-contract [owner-address issue-id] @@ -85,6 +92,7 @@ (defn self-sign-bounty "Walks through all issues eligible for bounty payout and signs corresponding transaction" [] + (log/info "In self-sign-bounty") (doseq [{contract-address :contract_address issue-id :issue_id payout-address :payout_address @@ -94,35 +102,42 @@ issue-number :issue_number balance-eth :balance_eth tokens :tokens - winner-login :winner_login} (db-bounties/pending-bounties) - :let [value (eth/get-balance-hex contract-address)]] - (if (empty? payout-address) - (do - (log/error "Cannot sign pending bounty - winner has no payout address") - (github/update-merged-issue-comment owner - repo - comment-id - contract-address - (eth-decimal->str balance-eth) - tokens - winner-login - true)) - (let [execute-hash (multisig/send-all contract-address payout-address)] - (log/info "Payout self-signed, called sign-all(" contract-address payout-address ") tx:" execute-hash) - (db-bounties/update-execute-hash issue-id execute-hash) - (db-bounties/update-winner-login issue-id winner-login) - (github/update-merged-issue-comment owner - repo - comment-id - contract-address - (eth-decimal->str balance-eth) - tokens - winner-login - false))))) + winner-login :winner_login} (db-bounties/pending-bounties)] + (try + (let [value (eth/get-balance-hex contract-address)] + (if (empty? payout-address) + (do + (log/error "Cannot sign pending bounty - winner has no payout address") + (github/update-merged-issue-comment owner + repo + comment-id + contract-address + (eth-decimal->str balance-eth) + tokens + winner-login + true)) + (let [execute-hash (multisig/send-all contract-address payout-address)] + (log/info "Payout self-signed, called sign-all(" contract-address payout-address ") tx:" execute-hash) + (db-bounties/update-execute-hash issue-id execute-hash) + (db-bounties/update-winner-login issue-id winner-login) + (github/update-merged-issue-comment owner + repo + comment-id + contract-address + (eth-decimal->str balance-eth) + tokens + winner-login + false)))) + (catch Throwable ex + (do (log/error "self-sign-bounty exception:" ex) + (clojure.stacktrace/print-stack-trace ex))))) + (log/info "Exit self-sign-bounty") + ) (defn update-confirm-hash "Gets transaction receipt for each pending payout and updates DB confirm_hash with tranaction ID of commiteth bot account's confirmation." [] + (log/info "In update-confirm-hash") (doseq [{issue-id :issue_id execute-hash :execute_hash} (db-bounties/pending-payouts)] (log/info "pending payout:" execute-hash) @@ -130,7 +145,8 @@ (log/info "execution receipt for issue #" issue-id ": " receipt) (when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)] (log/info "confirm hash:" confirm-hash) - (db-bounties/update-confirm-hash issue-id confirm-hash))))) + (db-bounties/update-confirm-hash issue-id confirm-hash)))) + (log/info "Exit update-confirm-hash")) (defn update-watch-hash @@ -154,6 +170,7 @@ (defn update-payout-receipt "Gets transaction receipt for each confirmed payout and updates payout_hash" [] + (log/info "In update-payout-receipt") (doseq [{issue-id :issue_id payout-hash :payout_hash contract-address :contract_address @@ -167,32 +184,38 @@ payee-login :payee_login updated :updated} (db-bounties/confirmed-payouts)] (log/debug "confirmed payout:" payout-hash) - (if-let [receipt (eth/get-transaction-receipt payout-hash)] - (let [contract-tokens (multisig/token-balances contract-address) - contract-eth-balance (eth/get-balance-wei contract-address)] - (if (or - (some #(> (second %) 0.0) contract-tokens) - (> contract-eth-balance 0)) - (do - (log/info "Contract still has funds") - (when (multisig/is-confirmed? contract-address confirm-id) - (log/info "Detected bounty with funds and confirmed payout, calling executeTransaction") - (let [execute-tx-hash (multisig/execute-tx contract-address confirm-id)] - (log/info "execute tx:" execute-tx-hash)))) + (try + (if-let [receipt (eth/get-transaction-receipt payout-hash)] + (let [contract-tokens (multisig/token-balances contract-address) + contract-eth-balance (eth/get-balance-wei contract-address)] + (if (or + (some #(> (second %) 0.0) contract-tokens) + (> contract-eth-balance 0)) + (do + (log/info "Contract still has funds") + (when (multisig/is-confirmed? contract-address confirm-id) + (log/info "Detected bounty with funds and confirmed payout, calling executeTransaction") + (let [execute-tx-hash (multisig/execute-tx contract-address confirm-id)] + (log/info "execute tx:" execute-tx-hash)))) - (do - (log/info "Payout has succeeded, saving payout receipt for issue #" issue-id ": " receipt) - (db-bounties/update-payout-receipt issue-id receipt) - (github/update-paid-issue-comment owner - repo - comment-id - contract-address - (eth-decimal->str balance-eth) - tokens - payee-login)))) - (when (older-than-3h? updated) - (log/info "Resetting payout hash for issue" issue-id "as it has not been mined in 3h") - (db-bounties/reset-payout-hash issue-id))))) + (do + (log/info "Payout has succeeded, saving payout receipt for issue #" issue-id ": " receipt) + (db-bounties/update-payout-receipt issue-id receipt) + (github/update-paid-issue-comment owner + repo + comment-id + contract-address + (eth-decimal->str balance-eth) + tokens + payee-login)))) + (when (older-than-3h? updated) + (log/info "Resetting payout hash for issue" issue-id "as it has not been mined in 3h") + (db-bounties/reset-payout-hash issue-id))) + (catch Throwable ex + (do (log/error "update-payout-receipt exception:" ex) + (clojure.stacktrace/print-stack-trace ex))))) + (log/info "Exit update-payout-receipt") + ) (defn abs "(abs n) is the absolute value of n" @@ -213,17 +236,24 @@ (defn update-bounty-token-balances "Helper function for updating internal ERC20 token balances to token multisig contract. Will be called periodically for all open bounty contracts." [issue-id bounty-addr watch-hash] + #_(log/info "In update-bounty-token-balances for issue" issue-id) (doseq [[tla token-data] (token-data/as-map)] - (let [balance (multisig/token-balance bounty-addr tla)] - (when (> balance 0) - (do - (log/debug "bounty at" bounty-addr "has" balance "of token" tla) - (let [internal-balance (multisig/token-balance-in-bounty bounty-addr tla)] - (when (and (nil? watch-hash) - (not= balance internal-balance)) - (log/info "balances not in sync, calling watch") - (let [hash (multisig/watch-token bounty-addr tla)] - (db-bounties/update-watch-hash issue-id hash))))))))) + (try + (let [balance (multisig/token-balance bounty-addr tla)] + (when (> balance 0) + (do + (log/info "bounty at" bounty-addr "has" balance "of token" tla) + (let [internal-balance (multisig/token-balance-in-bounty bounty-addr tla)] + (when (and (nil? watch-hash) + (not= balance internal-balance)) + (log/info "balances not in sync, calling watch") + (let [hash (multisig/watch-token bounty-addr tla)] + (db-bounties/update-watch-hash issue-id hash))))))) + (catch Throwable ex + (do (log/error "update-bounty-token-balances exception:" ex) + (clojure.stacktrace/print-stack-trace ex))))) + #_(log/info "Exit update-bounty-token-balances")) + (defn update-contract-internal-balances "It is required in our current smart contract to manually update it's internal balance when some tokens have been added." @@ -263,6 +293,7 @@ (defn update-balances [] + (log/info "In update-balances") (doseq [{contract-address :contract_address owner :owner repo :repo @@ -271,43 +302,48 @@ db-balance-eth :balance_eth db-tokens :tokens issue-number :issue_number} (db-bounties/open-bounty-contracts)] - (when comment-id - (let [balance-eth-str (eth/get-balance-eth contract-address 6) - balance-eth (read-string balance-eth-str) - token-balances (multisig/token-balances contract-address)] - (log/debug "update-balances" balance-eth - balance-eth-str token-balances owner repo issue-number) + (try + (when comment-id + (let [balance-eth-str (eth/get-balance-eth contract-address 6) + balance-eth (read-string balance-eth-str) + token-balances (multisig/token-balances contract-address)] + (log/debug "update-balances" balance-eth + balance-eth-str token-balances owner repo issue-number) - (when (or - (not (float= db-balance-eth balance-eth)) - (not= db-tokens token-balances)) - (log/debug "balances differ") - (log/debug "ETH (db):" db-balance-eth (type db-balance-eth) ) - (log/debug "ETH (chain):" balance-eth (type balance-eth) ) - (log/debug "ETH cmp:" (float= db-balance-eth balance-eth)) - (log/debug "tokens (db):" db-tokens (type db-tokens) (type (:SNT db-tokens))) - (log/debug "tokens (chain):" token-balances (type token-balances) (type (:SNT token-balances))) - (log/debug "tokens cmp:" (= db-tokens token-balances)) + (when (or + (not (float= db-balance-eth balance-eth)) + (not= db-tokens token-balances)) + (log/debug "balances differ") + (log/debug "ETH (db):" db-balance-eth (type db-balance-eth) ) + (log/debug "ETH (chain):" balance-eth (type balance-eth) ) + (log/debug "ETH cmp:" (float= db-balance-eth balance-eth)) + (log/debug "tokens (db):" db-tokens (type db-tokens) (type (:SNT db-tokens))) + (log/debug "tokens (chain):" token-balances (type token-balances) (type (:SNT token-balances))) + (log/debug "tokens cmp:" (= db-tokens token-balances)) - (issues/update-eth-balance contract-address balance-eth) - (issues/update-token-balances contract-address token-balances) - (bounties/update-bounty-comment-image issue-id - owner - repo - issue-number - contract-address - balance-eth - balance-eth-str - token-balances) - (github/update-comment owner - repo - comment-id - issue-number - contract-address - balance-eth - balance-eth-str - token-balances) - (update-issue-usd-value contract-address)))))) + (issues/update-eth-balance contract-address balance-eth) + (issues/update-token-balances contract-address token-balances) + (bounties/update-bounty-comment-image issue-id + owner + repo + issue-number + contract-address + balance-eth + balance-eth-str + token-balances) + (github/update-comment owner + repo + comment-id + issue-number + contract-address + balance-eth + balance-eth-str + token-balances) + (update-issue-usd-value contract-address)))) + (catch Throwable ex + (do (log/error "update-balances exception:" ex) + (clojure.stacktrace/print-stack-trace ex))))) + (log/info "Exit update-balances")) (defn wrap-in-try-catch [func] @@ -323,7 +359,7 @@ (defn run-1-min-interval-tasks [time] (do - (log/debug "run-1-min-interval-tasks" time) + (log/info "run-1-min-interval-tasks" time) ;; TODO: disabled for now. looks like it may cause extraneus ;; contract deployments and costs (run-tasks