Merge branch 'develop' into fix/remove-dep-on-older-web3j-and-solc

This commit is contained in:
Tetiana Churikova 2018-03-15 06:15:24 +02:00 committed by GitHub
commit 553dfd8fd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 451 additions and 330 deletions

View File

@ -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"]

2
Jenkinsfile vendored
View File

@ -36,4 +36,4 @@ def dockerreponame = "statusim/openbounty-app"
// slackSend color: 'bad', message: REPO + ":" + BRANCH_NAME + ' failed to build. ' + env.BUILD_URL
throw e
}
}
}

View File

@ -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.
@ -73,6 +74,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
@ -93,14 +95,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:

View File

@ -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"

3
html2png.sh Executable file
View File

@ -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

View File

@ -412,6 +412,14 @@ SELECT exists(SELECT 1
FROM issues
WHERE issue_id = :issue_id);
-- :name get-issue :? :1
-- :doc get issue from DB by repo-id and issue-number
SELECT issue_id, issue_number, is_open, winner_login, commit_sha
FROM issues
WHERE repo_id = :repo_id
AND issue_number = :issue_number;
-- :name open-bounties :? :*
-- :doc all open bounty issues

View File

@ -65,6 +65,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
src="https://www.facebook.com/tr?id=293089407869419&ev=PageView&noscript=1"
/></noscript>
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.1.8/semantic.min.css" rel="stylesheet" type="text/css" />
<link href="https://unpkg.com/tachyons@4.9.1/css/tachyons.min.css" rel="stylesheet" type="text/css" />
<link href="/css/style.css?v={{commiteth-version}}" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="/js/app.js?v={{commiteth-version}}"></script>
<script>

View File

@ -101,3 +101,9 @@
first
:exists
boolean)))
(defn get-issue
[repo-id issue-number]
(jdbc/with-db-connection [con-db *db*]
(db/get-issue con-db {:repo_id repo-id
:issue_number issue-number})))

View File

@ -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]

View File

@ -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))

View File

@ -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 {}

View File

@ -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])

View File

@ -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))))

View File

@ -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)
@ -216,7 +219,7 @@
(str "Tokens: "
(str/join " " (map (fn [[tla balance]] (format "%s: %.2f"
(subs (str tla) 1)
(float balance)))
(double balance)))
token-balances))
"\n")))
@ -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)))

View File

@ -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.

View File

@ -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 []

View File

@ -109,26 +109,17 @@
(extract pr-title))))
(defn ensure-bounty-issue
"Checks if an issue has a bounty label attached and returns its number"
[user repo issue-number]
(when-let [issue (github/get-issue user repo issue-number)]
(when (bounties/has-bounty-label? issue)
issue-number)))
(defn handle-claim
[user-id login name avatar_url owner repo repo-id bounty-issue-number pr-id pr-number head-sha merged? event-type]
[issue user-id login name avatar_url owner repo repo-id pr-id pr-number head-sha merged? event-type]
(users/create-user user-id login name nil avatar_url)
(let [issue (github/get-issue owner repo bounty-issue-number)
open-or-edit? (contains? #{:opened :edited} event-type)
(let [open-or-edit? (contains? #{:opened :edited} event-type)
close? (= :closed event-type)
pr-data {:repo_id repo-id
:pr_id pr-id
:pr_number pr-number
:user_id user-id
:issue_number bounty-issue-number
:issue_id (:id issue)
:issue_number (:issue_number issue)
:issue_id (:issue_id issue)
:state event-type}]
;; TODO: in the opened case if the submitting user has no
@ -138,18 +129,18 @@
(cond
open-or-edit? (do
(log/info "PR with reference to bounty issue"
bounty-issue-number "opened")
(:issue_number issue) "opened")
(pull-requests/save (merge pr-data {:state :opened
:commit_sha head-sha})))
close? (if merged?
(do (log/info "PR with reference to bounty issue"
bounty-issue-number "merged")
(:issue_number issue) "merged")
(pull-requests/save
(merge pr-data {:state :merged
:commit_sha head-sha}))
(issues/update-commit-sha (:id issue) head-sha))
(issues/update-commit-sha (:issue_id issue) head-sha))
(do (log/info "PR with reference to bounty issue"
bounty-issue-number "closed with no merge")
(:issue_number issue) "closed with no merge")
(pull-requests/save
(merge pr-data {:state :closed
:commit_sha head-sha})))))))
@ -177,26 +168,27 @@
pr-number :number
pr-body :body
pr-title :title} :pull_request}]
(log/debug "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title)
(log/debug (extract-issue-number pr-body pr-title))
(if-let [bounty-issue-number (->>
(extract-issue-number pr-body pr-title)
(first)
(ensure-bounty-issue owner repo))]
(do
(log/debug "Referenced bounty issue found" owner repo bounty-issue-number)
(handle-claim user-id
login name
avatar_url
owner repo
repo-id
bounty-issue-number
pr-id
pr-number
head-sha
merged?
event-type))
(log/info "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title)
(if-let [issue (some->> (extract-issue-number pr-body pr-title)
(first)
(issues/get-issue repo-id))]
(if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue
(do
(log/info "Referenced bounty issue found" owner repo (:issue_number issue))
(handle-claim issue
user-id
login name
avatar_url
owner repo
repo-id
pr-id
pr-number
head-sha
merged?
event-type))
(log/info "PR for issue already merged"))
(when (= :edited event-type)
; Remove PR if it does not reference any issue
(pull-requests/remove pr-id))))

View File

@ -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"
@ -203,27 +226,28 @@
(neg? n) (- n)
:else n))
(defn float=
([x y] (float= x y 0.0000001))
([x y epsilon]
(log/debug x y epsilon)
(let [scale (if (or (zero? x) (zero? y)) 1 (abs x))]
(<= (abs (- x y)) (* scale epsilon)))))
(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."
@ -261,8 +285,20 @@
(db-bounties/open-bounty-contracts)]
(update-issue-usd-value bounty-addr)))
(defn float=
([x y] (float= x y 0.0000001))
([x y epsilon]
(log/debug x y epsilon)
(let [scale (if (or (zero? x) (zero? y)) 1 (abs x))]
(<= (abs (- x y)) (* scale epsilon)))))
(defn map-float= [m1 m2]
(and (= (set (keys m1)) (set (keys m2)))
(every? #(float= (get m1 %1) (get m2 %1)) (keys m1))))
(defn update-balances
[]
(log/info "In update-balances")
(doseq [{contract-address :contract_address
owner :owner
repo :repo
@ -271,43 +307,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 (map-float= db-tokens token-balances)))
(log/info "balances differ")
(log/info "ETH (db):" db-balance-eth (type db-balance-eth) )
(log/info "ETH (chain):" balance-eth (type balance-eth) )
(log/info "ETH cmp:" (float= db-balance-eth balance-eth))
(log/info "tokens (db):" db-tokens (type db-tokens) (type (:SNT db-tokens)))
(log/info "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 +364,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

View File

@ -135,4 +135,10 @@
[:div.page-nav-text [:span (str "Page " page-number " of " page-count)]]
[draw-page-numbers page-number page-count container-element]]])))
(defn usd-string
"Turn a given float into a USD currency string based on the browsers locale setting.
A more complex and customizable approach can be found in goog.i18n.NumberFormat:
https://google.github.io/closure-library/api/goog.i18n.NumberFormat.html"
[usd-float]
(.toLocaleString usd-float js/navigator.language #js {:style "currency" :currency "USD"}))

View File

@ -1,6 +1,6 @@
(ns commiteth.manage-payouts
(:require [re-frame.core :as rf]
[commiteth.common :refer [human-time]]))
[commiteth.common :as common :refer [human-time]]))
@ -57,9 +57,21 @@
(:claims bounty))]
[claim-card bounty claim]))))
(defn bounty-stats [{:keys [paid unpaid]}]
[:div.cf
[:div.fl-ns.w-50-ns.tc.pv4
[:div.ttu.tracked "Open"]
[:div.f2.pa2 (common/usd-string (:combined-usd-value unpaid))]
[:div (:count unpaid) " bounties"]]
[:div.fl-ns.w-50-ns.tc.pv4
[:div.ttu.tracked "Paid"]
[:div.f2.pa2 (common/usd-string (:combined-usd-value paid))]
[:div (:count paid) " bounties"]]])
(defn manage-payouts-page []
(let [owner-bounties (rf/subscribe [:owner-bounties])
bounty-stats-data (rf/subscribe [:owner-bounties-stats])
owner-bounties-loading? (rf/subscribe [:get-in [:owner-bounties-loading?]])]
(fn []
(if @owner-bounties-loading?
@ -67,17 +79,14 @@
[:div.ui.active.inverted.dimmer
[:div.ui.text.loader "Loading"]]]
(let [web3 (.-web3 js/window)
bounties (vals @owner-bounties)
unpaid? #(empty? (:payout_hash %))
paid? #(not-empty (:payout_hash %))
unpaid-bounties (filter unpaid? bounties)
paid-bounties (filter paid? bounties)]
bounties (vals @owner-bounties)]
[:div.ui.container
(when (nil? web3)
[:div.ui.warning.message
[:i.warning.icon]
"To sign off claims, please view Status Open Bounty in Status, Mist or Metamask"])
[bounty-stats @bounty-stats-data]
[:h3 "New claims"]
[claim-list unpaid-bounties]
[claim-list (filter (complement :paid?) bounties)]
[:h3 "Old claims"]
[claim-list paid-bounties]])))))
[claim-list (filter :paid? bounties)]])))))

View File

@ -68,7 +68,26 @@
(reg-sub
:owner-bounties
(fn [db _]
(:owner-bounties db)))
(->> (for [[id bounty] (:owner-bounties db)]
;; TODO(martinklepsch) we might want to consider using a
;; special prefix or namespace for derived properties that
;; are added to domain records like this
;; e.g. `derived/paid?`
[id (assoc bounty :paid? (boolean (:payout_hash bounty)))])
(into {}))))
(reg-sub
:owner-bounties-stats
:<- [:owner-bounties]
(fn [owner-bounties _]
(let [sum-dollars (fn sum-dollars [bounties]
(reduce + (map #(js/parseFloat (:value_usd %)) bounties)))
{:keys [paid unpaid]} (group-by #(if (:paid? %) :paid :unpaid)
(vals owner-bounties))]
{:paid {:count (count paid)
:combined-usd-value (sum-dollars paid)}
:unpaid {:count (count unpaid)
:combined-usd-value (sum-dollars unpaid)}})))
(reg-sub
:pagination