Merge branch 'develop' into fix/update-comment-only-if-hash-changes-385

This commit is contained in:
Vitaliy Vlasov 2018-04-19 16:00:38 +03:00
commit da700edc08
No known key found for this signature in database
GPG Key ID: A7D57C347F2B2964
25 changed files with 592 additions and 238 deletions

View File

@ -26,6 +26,7 @@ WORKDIR /root/
RUN apt-get update
RUN apt-get -y install xvfb
RUN apt-get -y install wkhtmltopdf
RUN apt-get -y install less
COPY --from=builder /usr/src/app/target/uberjar/commiteth.jar .
COPY html2png.sh .

View File

@ -0,0 +1 @@
ALTER TABLE "public"."pull_requests" DROP COLUMN "title";

View File

@ -0,0 +1 @@
ALTER TABLE "public"."pull_requests" ADD COLUMN "title" character varying(256);

View File

@ -0,0 +1,7 @@
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pkey;
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pr_id_key;
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_fkey;
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pkey PRIMARY KEY (pr_id);
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pr_id_key UNIQUE (pr_id);

View File

@ -0,0 +1,6 @@
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pkey;
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pr_id_key;
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pkey PRIMARY KEY (pr_id, issue_id);
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pr_id_key UNIQUE (pr_id, issue_id);
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_fkey FOREIGN KEY (issue_id) REFERENCES issues(issue_id);

View File

@ -0,0 +1,78 @@
-- restore the previous version of the view
CREATE VIEW "public"."claims_view" AS SELECT i.title AS issue_title,
i.issue_number,
r.repo AS repo_name,
r.owner AS repo_owner,
COALESCE(u.name, u.login) AS user_name,
u.avatar_url AS user_avatar_url,
i.payout_receipt,
p.updated,
i.updated AS issue_updated,
i.balance_eth,
i.tokens,
i.value_usd,
p.state AS pr_state,
i.is_open AS issue_open,
(case when u.address IS NULL THEN false ELSE true END) AS user_has_address
FROM issues i,
users u,
repositories r,
pull_requests p
WHERE r.repo_id = i.repo_id
AND p.issue_id = i.issue_id
AND p.user_id = u.id
AND i.contract_address IS NOT NULL
AND i.comment_id IS NOT NULL
ORDER BY p.updated;
CREATE OR REPLACE VIEW "public"."activity_feed_view" AS
SELECT 'open-claim'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.issue_number,
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.updated
FROM claims_view
WHERE claims_view.pr_state = 0
AND claims_view.payout_receipt IS NULL
AND claims_view.issue_open IS TRUE
UNION
SELECT 'claim-pending'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.issue_number,
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.issue_updated AS updated
FROM claims_view
WHERE claims_view.pr_state = 1
AND claims_view.payout_receipt IS NULL
UNION
SELECT 'claim-payout'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.issue_number,
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.issue_updated AS updated
FROM claims_view
WHERE claims_view.pr_state = 1
AND claims_view.payout_receipt IS NOT NULL
ORDER BY 12 DESC;

View File

@ -0,0 +1,95 @@
DROP VIEW "public"."claims_view" CASCADE;
CREATE VIEW "public"."claims_view" AS SELECT i.title AS issue_title,
i.issue_number,
r.repo AS repo_name,
r.owner AS repo_owner,
p.title AS pr_title, -- added
p.pr_number AS pr_number, -- added
p.pr_id AS pr_id, -- added
i.issue_id AS issue_id, -- added
COALESCE(u.name, u.login) AS user_name,
u.avatar_url AS user_avatar_url,
i.payout_receipt,
p.updated,
i.updated AS issue_updated,
i.balance_eth,
i.tokens,
i.value_usd,
p.state AS pr_state,
i.is_open AS issue_open,
(case when u.address IS NULL THEN false ELSE true END) AS user_has_address
FROM issues i,
users u,
repositories r,
pull_requests p
WHERE r.repo_id = i.repo_id
AND p.issue_id = i.issue_id
AND p.user_id = u.id
AND i.contract_address IS NOT NULL
AND i.comment_id IS NOT NULL
ORDER BY p.updated;
CREATE VIEW "public"."activity_feed_view" AS -- altered
SELECT 'open-claim'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.pr_title, -- added
claims_view.pr_number, -- added
claims_view.pr_id, -- added
claims_view.issue_number,
claims_view.issue_id, -- added
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.updated
FROM claims_view
WHERE claims_view.pr_state = 0
AND claims_view.payout_receipt IS NULL
AND claims_view.issue_open IS TRUE
UNION
SELECT 'claim-pending'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.pr_title, -- added
claims_view.pr_number, -- added
claims_view.pr_id, -- added
claims_view.issue_number,
claims_view.issue_id, -- added
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.issue_updated AS updated
FROM claims_view
WHERE claims_view.pr_state = 1
AND claims_view.payout_receipt IS NULL
UNION
SELECT 'claim-payout'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.pr_title, -- added
claims_view.pr_number, -- added
claims_view.pr_id, -- added
claims_view.issue_number,
claims_view.issue_id, -- added
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.issue_updated AS updated
FROM claims_view
WHERE claims_view.pr_state = 1
AND claims_view.payout_receipt IS NOT NULL
ORDER BY 12 DESC;

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

View File

@ -207,6 +207,7 @@ AND i.transaction_hash IS NOT NULL;
INSERT INTO pull_requests (pr_id,
repo_id,
pr_number,
title,
issue_number,
issue_id,
commit_sha,
@ -215,16 +216,18 @@ INSERT INTO pull_requests (pr_id,
VALUES(:pr_id,
:repo_id,
:pr_number,
:title,
:issue_number,
:issue_id,
:commit_sha,
:user_id,
:state)
ON CONFLICT (pr_id) DO UPDATE
ON CONFLICT (pr_id,issue_id) DO UPDATE
SET
state = :state,
issue_number = :issue_number,
issue_id = :issue_id,
title = :title,
updated = timezone('utc'::text, now()),
commit_sha = :commit_sha;
@ -573,7 +576,11 @@ SELECT
issue_title,
repo_name,
repo_owner,
pr_number,
pr_title,
pr_id,
issue_number,
issue_id,
user_name,
user_avatar_url,
balance_eth,

View File

@ -21,34 +21,36 @@
(defn deploy-contract [owner owner-address repo issue-id issue-number]
(if (empty? owner-address)
(log/error "Unable to deploy bounty contract because"
"repo owner has no Ethereum addres")
(do
(log/info "deploying contract to " owner-address)
(if-let [transaction-hash (multisig/deploy-multisig owner-address)]
(log/errorf "issue %s: Unable to deploy bounty contract because repo owner has no Ethereum addres" issue-id)
(try
(log/infof "issue %s: Deploying contract to %s" issue-id owner-address)
(if-let [transaction-hash (multisig/deploy-multisig {:owner owner-address
:internal-tx-id (str "contract-github-issue-" issue-id)})]
(do
(log/info "Contract deployed, transaction-hash:"
transaction-hash)
(log/infof "issue %s: Contract deployed, transaction-hash: %s" issue-id transaction-hash)
(github/update-comment (to-map owner repo issue-number transaction-hash))
(log/infof "issue %s: post-deploying-comment response: %s" issue-id resp)
(issues/update-transaction-hash issue-id transaction-hash))
(log/error "Failed to deploy contract to" owner-address)))))
(log/errorf "issue %s Failed to deploy contract to %s" issue-id owner-address))
(catch Exception ex (log/errorf ex "issue %s: deploy-contract exception" issue-id)))))
(defn add-bounty-for-issue [repo repo-id issue]
(let [{issue-id :id
issue-number :number
issue-title :title} issue
created-issue (issues/create repo-id issue-id issue-number issue-title)
{:keys [address owner]} (users/get-repo-owner repo-id)]
(log/debug "Adding bounty for issue " repo issue-number "owner address: " address)
(log/debug "issue %s: Adding bounty for issue %s/%s - owner address: %s"
issue-id repo issue-number owner-address)
(if (= 1 created-issue)
(deploy-contract owner address repo issue-id issue-number)
(log/debug "Issue already exists in DB, ignoring"))))
(log/debug "issue %s: Issue already exists in DB, ignoring"))))
(defn maybe-add-bounty-for-issue [repo repo-id issue]
(let [res (issues/get-issues-count repo-id)
{count :count} res
limit-reached? (> count max-issues-limit)
_ (log/debug "*** get-issues-count" repo-id " " res " " count " " limit-reached?)]
limit-reached? (> count max-issues-limit)]
(log/debug "*** get-issues-count" repo-id " " res " " count " " limit-reached?)
(if limit-reached?
(log/debug "Total issues for repo limit reached " repo " " count)
(add-bounty-for-issue repo repo-id issue))))
@ -76,9 +78,7 @@
(issues/get-issue-titles)]
(let [gh-issue (github/get-issue owner repo issue-number)]
(if-not (= title (:title gh-issue))
(do
(log/info "Updating changed title for issue" (:id gh-issue))
(issues/update-issue-title (:id gh-issue) (:title gh-issue)))))))
(issues/update-issue-title (:id gh-issue) (:title gh-issue))))))
(defn assert-keys [m ks]
(doseq [k ks]

View File

@ -2,7 +2,8 @@
(:require [commiteth.db.core :refer [*db*] :as db]
[clojure.java.jdbc :as jdbc]
[commiteth.util.util :refer [to-db-map]]
[clojure.set :refer [rename-keys]]))
[clojure.set :refer [rename-keys]]
[clojure.tools.logging :as log]))
(defn create
"Creates issue"
@ -32,6 +33,7 @@
(defn update-issue-title
[issue-id title]
(log/info "issue %s: Updating changed title \"%s\"" issue-id title)
(jdbc/with-db-connection [con-db *db*]
(db/update-issue-title con-db {:issue_id issue-id
:title title})))

View File

@ -34,7 +34,7 @@
(defn update-repo-state
[repo-id repo-state]
(jdbc/with-db-connection [con-db *db*]
(db/update-repo-name con-db {:repo_id repo-id
(db/update-repo-state con-db {:repo_id repo-id
:repo_state repo-state})))
(defn get-repo
"Get a repo from DB given it's full name (owner/repo-name)"

View File

@ -28,7 +28,9 @@
(defn auto-gas-price? [] (env :auto-gas-price false))
(defn offline-signing? [] (env :offline-signing true))
(def web3j-obj (atom nil))
(def web3j-obj
(delay (Web3j/build (HttpService. (eth-rpc-url)))))
(def creds-obj (atom nil))
(defn wallet-file-path []
@ -37,10 +39,6 @@
(defn wallet-password []
(env :eth-password))
(defn create-web3j []
(or @web3j-obj
(swap! web3j-obj (constantly (Web3j/build (HttpService. (eth-rpc-url)))))))
(defn creds []
(or @creds-obj
(let [password (wallet-password)
@ -53,27 +51,59 @@
(throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn"
{:password password :file-path file-path}))))))
(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.
(defn get-web3j-nonce [web3j-instance]
(.. (.ethGetTransactionCount web3j-instance (env :eth-account) DefaultBlockParameterName/LATEST)
sendAsync
get
getTransactionCount))
(defprotocol INonceTracker
"The reason we need this is that we might send mutliple identical
transactions (e.g. bounty contract deploy with same owner) shortly
after another. In this case web3j's TX counting only increases once
a transaction has been confirmed and so we send multiple identical
transactions with the same nonce.
Web3j also provides a TransactionManager that increases nonces but it
does not track nonces related to transactions and so as far as I understand
it might cause transactions to be executed twice if they are retried.
https://github.com/web3j/web3j/blob/d19855475aa6620a7e93523bd9ede26ca50ed042/core/src/main/java/org/web3j/tx/RawTransactionManager.java"
(get-nonce [this internal-tx-id]
"Return the to be used nonce for an OpenBounty Ethereum
transaction identified by `internal-tx-id`. As these IDs are stable
we can use them to use consistent nonces for the same transaction."))
(defrecord NonceTracker [state]
INonceTracker
(get-nonce [this internal-tx-id]
(let [prev-nonce (get @state internal-tx-id)
web3j-nonce (get-web3j-nonce @web3j-obj)
nonces (set (vals @state))
nonce (if (seq nonces)
(inc (apply max nonces))
web3j-nonce)]
(when prev-nonce
(log/warnf "%s: tx will be retried (prev-nonce: %s, new-nonce: %s, web3j-nonce: %s)"
internal-tx-id prev-nonce nonce web3j-nonce))
;; TODO this is a memory leak since tracking state is never pruned
;; Since we're not doing 1000s of transactions every day yet we can
;; probably defer worrying about this until a bit later
(swap! state assoc internal-tx-id nonce)
nonce)))
(def nonce-tracker
(->NonceTracker (atom {})))
(defn get-signed-tx [{:keys [gas-price gas-limit to data internal-tx-id]}]
"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))
(let [nonce (get-nonce nonce-tracker internal-tx-id)]
(log/infof "%s: Signing nonce: %s, gas-price: %s, gas-limit: %s"
internal-tx-id nonce gas-price gas-limit)
(-> (RawTransaction/createTransaction (biginteger nonce) gas-price gas-limit to data)
(TransactionEncoder/signMessage (creds))
(Numeric/toHexString))))
(defn eth-gasstation-gas-price
"Get max of average and average_calc from gas station and use that
@ -122,19 +152,27 @@
nil)))))
(def req-id-tracker
;; HACK to ensure previous random-number approach doesn't lead to
;; unintended collisions
(atom 0))
(defn eth-rpc
[method params]
(let [request-id (rand-int 4096)
body (json/write-str {:jsonrpc "2.0"
:method method
:params params
:id request-id})
[{:keys [method params internal-tx-id]}]
{:pre [(string? method) (some? params)]}
(let [request-id (swap! req-id-tracker inc)
body {:jsonrpc "2.0"
:method method
:params params
:id request-id}
options {:headers {"content-type" "application/json"}
:body body}
:body (json/write-str body)}
response @(post (eth-rpc-url) options)
result (safe-read-str (:body response))]
(log/debug body "\n" result)
(when internal-tx-id
(log/infof "%s: eth-rpc %s" internal-tx-id method))
(log/debugf "%s: eth-rpc req(%s) body: %s" internal-tx-id request-id body)
(log/debugf "%s: eth-rpc req(%s) result: %s" internal-tx-id request-id result)
(cond
;; Ignore any responses that have mismatching request ID
(not= (:id result) request-id)
@ -142,7 +180,10 @@
;; If request ID matches but contains error, throw
(:error result)
(throw (ex-info "Error submitting transaction via eth-rpc" (:error result)))
(throw
(ex-info (format "%s: Error submitting transaction via eth-rpc %s"
(or internal-tx-id "(no-tx-id)") (:error result))
(:error result)))
:else
(:result result))))
@ -179,9 +220,10 @@
(defn estimate-gas
[from to value & [params]]
(let [geth-estimate (eth-rpc
"eth_estimateGas" [(merge params {:from from
:to to
:value value})])
{:method "eth_estimateGas"
:params [(merge params {:from from
:to to
:value value})]})
adjusted-gas (adjust-gas-estimate geth-estimate)]
(log/debug "estimated gas (geth):" geth-estimate)
@ -204,7 +246,8 @@
(defn get-balance-hex
[account]
(eth-rpc "eth_getBalance" [account "latest"]))
(eth-rpc {:method "eth_getBalance"
:params [account "latest"]}))
(defn get-balance-wei
[account]
@ -223,7 +266,8 @@
(defn get-transaction-receipt
[hash]
(eth-rpc "eth_getTransactionReceipt" [hash]))
(eth-rpc {:method "eth_getTransactionReceipt"
:params [hash]}))
(defn format-call-params
[method-id & params]
@ -233,10 +277,12 @@
(defn call
[contract method-id & params]
(let [data (apply format-call-params method-id params)]
(eth-rpc "eth_call" [{:to contract :data data} "latest"])))
(eth-rpc {:method "eth_call"
:params [{:to contract :data data} "latest"]})))
(defn execute
[from contract method-id gas-limit & params]
[{:keys [from contract method-id gas-limit params internal-tx-id]}]
{:pre [(string? method-id)]}
(let [data (apply format-call-params method-id params)
gas-price (gas-price)
value (format "0x%x" 0)
@ -247,21 +293,23 @@
(merge {:gasPrice (integer->hex gas-price)})
contract
(merge {:to contract}))
gas (if gas-limit gas-limit
(estimate-gas from contract value params))
gas (or gas-limit (estimate-gas from contract value params))
params (if (offline-signing?)
(get-signed-tx (biginteger gas-price)
(hex->big-integer gas)
contract
data)
(get-signed-tx {:internal-tx-id internal-tx-id
:gas-price (biginteger gas-price)
:gas-limit (hex->big-integer gas)
:to contract
:data data})
(assoc params :gas gas))]
(if (offline-signing?)
(eth-rpc
"eth_sendRawTransaction"
[params])
{:method "eth_sendRawTransaction"
:params [params]
:internal-tx-id internal-tx-id})
(eth-rpc
"personal_sendTransaction"
[params (eth-password)]))))
{:method "personal_sendTransaction"
:params [params (eth-password)]
:internal-tx-id internal-tx-id}))))
(defn hex-ch->num
[ch]
@ -322,7 +370,6 @@
eth-core
:start
(do
(swap! web3j-obj (constantly nil))
(swap! creds-obj (constantly nil))
(log/info "eth/core started"))
:stop

View File

@ -1,6 +1,5 @@
(ns commiteth.eth.multisig-wallet
(:require [commiteth.eth.core :as eth
:refer [create-web3j creds]]
(:require [commiteth.eth.core :as eth]
[commiteth.config :refer [env]]
[clojure.tools.logging :as log]
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
@ -36,24 +35,19 @@
[]
(env :tokenreg-base-format :status))
(defn create-new
[owner1 owner2 required]
(eth/execute (eth/eth-account)
(factory-contract-addr)
(:create method-ids)
(eth/integer->hex 865000) ;; gas-limit
0x40
0x2
required
owner1
owner2))
(defn deploy-multisig
"Deploy a new multisig contract to the blockchain with commiteth bot
and given owner as owners."
[owner]
(create-new (eth/eth-account) owner 2))
and given owner as owners.
`internal-tx-id` is used to identify what issue this multisig is deployed
for and manage nonces at a later point in time."
[{:keys [owner internal-tx-id]}]
{:pre [(string? owner) (string? internal-tx-id)]}
(eth/execute {:internal-tx-id internal-tx-id
:from (eth/eth-account)
:contract (factory-contract-addr)
:method-id (:create method-ids)
:gas-limit (eth/integer->hex 865000)
:params [0x40 0x2 2 (eth/eth-account) owner]}))
(defn find-event-in-tx-receipt [tx-receipt topic-id]
(let [logs (:logs tx-receipt)
@ -92,20 +86,18 @@
(defn send-all
[contract to]
(log/debug "multisig/send-all " contract to)
[{:keys [contract payout-address internal-tx-id]}]
{:pre [(string? contract) (string? payout-address) (string? internal-tx-id)]}
(log/debug "multisig/send-all " contract payout-address internal-tx-id)
(let [params (eth/format-call-params
(:withdraw-everything method-ids)
to)]
(eth/execute (eth/eth-account)
contract
(:submit-transaction method-ids)
nil
contract
0
"0x60" ;; TODO: document these
"0x24" ;; or refactor out
params)))
payout-address)]
(eth/execute {:internal-tx-id internal-tx-id
:from (eth/eth-account)
:contract contract
:method-id (:submit-transaction method-ids)
:gas-limit nil
:params [contract 0 "0x60" "0x24" params]})))
(defn get-token-address [token]
@ -118,17 +110,17 @@
(log/debug "multisig/watch-token" bounty-addr token)
(let [token-address (get-token-address token)]
(assert token-address)
(eth/execute (eth/eth-account)
bounty-addr
(:watch method-ids)
nil
token-address)))
(eth/execute {:internal-tx-id (str "watch-token-" (System/currentTimeMillis) "-" bounty-addr)
:from (eth/eth-account)
:contract bounty-addr
:method-id (:watch method-ids)
:gas-limit nil
:params [token-address]})))
(defn load-bounty-contract [addr]
(MultiSigTokenWallet/load addr
(create-web3j)
(creds)
@eth/web3j-obj
(eth/creds)
(eth/gas-price)
(BigInteger/valueOf 500000)))

View File

@ -1,6 +1,5 @@
(ns commiteth.eth.token-registry
(:require [commiteth.eth.core :as eth
:refer [create-web3j creds]]
(:require [commiteth.eth.core :as eth]
[commiteth.config :refer [env]]
[clojure.tools.logging :as log])
(:import [org.web3j
@ -23,8 +22,8 @@
(defn- load-tokenreg-contract [addr]
(TokenReg/load addr
(create-web3j)
(creds)
@eth/web3j-obj
(eth/creds)
(eth/gas-price)
(BigInteger/valueOf 21000)))
@ -59,9 +58,8 @@
(defn deploy-parity-tokenreg
"Deploy an instance of parity token-registry to current network"
[]
(let [web3j (create-web3j)]
(TokenReg/deploy web3j
(creds)
(eth/gas-price)
(BigInteger/valueOf 4000000) ;; gas limit
BigInteger/ZERO)))
(TokenReg/deploy @eth/web3j-obj
(eth/creds)
(eth/gas-price)
(BigInteger/valueOf 4000000) ;; gas limit
BigInteger/ZERO))

View File

@ -341,6 +341,8 @@
tokens
winner-login)
)]
(when (and (= state :merged) (empty? winner-address))
(log/warn "issue %s: Cannot sign pending bounty - winner has no payout address" issue-id))
(when (= :paid state)
(db-bounties/update-payout-receipt issue-id payout-receipt))

View File

@ -115,18 +115,18 @@
(defn handle-claim
[issue user-id login name avatar_url owner repo repo-id pr-id pr-number head-sha merged? event-type]
[issue user-id login name avatar_url owner repo repo-id pr-id pr-number pr-title head-sha merged? event-type]
(users/create-user user-id login name nil avatar_url)
(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
:title pr-title
:user_id user-id
:issue_number (:issue_number issue)
:issue_id (:issue_id issue)
:state event-type}]
;; TODO: in the opened case if the submitting user has no
;; Ethereum address stored, we could post a comment to the
;; Github PR explaining that payout is not possible if the PR is
@ -174,22 +174,24 @@
pr-body :body
pr-title :title} :pull_request}]
(log/info "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title)
(if-let [issue (some #(issues/get-issue repo-id %1) (extract-issue-number owner repo pr-body pr-title))]
(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"))
(if-let [issues (remove nil? (map #(issues/get-issue repo-id %1) (extract-issue-number owner repo pr-body pr-title)))]
(doseq [issue issues]
(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
pr-title
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))))
@ -383,7 +385,7 @@
(log/debug "webhook-app POST, headers" headers)
(let [raw-payload (slurp body)
payload (json/parse-string raw-payload true)]
(log/info "webhook-app POST, payload:" (pr-str payload))
(log/debug "webhook-app POST, payload:" (pr-str payload))
(if (validate-secret-one-hook payload raw-payload (get headers "x-hub-signature"))
(do
(log/debug "Github secret validation OK app")

View File

@ -38,25 +38,26 @@
(update-balances)))
)
(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")
(p :update-issue-contract-address
<<<<<<< HEAD
(doseq [{:keys [issue-id transaction-hash]} (issues/list-pending-deployments)]
(log/info "pending deployment:" transaction-hash)
(log/infof "issue %s: pending deployment: %s" issue-id 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)
(log/infof "issue %s: update-issue-contract-address: tx receipt: %s" issue-id receipt)
(if-let [contract-address (multisig/find-created-multisig-address receipt)]
(let [{:keys [owner repo comment-id issue-number] :as issue}
(issues/update-contract-address issue-id contract-address)
balance-eth-str (eth/get-balance-eth contract-address 6)
balance-eth (read-string balance-eth-str)
tokens {}]
(log/info "Updating comment")
(log/infof "issue %s: Updating comment" issue-id)
(github/update-comment (to-map issue-id
owner
repo
@ -66,10 +67,9 @@
balance-eth
balance-eth-str
tokens)))
(log/error "Failed to find contract address in tx logs")))
(log/errorf "issue %s: Failed to find contract address in tx logs" issue-id)))
(catch Throwable ex
(do (log/error "update-issue-contract-address exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))))
(log/errorf ex "issue %s: update-issue-contract-address exception:" issue-id)))))
(log/info "Exit update-issue-contract-address"))
@ -79,26 +79,37 @@
(p :deploy-pending-contracts
(doseq [{:keys [issue-id issue-number owner owner-address repo]}
(db-bounties/pending-contracts)]
(log/debug "Trying to re-deploy failed bounty contract deployment, issue-id:" issue-id)
(bounties/deploy-contract owner owner-address repo issue-id issue-number))))
(log/infof "issue %s: Trying to re-deploy failed bounty contract deployment" issue-id)
(try
(bounties/deploy-contract owner owner-address repo issue-id issue-number)
(doseq [{issue-id :issue_id
issue-number :issue_number
owner :owner
owner-address :owner_address
repo :repo} (db-bounties/pending-contracts)]
(log/infof "issue %s: Trying to re-deploy failed bounty contract deployment" issue-id)
(try
(bounties/deploy-contract owner owner-address repo issue-id issue-number)
(catch Throwable t
(log/errorf t "issue %s: deploy-pending-contracts exception: %s" issue-id (ex-data t)))))))
(defn self-sign-bounty
"Walks through all issues eligible for bounty payout and signs corresponding transaction"
[]
(log/info "In self-sign-bounty")
(p :self-sign-bounty
<<<<<<< HEAD
(doseq [{:keys [contract-address winner-address issue-id winner-login] :as issue}
(db-bounties/pending-bounties)]
(try
(let [value (eth/get-balance-hex contract-address)]
(when-not (empty? winner-address)
(let [execute-hash (multisig/send-all contract-address winner-address)]
(log/info "Payout self-signed, called sign-all(" contract-address winner-address ") tx:" execute-hash)
(log/infof "issue %s: Payout self-signed, called sign-all(%s) tx: %s" issue-id contract-address winner-address execute-hash)
(db-bounties/update-execute-hash-and-winner-login issue-id execute-hash winner-login)))
(github/update-comment issue))
(catch Throwable ex
(do (log/error "self-sign-bounty exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))))
(log/error ex "issue %s: self-sign-bounty exception" issue-id)))))
(log/info "Exit self-sign-bounty"))
(defn update-confirm-hash
@ -107,13 +118,16 @@
(log/info "In update-confirm-hash")
(p :update-confirm-hash
(doseq [{:keys [issue-id execute-hash]} (db-bounties/pending-payouts)]
(log/info "pending payout:" execute-hash)
(when-let [receipt (eth/get-transaction-receipt execute-hash)]
(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)))))
(log/info "Exit update-confirm-hash"))
(log/infof "issue %s: pending payout: %s" issue-id execute-hash)
(try
(when-let [receipt (eth/get-transaction-receipt execute-hash)]
(log/infof "issue %s: execution receipt for issue " issue-id receipt)
(when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)]
(log/infof "issue %s: confirm hash:" issue-id confirm-hash)
(db-bounties/update-confirm-hash issue-id confirm-hash)))
(catch Throwable ex
(log/errorf ex "issue %s: update-confirm-hash exception:" issue-id))) )
(log/info "Exit update-confirm-hash")))
(defn update-watch-hash
@ -121,9 +135,13 @@
[]
(p :update-watch-hash
(doseq [{:keys [issue-id watch-hash]} (db-bounties/pending-watch-calls)]
(log/info "pending watch call" watch-hash)
(when-let [receipt (eth/get-transaction-receipt watch-hash)]
(db-bounties/update-watch-hash issue-id nil)))))
(log/infof "issue %s: pending watch call %s" issue-id watch-hash)
(try
(when-let [receipt (eth/get-transaction-receipt watch-hash)]
(db-bounties/update-watch-hash issue-id nil))
(catch Throwable ex
(log/errorf ex "issue %s: update-watch-hash exception:" issue-id))
))))
(defn older-than-3h?
@ -141,7 +159,7 @@
(p :update-payout-receipt
(doseq [{:keys [payout-hash contract-address confirm-hash issue-id updated] :as issue}
(db-bounties/confirmed-payouts)]
(log/debug "confirmed payout:" payout-hash)
(log/infof "issue %s: confirmed payout: %s" issue-id payout-hash)
(try
(if-let [receipt (eth/get-transaction-receipt payout-hash)]
(let [contract-tokens (multisig/token-balances contract-address)
@ -150,23 +168,21 @@
(some #(> (second %) 0.0) contract-tokens)
(> contract-eth-balance 0))
(do
(log/info "Contract still has funds")
(log/infof "issue %s: Contract (%s) still has funds" issue-id contract-address)
(when (multisig/is-confirmed? contract-address confirm-hash)
(log/info "Detected bounty with funds and confirmed payout, calling executeTransaction")
(log/infof "issue %s: Detected bounty with funds and confirmed payout, calling executeTransaction" issue-id)
(let [execute-tx-hash (multisig/execute-tx contract-address confirm-hash)]
(log/info "execute tx:" execute-tx-hash))))
(log/infof "issue %s: execute tx: %s" issue-id execute-tx-hash))))
(do
(log/info "Payout has succeeded, saving payout receipt for issue #" issue-id ": " receipt)
(log/infof "issue %s: Payout has succeeded, payout receipt %s" issue-id receipt)
(github/update-comment (assoc issue :payout-receipt receipt)))))
(when (older-than-3h? updated)
(log/info "Resetting payout hash for issue" issue-id "as it has not been mined in 3h")
(log/warn "issue %s: Resetting payout hash for issue as it has not been mined in 3h" issue-id)
(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")
)
(catch Throwable ex
(log/error ex "issue %s: update-payout-receipt exception" issue-id)))))
(log/info "Exit update-payout-receipt"))
(defn abs
"(abs n) is the absolute value of n"
@ -179,26 +195,27 @@
(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."
"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)
(log/info "In update-bounty-token-balances for issue" issue-id)
(doseq [[tla token-data] (token-data/as-map)]
(try
(let [balance (multisig/token-balance bounty-addr tla)]
(when (> balance 0)
(do
(log/info "bounty at" bounty-addr "has" balance "of token" tla)
(log/infof "bounty %s: has %s of token %s" bounty-addr balance 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")
(log/infof "bounty %s: balances not in sync, calling watch" bounty-addr)
(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"))
(catch Throwable ex
(log/error ex "bounty %s: update-bounty-token-balances exception" bounty-addr))))
(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."
@ -256,8 +273,7 @@
(merge token-balances {:ETH current-balance-eth})))
(github/update-comment issue))))
(catch Throwable ex
(do (log/error "update-balances exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))))
(log/error ex "issue %s: update-balances exception" issue-id)))))
(log/info "Exit update-balances"))
@ -265,7 +281,7 @@
(try
(func)
(catch Throwable t
(log/error t))))
(log/error t (.getMessage t) (ex-data t)))))
(defn run-tasks [tasks]
(doall

View File

@ -4,51 +4,95 @@
[commiteth.common :refer [human-time
display-data-page
items-per-page
issue-url]]
issue-url
pull-request-url]]
[commiteth.handlers :as handlers]
[commiteth.db :as db]
[commiteth.ui-model :as ui-model]
[commiteth.subscriptions :as subs]
[commiteth.util :as util]))
(defn display-bounty-claims [claims]
[:div.bounty-claims-container.ph4.pv3
(for [claim claims]
(let [{:keys [avatar-url
pr-title
pr-id
updated
repo-owner
repo-name
pr-number]} claim]
^{:key pr-id}
[:div.bounty-claims-row.open-bounty-item-content.flex
[:div.bounty-claims-icon.pl2
[:div.ui.tiny.circular.image
[:img {:src avatar-url}]]]
[:span.bounty-claims-text.pt2.pl2
[:a.fw5
{:href (pull-request-url repo-owner repo-name pr-number)}
(if pr-title
pr-title
(str "#" pr-number " by " repo-owner " in " repo-name))]
[:span.time.pl2 (human-time updated)]]]))])
(defn blue-arrow-box [image-src]
"generates the appropriate container for a blue arrow"
[:span.blue-arrow-box.pa1
[:img.blue-arrow-image.v-mid {:src image-src}]])
(defn bounty-item [bounty]
(let [{avatar-url :repo_owner_avatar_url
owner :repo-owner
repo-name :repo-name
issue-title :issue-title
issue-number :issue-number
updated :updated
tokens :tokens
balance-eth :balance-eth
value-usd :value-usd
claim-count :claim-count} bounty
full-repo (str owner "/" repo-name)
repo-url (str "https://github.com/" full-repo)
repo-link [:a {:href repo-url} full-repo]
issue-link [:a
{:href (issue-url owner repo-name issue-number)}
issue-title]]
[:div.open-bounty-item
[:div.open-bounty-item-content
[:div.header issue-link]
[:div.bounty-item-row
[:div.time (human-time updated)]
[:span.bounty-repo-label repo-link]]
[:div.footer-row
[:div.balance-badge "ETH " balance-eth]
(for [[tla balance] tokens]
^{:key (random-uuid)}
[:div.balance-badge.token
(str (subs (str tla) 1) " " balance)])
[:span.usd-value-label "Value "] [:span.usd-balance-label (str "$" value-usd)]
(when (> claim-count 0)
[:span.open-claims-label (str claim-count " open claim"
(when (> claim-count 1) "s"))])]]
[:div.open-bounty-item-icon
[:div.ui.tiny.circular.image
[:img {:src avatar-url}]]]]))
(let [open-bounty-claims (rf/subscribe [::subs/open-bounty-claims])]
(fn [bounty]
(let [{avatar-url :repo_owner_avatar_url
owner :repo-owner
repo-name :repo-name
issue-title :issue-title
issue-number :issue-number
issue-id :issue-id
updated :updated
tokens :tokens
balance-eth :balance-eth
value-usd :value-usd
claim-count :claim-count
claims :claims} bounty
full-repo (str owner "/" repo-name)
repo-url (str "https://github.com/" full-repo)
repo-link [:a {:href repo-url} full-repo]
issue-link [:a
{:href (issue-url owner repo-name issue-number)}
issue-title]
open-claims-click #(rf/dispatch [::handlers/open-bounty-claim issue-id])
close-claims-click #(rf/dispatch [::handlers/close-bounty-claim issue-id])
matches-current-issue? (some #{issue-id} @open-bounty-claims)]
[:div
[:div.open-bounty-item.ph4
[:div.open-bounty-item-content
[:div.header issue-link]
[:div.bounty-item-row
[:div.time (human-time updated)]
[:span.bounty-repo-label repo-link]]
[:div.footer-row
[:div.balance-badge "ETH " balance-eth]
(for [[tla balance] tokens]
^{:key (random-uuid)}
[:div.balance-badge.token
(str (subs (str tla) 1) " " balance)])
[:span.usd-value-label "Value "] [:span.usd-balance-label (str "$" value-usd)]
(when (> claim-count 0)
[:span.open-claims-label
{:on-click (if matches-current-issue?
#(close-claims-click)
#(open-claims-click))}
(str claim-count " open claim"
(when (> claim-count 1) "s"))
(if matches-current-issue?
[blue-arrow-box "blue-arrow-up.png"]
[blue-arrow-box "blue-arrow-down.png"])])]]
[:div.open-bounty-item-icon
[:div.ui.tiny.circular.image
[:img {:src avatar-url}]]]]
(when matches-current-issue?
[display-bounty-claims claims])]))))
(defn bounties-filter-tooltip-value-input-view [label tooltip-open? opts]
[:div.open-bounties-filter-element-tooltip-value-input-container
@ -210,7 +254,7 @@
[:div
(let [left (inc (* (dec page-number) items-per-page))
right (dec (+ left item-count))]
[:div.item-counts-label-and-sorting-container
[:div.item-counts-label-and-sorting-container.ph4
[:div.item-counts-label
[:span (str "Showing " left "-" right " of " total-count)]]
[bounties-sort-view]])
@ -227,7 +271,7 @@
[:div.ui.text.loader.view-loading-label "Loading"]]]
[:div.ui.container.open-bounties-container
{:ref #(reset! container-element %1)}
[:div.open-bounties-header "Bounties"]
[:div.open-bounties-filter-and-sort
[:div.open-bounties-header.ph4.pt4 "Bounties"]
[:div.open-bounties-filter-and-sort.ph4
[bounty-filters-view]]
[bounties-list @bounty-page-data container-element]]))))

View File

@ -48,6 +48,9 @@
(defn issue-url [owner repo number]
(str "https://github.com/" owner "/" repo "/issues/" number))
(defn pull-request-url [owner repo number]
(str "https://github.com/" owner "/" repo "/pull/" number))
(def items-per-page 15)
(defn draw-page-numbers [page-number page-count container-element]
@ -132,7 +135,7 @@
:else
[:div
[draw-items]
[:div.page-nav-container
[:div.page-nav-container.ph4.pb4
[:div.page-direction-container
[draw-rect :backward]
[draw-rect :forward]]

View File

@ -4,7 +4,7 @@
(def default-db
{:page :bounties
:user nil
:user-profile-loaded? false
:user-profile-loaded? false
:repos-loading? false
:repos {}
:activity-feed-loading? false
@ -18,6 +18,7 @@
::ui-model/bounty-filter-type|currency nil
::ui-model/bounty-filter-type|date nil
::ui-model/bounty-filter-type|owner nil}
::open-bounty-claims #{}
:owner-bounties {}
:top-hunters []
:activity-feed []})

View File

@ -484,6 +484,16 @@
(.removeEventListener js/window "click" close-dropdown)
(assoc db :user-dropdown-open? false)))
(reg-event-db
::open-bounty-claim
(fn [db [_ opening-issue-id]]
(update db ::db/open-bounty-claims #(conj % opening-issue-id))))
(reg-event-db
::close-bounty-claim
(fn [db [_ closing-issue-id]]
(update db ::db/open-bounty-claims #(disj % closing-issue-id))))
(reg-event-db
::set-open-bounties-sorting-type
(fn [db [_ sorting-type]]

View File

@ -53,17 +53,24 @@
:open-bounties-page
:<- [::filtered-and-sorted-open-bounties]
:<- [:page-number]
(fn [[open-bounties page-number] _]
:<- [:activity-feed]
(fn [[open-bounties page-number activity-feed] _]
(let [total-count (count open-bounties)
start (* (dec page-number) items-per-page)
end (min total-count (+ items-per-page start))
items (subvec open-bounties start end)]
{:items items
:item-count (count items)
start (* (dec page-number) items-per-page)
end (min total-count (+ items-per-page start))
items (->> (subvec open-bounties start end)
(map (fn [bounty]
(let [matching-claims (filter
(fn [claim]
(= (:issue-id claim)
(:issue-id bounty)))
activity-feed)]
(assoc bounty :claims matching-claims)))))]
{:items items
:item-count (count items)
:total-count total-count
:page-number page-number
:page-count (Math/ceil (/ total-count items-per-page))})))
:page-count (Math/ceil (/ total-count items-per-page))})))
(reg-sub
:owner-bounties
@ -146,6 +153,11 @@
(fn [db _]
(:user-dropdown-open? db)))
(reg-sub
::open-bounty-claims
(fn [db _]
(::db/open-bounty-claims db)))
(reg-sub
::open-bounties-sorting-type
(fn [db _]

View File

@ -415,7 +415,6 @@ label[for="input-hidden"] {
background-color: #fff;
border-radius: 10px;
transform: translateY(-45px);
padding: 24px;
.open-bounties-header {
font-family: "PostGrotesk-Medium";
font-size: 21px;
@ -714,6 +713,17 @@ label[for="input-hidden"] {
padding: 4px;
text-align: center;
}
.blue-arrow-box {
width: 24px;
height: 24px;
}
.blue-arrow-image {
width: 13.5px;
height: 6px;
}
}
.open-bounty-item:first-child {
@ -748,8 +758,6 @@ label[for="input-hidden"] {
font-size: 15px;
}
border-bottom: #eaecee 1px solid !important;
.open-bounty-item-content {
width: 80%;
padding: .7em 0 1em;
@ -780,8 +788,28 @@ label[for="input-hidden"] {
flex-direction: row !important;
justify-content: space-between;
width: 80%;
}
.bounty-claims-row {
padding-top: 6px;
padding-bottom: 6px;
}
.bounty-claims-container {
background-color: #FBFBFB;
}
.bounty-claims-icon {
width: 40px;
}
.bounty-claims-text {
a {
text-decoration: none;
color: #42505c;
}
}
.bounty-repo-label a {
margin: auto;
font-family: "PostGrotesk-Book";
@ -804,6 +832,7 @@ label[for="input-hidden"] {
padding-left: 15px;
font-size: 15px;
color: #57a7ed;
cursor: pointer;
}
.activity-item-container {