Merge pull request #457 from status-im/develop

Prod deployment 2018/05/29
This commit is contained in:
Vitaliy Vlasov 2018-05-29 17:20:04 +03:00 committed by GitHub
commit bccd8d19b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 943 additions and 499 deletions

View File

@ -103,9 +103,9 @@ lein less auto
``` ```
### Solidity compilation ### Solidity compilation
Invoke `build-contracts` Leiningen task to compile Solidity files into Java classes: Compile Solidity files into Java classes with:
``` ```
lein build-contracts cd contracts && ./build.sh
``` ```
### Clojure app without REPL ### Clojure app without REPL

View File

@ -2,8 +2,8 @@
This directory contains all the underlying smart contracts used by the OpenBounty platform. This directory contains all the underlying smart contracts used by the OpenBounty platform.
- A script `contracts/build.sh` is part of this repository and can be used to -- A script `build.sh` is part of this directory and can be used to
compile the contracts and copy Java interfaces into `src/java/`. -compile the contracts and copy Java interfaces into `src/java/`.
In order to run the script the following dependencies have to be met: In order to run the script the following dependencies have to be met:

View File

@ -1,12 +1,18 @@
#!/bin/bash -eu #!/bin/bash -eu
SOLC=$(which solc) function print_dependency_message
WEB3J=$(which web3j) {
echo "error: " $1 "must already be installed!"
}
SOLC=$(which solc) || print_dependency_message "solc"
WEB3J=$(which web3j) || print_dependency_message "web3"
rm -f resources/contracts/*.{abi,bin} rm -f resources/contracts/*.{abi,bin}
# compile contracts # compile contracts
for f in contracts/{TokenReg,MultiSigTokenWallet*}.sol; do for f in {TokenReg,MultiSigTokenWallet*}.sol; do
$SOLC $f --overwrite --bin --abi --optimize -o resources/contracts $SOLC $f --overwrite --bin --abi --optimize -o resources/contracts
done done

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

View File

@ -296,7 +296,7 @@ SELECT
i.tokens AS tokens, i.tokens AS tokens,
i.value_usd AS value_usd, i.value_usd AS value_usd,
u.login AS winner_login, u.login AS winner_login,
u.address AS payout_address u.address AS winner_address
FROM issues i, pull_requests p, users u, repositories r FROM issues i, pull_requests p, users u, repositories r
WHERE WHERE
p.issue_id = i.issue_id p.issue_id = i.issue_id
@ -313,11 +313,9 @@ SELECT
i.issue_id AS issue_id, i.issue_id AS issue_id,
u.address AS payout_address, u.address AS payout_address,
i.execute_hash AS execute_hash i.execute_hash AS execute_hash
FROM issues i, pull_requests p, users u FROM issues i, repositories r, users u
WHERE WHERE i.repo_id = r.repo_id
p.issue_id = i.issue_id AND u.id = r.user_id
AND p.repo_id = i.repo_id
AND u.id = p.user_id
AND i.confirm_hash IS NULL AND i.confirm_hash IS NULL
AND i.execute_hash IS NOT NULL; AND i.execute_hash IS NOT NULL;
@ -348,6 +346,29 @@ AND u.id = p.user_id
AND i.payout_receipt IS NULL AND i.payout_receipt IS NULL
AND i.payout_hash IS NOT NULL; AND i.payout_hash IS NOT NULL;
-- :name confirmed-revocation-payouts :? :*
-- :doc lists all recently confirmed bounty revocations
SELECT
i.contract_address AS contract_address,
r.owner AS owner,
r.repo AS repo,
i.comment_id AS comment_id,
i.issue_number AS issue_number,
i.issue_id AS issue_id,
i.balance_eth AS balance_eth,
i.tokens AS tokens,
i.value_usd AS value_usd,
u.address AS payout_address,
u.login AS payee_login,
i.confirm_hash AS confirm_hash,
i.payout_hash AS payout_hash,
i.updated AS updated
FROM issues i, users u, repositories r
WHERE r.repo_id = i.repo_id
AND u.id = r.user_id
AND i.payout_receipt IS NULL
AND i.payout_hash IS NOT NULL;
-- :name update-winner-login :! :n -- :name update-winner-login :! :n
UPDATE issues UPDATE issues
SET winner_login = :winner_login SET winner_login = :winner_login
@ -384,29 +405,18 @@ SET payout_receipt = :payout_receipt::jsonb,
updated = timezone('utc'::text, now()) updated = timezone('utc'::text, now())
WHERE issue_id = :issue_id; WHERE issue_id = :issue_id;
-- :name update-token-balances :! :n
-- :doc updates issue with given token balances
UPDATE issues
SET tokens = :token_balances::jsonb,
updated = timezone('utc'::text, now())
WHERE contract_address = :contract_address;
-- :name update-usd-value :! :n
-- :doc updates issue with given USD value
UPDATE issues
SET value_usd = :usd_value,
value_usd_updated = timezone('utc'::text, now())
WHERE contract_address = :contract_address;
-- :name update-issue-open :! :n -- :name update-issue-open :! :n
-- :doc updates issue's open status -- :doc updates issue's open status
UPDATE issues UPDATE issues
SET is_open = :is_open SET is_open = :is_open
WHERE issue_id = :issue_id; WHERE issue_id = :issue_id;
-- :name reset-bot-confirmation :! :n
-- :doc updates issue's execute and confirm hash
UPDATE issues
SET execute_hash = NULL,
confirm_hash = NULL
WHERE issue_id = :issue_id;
-- :name issue-exists :1 -- :name issue-exists :1
-- :doc returns true if given issue exists -- :doc returns true if given issue exists
@ -428,14 +438,29 @@ SELECT
i.issue_number AS issue_number, i.issue_number AS issue_number,
i.is_open AS is_open, i.is_open AS is_open,
i.winner_login AS winner_login, i.winner_login AS winner_login,
i.transaction_hash AS transaction_hash,
i.contract_address AS contract_address,
i.confirm_hash AS confirm_hash,
i.execute_hash AS execute_hash,
i.payout_hash AS payout_hash,
i.watch_hash AS watch_hash,
i.payout_receipt AS payout_receipt,
i.commit_sha AS commit_sha, i.commit_sha AS commit_sha,
u.address AS owner_address,
u.login AS owner_login,
i.contract_address AS contract_address,
i.confirm_hash AS confirm_hash,
i.title AS title, i.title AS title,
i.comment_id AS comment_id, i.comment_id AS comment_id,
i.balance_eth AS balance_eth,
i.tokens AS tokens,
i.value_usd AS value_usd,
i.repo_id AS repo_id, i.repo_id AS repo_id,
r.owner AS owner, r.owner AS owner,
r.repo AS repo r.repo AS repo
FROM issues i, repositories r FROM issues i, repositories r, users u
WHERE r.repo_id = i.repo_id WHERE r.repo_id = i.repo_id
AND r.user_id = u.id
AND i.issue_id = :issue-id AND i.issue_id = :issue-id
@ -480,6 +505,7 @@ SELECT
i.balance_eth AS balance_eth, i.balance_eth AS balance_eth,
i.tokens AS tokens, i.tokens AS tokens,
i.value_usd AS value_usd, i.value_usd AS value_usd,
i.execute_hash AS execute_hash,
i.confirm_hash AS confirm_hash, i.confirm_hash AS confirm_hash,
i.payout_hash AS payout_hash, i.payout_hash AS payout_hash,
i.payout_receipt AS payout_receipt, i.payout_receipt AS payout_receipt,
@ -489,6 +515,7 @@ SELECT
r.owner AS repo_owner, r.owner AS repo_owner,
r.owner_avatar_url AS repo_owner_avatar_url, r.owner_avatar_url AS repo_owner_avatar_url,
o.address AS owner_address, o.address AS owner_address,
o.login AS owner_login,
u.address AS payout_address u.address AS payout_address
FROM users o, repositories r, issues i LEFT OUTER JOIN users u ON u.login = i.winner_login FROM users o, repositories r, issues i LEFT OUTER JOIN users u ON u.login = i.winner_login
WHERE WHERE
@ -568,10 +595,12 @@ AND r.repo_id = i.repo_id
AND r.owner = :owner AND r.owner = :owner
AND r.repo = :repo; AND r.repo = :repo;
-- :name update-eth-balance :! :n -- :name update-balances :! :n
-- :doc updates balance of a wallet attached to a given issue -- :doc updates balance of a wallet attached to a given issue
UPDATE issues UPDATE issues
SET balance_eth = :balance_eth, SET balance_eth = :balance_eth,
tokens = :token_balances::jsonb,
value_usd = :usd_value,
updated = timezone('utc'::text, now()) updated = timezone('utc'::text, now())
WHERE contract_address = :contract_address; WHERE contract_address = :contract_address;
@ -625,8 +654,8 @@ SELECT
pr_id, pr_id,
issue_number, issue_number,
issue_id, issue_id,
user_name, user_name as display_name,
user_avatar_url, user_avatar_url as avatar_url,
balance_eth, balance_eth,
tokens, tokens,
value_usd, value_usd,

View File

@ -1,14 +1,14 @@
(ns commiteth.bounties (ns commiteth.bounties
(:require [commiteth.db.issues :as issues] (:require [commiteth.db.issues :as issues]
[commiteth.db.bounties :as db-bounties]
[commiteth.db.users :as users] [commiteth.db.users :as users]
[commiteth.db.repositories :as repos] [commiteth.db.repositories :as repos]
[commiteth.db.comment-images :as comment-images]
[commiteth.eth.core :as eth] [commiteth.eth.core :as eth]
[commiteth.eth.tracker :as tracker] [commiteth.eth.tracker :as tracker]
[commiteth.github.core :as github] [commiteth.github.core :as github]
[commiteth.util.util :refer [to-map]]
[commiteth.eth.multisig-wallet :as multisig] [commiteth.eth.multisig-wallet :as multisig]
[commiteth.model.bounty :as bnt] [commiteth.model.bounty :as bnt]
[commiteth.util.png-rendering :as png-rendering]
[clojure.tools.logging :as log])) [clojure.tools.logging :as log]))
@ -22,6 +22,57 @@
(let [labels (:labels issue)] (let [labels (:labels issue)]
(some #(= label-name (:name %)) labels))) (some #(= label-name (:name %)) labels)))
(def last-states (atom {}))
(defn transition [{:keys [issue-id tx-info] :as bounty} state]
(let [bounty-not= (fn [current db]
(some #(not= (%1 current) (%1 db))
(disj (set (keys current)) :tx-info)))
bounty-from-db (issues/get-issue-by-id issue-id)
bounty (and (or
(and (= state :pending-contributor-address)
(not= state (get @last-states issue-id)))
(bounty-not= bounty bounty-from-db))
(merge bounty-from-db bounty))]
(when bounty
(case state
:deploying
(tracker/track-tx! tx-info)
:opened
(do
(tracker/untrack-tx! {:issue-id (:issue-id bounty)
:tx-hash (:transaction-hash bounty)
:result (:contract-address bounty)
:type :deploy})
(github/update-bounty-comment-image bounty))
:pending-sob-confirmation
(tracker/track-tx! tx-info)
:pending-maintainer-confirmation
(tracker/untrack-tx! tx-info)
:paid
(db-bounties/update-payout-receipt issue-id (:payout-receipt bounty))
:watch-set
(tracker/track-tx! tx-info)
:watch-reset
(tracker/untrack-tx! tx-info)
:update-balances
(do
(github/update-bounty-comment-image bounty)
(issues/update-balances (:contract-address bounty)
(:balance-eth bounty)
(:tokens bounty)
(:value-usd bounty)))
nil)
(github/update-comment bounty state)))
(swap! last-states assoc issue-id state))
(defn deploy-contract [owner-address issue-id] (defn deploy-contract [owner-address issue-id]
(if (empty? owner-address) (if (empty? owner-address)
(log/errorf "issue %s: Unable to deploy bounty contract because repo owner has no Ethereum addres" issue-id) (log/errorf "issue %s: Unable to deploy bounty contract because repo owner has no Ethereum addres" issue-id)
@ -31,23 +82,36 @@
:internal-tx-id [:deploy issue-id]})] :internal-tx-id [:deploy issue-id]})]
(do (do
(log/infof "issue %s: Contract deployed, transaction-hash: %s" issue-id (:tx-hash tx-info)) (log/infof "issue %s: Contract deployed, transaction-hash: %s" issue-id (:tx-hash tx-info))
(github/post-deploying-comment issue-id (transition {:issue-id issue-id
(:tx-hash tx-info)) :transaction-hash (:tx-hash tx-info)
(tracker/track-tx! tx-info)) :tx-info tx-info} :deploying))
(log/errorf "issue %s Failed to deploy contract to %s" issue-id 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))))) (catch Exception ex (log/errorf ex "issue %s: deploy-contract exception" issue-id)))))
(defn execute-payout [issue-id contract-address payout-address]
(if (empty? payout-address)
(do
(log/warn "issue %s: Cannot sign pending bounty - winner has no payout address" issue-id)
(transition {:issue-id issue-id} :pending-contributor-address))
(let [tx-info (multisig/send-all {:contract contract-address
:payout-address payout-address
:internal-tx-id [:execute issue-id]})]
(log/infof "issue %s: Payout self-signed, called sign-all(%s) tx: %s" issue-id contract-address payout-address (:tx-hash tx-info))
(transition {:execute-hash (:tx-hash tx-info)
:issue-id issue-id
:tx-info tx-info} :pending-sob-confirmation)
tx-info)))
(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)
{owner-address :address {:keys [address owner]} (users/get-repo-owner repo-id)]
owner :owner} (users/get-repo-owner repo-id)]
(log/debug "issue %s: Adding bounty for issue %s/%s - owner address: %s" (log/debug "issue %s: Adding bounty for issue %s/%s - owner address: %s"
issue-id repo issue-number owner-address) issue-id repo issue-number address)
(if (= 1 created-issue) (if (= 1 created-issue)
(deploy-contract owner-address issue-id) (deploy-contract address issue-id)
(log/debug "issue %s: 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]
@ -63,9 +127,8 @@
;; We have a max-limit to ensure people can't add more issues and ;; We have a max-limit to ensure people can't add more issues and
;; drain bot account until we have economic design in place ;; drain bot account until we have economic design in place
(defn add-bounties-for-existing-issues [full-name] (defn add-bounties-for-existing-issues [full-name]
(let [{repo-id :repo_id (let [{:keys [repo-id
owner :owner owner repo] } (repos/get-repo full-name)
repo :repo} (repos/get-repo full-name)
issues (github/get-issues owner repo) issues (github/get-issues owner repo)
bounty-issues (filter has-bounty-label? issues) bounty-issues (filter has-bounty-label? issues)
max-bounties (take max-issues-limit bounty-issues)] max-bounties (take max-issues-limit bounty-issues)]
@ -75,30 +138,13 @@
(map (partial maybe-add-bounty-for-issue repo repo-id) max-bounties)))) (map (partial maybe-add-bounty-for-issue repo repo-id) max-bounties))))
(defn update-bounty-comment-image [issue-id owner repo issue-number contract-address eth-balance eth-balance-str tokens]
(let [hash (github/github-comment-hash owner repo issue-number eth-balance)
issue-url (str owner "/" repo "/issues/" (str issue-number))
png-data (png-rendering/gen-comment-image
contract-address
eth-balance-str
tokens
issue-url)]
(log/debug "update-bounty-comment-image" issue-id owner repo issue-number)
(log/debug contract-address eth-balance-str)
(log/debug "hash" hash)
(if png-data
(comment-images/save-image! issue-id hash png-data)
(log/error "Failed ot generate PNG"))))
(defn update-bounty-issue-titles (defn update-bounty-issue-titles
"Update stored titles for bounty issues if changed on Github side" "Update stored titles for bounty issues if changed on Github side"
[] []
(log/debug "update-bounty-issue-titles") (log/debug "update-bounty-issue-titles")
(for [{:keys [title issue_number repo owner]} (for [{:keys [title issue-number repo owner]}
(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))
(issues/update-issue-title (:id gh-issue) (:title gh-issue)))))) (issues/update-issue-title (:id gh-issue) (:title gh-issue))))))
@ -118,29 +164,31 @@
- :pending-contributor-address - :pending-contributor-address
- :pending-maintainer-confirmation" - :pending-maintainer-confirmation"
[bounty] [bounty]
(assert-keys bounty [:winner_login :payout_address :confirm_hash :payout_hash (assert-keys bounty [:winner-login :payout-address :confirm-hash :payout-hash
:claims :tokens :contract_address]) :claims :tokens :contract-address])
;; Some bounties have been paid out manually, the payout hash ;; Some bounties have been paid out manually, the payout hash
;; was set properly but winner_login was not ;; was set properly but winner-login was not
(let [open-claims (fn open-claims [bounty] (let [open-claims (fn open-claims [bounty]
(filter bnt/open? (:claims bounty)))] (filter bnt/open? (:claims bounty)))]
(if-let [merged-or-paid? (or (:winner_login bounty) (if-let [merged-or-paid? (or (:winner-login bounty)
(:payout_hash bounty))] (:payout-receipt bounty))]
(cond (cond
(:payout_hash bounty) :paid (:payout-receipt bounty) :paid
(nil? (:payout_address bounty)) :pending-contributor-address (nil? (:payout-address bounty)) :pending-contributor-address
;; `confirm_hash` is set by us as soon as a PR is merged and the ;; `confirm-hash` is set by us as soon as a PR is merged and the
;; contributor address is known. Usually end users should not need ;; contributor address is known. Usually end users should not need
;; to be aware of this step. ;; to be aware of this step.
(nil? (:confirm_hash bounty)) :pending-sob-confirmation (nil? (:confirm-hash bounty)) :pending-sob-confirmation
;; `payout_hash` is set when the bounty issuer signs the payout ;; `payout-hash` is set when the bounty issuer signs the payout
(nil? (:payout_hash bounty)) :pending-maintainer-confirmation (nil? (:payout-hash bounty)) :pending-maintainer-confirmation
:else :merged) :else :merged)
(cond ; not yet merged (cond ; not yet merged
(< 1 (count (open-claims bounty))) :multiple-claims (< 1 (count (open-claims bounty))) :multiple-claims
(= 1 (count (open-claims bounty))) :claimed (= 1 (count (open-claims bounty))) :claimed
(seq (:tokens bounty)) :funded (seq (:tokens bounty)) :funded
(:contract_address bounty) :opened)))) (:contract-address bounty) :opened))))
(comment (comment
(def user 97496) (def user 97496)

View File

@ -1,5 +1,7 @@
(ns commiteth.db.bounties (ns commiteth.db.bounties
(:require [commiteth.db.core :refer [*db*] :as db] (:require [commiteth.db.core :refer [*db*] :as db]
[commiteth.util.util :refer [to-db-map]]
[clojure.tools.logging :as log]
[clojure.java.jdbc :as jdbc] [clojure.java.jdbc :as jdbc]
[clojure.set :refer [rename-keys]])) [clojure.set :refer [rename-keys]]))
@ -40,6 +42,10 @@
(jdbc/with-db-connection [con-db *db*] (jdbc/with-db-connection [con-db *db*]
(db/confirmed-payouts con-db))) (db/confirmed-payouts con-db)))
(defn confirmed-revocation-payouts
[]
(jdbc/with-db-connection [con-db *db*]
(db/confirmed-revocation-payouts con-db)))
(defn update-winner-login (defn update-winner-login
[issue-id login] [issue-id login]
@ -54,23 +60,37 @@
(defn update-payout-hash (defn update-payout-hash
[issue-id payout-hash] [issue-id payout-hash]
(jdbc/with-db-connection [con-db *db*] (jdbc/with-db-connection [con-db *db*]
(db/update-payout-hash con-db {:issue_id issue-id :payout_hash payout-hash}))) (db/update-payout-hash con-db (to-db-map issue-id payout-hash))))
(defn reset-payout-hash (defn reset-payout-hash
[issue-id] [issue-id]
(jdbc/with-db-connection [con-db *db*] (jdbc/with-db-connection [con-db *db*]
(db/reset-payout-hash con-db {:issue_id issue-id}))) (db/reset-payout-hash con-db {:issue_id issue-id})))
(def payout-receipt-keys
[:issue-id
:payout-hash
:contract-address
:repo
:owner
:comment-id
:issue-number
:balance-eth
:tokens
:confirm-hash
:payee-login
:updated])
(defn update-payout-receipt (defn update-payout-receipt
[issue-id payout-receipt] [issue-id payout-receipt]
(jdbc/with-db-connection [con-db *db*] (jdbc/with-db-connection [con-db *db*]
(db/update-payout-receipt con-db {:issue_id issue-id (db/update-payout-receipt con-db (to-db-map issue-id payout-receipt))))
:payout_receipt payout-receipt})))
(defn get-bounty (defn get-bounty
[owner repo issue-number] [owner repo issue-number]
(jdbc/with-db-connection [con-db *db*] (jdbc/with-db-connection [con-db *db*]
(db/get-bounty con-db {:owner owner :repo repo :issue_number issue-number}))) (log/info "get-bounty params:" (to-db-map owner repo issue-number))
(db/get-bounty con-db (to-db-map owner repo issue-number))))
(defn open-bounty-contracts (defn open-bounty-contracts
[] []

View File

@ -7,6 +7,7 @@
[mount.core :refer [defstate]] [mount.core :refer [defstate]]
[migratus.core :as migratus] [migratus.core :as migratus]
[mpg.core :as mpg] [mpg.core :as mpg]
[hugsql.core]
[clojure.string :as str]) [clojure.string :as str])
(:import org.postgresql.util.PGobject (:import org.postgresql.util.PGobject
java.sql.Array java.sql.Array
@ -92,3 +93,30 @@
(defn update! [& args] (defn update! [& args]
(apply jdbc/update! *db* args)) (apply jdbc/update! *db* args))
(defn convert-keys-to-lisp-case [res]
(->> res
(map #(vector (keyword (str/replace (name (first %1)) "_" "-"))
(second %1)))
(into {})))
(defn result-one-sql->lisp
[this result options]
(convert-keys-to-lisp-case (first result)))
(defn result-many-sql->lisp
[this result options]
(map convert-keys-to-lisp-case result))
(defmethod hugsql.core/hugsql-result-fn :1 [sym]
'commiteth.db.core/result-one-sql->lisp)
(defmethod hugsql.core/hugsql-result-fn :one [sym]
'commiteth.db.core/result-one-sql->lisp)
(defmethod hugsql.core/hugsql-result-fn :* [sym]
'commiteth.db.core/result-many-sql->lisp)
(defmethod hugsql.core/hugsql-result-fn :many [sym]
'commiteth.db.core/result-many-sql->lisp)

View File

@ -1,6 +1,7 @@
(ns commiteth.db.issues (ns commiteth.db.issues
(: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]]
[clojure.set :refer [rename-keys]] [clojure.set :refer [rename-keys]]
[clojure.tools.logging :as log])) [clojure.tools.logging :as log]))
@ -71,23 +72,13 @@
(jdbc/with-db-connection [con-db *db*] (jdbc/with-db-connection [con-db *db*]
(db/list-pending-deployments con-db))) (db/list-pending-deployments con-db)))
(defn update-eth-balance (defn update-balances
[contract-address balance-eth] [contract-address balance-eth token-balances usd-value]
(jdbc/with-db-connection [con-db *db*] (jdbc/with-db-connection [con-db *db*]
(db/update-eth-balance con-db {:contract_address contract-address (db/update-balances con-db (to-db-map contract-address
:balance_eth balance-eth}))) balance-eth
token-balances
(defn update-token-balances usd-value))))
[contract-address balances]
(jdbc/with-db-connection [con-db *db*]
(db/update-token-balances con-db {:contract_address contract-address
:token_balances balances})))
(defn update-usd-value
[contract-address usd-value]
(jdbc/with-db-connection [con-db *db*]
(db/update-usd-value con-db {:contract_address contract-address
:usd_value usd-value})))
(defn update-open-status (defn update-open-status
[issue-id is-open] [issue-id is-open]
@ -95,6 +86,12 @@
(db/update-issue-open con-db {:issue_id issue-id (db/update-issue-open con-db {:issue_id issue-id
:is_open is-open}))) :is_open is-open})))
(defn reset-bot-confirmation
"resets execute and confirm hash to null for given issue id"
[issue-id]
(jdbc/with-db-connection [con-db *db*]
(db/reset-bot-confirmation con-db {:issue_id issue-id})))
(defn is-bounty-issue? (defn is-bounty-issue?
[issue-id] [issue-id]
(let [res (jdbc/with-db-connection [con-db *db*] (let [res (jdbc/with-db-connection [con-db *db*]

View File

@ -48,7 +48,10 @@
(try (try
(eth-gasstation-gas-price) (eth-gasstation-gas-price)
(catch Throwable t (catch Throwable t
(log/error "Failed to get gas price with ethgasstation API" t) (let [cause (-> t
Throwable->map
:cause)]
(log/error "Failed to get gas price with ethgasstation API" cause))
(gas-price-from-config))) (gas-price-from-config)))
(gas-price-from-config))) (gas-price-from-config)))
@ -82,10 +85,10 @@
response @(post (eth-rpc-url) options) response @(post (eth-rpc-url) options)
result (safe-read-str (:body response))] result (safe-read-str (:body response))]
(when internal-tx-id (when internal-tx-id
(log/infof "%s: eth-rpc %s" tx-id-str method)) (log/debugf "%s: eth-rpc %s" tx-id-str method))
(log/debugf "%s: eth-rpc req(%s) body: %s" tx-id-str request-id body) (log/debugf "%s: eth-rpc req(%s) body: %s" tx-id-str request-id body)
(if tx-id-str (if tx-id-str
(log/infof "%s: eth-rpc req(%s) result: %s" tx-id-str request-id result) (log/debugf "%s: eth-rpc req(%s) result: %s" tx-id-str request-id result)
(log/debugf "no-tx-id: eth-rpc req(%s) result: %s" request-id result)) (log/debugf "no-tx-id: eth-rpc req(%s) result: %s" request-id result))
(cond (cond
;; Ignore any responses that have mismatching request ID ;; Ignore any responses that have mismatching request ID
@ -225,7 +228,7 @@
(hex->big-integer gas) (hex->big-integer gas)
contract contract
(:data params) (:data params)
nonce) (biginteger nonce))
tx-hash (try tx-hash (try
(eth-rpc (eth-rpc
{:method "eth_sendRawTransaction" {:method "eth_sendRawTransaction"

View File

@ -64,7 +64,42 @@
) )
(def tx-tracker (SequentialTxTracker. (atom nil))) (defrecord ParallelTxTracker [current-txs]
ITxTracker
(try-reserve-nonce [this]
(let [nonce (get-nonce)
monitored-nonces (set (keys @current-txs))
first-available-nonce (some #(if (monitored-nonces %1) nil %1) (iterate inc nonce))]
(swap! current-txs assoc first-available-nonce nil)
first-available-nonce))
(drop-nonce [this nonce]
(swap! current-txs dissoc nonce))
(track-tx [this tx-info]
(swap! current-txs update (:nonce tx-info) merge tx-info))
(untrack-tx [this tx-info]
(when (contains? (set (keys @current-txs)) (:nonce tx-info))
(swap! current-txs dissoc (:nonce tx-info))))
(prune-txs [this unmined-txs]
(swap! current-txs
(fn [txs]
(let [unmined-tx-hashes (set (map :tx-hash unmined-txs))
time-threshold (t/minus (t/now) (t/minutes 10))
nonces-to-remove
(->> txs
vals
(filter #(or (unmined-tx-hashes (:tx-hash %1))
(and (:timestamp %1)
(t/before? (:timestamp %1) time-threshold))))
(map :nonce))]
(apply dissoc txs nonces-to-remove)))))
)
(def tx-tracker (ParallelTxTracker. (atom nil)))
(defn try-reserve-nonce! [] (defn try-reserve-nonce! []
(try-reserve-nonce tx-tracker)) (try-reserve-nonce tx-tracker))

View File

@ -15,6 +15,10 @@
[commiteth.db.issues :as db-issues] [commiteth.db.issues :as db-issues]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
[cheshire.core :as json] [cheshire.core :as json]
[commiteth.util.png-rendering :as png-rendering]
[commiteth.db.issues :as db-issues]
[commiteth.db.bounties :as db-bounties]
[commiteth.db.comment-images :as comment-images]
[clojure.string :as str]) [clojure.string :as str])
(:import [java.util UUID])) (:import [java.util UUID]))
@ -230,7 +234,7 @@
(str "Contract address: [" addr "](" url-base "/address/" addr ")\n"))) (str "Contract address: [" addr "](" url-base "/address/" addr ")\n")))
(defn generate-open-comment (defn generate-open-comment
[owner repo issue-number contract-address eth-balance eth-balance-str tokens] [owner repo issue-number contract-address eth-balance tokens]
(let [image-url (md-image "QR Code" (get-qr-url owner repo issue-number eth-balance)) (let [image-url (md-image "QR Code" (get-qr-url owner repo issue-number eth-balance))
site-url (md-url (server-address) (server-address))] site-url (md-url (server-address) (server-address))]
(format (str "Current balance: %s ETH\n" (format (str "Current balance: %s ETH\n"
@ -243,14 +247,14 @@
(if (on-testnet?) (if (on-testnet?)
"To fund it, send test ETH or test ERC20/ERC223 tokens to the contract address." "To fund it, send test ETH or test ERC20/ERC223 tokens to the contract address."
"To fund it, send ETH or ERC20/ERC223 tokens to the contract address.")) "To fund it, send ETH or ERC20/ERC223 tokens to the contract address."))
eth-balance-str image-url site-url))) (.toPlainString (bigdec eth-balance)) image-url site-url)))
(defn learn-more-text [] (defn learn-more-text []
(let [site-url (md-url (server-address) (server-address))] (let [site-url (md-url (server-address) (server-address))]
(format "Visit %s to learn more.\n" site-url))) (format "Visit %s to learn more.\n" site-url)))
(defn generate-merged-comment (defn generate-merged-comment
[contract-address eth-balance-str tokens winner-login winner-address-missing?] [contract-address eth-balance tokens winner-login winner-address-missing?]
(format (str "Balance: %s ETH\n" (format (str "Balance: %s ETH\n"
(token-balances-text tokens) (token-balances-text tokens)
(contract-addr-text contract-address) (contract-addr-text contract-address)
@ -260,17 +264,17 @@
"Pending maintainer confirmation") "\n") "Pending maintainer confirmation") "\n")
"Winner: %s\n" "Winner: %s\n"
(learn-more-text)) (learn-more-text))
eth-balance-str winner-login)) (.toPlainString (bigdec eth-balance)) winner-login))
(defn generate-paid-comment (defn generate-paid-comment
[contract-address eth-balance-str tokens payee-login] [contract-address eth-balance tokens payee-login]
(format (str "Balance: %s ETH\n" (format (str "Balance: %s ETH\n"
(token-balances-text tokens) (token-balances-text tokens)
(contract-addr-text contract-address) (contract-addr-text contract-address)
(network-text) (network-text)
"Paid to: %s\n" "Paid to: %s\n"
(learn-more-text)) (learn-more-text))
eth-balance-str payee-login)) (.toPlainString (bigdec eth-balance)) payee-login))
(defn make-patch-request [end-point positional query] (defn make-patch-request [end-point positional query]
(let [{:keys [auth oauth-token] (let [{:keys [auth oauth-token]
@ -290,6 +294,22 @@
:otp))] :otp))]
(assoc req :body (json/generate-string (or raw-query proper-query))))) (assoc req :body (json/generate-string (or raw-query proper-query)))))
(defn update-bounty-comment-image [{:keys [issue-id owner repo issue-number contract-address balance-eth tokens]}]
(let [hash (github-comment-hash owner repo issue-number balance-eth)
issue-url (str owner "/" repo "/issues/" (str issue-number))
png-data (png-rendering/gen-comment-image
contract-address
(.toPlainString (bigdec balance-eth))
tokens
issue-url)]
(log/debug "update-bounty-comment-image" issue-id owner repo issue-number)
(log/debug contract-address balance-eth)
(log/debug "hash" hash)
(if png-data
(comment-images/save-image! issue-id hash png-data)
(log/error "Failed ot generate PNG"))))
(defn post-deploying-comment (defn post-deploying-comment
[issue-id tx-id] [issue-id tx-id]
(let [{owner :owner (let [{owner :owner
@ -310,52 +330,51 @@
(defn update-comment (defn update-comment
"Update comment for an open bounty issue" "Update comment for an open bounty issue"
[owner repo comment-id issue-number contract-address eth-balance eth-balance-str tokens] [{:keys [issue-id owner repo comment-id issue-number contract-address
(let [comment (generate-open-comment owner balance-eth tokens
payout-receipt
owner-login
winner-login transaction-hash] :as issue}
state]
(let [comment (case state
:deploying
(generate-deploying-comment owner repo issue-number transaction-hash)
(:opened :update-balances)
(generate-open-comment owner
repo repo
issue-number issue-number
contract-address contract-address
eth-balance balance-eth
eth-balance-str tokens)
tokens)] :pending-sob-confirmation
(log/debug (str "Updating " owner "/" repo "/" issue-number (generate-merged-comment contract-address
balance-eth
tokens
(or winner-login owner-login)
false)
:pending-contributor-address
(generate-merged-comment contract-address
balance-eth
tokens
(or winner-login owner-login)
true)
:paid
(generate-paid-comment contract-address
balance-eth
tokens
(or winner-login owner-login))
nil)]
(log/info (str "Updating " owner "/" repo "/" issue-number
" comment #" comment-id " with contents: " comment)) " comment #" comment-id " with contents: " comment))
(if (= state :deploying)
(let [resp (issues/create-comment owner repo issue-number comment (self-auth-params))
comment-id (:id resp)]
(db-issues/update-comment-id issue-id comment-id))
(when comment
(let [req (make-patch-request "repos/%s/%s/issues/comments/%s" (let [req (make-patch-request "repos/%s/%s/issues/comments/%s"
[owner repo comment-id] [owner repo comment-id]
(assoc (self-auth-params) :body comment))] (assoc (self-auth-params) :body comment))]
(tentacles/safe-parse (http/request req))))) (tentacles/safe-parse (http/request req)))))))
(defn update-merged-issue-comment
"Update comment for a bounty issue with winning claim (waiting to be
signed off by maintainer/user ETH address missing)"
[owner repo comment-id contract-address eth-balance-str tokens winner-login winner-address-missing?]
(let [comment (generate-merged-comment contract-address
eth-balance-str
tokens
winner-login
winner-address-missing?)]
(log/debug (str "Updating merged bounty issue (" owner "/" repo ")"
" comment#" comment-id " with contents: " comment))
(let [req (make-patch-request "repos/%s/%s/issues/comments/%s"
[owner repo comment-id]
(assoc (self-auth-params) :body comment))]
(tentacles/safe-parse (http/request req)))))
(defn update-paid-issue-comment
"Update comment for a paid out bounty issue"
[owner repo comment-id contract-address eth-balance-str tokens payee-login]
(let [comment (generate-paid-comment contract-address
eth-balance-str
tokens
payee-login)]
(log/debug (str "Updating paid bounty (" owner "/" repo ")"
" comment#" comment-id " with contents: " comment))
(let [req (make-patch-request "repos/%s/%s/issues/comments/%s"
[owner repo comment-id]
(assoc (self-auth-params) :body comment))]
(tentacles/safe-parse (http/request req)))))
(defn get-issue (defn get-issue
[owner repo issue-number] [owner repo issue-number]

View File

@ -12,19 +12,16 @@
(context "/qr" [] (context "/qr" []
(GET "/:owner/:repo/bounty/:issue{[0-9]{1,9}}/:hash/qr.png" [owner repo issue hash] (GET "/:owner/:repo/bounty/:issue{[0-9]{1,9}}/:hash/qr.png" [owner repo issue hash]
(log/debug "qr PNG GET" owner repo issue hash) (log/debug "qr PNG GET" owner repo issue hash)
(if-let [{address :contract_address (if-let [{:keys [contract-address repo issue-id balance-eth]}
repo :repo
issue-id :issue_id
balance-eth :balance_eth}
(bounties/get-bounty owner (bounties/get-bounty owner
repo repo
(Integer/parseInt issue))] (Integer/parseInt issue))]
(do (do
(log/debug "address:" address) (log/debug "address:" contract-address)
(log/debug owner repo issue balance-eth) (log/debug owner repo issue balance-eth)
(log/debug hash (github/github-comment-hash owner repo issue balance-eth)) (log/debug hash (github/github-comment-hash owner repo issue balance-eth))
(if address (if contract-address
(if-let [{png-data :png_data} (if-let [{:keys [png-data]}
(comment-images/get-image-data (comment-images/get-image-data
issue-id hash)] issue-id hash)]
(do (log/debug "PNG found") (do (log/debug "PNG found")

View File

@ -27,10 +27,10 @@
[token] [token]
(let [user (github/get-user token) (let [user (github/get-user token)
{email :email {email :email
user-id :id} user] user-id :id} user
(log/debug "get-or-create-user" user) db-user (users/get-user user-id)]
(or (if (:id db-user)
(users/get-user user-id) db-user
(create-user token user)))) (create-user token user))))
(defroutes redirect-routes (defroutes redirect-routes

View File

@ -1,6 +1,7 @@
(ns commiteth.routes.services (ns commiteth.routes.services
(:require [ring.util.http-response :refer :all] (:require [ring.util.http-response :refer :all]
[compojure.api.sweet :refer :all] [compojure.api.sweet :refer :all]
[compojure.api.exception :as ex]
[schema.core :as s] [schema.core :as s]
[compojure.api.meta :refer [restructure-param]] [compojure.api.meta :refer [restructure-param]]
[buddy.auth.accessrules :refer [restrict]] [buddy.auth.accessrules :refer [restrict]]
@ -10,16 +11,20 @@
[commiteth.db.usage-metrics :as usage-metrics] [commiteth.db.usage-metrics :as usage-metrics]
[commiteth.db.repositories :as repositories] [commiteth.db.repositories :as repositories]
[commiteth.db.bounties :as bounties-db] [commiteth.db.bounties :as bounties-db]
[commiteth.db.issues :as issues]
[commiteth.bounties :as bounties] [commiteth.bounties :as bounties]
[commiteth.eth.core :as eth] [commiteth.eth.core :as eth]
[commiteth.eth.tracker :as tracker]
[commiteth.github.core :as github] [commiteth.github.core :as github]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
[commiteth.config :refer [env]] [commiteth.config :refer [env]]
[commiteth.util.util :refer [usd-decimal->str [commiteth.util.util :refer [usd-decimal->str
eth-decimal->str]] eth-decimal->str
to-db-map]]
[crypto.random :as random] [crypto.random :as random]
[clojure.set :refer [rename-keys]] [clojure.set :refer [rename-keys]]
[clojure.string :as str])) [clojure.string :as str]
[commiteth.eth.multisig-wallet :as multisig]))
(defn add-bounties-for-existing-issues? [] (defn add-bounties-for-existing-issues? []
(env :add-bounties-for-existing-issues false)) (env :add-bounties-for-existing-issues false))
@ -63,26 +68,14 @@
(def bounty-renames (def bounty-renames
;; TODO this needs to go away ASAP we need to be super consistent ;; TODO this needs to go away ASAP we need to be super consistent
;; about keys unless we will just step on each others toes constantly ;; about keys unless we will just step on each others toes constantly
{:user_name :display-name {:user-name :display-name
:user_avatar_url :avatar-url :user-avatar-url :avatar-url
:issue_title :issue-title :type :item-type})
:pr_title :pr-title
:pr_number :pr-number
:pr_id :pr-id
:type :item-type
:repo_name :repo-name
:repo_owner :repo-owner
:issue_number :issue-number
:issue_id :issue-id
:value_usd :value-usd
:claim_count :claim-count
:balance_eth :balance-eth
:user_has_address :user-has-address})
(defn ^:private enrich-owner-bounties [owner-bounty] (defn ^:private enrich-owner-bounties [owner-bounty]
(let [claims (map (let [claims (map
#(update % :value_usd usd-decimal->str) #(update % :value-usd usd-decimal->str)
(bounties-db/bounty-claims (:issue_id owner-bounty))) (bounties-db/bounty-claims (:issue-id owner-bounty)))
with-claims (assoc owner-bounty :claims claims)] with-claims (assoc owner-bounty :claims claims)]
(-> with-claims (-> with-claims
(rename-keys bounty-renames) (rename-keys bounty-renames)
@ -98,9 +91,7 @@
(into {})))) (into {}))))
(defn top-hunters [] (defn top-hunters []
(let [renames {:user_name :display-name (let [renames {:user-name :display-name}]
:avatar_url :avatar-url
:total_usd :total-usd}]
(map #(-> % (map #(-> %
(rename-keys renames) (rename-keys renames)
(update :total-usd usd-decimal->str)) (update :total-usd usd-decimal->str))
@ -155,13 +146,25 @@
(let [whitelist (env :user-whitelist #{})] (let [whitelist (env :user-whitelist #{})]
(whitelist user))) (whitelist user)))
(defn execute-revocation [issue-id contract-address payout-address]
(log/info (str "executing revocation for " issue-id "at" contract-address))
(try
(let [tx-info (bounties/execute-payout issue-id contract-address payout-address)]
(:tx-hash tx-info))
(catch Throwable ex
(log/errorf ex "error revoking funds for %s" issue-id))))
(defapi service-routes (defapi service-routes
(when (:dev env) (when (:dev env)
{:swagger {:ui "/swagger-ui" {:swagger {:ui "/swagger-ui"
:spec "/swagger.json" :spec "/swagger.json"
:data {:info {:version "0.1" :data {:info {:version "0.1"
:title "commitETH API" :title "commitETH API"
:description "commitETH API"}}}}) :description "commitETH API"}}}
:exceptions {:handlers
{::ex/request-parsing (ex/with-logging ex/request-parsing-handler :info)
::ex/response-validation (ex/with-logging ex/response-validation-handler :error)}}})
(context "/api" [] (context "/api" []
(GET "/top-hunters" [] (GET "/top-hunters" []
@ -192,11 +195,11 @@
:auth-rules authenticated? :auth-rules authenticated?
:current-user user :current-user user
:body [body {:address s/Str :body [body {:address s/Str
:is_hidden_in_hunters s/Bool}] :is-hidden-in-hunters s/Bool}]
:summary "Updates user's fields." :summary "Updates user's fields."
(let [user-id (:id user) (let [user-id (:id user)
{:keys [address]} body] {:keys [address is-hidden-in-hunters]} body]
(when-not (eth/valid-address? address) (when-not (eth/valid-address? address)
(log/debugf "POST /user: Wrong address %s" address) (log/debugf "POST /user: Wrong address %s" address)
@ -205,7 +208,8 @@
(db/with-tx (db/with-tx
(when-not (db/user-exists? {:id user-id}) (when-not (db/user-exists? {:id user-id})
(not-found! "No such a user.")) (not-found! "No such a user."))
(db/update! :users body ["id = ?" user-id])) (db/update! :users (to-db-map address is-hidden-in-hunters)
["id = ?" user-id]))
(ok))) (ok)))
@ -229,7 +233,7 @@
result (bounties-db/update-payout-hash result (bounties-db/update-payout-hash
(Integer/parseInt issue) (Integer/parseInt issue)
payout-hash)] payout-hash)]
(log/info "result" result) (log/debug "result" result)
(if (= 1 result) (if (= 1 result)
(ok) (ok)
(internal-server-error))))) (internal-server-error)))))
@ -237,4 +241,22 @@
:auth-rules authenticated? :auth-rules authenticated?
:current-user user :current-user user
(log/debug "/user/bounties") (log/debug "/user/bounties")
(ok (user-bounties user)))))) (ok (user-bounties user)))
(POST "/revoke" {{issue-id :issue-id} :params}
:auth-rules authenticated?
:current-user user
(let [{:keys [contract-address owner-address]} (issues/get-issue-by-id issue-id)]
(do (log/infof "calling revoke-initiate for %s with %s %s" issue-id contract-address owner-address)
(if-let [execute-hash (execute-revocation issue-id contract-address owner-address)]
(ok {:issue-id issue-id
:execute-hash execute-hash
:contract-address contract-address})
(bad-request (str "Unable to withdraw funds from " contract-address))))))
(POST "/remove-bot-confirmation" {{issue-id :issue-id} :params}
:auth-rules authenticated?
:current-user user
(do (log/infof "calling remove-bot-confirmation for %s " issue-id)
;; if this resulted in updating a row, return success
(if (pos? (issues/reset-bot-confirmation issue-id))
(ok (str "Updated execute and confirm hash for " issue-id))
(bad-request (str "Unable to update execute and confirm hash for " issue-id))))))))

View File

@ -125,8 +125,8 @@
:pr_number pr-number :pr_number pr-number
:title pr-title :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
@ -134,17 +134,17 @@
;; merged ;; merged
(cond (cond
open-or-edit? (do open-or-edit? (do
(log/infof "issue %s: PR with reference to bounty issue opened" (:issue_number issue)) (log/infof "issue %s: PR with reference to bounty issue opened" (:issue-number issue))
(pull-requests/save (merge pr-data {:state :opened (pull-requests/save (merge pr-data {:state :opened
:commit_sha head-sha}))) :commit_sha head-sha})))
close? (if merged? close? (if merged?
(do (log/infof "issue %s: PR with reference to bounty issue merged" (:issue_number issue)) (do (log/infof "issue %s: PR with reference to bounty issue merged" (:issue-number issue))
(pull-requests/save (pull-requests/save
(merge pr-data {:state :merged (merge pr-data {:state :merged
:commit_sha head-sha})) :commit_sha head-sha}))
(issues/update-commit-sha (:issue_id issue) head-sha) (issues/update-commit-sha (:issue-id issue) head-sha)
(db-bounties/update-winner-login (:issue_id issue) login)) (db-bounties/update-winner-login (:issue-id issue) login))
(do (log/infof "issue %s: PR with reference to bounty issue closed with no merge" (:issue_number issue)) (do (log/infof "issue %s: PR with reference to bounty issue closed with no merge" (:issue-number issue))
(pull-requests/save (pull-requests/save
(merge pr-data {:state :closed (merge pr-data {:state :closed
:commit_sha head-sha}))))))) :commit_sha head-sha})))))))
@ -177,7 +177,7 @@
(doseq [issue issues] (doseq [issue issues]
(if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue (if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue
(do (do
(log/info "Referenced bounty issue found" owner repo (:issue_number issue)) (log/info "Referenced bounty issue found" owner repo (:issue-number issue))
(handle-claim issue (handle-claim issue
user-id user-id
login name login name

View File

@ -2,16 +2,18 @@
(:require [commiteth.eth.core :as eth] (:require [commiteth.eth.core :as eth]
[commiteth.eth.multisig-wallet :as multisig] [commiteth.eth.multisig-wallet :as multisig]
[commiteth.eth.token-data :as token-data] [commiteth.eth.token-data :as token-data]
[commiteth.eth.tracker :as tracker]
[commiteth.github.core :as github] [commiteth.github.core :as github]
[commiteth.db.issues :as issues] [commiteth.db.issues :as issues]
[commiteth.eth.tracker :as tracker]
[commiteth.util.util :refer [to-map]]
[taoensso.tufte :as tufte :refer (defnp p profiled profile)] [taoensso.tufte :as tufte :refer (defnp p profiled profile)]
[commiteth.db.bounties :as db-bounties] [commiteth.db.bounties :as db-bounties]
[commiteth.bounties :as bounties] [commiteth.bounties :as bounties]
[commiteth.util.crypto-fiat-value :as fiat-util] [commiteth.util.crypto-fiat-value :as fiat-util]
[commiteth.util.util :refer [eth-decimal->str]] [commiteth.util.util :as util]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
[mount.core :as mount] [mount.core :as mount]
[clojure.string :as str]
[clj-time.core :as t] [clj-time.core :as t]
[clj-time.coerce :as time-coerce] [clj-time.coerce :as time-coerce]
[clj-time.periodic :refer [periodic-seq]] [clj-time.periodic :refer [periodic-seq]]
@ -30,12 +32,10 @@
(profile {} (update-watch-hash)) (profile {} (update-watch-hash))
(profile {} (update-payout-receipt)) (profile {} (update-payout-receipt))
(profile {} (update-contract-internal-balances)) (profile {} (update-contract-internal-balances))
(profile {} (update-open-issue-usd-values))
(profile {} (update-balances)) (profile {} (update-balances))
(profile {} (profile {}
(doseq [i (range 5)] (doseq [i (range 5)]
(update-contract-internal-balances) (update-contract-internal-balances)
(update-open-issue-usd-values)
(update-balances))) (update-balances)))
) )
@ -46,41 +46,14 @@
[] []
(log/info "In update-issue-contract-address") (log/info "In update-issue-contract-address")
(p :update-issue-contract-address (p :update-issue-contract-address
(doseq [{issue-id :issue_id (doseq [{:keys [issue-id transaction-hash] :as issue} (issues/list-pending-deployments)]
transaction-hash :transaction_hash} (issues/list-pending-deployments)]
(log/infof "issue %s: pending deployment: %s" issue-id 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/infof "issue %s: update-issue-contract-address: tx receipt: %s" 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)] (if-let [contract-address (multisig/find-created-multisig-address receipt)]
(let [_ (tracker/untrack-tx! {:issue-id issue-id (bounties/transition (assoc issue :contract-address contract-address)
:tx-hash transaction-hash :opened)
:result contract-address
:type :deploy})
{owner :owner
repo :repo
comment-id :comment_id
issue-number :issue_number} (issues/get-issue-by-id issue-id)
balance-eth-str (eth/get-balance-eth contract-address 6)
balance-eth (read-string balance-eth-str)]
(log/infof "issue %s: Updating comment image" issue-id)
(bounties/update-bounty-comment-image issue-id
owner
repo
issue-number
contract-address
balance-eth
balance-eth-str
{})
(log/infof "issue %s: Updating comment" issue-id)
(github/update-comment owner
repo
comment-id
issue-number
contract-address
balance-eth
balance-eth-str
{}))
(log/errorf "issue %s: Failed to find contract address in tx logs" issue-id))) (log/errorf "issue %s: Failed to find contract address in tx logs" issue-id)))
(catch Throwable ex (catch Throwable ex
(log/errorf ex "issue %s: update-issue-contract-address exception:" issue-id))))) (log/errorf ex "issue %s: update-issue-contract-address exception:" issue-id)))))
@ -93,8 +66,8 @@
label is addded to an issue. This function deploys such contracts." label is addded to an issue. This function deploys such contracts."
[] []
(p :deploy-pending-contracts (p :deploy-pending-contracts
(doseq [{issue-id :issue_id (doseq [{:keys [issue-id owner-address]}
owner-address :owner_address} (db-bounties/pending-contracts)] (db-bounties/pending-contracts)]
(log/infof "issue %s: Trying to re-deploy failed bounty contract deployment" issue-id) (log/infof "issue %s: Trying to re-deploy failed bounty contract deployment" issue-id)
(try (try
(bounties/deploy-contract owner-address issue-id) (bounties/deploy-contract owner-address issue-id)
@ -106,87 +79,69 @@
[] []
(log/info "In self-sign-bounty") (log/info "In self-sign-bounty")
(p :self-sign-bounty (p :self-sign-bounty
(doseq [{contract-address :contract_address (doseq [{:keys [contract-address winner-address issue-id] :as issue}
issue-id :issue_id (db-bounties/pending-bounties)]
payout-address :payout_address
repo :repo
owner :owner
comment-id :comment_id
issue-number :issue_number
balance-eth :balance_eth
tokens :tokens
winner-login :winner_login} (db-bounties/pending-bounties)]
(try (try
;; TODO(martin) delete this shortly after org-dashboard deploy ;; TODO(martin) delete this shortly after org-dashboard deploy
;; as we're now setting `winner_login` when handling a new claims ;; as we're now setting `winner_login` when handling a new claims
;; coming in via webhooks (see `commiteth.routes.webhooks/handle-claim`) ;; coming in via webhooks (see `commiteth.routes.webhooks/handle-claim`)
(db-bounties/update-winner-login issue-id winner-login) ;(db-bounties/update-winner-login issue-id winner-login)
(let [value (eth/get-balance-hex contract-address)] (bounties/execute-payout issue-id contract-address winner-address)
(if (empty? payout-address)
(do
(log/warn "issue %s: Cannot sign pending bounty - winner (%s) has no payout address" issue-id winner-login)
(github/update-merged-issue-comment owner
repo
comment-id
contract-address
(eth-decimal->str balance-eth)
tokens
winner-login
true))
(let [tx-info (multisig/send-all {:contract contract-address
:payout-address payout-address
:internal-tx-id [:execute issue-id]})]
(log/infof "issue %s: Payout self-signed, called sign-all(%s) tx: %s" issue-id contract-address payout-address (:tx-hash tx-info))
(tracker/track-tx! tx-info)
(github/update-merged-issue-comment owner
repo
comment-id
contract-address
(eth-decimal->str balance-eth)
tokens
winner-login
false))))
(catch Throwable ex (catch Throwable ex
(log/error ex "issue %s: self-sign-bounty exception" issue-id))))) (log/error ex "issue %s: self-sign-bounty exception" issue-id)))))
(log/info "Exit self-sign-bounty")) (log/info "Exit self-sign-bounty"))
(defn update-confirm-hash (defn update-confirm-hash
"Gets transaction receipt for each pending payout and updates DB confirm_hash with tranaction ID of commiteth bot account's confirmation." "Gets transaction receipt for each pending payout and updates DB confirm_hash with tranaction ID of commiteth bot account's confirmation."
[] [issue-id execute-hash]
(log/info "In update-confirm-hash") (log/info "In update-confirm-hash")
(p :update-confirm-hash (p :update-confirm-hash
(doseq [{issue-id :issue_id
execute-hash :execute_hash} (db-bounties/pending-payouts)]
(log/infof "issue %s: pending payout: %s" issue-id execute-hash)
(try (try
(log/infof "issue %s: pending payout: %s" issue-id execute-hash)
(when-let [receipt (eth/get-transaction-receipt execute-hash)] (when-let [receipt (eth/get-transaction-receipt execute-hash)]
(log/infof "issue %s: execution receipt for issue " issue-id receipt) (log/infof "issue %s: execution receipt for issue " issue-id receipt)
(when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)] (when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)]
(log/infof "issue %s: confirm hash:" issue-id confirm-hash) (log/infof "issue %s: confirm hash: %s" issue-id confirm-hash)
(tracker/untrack-tx! {:issue-id issue-id (bounties/transition {:issue-id issue-id
:confirm-hash confirm-hash
:tx-info {:issue-id issue-id
:tx-hash execute-hash :tx-hash execute-hash
:result confirm-hash :result confirm-hash
:type :execute}))) :type :execute}}
(catch Throwable ex :pending-maintainer-confirmation)
(log/errorf ex "issue %s: update-confirm-hash exception:" issue-id)))))
(log/info "Exit update-confirm-hash"))
))
(catch Throwable ex
(log/errorf ex "issue %s: update-confirm-hash exception:" issue-id)))
(log/info "Exit update-confirm-hash")))
(defn update-confirm-hashes
"Gets transaction receipt for each pending payout and updates DB confirm_hash with tranaction ID of commiteth bot account's confirmation."
[]
(log/info "In update-confirm-hashes")
(p :update-confirm-hash
(doseq [{:keys [issue-id execute-hash]} (db-bounties/pending-payouts)]
(update-confirm-hash issue-id execute-hash)))
(log/info "Exit update-confirm-hashes"))
(defn update-watch-hash (defn update-watch-hash
"Sets watch-hash to NULL for bounties where watch tx has been mined. Used to avoid unneeded watch transactions in update-bounty-token-balances" "Sets watch-hash to NULL for bounties where watch tx has been mined. Used to avoid unneeded watch transactions in update-bounty-token-balances"
[] []
(p :update-watch-hash (p :update-watch-hash
(doseq [{issue-id :issue_id (doseq [{:keys [issue-id watch-hash]} (db-bounties/pending-watch-calls)]
watch-hash :watch_hash} (db-bounties/pending-watch-calls)]
(log/infof "issue %s: pending watch call %s" issue-id watch-hash) (log/infof "issue %s: pending watch call %s" issue-id watch-hash)
(try (try
(when-let [receipt (eth/get-transaction-receipt watch-hash)] (when-let [receipt (eth/get-transaction-receipt watch-hash)]
(tracker/untrack-tx! {:issue-id issue-id (bounties/transition {:issue-id issue-id
:tx-info
{:issue-id issue-id
:tx-hash watch-hash :tx-hash watch-hash
:result nil :result nil
:type :watch})) :type :watch}} :watch-reset))
(catch Throwable ex (catch Throwable ex
(log/errorf ex "issue %s: update-watch-hash exception:" issue-id)))))) (log/errorf ex "issue %s: update-watch-hash exception:" issue-id))
))))
(defn older-than-3h? (defn older-than-3h?
@ -199,23 +154,12 @@
(defn update-payout-receipt (defn update-payout-receipt
"Gets transaction receipt for each confirmed payout and updates payout_hash" "Gets transaction receipt for each confirmed payout and updates payout_hash"
[] [{:keys [payout-hash contract-address confirm-hash issue-id updated] :as bounty}]
{:pre [(util/contains-all-keys bounty db-bounties/payout-receipt-keys)]}
(log/info "In update-payout-receipt") (log/info "In update-payout-receipt")
(p :update-payout-receipt (p :update-payout-receipt
(doseq [{issue-id :issue_id
payout-hash :payout_hash
contract-address :contract_address
repo :repo
owner :owner
comment-id :comment_id
issue-number :issue_number
balance-eth :balance_eth
tokens :tokens
confirm-id :confirm_hash
payee-login :payee_login
updated :updated} (db-bounties/confirmed-payouts)]
(log/infof "issue %s: confirmed payout: %s" issue-id payout-hash)
(try (try
(log/infof "issue %s: confirmed payout: %s" issue-id payout-hash)
(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)
contract-eth-balance (eth/get-balance-wei contract-address)] contract-eth-balance (eth/get-balance-wei contract-address)]
@ -224,27 +168,37 @@
(> contract-eth-balance 0)) (> contract-eth-balance 0))
(do (do
(log/infof "issue %s: Contract (%s) still has funds" issue-id contract-address) (log/infof "issue %s: Contract (%s) still has funds" issue-id contract-address)
(when (multisig/is-confirmed? contract-address confirm-id) (when (multisig/is-confirmed? contract-address confirm-hash)
(log/infof "issue %s: Detected bounty with funds and confirmed payout, calling executeTransaction" issue-id) (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-id)] (let [execute-tx-hash (multisig/execute-tx contract-address confirm-hash)]
(log/infof "issue %s: execute tx: %s" issue-id execute-tx-hash)))) (log/infof "issue %s: execute tx: %s" issue-id execute-tx-hash))))
(do (do
(log/infof "issue %s: Payout has succeeded, payout receipt %s" issue-id receipt) (log/infof "issue %s: Payout has succeeded, payout receipt %s" issue-id receipt)
(db-bounties/update-payout-receipt issue-id receipt) (bounties/transition (assoc bounty :payout-receipt receipt) :paid))))
(github/update-paid-issue-comment owner
repo
comment-id
contract-address
(eth-decimal->str balance-eth)
tokens
payee-login))))
(when (older-than-3h? updated) (when (older-than-3h? updated)
(log/warn "issue %s: Resetting payout hash for issue as it has not been mined in 3h" issue-id) (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
(log/error ex "issue %s: update-payout-receipt exception" issue-id))))) (log/error ex "issue %s: update-payout-receipt exception" issue-id)))))
(log/info "Exit update-payout-receipt"))
(defn update-payout-receipts
"Gets transaction receipt for each confirmed payout and updates payout_hash"
[]
(log/info "In update-payout-receipts")
(p :update-payout-receipts
(doseq [bounty (db-bounties/confirmed-payouts)]
(update-payout-receipt bounty))
(log/info "Exit update-payout-receipts")))
(defn update-revoked-payout-receipts
"Gets transaction receipt for each confirmed revocation and updates payout_hash"
[]
(log/info "In update-revoked-payout-receipts")
(p :update-revoked-payout-receipts
;; todo see if confirmed-payouts & confirmed-revocation-payouts can be combined
(doseq [bounty (db-bounties/confirmed-revocation-payouts)]
(update-payout-receipt bounty))
(log/info "Exit update-revoked-payout-receipts")))
(defn abs (defn abs
"(abs n) is the absolute value of n" "(abs n) is the absolute value of n"
@ -255,7 +209,6 @@
(neg? n) (- n) (neg? n) (- n)
:else n)) :else n))
(defn update-bounty-token-balances (defn update-bounty-token-balances
"Helper function for updating internal ERC20 token balances to token "Helper function for updating internal ERC20 token balances to token
multisig contract. Will be called periodically for all open bounty multisig contract. Will be called periodically for all open bounty
@ -275,7 +228,9 @@
(let [tx-info (multisig/watch-token {:bounty-addr bounty-addr (let [tx-info (multisig/watch-token {:bounty-addr bounty-addr
:token tla :token tla
:internal-tx-id [:watch issue-id]})] :internal-tx-id [:watch issue-id]})]
(tracker/track-tx! tx-info))))))) (bounties/transition {:issue-id issue-id
:tx-info tx-info}
:watch-set)))))))
(catch Throwable ex (catch Throwable ex
(log/error ex "bounty %s: update-bounty-token-balances exception" bounty-addr)))) (log/error ex "bounty %s: update-bounty-token-balances exception" bounty-addr))))
(log/info "Exit update-bounty-token-balances")) (log/info "Exit update-bounty-token-balances"))
@ -286,39 +241,11 @@
[] []
(log/info "In update-contract-internal-balances") (log/info "In update-contract-internal-balances")
(p :update-contract-internal-balances (p :update-contract-internal-balances
(doseq [{issue-id :issue_id (doseq [{:keys [issue-id contract-address watch-hash]}
bounty-address :contract_address
watch-hash :watch_hash}
(db-bounties/open-bounty-contracts)] (db-bounties/open-bounty-contracts)]
(update-bounty-token-balances issue-id bounty-address watch-hash))) (update-bounty-token-balances issue-id contract-address watch-hash)))
(log/info "Exit update-contract-internal-balances")) (log/info "Exit update-contract-internal-balances"))
(defn get-bounty-funds
"Get funds in given bounty contract.
Returns map of asset -> balance
+ key total-usd -> current total USD value for all funds"
[bounty-addr]
(let [token-balances (multisig/token-balances bounty-addr)
eth-balance (read-string (eth/get-balance-eth bounty-addr 6))
all-funds
(merge token-balances
{:ETH eth-balance})]
(merge all-funds {:total-usd (fiat-util/bounty-usd-value all-funds)})))
(defn update-issue-usd-value
[bounty-addr]
(let [funds (get-bounty-funds bounty-addr)]
(issues/update-usd-value bounty-addr
(:total-usd funds))))
(defn update-open-issue-usd-values
"Sum up current USD values of all crypto assets in a bounty and store to DB"
[]
(p :update-open-issue-usd-values
(doseq [{bounty-addr :contract_address}
(db-bounties/open-bounty-contracts)]
(update-issue-usd-value bounty-addr))))
(defn float= (defn float=
([x y] (float= x y 0.0000001)) ([x y] (float= x y 0.0000001))
@ -331,56 +258,43 @@
(and (= (set (keys m1)) (set (keys m2))) (and (= (set (keys m1)) (set (keys m2)))
(every? #(float= (get m1 %1) (get m2 %1)) (keys m1)))) (every? #(float= (get m1 %1) (get m2 %1)) (keys m1))))
(defn update-balances (defn update-balances
[] []
(log/info "In update-balances") (log/info "In update-balances")
(p :update-balances (p :update-balances
(doseq [{contract-address :contract_address (doseq [{:keys [contract-address owner
owner :owner repo balance-eth tokens
repo :repo issue-id
comment-id :comment_id issue-number
issue-id :issue_id comment-id] :as issue}
db-balance-eth :balance_eth (db-bounties/open-bounty-contracts)]
db-tokens :tokens
issue-number :issue_number} (db-bounties/open-bounty-contracts)]
(try (try
(when comment-id (when comment-id
(let [balance-eth-str (eth/get-balance-eth contract-address 6) (let [balance-eth-str (eth/get-balance-eth contract-address 6)
balance-eth (read-string balance-eth-str) current-balance-eth (read-string balance-eth-str)
token-balances (multisig/token-balances contract-address)] token-balances (multisig/token-balances contract-address)]
(log/debug "issue" issue-id ": update-balances" balance-eth (log/debug "update-balances" balance-eth
balance-eth-str token-balances owner repo issue-number) balance-eth-str token-balances owner repo issue-number)
(when (or (when (or
(not (float= db-balance-eth balance-eth)) (not (float= current-balance-eth balance-eth))
(not (map-float= db-tokens token-balances))) (not (map-float= tokens token-balances)))
(log/info "balances differ") (log/info "balances differ")
(log/info "ETH (db):" db-balance-eth (type db-balance-eth) ) (log/info "ETH (db):" balance-eth (type balance-eth) )
(log/info "ETH (chain):" balance-eth (type balance-eth) ) (log/info "ETH (chain):" current-balance-eth (type current-balance-eth) )
(log/info "ETH cmp:" (float= db-balance-eth balance-eth)) (log/info "ETH cmp:" (float= balance-eth current-balance-eth))
(log/info "tokens (db):" db-tokens (type db-tokens) (type (:SNT db-tokens))) (log/info "tokens (db):" tokens (type tokens) (type (:SNT tokens)))
(log/info "tokens (chain):" token-balances (type token-balances) (type (:SNT token-balances))) (log/info "tokens (chain):" token-balances (type token-balances) (type (:SNT token-balances)))
(log/debug "tokens cmp:" (= db-tokens token-balances)) (log/debug "tokens cmp:" (= tokens token-balances))
(bounties/transition {:issue-id issue-id
:balance-eth current-balance-eth
:tokens token-balances
:value-usd (fiat-util/bounty-usd-value
(merge token-balances {:ETH current-balance-eth}))} :update-balances)
(issues/update-eth-balance contract-address balance-eth)
(issues/update-token-balances contract-address token-balances) )))
(bounties/update-bounty-comment-image issue-id
owner
repo
issue-number
contract-address
balance-eth
balance-eth-str
token-balances)
(github/update-comment owner
repo
comment-id
issue-number
contract-address
balance-eth
balance-eth-str
token-balances)
(update-issue-usd-value contract-address))))
(catch Throwable ex (catch Throwable ex
(log/error ex "issue %s: update-balances exception" issue-id))))) (log/error ex "issue %s: update-balances exception" issue-id)))))
(log/info "Exit update-balances")) (log/info "Exit update-balances"))
@ -412,8 +326,9 @@
(run-tasks (run-tasks
[deploy-pending-contracts [deploy-pending-contracts
update-issue-contract-address update-issue-contract-address
update-confirm-hash update-confirm-hashes
update-payout-receipt update-payout-receipts
update-revoked-payout-receipts
update-watch-hash update-watch-hash
check-tx-receipts check-tx-receipts
self-sign-bounty self-sign-bounty
@ -426,8 +341,7 @@
(log/info "run-10-min-interval-tasks" time) (log/info "run-10-min-interval-tasks" time)
(run-tasks (run-tasks
[update-contract-internal-balances [update-contract-internal-balances
update-balances update-balances])
update-open-issue-usd-values])
(log/info "run-10-min-interval-tasks done"))) (log/info "run-10-min-interval-tasks done")))

View File

@ -1,7 +1,6 @@
(ns commiteth.util.png-rendering (ns commiteth.util.png-rendering
(:require [commiteth.layout :refer [render]] (:require [commiteth.layout :refer [render]]
[commiteth.config :refer [env]] [commiteth.config :refer [env]]
[commiteth.github.core :as github]
[commiteth.db.comment-images :as db] [commiteth.db.comment-images :as db]
[commiteth.db.bounties :as db-bounties] [commiteth.db.bounties :as db-bounties]
[clj.qrgen :as qr] [clj.qrgen :as qr]
@ -58,21 +57,6 @@
nil)))) nil))))
(defn export-comment-image
"Retrieve image PNG from DB and write to file"
[owner repo issue-number filename]
(let [{owner :owner
repo :repo
issue-id :issue_id
balance-eth :balance_eth} (db-bounties/get-bounty owner repo issue-number)
hash (github/github-comment-hash
owner
repo
issue-number
balance-eth)]
(with-open [w (io/output-stream filename)]
(.write w (:png_data (db/get-image-data issue-id hash))))))
(comment (comment
(with-open [w (io/output-stream "foo.png")] (with-open [w (io/output-stream "foo.png")]

View File

@ -1,6 +1,7 @@
(ns commiteth.util.util (ns commiteth.util.util
(:require (:require
[clj-http.client :as http] [clj-http.client :as http]
[clojure.string :as str]
[clojure.data.json :as json])) [clojure.data.json :as json]))
@ -14,3 +15,14 @@
(->> (http/get url) (->> (http/get url)
(:body) (:body)
(json/read-str))) (json/read-str)))
(defmacro to-map [& vars]
(into {} (map #(vector (keyword %1) %1) vars)))
(defmacro to-db-map [& vars]
(into {} (map #(vector (keyword (str/replace (name %1) "-" "_")) %1) vars)))
(defn contains-all-keys [m ks]
{:pre [(map? m) [(vector? ks)]]}
(every?
#(contains? m %) ks))

View File

@ -11,20 +11,20 @@
;; to communicate what datatypes are returned where. ;; to communicate what datatypes are returned where.
(defn open? [claim] (defn open? [claim]
(assert (find claim :pr_state)) (assert (find claim :pr-state))
(= 0 (:pr_state claim))) (= 0 (:pr-state claim)))
(defn merged? [claim] (defn merged? [claim]
(assert (find claim :pr_state)) (assert (find claim :pr-state))
(= 1 (:pr_state claim))) (= 1 (:pr-state claim)))
(defn paid? [claim] (defn paid? [claim]
(assert (find claim :payout_hash)) (assert (find claim :payout-hash))
(not-empty (:payout_hash claim))) (not-empty (:payout-hash claim)))
(defn bot-confirm-unmined? [bounty] (defn bot-confirm-unmined? [bounty]
(assert (find bounty :confirm_hash)) (assert (find bounty :confirm-hash))
(empty? (:confirm_hash bounty))) (empty? (:confirm-hash bounty)))
(defn confirming? [bounty] (defn confirming? [bounty]
(:confirming? bounty)) (:confirming? bounty))

View File

@ -5,6 +5,14 @@
{:pre [(string? tla)]} {:pre [(string? tla)]}
(get {"ETH" "#57a7ed"} tla "#4360df")) (get {"ETH" "#57a7ed"} tla "#4360df"))
(defn pending-badge []
"static component for pending badge"
[:div.dib.ph2.pv1.relative
{:style {:color "#CCAC00"}}
[:div.absolute.top-0.left-0.right-0.bottom-0.o-30.br2
{:style {:background-color "#FFD700"}}]
[:span.pg-med "Refund pending"]])
(defn balance-badge (defn balance-badge
[tla balance] [tla balance]
{:pre [(keyword? tla)]} {:pre [(keyword? tla)]}

View File

@ -45,7 +45,7 @@
(defn bounty-item [bounty] (defn bounty-item [bounty]
(let [open-bounty-claims (rf/subscribe [::subs/open-bounty-claims])] (let [open-bounty-claims (rf/subscribe [::subs/open-bounty-claims])]
(fn [bounty] (fn [bounty]
(let [{avatar-url :repo_owner_avatar_url (let [{avatar-url :repo-owner-avatar-url
owner :repo-owner owner :repo-owner
repo-name :repo-name repo-name :repo-name
issue-title :issue-title issue-title :issue-title

View File

@ -7,6 +7,7 @@
[commiteth.routes] [commiteth.routes]
[commiteth.handlers] [commiteth.handlers]
[commiteth.subscriptions] [commiteth.subscriptions]
[commiteth.interceptors]
[commiteth.activity :refer [activity-page]] [commiteth.activity :refer [activity-page]]
[commiteth.bounties :refer [bounties-page]] [commiteth.bounties :refer [bounties-page]]
[commiteth.repos :refer [repos-page]] [commiteth.repos :refer [repos-page]]

View File

@ -19,6 +19,7 @@
::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 #{} ::open-bounty-claims #{}
::pending-revocations {}
:owner-bounties {} :owner-bounties {}
:top-hunters [] :top-hunters []
:activity-feed []}) :activity-feed []})

View File

@ -1,6 +1,7 @@
(ns commiteth.handlers (ns commiteth.handlers
(:require [commiteth.db :as db] (:require [commiteth.db :as db]
[re-frame.core :refer [dispatch [re-frame.core :refer [debug
dispatch
reg-event-db reg-event-db
reg-event-fx reg-event-fx
reg-fx reg-fx
@ -14,12 +15,16 @@
:refer [reg-co-fx!]] :refer [reg-co-fx!]]
[commiteth.ui-model :as ui-model] [commiteth.ui-model :as ui-model]
[commiteth.common :as common] [commiteth.common :as common]
[commiteth.routes :as routes])) [commiteth.routes :as routes]
[commiteth.interceptors]))
(rf-storage/reg-co-fx! :commiteth-sob {:fx :store (rf-storage/reg-co-fx! :commiteth-sob {:fx :store
:cofx :store}) :cofx :store})
;; https://github.com/Day8/re-frame/blob/master/docs/Debugging-Event-Handlers.md
(def interceptors [(when ^boolean goog.DEBUG debug)])
(reg-fx (reg-fx
:http :http
(fn [{:keys [method url on-success on-error finally params]}] (fn [{:keys [method url on-success on-error finally params]}]
@ -110,6 +115,16 @@
(fn [db _] (fn [db _]
(dissoc db :flash-message))) (dissoc db :flash-message)))
(reg-event-db
:set-revoke-modal
(fn [db [_ bounty]]
(assoc db :revoke-modal-bounty bounty)))
(reg-event-db
:clear-revoke-modal
(fn [db [_ bounty]]
(dissoc db :revoke-modal-bounty bounty)))
(defn assoc-in-if-not-empty [m path val] (defn assoc-in-if-not-empty [m path val]
(if (seq val) (if (seq val)
(assoc-in m path val) (assoc-in m path val)
@ -217,6 +232,8 @@
(reg-event-db (reg-event-db
:set-owner-bounties :set-owner-bounties
[commiteth.interceptors/watch-confirm-hash
commiteth.interceptors/watch-payout-receipt]
(fn [db [_ issues]] (fn [db [_ issues]]
(assoc db (assoc db
:owner-bounties issues :owner-bounties issues
@ -376,6 +393,7 @@
(reg-event-fx (reg-event-fx
:save-payout-hash :save-payout-hash
interceptors
(fn [{:keys [db]} [_ issue-id payout-hash]] (fn [{:keys [db]} [_ issue-id payout-hash]]
{:db db {:db db
:http {:method POST :http {:method POST
@ -386,13 +404,15 @@
(defn send-transaction-callback (defn send-transaction-callback
[issue-id] [issue-id pending-revocations]
(fn [error payout-hash] (fn [error payout-hash]
(println "send-transaction-callback" error payout-hash) (println "send-transaction-callback" error payout-hash)
(when error (when error
(if (empty? pending-revocations)
(dispatch [:set-flash-message (dispatch [:set-flash-message
:error :error
(str "Error sending transaction: " error)]) (str "Error sending transaction: " error)])
(dispatch [:remove-bot-confirmation issue-id]))
(dispatch [:payout-confirm-failed issue-id])) (dispatch [:payout-confirm-failed issue-id]))
(when payout-hash (when payout-hash
(dispatch [:save-payout-hash issue-id payout-hash])))) (dispatch [:save-payout-hash issue-id payout-hash]))))
@ -405,14 +425,73 @@
(defn strip-0x [x] (defn strip-0x [x]
(str/replace x #"^0x" "")) (str/replace x #"^0x" ""))
(defn set-pending-revocation [location issue-id confirming-account]
(assoc-in location [::db/pending-revocations issue-id]
{:confirming-account confirming-account}))
(reg-event-fx
:set-pending-revocation
[interceptors (inject-cofx :store)]
(fn [{:keys [db store]} [_ issue-id confirming-account]]
{:db (set-pending-revocation db issue-id confirming-account)
:store (set-pending-revocation store issue-id confirming-account)}))
(reg-event-fx
:remove-pending-revocation
[interceptors (inject-cofx :store)]
(fn [{:keys [db store]} [_ issue-id]]
{:db (dissoc-in db [::db/pending-revocations issue-id])
:store (dissoc-in store [::db/pending-revocations issue-id])}))
(reg-event-fx
:remove-bot-confirmation
interceptors
(fn [{:keys [db]} [_ issue-id]]
{:http {:method POST
:url "/api/user/remove-bot-confirmation"
:params {:token (get-admin-token db)
:issue-id issue-id}
:on-success #(dispatch [:remove-pending-revocation issue-id])
:on-error #(println "error removing bot confirmation for " issue-id)}}))
(reg-event-fx
:revoke-bounty-success
(fn [{:keys [db]} [_ {:keys [issue-id owner-address contract-address confirm-hash]}]]
{:dispatch [:set-pending-revocation issue-id :commiteth]}))
(reg-event-fx
:revoke-bounty-error
interceptors
(fn [{:keys [db]} [_ issue-id response]]
{:dispatch [:set-flash-message
:error (if (= 400 (:status response))
(:response response)
(str "Failed to initiate revocation for: " issue-id
(:status-text response)))]}))
(reg-event-fx
:revoke-bounty
interceptors
(fn [{:keys [db]} [_ issue-id]]
{:http {:method POST
:url "/api/user/revoke"
:on-success #(dispatch [:revoke-bounty-success %])
:on-error #(dispatch [:revoke-bounty-error %])
:params {:token (get-admin-token db)
:issue-id issue-id}}
:dispatch [:clear-revoke-modal]}))
(reg-event-fx (reg-event-fx
:confirm-payout :confirm-payout
(fn [{:keys [db]} [_ {issue-id :issue_id interceptors
owner-address :owner_address (fn [{:keys [db]} [_ {:keys [issue-id
contract-address :contract_address owner-address
confirm-hash :confirm_hash} issue]] contract-address
confirm-hash]
:as issue}]]
(println (:web3 db)) (println (:web3 db))
(let [w3 (:web3 db) (let [w3 (:web3 db)
pending-revocations (::db/pending-revocations db)
confirm-method-id (sig->method-id w3 "confirmTransaction(uint256)") confirm-method-id (sig->method-id w3 "confirmTransaction(uint256)")
confirm-id (strip-0x confirm-hash) confirm-id (strip-0x confirm-hash)
data (str confirm-method-id data (str confirm-method-id
@ -426,7 +505,7 @@
(println "data:" data) (println "data:" data)
(try (try
(web3-eth/send-transaction! w3 payload (web3-eth/send-transaction! w3 payload
(send-transaction-callback issue-id)) (send-transaction-callback issue-id pending-revocations))
{:db (assoc-in db [:owner-bounties issue-id :confirming?] true)} {:db (assoc-in db [:owner-bounties issue-id :confirming?] true)}
(catch js/Error e (catch js/Error e
{:db (assoc-in db [:owner-bounties issue-id :confirm-failed?] true) {:db (assoc-in db [:owner-bounties issue-id :confirm-failed?] true)
@ -437,6 +516,7 @@
(reg-event-fx (reg-event-fx
:payout-confirmed :payout-confirmed
interceptors
(fn [{:keys [db]} [_ issue-id]] (fn [{:keys [db]} [_ issue-id]]
{:dispatch [:load-owner-bounties] {:dispatch [:load-owner-bounties]
:db (-> db :db (-> db
@ -490,6 +570,21 @@
(.removeEventListener js/window "click" close-dropdown) (.removeEventListener js/window "click" close-dropdown)
(assoc db :user-dropdown-open? false))) (assoc db :user-dropdown-open? false)))
(defn close-three-dots []
(dispatch [:three-dots-close]))
(reg-event-db
:three-dots-open
(fn [db [_ issue-id]]
(.addEventListener js/window "click" close-three-dots)
(update db ::db/unclaimed-options (fnil conj #{}) issue-id)))
(reg-event-db
:three-dots-close
(fn [db [_ issue-id]]
(.removeEventListener js/window "click" close-three-dots)
(assoc db ::db/unclaimed-options #{})))
(reg-event-db (reg-event-db
::open-bounty-claim ::open-bounty-claim
(fn [db [_ opening-issue-id]] (fn [db [_ opening-issue-id]]

View File

@ -0,0 +1,76 @@
(ns commiteth.interceptors
(:require [commiteth.db :as db]
[re-frame.core :as rf]
[clojure.data :as data]))
(defn get-confirming-issue-id [owner pending-revocations]
"returns the issue id for the current revocation matching the desired owner type"
(some (fn [[issue-id revocation]]
(when (= owner (:confirming-account revocation))
issue-id))
pending-revocations))
(defn dispatch-confirm-payout [bounty]
"dispatches a bounty via reframe dispatch"
(rf/dispatch [:confirm-payout bounty]))
(defn dispatch-set-pending-revocation [bounty]
"update the currently confirming account to owner"
(rf/dispatch [:set-pending-revocation (:issue-id bounty) :owner]))
(defn dispatch-remove-pending-revocation [bounty]
"dispatches a bounty via reframe dispatch"
(rf/dispatch [:remove-pending-revocation (:issue-id bounty)]))
(def watch-confirm-hash
"revocations move through 2 states, confirmation by commiteth and then the repo owner
if a commiteth revocation is detected, check to see if its confirm hash is set, and, if it is
dispatch a confirm payout event and update the confirming account to owner
*Warning* this inteceptor is only intended for use with the
`:load-owner-bounties` event
More information on re-frame interceptors can be found here:
https://github.com/Day8/re-frame/blob/master/docs/Interceptors.md"
(rf/->interceptor
:id :watch-confirm-hash
:after (fn confirm-hash-update-after
[context]
(println "watch confirm hash interceptor...")
(let [pending-revocations (get-in context [:effects :db ::db/pending-revocations])
updated-bounties (get-in context [:effects :db :owner-bounties])
confirming-issue-id (get-confirming-issue-id :commiteth pending-revocations)]
(when-let [revoking-bounty (get updated-bounties confirming-issue-id)]
(if (:confirm-hash revoking-bounty)
(do (dispatch-confirm-payout revoking-bounty)
(dispatch-set-pending-revocation revoking-bounty))
(println (str "currently revoking " confirming-issue-id " but confirm hash has not been set yet."))))
;; interceptor must return context
context))))
(def watch-payout-receipt
"examine pending revocations with their currently confirming account set to owner
when one of them has its payout_receipt set, dispatch `remove-pending-revocation`
*Warning* this inteceptor is only intended for use with the
`:load-owner-bounties` event
More information on re-frame interceptors can be found here:
https://github.com/Day8/re-frame/blob/master/docs/Interceptors.md"
(rf/->interceptor
:id :watch-payout-receipt
:after (fn payout-receipt-update-after
[context]
(println "watch payout receipt interceptor...")
(let [pending-revocations (get-in context [:effects :db ::db/pending-revocations])
updated-bounties (get-in context [:effects :db :owner-bounties])
confirming-issue-id (get-confirming-issue-id :owner pending-revocations)]
(when-let [revoking-bounty (get updated-bounties confirming-issue-id)]
(if (:payout-receipt revoking-bounty)
(dispatch-remove-pending-revocation revoking-bounty)
(println (str "currently revoking " confirming-issue-id " but payout receipt has not been set yet."))))
;; interceptor must return context
context))))

View File

@ -6,13 +6,24 @@
[commiteth.routes :as routes] [commiteth.routes :as routes]
[commiteth.model.bounty :as bnt] [commiteth.model.bounty :as bnt]
[commiteth.ui.balances :as ui-balances] [commiteth.ui.balances :as ui-balances]
[commiteth.config :as config]
[commiteth.common :as common :refer [human-time]])) [commiteth.common :as common :refer [human-time]]))
(defn pr-url [{owner :repo_owner (defn pr-url [{owner :repo-owner
pr-number :pr_number pr-number :pr-number
repo :repo_name}] repo :repo-name}]
(str "https://github.com/" owner "/" repo "/pull/" pr-number)) (str "https://github.com/" owner "/" repo "/pull/" pr-number))
(defn etherscan-tx-url [tx-id]
(str "https://"
(when (config/on-testnet?) "ropsten.")
"etherscan.io/tx/" tx-id))
(defn etherscan-address-url [address]
(str "https://"
(when (config/on-testnet?) "ropsten.")
"etherscan.io/address/" address))
(def primary-button-button :button.f7.ttu.tracked.outline-0.bg-sob-blue.white.pv3.ph4.pg-med.br3.bn.pointer.shadow-7) (def primary-button-button :button.f7.ttu.tracked.outline-0.bg-sob-blue.white.pv3.ph4.pg-med.br3.bn.pointer.shadow-7)
(def primary-button-link :a.dib.tc.f7.ttu.tracked.bg-sob-blue.white.pv2.ph3.pg-med.br2.pointer.hover-white.shadow-7) (def primary-button-link :a.dib.tc.f7.ttu.tracked.bg-sob-blue.white.pv2.ph3.pg-med.br2.pointer.hover-white.shadow-7)
@ -46,7 +57,7 @@
(when (and merged? (not paid?)) (when (and merged? (not paid?))
[primary-button-button [primary-button-button
(merge {:on-click #(rf/dispatch [:confirm-payout claim])} (merge {:on-click #(rf/dispatch [:confirm-payout claim])}
(if (and merged? (not paid?) (:payout_address bounty)) (if (and merged? (not paid?) (:payout-address bounty))
{} {}
{:disabled true}) {:disabled true})
(when (and (or (bnt/confirming? bounty) (when (and (or (bnt/confirming? bounty)
@ -57,12 +68,14 @@
"Signed off" "Signed off"
"Confirm Payment")]))) "Confirm Payment")])))
(defn confirm-row [bounty claim] (defn confirm-row [bounty claim]
(let [payout-address-available? (:payout_address bounty)] (let [payout-address-available? (:payout-address bounty)]
[:div [:div
(when-not payout-address-available? (when-not payout-address-available?
[:div.bg-sob-blue-o-20.pv2.ph3.br3.mb3.f6 [:div.bg-sob-blue-o-20.pv2.ph3.br3.mb3.f6
[:p [:span.pg-med (or (:user_name claim) (:user_login claim)) [:p [:span.pg-med (or (:user-name claim) (:user-login claim))
"s payment address is pending."] " You will be able to confirm the payment once the address is provided."]]) "s payment address is pending."] " You will be able to confirm the payment once the address is provided."]])
[:div.cf [:div.cf
[:div.dt.fr [:div.dt.fr
@ -84,10 +97,10 @@
"View Pull Request"]) "View Pull Request"])
(defn claim-card [bounty claim {:keys [render-view-claim-button?] :as opts}] (defn claim-card [bounty claim {:keys [render-view-claim-button?] :as opts}]
(let [{user-name :user_name (let [{user-name :user-name
user-login :user_login user-login :user-login
avatar-url :user_avatar_url} claim avatar-url :user-avatar-url} claim
winner-login (:winner_login bounty)] winner-login (:winner-login bounty)]
[:div.pv2 [:div.pv2
[:div.flex [:div.flex
{:class (when (and (bnt/paid? claim) (not (= user-login winner-login))) {:class (when (and (bnt/paid? claim) (not (= user-login winner-login)))
@ -105,7 +118,7 @@
[:span "No payout"]))] [:span "No payout"]))]
[:div.f6.gray "Submitted a claim via " [:div.f6.gray "Submitted a claim via "
[:a.gray {:href (pr-url claim)} [:a.gray {:href (pr-url claim)}
(str (:repo_owner claim) "/" (:repo_name claim) " PR #" (:pr_number claim))]] (str (:repo-owner claim) "/" (:repo-name claim) " PR #" (:pr-number claim))]]
;; We render the button twice for difference screen sizes, first button is for small screens: ;; We render the button twice for difference screen sizes, first button is for small screens:
;; 1) db + dn-ns: `display: block` + `display: none` for not-small screens ;; 1) db + dn-ns: `display: block` + `display: none` for not-small screens
;; 2) dn + db-ns: `display: none` + `display: block` for not-small screens ;; 2) dn + db-ns: `display: none` + `display: block` for not-small screens
@ -126,7 +139,7 @@
;; FIXME we remove all bounties that Andy 'won' as this basically ;; FIXME we remove all bounties that Andy 'won' as this basically
;; has been our method for revocations. This needs to be cleaned up ASAP. ;; has been our method for revocations. This needs to be cleaned up ASAP.
;; https://github.com/status-im/open-bounty/issues/284 ;; https://github.com/status-im/open-bounty/issues/284
(for [bounty (filter #(not= "andytudhope" (:winner_login %)) bounties) (for [bounty (filter #(not= "andytudhope" (:winner-login %)) bounties)
;; Identifying the winning claim like this is a bit ;; Identifying the winning claim like this is a bit
;; imprecise if there have been two PRs for the same ;; imprecise if there have been two PRs for the same
;; bounty by the same contributor ;; bounty by the same contributor
@ -134,8 +147,8 @@
;; ignore this edge case for a first version ;; ignore this edge case for a first version
:let [winning-claim (->> (:claims bounty) :let [winning-claim (->> (:claims bounty)
(filter #(and (bnt/merged? %) (filter #(and (bnt/merged? %)
(= (:user_login %) (= (:user-login %)
(:winner_login bounty)))) (:winner-login bounty))))
util/assert-first)]] util/assert-first)]]
^{:key (:issue-id bounty)} ^{:key (:issue-id bounty)}
[:div.mb3.br3.shadow-6.bg-white [:div.mb3.br3.shadow-6.bg-white
@ -163,7 +176,7 @@
(str "Current Claims (" (count claims) ")") (str "Current Claims (" (count claims) ")")
"Current Claim")] "Current Claim")]
(for [[idx claim] (zipmap (range) claims)] (for [[idx claim] (zipmap (range) claims)]
^{:key (:pr_id claim)} ^{:key (:pr-id claim)}
[:div [:div
{:class (when (> idx 0) "bt b--light-gray pt2")} {:class (when (> idx 0) "bt b--light-gray pt2")}
[claim-card bounty claim {:render-view-claim-button? true}]])]])))) [claim-card bounty claim {:render-view-claim-button? true}]])]]))))
@ -231,16 +244,80 @@
[:div bottom]]]) [:div bottom]]])
(defn small-card-balances [bounty] (defn small-card-balances [bounty]
[:div.f6 (let [pending-revocations (rf/subscribe [:pending-revocations])]
(fn [bounty]
[:div.f6.fl.w-80
[ui-balances/token-balances (bnt/crypto-balances bounty) :label] [ui-balances/token-balances (bnt/crypto-balances bounty) :label]
[:div [:div
[ui-balances/usd-value-label (:value-usd bounty)]]]) [ui-balances/usd-value-label (:value-usd bounty)]]
(when (some #(= (:issue-id %)
(:issue-id bounty)) @pending-revocations)
[:div.pt1
[ui-balances/pending-badge]])])))
(defn three-dots-box [image-src]
"generates the appropriate container for menu dots"
[:span.pt2.pointer
[:img.o-50.pl3.pt2 {:src image-src}]])
(defn check-box [image-src]
"generates the appropriate container for a blue arrow"
[:span.pr2
[:img.w1.v-mid.o-50 {:src image-src}]])
(defn three-dots [issue-id]
[:div
[:div
{:on-click #(rf/dispatch [:three-dots-open issue-id])}
[three-dots-box "ic-more-vert-black-24dp-1x.png"]]])
(defn revoke-modal []
(let [bounty @(rf/subscribe [:revoke-modal-bounty])]
(fn []
(when bounty
(let [owner-address (:owner-address bounty)]
;; width requires a deliberate override of semantic.min.css
[:div.ui.active.modal.br3 {:style {:top 100
:width 650}}
[:div.pa4
[:h3.dark-gray "Are you sure you want to request a refund?"]
[:p.silver "This will set your bounty"
[:span.pg-med " value to $0."]
" Don't worry, your issue will still be accessible to the community. You can check the status of your refund at the top of the dashboard."]
[:div.bg-sob-tint.br3.pa3
[:p.fw4 (:issue-title bounty)]
[ui-balances/token-balances (bnt/crypto-balances bounty) :label]
[:p [ui-balances/usd-value-label (:value-usd bounty)]]
[:p.silver "To be refunded to: " owner-address]]
[:div.pt3
[primary-button-button
{:on-click #(rf/dispatch [:revoke-bounty (:issue-id bounty)])}
"REQUEST REFUND"]
[:span.dark-gray.pointer.fw4.f7.ml3
{:role "button"
:on-click #(rf/dispatch [:clear-revoke-modal])}
"CANCEL"]]]])))))
(defn revoke-dropdown [bounty]
(let [menu (if (contains? @(rf/subscribe [:three-dots-open?]) (:issue-id bounty))
[:div.ui.menu.revoke.transition {:tab-index -1}]
[:div.ui.menu.transition.hidden])]
[:div.fl.w-20
(if (empty? @(rf/subscribe [:pending-revocations]))
[three-dots (:issue-id bounty)])
(into menu [[:div
[:a.pa2
{:on-click #(rf/dispatch [:set-revoke-modal bounty])}
"Revoke"]]])]))
(defn unclaimed-bounty [bounty] (defn unclaimed-bounty [bounty]
[:div.w-third-l.fl-l.pa2 [:div.w-third-l.fl-l.pa2
[square-card [square-card
[bounty-title-link bounty {:show-date? true :max-length 60}] [bounty-title-link bounty {:show-date? true :max-length 60}]
[small-card-balances bounty]]]) [:div [small-card-balances bounty]
(when (pos? (:value-usd bounty))
[revoke-dropdown bounty])]]])
(defn paid-bounty [bounty] (defn paid-bounty [bounty]
[:div.w-third-l.fl-l.pa2 [:div.w-third-l.fl-l.pa2
@ -248,7 +325,10 @@
[:div [:div
[bounty-title-link bounty {:show-date? false :max-length 60}] [bounty-title-link bounty {:show-date? false :max-length 60}]
[:div.f6.mt1.gray [:div.f6.mt1.gray
"Paid out to " [:span.pg-med.fw5 "@" (:winner_login bounty)]]] "Paid out to " [:span.pg-med.fw5 "@" (or (:winner-login bounty)
;; use repo owner for revoked bounties
;; where no winner login is set
(:owner-login bounty))]]]
[small-card-balances bounty]]]) [small-card-balances bounty]]])
(defn expandable-bounty-list [bounty-component bounties] (defn expandable-bounty-list [bounty-component bounties]
@ -272,9 +352,27 @@
(defn count-pill [n] (defn count-pill [n]
[:span.v-top.ml3.ph3.pv1.bg-black-05.gray.br3.f7 n]) [:span.v-top.ml3.ph3.pv1.bg-black-05.gray.br3.f7 n])
(defn salute [name] (defn pending-banner []
(let [banner-info (rf/subscribe [:pending-revocations])]
(fn pending-banner-render []
(when @banner-info
(into [:div]
(for [revoking-bounty @banner-info]
^{:key (:contract-address revoking-bounty)}
[:div.relative.pa3.pr4.bg-sob-green.br3.nt1
[:div
(case (:confirming-account revoking-bounty)
:commiteth [:p.v-mid [check-box "ic-check-circle-black-24dp-2x.png"]
[:span.pg-med "Transaction sent."] " Your refund requires two confirmations. After the first one "
[:a.sob-blue.pg-med {:href (etherscan-address-url (:contract-address revoking-bounty)) :target "_blank"} " completes "]
"you'll be prompted to sign the second via metamask."]
:owner [:p.v-mid [check-box "ic-check-circle-black-24dp-2x.png"]
[:span.pg-med "Transaction sent."] " Once your metamask transaction is confirmed your revocation will be complete. Follow the final step "
[:a.sob-blue.pg-med {:href (etherscan-address-url (:contract-address revoking-bounty)) :target "_blank"} " here. "]])]]))))))
(defn salute []
(let [msg-info (rf/subscribe [:dashboard/banner-msg])] (let [msg-info (rf/subscribe [:dashboard/banner-msg])]
(fn salute-render [name] (fn salute-render []
(when @msg-info (when @msg-info
[:div.relative.pa3.pr4.bg-sob-blue-o-20.br3.nt1 [:div.relative.pa3.pr4.bg-sob-blue-o-20.br3.nt1
[:div.f3.dark-gray.absolute.top-0.right-0.pa3.b.pointer [:div.f3.dark-gray.absolute.top-0.right-0.pa3.b.pointer
@ -326,7 +424,8 @@
user (rf/subscribe [:user]) user (rf/subscribe [:user])
owner-bounties (rf/subscribe [:owner-bounties]) owner-bounties (rf/subscribe [:owner-bounties])
bounty-stats-data (rf/subscribe [:owner-bounties-stats]) bounty-stats-data (rf/subscribe [:owner-bounties-stats])
owner-bounties-loading? (rf/subscribe [:get-in [:owner-bounties-loading?]])] owner-bounties-loading? (rf/subscribe [:get-in [:owner-bounties-loading?]])
revoke-modal-bounty (rf/subscribe [:revoke-modal-bounty])]
(fn manage-payouts-page-render [] (fn manage-payouts-page-render []
(cond (cond
(nil? @user) (nil? @user)
@ -348,13 +447,16 @@
(get grouped :pending-contributor-address))] (get grouped :pending-contributor-address))]
[:div.center.mw9.pa2.pa0-l [:div.center.mw9.pa2.pa0-l
[manage-bounties-title] [manage-bounties-title]
[salute "Andy"] [salute]
[pending-banner]
[:div.dn-l.db-ns.mt4 [:div.dn-l.db-ns.mt4
[bounty-stats-new @bounty-stats-data]] [bounty-stats-new @bounty-stats-data]]
(when (nil? (common/web3)) (when (nil? (common/web3))
[:div.ui.warning.message [:div.ui.warning.message
[:i.warning.icon] [:i.warning.icon]
"To sign off claims, please view Status Open Bounty in Status, Mist or Metamask"]) "To sign off claims, please view Status Open Bounty in Status, Mist or Metamask"])
(when @revoke-modal-bounty
[revoke-modal])
[manage-bounties-nav route-id] [manage-bounties-nav route-id]
[:div.cf [:div.cf
[:div.fr.w-third.pl4.mb3.dn.db-l [:div.fr.w-third.pl4.mb3.dn.db-l

View File

@ -41,6 +41,11 @@
(fn [db _] (fn [db _]
(:flash-message db))) (:flash-message db)))
(reg-sub
:revoke-modal-bounty
(fn [db _]
(:revoke-modal-bounty db)))
(reg-sub (reg-sub
:open-bounties :open-bounties
(fn [db _] (fn [db _]
@ -82,7 +87,10 @@
;; special prefix or namespace for derived properties that ;; special prefix or namespace for derived properties that
;; are added to domain records like this ;; are added to domain records like this
;; e.g. `derived/paid?` ;; e.g. `derived/paid?`
[id (assoc bounty :paid? (boolean (:payout_hash bounty)))]) [id (assoc bounty :paid? (boolean (and (:payout_receipt bounty)
;; bounties with winner logins
;; were not revoked
(:winner_login bounty))))])
(into {})))) (into {}))))
(reg-sub (reg-sub
@ -179,6 +187,19 @@
(fn [db _] (fn [db _]
(:user-dropdown-open? db))) (:user-dropdown-open? db)))
(reg-sub
:three-dots-open?
(fn [db _]
(::db/unclaimed-options db)))
(reg-sub
:pending-revocations
(fn [db _]
(map (fn [[issue-id revocations]]
(merge revocations
(get-in db [:owner-bounties issue-id])))
(::db/pending-revocations db))))
(reg-sub (reg-sub
::open-bounty-claims ::open-bounty-claims
(fn [db _] (fn [db _]

View File

@ -10,7 +10,7 @@
(let [db (rf/subscribe [:db]) (let [db (rf/subscribe [:db])
updating-user (rf/subscribe [:get-in [:updating-user]]) updating-user (rf/subscribe [:get-in [:updating-user]])
address (r/atom @(rf/subscribe [:get-in [:user :address]])) address (r/atom @(rf/subscribe [:get-in [:user :address]]))
hidden (r/atom @(rf/subscribe [:get-in [:user :is_hidden_in_hunters]]))] hidden (r/atom @(rf/subscribe [:get-in [:user :is-hidden-in-hunters]]))]
(fn [] (fn []
(let [web3 (:web3 @db) (let [web3 (:web3 @db)
@ -64,7 +64,7 @@
[:button [:button
(merge {:on-click (merge {:on-click
#(rf/dispatch [:save-user-fields {:address @address #(rf/dispatch [:save-user-fields {:address @address
:is_hidden_in_hunters @hidden}]) :is-hidden-in-hunters @hidden}])
:disabled (str/blank? @address) :disabled (str/blank? @address)
:class (str "ui button small update-address-button" :class (str "ui button small update-address-button"
(when @updating-user (when @updating-user

View File

@ -250,9 +250,29 @@ label[for="input-hidden"] {
a { a {
color: #fff; color: #fff;
pointer: default; pointer: default;
display:block;
} }
} }
.ui.menu.revoke.transition {
font-family: "PostGrotesk-Book";
font-size: 1em;
border-radius: 8px;
border: none;
padding: .5em;
background-color: #fff;
a {
color: #8d99a4;;
cursor: pointer;
display:block;
}
min-width:80px;
position:absolute;
z-index: 800000;
right: 22px;
bottom:60px;
}
.logout-link { .logout-link {
color: #fff!important; color: #fff!important;
} }

View File

@ -29,6 +29,12 @@
.hover-sob-sky:hover, .hover-sob-sky:focus { color: #f2f5f8; } .hover-sob-sky:hover, .hover-sob-sky:focus { color: #f2f5f8; }
.hover-bg-sob-sky:hover, .hover-bg-sob-sky:focus { background-color: #f2f5f8; } .hover-bg-sob-sky:hover, .hover-bg-sob-sky:focus { background-color: #f2f5f8; }
.sob-green { color: #d1ead8; }
.bg-sob-green { background-color: #d1ead8; }
.b--sob-green { border-color: #d1ead8; }
.hover-sob-green:hover, .hover-sob-green:focus { color: #d1ead8; }
.hover-bg-sob-green:hover, .hover-bg-sob-green:focus { background-color: #d1ead8; }
.sob-tint { color: #f7f9fa; } .sob-tint { color: #f7f9fa; }
.bg-sob-tint { background-color: #f7f9fa; } .bg-sob-tint { background-color: #f7f9fa; }
.b--sob-tint { border-color: #f7f9fa; } .b--sob-tint { border-color: #f7f9fa; }

View File

@ -36,5 +36,5 @@
:address "address" :address "address"
:created nil :created nil
:welcome_email_sent 0 :welcome_email_sent 0
:is_hidden_in_hunters false} :is-hidden-in-hunters false}
(db/get-user t-conn {:id 1}))))) (db/get-user t-conn {:id 1})))))