Merge branch 'fix/update-github-comment-339' of github.com:status-im/open-bounty into fix/update-github-comment-339
This commit is contained in:
commit
ed3e9340e4
|
@ -23,7 +23,12 @@ RUN lein uberjar
|
|||
FROM clojure
|
||||
WORKDIR /root/
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install xvfb
|
||||
RUN apt-get -y install wkhtmltopdf
|
||||
|
||||
COPY --from=builder /usr/src/app/target/uberjar/commiteth.jar .
|
||||
COPY html2png.sh .
|
||||
|
||||
CMD [""]
|
||||
ENTRYPOINT ["/usr/bin/java", "-Duser.timezone=UTC", "-Dconf=config-test.edn", "-jar", "/root/commiteth.jar"]
|
||||
|
|
14
README.md
14
README.md
|
@ -1,4 +1,5 @@
|
|||
# Status Open Bounty
|
||||
[![Riot Chat Badge](https://img.shields.io/badge/join%20%23openbounty-riot-green.svg)](https://chat.status.im/#/room/#openbounty:status.im)
|
||||
|
||||
Allows you to set bounties for Github issues, paid out in Ether or any ERC-20 token.
|
||||
|
||||
|
@ -71,6 +72,7 @@ eth-account | Ethereum account ID for the bot
|
|||
eth-password | Ethereum account password for the bot
|
||||
eth-rpc-url | RPC URL to Ethereum node, e.g. Geth. Either local or remote
|
||||
eth-wallet-file | Location of wallet file. If Geth is run with the parameters as given below, it will reside under `$HOME/.ropsten/keystore`
|
||||
offline-signing | Specifies whether to sign transactions locally before sending. Default is true. Set to false when connecting to local Geth node that unlocks accounts
|
||||
tokenreg-base-format | Should be set to `:status`
|
||||
github-client-id | Related to OAuth. Copied from GitHub account Settings->Developer settings->OAuth Apps
|
||||
github-client-secret | Related to OAuth. Copied from GitHub account Settings->Developer settings->OAuth Apps
|
||||
|
@ -91,14 +93,22 @@ Follow the steps [here](https://developer.github.com/apps/building-github-apps/c
|
|||
|
||||
## Running
|
||||
|
||||
### Geth
|
||||
Launch a local geth node with the bot account unlocked:
|
||||
### Ethereum node
|
||||
There are two options for connecting to an Ethereum node: either run a local node with an unlocked account, or connect to a remote Geth node or Infura. We will be connecting to Ropsten which is a test Ethereum network.
|
||||
|
||||
#### Local
|
||||
|
||||
In order to launch a local geth node with the bot account unlocked issue the following command:
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
geth --fast --testnet --cache=1024 --datadir=$HOME/.ropsten --verbosity 4 --port 50100 --ipcpath ~/.ropsten/geth.ipc --rpc --rpcaddr 127.0.0.1 --rpcport 8545 --rpcapi db,eth,net,web3,personal --rpccorsdomain "https://wallet.ethereum.org" --unlock "0xYOUR_ADDR" --password <(echo "YOUR_PASSPHRASE")
|
||||
```
|
||||
|
||||
#### Remote
|
||||
Register at [Infura](https://infura.io/signup). You will receive an email with provider URLs. Paste an URL for the Ropsten network into `config.edn` under `:eth-rpc-url` key, and set `:offline-signing` to true.
|
||||
|
||||
|
||||
### CSS auto-compilation
|
||||
Launch the following command in a separate shell:
|
||||
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
:eth-rpc-url "http://localhost:8545"
|
||||
:eth-wallet-file "/some/location"
|
||||
|
||||
;; Specifies whether to sign transactions before sending. Should be false for local Geth, true for remote Infura
|
||||
:offline-signing true
|
||||
|
||||
;; address of token registry to be used
|
||||
;; this is the default value for ropsten
|
||||
:tokenreg-addr "0x7d127a3e3b5e72cd8f15e7dee650abe4fcced2b9"
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash -eu
|
||||
# need to run wkhtmltoimage in quiet mode because it misuses stdout
|
||||
xvfb-run -a -s "-screen 0 640x480x16" wkhtmltoimage -q "$@" 2>/dev/null
|
|
@ -5,7 +5,7 @@
|
|||
:exclusions [joda-time]]
|
||||
[re-frame "0.10.2"]
|
||||
[cljs-ajax "0.7.2"]
|
||||
[secretary "1.2.3"]
|
||||
[funcool/bide "1.6.0"]
|
||||
[reagent-utils "0.2.1"]
|
||||
[reagent "0.7.0"]
|
||||
[org.clojure/clojurescript "1.9.946"]
|
||||
|
|
|
@ -102,19 +102,16 @@ WHERE i.repo_id = :repo_id
|
|||
AND i.confirm_hash is null
|
||||
AND i.is_open = true;
|
||||
|
||||
-- :name update-repo-generic :! :n
|
||||
/* :require [clojure.string :as string]
|
||||
[hugsql.parameters :refer [identifier-param-quote]] */
|
||||
-- :name update-repo-name :! :n
|
||||
UPDATE repositories
|
||||
SET
|
||||
/*~
|
||||
(string/join ","
|
||||
(for [[field _] (:updates params)]
|
||||
(str (identifier-param-quote (name field) options)
|
||||
" = :v:updates." (name field))))
|
||||
~*/
|
||||
where repo_id = :repo_id;
|
||||
SET repo = :repo_name
|
||||
WHERE repo_id = :repo_id
|
||||
AND repo != :repo_name
|
||||
|
||||
-- :name update-repo-state :! :n
|
||||
UPDATE repositories
|
||||
SET state = :repo_state
|
||||
WHERE repo_id = :repo_id
|
||||
|
||||
|
||||
-- Issues --------------------------------------------------------------------------
|
||||
|
@ -401,6 +398,14 @@ SELECT exists(SELECT 1
|
|||
FROM issues
|
||||
WHERE issue_id = :issue_id);
|
||||
|
||||
-- :name get-issue :? :1
|
||||
-- :doc get issue from DB by repo-id and issue-number
|
||||
SELECT issue_id, issue_number, is_open, winner_login, commit_sha
|
||||
FROM issues
|
||||
WHERE repo_id = :repo_id
|
||||
AND issue_number = :issue_number;
|
||||
|
||||
|
||||
|
||||
-- :name open-bounties :? :*
|
||||
-- :doc all open bounty issues
|
||||
|
|
|
@ -65,6 +65,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
|||
src="https://www.facebook.com/tr?id=293089407869419&ev=PageView&noscript=1"
|
||||
/></noscript>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.1.8/semantic.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="https://unpkg.com/tachyons@4.9.1/css/tachyons.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="/css/style.css?v={{commiteth-version}}" rel="stylesheet" type="text/css" />
|
||||
<script type="text/javascript" src="/js/app.js?v={{commiteth-version}}"></script>
|
||||
<script>
|
||||
|
|
|
@ -95,3 +95,9 @@
|
|||
first
|
||||
:exists
|
||||
boolean)))
|
||||
|
||||
(defn get-issue
|
||||
[repo-id issue-number]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/get-issue con-db {:repo_id repo-id
|
||||
:issue_number issue-number})))
|
||||
|
|
|
@ -25,13 +25,17 @@
|
|||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/get-enabled-repositories con-db {:user_id user-id}))))
|
||||
|
||||
(defn update-repo
|
||||
[repo-id updates]
|
||||
(defn update-repo-name
|
||||
[repo-id repo-name]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/update-repo-generic con-db {:repo_id repo-id
|
||||
:updates updates})))
|
||||
|
||||
(db/update-repo-name con-db {:repo_id repo-id
|
||||
:repo_name repo-name})))
|
||||
|
||||
(defn update-repo-state
|
||||
[repo-id repo-state]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/update-repo-name con-db {:repo_id repo-id
|
||||
:repo_state repo-state})))
|
||||
(defn get-repo
|
||||
"Get a repo from DB given it's full name (owner/repo-name)"
|
||||
[full-name]
|
||||
|
|
|
@ -7,14 +7,65 @@
|
|||
[clojure.tools.logging :as log]
|
||||
[clojure.string :as str]
|
||||
[pandect.core :as pandect]
|
||||
[commiteth.util.util :refer [json-api-request]]))
|
||||
[commiteth.util.util :refer [json-api-request]])
|
||||
(:import [org.web3j
|
||||
protocol.Web3j
|
||||
protocol.http.HttpService
|
||||
protocol.core.DefaultBlockParameterName
|
||||
protocol.core.methods.response.EthGetTransactionCount
|
||||
protocol.core.methods.request.RawTransaction
|
||||
utils.Numeric
|
||||
crypto.Credentials
|
||||
crypto.TransactionEncoder
|
||||
crypto.WalletUtils]))
|
||||
|
||||
(defn eth-rpc-url [] (env :eth-rpc-url "http://localhost:8545"))
|
||||
(defn eth-account [] (:eth-account env))
|
||||
(defn eth-password [] (:eth-password env))
|
||||
(defn gas-estimate-factor [] (env :gas-estimate-factor 1.0))
|
||||
(defn auto-gas-price? [] (env :auto-gas-price? false))
|
||||
(defn auto-gas-price? [] (env :auto-gas-price false))
|
||||
(defn offline-signing? [] (env :offline-signing true))
|
||||
|
||||
(defn wallet-file-path []
|
||||
(env :eth-wallet-file))
|
||||
|
||||
(defn wallet-password []
|
||||
(env :eth-password))
|
||||
|
||||
(defn creds []
|
||||
(let [password (wallet-password)
|
||||
file-path (wallet-file-path)]
|
||||
(if (and password file-path)
|
||||
(WalletUtils/loadCredentials
|
||||
password
|
||||
file-path)
|
||||
(throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn"
|
||||
{:password password :file-path file-path})))))
|
||||
|
||||
(defn create-web3j []
|
||||
(Web3j/build (HttpService. (eth-rpc-url))))
|
||||
|
||||
(defn get-signed-tx [gas-price gas-limit to data]
|
||||
"Create a sign a raw transaction.
|
||||
'From' argument is not needed as it's already
|
||||
encoded in credentials.
|
||||
See https://web3j.readthedocs.io/en/latest/transactions.html#offline-transaction-signing"
|
||||
(let [web3j (create-web3j)
|
||||
nonce (.. (.ethGetTransactionCount web3j
|
||||
(env :eth-account)
|
||||
DefaultBlockParameterName/LATEST)
|
||||
sendAsync
|
||||
get
|
||||
getTransactionCount)
|
||||
tx (RawTransaction/createTransaction
|
||||
nonce
|
||||
gas-price
|
||||
gas-limit
|
||||
to
|
||||
data)
|
||||
signed (TransactionEncoder/signMessage tx (creds))
|
||||
hex-string (Numeric/toHexString signed)]
|
||||
hex-string))
|
||||
(defn eth-gasstation-gas-price
|
||||
[]
|
||||
(let [data (json-api-request "https://ethgasstation.info/json/ethgasAPI.json")
|
||||
|
@ -40,6 +91,21 @@
|
|||
(gas-price-from-config)))
|
||||
(gas-price-from-config)))
|
||||
|
||||
(defn safe-read-str [s]
|
||||
(if (nil? s)
|
||||
(do
|
||||
(log/error "JSON response is nil")
|
||||
nil)
|
||||
(try
|
||||
(json/read-str s :key-fn keyword)
|
||||
(catch Exception ex
|
||||
(do (log/error "Exception when parsing json string:"
|
||||
s
|
||||
"message:"
|
||||
ex)
|
||||
|
||||
nil)))))
|
||||
|
||||
(defn eth-rpc
|
||||
[method params]
|
||||
(let [request-id (rand-int 4096)
|
||||
|
@ -49,8 +115,8 @@
|
|||
:id request-id})
|
||||
options {:headers {"content-type" "application/json"}
|
||||
:body body}
|
||||
response (:body @(post (eth-rpc-url) options))
|
||||
result (json/read-str response :key-fn keyword)]
|
||||
response @(post (eth-rpc-url) options)
|
||||
result (safe-read-str (:body response))]
|
||||
(log/debug body "\n" result)
|
||||
|
||||
(if (= (:id result) request-id)
|
||||
|
@ -128,54 +194,16 @@
|
|||
[account digits]
|
||||
(hex->eth (get-balance-hex account) digits))
|
||||
|
||||
(defn send-transaction
|
||||
"Send transaction using default commiteth bot account."
|
||||
[from to value & [params]]
|
||||
(let [args (merge params
|
||||
{:from from
|
||||
:value value}
|
||||
(when-not (nil? (gas-price))
|
||||
{:gasPrice (integer->hex (gas-price))})
|
||||
(when-not (contains? params :gas)
|
||||
{:gas
|
||||
(estimate-gas from to value params)}))]
|
||||
(log/debug "args:" args)
|
||||
(eth-rpc
|
||||
"personal_sendTransaction"
|
||||
[(if-not (nil? to)
|
||||
(merge args {:to to})
|
||||
args)
|
||||
(eth-password)])))
|
||||
|
||||
(defn send-transaction-using-from-account
|
||||
"Send transaction using account address in parameter from. Assumes
|
||||
account has been unlocked."
|
||||
[from to value & [params]]
|
||||
(let [args (merge params
|
||||
{:from from
|
||||
:value value}
|
||||
(when-not (nil? (gas-price))
|
||||
{:gasPrice (integer->hex (gas-price))})
|
||||
(when-not (contains? params :gas)
|
||||
{:gas
|
||||
(estimate-gas from to value params)}))]
|
||||
(log/debug "args:" args)
|
||||
(eth-rpc
|
||||
"eth_sendTransaction"
|
||||
[(if-not (nil? to)
|
||||
(merge args {:to to})
|
||||
args)])))
|
||||
|
||||
(defn get-transaction-receipt
|
||||
[hash]
|
||||
(eth-rpc "eth_getTransactionReceipt" [hash]))
|
||||
|
||||
(defn- format-param
|
||||
[param]
|
||||
(if (number? param)
|
||||
(format "%064x" param)
|
||||
(clojure.string/replace (format "%64s" (subs param 2)) " " "0")))
|
||||
|
||||
(defn get-transaction-receipt
|
||||
[hash]
|
||||
(eth-rpc "eth_getTransactionReceipt" [hash]))
|
||||
|
||||
(defn format-call-params
|
||||
[method-id & params]
|
||||
(let [params (join (map format-param params))]
|
||||
|
@ -189,33 +217,30 @@
|
|||
(defn execute
|
||||
[from contract method-id gas-limit & params]
|
||||
(let [data (apply format-call-params method-id params)
|
||||
value (format "0x%x" 0)]
|
||||
(send-transaction from contract value (merge
|
||||
{:data data}
|
||||
(when gas-limit
|
||||
{:gas gas-limit})))))
|
||||
|
||||
(defn execute-using-addr
|
||||
[from-addr from-passphrase contract method-id & params]
|
||||
(eth-rpc "personal_unlockAccount" [from-addr from-passphrase 30])
|
||||
(let [data (apply format-call-params method-id params)
|
||||
value (format "0x%x" 0)]
|
||||
(send-transaction-using-from-account from-addr
|
||||
contract
|
||||
value
|
||||
{:data data})))
|
||||
|
||||
(defn transfer-eth
|
||||
"Transfer amount-wei of ETH from from-addr to to-addr."
|
||||
[from-addr from-passphrase to-addr amount-wei]
|
||||
(eth-rpc "personal_unlockAccount" [from-addr from-passphrase 30])
|
||||
(let [data "0x"
|
||||
value (integer->hex amount-wei)]
|
||||
(send-transaction-using-from-account from-addr
|
||||
to-addr
|
||||
value
|
||||
{:data data})))
|
||||
|
||||
gas-price (gas-price)
|
||||
value (format "0x%x" 0)
|
||||
params (cond-> {:data data
|
||||
:from from
|
||||
:value value}
|
||||
gas-price
|
||||
(merge {:gasPrice (integer->hex gas-price)})
|
||||
contract
|
||||
(merge {:to contract}))
|
||||
gas (if gas-limit gas-limit
|
||||
(estimate-gas from contract value params))
|
||||
params (if (offline-signing?)
|
||||
(get-signed-tx (biginteger gas-price)
|
||||
(hex->big-integer gas)
|
||||
contract
|
||||
data)
|
||||
(assoc params :gas gas))]
|
||||
(if (offline-signing?)
|
||||
(eth-rpc
|
||||
"eth_sendRawTransaction"
|
||||
[params])
|
||||
(eth-rpc
|
||||
"personal_sendTransaction"
|
||||
[params (eth-password)]))))
|
||||
|
||||
(defn hex-ch->num
|
||||
[ch]
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
(ns commiteth.eth.multisig-wallet
|
||||
(:require [commiteth.eth.core :as eth]
|
||||
[commiteth.config :refer [env]]
|
||||
[commiteth.eth.web3j
|
||||
(:require [commiteth.eth.core :as eth
|
||||
:refer [create-web3j creds]]
|
||||
[commiteth.config :refer [env]]
|
||||
[clojure.tools.logging :as log]
|
||||
[commiteth.eth.token-data :as token-data])
|
||||
(:import [org.web3j
|
||||
|
@ -191,30 +190,6 @@
|
|||
[tla (token-balance bounty-addr tla)]))) addrs)))
|
||||
{})))
|
||||
|
||||
(defn transfer-tokens
|
||||
"Transfer mount of given ERC20 token from from-addr to
|
||||
to-addr. Connected geth needs to have keys for the account and
|
||||
passphrase needs to be supplied. Returns transaction ID."
|
||||
[from-addr from-passphrase token to-addr amount]
|
||||
(let [token-addr (get-token-address token)]
|
||||
(eth/execute-using-addr from-addr
|
||||
from-passphrase
|
||||
token-addr
|
||||
(method-ids :transfer)
|
||||
to-addr
|
||||
amount)))
|
||||
|
||||
(defn get-owners
|
||||
"Return vector of multisig owner addresses."
|
||||
[bounty-addr]
|
||||
(let [bounty-contract (load-bounty-contract bounty-addr)
|
||||
owner-addresses (-> bounty-contract
|
||||
(.getOwners)
|
||||
.get)]
|
||||
(if owner-addresses
|
||||
(mapv #(.toString %) (.getValue owner-addresses))
|
||||
[])))
|
||||
|
||||
(defn uint256 [x]
|
||||
(org.web3j.abi.datatypes.generated.Uint256. x))
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
(defn update-data []
|
||||
(let [test-data (env :testnet-token-data)
|
||||
token-data
|
||||
(if (and (env :on-testnet true)
|
||||
(if (and #_(env :on-testnet true)
|
||||
test-data)
|
||||
test-data
|
||||
(into {}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
(ns commiteth.eth.token-registry
|
||||
(:require [commiteth.eth.core :as eth]
|
||||
[commiteth.eth.web3j
|
||||
(:require [commiteth.eth.core :as eth
|
||||
:refer [create-web3j creds]]
|
||||
[commiteth.config :refer [env]]
|
||||
[clojure.tools.logging :as log])
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
(ns commiteth.eth.web3j
|
||||
(:require [commiteth.eth.core :as eth]
|
||||
[commiteth.config :refer [env]])
|
||||
(:import [org.web3j
|
||||
protocol.Web3j
|
||||
protocol.http.HttpService
|
||||
crypto.Credentials
|
||||
crypto.WalletUtils]))
|
||||
|
||||
|
||||
(defn wallet-file-path []
|
||||
(env :eth-wallet-file))
|
||||
|
||||
(defn wallet-password []
|
||||
(env :eth-password))
|
||||
|
||||
(defn creds []
|
||||
(let [password (wallet-password)
|
||||
file-path (wallet-file-path)]
|
||||
(if (and password file-path)
|
||||
(WalletUtils/loadCredentials
|
||||
password
|
||||
file-path)
|
||||
(throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn"
|
||||
{:password password :file-path file-path})))))
|
||||
|
||||
(defn create-web3j []
|
||||
(Web3j/build (HttpService. (eth/eth-rpc-url))))
|
|
@ -3,6 +3,7 @@
|
|||
[core :as tentacles]
|
||||
[repos :as repos]
|
||||
[oauth :as oauth]
|
||||
[search :as search]
|
||||
[users :as users]
|
||||
[repos :as repos]
|
||||
[issues :as issues]
|
||||
|
@ -27,6 +28,8 @@
|
|||
(defn on-testnet? [] (env :on-testnet))
|
||||
(defn webhook-secret [] (env :webhook-secret))
|
||||
|
||||
|
||||
|
||||
(defn authorize-url [scope]
|
||||
(let [params (codec/form-encode {:client_id (client-id)
|
||||
:redirect_uri (redirect-uri)
|
||||
|
@ -216,7 +219,7 @@
|
|||
(str "Tokens: "
|
||||
(str/join " " (map (fn [[tla balance]] (format "%s: %.2f"
|
||||
(subs (str tla) 1)
|
||||
(float balance)))
|
||||
(double balance)))
|
||||
token-balances))
|
||||
"\n")))
|
||||
|
||||
|
@ -360,3 +363,32 @@
|
|||
(let [[owner repo] (str/split full-repo #"/")]
|
||||
(log/debug "creating bounty label" (str owner "/" repo) token)
|
||||
(issues/create-label owner repo "bounty" "fafad2" (auth-params token))))
|
||||
|
||||
(defn get-labeled-issues-for-repos [repos auth-params]
|
||||
"Find all issues with a bounty label in provided repos"
|
||||
(let [get-last-part (fn get-last-part [s]
|
||||
(subs s (inc (str/last-index-of s "/"))))
|
||||
get-issue-info (fn get-issue-info [r issue]
|
||||
(hash-map :owner r
|
||||
:repo (get-last-part (:repository_url issue))
|
||||
:id (:id issue)
|
||||
:number (:number issue)
|
||||
:title (:title issue)
|
||||
:url (:html_url issue)
|
||||
:created_at (:created_at issue)
|
||||
:closed_at (:closed_at issue)))
|
||||
issues (for [r repos]
|
||||
(loop [repo-issues [] i 1]
|
||||
(let [params (into auth-params
|
||||
{:sort "created" :order "desc" :page i})
|
||||
issues-page (-> (search/search-issues
|
||||
nil
|
||||
{:label "bounty" :user r :type "issue"}
|
||||
params)
|
||||
:items)]
|
||||
(if (first issues-page)
|
||||
(recur (into repo-issues
|
||||
(map (partial get-issue-info r) issues-page))
|
||||
(inc i))
|
||||
repo-issues))))]
|
||||
(apply concat issues)))
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
body (keywordize-keys (codec/form-decode (:body resp)))
|
||||
scope (:scope body)
|
||||
access-token (:access_token body)]
|
||||
(log/info "access-token:" access-token)
|
||||
(log/debug "github sign-in callback, response body:" body)
|
||||
(if (:error body)
|
||||
;; Why does Mist browser sends two redirects at the same time? The latter results in 401 error.
|
||||
|
|
|
@ -46,67 +46,6 @@
|
|||
(log/debug "token" token "member?" member?)
|
||||
member?))
|
||||
|
||||
(defn enable-repo [repo-id repo full-repo token]
|
||||
(log/debug "enable-repo" repo-id repo)
|
||||
(when (github/webhook-exists? full-repo token)
|
||||
(github/remove-our-webhooks full-repo token))
|
||||
|
||||
(let [hook-secret (random/base64 32)]
|
||||
(repositories/update-repo repo-id {:state 1
|
||||
:hook_secret hook-secret})
|
||||
(let [created-hook (github/add-webhook full-repo token hook-secret)]
|
||||
(log/debug "Created webhook:" created-hook)
|
||||
(repositories/update-repo repo-id {:hook_id (:id created-hook)})))
|
||||
(github/create-label full-repo token)
|
||||
(repositories/update-repo repo-id {:state 2})
|
||||
(when (add-bounties-for-existing-issues?)
|
||||
(bounties/add-bounties-for-existing-issues full-repo)))
|
||||
|
||||
(defn disable-repo [repo-id full-repo hook-id token]
|
||||
(log/debug "disable-repo" repo-id full-repo)
|
||||
(github/remove-webhook full-repo hook-id token)
|
||||
(repositories/update-repo repo-id {:hook_secret ""
|
||||
:state 0
|
||||
:hook_id nil}))
|
||||
|
||||
;; NOTE(oskarth): This and above two functions about to be deprecated with Github App
|
||||
(defn handle-toggle-repo [user params can-create?]
|
||||
(log/info "XXX handle-toggle-repo" (pr-str user) (pr-str params))
|
||||
(let [{user-id :id} user
|
||||
{repo-id :id
|
||||
full-repo :full_name
|
||||
owner-avatar-url :owner-avatar-url
|
||||
token :token
|
||||
repo :name} params
|
||||
[owner _] (str/split full-repo #"/")
|
||||
db-user (users/get-user (:id user))]
|
||||
|
||||
(cond (not can-create?)
|
||||
{:status 400
|
||||
:body "Please join our Riot - chat.status.im/#/register and request
|
||||
access in our #openbounty room to have your account whitelisted"}
|
||||
|
||||
(empty? (:address db-user))
|
||||
{:status 400
|
||||
:body "Please add your ethereum address to your profile first"}
|
||||
|
||||
:else
|
||||
(try
|
||||
(let [_ (println "CREATING")
|
||||
db-item (repositories/create (merge params {:user_id user-id
|
||||
:owner owner}))
|
||||
is-enabled (= 2 (:state db-item))]
|
||||
(if is-enabled
|
||||
(disable-repo repo-id full-repo (:hook_id db-item) token)
|
||||
(enable-repo repo-id repo full-repo token))
|
||||
(ok (merge
|
||||
{:enabled (not is-enabled)}
|
||||
(select-keys params [:id :full_name]))))
|
||||
(catch Exception e
|
||||
(log/error "exception when enabling repo" e)
|
||||
(repositories/update-repo repo-id {:state -1})
|
||||
(internal-server-error))))))
|
||||
|
||||
(defn in? [coll elem]
|
||||
(some #(= elem %) coll))
|
||||
|
||||
|
@ -146,26 +85,40 @@
|
|||
|
||||
|
||||
(defn prettify-bounty-items [bounty-items]
|
||||
(let [renames {:user_name :display-name
|
||||
:user_avatar_url :avatar-url
|
||||
:issue_title :issue-title
|
||||
:type :item-type
|
||||
:repo_name :repo-name
|
||||
:repo_owner :repo-owner
|
||||
:issue_number :issue-number
|
||||
:value_usd :value-usd
|
||||
:claim_count :claim-count
|
||||
:balance_eth :balance-eth
|
||||
:user_has_address :user-has-address}]
|
||||
(let [format-float (fn [bounty balance]
|
||||
(try
|
||||
(format "%.2f" (double balance))
|
||||
(catch Throwable ex
|
||||
(log/error (str (:repo-owner bounty)
|
||||
"/"
|
||||
(:repo-name bounty)
|
||||
"/"
|
||||
(:issue-number bounty))
|
||||
"Failed to convert token value:" balance)
|
||||
"0.00")))
|
||||
update-token-values (fn [bounty]
|
||||
(->> bounty
|
||||
:tokens
|
||||
(map (fn [[tla balance]]
|
||||
[tla (format-float bounty balance)]))
|
||||
(into {})
|
||||
(assoc bounty :tokens)))
|
||||
renames {:user_name :display-name
|
||||
:user_avatar_url :avatar-url
|
||||
:issue_title :issue-title
|
||||
:type :item-type
|
||||
:repo_name :repo-name
|
||||
:repo_owner :repo-owner
|
||||
:issue_number :issue-number
|
||||
:value_usd :value-usd
|
||||
:claim_count :claim-count
|
||||
:balance_eth :balance-eth
|
||||
:user_has_address :user-has-address}]
|
||||
(map #(-> %
|
||||
(rename-keys renames)
|
||||
(update :value-usd usd-decimal->str)
|
||||
(update :balance-eth eth-decimal->str)
|
||||
(update :tokens (fn [tokens]
|
||||
(into {}
|
||||
(map (fn [[tla balance]]
|
||||
[tla (format "%.2f" balance)])
|
||||
tokens)))))
|
||||
update-token-values)
|
||||
bounty-items)))
|
||||
|
||||
(defn activity-feed []
|
||||
|
@ -272,9 +225,4 @@
|
|||
:auth-rules authenticated?
|
||||
:current-user user
|
||||
(log/debug "/user/bounties")
|
||||
(ok (user-bounties user)))
|
||||
(POST "/repository/toggle" {:keys [params]}
|
||||
;; NOTE: Don't allow anyone to create repos; manual add
|
||||
:auth-rules authenticated?
|
||||
:current-user user
|
||||
(handle-toggle-repo user params (user-whitelisted? (:login user)))))))
|
||||
(ok (user-bounties user))))))
|
||||
|
|
|
@ -109,26 +109,17 @@
|
|||
(extract pr-title))))
|
||||
|
||||
|
||||
(defn ensure-bounty-issue
|
||||
"Checks if an issue has a bounty label attached and returns its number"
|
||||
[user repo issue-number]
|
||||
(when-let [issue (github/get-issue user repo issue-number)]
|
||||
(when (bounties/has-bounty-label? issue)
|
||||
issue-number)))
|
||||
|
||||
|
||||
(defn handle-claim
|
||||
[user-id login name avatar_url owner repo repo-id bounty-issue-number pr-id pr-number head-sha merged? event-type]
|
||||
[issue user-id login name avatar_url owner repo repo-id pr-id pr-number head-sha merged? event-type]
|
||||
(users/create-user user-id login name nil avatar_url)
|
||||
(let [issue (github/get-issue owner repo bounty-issue-number)
|
||||
open-or-edit? (contains? #{:opened :edited} event-type)
|
||||
(let [open-or-edit? (contains? #{:opened :edited} event-type)
|
||||
close? (= :closed event-type)
|
||||
pr-data {:repo_id repo-id
|
||||
:pr_id pr-id
|
||||
:pr_number pr-number
|
||||
:user_id user-id
|
||||
:issue_number bounty-issue-number
|
||||
:issue_id (:id issue)
|
||||
:issue_number (:issue_number issue)
|
||||
:issue_id (:issue_id issue)
|
||||
:state event-type}]
|
||||
|
||||
;; TODO: in the opened case if the submitting user has no
|
||||
|
@ -138,18 +129,18 @@
|
|||
(cond
|
||||
open-or-edit? (do
|
||||
(log/info "PR with reference to bounty issue"
|
||||
bounty-issue-number "opened")
|
||||
(:issue_number issue) "opened")
|
||||
(pull-requests/save (merge pr-data {:state :opened
|
||||
:commit_sha head-sha})))
|
||||
close? (if merged?
|
||||
(do (log/info "PR with reference to bounty issue"
|
||||
bounty-issue-number "merged")
|
||||
(:issue_number issue) "merged")
|
||||
(pull-requests/save
|
||||
(merge pr-data {:state :merged
|
||||
:commit_sha head-sha}))
|
||||
(issues/update-commit-sha (:id issue) head-sha))
|
||||
(issues/update-commit-sha (:issue_id issue) head-sha))
|
||||
(do (log/info "PR with reference to bounty issue"
|
||||
bounty-issue-number "closed with no merge")
|
||||
(:issue_number issue) "closed with no merge")
|
||||
(pull-requests/save
|
||||
(merge pr-data {:state :closed
|
||||
:commit_sha head-sha})))))))
|
||||
|
@ -177,26 +168,27 @@
|
|||
pr-number :number
|
||||
pr-body :body
|
||||
pr-title :title} :pull_request}]
|
||||
(log/debug "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title)
|
||||
(log/debug (extract-issue-number pr-body pr-title))
|
||||
(if-let [bounty-issue-number (->>
|
||||
(extract-issue-number pr-body pr-title)
|
||||
(first)
|
||||
(ensure-bounty-issue owner repo))]
|
||||
(do
|
||||
(log/debug "Referenced bounty issue found" owner repo bounty-issue-number)
|
||||
(handle-claim user-id
|
||||
login name
|
||||
avatar_url
|
||||
owner repo
|
||||
repo-id
|
||||
bounty-issue-number
|
||||
pr-id
|
||||
pr-number
|
||||
head-sha
|
||||
merged?
|
||||
event-type))
|
||||
(log/info "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title)
|
||||
(if-let [issue (some->> (extract-issue-number pr-body pr-title)
|
||||
(first)
|
||||
(issues/get-issue repo-id))]
|
||||
(if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue
|
||||
(do
|
||||
(log/info "Referenced bounty issue found" owner repo (:issue_number issue))
|
||||
(handle-claim issue
|
||||
user-id
|
||||
login name
|
||||
avatar_url
|
||||
owner repo
|
||||
repo-id
|
||||
pr-id
|
||||
pr-number
|
||||
head-sha
|
||||
merged?
|
||||
event-type))
|
||||
(log/info "PR for issue already merged"))
|
||||
(when (= :edited event-type)
|
||||
; Remove PR if it does not reference any issue
|
||||
(pull-requests/remove pr-id))))
|
||||
|
||||
|
||||
|
@ -207,9 +199,15 @@
|
|||
new-title (:title gh-issue)]
|
||||
(issues/update-issue-title issue-id new-title)))
|
||||
|
||||
(defn update-repo-name [webhook-payload]
|
||||
"Update repo name in DB if changed"
|
||||
(let [{repo-id :id
|
||||
repo-name :name} (:repository webhook-payload)]
|
||||
(repositories/update-repo-name repo-id repo-name)))
|
||||
|
||||
(defn handle-issue
|
||||
[webhook-payload]
|
||||
(update-repo-name webhook-payload)
|
||||
(when-let [action (:action webhook-payload)]
|
||||
(log/debug "handle-issue" action)
|
||||
(when (labeled-as-bounty? action webhook-payload)
|
||||
|
@ -224,17 +222,17 @@
|
|||
(handle-issue-reopened webhook-payload)))
|
||||
(ok))
|
||||
|
||||
(defn enable-repo-2 [repo-id full-repo]
|
||||
(log/debug "enable-repo-2" repo-id full-repo)
|
||||
(defn enable-repo [repo-id full-repo]
|
||||
(log/debug "enable-repo" repo-id full-repo)
|
||||
;; TODO(oskarth): Add granular permissions to enable creation of label
|
||||
#_(github/create-label full-repo)
|
||||
(repositories/update-repo repo-id {:state 2})
|
||||
(repositories/update-repo-state repo-id 2)
|
||||
(when (add-bounties-for-existing-issues?)
|
||||
(bounties/add-bounties-for-existing-issues full-repo)))
|
||||
|
||||
(defn disable-repo-2 [repo-id full-repo]
|
||||
(log/debug "disable-repo-2" repo-id full-repo)
|
||||
(repositories/update-repo repo-id {:state 0}))
|
||||
(defn disable-repo [repo-id full-repo]
|
||||
(log/debug "disable-repo" repo-id full-repo)
|
||||
(repositories/update-repo-state repo-id 0))
|
||||
|
||||
(defn full-repo->owner [full-repo]
|
||||
(try
|
||||
|
@ -244,8 +242,6 @@
|
|||
(log/error "exception when parsing repo" e)
|
||||
nil)))
|
||||
|
||||
;; NOTE(oskarth): Together with {enable,disable}-repo-2 above, this replaces
|
||||
;; handle-toggle-repo for Github App.
|
||||
(defn handle-add-repo [user-id username owner-avatar-url repo can-create?]
|
||||
(let [repo-id (:id repo)
|
||||
repo-name (:name repo)
|
||||
|
@ -285,14 +281,14 @@
|
|||
_ (log/info "handle-add-repo db-item" db-item)
|
||||
is-enabled (= 2 (:state db-item))]
|
||||
(if is-enabled
|
||||
(disable-repo-2 repo-id full-repo)
|
||||
(enable-repo-2 repo-id full-repo))
|
||||
(disable-repo repo-id full-repo)
|
||||
(enable-repo repo-id full-repo))
|
||||
(ok {:enabled (not is-enabled)
|
||||
:id repo-id
|
||||
:full_name full-repo}))
|
||||
(catch Exception e
|
||||
(log/error "exception when enabling repo" e)
|
||||
(repositories/update-repo repo-id {:state -1})
|
||||
(repositories/update-repo-state repo-id -1)
|
||||
(internal-server-error))))))
|
||||
|
||||
(defn handle-installation [{:keys [action installation repositories sender]}]
|
||||
|
@ -335,12 +331,13 @@
|
|||
(ok))
|
||||
|
||||
(defn handle-pull-request
|
||||
[pull-request]
|
||||
(let [action (keyword (:action pull-request))]
|
||||
[webhook-payload]
|
||||
(update-repo-name webhook-payload)
|
||||
(let [action (keyword (:action webhook-payload))]
|
||||
(when (contains? #{:opened
|
||||
:edited
|
||||
:closed} action)
|
||||
(handle-pull-request-event action pull-request))
|
||||
(handle-pull-request-event action webhook-payload))
|
||||
(ok)))
|
||||
|
||||
|
||||
|
@ -393,14 +390,5 @@
|
|||
"pull_request" (handle-pull-request payload)
|
||||
"installation" (handle-installation payload)
|
||||
"installation_repositories" (handle-installation-repositories payload)
|
||||
|
||||
;; NOTE(oskarth): These two webhooks are / will be deprecated on
|
||||
;; November 22, 2017 but they keep being called. According to
|
||||
;; documentation they should contain same format.
|
||||
;; https://developer.github.com/webhooks/
|
||||
"integration_installation" (handle-installation payload)
|
||||
"integration_installation_repositories" (handle-installation-repositories payload)
|
||||
(ok)))
|
||||
(forbidden)))))
|
||||
|
||||
|
||||
|
|
|
@ -15,43 +15,50 @@
|
|||
[clj-time.periodic :refer [periodic-seq]]
|
||||
[chime :refer [chime-at]]))
|
||||
|
||||
|
||||
(defn update-issue-contract-address
|
||||
"For each pending deployment: gets transaction receipt, updates db
|
||||
state (contract-address, comment-id) and posts github comment"
|
||||
[]
|
||||
(log/info "In update-issue-contract-address")
|
||||
(doseq [{issue-id :issue_id
|
||||
transaction-hash :transaction_hash} (issues/list-pending-deployments)]
|
||||
(log/debug "pending deployment:" transaction-hash)
|
||||
(when-let [receipt (eth/get-transaction-receipt transaction-hash)]
|
||||
(log/info "update-issue-contract-address: transaction receipt for issue #"
|
||||
issue-id ": " receipt)
|
||||
(if-let [contract-address (multisig/find-created-multisig-address receipt)]
|
||||
(let [issue (issues/update-contract-address issue-id contract-address)
|
||||
{owner :owner
|
||||
repo :repo
|
||||
comment-id :comment_id
|
||||
issue-number :issue_number} issue
|
||||
balance-eth-str (eth/get-balance-eth contract-address 6)
|
||||
balance-eth (read-string balance-eth-str)]
|
||||
(log/info "Updating comment image")
|
||||
(bounties/update-bounty-comment-image issue-id
|
||||
owner
|
||||
repo
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
{})
|
||||
(log/info "Updating comment")
|
||||
(github/update-comment owner
|
||||
repo
|
||||
comment-id
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
{}))
|
||||
(log/error "Failed to find contract address in tx logs")))))
|
||||
(log/info "pending deployment:" transaction-hash)
|
||||
(try
|
||||
(when-let [receipt (eth/get-transaction-receipt transaction-hash)]
|
||||
(log/info "update-issue-contract-address: transaction receipt for issue #"
|
||||
issue-id ": " receipt)
|
||||
(if-let [contract-address (multisig/find-created-multisig-address receipt)]
|
||||
(let [issue (issues/update-contract-address issue-id contract-address)
|
||||
{owner :owner
|
||||
repo :repo
|
||||
comment-id :comment_id
|
||||
issue-number :issue_number} issue
|
||||
balance-eth-str (eth/get-balance-eth contract-address 6)
|
||||
balance-eth (read-string balance-eth-str)]
|
||||
(log/info "Updating comment image")
|
||||
(bounties/update-bounty-comment-image issue-id
|
||||
owner
|
||||
repo
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
{})
|
||||
(log/info "Updating comment")
|
||||
(github/update-comment owner
|
||||
repo
|
||||
comment-id
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
{}))
|
||||
(log/error "Failed to find contract address in tx logs")))
|
||||
(catch Throwable ex
|
||||
(do (log/error "update-issue-contract-address exception:" ex)
|
||||
(clojure.stacktrace/print-stack-trace ex)))))
|
||||
(log/info "Exit update-issue-contract-address"))
|
||||
|
||||
|
||||
(defn deploy-pending-contracts
|
||||
|
@ -68,6 +75,7 @@
|
|||
(defn self-sign-bounty
|
||||
"Walks through all issues eligible for bounty payout and signs corresponding transaction"
|
||||
[]
|
||||
(log/info "In self-sign-bounty")
|
||||
(doseq [{contract-address :contract_address
|
||||
issue-id :issue_id
|
||||
payout-address :payout_address
|
||||
|
@ -77,35 +85,42 @@
|
|||
issue-number :issue_number
|
||||
balance-eth :balance_eth
|
||||
tokens :tokens
|
||||
winner-login :winner_login} (db-bounties/pending-bounties)
|
||||
:let [value (eth/get-balance-hex contract-address)]]
|
||||
(if (empty? payout-address)
|
||||
(do
|
||||
(log/error "Cannot sign pending bounty - winner has no payout address")
|
||||
(github/update-merged-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
contract-address
|
||||
(eth-decimal->str balance-eth)
|
||||
tokens
|
||||
winner-login
|
||||
true))
|
||||
(let [execute-hash (multisig/send-all contract-address payout-address)]
|
||||
(log/info "Payout self-signed, called sign-all(" contract-address payout-address ") tx:" execute-hash)
|
||||
(db-bounties/update-execute-hash issue-id execute-hash)
|
||||
(db-bounties/update-winner-login issue-id winner-login)
|
||||
(github/update-merged-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
contract-address
|
||||
(eth-decimal->str balance-eth)
|
||||
tokens
|
||||
winner-login
|
||||
false)))))
|
||||
winner-login :winner_login} (db-bounties/pending-bounties)]
|
||||
(try
|
||||
(let [value (eth/get-balance-hex contract-address)]
|
||||
(if (empty? payout-address)
|
||||
(do
|
||||
(log/error "Cannot sign pending bounty - winner has no payout address")
|
||||
(github/update-merged-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
contract-address
|
||||
(eth-decimal->str balance-eth)
|
||||
tokens
|
||||
winner-login
|
||||
true))
|
||||
(let [execute-hash (multisig/send-all contract-address payout-address)]
|
||||
(log/info "Payout self-signed, called sign-all(" contract-address payout-address ") tx:" execute-hash)
|
||||
(db-bounties/update-execute-hash issue-id execute-hash)
|
||||
(db-bounties/update-winner-login issue-id winner-login)
|
||||
(github/update-merged-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
contract-address
|
||||
(eth-decimal->str balance-eth)
|
||||
tokens
|
||||
winner-login
|
||||
false))))
|
||||
(catch Throwable ex
|
||||
(do (log/error "self-sign-bounty exception:" ex)
|
||||
(clojure.stacktrace/print-stack-trace ex)))))
|
||||
(log/info "Exit self-sign-bounty")
|
||||
)
|
||||
|
||||
(defn update-confirm-hash
|
||||
"Gets transaction receipt for each pending payout and updates DB confirm_hash with tranaction ID of commiteth bot account's confirmation."
|
||||
[]
|
||||
(log/info "In update-confirm-hash")
|
||||
(doseq [{issue-id :issue_id
|
||||
execute-hash :execute_hash} (db-bounties/pending-payouts)]
|
||||
(log/info "pending payout:" execute-hash)
|
||||
|
@ -113,7 +128,8 @@
|
|||
(log/info "execution receipt for issue #" issue-id ": " receipt)
|
||||
(when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)]
|
||||
(log/info "confirm hash:" confirm-hash)
|
||||
(db-bounties/update-confirm-hash issue-id confirm-hash)))))
|
||||
(db-bounties/update-confirm-hash issue-id confirm-hash))))
|
||||
(log/info "Exit update-confirm-hash"))
|
||||
|
||||
|
||||
(defn update-watch-hash
|
||||
|
@ -137,6 +153,7 @@
|
|||
(defn update-payout-receipt
|
||||
"Gets transaction receipt for each confirmed payout and updates payout_hash"
|
||||
[]
|
||||
(log/info "In update-payout-receipt")
|
||||
(doseq [{issue-id :issue_id
|
||||
payout-hash :payout_hash
|
||||
contract-address :contract_address
|
||||
|
@ -150,32 +167,38 @@
|
|||
payee-login :payee_login
|
||||
updated :updated} (db-bounties/confirmed-payouts)]
|
||||
(log/debug "confirmed payout:" payout-hash)
|
||||
(if-let [receipt (eth/get-transaction-receipt payout-hash)]
|
||||
(let [contract-tokens (multisig/token-balances contract-address)
|
||||
contract-eth-balance (eth/get-balance-wei contract-address)]
|
||||
(if (or
|
||||
(some #(> (second %) 0.0) contract-tokens)
|
||||
(> contract-eth-balance 0))
|
||||
(do
|
||||
(log/info "Contract still has funds")
|
||||
(when (multisig/is-confirmed? contract-address confirm-id)
|
||||
(log/info "Detected bounty with funds and confirmed payout, calling executeTransaction")
|
||||
(let [execute-tx-hash (multisig/execute-tx contract-address confirm-id)]
|
||||
(log/info "execute tx:" execute-tx-hash))))
|
||||
(try
|
||||
(if-let [receipt (eth/get-transaction-receipt payout-hash)]
|
||||
(let [contract-tokens (multisig/token-balances contract-address)
|
||||
contract-eth-balance (eth/get-balance-wei contract-address)]
|
||||
(if (or
|
||||
(some #(> (second %) 0.0) contract-tokens)
|
||||
(> contract-eth-balance 0))
|
||||
(do
|
||||
(log/info "Contract still has funds")
|
||||
(when (multisig/is-confirmed? contract-address confirm-id)
|
||||
(log/info "Detected bounty with funds and confirmed payout, calling executeTransaction")
|
||||
(let [execute-tx-hash (multisig/execute-tx contract-address confirm-id)]
|
||||
(log/info "execute tx:" execute-tx-hash))))
|
||||
|
||||
(do
|
||||
(log/info "Payout has succeeded, saving payout receipt for issue #" issue-id ": " receipt)
|
||||
(db-bounties/update-payout-receipt issue-id receipt)
|
||||
(github/update-paid-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
contract-address
|
||||
(eth-decimal->str balance-eth)
|
||||
tokens
|
||||
payee-login))))
|
||||
(when (older-than-3h? updated)
|
||||
(log/info "Resetting payout hash for issue" issue-id "as it has not been mined in 3h")
|
||||
(db-bounties/reset-payout-hash issue-id)))))
|
||||
(do
|
||||
(log/info "Payout has succeeded, saving payout receipt for issue #" issue-id ": " receipt)
|
||||
(db-bounties/update-payout-receipt issue-id receipt)
|
||||
(github/update-paid-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
contract-address
|
||||
(eth-decimal->str balance-eth)
|
||||
tokens
|
||||
payee-login))))
|
||||
(when (older-than-3h? updated)
|
||||
(log/info "Resetting payout hash for issue" issue-id "as it has not been mined in 3h")
|
||||
(db-bounties/reset-payout-hash issue-id)))
|
||||
(catch Throwable ex
|
||||
(do (log/error "update-payout-receipt exception:" ex)
|
||||
(clojure.stacktrace/print-stack-trace ex)))))
|
||||
(log/info "Exit update-payout-receipt")
|
||||
)
|
||||
|
||||
(defn abs
|
||||
"(abs n) is the absolute value of n"
|
||||
|
@ -186,27 +209,28 @@
|
|||
(neg? n) (- n)
|
||||
:else n))
|
||||
|
||||
(defn float=
|
||||
([x y] (float= x y 0.0000001))
|
||||
([x y epsilon]
|
||||
(log/debug x y epsilon)
|
||||
(let [scale (if (or (zero? x) (zero? y)) 1 (abs x))]
|
||||
(<= (abs (- x y)) (* scale epsilon)))))
|
||||
|
||||
(defn update-bounty-token-balances
|
||||
"Helper function for updating internal ERC20 token balances to token multisig contract. Will be called periodically for all open bounty contracts."
|
||||
[issue-id bounty-addr watch-hash]
|
||||
#_(log/info "In update-bounty-token-balances for issue" issue-id)
|
||||
(doseq [[tla token-data] (token-data/as-map)]
|
||||
(let [balance (multisig/token-balance bounty-addr tla)]
|
||||
(when (> balance 0)
|
||||
(do
|
||||
(log/debug "bounty at" bounty-addr "has" balance "of token" tla)
|
||||
(let [internal-balance (multisig/token-balance-in-bounty bounty-addr tla)]
|
||||
(when (and (nil? watch-hash)
|
||||
(not= balance internal-balance))
|
||||
(log/info "balances not in sync, calling watch")
|
||||
(let [hash (multisig/watch-token bounty-addr tla)]
|
||||
(db-bounties/update-watch-hash issue-id hash)))))))))
|
||||
(try
|
||||
(let [balance (multisig/token-balance bounty-addr tla)]
|
||||
(when (> balance 0)
|
||||
(do
|
||||
(log/info "bounty at" bounty-addr "has" balance "of token" tla)
|
||||
(let [internal-balance (multisig/token-balance-in-bounty bounty-addr tla)]
|
||||
(when (and (nil? watch-hash)
|
||||
(not= balance internal-balance))
|
||||
(log/info "balances not in sync, calling watch")
|
||||
(let [hash (multisig/watch-token bounty-addr tla)]
|
||||
(db-bounties/update-watch-hash issue-id hash)))))))
|
||||
(catch Throwable ex
|
||||
(do (log/error "update-bounty-token-balances exception:" ex)
|
||||
(clojure.stacktrace/print-stack-trace ex)))))
|
||||
#_(log/info "Exit update-bounty-token-balances"))
|
||||
|
||||
|
||||
(defn update-contract-internal-balances
|
||||
"It is required in our current smart contract to manually update it's internal balance when some tokens have been added."
|
||||
|
@ -244,8 +268,20 @@
|
|||
(db-bounties/open-bounty-contracts)]
|
||||
(update-issue-usd-value bounty-addr)))
|
||||
|
||||
(defn float=
|
||||
([x y] (float= x y 0.0000001))
|
||||
([x y epsilon]
|
||||
(log/debug x y epsilon)
|
||||
(let [scale (if (or (zero? x) (zero? y)) 1 (abs x))]
|
||||
(<= (abs (- x y)) (* scale epsilon)))))
|
||||
|
||||
(defn map-float= [m1 m2]
|
||||
(and (= (set (keys m1)) (set (keys m2)))
|
||||
(every? #(float= (get m1 %1) (get m2 %1)) (keys m1))))
|
||||
|
||||
(defn update-balances
|
||||
[]
|
||||
(log/info "In update-balances")
|
||||
(doseq [{contract-address :contract_address
|
||||
owner :owner
|
||||
repo :repo
|
||||
|
@ -254,43 +290,48 @@
|
|||
db-balance-eth :balance_eth
|
||||
db-tokens :tokens
|
||||
issue-number :issue_number} (db-bounties/open-bounty-contracts)]
|
||||
(when comment-id
|
||||
(let [balance-eth-str (eth/get-balance-eth contract-address 6)
|
||||
balance-eth (read-string balance-eth-str)
|
||||
token-balances (multisig/token-balances contract-address)]
|
||||
(log/debug "update-balances" balance-eth
|
||||
balance-eth-str token-balances owner repo issue-number)
|
||||
(try
|
||||
(when comment-id
|
||||
(let [balance-eth-str (eth/get-balance-eth contract-address 6)
|
||||
balance-eth (read-string balance-eth-str)
|
||||
token-balances (multisig/token-balances contract-address)]
|
||||
(log/debug "update-balances" balance-eth
|
||||
balance-eth-str token-balances owner repo issue-number)
|
||||
|
||||
(when (or
|
||||
(not (float= db-balance-eth balance-eth))
|
||||
(not= db-tokens token-balances))
|
||||
(log/debug "balances differ")
|
||||
(log/debug "ETH (db):" db-balance-eth (type db-balance-eth) )
|
||||
(log/debug "ETH (chain):" balance-eth (type balance-eth) )
|
||||
(log/debug "ETH cmp:" (float= db-balance-eth balance-eth))
|
||||
(log/debug "tokens (db):" db-tokens (type db-tokens) (type (:SNT db-tokens)))
|
||||
(log/debug "tokens (chain):" token-balances (type token-balances) (type (:SNT token-balances)))
|
||||
(log/debug "tokens cmp:" (= db-tokens token-balances))
|
||||
(when (or
|
||||
(not (float= db-balance-eth balance-eth))
|
||||
(not (map-float= db-tokens token-balances)))
|
||||
(log/info "balances differ")
|
||||
(log/info "ETH (db):" db-balance-eth (type db-balance-eth) )
|
||||
(log/info "ETH (chain):" balance-eth (type balance-eth) )
|
||||
(log/info "ETH cmp:" (float= db-balance-eth balance-eth))
|
||||
(log/info "tokens (db):" db-tokens (type db-tokens) (type (:SNT db-tokens)))
|
||||
(log/info "tokens (chain):" token-balances (type token-balances) (type (:SNT token-balances)))
|
||||
(log/debug "tokens cmp:" (= db-tokens token-balances))
|
||||
|
||||
(issues/update-eth-balance contract-address balance-eth)
|
||||
(issues/update-token-balances contract-address token-balances)
|
||||
(bounties/update-bounty-comment-image issue-id
|
||||
owner
|
||||
repo
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
token-balances)
|
||||
(github/update-comment owner
|
||||
repo
|
||||
comment-id
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
token-balances)
|
||||
(update-issue-usd-value contract-address))))))
|
||||
(issues/update-eth-balance contract-address balance-eth)
|
||||
(issues/update-token-balances contract-address token-balances)
|
||||
(bounties/update-bounty-comment-image issue-id
|
||||
owner
|
||||
repo
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
token-balances)
|
||||
(github/update-comment owner
|
||||
repo
|
||||
comment-id
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
token-balances)
|
||||
(update-issue-usd-value contract-address))))
|
||||
(catch Throwable ex
|
||||
(do (log/error "update-balances exception:" ex)
|
||||
(clojure.stacktrace/print-stack-trace ex)))))
|
||||
(log/info "Exit update-balances"))
|
||||
|
||||
|
||||
(defn wrap-in-try-catch [func]
|
||||
|
@ -306,7 +347,7 @@
|
|||
|
||||
(defn run-1-min-interval-tasks [time]
|
||||
(do
|
||||
(log/debug "run-1-min-interval-tasks" time)
|
||||
(log/info "run-1-min-interval-tasks" time)
|
||||
;; TODO: disabled for now. looks like it may cause extraneus
|
||||
;; contract deployments and costs
|
||||
(run-tasks
|
||||
|
|
|
@ -135,4 +135,10 @@
|
|||
[:div.page-nav-text [:span (str "Page " page-number " of " page-count)]]
|
||||
[draw-page-numbers page-number page-count container-element]]])))
|
||||
|
||||
(defn usd-string
|
||||
"Turn a given float into a USD currency string based on the browsers locale setting.
|
||||
|
||||
A more complex and customizable approach can be found in goog.i18n.NumberFormat:
|
||||
https://google.github.io/closure-library/api/goog.i18n.NumberFormat.html"
|
||||
[usd-float]
|
||||
(.toLocaleString usd-float js/navigator.language #js {:style "currency" :currency "USD"}))
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
(ns commiteth.core
|
||||
(:require [reagent.core :as r]
|
||||
[re-frame.core :as rf]
|
||||
[secretary.core :as secretary]
|
||||
[goog.events :as events]
|
||||
[goog.history.EventType :as HistoryEventType]
|
||||
[commiteth.ajax :refer [load-interceptors!]]
|
||||
[commiteth.routes]
|
||||
[commiteth.handlers]
|
||||
[commiteth.subscriptions]
|
||||
[commiteth.activity :refer [activity-page]]
|
||||
|
@ -53,7 +53,7 @@
|
|||
[:a
|
||||
(merge props
|
||||
(if (keyword? target)
|
||||
{:on-click #(rf/dispatch [target])}
|
||||
{:on-click #(commiteth.routes/nav! target)}
|
||||
{:href target}))
|
||||
caption]]))]]))
|
||||
|
||||
|
@ -66,7 +66,7 @@
|
|||
[:div.ui.container.user-component
|
||||
[user-dropdown
|
||||
@user
|
||||
[[:update-address "My Payment Details" {}]
|
||||
[[:settings "My Payment Details" {}]
|
||||
["/logout" "Sign Out" {:class "logout-link"}]]
|
||||
mobile?]]
|
||||
[:a.ui.button.small.login-button {:href js/authorizeUrl} (str "LOG IN"
|
||||
|
@ -89,7 +89,7 @@
|
|||
(for [[page caption] tabs]
|
||||
(let [props {:class (str "ui item"
|
||||
(when (= @current-page page) " active"))
|
||||
:on-click #(rf/dispatch [:set-active-page page])}]
|
||||
:on-click #(commiteth.routes/nav! page)}]
|
||||
^{:key page} [:div props caption])))))))
|
||||
|
||||
|
||||
|
@ -124,15 +124,6 @@
|
|||
[flash-message-pane])])))
|
||||
|
||||
|
||||
(def pages
|
||||
{:activity #'activity-page
|
||||
:bounties #'bounties-page
|
||||
:repos #'repos-page
|
||||
:manage-payouts #'manage-payouts-page
|
||||
:update-address #'update-address-page
|
||||
:usage-metrics #'usage-metrics-page})
|
||||
|
||||
|
||||
(defn top-hunters []
|
||||
(let [top-hunters (rf/subscribe [:top-hunters])]
|
||||
(fn []
|
||||
|
@ -211,7 +202,13 @@
|
|||
[:div {:class (str (if (show-top-hunters?) "eleven" "sixteen")
|
||||
" wide computer sixteen wide tablet column")}
|
||||
[:div.ui.container
|
||||
[(pages @current-page)]]]
|
||||
(case @current-page
|
||||
:activity [activity-page]
|
||||
:bounties [bounties-page]
|
||||
:repos [repos-page]
|
||||
:manage-payouts [manage-payouts-page]
|
||||
:settings [update-address-page]
|
||||
:usage-metrics [usage-metrics-page])]]
|
||||
(when (show-top-hunters?)
|
||||
[:div.five.wide.column.computer.only
|
||||
[:div.ui.container.top-hunters
|
||||
|
@ -220,28 +217,6 @@
|
|||
[top-hunters]]])]]]
|
||||
[footer]])))
|
||||
|
||||
(secretary/set-config! :prefix "#")
|
||||
|
||||
(secretary/defroute "/" []
|
||||
(rf/dispatch [:set-active-page :bounties]))
|
||||
|
||||
(secretary/defroute "/activity" []
|
||||
(rf/dispatch [:set-active-page :activity]))
|
||||
|
||||
|
||||
(secretary/defroute "/repos" []
|
||||
(if js/user
|
||||
(rf/dispatch [:set-active-page :repos])
|
||||
(secretary/dispatch! "/")))
|
||||
|
||||
(defn hook-browser-navigation! []
|
||||
(doto (History.)
|
||||
(events/listen
|
||||
HistoryEventType/NAVIGATE
|
||||
(fn [event]
|
||||
(secretary/dispatch! (.-token event))))
|
||||
(.setEnabled true)))
|
||||
|
||||
(defn mount-components []
|
||||
(r/render [#'page] (.getElementById js/document "app")))
|
||||
|
||||
|
@ -286,7 +261,7 @@
|
|||
(when config/debug?
|
||||
(enable-re-frisk!))
|
||||
(load-interceptors!)
|
||||
(hook-browser-navigation!)
|
||||
(commiteth.routes/setup-nav!)
|
||||
(load-data true)
|
||||
(.addEventListener js/window "click" #(rf/dispatch [:clear-flash-message]))
|
||||
(on-js-load))
|
||||
|
|
|
@ -312,12 +312,6 @@
|
|||
(:status-text response)))]}))
|
||||
|
||||
|
||||
(reg-event-fx
|
||||
:update-address
|
||||
(fn [{:keys [db]} [_]]
|
||||
{:db db
|
||||
:dispatch [:set-active-page :update-address]}))
|
||||
|
||||
(reg-event-db
|
||||
:update-user
|
||||
(fn [db [_ fields]]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(ns commiteth.manage-payouts
|
||||
(:require [re-frame.core :as rf]
|
||||
[commiteth.common :refer [human-time]]))
|
||||
[commiteth.common :as common :refer [human-time]]))
|
||||
|
||||
|
||||
|
||||
|
@ -57,9 +57,21 @@
|
|||
(:claims bounty))]
|
||||
[claim-card bounty claim]))))
|
||||
|
||||
(defn bounty-stats [{:keys [paid unpaid]}]
|
||||
[:div.cf
|
||||
[:div.fl-ns.w-50-ns.tc.pv4
|
||||
[:div.ttu.tracked "Open"]
|
||||
[:div.f2.pa2 (common/usd-string (:combined-usd-value unpaid))]
|
||||
[:div (:count unpaid) " bounties"]]
|
||||
|
||||
[:div.fl-ns.w-50-ns.tc.pv4
|
||||
[:div.ttu.tracked "Paid"]
|
||||
[:div.f2.pa2 (common/usd-string (:combined-usd-value paid))]
|
||||
[:div (:count paid) " bounties"]]])
|
||||
|
||||
(defn manage-payouts-page []
|
||||
(let [owner-bounties (rf/subscribe [:owner-bounties])
|
||||
bounty-stats-data (rf/subscribe [:owner-bounties-stats])
|
||||
owner-bounties-loading? (rf/subscribe [:get-in [:owner-bounties-loading?]])]
|
||||
(fn []
|
||||
(if @owner-bounties-loading?
|
||||
|
@ -67,17 +79,14 @@
|
|||
[:div.ui.active.inverted.dimmer
|
||||
[:div.ui.text.loader "Loading"]]]
|
||||
(let [web3 (.-web3 js/window)
|
||||
bounties (vals @owner-bounties)
|
||||
unpaid? #(empty? (:payout_hash %))
|
||||
paid? #(not-empty (:payout_hash %))
|
||||
unpaid-bounties (filter unpaid? bounties)
|
||||
paid-bounties (filter paid? bounties)]
|
||||
bounties (vals @owner-bounties)]
|
||||
[:div.ui.container
|
||||
(when (nil? web3)
|
||||
[:div.ui.warning.message
|
||||
[:i.warning.icon]
|
||||
"To sign off claims, please view Status Open Bounty in Status, Mist or Metamask"])
|
||||
[bounty-stats @bounty-stats-data]
|
||||
[:h3 "New claims"]
|
||||
[claim-list unpaid-bounties]
|
||||
[claim-list (filter (complement :paid?) bounties)]
|
||||
[:h3 "Old claims"]
|
||||
[claim-list paid-bounties]])))))
|
||||
[claim-list (filter :paid? bounties)]])))))
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
(ns commiteth.routes
|
||||
(:require [bide.core :as bide]
|
||||
[re-frame.core :as rf]))
|
||||
|
||||
(defonce router
|
||||
(bide/router [["/" :bounties]
|
||||
["/activity" :activity]
|
||||
["/repos" :repos]
|
||||
["/manage-payouts" :manage-payouts]
|
||||
["/settings" :settings]
|
||||
["/usage-metrics" :usage-metrics]]))
|
||||
|
||||
(defn on-navigate
|
||||
"A function which will be called on each route change."
|
||||
[name params query]
|
||||
(println "Route change to: " name params query)
|
||||
(rf/dispatch [:set-active-page name]))
|
||||
|
||||
(defn setup-nav! []
|
||||
(bide/start! router {:default :bounties
|
||||
:on-navigate on-navigate}))
|
||||
|
||||
(defn nav! [route-id]
|
||||
(bide/navigate! router route-id {}))
|
||||
|
|
@ -68,7 +68,26 @@
|
|||
(reg-sub
|
||||
:owner-bounties
|
||||
(fn [db _]
|
||||
(:owner-bounties db)))
|
||||
(->> (for [[id bounty] (:owner-bounties db)]
|
||||
;; TODO(martinklepsch) we might want to consider using a
|
||||
;; special prefix or namespace for derived properties that
|
||||
;; are added to domain records like this
|
||||
;; e.g. `derived/paid?`
|
||||
[id (assoc bounty :paid? (boolean (:payout_hash bounty)))])
|
||||
(into {}))))
|
||||
|
||||
(reg-sub
|
||||
:owner-bounties-stats
|
||||
:<- [:owner-bounties]
|
||||
(fn [owner-bounties _]
|
||||
(let [sum-dollars (fn sum-dollars [bounties]
|
||||
(reduce + (map #(js/parseFloat (:value_usd %)) bounties)))
|
||||
{:keys [paid unpaid]} (group-by #(if (:paid? %) :paid :unpaid)
|
||||
(vals owner-bounties))]
|
||||
{:paid {:count (count paid)
|
||||
:combined-usd-value (sum-dollars paid)}
|
||||
:unpaid {:count (count unpaid)
|
||||
:combined-usd-value (sum-dollars unpaid)}})))
|
||||
|
||||
(reg-sub
|
||||
:pagination
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
FROM python:3.6
|
||||
COPY end-to-end/requirements.txt /app/requirements.txt
|
||||
WORKDIR /app
|
||||
RUN pip3 install -r requirements.txt
|
||||
COPY end-to-end /app
|
||||
ENTRYPOINT ["python3"]
|
||||
CMD ["-m", "pytest", "-m", "sanity"]
|
|
@ -1,7 +1,8 @@
|
|||
node ('linux1') {sauce('1be1b688-e0e7-4314-92a0-db11f52d3c00') {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/develop']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[url: 'https://github.com/status-im/open-bounty.git']]])
|
||||
configFileProvider([configFile(fileId: 'sob_automation_test_config', targetLocation: 'test/end-to-end/tests')]) {
|
||||
try {sh 'cd test/end-to-end/tests && python3 -m pytest -m sanity --build=$BUILD_NAME -v -n 1'
|
||||
try {withCredentials([string(credentialsId: 'SOB_SAUCE_ACCESS_KEY', variable: 'SAUCE_ACCESS_KEY'), string(credentialsId: 'SOB_SAUCE_USERNAME', variable: 'SAUCE_USERNAME')])
|
||||
{sh 'cd test && docker build -t end2end . && docker run --rm -e "SAUCE_USERNAME="${SAUCE_USERNAME} -e "SAUCE_ACCESS_KEY="${SAUCE_ACCESS_KEY} --name end2end-container end2end -m pytest -m sanity --build=$BUILD_NAME -v -n 1'}
|
||||
}
|
||||
finally {
|
||||
saucePublisher()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import configparser
|
||||
import os
|
||||
|
||||
class TestData(object):
|
||||
|
||||
|
@ -9,7 +10,7 @@ class TestData(object):
|
|||
# define here path to your config.ini file
|
||||
# example - config_example.ini
|
||||
|
||||
self.config.read('config.ini')
|
||||
self.config.read(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'config.ini'))
|
||||
|
||||
# self.issue['title'] is set in GithubPage::create_new_bounty
|
||||
# self.issue['id'] is set in GithubPage::create_new_bounty
|
||||
|
|
|
@ -18,6 +18,8 @@ class BaseTestCase:
|
|||
sauce_lab_cap = dict()
|
||||
sauce_lab_cap['name'] = test_data.test_name
|
||||
sauce_lab_cap['build'] = pytest.config.getoption('build')
|
||||
sauce_lab_cap['idleTimeout'] = 900
|
||||
sauce_lab_cap['commandTimeout'] = 500
|
||||
sauce_lab_cap['platform'] = "MAC"
|
||||
sauce_lab_cap['browserName'] = 'Chrome'
|
||||
sauce_lab_cap['screenResolution'] = '2048x1536'
|
||||
|
@ -67,8 +69,9 @@ class BaseTestCase:
|
|||
|
||||
if cls.environment == 'sauce':
|
||||
for caps in cls.capabilities_dev, cls.capabilities_org:
|
||||
cls.get_remote_caps(cls)
|
||||
remote = cls.get_remote_caps(cls)
|
||||
new_caps = caps.to_capabilities()
|
||||
new_caps.update(remote)
|
||||
driver = webdriver.Remote(cls.executor_sauce_lab,
|
||||
desired_capabilities=new_caps)
|
||||
drivers.append(driver)
|
||||
|
@ -79,6 +82,7 @@ class BaseTestCase:
|
|||
cls.driver_dev = drivers[0]
|
||||
cls.driver_org = drivers[1]
|
||||
|
||||
|
||||
for driver in drivers:
|
||||
driver.implicitly_wait(10)
|
||||
|
||||
|
@ -132,9 +136,9 @@ class BaseTestCase:
|
|||
remove_installation(cls.driver_org)
|
||||
|
||||
######DEV
|
||||
|
||||
cls.github_dev.delete_fork()
|
||||
cls.github_dev.clean_repo_local_folder()
|
||||
cls.github_dev.delete_fork()
|
||||
|
||||
try:
|
||||
cls.driver_dev.quit()
|
||||
cls.driver_org.quit()
|
||||
|
|
Loading…
Reference in New Issue