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 update
RUN apt-get -y install xvfb RUN apt-get -y install xvfb
RUN apt-get -y install wkhtmltopdf RUN apt-get -y install wkhtmltopdf
RUN apt-get -y install less
COPY --from=builder /usr/src/app/target/uberjar/commiteth.jar . COPY --from=builder /usr/src/app/target/uberjar/commiteth.jar .
COPY html2png.sh . 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, INSERT INTO pull_requests (pr_id,
repo_id, repo_id,
pr_number, pr_number,
title,
issue_number, issue_number,
issue_id, issue_id,
commit_sha, commit_sha,
@ -215,16 +216,18 @@ INSERT INTO pull_requests (pr_id,
VALUES(:pr_id, VALUES(:pr_id,
:repo_id, :repo_id,
:pr_number, :pr_number,
:title,
:issue_number, :issue_number,
:issue_id, :issue_id,
:commit_sha, :commit_sha,
:user_id, :user_id,
:state) :state)
ON CONFLICT (pr_id) DO UPDATE ON CONFLICT (pr_id,issue_id) DO UPDATE
SET SET
state = :state, state = :state,
issue_number = :issue_number, issue_number = :issue_number,
issue_id = :issue_id, issue_id = :issue_id,
title = :title,
updated = timezone('utc'::text, now()), updated = timezone('utc'::text, now()),
commit_sha = :commit_sha; commit_sha = :commit_sha;
@ -573,7 +576,11 @@ SELECT
issue_title, issue_title,
repo_name, repo_name,
repo_owner, repo_owner,
pr_number,
pr_title,
pr_id,
issue_number, issue_number,
issue_id,
user_name, user_name,
user_avatar_url, user_avatar_url,
balance_eth, balance_eth,

View File

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

View File

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

View File

@ -34,7 +34,7 @@
(defn update-repo-state (defn update-repo-state
[repo-id repo-state] [repo-id repo-state]
(jdbc/with-db-connection [con-db *db*] (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}))) :repo_state repo-state})))
(defn get-repo (defn get-repo
"Get a repo from DB given it's full name (owner/repo-name)" "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 auto-gas-price? [] (env :auto-gas-price false))
(defn offline-signing? [] (env :offline-signing true)) (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)) (def creds-obj (atom nil))
(defn wallet-file-path [] (defn wallet-file-path []
@ -37,10 +39,6 @@
(defn wallet-password [] (defn wallet-password []
(env :eth-password)) (env :eth-password))
(defn create-web3j []
(or @web3j-obj
(swap! web3j-obj (constantly (Web3j/build (HttpService. (eth-rpc-url)))))))
(defn creds [] (defn creds []
(or @creds-obj (or @creds-obj
(let [password (wallet-password) (let [password (wallet-password)
@ -53,27 +51,59 @@
(throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn" (throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn"
{:password password :file-path file-path})))))) {:password password :file-path file-path}))))))
(defn get-signed-tx [gas-price gas-limit to data] (defn get-web3j-nonce [web3j-instance]
"Create a sign a raw transaction. (.. (.ethGetTransactionCount web3j-instance (env :eth-account) DefaultBlockParameterName/LATEST)
'From' argument is not needed as it's already sendAsync
encoded in credentials. 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" See https://web3j.readthedocs.io/en/latest/transactions.html#offline-transaction-signing"
(let [web3j (create-web3j) (let [nonce (get-nonce nonce-tracker internal-tx-id)]
nonce (.. (.ethGetTransactionCount web3j (log/infof "%s: Signing nonce: %s, gas-price: %s, gas-limit: %s"
(env :eth-account) internal-tx-id nonce gas-price gas-limit)
DefaultBlockParameterName/LATEST) (-> (RawTransaction/createTransaction (biginteger nonce) gas-price gas-limit to data)
sendAsync (TransactionEncoder/signMessage (creds))
get (Numeric/toHexString))))
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 (defn eth-gasstation-gas-price
"Get max of average and average_calc from gas station and use that "Get max of average and average_calc from gas station and use that
@ -122,19 +152,27 @@
nil))))) nil)))))
(def req-id-tracker
;; HACK to ensure previous random-number approach doesn't lead to
;; unintended collisions
(atom 0))
(defn eth-rpc (defn eth-rpc
[method params] [{:keys [method params internal-tx-id]}]
(let [request-id (rand-int 4096) {:pre [(string? method) (some? params)]}
body (json/write-str {:jsonrpc "2.0" (let [request-id (swap! req-id-tracker inc)
:method method body {:jsonrpc "2.0"
:params params :method method
:id request-id}) :params params
:id request-id}
options {:headers {"content-type" "application/json"} options {:headers {"content-type" "application/json"}
:body body} :body (json/write-str body)}
response @(post (eth-rpc-url) options) response @(post (eth-rpc-url) options)
result (safe-read-str (:body response))] 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 (cond
;; Ignore any responses that have mismatching request ID ;; Ignore any responses that have mismatching request ID
(not= (:id result) request-id) (not= (:id result) request-id)
@ -142,7 +180,10 @@
;; If request ID matches but contains error, throw ;; If request ID matches but contains error, throw
(:error result) (: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 :else
(:result result)))) (:result result))))
@ -179,9 +220,10 @@
(defn estimate-gas (defn estimate-gas
[from to value & [params]] [from to value & [params]]
(let [geth-estimate (eth-rpc (let [geth-estimate (eth-rpc
"eth_estimateGas" [(merge params {:from from {:method "eth_estimateGas"
:to to :params [(merge params {:from from
:value value})]) :to to
:value value})]})
adjusted-gas (adjust-gas-estimate geth-estimate)] adjusted-gas (adjust-gas-estimate geth-estimate)]
(log/debug "estimated gas (geth):" geth-estimate) (log/debug "estimated gas (geth):" geth-estimate)
@ -204,7 +246,8 @@
(defn get-balance-hex (defn get-balance-hex
[account] [account]
(eth-rpc "eth_getBalance" [account "latest"])) (eth-rpc {:method "eth_getBalance"
:params [account "latest"]}))
(defn get-balance-wei (defn get-balance-wei
[account] [account]
@ -223,7 +266,8 @@
(defn get-transaction-receipt (defn get-transaction-receipt
[hash] [hash]
(eth-rpc "eth_getTransactionReceipt" [hash])) (eth-rpc {:method "eth_getTransactionReceipt"
:params [hash]}))
(defn format-call-params (defn format-call-params
[method-id & params] [method-id & params]
@ -233,10 +277,12 @@
(defn call (defn call
[contract method-id & params] [contract method-id & params]
(let [data (apply format-call-params 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 (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) (let [data (apply format-call-params method-id params)
gas-price (gas-price) gas-price (gas-price)
value (format "0x%x" 0) value (format "0x%x" 0)
@ -247,21 +293,23 @@
(merge {:gasPrice (integer->hex gas-price)}) (merge {:gasPrice (integer->hex gas-price)})
contract contract
(merge {:to contract})) (merge {:to contract}))
gas (if gas-limit gas-limit gas (or gas-limit (estimate-gas from contract value params))
(estimate-gas from contract value params))
params (if (offline-signing?) params (if (offline-signing?)
(get-signed-tx (biginteger gas-price) (get-signed-tx {:internal-tx-id internal-tx-id
(hex->big-integer gas) :gas-price (biginteger gas-price)
contract :gas-limit (hex->big-integer gas)
data) :to contract
:data data})
(assoc params :gas gas))] (assoc params :gas gas))]
(if (offline-signing?) (if (offline-signing?)
(eth-rpc (eth-rpc
"eth_sendRawTransaction" {:method "eth_sendRawTransaction"
[params]) :params [params]
:internal-tx-id internal-tx-id})
(eth-rpc (eth-rpc
"personal_sendTransaction" {:method "personal_sendTransaction"
[params (eth-password)])))) :params [params (eth-password)]
:internal-tx-id internal-tx-id}))))
(defn hex-ch->num (defn hex-ch->num
[ch] [ch]
@ -322,7 +370,6 @@
eth-core eth-core
:start :start
(do (do
(swap! web3j-obj (constantly nil))
(swap! creds-obj (constantly nil)) (swap! creds-obj (constantly nil))
(log/info "eth/core started")) (log/info "eth/core started"))
:stop :stop

View File

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

View File

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

View File

@ -341,6 +341,8 @@
tokens tokens
winner-login) 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) (when (= :paid state)
(db-bounties/update-payout-receipt issue-id payout-receipt)) (db-bounties/update-payout-receipt issue-id payout-receipt))

View File

@ -115,18 +115,18 @@
(defn handle-claim (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) (users/create-user user-id login name nil avatar_url)
(let [open-or-edit? (contains? #{:opened :edited} event-type) (let [open-or-edit? (contains? #{:opened :edited} event-type)
close? (= :closed event-type) close? (= :closed event-type)
pr-data {:repo_id repo-id pr-data {:repo_id repo-id
:pr_id pr-id :pr_id pr-id
:pr_number pr-number :pr_number pr-number
:title pr-title
:user_id user-id :user_id user-id
:issue_number (:issue_number issue) :issue_number (:issue_number issue)
:issue_id (:issue_id issue) :issue_id (:issue_id issue)
:state event-type}] :state event-type}]
;; TODO: in the opened case if the submitting user has no ;; TODO: in the opened case if the submitting user has no
;; Ethereum address stored, we could post a comment to the ;; Ethereum address stored, we could post a comment to the
;; Github PR explaining that payout is not possible if the PR is ;; Github PR explaining that payout is not possible if the PR is
@ -174,22 +174,24 @@
pr-body :body pr-body :body
pr-title :title} :pull_request}] pr-title :title} :pull_request}]
(log/info "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title) (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-let [issues (remove nil? (map #(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 (doseq [issue issues]
(do (if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue
(log/info "Referenced bounty issue found" owner repo (:issue_number issue)) (do
(handle-claim issue (log/info "Referenced bounty issue found" owner repo (:issue_number issue))
user-id (handle-claim issue
login name user-id
avatar_url login name
owner repo avatar_url
repo-id owner repo
pr-id repo-id
pr-number pr-id
head-sha pr-number
merged? pr-title
event-type)) head-sha
(log/info "PR for issue already merged")) merged?
event-type))
(log/info "PR for issue already merged")))
(when (= :edited event-type) (when (= :edited event-type)
; Remove PR if it does not reference any issue ; Remove PR if it does not reference any issue
(pull-requests/remove pr-id)))) (pull-requests/remove pr-id))))
@ -383,7 +385,7 @@
(log/debug "webhook-app POST, headers" headers) (log/debug "webhook-app POST, headers" headers)
(let [raw-payload (slurp body) (let [raw-payload (slurp body)
payload (json/parse-string raw-payload true)] 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")) (if (validate-secret-one-hook payload raw-payload (get headers "x-hub-signature"))
(do (do
(log/debug "Github secret validation OK app") (log/debug "Github secret validation OK app")

View File

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

View File

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

View File

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

View File

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

View File

@ -484,6 +484,16 @@
(.removeEventListener js/window "click" close-dropdown) (.removeEventListener js/window "click" close-dropdown)
(assoc db :user-dropdown-open? false))) (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 (reg-event-db
::set-open-bounties-sorting-type ::set-open-bounties-sorting-type
(fn [db [_ sorting-type]] (fn [db [_ sorting-type]]

View File

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

View File

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