From c11d4ef0f0f16e6529a3305aca5471bc7e67f5f7 Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Mon, 27 May 2019 22:01:39 -0300 Subject: [PATCH 01/90] (add) base elements for new network selector --- apps/block_scout_web/assets/css/app.scss | 2 + .../css/components/_network-selector.scss | 283 ++++++++++++++++++ .../assets/css/components/_radio.scss | 49 +++ .../network-selector-icons/aerum-mainnet.png | Bin 0 -> 4225 bytes .../callisto-mainnet.png | Bin 0 -> 2871 bytes .../network-selector-icons/callisto.svg | 7 + .../images/network-selector-icons/core.svg | 4 + .../images/network-selector-icons/dai.svg | 33 ++ .../ethereum-classic.png | Bin 0 -> 2965 bytes .../ethereum-mainnet.png | Bin 0 -> 3634 bytes .../network-selector-icons/goerli-testnet.png | Bin 0 -> 4936 bytes .../network-selector-icons/kovan-testnet.png | Bin 0 -> 2846 bytes .../network-selector-icons/poa-core.png | Bin 0 -> 1639 bytes .../network-selector-icons/poa-sokol.png | Bin 0 -> 1714 bytes .../rinkeby-testnet.png | Bin 0 -> 2171 bytes .../images/network-selector-icons/rinkeby.svg | 15 + .../ropsten-testnet.png | Bin 0 -> 1736 bytes .../images/network-selector-icons/ropsten.svg | 4 + .../network-selector-icons/rsk-mainnet.png | Bin 0 -> 3811 bytes .../images/network-selector-icons/sokol.svg | 4 + .../network-selector-icons/xdai-chain.png | Bin 0 -> 7638 bytes .../layout/_network_selector.html.eex | 51 ++++ .../layout/_network_selector_item.html.eex | 25 ++ .../templates/layout/_topnav.html.eex | 1 + 24 files changed, 478 insertions(+) create mode 100644 apps/block_scout_web/assets/css/components/_network-selector.scss create mode 100644 apps/block_scout_web/assets/css/components/_radio.scss create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/aerum-mainnet.png create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/callisto-mainnet.png create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/callisto.svg create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/core.svg create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/dai.svg create mode 100755 apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-classic.png create mode 100755 apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-mainnet.png create mode 100755 apps/block_scout_web/assets/static/images/network-selector-icons/goerli-testnet.png create mode 100755 apps/block_scout_web/assets/static/images/network-selector-icons/kovan-testnet.png create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/poa-core.png create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/poa-sokol.png create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby-testnet.png create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby.svg create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/ropsten-testnet.png create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/ropsten.svg create mode 100755 apps/block_scout_web/assets/static/images/network-selector-icons/rsk-mainnet.png create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/sokol.svg create mode 100644 apps/block_scout_web/assets/static/images/network-selector-icons/xdai-chain.png create mode 100644 apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex create mode 100644 apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex diff --git a/apps/block_scout_web/assets/css/app.scss b/apps/block_scout_web/assets/css/app.scss index 3838fd96c1..469e9ab5c4 100644 --- a/apps/block_scout_web/assets/css/app.scss +++ b/apps/block_scout_web/assets/css/app.scss @@ -121,6 +121,8 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; @import "components/api"; @import "components/alerts"; @import "components/errors"; +@import "components/radio"; +@import "components/network-selector"; :export { dashboardBannerChartAxisFontColor: $dashboard-banner-chart-axis-font-color; diff --git a/apps/block_scout_web/assets/css/components/_network-selector.scss b/apps/block_scout_web/assets/css/components/_network-selector.scss new file mode 100644 index 0000000000..cfa79942d2 --- /dev/null +++ b/apps/block_scout_web/assets/css/components/_network-selector.scss @@ -0,0 +1,283 @@ +$network-selector-overlay-background: $modal-overlay-color !default; +$network-selector-close-color: $primary !default; +$network-selector-horizontal-padding: 28px; +$btn-network-selector-load-more-background: #fff !default; +$btn-network-selector-load-more-color: $primary !default; +$network-selector-search-input-color: #a3a9b5 !default; +$network-selector-tab-active-border-color: $primary !default; +$network-selector-item-icon-dimensions: 30px !default; + +.network-selector-overlay { + background-color: rgba($network-selector-overlay-background, 0.9); + bottom: 0; + display: flex; + left: 0; + position: fixed; + right: 0; + top: 0; + z-index: 123; +} + +.network-selector { + background-color: #fff; + display: flex; + flex-direction: column; + flex-grow: 1; + flex-shrink: 1; + margin-left: auto; + max-width: 398px; + padding: 28px 0 35px; +} + +.network-selector-close { + flex-shrink: 0; + padding: 0 $network-selector-horizontal-padding; + margin: 0 0 8px; + + svg { + cursor: pointer; + display: block; + margin-left: auto; + } + + path { + fill: $network-selector-close-color; + } +} + +.network-selector-text-container { + flex-shrink: 0; + margin: 0 0 15px; + padding: 0 $network-selector-horizontal-padding; +} + +.network-selector-title { + color: #333; + font-size: 18px; + font-weight: normal; + line-height: 1.2; + margin: 0 0 10px; + padding: 0; +} + +.network-selector-text { + color: #a3a9b5; + font-size: 12px; + font-weight: normal; + line-height: 1.67; + margin: 0; + padding: 0; +} + +.network-selector-search-container { + align-items: center; + background-color: #f5f6fa; + display: flex; + flex-shrink: 0; + height: 62px; + margin: 0; + padding: 0 $network-selector-horizontal-padding; + + path { + flex-grow: 0; + flex-shrink: 0; + fill: $network-selector-search-input-color; + } +} + +.network-selector-search-input { + background-color: transparent; + border-color: transparent; + color: #333; + flex-grow: 1; + font-size: 14px; + font-weight: 600; + height: 100%; + outline: none; + padding: 0 20px 0 10px; + + &[placeholder]{ + color: $network-selector-search-input-color !important; + } + &::-webkit-input-placeholder { /* Chrome/Opera/Safari */ + color: $network-selector-search-input-color !important; + } + &::-moz-placeholder { /* Firefox 19+ */ + color: $network-selector-search-input-color !important; + } + &:-ms-input-placeholder { /* IE 10+ */ + color: $network-selector-search-input-color !important; + } + &:-moz-placeholder { /* Firefox 18- */ + color: $network-selector-search-input-color !important; + } +} + +.network-selector-tabs-container { + border-bottom: 1px solid $base-border-color; + display: flex; + flex-shrink: 0; + margin: 0 $network-selector-horizontal-padding; +} + +.network-selector-tab { + color: #a3a9b5; + cursor: pointer; + font-size: 14px; + font-weight: 600; + line-height: 1.2; + padding: 20px 20px 15px; + position: relative; + text-align: center; + + &:hover { + color: #333; + } + + &.active { + color: #333; + + &::after { + background-color: $network-selector-tab-active-border-color; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + bottom: 0; + content: ""; + height: 4px; + left: 0; + position: absolute; + right: 0; + } + } +} + +.network-selector-tab-content { + display: none; + + &.active { + display: block; + } +} + +.network-selector-item { + border-bottom: 1px solid $base-border-color; + display: flex; + position: relative; + + .radio { + margin: 0 15px 0 0; + } + + .radio-icon { + margin: 0; + } + + &:last-child { + border-bottom: none; + } +} + +.network-selector-item-url { + align-items: center; + cursor: pointer; + display: flex; + flex-grow: 1; + margin: 0; + padding: 20px 20px 20px 0; +} + +.network-selector-item-icon { + background-color: #dfdfdf; + background-position: 50% 50%; + background-repeat: no-repeat; + background-size: contain; + border-radius: 50%; + flex-grow: 0; + flex-shrink: 0; + height: $network-selector-item-icon-dimensions; + margin: 0 15px 0 0; + width: $network-selector-item-icon-dimensions; +} + +.network-selector-item-title { + color: #333; + flex-grow: 1; + font-size: 14px; + font-weight: normal; + line-height: 1.2; + overflow: hidden; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} + +.network-selector-item-type { + color: #a3a9b5; + flex-shrink: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.2; + padding-left: 10px; + text-align: right; + white-space: nowrap; +} + +.network-selector-item-content { + align-items: center; + display: flex; + flex-grow: 1; +} + +.network-selector-networks-container { + flex-grow: 1; + flex-shrink: 1; + margin: 0 0 30px; + min-height: 100px; + overflow: auto; + padding: 0 $network-selector-horizontal-padding; +} + +.network-selector-load-more-container { + flex-shrink: 1; + padding: 0 $network-selector-horizontal-padding; + + .btn-network-selector-load-more { + @include btn-line($btn-network-selector-load-more-background, $btn-network-selector-load-more-color); + width: 100%; + } +} + +.network-selector-item-favorite { + align-items: center; + cursor: pointer; + display: flex; + flex-grow: 1; + flex-shrink: 0; + margin: 0; + max-width: 16px; + position: relative; + + input[type="checkbox"] { + cursor: pointer; + height: 100%; + opacity: 0; + position: absolute; + width: 100%; + z-index: 5; + + &:checked + svg { + position: relative; + z-index: 1; + + path { + fill: #ffb20d; + } + } + } + + &:hover { + path { + fill: rgba(#ffb20d, 0.4); + } + } +} \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_radio.scss b/apps/block_scout_web/assets/css/components/_radio.scss new file mode 100644 index 0000000000..ebcc2ac892 --- /dev/null +++ b/apps/block_scout_web/assets/css/components/_radio.scss @@ -0,0 +1,49 @@ +$radio-color: $primary !default; +$radio-dimensions: 20px !default; + +.radio { + align-items: center; + display: flex; + position: relative; + + input[type="radio"] { + height: 100%; + opacity: 0; + position: absolute; + width: 100%; + z-index: 5; + + &:checked + .radio-icon::before { + background-color: $radio-color; + border-radius: 50%; + content: ""; + height: 12px; + left: 50%; + position: absolute; + top: 50%; + transform: translateX(-50%) translateY(-50%); + width: 12px; + } + } + + .radio-icon { + border: 1px solid $base-border-color; + border-radius: 50%; + flex-grow: 0; + flex-shrink: 0; + height: $radio-dimensions; + margin: 0 10px 0 0; + position: relative; + width: $radio-dimensions; + z-index: 1; + } + + .radio-text { + font-size: 14px; + font-weight: normal; + line-height: 1.2; + position: relative; + white-space: nowrap; + z-index: 1; + } +} diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/aerum-mainnet.png b/apps/block_scout_web/assets/static/images/network-selector-icons/aerum-mainnet.png new file mode 100644 index 0000000000000000000000000000000000000000..cdef9a2edb814eeebcb9317e0fae60b4a858f6ac GIT binary patch literal 4225 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 literal 0 HcmV?d00001 diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/callisto-mainnet.png b/apps/block_scout_web/assets/static/images/network-selector-icons/callisto-mainnet.png new file mode 100644 index 0000000000000000000000000000000000000000..2e9f2b2730b309d9df7f0216700bebf5a638ac26 GIT binary patch literal 2871 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 + + + + + + diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/core.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/core.svg new file mode 100644 index 0000000000..63c66a6baa --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/core.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/dai.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/dai.svg new file mode 100644 index 0000000000..c4f800733e --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/dai.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-classic.png b/apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-classic.png new file mode 100755 index 0000000000000000000000000000000000000000..5ca5d8fe7bbbe31f48a59dd2f908d48c7f723654 GIT binary patch literal 2965 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 literal 0 HcmV?d00001 diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/goerli-testnet.png b/apps/block_scout_web/assets/static/images/network-selector-icons/goerli-testnet.png new file mode 100755 index 0000000000000000000000000000000000000000..800590fee5a6b20f365d726451935eebdff86a99 GIT binary patch literal 4936 zcmbVQ2{_bi+m|gd)<#W{rZFePn0?HQ-B3*;Sx!TtF*BIV*34KdTb6K0lxnn)LzWPV zqDYdYNF7U&rG-SY)sV_JTD<3dzteZU*EiSopZ~M`e!u6w@8^E*>w5kPPL5lY71t_C zNl7V_x7s>O-X4pOg0$qn>VBu2iZE zOr>*u7{(!No&+r=Wo8+|qtXHxLa-0RkI6BIysxQ)fSGi2$S$k{!huI(_%pYL@fj{* zj;^$@02+Y~v9ti2g%BkIY=)2u4q>x60%C|c-7fKUc^V_;Y^|gr(`FQ1FN(K7%Uc@?E)H*3Vsb`l$jYkrsCq3)Xj_ z(wLk@X@f z6D732LnTQ^rwXb64cNhfNahHHR1S?nwl#-Hj5202=|n6Fg+-g-Q7{yWMu%aj7&;8^ zOJKmzcr2ZP#8GiLtj{;QBz#*gEpRc0KlyYCAAv_Au_kB)hJZFfBJBtS1QKnBw>7ag zv9?B$Ncf-J9ek$bq)}P_KMgvK=*#7^sgkr{vZ;OyIFI880WWP2k;G+j`I5j%qKp3f zIGIFp;&XkOEXjnxd5aC0Y)8T&2{;@KWsF=rB9bsHI>z8LgBbM9d@dXOEp>^^e+U)A zWHZtZjYIv~)$d&TFT;eg`z05?WXx8kK$2gfKPrF=WB-p9iz%^9ax#ci+Tv-JI7F4$ z4EZtmPX_;Yd;489*qp1z6t;G``g^#3fE7#q#7YxPFN#N{*J%$Sy5i_^aEn!{=+g>blv9 zDDh>Wm(eOs_j|zY4G3WabiXj9tEBd8cE!r!y2)a0WzdWh@I>3N_sYbVA$A$$v|BrS z%6B$8Z})@`CFvZk`hBHt`_`~PIlFlJRj>4YI}_jfvn@;nzo`LAG?SF08iuDoyn8eM zg&W=uRI>Q`;iCMfPl`oPqf4^!g{L;Ps)IJa73JxkI{vUbFKoWgv#dM){Fl$GgT}N^ zFx-KS$?Z-+^(;l(v$;r7&WhF?(W$&lDTQ+k|INTcT=X;js5DoHlcUuv3eGMq8Is#m z#f|uI$IvTrb}GkPTlbmPshR+w`G?(kkxpPFxWb)k=b^)-Bdd*{@Q%Z@Xa zrt&ruBL6%_Ny(pZ@7nuQ7Cnf@^5ZR=S!ZIrsPj#+?tGBy zPLQ+uHrXg$=qh%i+4O_plv=a)_71_J@n;Vs!MOIVuV$Ld*4=!LEt7w{TmMA=*OCKm z4u3RM4Dw>~3PzfhOoKV`CqITgLRgSI3|ZHrb;! z%RY?>`XeUqC2Vu>v>a2_(@Xg?OlNMUshUGp%q@%`P=6RU+n}1fus?%wJ$YHcg;PiL zICay-bF7pZXA8&m-DnZ{V%PcD8zAkq9uyD4b@ z2XdJSZZW8+9=xOWr@_+jsnHxz!d;ISMhY9r3DF1~(w!JGQG1}zFho>P?p?Xt*{ZE}degp1VeLd%%q~i_%tZ-43_JelfBK7b?du5Nt}5 zLvGgcug{3?-A?Z4?Ck43b0khiZH3(gYvXcd`znXhes4+(91g=~C?7VIS6&Y|c|rdX zZ~%wK!u0on$7lv|IiQ|Srf7 ZJgnlqM!2K3_CJuxjs_tzfLZFp3A6tK?Z#J1Rj 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 literal 0 HcmV?d00001 diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/poa-core.png b/apps/block_scout_web/assets/static/images/network-selector-icons/poa-core.png new file mode 100644 index 0000000000000000000000000000000000000000..68bb5976f15b7125fca21f56aaa8b663ba68d705 GIT binary patch literal 1639 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 LZ6CFs5}x)S!$o_{ literal 0 HcmV?d00001 diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby.svg new file mode 100644 index 0000000000..1991096926 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/ropsten-testnet.png b/apps/block_scout_web/assets/static/images/network-selector-icons/ropsten-testnet.png new file mode 100644 index 0000000000000000000000000000000000000000..91581113dffb031bfe914a1290be32cd22683c8f GIT binary patch literal 1736 zcmaJ?X;2eq7!E3+I5<@hDsn7K6=_L!lR(HuA|MG6AvA<2h^??B8wil>x>-m7u~tE= z)QpPAC}k-18bv$Q4g&=v9;1|sFe(-6Q3s4#JU|5xz)reBj{Yd!`F6i!-sgF*=bPOj zi;s48@^E4>7|xOyk({1WZ13zD^ta)+o2%(*5hYHh5^*(UKuL@dp~BNKK%zx6Fgb>* z@;9_$p$x{%Wg0~?l`M@DC~++dwb`(YT7pJ17@=WC0##;X6p)T(Xmmnwpt%JEG%6vO z6fA|ML?o7}i76nl#DaK*vLIWDsKBrlK&Vkb6KF9C1&rDponBxRf@8b_dTl#~Kw!*- z$`*njf=ZUkfJmIgfM6DnsbmKQ10e{D&Ep0kNFcz0*?b7*Lu@XS%@YK{0uBcle?ZzB zsZtB%qNs6S^iBw7QWPP8AcMicGH_WqnE|m81c6`<#NjY$3#L9_N1;ZhPVYCtAj0%Y zQbSN0TnE?~(R4hQ5`uK3A5zc~le9Yhc$w&eK}M8-*euwV(uAQ@`v0a{?WDDyl4Dcx z{wJ|skxyWd9Mj{uq>^r&+Rx^S5JZv~O5vme$8#pCD9gktT%U;(KxCw?Y6!4QiYhfa z+hM>MN-7mdbb1QaDKUvi2+}+(jYcIvg5aPKID{X9z`<;`7(rk-YGqJlFegF`M?|fR zn!t*1Wv&*}Q4?6z6c(8lYZHW)pgoH)Qj>?NqDWi|j3qA6OdktxTE20tYWi40ro}>Z zFp#ai|7!Qd5v?KHa#FnXW>Wr`j@CR$i@QAzTtnX(oZLgiLZ%y73A4cA-V4Lc0!eKO-qmWuA^>*XLa$Y4^&*by*Z>; zWXZ91y?xW)oy_l0L-(sP=D93AoZ5YPPi$b-seP?mE%INJ(#4)7?Zba>yEwRNhM}-F z-QTc#m8AB4Xg?07!{!od}!oDs)iide zUEc&;Ea7pUxNeGHkfcj0E2$G#nJWAlqYtiJQi3HNTfr?cciiUkIeuk*J@$Rat>rZp z2_L7;B~(mB6*)Tyfb^6Z>Grml{Y=Q|hiheo`LJYDZZnM{%m`?i7ghVHf5MyvMr^0U|b%GBZ&r={lD*D<-= zM<-6)i+g^i!`|uYrmHog@M=JsZf&Y<8m&vre7sr~P;~gs_TJ*Q-s@|+O6!ZF752l| z=704`{jF$&#N%c2*>qn0{#EOood&|pi2e%In3!a)P5GiLYVqBzSKU4S@*Z?Uv-l&a z-!hB5FTXzom{ZmRriN;N#r1Zc`a;{TwQi2*MSb(52g2SBEnRc4;^BhW`4ZJa;^YZC mM}jf^I5(x-L$XokIfkWj$@lu8mgBa6i$okR`f+92mVW@(W|@uv literal 0 HcmV?d00001 diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/ropsten.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/ropsten.svg new file mode 100644 index 0000000000..87c98f79d2 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/ropsten.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/rsk-mainnet.png b/apps/block_scout_web/assets/static/images/network-selector-icons/rsk-mainnet.png new file mode 100755 index 0000000000000000000000000000000000000000..c7c0e067c83fd0222cbee6d354e73ed5e2eb02b2 GIT binary patch literal 3811 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 literal 0 HcmV?d00001 diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/sokol.svg b/apps/block_scout_web/assets/static/images/network-selector-icons/sokol.svg new file mode 100644 index 0000000000..b2ced66189 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/network-selector-icons/sokol.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/block_scout_web/assets/static/images/network-selector-icons/xdai-chain.png b/apps/block_scout_web/assets/static/images/network-selector-icons/xdai-chain.png new file mode 100644 index 0000000000000000000000000000000000000000..ab35a5b07235016486d096fdd62fcd0cba932b81 GIT binary patch literal 7638 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 X+d@w4jj9xX|Fo(r>nPPK*uMTBp*k`w literal 0 HcmV?d00001 diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex new file mode 100644 index 0000000000..042fc2135e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex @@ -0,0 +1,51 @@ +
+
+
+ + + +
+
+

<%= gettext("Change Network") %>

+

<%= gettext("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.") %>

+
+
+ + + + +
+
+
<%= gettext("All") %>
+
<%= gettext("Mainnet") %>
+
<%= gettext("Testnet") %>
+
<%= gettext("Favorites") %>
+
+
+
+ <%= for %{url: url, title: title} <- other_networks() do %> + <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Mainnet" %> + <% end %> +
+ <% main_nets = main_nets(other_networks()) %> +
+ <%= for %{url: url, title: title} <- main_nets do %> + <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: assigns[:tab_type] %> + <% end %> +
+ <% test_nets = test_nets(other_networks()) %> +
+ <%= for %{url: url, title: title} <- test_nets do %> + <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: assigns[:tab_type] %> + <% end %> +
+
+ No content. +
+ +
+
+ +
+
+
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex new file mode 100644 index 0000000000..2636542e4e --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex @@ -0,0 +1,25 @@ +
+ + +
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex index 514422bcc8..1839c6d445 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex @@ -131,3 +131,4 @@ +<%= render BlockScoutWeb.LayoutView, "_network_selector.html" %> \ No newline at end of file From 239b5ac6c476498d884323922ff0f6b4dd8d4053 Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Tue, 28 May 2019 13:00:27 -0300 Subject: [PATCH 02/90] (add) new network selector behaviours --- .../css/components/_network-selector.scss | 47 +++++++++- apps/block_scout_web/assets/js/app.js | 1 + .../assets/js/lib/network_selector.js | 71 ++++++++++++++ .../layout/_network_selector.html.eex | 92 ++++++++++--------- .../layout/_network_selector_item.html.eex | 2 +- .../templates/layout/_topnav.html.eex | 16 +--- 6 files changed, 165 insertions(+), 64 deletions(-) create mode 100644 apps/block_scout_web/assets/js/lib/network_selector.js diff --git a/apps/block_scout_web/assets/css/components/_network-selector.scss b/apps/block_scout_web/assets/css/components/_network-selector.scss index cfa79942d2..e4305befb9 100644 --- a/apps/block_scout_web/assets/css/components/_network-selector.scss +++ b/apps/block_scout_web/assets/css/components/_network-selector.scss @@ -7,10 +7,14 @@ $network-selector-search-input-color: #a3a9b5 !default; $network-selector-tab-active-border-color: $primary !default; $network-selector-item-icon-dimensions: 30px !default; +.network-selector-visible { + position: fixed; +} + .network-selector-overlay { background-color: rgba($network-selector-overlay-background, 0.9); bottom: 0; - display: flex; + display: none; left: 0; position: fixed; right: 0; @@ -18,6 +22,12 @@ $network-selector-item-icon-dimensions: 30px !default; z-index: 123; } +.network-selector-wrapper { + display: flex; + height: 100%; + width: 100%; +} + .network-selector { background-color: #fff; display: flex; @@ -26,7 +36,10 @@ $network-selector-item-icon-dimensions: 30px !default; flex-shrink: 1; margin-left: auto; max-width: 398px; + min-width: 0; padding: 28px 0 35px; + position: relative; + transition: right 0.25s ease-out; } .network-selector-close { @@ -123,12 +136,16 @@ $network-selector-item-icon-dimensions: 30px !default; .network-selector-tab { color: #a3a9b5; cursor: pointer; + flex-shrink: 1; font-size: 14px; font-weight: 600; line-height: 1.2; - padding: 20px 20px 15px; + min-width: 0; + padding: 20px 18px 15px; position: relative; text-align: center; + user-select: none; + white-space: nowrap; &:hover { color: #333; @@ -136,6 +153,7 @@ $network-selector-item-icon-dimensions: 30px !default; &.active { color: #333; + cursor: default; &::after { background-color: $network-selector-tab-active-border-color; @@ -165,7 +183,12 @@ $network-selector-item-icon-dimensions: 30px !default; position: relative; .radio { + cursor: pointer; margin: 0 15px 0 0; + + input[type="radio"] { + cursor: pointer; + } } .radio-icon { @@ -183,7 +206,13 @@ $network-selector-item-icon-dimensions: 30px !default; display: flex; flex-grow: 1; margin: 0; - padding: 20px 20px 20px 0; + padding: 20px 0; + + &:hover { + .network-selector-item-type { + color: #333; + } + } } .network-selector-item-icon { @@ -208,6 +237,7 @@ $network-selector-item-icon-dimensions: 30px !default; overflow: hidden; text-align: left; text-overflow: ellipsis; + user-select: none; white-space: nowrap; } @@ -219,6 +249,7 @@ $network-selector-item-icon-dimensions: 30px !default; line-height: 1.2; padding-left: 10px; text-align: right; + user-select: none; white-space: nowrap; } @@ -254,7 +285,8 @@ $network-selector-item-icon-dimensions: 30px !default; flex-grow: 1; flex-shrink: 0; margin: 0; - max-width: 16px; + max-width: 36px; + padding-left: 20px; position: relative; input[type="checkbox"] { @@ -280,4 +312,11 @@ $network-selector-item-icon-dimensions: 30px !default; fill: rgba(#ffb20d, 0.4); } } +} + +.network-selector-tab-content-empty { + font-size: 16px; + font-weight: 600; + padding: 40px; + text-align: center; } \ No newline at end of file diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index f10e3d696d..f37659ccba 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -54,3 +54,4 @@ import './lib/tooltip' import './lib/modals' import './lib/try_api' import './lib/card_tabs' +import './lib/network_selector' diff --git a/apps/block_scout_web/assets/js/lib/network_selector.js b/apps/block_scout_web/assets/js/lib/network_selector.js new file mode 100644 index 0000000000..f7765329a0 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/network_selector.js @@ -0,0 +1,71 @@ +import $ from 'jquery' + +$(function () { + const mainBody = $('body') + const showNetworkSelector = $('.js-show-network-selector') + const hideNetworkSelector = $('.js-network-selector-close') + const networkSelector = $('.js-network-selector') + const networkSelectorOverlay = $('.js-network-selector-overlay') + const networkSelectorTab = $('.js-network-selector-tab') + const networkSelectorTabContent = $('.js-network-selector-tab-content') + const networkSelectorItemURL = $('.js-network-selector-item-url') + const FADE_IN_DELAY = 250 + + showNetworkSelector.on('click', (e) => { + e.preventDefault() + openNetworkSelector() + }) + + hideNetworkSelector.on('click', (e) => { + e.preventDefault() + closeNetworkSelector() + }) + + networkSelectorTab.on('click', function (e) { + e.preventDefault() + setNetworkTab($(this)) + }) + + networkSelectorItemURL.on('click', function (e) { + window.location = $(this).attr('network-selector-item-url') + }) + + let setNetworkTab = (currentTab) => { + if (currentTab.hasClass('active')) return + + networkSelectorTab.removeClass('active') + currentTab.addClass('active') + networkSelectorTabContent.removeClass('active') + $(`[network-selector-tab="${currentTab.attr('network-selector-tab-filter')}"]`).addClass('active') + } + + let openNetworkSelector = () => { + mainBody.addClass('network-selector-visible') + networkSelectorOverlay.fadeIn(FADE_IN_DELAY) + setNetworkSelectorVisiblePosition() + } + + let closeNetworkSelector = () => { + mainBody.removeClass('network-selector-visible') + networkSelectorOverlay.fadeOut(FADE_IN_DELAY) + setNetworkSelectorHiddenPosition() + } + + let getNetworkSelectorWidth = () => { + return parseInt(networkSelector.css('width')) || parseInt(networkSelector.css('max-width')) + } + + let setNetworkSelectorHiddenPosition = () => { + return networkSelector.css({ 'right': `-${getNetworkSelectorWidth()}px` }) + } + + let setNetworkSelectorVisiblePosition = () => { + return networkSelector.css({ 'right': '0' }) + } + + let init = () => { + setNetworkSelectorHiddenPosition() + } + + init() +}) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex index 042fc2135e..eadf1b4503 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex @@ -1,51 +1,55 @@ -
-
-
- - - -
-
-

<%= gettext("Change Network") %>

-

<%= gettext("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.") %>

-
-
- - - - -
-
-
<%= gettext("All") %>
-
<%= gettext("Mainnet") %>
-
<%= gettext("Testnet") %>
-
<%= gettext("Favorites") %>
-
-
-
- <%= for %{url: url, title: title} <- other_networks() do %> - <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Mainnet" %> - <% end %> +
+
+
+
+ + +
- <% main_nets = main_nets(other_networks()) %> -
- <%= for %{url: url, title: title} <- main_nets do %> - <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: assigns[:tab_type] %> - <% end %> +
+

<%= gettext("Change Network") %>

+

<%= gettext("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.") %>

- <% test_nets = test_nets(other_networks()) %> -
- <%= for %{url: url, title: title} <- test_nets do %> - <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: assigns[:tab_type] %> - <% end %> +
+ + + + +
+
+
<%= gettext("All") %>
+
<%= gettext("Mainnet") %>
+
<%= gettext("Testnet") %>
+
<%= gettext("Favorites") %>
-
- No content. +
+ <% main_nets = main_nets(other_networks()) %> + <% test_nets = test_nets(other_networks()) %> +
+ <%= for %{url: url, title: title} <- main_nets do %> + <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Mainnet" %> + <% end %> + <%= for %{url: url, title: title} <- test_nets do %> + <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Testnet" %> + <% end %> +
+
+ <%= for %{url: url, title: title} <- main_nets do %> + <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Mainnet" %> + <% end %> +
+
+ <%= for %{url: url, title: title} <- test_nets do %> + <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Testnet" %> + <% end %> +
+
+
No content.
+
+
+
+
- -
-
-
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex index 2636542e4e..a798178455 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex @@ -1,5 +1,5 @@
-
From 094b46f7cb775fb73a4202d1c8943373acc87a4c Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Tue, 28 May 2019 14:15:35 -0300 Subject: [PATCH 03/90] (update) changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34eaca672a..fdb6bdfc16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Current ### Features +- [#2044](https://github.com/poanetwork/blockscout/pull/2044) - New network selector. - [#1963](https://github.com/poanetwork/blockscout/pull/1963), [#1959](https://github.com/poanetwork/blockscout/pull/1959), [#1948](https://github.com/poanetwork/blockscout/pull/1948), [#1936](https://github.com/poanetwork/blockscout/pull/1936), [#1925](https://github.com/poanetwork/blockscout/pull/1925), [#1922](https://github.com/poanetwork/blockscout/pull/1922), [#1903](https://github.com/poanetwork/blockscout/pull/1903), [#1874](https://github.com/poanetwork/blockscout/pull/1874), [#1895](https://github.com/poanetwork/blockscout/pull/1895), [#2031](https://github.com/poanetwork/blockscout/pull/2031) - added new themes and logos for poa, eth, rinkeby, goerli, ropsten, kovan, sokol, xdai, etc, rsk - [#2010](https://github.com/poanetwork/blockscout/pull/2010) - added "block not found" and "tx not found pages" - [#1928](https://github.com/poanetwork/blockscout/pull/1928) - pagination styles were updated From 8ae7e32ed7372601d6825bd0867c11489354ddb5 Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Tue, 28 May 2019 14:17:00 -0300 Subject: [PATCH 04/90] (update) internationalization --- apps/block_scout_web/priv/gettext/default.pot | 44 +++++++++++++++++-- .../priv/gettext/en/LC_MESSAGES/default.po | 44 +++++++++++++++++-- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index c7c75a3770..6c35a0f4a4 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -112,6 +112,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -674,8 +675,8 @@ msgid "Responses" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:97 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 msgid "Search" msgstr "" @@ -1540,8 +1541,8 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:105 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:91 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:95 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1720,3 +1721,38 @@ msgstr "" #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:17 msgid "There are no token transfers for this transaction" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 +msgid "Change Network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +msgid "Favorites" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 +msgid "Mainnet" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 +msgid "Search network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:51 +msgid "Show More Networks" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +msgid "Testnet" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 844c8db298..f97921888d 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -112,6 +112,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -674,8 +675,8 @@ msgid "Responses" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:97 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 msgid "Search" msgstr "" @@ -1540,8 +1541,8 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:105 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:91 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:95 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1720,3 +1721,38 @@ msgstr "" #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:17 msgid "There are no token transfers for this transaction" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 +msgid "Change Network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +msgid "Favorites" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 +msgid "Mainnet" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 +msgid "Search network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:51 +msgid "Show More Networks" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +msgid "Testnet" +msgstr "" From 4462646c903806cbb66bb289a70a7460009fa414 Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Mon, 3 Jun 2019 10:38:04 -0300 Subject: [PATCH 05/90] (update) internationalization files --- apps/block_scout_web/priv/gettext/default.pot | 44 +++++++++++++++++-- .../priv/gettext/en/LC_MESSAGES/default.po | 44 +++++++++++++++++-- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 2caf439866..53ae7cdbb1 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -112,6 +112,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -675,8 +676,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:97 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 msgid "Search" msgstr "" @@ -1541,8 +1542,8 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:105 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:91 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:95 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1726,3 +1727,38 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:12 msgid "Topic" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 +msgid "Change Network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +msgid "Favorites" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 +msgid "Mainnet" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 +msgid "Search network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:51 +msgid "Show More Networks" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +msgid "Testnet" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 6bbaa0f4b7..bb19f262ab 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -112,6 +112,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -675,8 +676,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:97 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 msgid "Search" msgstr "" @@ -1541,8 +1542,8 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:105 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:91 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:95 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1726,3 +1727,38 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:12 msgid "Topic" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 +msgid "Change Network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +msgid "Favorites" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 +msgid "Mainnet" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 +msgid "Search network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:51 +msgid "Show More Networks" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +msgid "Testnet" +msgstr "" From a88e3d42433015423733e7c08bc972ff3024672b Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Mon, 3 Jun 2019 12:47:59 -0300 Subject: [PATCH 06/90] (update) language files --- apps/block_scout_web/priv/gettext/default.pot | 44 ++++++++++++-- .../priv/gettext/en/LC_MESSAGES/default.po | 58 +++++++++++++++---- 2 files changed, 87 insertions(+), 15 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index c6d41cb84a..7dca514693 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -112,6 +112,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -665,8 +666,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:97 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 msgid "Search" msgstr "" @@ -1481,8 +1482,8 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:105 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:91 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:95 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1694,3 +1695,38 @@ msgstr "" #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:3 msgid "New Smart Contract Verification" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 +msgid "Change Network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +msgid "Favorites" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 +msgid "Mainnet" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 +msgid "Search network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:51 +msgid "Show More Networks" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +msgid "Testnet" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 59a612407d..12e0d15bb0 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -112,6 +112,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -207,8 +208,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/address/overview.html.eex:142 -#: lib/block_scout_web/templates/address/overview.html.eex:150 +#: lib/block_scout_web/templates/address/overview.html.eex:144 +#: lib/block_scout_web/templates/address/overview.html.eex:152 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114 msgid "Close" @@ -624,7 +625,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:33 -#: lib/block_scout_web/templates/address/overview.html.eex:141 +#: lib/block_scout_web/templates/address/overview.html.eex:143 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105 msgid "QR Code" @@ -665,8 +666,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:97 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 msgid "Search" msgstr "" @@ -1481,8 +1482,8 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:105 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:91 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:95 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1667,12 +1668,12 @@ msgstr "" msgid "ABI-encoded Constructor Arguments (if required by the contract)" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87 msgid "Enter the Solidity Contract Code" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:127 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:149 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:171 @@ -1681,7 +1682,7 @@ msgstr "" msgid "Library Address" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:117 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:139 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:161 @@ -1690,7 +1691,42 @@ msgstr "" msgid "Library Name" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:3 msgid "New Smart Contract Verification" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 +msgid "Change Network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +msgid "Favorites" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 +msgid "Mainnet" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 +msgid "Search network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:51 +msgid "Show More Networks" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +msgid "Testnet" +msgstr "" From 82a977033cf643365eebd01cf5bf2ef3e3003bb7 Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Mon, 3 Jun 2019 12:53:53 -0300 Subject: [PATCH 07/90] (fix) main container position when the network selector is showing --- .../assets/css/components/_network-selector.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/block_scout_web/assets/css/components/_network-selector.scss b/apps/block_scout_web/assets/css/components/_network-selector.scss index e4305befb9..4b1068fab4 100644 --- a/apps/block_scout_web/assets/css/components/_network-selector.scss +++ b/apps/block_scout_web/assets/css/components/_network-selector.scss @@ -8,7 +8,11 @@ $network-selector-tab-active-border-color: $primary !default; $network-selector-item-icon-dimensions: 30px !default; .network-selector-visible { + bottom: 0; + left: 0; position: fixed; + right: 0; + top: 0; } .network-selector-overlay { From 1d90f370d502a15a8677e2842dc1fc20ed947f80 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 5 Jun 2019 13:33:27 +0300 Subject: [PATCH 08/90] add prototype to export transactions as csv --- .../chain/address_transaction_csv_exporter.ex | 72 +++++++++++++++++++ apps/explorer/mix.exs | 2 + .../address_transaction_csv_exporter_test.exs | 43 +++++++++++ mix.lock | 2 + 4 files changed, 119 insertions(+) create mode 100644 apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex create mode 100644 apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex new file mode 100644 index 0000000000..66d47bba5b --- /dev/null +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -0,0 +1,72 @@ +defmodule Explorer.Chain.AddressTransactionCsvExporter do + alias Explorer.Chain + alias Explorer.Chain.{Address, Transaction} + + @necessity_by_association [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [token_transfers: :token] => :optional, + [token_transfers: :to_address] => :optional, + [token_transfers: :from_address] => :optional, + [token_transfers: :token_contract_address] => :optional, + :block => :required + } + ] + + def export(address) do + address + |> Chain.address_to_transactions_with_rewards(@necessity_by_association) + |> to_csv_format(address) + |> dump_data_to_csv() + end + + defp dump_data_to_csv(transactions) do + {:ok, path} = Briefly.create() + + transactions + |> NimbleCSV.RFC4180.dump_to_stream() + |> Stream.into(File.stream!(path)) + |> Enum.to_list() + + path + end + + defp to_csv_format(transactions, address) do + # , "ETHCurrentPrice", "ETHPriceAtTxDate", "TxFee", "Status", "ErrCode"] + row_names = [ + "TxHash", + "BlockNumber", + "UnixTimestamp", + "FromAddress", + "ToAddress", + "ContractAddress", + "Type", + "Value" + ] + + transaction_lists = + transactions + |> Stream.map(fn transaction -> + [ + to_string(transaction.hash), + transaction.block_number, + transaction.block.timestamp, + to_string(transaction.from_address), + to_string(transaction.to_address), + to_string(transaction.created_contract_address), + type(transaction, address), + 1 + ] + end) + + Stream.concat([row_names], transaction_lists) + end + + defp type(%Transaction{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT" + + defp type(%Transaction{to_address_hash: to_address}, %Address{hash: to_address}), do: "IN" + + defp type(_, _), do: "" +end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index ee233e3386..2e0bd82138 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -65,6 +65,7 @@ defmodule Explorer.Mixfile do {:benchee, "~> 0.13.1", only: :test}, # CSV output for benchee {:benchee_csv, "~> 0.8.0", only: :test}, + {:briefly, "~> 0.4", github: "CargoSense/briefly"}, {:bypass, "~> 1.0", only: :test}, {:comeonin, "~> 4.0"}, {:credo, "1.0.0", only: :test, runtime: false}, @@ -91,6 +92,7 @@ defmodule Explorer.Mixfile do {:math, "~> 0.3.0"}, {:mock, "~> 0.3.0", only: [:test], runtime: false}, {:mox, "~> 0.4", only: [:test]}, + {:nimble_csv, "~> 0.6.0"}, {:poison, "~> 3.1"}, {:postgrex, ">= 0.0.0"}, # For compatibility with `prometheus_process_collector`, which hasn't been updated yet diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs new file mode 100644 index 0000000000..40987e1afc --- /dev/null +++ b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs @@ -0,0 +1,43 @@ +defmodule Explorer.Chain.AddressTransactionCsvExporterTest do + use Explorer.DataCase + + alias Explorer.Chain.AddressTransactionCsvExporter + + describe "export/1" do + test "exports address transactions to csv" do + address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + |> Repo.preload(:token_transfers) + + [result] = + address + |> AddressTransactionCsvExporter.export() + |> File.stream!() + |> NimbleCSV.RFC4180.parse_stream() + |> Stream.map(fn [hash, block_number, timestamp, from_address, to_address, created_address, type, value] -> + %{ + hash: hash, + block_number: block_number, + timestamp: timestamp, + from_address: from_address, + to_address: to_address, + created_address: created_address, + type: type, + value: value + } + end) + |> Enum.to_list() + + assert result.block_number == to_string(transaction.block_number) + assert result.created_address == to_string(transaction.created_contract_address_hash) + assert result.from_address == to_string(transaction.from_address) + assert result.to_address == to_string(transaction.to_address) + assert result.hash == to_string(transaction.hash) + assert result.type == "OUT" + end + end +end diff --git a/mix.lock b/mix.lock index df1be8c242..efc77457a2 100644 --- a/mix.lock +++ b/mix.lock @@ -11,6 +11,7 @@ "benchee": {:hex, :benchee, "0.13.2", "30cd4ff5f593fdd218a9b26f3c24d580274f297d88ad43383afe525b1543b165", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, "benchee_csv": {:hex, :benchee_csv, "0.8.0", "0ca094677d6e2b2f601b7ee7864b754789ef9d24d079432e5e3d6f4fb83a4d80", [:mix], [{:benchee, "~> 0.12", [hex: :benchee, optional: false]}, {:csv, "~> 2.0", [hex: :csv, optional: false]}]}, "binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], []}, + "briefly": {:git, "https://github.com/CargoSense/briefly.git", "2526e9674a4e6996137e066a1295ea60962712b8", []}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []}, "bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm"}, "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, @@ -72,6 +73,7 @@ "mock": {:hex, :mock, "0.3.2", "e98e998fd76c191c7e1a9557c8617912c53df3d4a6132f561eb762b699ef59fa", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "mox": {:hex, :mox, "0.4.0", "7f120840f7d626184a3d65de36189ca6f37d432e5d63acd80045198e4c5f7e6e", [:mix], [], "hexpm"}, "msgpax": {:hex, :msgpax, "2.2.2", "559a07806bbe5fdd31e4597455be030bd96356f7a621ca9eed832747c83bfb67", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"}, + "nimble_csv": {:hex, :nimble_csv, "0.6.0", "a3673f26d41f986774fe6060e309615343d3cb83a6d435754d8b1fdbd5764879", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, "optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm"}, "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], []}, From b2b0be905e63bcbcb3acfb5d8ec082a780b549e0 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 5 Jun 2019 15:22:38 +0300 Subject: [PATCH 09/90] fetch all transactions for csv export --- .../chain/address_transaction_csv_exporter.ex | 24 +++++++++++++- .../address_transaction_csv_exporter_test.exs | 33 +++++++++++++++++++ apps/indexer/mix.exs | 2 +- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index 66d47bba5b..01dccaf3e2 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -1,6 +1,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do alias Explorer.Chain alias Explorer.Chain.{Address, Transaction} + alias Explorer.PagingOptions @necessity_by_association [ necessity_by_association: %{ @@ -15,13 +16,34 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do } ] + @page_size 150 + + @paging_options %PagingOptions{page_size: @page_size + 1} + def export(address) do address - |> Chain.address_to_transactions_with_rewards(@necessity_by_association) + |> fetch_all_transactions(@paging_options) |> to_csv_format(address) |> dump_data_to_csv() end + defp fetch_all_transactions(address, paging_options, acc \\ []) do + options = Keyword.merge(@necessity_by_association, paging_options: paging_options) + + transactions = Chain.address_to_transactions_with_rewards(address, options) + + new_acc = transactions ++ acc + + case Enum.split(transactions, @page_size) do + {_, _transactions, [%Transaction{block_number: block_number, index: index}]} -> + new_paging_options = %{@paging_options | key: {block_number, index}} + fetch_all_transactions(address, new_paging_options, new_acc) + + {_, []} -> + new_acc + end + end + defp dump_data_to_csv(transactions) do {:ok, path} = Briefly.create() diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs index 40987e1afc..ddff5e6dea 100644 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs @@ -39,5 +39,38 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do assert result.hash == to_string(transaction.hash) assert result.type == "OUT" end + + test "fetches all transactions" do + address = insert(:address) + + 1..200 + |> Enum.map(fn _ -> + :transaction + |> insert(from_address: address) + |> with_block() + end) + |> Enum.count() + + result = + address + |> AddressTransactionCsvExporter.export() + |> File.stream!() + |> NimbleCSV.RFC4180.parse_stream() + |> Stream.map(fn [hash, block_number, timestamp, from_address, to_address, created_address, type, value] -> + %{ + hash: hash, + block_number: block_number, + timestamp: timestamp, + from_address: from_address, + to_address: to_address, + created_address: created_address, + type: type, + value: value + } + end) + |> Enum.to_list() + + assert Enum.count(result) == 200 + end end end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index 9b8bd4d6ed..f70d758912 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -49,7 +49,7 @@ defmodule Indexer.MixProject do # JSONRPC access to Parity for `Explorer.Indexer` {:ethereum_jsonrpc, in_umbrella: true}, # RLP encoding - {:ex_rlp, "~> 0.3"}, + {:ex_rlp, "~> 0.5.2"}, # Code coverage {:excoveralls, "~> 0.10.0", only: [:test], github: "KronicDeth/excoveralls", branch: "circle-workflows"}, # Importing to database From ac56c61784950778bcdc7f8fcaac2569767f0da1 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 5 Jun 2019 15:37:12 +0300 Subject: [PATCH 10/90] fix pattern matching --- .../lib/explorer/chain/address_transaction_csv_exporter.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index 01dccaf3e2..bb0ef9f636 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -35,7 +35,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do new_acc = transactions ++ acc case Enum.split(transactions, @page_size) do - {_, _transactions, [%Transaction{block_number: block_number, index: index}]} -> + {_transactions, [%Transaction{block_number: block_number, index: index}]} -> new_paging_options = %{@paging_options | key: {block_number, index}} fetch_all_transactions(address, new_paging_options, new_acc) From 78a501066231a25696f6a0c4920b6eda4db8193c Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 5 Jun 2019 15:44:03 +0300 Subject: [PATCH 11/90] fix credo --- .../chain/address_transaction_csv_exporter.ex | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index bb0ef9f636..54bf38df6f 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -1,7 +1,11 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do - alias Explorer.Chain + @moduledoc """ + Exports transactions to a csv file. + """ + + alias Explorer.{Chain, PagingOptions} alias Explorer.Chain.{Address, Transaction} - alias Explorer.PagingOptions + alias NimbleCSV.RFC4180 @necessity_by_association [ necessity_by_association: %{ @@ -20,6 +24,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do @paging_options %PagingOptions{page_size: @page_size + 1} + @spec export(Address.t()) :: String.t() def export(address) do address |> fetch_all_transactions(@paging_options) @@ -47,10 +52,11 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do defp dump_data_to_csv(transactions) do {:ok, path} = Briefly.create() - transactions - |> NimbleCSV.RFC4180.dump_to_stream() - |> Stream.into(File.stream!(path)) - |> Enum.to_list() + _ = + transactions + |> RFC4180.dump_to_stream() + |> Stream.into(File.stream!(path)) + |> Enum.to_list() path end From 4ea5e97306562523411b13049cbbcd221b843762 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 5 Jun 2019 16:23:01 +0300 Subject: [PATCH 12/90] add additional fields to csv file --- .../chain/address_transaction_csv_exporter.ex | 8 +++-- .../address_transaction_csv_exporter_test.exs | 32 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index 54bf38df6f..e115827865 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -71,7 +71,9 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do "ToAddress", "ContractAddress", "Type", - "Value" + "Value", + "Status", + "ErrCode" ] transaction_lists = @@ -85,7 +87,9 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do to_string(transaction.to_address), to_string(transaction.created_contract_address), type(transaction, address), - 1 + 1, + transaction.status, + transaction.error ] end) diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs index ddff5e6dea..10a6f60833 100644 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs @@ -18,7 +18,18 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do |> AddressTransactionCsvExporter.export() |> File.stream!() |> NimbleCSV.RFC4180.parse_stream() - |> Stream.map(fn [hash, block_number, timestamp, from_address, to_address, created_address, type, value] -> + |> Stream.map(fn [ + hash, + block_number, + timestamp, + from_address, + to_address, + created_address, + type, + value, + status, + error + ] -> %{ hash: hash, block_number: block_number, @@ -27,7 +38,9 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do to_address: to_address, created_address: created_address, type: type, - value: value + value: value, + status: status, + error: error } end) |> Enum.to_list() @@ -38,6 +51,9 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do assert result.to_address == to_string(transaction.to_address) assert result.hash == to_string(transaction.hash) assert result.type == "OUT" + assert result.value == "1" + assert result.status == to_string(transaction.status) + assert result.error == to_string(transaction.error) end test "fetches all transactions" do @@ -56,18 +72,6 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do |> AddressTransactionCsvExporter.export() |> File.stream!() |> NimbleCSV.RFC4180.parse_stream() - |> Stream.map(fn [hash, block_number, timestamp, from_address, to_address, created_address, type, value] -> - %{ - hash: hash, - block_number: block_number, - timestamp: timestamp, - from_address: from_address, - to_address: to_address, - created_address: created_address, - type: type, - value: value - } - end) |> Enum.to_list() assert Enum.count(result) == 200 From 228a77db4870932c7a9e293f173651e2386fe8c4 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 5 Jun 2019 16:29:32 +0300 Subject: [PATCH 13/90] add real transaction value --- .../lib/explorer/chain/address_transaction_csv_exporter.ex | 4 ++-- .../explorer/chain/address_transaction_csv_exporter_test.exs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index e115827865..1c6c7c82d9 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -4,7 +4,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do """ alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.{Address, Transaction} + alias Explorer.Chain.{Address, Transaction, Wei} alias NimbleCSV.RFC4180 @necessity_by_association [ @@ -87,7 +87,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do to_string(transaction.to_address), to_string(transaction.created_contract_address), type(transaction, address), - 1, + Wei.to(transaction.value, :wei), transaction.status, transaction.error ] diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs index 10a6f60833..0f84ee3d8e 100644 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs @@ -1,7 +1,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do use Explorer.DataCase - alias Explorer.Chain.AddressTransactionCsvExporter + alias Explorer.Chain.{AddressTransactionCsvExporter, Wei} describe "export/1" do test "exports address transactions to csv" do @@ -51,7 +51,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do assert result.to_address == to_string(transaction.to_address) assert result.hash == to_string(transaction.hash) assert result.type == "OUT" - assert result.value == "1" + assert result.value == transaction.value |> Wei.to(:wei) |> to_string() assert result.status == to_string(transaction.status) assert result.error == to_string(transaction.error) end From ba8ae89db69fd14c4b259603a413d0cb3330e43f Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 6 Jun 2019 10:40:00 +0300 Subject: [PATCH 14/90] add fee --- .../chain/address_transaction_csv_exporter.ex | 11 +++++++++++ .../chain/address_transaction_csv_exporter_test.exs | 3 +++ 2 files changed, 14 insertions(+) diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index 1c6c7c82d9..0486e9a6c7 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -72,6 +72,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do "ContractAddress", "Type", "Value", + "Fee", "Status", "ErrCode" ] @@ -88,6 +89,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do to_string(transaction.created_contract_address), type(transaction, address), Wei.to(transaction.value, :wei), + fee(transaction), transaction.status, transaction.error ] @@ -101,4 +103,13 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do defp type(%Transaction{to_address_hash: to_address}, %Address{hash: to_address}), do: "IN" defp type(_, _), do: "" + + defp fee(transaction) do + transaction + |> Chain.fee(:wei) + |> case do + {:actual, value} -> value + {:maximum, value} -> "Max of #{value}" + end + end end diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs index 0f84ee3d8e..b87737e1d2 100644 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs @@ -27,6 +27,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do created_address, type, value, + fee, status, error ] -> @@ -39,6 +40,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do created_address: created_address, type: type, value: value, + fee: fee, status: status, error: error } @@ -52,6 +54,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do assert result.hash == to_string(transaction.hash) assert result.type == "OUT" assert result.value == transaction.value |> Wei.to(:wei) |> to_string() + assert result.fee assert result.status == to_string(transaction.status) assert result.error == to_string(transaction.error) end From e96d53a752244a6d93816698faf3cb1c58d5db10 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 6 Jun 2019 10:57:38 +0300 Subject: [PATCH 15/90] add current token price --- .../chain/address_transaction_csv_exporter.ex | 15 ++++++++++----- .../address_transaction_csv_exporter_test.exs | 7 +++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index 0486e9a6c7..6590f8e3b6 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -3,8 +3,9 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do Exports transactions to a csv file. """ - alias Explorer.{Chain, PagingOptions} + alias Explorer.{Chain, Market, PagingOptions} alias Explorer.Chain.{Address, Transaction, Wei} + alias Explorer.ExchangeRates.Token alias NimbleCSV.RFC4180 @necessity_by_association [ @@ -26,9 +27,11 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do @spec export(Address.t()) :: String.t() def export(address) do + exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + address |> fetch_all_transactions(@paging_options) - |> to_csv_format(address) + |> to_csv_format(address, exchange_rate) |> dump_data_to_csv() end @@ -61,7 +64,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do path end - defp to_csv_format(transactions, address) do + defp to_csv_format(transactions, address, exchange_rate) do # , "ETHCurrentPrice", "ETHPriceAtTxDate", "TxFee", "Status", "ErrCode"] row_names = [ "TxHash", @@ -74,7 +77,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do "Value", "Fee", "Status", - "ErrCode" + "ErrCode", + "CurrentPrice" ] transaction_lists = @@ -91,7 +95,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do Wei.to(transaction.value, :wei), fee(transaction), transaction.status, - transaction.error + transaction.error, + exchange_rate.usd_value ] end) diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs index b87737e1d2..a450cc7f87 100644 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs @@ -29,7 +29,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do value, fee, status, - error + error, + cur_price ] -> %{ hash: hash, @@ -42,7 +43,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do value: value, fee: fee, status: status, - error: error + error: error, + current_price: cur_price } end) |> Enum.to_list() @@ -57,6 +59,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do assert result.fee assert result.status == to_string(transaction.status) assert result.error == to_string(transaction.error) + assert result.current_price end test "fetches all transactions" do From 4bfc37ba2c4d76e6758f1e0587e541d8ce99f10c Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 6 Jun 2019 11:41:49 +0300 Subject: [PATCH 16/90] add opening and closing prices --- .../chain/address_transaction_csv_exporter.ex | 33 +++++++++++++++++-- .../address_transaction_csv_exporter_test.exs | 11 +++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index 6590f8e3b6..4bdd965579 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -3,7 +3,13 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do Exports transactions to a csv file. """ - alias Explorer.{Chain, Market, PagingOptions} + import Ecto.Query, + only: [ + from: 2 + ] + + alias Explorer.{Chain, Market, PagingOptions, Repo} + alias Explorer.Market.MarketHistory alias Explorer.Chain.{Address, Transaction, Wei} alias Explorer.ExchangeRates.Token alias NimbleCSV.RFC4180 @@ -78,12 +84,16 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do "Fee", "Status", "ErrCode", - "CurrentPrice" + "CurrentPrice", + "TxDateOpeningPrice", + "TxDateClosingPrice" ] transaction_lists = transactions |> Stream.map(fn transaction -> + {opening_price, closing_price} = price_at_date(transaction.block.timestamp) + [ to_string(transaction.hash), transaction.block_number, @@ -96,7 +106,9 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do fee(transaction), transaction.status, transaction.error, - exchange_rate.usd_value + exchange_rate.usd_value, + opening_price, + closing_price ] end) @@ -117,4 +129,19 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do {:maximum, value} -> "Max of #{value}" end end + + defp price_at_date(datetime) do + date = DateTime.to_date(datetime) + + query = + from( + mh in MarketHistory, + where: mh.date == ^date + ) + + case Repo.one(query) do + nil -> {nil, nil} + price -> {price.opening_price, price.closing_price} + end + end end diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs index a450cc7f87..1150434a40 100644 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs @@ -30,7 +30,9 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do fee, status, error, - cur_price + cur_price, + op_price, + cl_price ] -> %{ hash: hash, @@ -44,12 +46,15 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do fee: fee, status: status, error: error, - current_price: cur_price + current_price: cur_price, + opening_price: op_price, + closing_price: cl_price } end) |> Enum.to_list() assert result.block_number == to_string(transaction.block_number) + assert result.timestamp assert result.created_address == to_string(transaction.created_contract_address_hash) assert result.from_address == to_string(transaction.from_address) assert result.to_address == to_string(transaction.to_address) @@ -60,6 +65,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do assert result.status == to_string(transaction.status) assert result.error == to_string(transaction.error) assert result.current_price + assert result.opening_price + assert result.closing_price end test "fetches all transactions" do From f984c9129edebf266e0912efad3e7af992448f37 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 6 Jun 2019 14:03:04 +0300 Subject: [PATCH 17/90] create endpoing with csv --- .../address_transaction_controller.ex | 21 ++++ .../lib/block_scout_web/router.ex | 2 + .../address_transaction_controller_test.exs | 18 ++++ .../chain/address_transaction_csv_exporter.ex | 100 ++++++++---------- .../address_transaction_csv_exporter_test.exs | 52 +++++---- 5 files changed, 118 insertions(+), 75 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index da22b8c042..fe3681cf3e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -10,6 +10,7 @@ defmodule BlockScoutWeb.AddressTransactionController do alias BlockScoutWeb.TransactionView alias Explorer.{Chain, Market} + alias Explorer.Chain.AddressTransactionCsvExporter alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -106,4 +107,24 @@ defmodule BlockScoutWeb.AddressTransactionController do not_found(conn) end end + + def transactions_csv(conn, %{"address_id" => address_hash_string}) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + address + |> AddressTransactionCsvExporter.export() + |> Enum.into( + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=transactions.csv") + |> send_chunked(200) + ) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index aa19725811..8d9d057c3f 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -240,6 +240,8 @@ defmodule BlockScoutWeb.Router do get("/search_logs", AddressLogsController, :search_logs) + get("/transactions_csv", AddressTransactionController, :transactions_csv) + get("/token_autocomplete", ChainController, :token_autocomplete) get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs index 028387501a..b5b6e7280b 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs @@ -132,4 +132,22 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do end) end end + + describe "GET transactions_csv/2" do + test "download csv file with transactions", %{conn: conn} do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block() + + :transaction + |> insert(from_address: address) + |> with_block() + + conn = get(conn, "/transactions_csv", %{"address_id" => to_string(address.hash)}) + + assert conn.resp_body |> String.split("\n") |> Enum.count() == 3 + end + end end diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index 4bdd965579..09d0b83dbc 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -31,14 +31,14 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do @paging_options %PagingOptions{page_size: @page_size + 1} - @spec export(Address.t()) :: String.t() + @spec export(Address.t()) :: Enumerable.t() def export(address) do exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() address |> fetch_all_transactions(@paging_options) |> to_csv_format(address, exchange_rate) - |> dump_data_to_csv() + |> dump_to_stream() end defp fetch_all_transactions(address, paging_options, acc \\ []) do @@ -58,61 +58,53 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do end end - defp dump_data_to_csv(transactions) do - {:ok, path} = Briefly.create() - - _ = - transactions - |> RFC4180.dump_to_stream() - |> Stream.into(File.stream!(path)) - |> Enum.to_list() - - path + defp dump_to_stream(transactions) do + transactions + |> RFC4180.dump_to_stream() end defp to_csv_format(transactions, address, exchange_rate) do - # , "ETHCurrentPrice", "ETHPriceAtTxDate", "TxFee", "Status", "ErrCode"] - row_names = [ - "TxHash", - "BlockNumber", - "UnixTimestamp", - "FromAddress", - "ToAddress", - "ContractAddress", - "Type", - "Value", - "Fee", - "Status", - "ErrCode", - "CurrentPrice", - "TxDateOpeningPrice", - "TxDateClosingPrice" - ] - - transaction_lists = - transactions - |> Stream.map(fn transaction -> - {opening_price, closing_price} = price_at_date(transaction.block.timestamp) - - [ - to_string(transaction.hash), - transaction.block_number, - transaction.block.timestamp, - to_string(transaction.from_address), - to_string(transaction.to_address), - to_string(transaction.created_contract_address), - type(transaction, address), - Wei.to(transaction.value, :wei), - fee(transaction), - transaction.status, - transaction.error, - exchange_rate.usd_value, - opening_price, - closing_price - ] - end) - - Stream.concat([row_names], transaction_lists) + # row_names = [ + # "TxHash", + # "BlockNumber", + # "UnixTimestamp", + # "FromAddress", + # "ToAddress", + # "ContractAddress", + # "Type", + # "Value", + # "Fee", + # "Status", + # "ErrCode", + # "CurrentPrice", + # "TxDateOpeningPrice", + # "TxDateClosingPrice" + # ] + + # transaction_lists = + transactions + |> Stream.map(fn transaction -> + {opening_price, closing_price} = price_at_date(transaction.block.timestamp) + + [ + to_string(transaction.hash), + transaction.block_number, + transaction.block.timestamp, + to_string(transaction.from_address), + to_string(transaction.to_address), + to_string(transaction.created_contract_address), + type(transaction, address), + Wei.to(transaction.value, :wei), + fee(transaction), + transaction.status, + transaction.error, + exchange_rate.usd_value, + opening_price, + closing_price + ] + end) + + # Stream.concat([row_names], transaction_lists) end defp type(%Transaction{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT" diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs index 1150434a40..a198b59625 100644 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs @@ -16,24 +16,37 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do [result] = address |> AddressTransactionCsvExporter.export() - |> File.stream!() - |> NimbleCSV.RFC4180.parse_stream() - |> Stream.map(fn [ - hash, - block_number, - timestamp, - from_address, - to_address, - created_address, - type, - value, - fee, - status, - error, - cur_price, - op_price, - cl_price - ] -> + |> Enum.to_list() + |> Enum.map(fn [ + hash, + _, + block_number, + _, + timestamp, + _, + from_address, + _, + to_address, + _, + created_address, + _, + type, + _, + value, + _, + fee, + _, + status, + _, + error, + _, + cur_price, + _, + op_price, + _, + cl_price, + _ + ] -> %{ hash: hash, block_number: block_number, @@ -51,7 +64,6 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do closing_price: cl_price } end) - |> Enum.to_list() assert result.block_number == to_string(transaction.block_number) assert result.timestamp @@ -83,8 +95,6 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do result = address |> AddressTransactionCsvExporter.export() - |> File.stream!() - |> NimbleCSV.RFC4180.parse_stream() |> Enum.to_list() assert Enum.count(result) == 200 From 36218f0a6651defb35db01fd2ab4a35e8c59f393 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 6 Jun 2019 14:20:52 +0300 Subject: [PATCH 18/90] add button in UI to download csv --- .../block_scout_web/templates/address_transaction/index.html.eex | 1 + apps/explorer/mix.exs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex index 11b4de110c..87ab7eba56 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex @@ -6,6 +6,7 @@
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
+ to_string(@address.hash)}) %>><%= gettext("Download all transactions as csv") %>
<%= gettext "Connection Lost, click to load newer transactions" %> diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 2e0bd82138..cddef9e45d 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -65,7 +65,6 @@ defmodule Explorer.Mixfile do {:benchee, "~> 0.13.1", only: :test}, # CSV output for benchee {:benchee_csv, "~> 0.8.0", only: :test}, - {:briefly, "~> 0.4", github: "CargoSense/briefly"}, {:bypass, "~> 1.0", only: :test}, {:comeonin, "~> 4.0"}, {:credo, "1.0.0", only: :test, runtime: false}, From d210249a748e90829136758435f0ba1758080a5a Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 6 Jun 2019 14:27:59 +0300 Subject: [PATCH 19/90] add column names --- .../address_transaction_controller_test.exs | 2 +- .../chain/address_transaction_csv_exporter.ex | 82 +++++++++---------- .../address_transaction_csv_exporter_test.exs | 2 + 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs index b5b6e7280b..94ac9a982a 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs @@ -147,7 +147,7 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do conn = get(conn, "/transactions_csv", %{"address_id" => to_string(address.hash)}) - assert conn.resp_body |> String.split("\n") |> Enum.count() == 3 + assert conn.resp_body |> String.split("\n") |> Enum.count() == 4 end end end diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex index 09d0b83dbc..32028e02d3 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex @@ -64,47 +64,47 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do end defp to_csv_format(transactions, address, exchange_rate) do - # row_names = [ - # "TxHash", - # "BlockNumber", - # "UnixTimestamp", - # "FromAddress", - # "ToAddress", - # "ContractAddress", - # "Type", - # "Value", - # "Fee", - # "Status", - # "ErrCode", - # "CurrentPrice", - # "TxDateOpeningPrice", - # "TxDateClosingPrice" - # ] - - # transaction_lists = - transactions - |> Stream.map(fn transaction -> - {opening_price, closing_price} = price_at_date(transaction.block.timestamp) - - [ - to_string(transaction.hash), - transaction.block_number, - transaction.block.timestamp, - to_string(transaction.from_address), - to_string(transaction.to_address), - to_string(transaction.created_contract_address), - type(transaction, address), - Wei.to(transaction.value, :wei), - fee(transaction), - transaction.status, - transaction.error, - exchange_rate.usd_value, - opening_price, - closing_price - ] - end) - - # Stream.concat([row_names], transaction_lists) + row_names = [ + "TxHash", + "BlockNumber", + "UnixTimestamp", + "FromAddress", + "ToAddress", + "ContractAddress", + "Type", + "Value", + "Fee", + "Status", + "ErrCode", + "CurrentPrice", + "TxDateOpeningPrice", + "TxDateClosingPrice" + ] + + transaction_lists = + transactions + |> Stream.map(fn transaction -> + {opening_price, closing_price} = price_at_date(transaction.block.timestamp) + + [ + to_string(transaction.hash), + transaction.block_number, + transaction.block.timestamp, + to_string(transaction.from_address), + to_string(transaction.to_address), + to_string(transaction.created_contract_address), + type(transaction, address), + Wei.to(transaction.value, :wei), + fee(transaction), + transaction.status, + transaction.error, + exchange_rate.usd_value, + opening_price, + closing_price + ] + end) + + Stream.concat([row_names], transaction_lists) end defp type(%Transaction{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT" diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs index a198b59625..1f722abf87 100644 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs @@ -17,6 +17,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do address |> AddressTransactionCsvExporter.export() |> Enum.to_list() + |> Enum.drop(1) |> Enum.map(fn [ hash, _, @@ -96,6 +97,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do address |> AddressTransactionCsvExporter.export() |> Enum.to_list() + |> Enum.drop(1) assert Enum.count(result) == 200 end From 5bd771c3d36e4d5d66ad5b020f48568b000194d4 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 6 Jun 2019 14:32:51 +0300 Subject: [PATCH 20/90] fix gettext --- apps/block_scout_web/priv/gettext/default.pot | 19 +++++++---- .../priv/gettext/en/LC_MESSAGES/default.po | 33 +++++++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index c6d41cb84a..90c5b8751b 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -111,7 +111,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:23 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:24 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -244,7 +244,7 @@ msgid "Connection Lost, click to load newer internal transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:11 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:12 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:15 #: lib/block_scout_web/templates/transaction/index.html.eex:15 msgid "Connection Lost, click to load newer transactions" @@ -404,7 +404,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:44 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:40 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:41 #: lib/block_scout_web/views/address_internal_transaction_view.ex:7 #: lib/block_scout_web/views/address_transaction_view.ex:7 msgid "From" @@ -742,7 +742,7 @@ msgid "There are no tokens." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:62 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:63 msgid "There are no transactions for this address." msgstr "" @@ -763,7 +763,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:33 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:29 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:30 #: lib/block_scout_web/views/address_internal_transaction_view.ex:6 #: lib/block_scout_web/views/address_transaction_view.ex:6 msgid "To" @@ -867,7 +867,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:3 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:15 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:16 #: lib/block_scout_web/templates/block_transaction/index.html.eex:10 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/chain/show.html.eex:108 @@ -1211,7 +1211,7 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:21 #: lib/block_scout_web/templates/address_token/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:57 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:58 #: lib/block_scout_web/templates/address_validation/index.html.eex:22 #: lib/block_scout_web/templates/block_transaction/index.html.eex:23 #: lib/block_scout_web/templates/chain/show.html.eex:91 @@ -1694,3 +1694,8 @@ msgstr "" #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:3 msgid "New Smart Contract Verification" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_transaction/index.html.eex:9 +msgid "Download all transactions as csv" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 59a612407d..6ffe74c683 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -111,7 +111,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:23 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:24 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -207,8 +207,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/address/overview.html.eex:142 -#: lib/block_scout_web/templates/address/overview.html.eex:150 +#: lib/block_scout_web/templates/address/overview.html.eex:144 +#: lib/block_scout_web/templates/address/overview.html.eex:152 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114 msgid "Close" @@ -244,7 +244,7 @@ msgid "Connection Lost, click to load newer internal transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:11 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:12 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:15 #: lib/block_scout_web/templates/transaction/index.html.eex:15 msgid "Connection Lost, click to load newer transactions" @@ -404,7 +404,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:44 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:40 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:41 #: lib/block_scout_web/views/address_internal_transaction_view.ex:7 #: lib/block_scout_web/views/address_transaction_view.ex:7 msgid "From" @@ -624,7 +624,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:33 -#: lib/block_scout_web/templates/address/overview.html.eex:141 +#: lib/block_scout_web/templates/address/overview.html.eex:143 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105 msgid "QR Code" @@ -742,7 +742,7 @@ msgid "There are no tokens." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:62 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:63 msgid "There are no transactions for this address." msgstr "" @@ -763,7 +763,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:33 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:29 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:30 #: lib/block_scout_web/views/address_internal_transaction_view.ex:6 #: lib/block_scout_web/views/address_transaction_view.ex:6 msgid "To" @@ -867,7 +867,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:3 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:15 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:16 #: lib/block_scout_web/templates/block_transaction/index.html.eex:10 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/chain/show.html.eex:108 @@ -1211,7 +1211,7 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:21 #: lib/block_scout_web/templates/address_token/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:57 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:58 #: lib/block_scout_web/templates/address_validation/index.html.eex:22 #: lib/block_scout_web/templates/block_transaction/index.html.eex:23 #: lib/block_scout_web/templates/chain/show.html.eex:91 @@ -1667,12 +1667,12 @@ msgstr "" msgid "ABI-encoded Constructor Arguments (if required by the contract)" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87 msgid "Enter the Solidity Contract Code" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:127 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:149 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:171 @@ -1681,7 +1681,7 @@ msgstr "" msgid "Library Address" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:117 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:139 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:161 @@ -1690,7 +1690,12 @@ msgstr "" msgid "Library Name" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:3 msgid "New Smart Contract Verification" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_transaction/index.html.eex:9 +msgid "Download all transactions as csv" +msgstr "" From 1b009499ed68ab170cb5535104edc98c08b86eb1 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 11 Jun 2019 14:28:32 +0300 Subject: [PATCH 21/90] export token transfers to csv file --- .../address_token_transfer_csv_exporter.ex | 119 ++++++++++++++++++ apps/explorer/mix.exs | 1 + ...dress_token_transfer_csv_exporter_test.exs | 72 +++++++++++ mix.lock | 1 + 4 files changed, 193 insertions(+) create mode 100644 apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex create mode 100644 apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex new file mode 100644 index 0000000000..2292a23c22 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex @@ -0,0 +1,119 @@ +defmodule Explorer.Chain.AddressTokenTransferCsvExporter do + @moduledoc """ + Exports token transfers to a csv file. + """ + + alias Explorer.{Chain, PagingOptions} + alias Explorer.Chain.{Address, Transaction, TokenTransfer} + alias NimbleCSV.RFC4180 + + @necessity_by_association [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [token_transfers: :token] => :optional, + [token_transfers: :to_address] => :optional, + [token_transfers: :from_address] => :optional, + [token_transfers: :token_contract_address] => :optional, + :block => :required + } + ] + + @page_size 150 + @paging_options %PagingOptions{page_size: @page_size + 1} + + def export(address) do + address + |> fetch_all_transactions(@paging_options) + |> to_token_transfers() + |> to_csv_format(address) + |> dump_to_stream() + end + + defp fetch_all_transactions(address, paging_options, acc \\ []) do + options = Keyword.merge(@necessity_by_association, paging_options: paging_options) + + transactions = + address + |> Chain.address_to_transactions_with_rewards(options) + |> Enum.filter(fn transaction -> Enum.count(transaction.token_transfers) > 0 end) + + new_acc = transactions ++ acc + + case Enum.split(transactions, @page_size) do + {_transactions, [%Transaction{block_number: block_number, index: index}]} -> + new_paging_options = %{@paging_options | key: {block_number, index}} + fetch_all_transactions(address, new_paging_options, new_acc) + + {_, []} -> + new_acc + end + end + + defp to_token_transfers(transactions) do + transactions + |> Enum.flat_map(fn transaction -> + transaction.token_transfers + |> Enum.map(fn transfer -> %{transfer | transaction: transaction} end) + end) + end + + defp dump_to_stream(transactions) do + transactions + |> RFC4180.dump_to_stream() + end + + defp to_csv_format(token_transfers, address) do + row_names = [ + "TxHash", + "BlockNumber", + "UnixTimestamp", + "FromAddress", + "ToAddress", + "TokenContractAddress", + "Type", + "TokenSymbol", + "TokensTransferred", + "TransactionFee", + "Status", + "ErrCode" + ] + + token_transfer_lists = + token_transfers + |> Stream.map(fn token_transfer -> + [ + to_string(token_transfer.transaction_hash), + token_transfer.transaction.block_number, + token_transfer.transaction.block.timestamp, + to_string(token_transfer.from_address), + to_string(token_transfer.to_address), + to_string(token_transfer.token_contract_address), + type(token_transfer, address), + token_transfer.token.symbol, + token_transfer.amount, + fee(token_transfer.transaction), + token_transfer.transaction.status, + token_transfer.transaction.error + ] + end) + + Stream.concat([row_names], token_transfer_lists) + end + + defp type(%TokenTransfer{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT" + + defp type(%TokenTransfer{to_address_hash: to_address}, %Address{hash: to_address}), do: "IN" + + defp type(_, _), do: "" + + defp fee(transaction) do + transaction + |> Chain.fee(:wei) + |> case do + {:actual, value} -> value + {:maximum, value} -> "Max of #{value}" + end + end +end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 09257b9aee..d73e0a9920 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -92,6 +92,7 @@ defmodule Explorer.Mixfile do {:mock, "~> 0.3.0", only: [:test], runtime: false}, {:mox, "~> 0.4", only: [:test]}, {:poison, "~> 3.1"}, + {:nimble_csv, "~> 0.6.0"}, {:postgrex, ">= 0.0.0"}, # For compatibility with `prometheus_process_collector`, which hasn't been updated yet {:prometheus, "~> 4.0", override: true}, diff --git a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs new file mode 100644 index 0000000000..9989290264 --- /dev/null +++ b/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs @@ -0,0 +1,72 @@ +defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do + use Explorer.DataCase + + alias Explorer.Chain.AddressTokenTransferCsvExporter + + describe "export/1" do + test "exports token transfers to csv" do + address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + token_transfer = insert(:token_transfer, transaction: transaction, from_address: address) + + [result] = + address + |> AddressTokenTransferCsvExporter.export() + |> Enum.to_list() + |> Enum.drop(1) + |> Enum.map(fn [ + tx_hash, + _, + block_number, + _, + timestamp, + _, + from_address, + _, + to_address, + _, + token_contract_address, + _, + type, + _, + token_symbol, + _, + tokens_transferred, + _, + transaction_fee, + _, + status, + _, + err_code, + _ + ] -> + %{ + tx_hash: tx_hash, + block_number: block_number, + timestamp: timestamp, + from_address: from_address, + to_address: to_address, + token_contract_address: token_contract_address, + type: type, + token_symbol: token_symbol, + tokens_transferred: tokens_transferred, + transaction_fee: transaction_fee, + status: status, + err_code: err_code + } + end) + + assert result.block_number == to_string(transaction.block_number) + assert result.tx_hash == to_string(transaction.hash) + assert result.from_address == to_string(token_transfer.from_address_hash) + assert result.to_address == to_string(token_transfer.to_address_hash) + assert result.timestamp == to_string(transaction.block.timestamp) + assert result.type == "OUT" + end + end +end diff --git a/mix.lock b/mix.lock index 5bcd8cacf1..d1011c206b 100644 --- a/mix.lock +++ b/mix.lock @@ -73,6 +73,7 @@ "mock": {:hex, :mock, "0.3.2", "e98e998fd76c191c7e1a9557c8617912c53df3d4a6132f561eb762b699ef59fa", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "mox": {:hex, :mox, "0.4.0", "7f120840f7d626184a3d65de36189ca6f37d432e5d63acd80045198e4c5f7e6e", [:mix], [], "hexpm"}, "msgpax": {:hex, :msgpax, "2.2.2", "559a07806bbe5fdd31e4597455be030bd96356f7a621ca9eed832747c83bfb67", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"}, + "nimble_csv": {:hex, :nimble_csv, "0.6.0", "a3673f26d41f986774fe6060e309615343d3cb83a6d435754d8b1fdbd5764879", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, "optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm"}, "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], []}, From 3b29b6b04908010b3075efbba5d08ceb0cb8009a Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 11 Jun 2019 14:35:46 +0300 Subject: [PATCH 22/90] fix credo --- .../lib/explorer/chain/address_token_transfer_csv_exporter.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex index 2292a23c22..4073245383 100644 --- a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex @@ -4,7 +4,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do """ alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.{Address, Transaction, TokenTransfer} + alias Explorer.Chain.{Address, TokenTransfer, Transaction} alias NimbleCSV.RFC4180 @necessity_by_association [ From e5f92472543e3dcd72202cddd7426fa94c4ee163 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 11 Jun 2019 14:48:49 +0300 Subject: [PATCH 23/90] fix test --- .../chain/address_token_transfer_csv_exporter_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs index 9989290264..24bd9d7f04 100644 --- a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs @@ -63,8 +63,8 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do assert result.block_number == to_string(transaction.block_number) assert result.tx_hash == to_string(transaction.hash) - assert result.from_address == to_string(token_transfer.from_address_hash) - assert result.to_address == to_string(token_transfer.to_address_hash) + assert result.from_address == token_transfer.from_address_hash |> to_string() |> String.downcase() + assert result.to_address == token_transfer.to_address_hash |> to_string() |> String.downcase() assert result.timestamp == to_string(transaction.block.timestamp) assert result.type == "OUT" end From 5f77a6a879e143a5fe3369d56cb317fd07229633 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 11 Jun 2019 14:57:51 +0300 Subject: [PATCH 24/90] add token transfers csv endpoint --- .../address_transaction_controller.ex | 21 +++++++++++++++++++ .../lib/block_scout_web/router.ex | 2 ++ .../address_transaction_controller_test.exs | 18 ++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index da22b8c042..808d470dde 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -10,6 +10,7 @@ defmodule BlockScoutWeb.AddressTransactionController do alias BlockScoutWeb.TransactionView alias Explorer.{Chain, Market} + alias Explorer.Chain.AddressTokenTransferCsvExporter alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -106,4 +107,24 @@ defmodule BlockScoutWeb.AddressTransactionController do not_found(conn) end end + + def token_transfers_csv(conn, %{"address_id" => address_hash_string}) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + address + |> AddressTokenTransferCsvExporter.export() + |> Enum.into( + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=transactions.csv") + |> send_chunked(200) + ) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index 786c77cdff..b1a4ecc8d2 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -244,6 +244,8 @@ defmodule BlockScoutWeb.Router do get("/token_autocomplete", ChainController, :token_autocomplete) + get("/token_transfers_csv", AddressTransactionController, :token_transfers_csv) + get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks) get("/api_docs", APIDocsController, :index) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs index 028387501a..1f94a7b8ea 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs @@ -132,4 +132,22 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do end) end end + + describe "GET token_transfers_csv/2" do + test "exports token transfers to csv", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + insert(:token_transfer, transaction: transaction, from_address: address) + insert(:token_transfer, transaction: transaction, to_address: address) + + conn = get(conn, "/token_transfers_csv", %{"address_id" => to_string(address.hash)}) + + assert conn.resp_body |> String.split("\n") |> Enum.count() == 4 + end + end end From 2e986c3aff97fa46b7ad93737c965e687d55b01f Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 11 Jun 2019 15:28:20 +0300 Subject: [PATCH 25/90] add download button --- .../controllers/address_transaction_controller.ex | 2 +- .../block_scout_web/templates/address_token/index.html.eex | 1 + .../explorer/chain/address_token_transfer_csv_exporter.ex | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index 808d470dde..193d03f9c5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -116,7 +116,7 @@ defmodule BlockScoutWeb.AddressTransactionController do |> Enum.into( conn |> put_resp_content_type("application/csv") - |> put_resp_header("content-disposition", "attachment; filename=transactions.csv") + |> put_resp_header("content-disposition", "attachment; filename=token_transfers.csv") |> send_chunked(200) ) else diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex index 43457d3bc1..fa9dc12bd7 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex @@ -5,6 +5,7 @@
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
+ to_string(@address.hash)}) %>><%= gettext("Download all token transfers as csv") %>

<%= gettext "Tokens" %>

<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex index 4073245383..96bd56afef 100644 --- a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex @@ -87,9 +87,9 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do to_string(token_transfer.transaction_hash), token_transfer.transaction.block_number, token_transfer.transaction.block.timestamp, - to_string(token_transfer.from_address), - to_string(token_transfer.to_address), - to_string(token_transfer.token_contract_address), + token_transfer.from_address |> to_string() |> String.downcase(), + token_transfer.to_address |> to_string() |> String.downcase(), + token_transfer.token_contract_address |> to_string() |> String.downcase(), type(token_transfer, address), token_transfer.token.symbol, token_transfer.amount, From 06906b2f20cc1fbab81990260bfb63a275c99560 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 11 Jun 2019 15:34:49 +0300 Subject: [PATCH 26/90] fix gettext --- apps/block_scout_web/priv/gettext/default.pot | 11 ++++++++--- .../priv/gettext/en/LC_MESSAGES/default.po | 13 +++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index e9763e2287..f71be7b3dd 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -732,7 +732,7 @@ msgid "There are no token transfers for this address." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_token/index.html.eex:18 +#: lib/block_scout_web/templates/address_token/index.html.eex:19 msgid "There are no tokens for this address." msgstr "" @@ -810,7 +810,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:8 -#: lib/block_scout_web/templates/address_token/index.html.eex:8 +#: lib/block_scout_web/templates/address_token/index.html.eex:9 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:9 #: lib/block_scout_web/views/address_view.ex:304 msgid "Tokens" @@ -1207,7 +1207,7 @@ msgstr "" #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:61 #: lib/block_scout_web/templates/address_logs/index.html.eex:21 -#: lib/block_scout_web/templates/address_token/index.html.eex:13 +#: lib/block_scout_web/templates/address_token/index.html.eex:14 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 #: lib/block_scout_web/templates/address_transaction/index.html.eex:57 #: lib/block_scout_web/templates/address_validation/index.html.eex:22 @@ -1697,3 +1697,8 @@ msgstr "" #: lib/block_scout_web/templates/transaction/overview.html.eex:178 msgid " Token Transfer" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_token/index.html.eex:8 +msgid "Download all token transfers as csv" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 654a848278..6d0d199372 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -732,7 +732,7 @@ msgid "There are no token transfers for this address." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_token/index.html.eex:18 +#: lib/block_scout_web/templates/address_token/index.html.eex:19 msgid "There are no tokens for this address." msgstr "" @@ -810,7 +810,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:8 -#: lib/block_scout_web/templates/address_token/index.html.eex:8 +#: lib/block_scout_web/templates/address_token/index.html.eex:9 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:9 #: lib/block_scout_web/views/address_view.ex:304 msgid "Tokens" @@ -1207,7 +1207,7 @@ msgstr "" #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:61 #: lib/block_scout_web/templates/address_logs/index.html.eex:21 -#: lib/block_scout_web/templates/address_token/index.html.eex:13 +#: lib/block_scout_web/templates/address_token/index.html.eex:14 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 #: lib/block_scout_web/templates/address_transaction/index.html.eex:57 #: lib/block_scout_web/templates/address_validation/index.html.eex:22 @@ -1693,7 +1693,12 @@ msgstr "" msgid "New Smart Contract Verification" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:178 msgid " Token Transfer" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_token/index.html.eex:8 +msgid "Download all token transfers as csv" +msgstr "" From ec2719936fad336cc5e2f25000f2a8774820bcfc Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 18 Jun 2019 13:08:34 +0300 Subject: [PATCH 27/90] show all token transfers --- .../templates/transaction/overview.html.eex | 8 +++++--- .../lib/block_scout_web/views/transaction_view.ex | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index 396b8dd7c0..ca188ea231 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -170,18 +170,20 @@
<%= case token_transfer_type(@transaction) do %> - <% {type, token_transfer} -> %> + <% {type, transaction_with_transfers} when is_atom(type) -> %>

<%= if type == :erc20, do: gettext("ERC-20"), else: gettext("ERC-721")%><%= gettext " Token Transfer" %>

+ <%= for transfer <- transaction_with_transfers.token_transfers do %>

- <%= token_transfer_amount(token_transfer) %> - <%= link(token_symbol(token_transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, token_transfer.token.contract_address_hash)) %> + <%= token_transfer_amount(transfer) %> + <%= link(token_symbol(transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, transfer.token.contract_address_hash)) %>

+ <% end %>
<% _ -> %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 026147ac3e..a012e138a0 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.TransactionView do alias BlockScoutWeb.{AddressView, BlockView, TabHelpers} alias Cldr.Number - alias Explorer.Chain + alias Explorer.{Chain, Repo} alias Explorer.Chain.Block.Reward alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction, Wei} alias Explorer.ExchangeRates.Token @@ -33,7 +33,9 @@ defmodule BlockScoutWeb.TransactionView do def value_transfer?(_), do: false def token_transfer_type(transaction) do - Chain.transaction_token_transfer_type(transaction) + transaction_with_transfers = Repo.preload(transaction, token_transfers: :token) + + {Chain.transaction_token_transfer_type(transaction), transaction_with_transfers} end def processing_time_duration(%Transaction{block: nil}) do From 9470d33a50463c9002f513fa4e86d52758e2be8d Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 18 Jun 2019 13:08:46 +0300 Subject: [PATCH 28/90] token transfer type fixes --- apps/explorer/lib/explorer/chain.ex | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index c40c2a492b..72b907d552 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2907,8 +2907,6 @@ defmodule Explorer.Chain do ) do zero_wei = %Wei{value: Decimal.new(0)} - transaction = Repo.preload(transaction, token_transfers: :token) - # https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721.sol#L35 case {to_string(input), value} do # transferFrom(address,address,uint256) @@ -2932,6 +2930,9 @@ defmodule Explorer.Chain do find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) + {"0xf907fc5b" <> _params, ^zero_wei} -> + :erc20 + # check for ERC 20 or for old ERC 721 token versions {unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} -> types = [:address, {:uint, 256}] @@ -2957,22 +2958,23 @@ defmodule Explorer.Chain do token_transfer.from_address_hash.bytes == from_address && token_transfer.to_address_hash.bytes == to_address end) - if token_transfer, do: {:erc721, token_transfer} + if token_transfer, do: :erc721 end defp find_erc721_or_erc20_token_transfer(token_transfers, {address, decimal_value}) do token_transfer = Enum.find(token_transfers, fn token_transfer -> - token_transfer.to_address_hash.bytes == address && - (token_transfer.amount == decimal_value || token_transfer.token_id) + token_transfer.to_address_hash.bytes == address && token_transfer.amount == decimal_value end) if token_transfer do case token_transfer.token do - %Token{type: "ERC-20"} -> {:erc20, token_transfer} - %Token{type: "ERC-721"} -> {:erc721, token_transfer} + %Token{type: "ERC-20"} -> :erc20 + %Token{type: "ERC-721"} -> :erc721 _ -> nil end + else + :erc20 end end From 2790cf5f88e65eaf5fefe1b718097a45b868dc88 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 18 Jun 2019 13:25:52 +0300 Subject: [PATCH 29/90] add generic title for token transfers --- .../templates/transaction/overview.html.eex | 2 +- .../lib/block_scout_web/views/transaction_view.ex | 11 ++++++++++- apps/explorer/lib/explorer/chain.ex | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index ca188ea231..b168045fe1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -175,7 +175,7 @@
-

<%= if type == :erc20, do: gettext("ERC-20"), else: gettext("ERC-721")%><%= gettext " Token Transfer" %>

+

<%= token_type_name(type)%><%= gettext " Token Transfer" %>

<%= for transfer <- transaction_with_transfers.token_transfers do %>

diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index a012e138a0..1ed50d28ab 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -35,7 +35,16 @@ defmodule BlockScoutWeb.TransactionView do def token_transfer_type(transaction) do transaction_with_transfers = Repo.preload(transaction, token_transfers: :token) - {Chain.transaction_token_transfer_type(transaction), transaction_with_transfers} + type = Chain.transaction_token_transfer_type(transaction) + if type, do: {type, transaction_with_transfers} + end + + def token_type_name(type) do + case type do + :erc20 -> gettext("ERC-20 ") + :erc721 -> gettext("ERC-721 ") + :token_transfer -> "" + end end def processing_time_duration(%Transaction{block: nil}) do diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 72b907d552..e24db3ef89 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2943,6 +2943,9 @@ defmodule Explorer.Chain do find_erc721_or_erc20_token_transfer(transaction.token_transfers, {address, decimal_value}) + {_params, ^zero_wei} -> + if Enum.count(transaction.token_transfers) > 0, do: :token_transfer + _ -> nil end From 79bed238e919537e62b534320308cfc4a2ac8296 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 18 Jun 2019 14:07:54 +0300 Subject: [PATCH 30/90] fix gettext --- apps/block_scout_web/priv/gettext/default.pot | 60 +++++++++--------- .../priv/gettext/en/LC_MESSAGES/default.po | 62 +++++++++---------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 4e05e8266f..01aa2921c3 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -49,7 +49,7 @@ msgid "%{subnetwork} Explorer - BlockScout" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:133 +#: lib/block_scout_web/views/transaction_view.ex:144 msgid "(Awaiting internal transactions for status)" msgstr "" @@ -274,12 +274,12 @@ msgid "Contract Address Pending" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:210 +#: lib/block_scout_web/views/transaction_view.ex:221 msgid "Contract Call" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:209 +#: lib/block_scout_web/views/transaction_view.ex:220 msgid "Contract Creation" msgstr "" @@ -362,12 +362,12 @@ msgid "Error trying to fetch balances." msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:137 +#: lib/block_scout_web/views/transaction_view.ex:148 msgid "Error: %{reason}" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:135 +#: lib/block_scout_web/views/transaction_view.ex:146 msgid "Error: (Awaiting internal transactions for reason)" msgstr "" @@ -377,7 +377,7 @@ msgstr "" #: lib/block_scout_web/templates/layout/app.html.eex:55 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_tile.html.eex:30 -#: lib/block_scout_web/templates/transaction/overview.html.eex:192 +#: lib/block_scout_web/templates/transaction/overview.html.eex:194 #: lib/block_scout_web/views/wei_helpers.ex:78 msgid "Ether" msgstr "" @@ -472,7 +472,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/views/address_view.ex:306 -#: lib/block_scout_web/views/transaction_view.ex:263 +#: lib/block_scout_web/views/transaction_view.ex:274 msgid "Internal Transactions" msgstr "" @@ -489,7 +489,7 @@ msgid "Less than" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:220 +#: lib/block_scout_web/templates/transaction/overview.html.eex:222 msgid "Limit" msgstr "" @@ -499,7 +499,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/views/address_view.ex:312 -#: lib/block_scout_web/views/transaction_view.ex:264 +#: lib/block_scout_web/views/transaction_view.ex:275 msgid "Logs" msgstr "" @@ -511,7 +511,7 @@ msgid "Market Cap" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:118 +#: lib/block_scout_web/views/transaction_view.ex:129 msgid "Max of" msgstr "" @@ -601,8 +601,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/layout/_topnav.html.eex:44 -#: lib/block_scout_web/views/transaction_view.ex:132 -#: lib/block_scout_web/views/transaction_view.ex:166 +#: lib/block_scout_web/views/transaction_view.ex:143 +#: lib/block_scout_web/views/transaction_view.ex:177 msgid "Pending" msgstr "" @@ -689,7 +689,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:134 +#: lib/block_scout_web/views/transaction_view.ex:145 msgid "Success" msgstr "" @@ -794,7 +794,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:5 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4 -#: lib/block_scout_web/views/transaction_view.ex:208 +#: lib/block_scout_web/views/transaction_view.ex:219 msgid "Token Transfer" msgstr "" @@ -804,7 +804,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 #: lib/block_scout_web/views/tokens/overview_view.ex:35 -#: lib/block_scout_web/views/transaction_view.ex:262 +#: lib/block_scout_web/views/transaction_view.ex:273 msgid "Token Transfers" msgstr "" @@ -844,7 +844,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:3 -#: lib/block_scout_web/views/transaction_view.ex:211 +#: lib/block_scout_web/views/transaction_view.ex:222 msgid "Transaction" msgstr "" @@ -906,7 +906,7 @@ msgid "Unique Token" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:214 +#: lib/block_scout_web/templates/transaction/overview.html.eex:216 msgid "Used" msgstr "" @@ -926,7 +926,7 @@ msgid "Validations" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:192 +#: lib/block_scout_web/templates/transaction/overview.html.eex:194 msgid "Value" msgstr "" @@ -1519,16 +1519,6 @@ msgstr "" msgid "Optimization runs" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:178 -msgid "ERC-20" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:178 -msgid "ERC-721" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:4 msgid "API Documentation" @@ -1545,14 +1535,14 @@ msgid "View All Transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:210 +#: lib/block_scout_web/templates/transaction/overview.html.eex:212 msgid "Gas" msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 -#: lib/block_scout_web/views/transaction_view.ex:265 +#: lib/block_scout_web/views/transaction_view.ex:276 msgid "Raw Trace" msgstr "" @@ -1702,3 +1692,13 @@ msgstr "" #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27 msgid "There is no decompilded contracts for this address." msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:44 +msgid "ERC-20 " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:45 +msgid "ERC-721 " +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 142aec2b37..b90f812c34 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -49,7 +49,7 @@ msgid "%{subnetwork} Explorer - BlockScout" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:133 +#: lib/block_scout_web/views/transaction_view.ex:144 msgid "(Awaiting internal transactions for status)" msgstr "" @@ -274,12 +274,12 @@ msgid "Contract Address Pending" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:210 +#: lib/block_scout_web/views/transaction_view.ex:221 msgid "Contract Call" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:209 +#: lib/block_scout_web/views/transaction_view.ex:220 msgid "Contract Creation" msgstr "" @@ -362,12 +362,12 @@ msgid "Error trying to fetch balances." msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:137 +#: lib/block_scout_web/views/transaction_view.ex:148 msgid "Error: %{reason}" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:135 +#: lib/block_scout_web/views/transaction_view.ex:146 msgid "Error: (Awaiting internal transactions for reason)" msgstr "" @@ -377,7 +377,7 @@ msgstr "" #: lib/block_scout_web/templates/layout/app.html.eex:55 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_tile.html.eex:30 -#: lib/block_scout_web/templates/transaction/overview.html.eex:192 +#: lib/block_scout_web/templates/transaction/overview.html.eex:194 #: lib/block_scout_web/views/wei_helpers.ex:78 msgid "Ether" msgstr "POA" @@ -472,7 +472,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/views/address_view.ex:306 -#: lib/block_scout_web/views/transaction_view.ex:263 +#: lib/block_scout_web/views/transaction_view.ex:274 msgid "Internal Transactions" msgstr "" @@ -489,7 +489,7 @@ msgid "Less than" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:220 +#: lib/block_scout_web/templates/transaction/overview.html.eex:222 msgid "Limit" msgstr "" @@ -499,7 +499,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/views/address_view.ex:312 -#: lib/block_scout_web/views/transaction_view.ex:264 +#: lib/block_scout_web/views/transaction_view.ex:275 msgid "Logs" msgstr "" @@ -511,7 +511,7 @@ msgid "Market Cap" msgstr "" #, elixir-format -#: lib/block_scout_web/views/transaction_view.ex:118 +#: lib/block_scout_web/views/transaction_view.ex:129 msgid "Max of" msgstr "" @@ -601,8 +601,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/layout/_topnav.html.eex:44 -#: lib/block_scout_web/views/transaction_view.ex:132 -#: lib/block_scout_web/views/transaction_view.ex:166 +#: lib/block_scout_web/views/transaction_view.ex:143 +#: lib/block_scout_web/views/transaction_view.ex:177 msgid "Pending" msgstr "" @@ -689,7 +689,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:134 +#: lib/block_scout_web/views/transaction_view.ex:145 msgid "Success" msgstr "" @@ -794,7 +794,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:5 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4 -#: lib/block_scout_web/views/transaction_view.ex:208 +#: lib/block_scout_web/views/transaction_view.ex:219 msgid "Token Transfer" msgstr "" @@ -804,7 +804,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 #: lib/block_scout_web/views/tokens/overview_view.ex:35 -#: lib/block_scout_web/views/transaction_view.ex:262 +#: lib/block_scout_web/views/transaction_view.ex:273 msgid "Token Transfers" msgstr "" @@ -844,7 +844,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:3 -#: lib/block_scout_web/views/transaction_view.ex:211 +#: lib/block_scout_web/views/transaction_view.ex:222 msgid "Transaction" msgstr "" @@ -906,7 +906,7 @@ msgid "Unique Token" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:214 +#: lib/block_scout_web/templates/transaction/overview.html.eex:216 msgid "Used" msgstr "" @@ -926,7 +926,7 @@ msgid "Validations" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:192 +#: lib/block_scout_web/templates/transaction/overview.html.eex:194 msgid "Value" msgstr "" @@ -1519,16 +1519,6 @@ msgstr "" msgid "Optimization runs" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:178 -msgid "ERC-20" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:178 -msgid "ERC-721" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:4 msgid "API Documentation" @@ -1545,14 +1535,14 @@ msgid "View All Transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:210 +#: lib/block_scout_web/templates/transaction/overview.html.eex:212 msgid "Gas" msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 -#: lib/block_scout_web/views/transaction_view.ex:265 +#: lib/block_scout_web/views/transaction_view.ex:276 msgid "Raw Trace" msgstr "" @@ -1698,7 +1688,17 @@ msgstr "" msgid " Token Transfer" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27 msgid "There is no decompilded contracts for this address." msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/views/transaction_view.ex:44 +msgid "ERC-20 " +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/views/transaction_view.ex:45 +msgid "ERC-721 " +msgstr "" From 20140b0fcb272309f4a1e7710c2d9979d52e0128 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 18 Jun 2019 14:09:01 +0300 Subject: [PATCH 31/90] fix dialyzer --- apps/explorer/lib/explorer/chain.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index e24db3ef89..8284a0e2ac 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2896,7 +2896,7 @@ defmodule Explorer.Chain do end @spec transaction_token_transfer_type(Transaction.t()) :: - {:erc20, TokenTransfer.t()} | {:erc721, TokenTransfer.t()} | nil + :erc20 | :erc721 | :token_transfer | nil def transaction_token_transfer_type( %Transaction{ status: :ok, From 33eb12091054995f7b9be9c694d7e2cdd24186ef Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 18 Jun 2019 14:48:30 +0300 Subject: [PATCH 32/90] fix tests --- apps/explorer/test/explorer/chain_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 442d051e0d..2cc8273adf 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -3954,7 +3954,7 @@ defmodule Explorer.ChainTest do insert(:token_transfer, from_address: from_address, to_address: to_address, transaction: transaction) - assert {:erc721, _found_token_transfer} = Chain.transaction_token_transfer_type(transaction) + assert :erc721 = Chain.transaction_token_transfer_type(Repo.preload(transaction, :token_trasfers)) end test "detects erc20 token transfer" do @@ -3980,7 +3980,7 @@ defmodule Explorer.ChainTest do amount: 8_025_000_000_000_000_000_000 ) - assert {:erc20, _found_token_transfer} = Chain.transaction_token_transfer_type(transaction) + assert :erc20 = Chain.transaction_token_transfer_type(Repo.preload(transaction, :token_trasfers)) end end From 96bfe828fb7e8f161562c7f6798e122d71400860 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 18 Jun 2019 14:56:14 +0300 Subject: [PATCH 33/90] fix typo --- apps/explorer/test/explorer/chain_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 2cc8273adf..5e7472bae1 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -3954,7 +3954,7 @@ defmodule Explorer.ChainTest do insert(:token_transfer, from_address: from_address, to_address: to_address, transaction: transaction) - assert :erc721 = Chain.transaction_token_transfer_type(Repo.preload(transaction, :token_trasfers)) + assert :erc721 = Chain.transaction_token_transfer_type(Repo.preload(transaction, :token_transfers)) end test "detects erc20 token transfer" do @@ -3980,7 +3980,7 @@ defmodule Explorer.ChainTest do amount: 8_025_000_000_000_000_000_000 ) - assert :erc20 = Chain.transaction_token_transfer_type(Repo.preload(transaction, :token_trasfers)) + assert :erc20 = Chain.transaction_token_transfer_type(Repo.preload(transaction, :token_transfers)) end end From 0ac8d0e10a331dc6caed54cf1b5b3658bf9763da Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 18 Jun 2019 15:04:59 +0300 Subject: [PATCH 34/90] preload token --- apps/explorer/test/explorer/chain_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 5e7472bae1..f789a883ea 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -3954,7 +3954,7 @@ defmodule Explorer.ChainTest do insert(:token_transfer, from_address: from_address, to_address: to_address, transaction: transaction) - assert :erc721 = Chain.transaction_token_transfer_type(Repo.preload(transaction, :token_transfers)) + assert :erc721 = Chain.transaction_token_transfer_type(Repo.preload(transaction, token_transfers: :token)) end test "detects erc20 token transfer" do @@ -3980,7 +3980,7 @@ defmodule Explorer.ChainTest do amount: 8_025_000_000_000_000_000_000 ) - assert :erc20 = Chain.transaction_token_transfer_type(Repo.preload(transaction, :token_transfers)) + assert :erc20 = Chain.transaction_token_transfer_type(Repo.preload(transaction, token_transfers: :token)) end end From b30ff3de25f9540e629bcee8751a10ee3797b4af Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 18 Jun 2019 15:16:46 +0300 Subject: [PATCH 35/90] add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb0a5f0898..487e4d6266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Current ### Features +- [#2190](https://github.com/poanetwork/blockscout/pull/2190) - show all token transfers - [#2109](https://github.com/poanetwork/blockscout/pull/2109) - use bigger updates instead of `Multi` transactions in BlocksTransactionsMismatch - [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache - [#2151](https://github.com/poanetwork/blockscout/pull/2151) - hide dropdown menu then other networks list is empty From 99d0a98fd37e0d5fd150c942072b6d60ed17c919 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 18 Jun 2019 15:18:00 +0300 Subject: [PATCH 36/90] fix CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 487e4d6266..891517bd5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ## Current +### Features + - [#2190](https://github.com/poanetwork/blockscout/pull/2190) - show all token transfers ### Features -- [#2190](https://github.com/poanetwork/blockscout/pull/2190) - show all token transfers - [#2109](https://github.com/poanetwork/blockscout/pull/2109) - use bigger updates instead of `Multi` transactions in BlocksTransactionsMismatch - [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache - [#2151](https://github.com/poanetwork/blockscout/pull/2151) - hide dropdown menu then other networks list is empty From c66ec8018c6973752d96b074760c3af6065d663e Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Wed, 19 Jun 2019 16:37:41 +0300 Subject: [PATCH 37/90] search network input functionality, add to favorites functionality --- .../css/components/_network-selector.scss | 3 +- apps/block_scout_web/assets/js/app.js | 2 + .../assets/js/pages/favorites.js | 41 +++++++++++++++++++ .../assets/js/pages/network-search.js | 21 ++++++++++ .../layout/_network_selector.html.eex | 6 +-- .../layout/_network_selector_item.html.eex | 4 +- 6 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 apps/block_scout_web/assets/js/pages/favorites.js create mode 100644 apps/block_scout_web/assets/js/pages/network-search.js diff --git a/apps/block_scout_web/assets/css/components/_network-selector.scss b/apps/block_scout_web/assets/css/components/_network-selector.scss index 4b1068fab4..3086639050 100644 --- a/apps/block_scout_web/assets/css/components/_network-selector.scss +++ b/apps/block_scout_web/assets/css/components/_network-selector.scss @@ -41,7 +41,7 @@ $network-selector-item-icon-dimensions: 30px !default; margin-left: auto; max-width: 398px; min-width: 0; - padding: 28px 0 35px; + padding-top: 28px; position: relative; transition: right 0.25s ease-out; } @@ -266,7 +266,6 @@ $network-selector-item-icon-dimensions: 30px !default; .network-selector-networks-container { flex-grow: 1; flex-shrink: 1; - margin: 0 0 30px; min-height: 100px; overflow: auto; padding: 0 $network-selector-horizontal-padding; diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index 25745125e2..fe0fd5250d 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -31,6 +31,8 @@ import './pages/chain' import './pages/pending_transactions' import './pages/transaction' import './pages/transactions' +import './pages/favorites' +import './pages/network-search' import './pages/admin/tasks.js' diff --git a/apps/block_scout_web/assets/js/pages/favorites.js b/apps/block_scout_web/assets/js/pages/favorites.js new file mode 100644 index 0000000000..56cbe04d4f --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/favorites.js @@ -0,0 +1,41 @@ +import $ from 'jquery' + +var favoritesQuantity = 0, + favoritesContainer = $(".js-favorites-tab"), + favoritesNetworksUrls = []; + +$(document).on("change", ".network-selector-item-favorite input[type='checkbox']", function () { + + var networkUrl = $(this).attr("data-url"), + thisStatus = $(this).is(":checked"), + parent = $(".network-selector-item[data-url='" + networkUrl +"'").clone(), + workWith = $(".network-selector-item[data-url='" + networkUrl +"'"); + + // Add new checkbox status to same network in another tabs + $(".network-selector-item-favorite input[data-url='" + networkUrl +"']").prop("checked", thisStatus); + + // Push or remove favorite networks to array + var found = $.inArray(networkUrl, favoritesNetworksUrls); + if (found < 0 && thisStatus == true) { + favoritesNetworksUrls.push(networkUrl); + } else { + var index = favoritesNetworksUrls.indexOf(networkUrl); + if(index!=-1){ + favoritesNetworksUrls.splice(index, 1); + } + } + console.log(favoritesNetworksUrls); + // Append or remove item from 'favorites' tab + + if (thisStatus == true) { + favoritesContainer.append(parent[0]); + $(".js-favorites-tab .network-selector-tab-content-empty").hide(); + } else { + var willRemoved = favoritesContainer.find(workWith); + willRemoved.remove(); + if (favoritesNetworksUrls.length == 0) { + $(".js-favorites-tab .network-selector-tab-content-empty").show(); + } + } + +}); \ No newline at end of file diff --git a/apps/block_scout_web/assets/js/pages/network-search.js b/apps/block_scout_web/assets/js/pages/network-search.js new file mode 100644 index 0000000000..e508bbe91b --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/network-search.js @@ -0,0 +1,21 @@ +import $ from 'jquery' + +var networkSearchInput = $(".network-selector-search-input"), + networkSearchInputVal = ""; + +$(networkSearchInput).on("input", function() { + networkSearchInputVal = $(this).val(); + + $.expr[":"].Contains = $.expr.createPseudo(function(arg) { + return function( elem ) { + return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0; + }; + }); + + if (networkSearchInputVal == "") { + $(".network-selector-item").show(); + } else { + $(".network-selector-item").hide(); + $(".network-selector-item:Contains('" + networkSearchInputVal + "')").show(); + } +}); \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex index eadf1b4503..044d8790bc 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex @@ -43,13 +43,13 @@ <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Testnet" %> <% end %>

-
+
No content.
-
+
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex index a798178455..11f3c83dfe 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex @@ -1,4 +1,4 @@ -
+
-
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex index 11f3c83dfe..af6d8df375 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex @@ -5,7 +5,7 @@ - .png');"> + .png');"> <%= @title %> diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 7dca514693..1b22f7969c 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1721,11 +1721,6 @@ msgstr "" msgid "Search network" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:51 -msgid "Show More Networks" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 msgid "Testnet" From 1393edbaae43d0c827d8213745f1937dabbb89cc Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Thu, 20 Jun 2019 11:06:55 +0300 Subject: [PATCH 43/90] jslint test fix --- apps/block_scout_web/assets/js/pages/favorites.js | 6 +++--- apps/block_scout_web/assets/js/pages/network-search.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/block_scout_web/assets/js/pages/favorites.js b/apps/block_scout_web/assets/js/pages/favorites.js index 38d4da45ee..9f9d8f39cc 100644 --- a/apps/block_scout_web/assets/js/pages/favorites.js +++ b/apps/block_scout_web/assets/js/pages/favorites.js @@ -11,9 +11,9 @@ if (localStorage.getItem('favoritesNetworksUrls') === null) { $(document).on('change', ".network-selector-item-favorite input[type='checkbox']", function () { var networkUrl = $(this).attr('data-url') - var thisStatus = $(this).is(':checked'); - var parent = $(".network-selector-item[data-url='" + networkUrl + "'").clone(); - var workWith = $(".network-selector-item[data-url='" + networkUrl + "'"); + var thisStatus = $(this).is(':checked') + var parent = $(".network-selector-item[data-url='" + networkUrl + "'").clone() + var workWith = $(".network-selector-item[data-url='" + networkUrl + "'") // Add new checkbox status to same network in another tabs $(".network-selector-item-favorite input[data-url='" + networkUrl + "']").prop('checked', thisStatus) diff --git a/apps/block_scout_web/assets/js/pages/network-search.js b/apps/block_scout_web/assets/js/pages/network-search.js index b73ea635b9..36b7e02348 100644 --- a/apps/block_scout_web/assets/js/pages/network-search.js +++ b/apps/block_scout_web/assets/js/pages/network-search.js @@ -1,7 +1,7 @@ import $ from 'jquery' var networkSearchInput = $('.network-selector-search-input') -var networkSearchInputVal = "" +var networkSearchInputVal = '' $(networkSearchInput).on('input', function () { networkSearchInputVal = $(this).val() From c656b70df60c6402d64040d5f6ca47f87aac0ef5 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 20 Jun 2019 13:49:36 +0300 Subject: [PATCH 44/90] fix large contract verification --- .../smart_contract/solidity/code_compiler.ex | 10 +- apps/explorer/mix.exs | 1 + apps/explorer/priv/compile_solc.js | 5 +- .../solidity/code_compiler_test.exs | 14 + .../smart_contract/large_smart_contract.sol | 3874 +++++++++++++++++ mix.lock | 1 + 6 files changed, 3903 insertions(+), 2 deletions(-) create mode 100644 apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex index 3e41bb08fa..14262e7fb9 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex @@ -91,7 +91,7 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do "node", [ Application.app_dir(:explorer, "priv/compile_solc.js"), - code, + create_source_file(code), compiler_version, optimize_value(optimize), optimization_runs, @@ -162,4 +162,12 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do defp optimize_value(true), do: "1" defp optimize_value("true"), do: "1" + + defp create_source_file(source) do + {:ok, path} = Briefly.create() + + File.write!(path, source) + + path + end end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 09257b9aee..f224471740 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -66,6 +66,7 @@ defmodule Explorer.Mixfile do # CSV output for benchee {:benchee_csv, "~> 0.8.0", only: :test}, {:bypass, "~> 1.0", only: :test}, + {:briefly, "~> 0.4", github: "CargoSense/briefly"}, {:comeonin, "~> 4.0"}, {:credo, "1.0.0", only: :test, runtime: false}, # For Absinthe to load data in batches diff --git a/apps/explorer/priv/compile_solc.js b/apps/explorer/priv/compile_solc.js index eea727802e..99048d9ffd 100755 --- a/apps/explorer/priv/compile_solc.js +++ b/apps/explorer/priv/compile_solc.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -var sourceCode = process.argv[2]; +var sourceCodePath = process.argv[2]; var version = process.argv[3]; var optimize = process.argv[4]; var optimizationRuns = parseInt(process.argv[5], 10); @@ -13,6 +13,9 @@ var solc = require('solc') var compilerSnapshot = require(compilerVersionPath); var solc = solc.setupMethods(compilerSnapshot); +var fs = require('fs'); +var sourceCode = fs.readFileSync(sourceCodePath, 'utf8'); + const input = { language: 'Solidity', sources: { diff --git a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs index be68a03a0e..d1c1d04b45 100644 --- a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs +++ b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs @@ -268,6 +268,20 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do assert Enum.any?(abi, fn el -> el["type"] == "constructor" end) end + + test "can compile a large file" do + path = File.cwd!() <> "/test/support/fixture/smart_contract/large_smart_contract.sol" + contract = File.read!(path) + + assert {:ok, %{"abi" => abi}} = + CodeCompiler.run( + name: "HomeWorkDeployer", + compiler_version: "v0.5.9+commit.e560f70d", + code: contract, + evm_version: "constantinople", + optimize: true + ) + end end describe "get_contract_info/1" do diff --git a/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol b/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol new file mode 100644 index 0000000000..6b147f21d8 --- /dev/null +++ b/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol @@ -0,0 +1,3874 @@ +/** + *Submitted for verification at Etherscan.io on 2019-06-05 +*/ + +pragma solidity 0.5.9; // optimization enabled, runs: 10000, evm: constantinople + + +/** + * @title HomeWork Interface (version 1) - EIP165 ID 0xe5399799 + * @author 0age + * @notice Homework is a utility to find, share, and reuse "home" addresses for + * contracts. Anyone can work to find a new home address by searching for keys, + * a 32-byte value with the first 20 bytes equal to the finder's calling address + * (or derived by hashing an arbitrary 32-byte salt and the caller's address), + * and can then deploy any contract they like (even one with a constructor) to + * the address, or mint an ERC721 token that the owner can redeem that will then + * allow them to do the same. Also, if the contract is `SELFDESTRUCT`ed, a new + * contract can be redeployed by the current controller to the same address! + * @dev This contract allows contract addresses to be located ahead of time, and + * for arbitrary bytecode to be deployed (and redeployed if so desired, i.e. + * metamorphic contracts) to the located address by a designated controller. To + * enable this, the contract first deploys an "initialization-code-in-runtime" + * contract, with the creation code of the contract you want to deploy stored in + * RUNTIME code. Then, to deploy the actual contract, it retrieves the address + * of the storage contract and `DELEGATECALL`s into it to execute the init code + * and, if successful, retrieves and returns the contract runtime code. Rather + * than using a located address directly, you can also lock it in the contract + * and mint and ERC721 token for it, which can then be redeemed in order to gain + * control over deployment to the address (note that tokens may not be minted if + * the contract they control currently has a deployed contract at that address). + * Once a contract undergoes metamorphosis, all existing storage will be deleted + * and any existing contract code will be replaced with the deployed contract + * code of the new implementation contract. The mechanisms behind this contract + * are highly experimental - proceed with caution and please share any exploits + * or optimizations you discover. + */ +interface IHomeWork { + // Fires when a contract is deployed or redeployed to a given home address. + event NewResident( + address indexed homeAddress, + bytes32 key, + bytes32 runtimeCodeHash + ); + + // Fires when a new runtime storage contract is deployed. + event NewRuntimeStorageContract( + address runtimeStorageContract, + bytes32 runtimeCodeHash + ); + + // Fires when a controller is changed from the default controller. + event NewController(bytes32 indexed key, address newController); + + // Fires when a new high score is submitted. + event NewHighScore(bytes32 key, address submitter, uint256 score); + + // Track total contract deploys and current controller for each home address. + struct HomeAddress { + bool exists; + address controller; + uint88 deploys; + } + + // Track derivation of key for a given home address based on salt & submitter. + struct KeyInformation { + bytes32 key; + bytes32 salt; + address submitter; + } + + /** + * @notice Deploy a new contract with the desired initialization code to the + * home address corresponding to a given key. Two conditions must be met: the + * submitter must be designated as the controller of the home address (with + * the initial controller set to the address corresponding to the first twenty + * bytes of the key), and there must not be a contract currently deployed at + * the home address. These conditions can be checked by calling + * `getHomeAddressInformation` and `isDeployable` with the same key. + * @param key bytes32 The unique value used to derive the home address. + * @param initializationCode bytes The contract creation code that will be + * used to deploy the contract to the home address. + * @return The home address of the deployed contract. + * @dev In order to deploy the contract to the home address, a new contract + * will be deployed with runtime code set to the initialization code of the + * contract that will be deployed to the home address. Then, metamorphic + * initialization code will retrieve that initialization code and use it to + * set up and deploy the desired contract to the home address. Bear in mind + * that the deployed contract will interpret msg.sender as the address of THIS + * contract, and not the address of the submitter - if the constructor of the + * deployed contract uses msg.sender to set up ownership or other variables, + * you must modify it to accept a constructor argument with the appropriate + * address, or alternately to hard-code the intended address. Also, if your + * contract DOES have constructor arguments, remember to include them as + * ABI-encoded arguments at the end of the initialization code, just as you + * would when performing a standard deploy. You may also want to provide the + * key to `setReverseLookup` in order to find it again using only the home + * address to prevent accidentally losing the key. + */ + function deploy(bytes32 key, bytes calldata initializationCode) + external + payable + returns (address homeAddress, bytes32 runtimeCodeHash); + + /** + * @notice Mint an ERC721 token to the supplied owner that can be redeemed in + * order to gain control of a home address corresponding to a given key. Two + * conditions must be met: the submitter must be designated as the controller + * of the home address (with the initial controller set to the address + * corresponding to the first 20 bytes of the key), and there must not be a + * contract currently deployed at the home address. These conditions can be + * checked by calling `getHomeAddressInformation` and `isDeployable` with the + * same key. + * @param key bytes32 The unique value used to derive the home address. + * @param owner address The account that will be granted ownership of the + * ERC721 token. + * @dev In order to mint an ERC721 token, the assocated home address cannot be + * in use, or else the token will not be able to deploy to the home address. + * The controller is set to this contract until the token is redeemed, at + * which point the redeemer designates a new controller for the home address. + * The key of the home address and the tokenID of the ERC721 token are the + * same value, but different types (bytes32 vs. uint256). + */ + function lock(bytes32 key, address owner) external; + + /** + * @notice Burn an ERC721 token to allow the supplied controller to gain the + * ability to deploy to the home address corresponding to the key matching the + * burned token. The submitter must be designated as either the owner of the + * token or as an approved spender. + * @param tokenId uint256 The ID of the ERC721 token to redeem. + * @param controller address The account that will be granted control of the + * home address corresponding to the given token. + * @dev The controller cannot be designated as the address of this contract, + * the null address, or the home address (the restriction on setting the home + * address as the controller is due to the fact that the home address will not + * be able to deploy to itself, as it needs to be empty before a contract can + * be deployed to it). + */ + function redeem(uint256 tokenId, address controller) external; + + /** + * @notice Transfer control over deployment to the home address corresponding + * to a given key. The caller must be designated as the current controller of + * the home address (with the initial controller set to the address + * corresponding to the first 20 bytes of the key) - This condition can be + * checked by calling `getHomeAddressInformation` with the same key. + * @param key bytes32 The unique value used to derive the home address. + * @param controller address The account that will be granted control of the + * home address corresponding to the given key. + * @dev The controller cannot be designated as the address of this contract, + * the null address, or the home address (the restriction on setting the home + * address as the controller is due to the fact that the home address will not + * be able to deploy to itself, as it needs to be empty before a contract can + * be deployed to it). + */ + function assignController(bytes32 key, address controller) external; + + /** + * @notice Transfer control over deployment to the home address corresponding + * to a given key to the null address, which will prevent it from being + * deployed to again in the future. The caller must be designated as the + * current controller of the corresponding home address (with the initial + * controller set to the address corresponding to the first 20 bytes of the + * key) - This condition can be checked by calling `getHomeAddressInformation` + * with the same key. + * @param key bytes32 The unique value used to derive the home address. + */ + function relinquishControl(bytes32 key) external; + + /** + * @notice Burn an ERC721 token, set a supplied controller, and deploy a new + * contract with the supplied initialization code to the corresponding home + * address for the given token. The submitter must be designated as either the + * owner of the token or as an approved spender. + * @param tokenId uint256 The ID of the ERC721 token to redeem. + * @param controller address The account that will be granted control of the + * home address corresponding to the given token. + * @param initializationCode bytes The contract creation code that will be + * used to deploy the contract to the home address. + * @return The home address and runtime code hash of the deployed contract. + * @dev In order to deploy the contract to the home address, a new contract + * will be deployed with runtime code set to the initialization code of the + * contract that will be deployed to the home address. Then, metamorphic + * initialization code will retrieve that initialization code and use it to + * set up and deploy the desired contract to the home address. Bear in mind + * that the deployed contract will interpret msg.sender as the address of THIS + * contract, and not the address of the submitter - if the constructor of the + * deployed contract uses msg.sender to set up ownership or other variables, + * you must modify it to accept a constructor argument with the appropriate + * address, or alternately to hard-code the intended address. Also, if your + * contract DOES have constructor arguments, remember to include them as + * ABI-encoded arguments at the end of the initialization code, just as you + * would when performing a standard deploy. You may also want to provide the + * key to `setReverseLookup` in order to find it again using only the home + * address to prevent accidentally losing the key. The controller cannot be + * designated as the address of this contract, the null address, or the home + * address (the restriction on setting the home address as the controller is + * due to the fact that the home address will not be able to deploy to itself, + * as it needs to be empty before a contract can be deployed to it). Also, + * checks on the contract at the home address being empty or not having the + * correct controller are unnecessary, as they are performed when minting the + * token and cannot be altered until the token is redeemed. + */ + function redeemAndDeploy( + uint256 tokenId, + address controller, + bytes calldata initializationCode + ) + external + payable + returns (address homeAddress, bytes32 runtimeCodeHash); + + /** + * @notice Derive a new key by concatenating an arbitrary 32-byte salt value + * and the address of the caller and performing a keccak256 hash. This allows + * for the creation of keys with additional entropy where desired while also + * preventing collisions with standard keys. The caller will be set as the + * controller of the derived key. + * @param salt bytes32 The desired salt value to use (along with the address + * of the caller) when deriving the resultant key and corresponding home + * address. + * @return The derived key. + * @dev Home addresses from derived keys will take longer to "mine" or locate, + * as an additional hash must be performed when computing the corresponding + * home address for each given salt input. Each caller will derive a different + * key even if they are supplying the same salt value. + */ + function deriveKey(bytes32 salt) external returns (bytes32 key); + + /** + * @notice Mint an ERC721 token to the supplied owner that can be redeemed in + * order to gain control of a home address corresponding to a given derived + * key. Two conditions must be met: the submitter must be designated as the + * current controller of the home address, and there must not be a contract + * currently deployed at the home address. These conditions can be checked by + * calling `getHomeAddressInformation` and `isDeployable` with the key + * determined by calling `getDerivedKey`. + * @param salt bytes32 The salt value that is used to derive the key. + * @param owner address The account that will be granted ownership of the + * ERC721 token. + * @return The derived key. + * @dev In order to mint an ERC721 token, the assocated home address cannot be + * in use, or else the token will not be able to deploy to the home address. + * The controller is set to this contract until the token is redeemed, at + * which point the redeemer designates a new controller for the home address. + * The key of the home address and the tokenID of the ERC721 token are the + * same value, but different types (bytes32 vs. uint256). + */ + function deriveKeyAndLock(bytes32 salt, address owner) + external + returns (bytes32 key); + + /** + * @notice Transfer control over deployment to the home address corresponding + * to a given derived key. The caller must be designated as the current + * controller of the home address - This condition can be checked by calling + * `getHomeAddressInformation` with the key obtained via `getDerivedKey`. + * @param salt bytes32 The salt value that is used to derive the key. + * @param controller address The account that will be granted control of the + * home address corresponding to the given derived key. + * @return The derived key. + * @dev The controller cannot be designated as the address of this contract, + * the null address, or the home address (the restriction on setting the home + * address as the controller is due to the fact that the home address will not + * be able to deploy to itself, as it needs to be empty before a contract can + * be deployed to it). + */ + function deriveKeyAndAssignController(bytes32 salt, address controller) + external + returns (bytes32 key); + + /** + * @notice Transfer control over deployment to the home address corresponding + * to a given derived key to the null address, which will prevent it from + * being deployed to again in the future. The caller must be designated as the + * current controller of the home address - This condition can be checked by + * calling `getHomeAddressInformation` with the key determined by calling + * `getDerivedKey`. + * @param salt bytes32 The salt value that is used to derive the key. + * @return The derived key. + */ + function deriveKeyAndRelinquishControl(bytes32 salt) + external + returns (bytes32 key); + + /** + * @notice Record a key that corresponds to a given home address by supplying + * said key and using it to derive the address. This enables reverse lookup + * of a key using only the home address in question. This method may be called + * by anyone - control of the key is not required. + * @param key bytes32 The unique value used to derive the home address. + * @dev This does not set the salt or submitter fields, as those apply only to + * derived keys (although a derived key may also be set with this method, just + * without the derived fields). + */ + function setReverseLookup(bytes32 key) external; + + /** + * @notice Record the derived key that corresponds to a given home address by + * supplying the salt and submitter that were used to derive the key. This + * facititates reverse lookup of the derivation method of a key using only the + * home address in question. This method may be called by anyone - control of + * the derived key is not required. + * @param salt bytes32 The salt value that is used to derive the key. + * @param submitter address The account that submits the salt that is used to + * derive the key. + */ + function setDerivedReverseLookup(bytes32 salt, address submitter) external; + + /** + * @notice Deploy a new storage contract with the supplied code as runtime + * code without deploying a contract to a home address. This can be used to + * store the contract creation code for use in future deployments of contracts + * to home addresses. + * @param codePayload bytes The code to set as the runtime code of the + * deployed contract. + * @return The address of the deployed storage contract. + * @dev Consider placing adequate protections on the storage contract to + * prevent unwanted callers from modifying or destroying it. Also, if you are + * placing contract contract creation code into the runtime storage contract, + * remember to include any constructor parameters as ABI-encoded arguments at + * the end of the contract creation code, similar to how you would perform a + * standard deployment. + */ + function deployRuntimeStorageContract(bytes calldata codePayload) + external + returns (address runtimeStorageContract); + + /** + * @notice Deploy a new contract with the initialization code stored in the + * runtime code at the specified initialization runtime storage contract to + * the home address corresponding to a given key. Two conditions must be met: + * the submitter must be designated as the controller of the home address + * (with the initial controller set to the address corresponding to the first + * 20 bytes of the key), and there must not be a contract currently deployed + * at the home address. These conditions can be checked by calling + * `getHomeAddressInformation` and `isDeployable` with the same key. + * @param key bytes32 The unique value used to derive the home address. + * @param initializationRuntimeStorageContract address The storage contract + * with runtime code equal to the contract creation code that will be used to + * deploy the contract to the home address. + * @return The home address and runtime code hash of the deployed contract. + * @dev When deploying a contract to a home address via this method, the + * metamorphic initialization code will retrieve whatever initialization code + * currently resides at the specified address and use it to set up and deploy + * the desired contract to the home address. Bear in mind that the deployed + * contract will interpret msg.sender as the address of THIS contract, and not + * the address of the submitter - if the constructor of the deployed contract + * uses msg.sender to set up ownership or other variables, you must modify it + * to accept a constructor argument with the appropriate address, or + * alternately to hard-code the intended address. Also, if your contract DOES + * have constructor arguments, remember to include them as ABI-encoded + * arguments at the end of the initialization code, just as you would when + * performing a standard deploy. You may also want to provide the key to + * `setReverseLookup` in order to find it again using only the home address to + * prevent accidentally losing the key. + */ + function deployViaExistingRuntimeStorageContract( + bytes32 key, + address initializationRuntimeStorageContract + ) + external + payable + returns (address homeAddress, bytes32 runtimeCodeHash); + + /** + * @notice Burn an ERC721 token, set a supplied controller, and deploy a new + * contract with the initialization code stored in the runtime code at the + * specified initialization runtime storage contract to the home address + * corresponding to a given key. The submitter must be designated as either + * the owner of the token or as an approved spender. + * @param tokenId uint256 The ID of the ERC721 token to redeem. + * @param controller address The account that will be granted control of the + * home address corresponding to the given token. + * @param initializationRuntimeStorageContract address The storage contract + * with runtime code equal to the contract creation code that will be used to + * deploy the contract to the home address. + * @return The home address and runtime code hash of the deployed contract. + * @dev When deploying a contract to a home address via this method, the + * metamorphic initialization code will retrieve whatever initialization code + * currently resides at the specified address and use it to set up and deploy + * the desired contract to the home address. Bear in mind that the deployed + * contract will interpret msg.sender as the address of THIS contract, and not + * the address of the submitter - if the constructor of the deployed contract + * uses msg.sender to set up ownership or other variables, you must modify it + * to accept a constructor argument with the appropriate address, or + * alternately to hard-code the intended address. Also, if your contract DOES + * have constructor arguments, remember to include them as ABI-encoded + * arguments at the end of the initialization code, just as you would when + * performing a standard deploy. You may also want to provide the key to + * `setReverseLookup` in order to find it again using only the home address to + * prevent accidentally losing the key. The controller cannot be designated as + * the address of this contract, the null address, or the home address (the + * restriction on setting the home address as the controller is due to the + * fact that the home address will not be able to deploy to itself, as it + * needs to be empty before a contract can be deployed to it). Also, checks on + * the contract at the home address being empty or not having the correct + * controller are unnecessary, as they are performed when minting the token + * and cannot be altered until the token is redeemed. + */ + function redeemAndDeployViaExistingRuntimeStorageContract( + uint256 tokenId, + address controller, + address initializationRuntimeStorageContract + ) + external + payable + returns (address homeAddress, bytes32 runtimeCodeHash); + + /** + * @notice Deploy a new contract with the desired initialization code to the + * home address corresponding to a given derived key. Two conditions must be + * met: the submitter must be designated as the controller of the home + * address, and there must not be a contract currently deployed at the home + * address. These conditions can be checked by calling + * `getHomeAddressInformation` and `isDeployable` with the key obtained by + * calling `getDerivedKey`. + * @param salt bytes32 The salt value that is used to derive the key. + * @param initializationCode bytes The contract creation code that will be + * used to deploy the contract to the home address. + * @return The home address, derived key, and runtime code hash of the + * deployed contract. + * @dev In order to deploy the contract to the home address, a new contract + * will be deployed with runtime code set to the initialization code of the + * contract that will be deployed to the home address. Then, metamorphic + * initialization code will retrieve that initialization code and use it to + * set up and deploy the desired contract to the home address. Bear in mind + * that the deployed contract will interpret msg.sender as the address of THIS + * contract, and not the address of the submitter - if the constructor of the + * deployed contract uses msg.sender to set up ownership or other variables, + * you must modify it to accept a constructor argument with the appropriate + * address, or alternately to hard-code the intended address. Also, if your + * contract DOES have constructor arguments, remember to include them as + * ABI-encoded arguments at the end of the initialization code, just as you + * would when performing a standard deploy. You may want to provide the salt + * and submitter to `setDerivedReverseLookup` in order to find the salt, + * submitter, and derived key using only the home address to prevent + * accidentally losing them. + */ + function deriveKeyAndDeploy(bytes32 salt, bytes calldata initializationCode) + external + payable + returns (address homeAddress, bytes32 key, bytes32 runtimeCodeHash); + + /** + * @notice Deploy a new contract with the initialization code stored in the + * runtime code at the specified initialization runtime storage contract to + * the home address corresponding to a given derived key. Two conditions must + * be met: the submitter must be designated as the controller of the home + * address, and there must not be a contract currently deployed at the home + * address. These conditions can be checked by calling + * `getHomeAddressInformation` and `isDeployable` with the key obtained by + * calling `getDerivedKey`. + * @param salt bytes32 The salt value that is used to derive the key. + * @param initializationRuntimeStorageContract address The storage contract + * with runtime code equal to the contract creation code that will be used to + * deploy the contract to the home address. + * @return The home address, derived key, and runtime code hash of the + * deployed contract. + * @dev When deploying a contract to a home address via this method, the + * metamorphic initialization code will retrieve whatever initialization code + * currently resides at the specified address and use it to set up and deploy + * the desired contract to the home address. Bear in mind that the deployed + * contract will interpret msg.sender as the address of THIS contract, and not + * the address of the submitter - if the constructor of the deployed contract + * uses msg.sender to set up ownership or other variables, you must modify it + * to accept a constructor argument with the appropriate address, or + * alternately to hard-code the intended address. Also, if your contract DOES + * have constructor arguments, remember to include them as ABI-encoded + * arguments at the end of the initialization code, just as you would when + * performing a standard deploy. You may want to provide the salt and + * submitter to `setDerivedReverseLookup` in order to find the salt, + * submitter, and derived key using only the home address to prevent + * accidentally losing them. + */ + function deriveKeyAndDeployViaExistingRuntimeStorageContract( + bytes32 salt, + address initializationRuntimeStorageContract + ) + external + payable + returns (address homeAddress, bytes32 key, bytes32 runtimeCodeHash); + + /** + * @notice Mint multiple ERC721 tokens, designated by their keys, to the + * specified owner. Keys that aren't controlled, or that point to home + * addresses that are currently deployed, will be skipped. + * @param owner address The account that will be granted ownership of the + * ERC721 tokens. + * @param keys bytes32[] An array of values used to derive each home address. + * @dev If you plan to use this method regularly or want to keep gas costs to + * an absolute minimum, and are willing to go without standard ABI encoding, + * see `batchLock_63efZf` for a more efficient (and unforgiving) + * implementation. For batch token minting with *derived* keys, see + * `deriveKeysAndBatchLock`. + */ + function batchLock(address owner, bytes32[] calldata keys) external; + + /** + * @notice Mint multiple ERC721 tokens, designated by salts that are hashed + * with the caller's address to derive each key, to the specified owner. + * Derived keys that aren't controlled, or that point to home addresses that + * are currently deployed, will be skipped. + * @param owner address The account that will be granted ownership of the + * ERC721 tokens. + * @param salts bytes32[] An array of values used to derive each key and + * corresponding home address. + * @dev See `batchLock` for batch token minting with standard, non-derived + * keys. + */ + function deriveKeysAndBatchLock(address owner, bytes32[] calldata salts) + external; + + /** + * @notice Efficient version of `batchLock` that uses less gas. The first 20 + * bytes of each key are automatically populated using msg.sender, and the + * remaining key segments are passed in as a packed byte array, using twelve + * bytes per segment, with a function selector of 0x00000000 followed by a + * twenty-byte segment for the desired owner of the minted ERC721 tokens. Note + * that an attempt to lock a key that is not controlled or with its contract + * already deployed will cause the entire batch to revert. Checks on whether + * the owner is a valid ERC721 receiver are also skipped, similar to using + * `transferFrom` instead of `safeTransferFrom`. + */ + function batchLock_63efZf(/* packed owner and key segments */) external; + + /** + * @notice Submit a key to claim the "high score" - the lower the uint160 + * value of the key's home address, the higher the score. The high score + * holder has the exclusive right to recover lost ether and tokens on this + * contract. + * @param key bytes32 The unique value used to derive the home address that + * will determine the resultant score. + * @dev The high score must be claimed by a direct key (one that is submitted + * by setting the first 20 bytes of the key to the address of the submitter) + * and not by a derived key, and is non-transferrable. If you want to help + * people recover their lost tokens, you might consider deploying a contract + * to the high score address (probably a metamorphic one so that you can use + * the home address later) with your contact information. + */ + function claimHighScore(bytes32 key) external; + + /** + * @notice Transfer any ether or ERC20 tokens that have somehow ended up at + * this contract by specifying a token address (set to the null address for + * ether) as well as a recipient address. Only the high score holder can + * recover lost ether and tokens on this contract. + * @param token address The contract address of the ERC20 token to recover, or + * the null address for recovering Ether. + * @param recipient address payable The account where recovered funds should + * be transferred. + * @dev If you are trying to recover funds that were accidentally sent into + * this contract, see if you can contact the holder of the current high score, + * found by calling `getHighScore`. Better yet, try to find a new high score + * yourself! + */ + function recover(IERC20 token, address payable recipient) external; + + /** + * @notice "View" function to determine if a contract can currently be + * deployed to a home address given the corresponding key. A contract is only + * deployable if no account currently exists at the address - any existing + * contract must be destroyed via `SELFDESTRUCT` before a new contract can be + * deployed to a home address. This method does not modify state but is + * inaccessible via staticcall. + * @param key bytes32 The unique value used to derive the home address. + * @return A boolean signifying if a contract can be deployed to the home + * address that corresponds to the provided key. + * @dev This will not detect if a contract is not deployable due control + * having been relinquished on the key. + */ + function isDeployable(bytes32 key) + external + /* view */ + returns (bool deployable); + + /** + * @notice View function to get the current "high score", or the lowest + * uint160 value of a home address of all keys submitted. The high score + * holder has the exclusive right to recover lost ether and tokens on this + * contract. + * @return The current high score holder, their score, and the submitted key. + */ + function getHighScore() + external + view + returns (address holder, uint256 score, bytes32 key); + + /** + * @notice View function to get information on a home address given the + * corresponding key. + * @param key bytes32 The unique value used to derive the home address. + * @return The home address, the current controller of the address, the number + * of times the home address has been deployed to, and the code hash of the + * runtime currently found at the home address, if any. + * @dev There is also an `isDeployable` method for determining if a contract + * can be deployed to the address, but in extreme cases it must actually + * perform a dry-run to determine if the contract is deployable, which means + * that it does not support staticcalls. There is also a convenience method, + * `hasNeverBeenDeployed`, but the information it conveys can be determined + * from this method alone as well. + */ + function getHomeAddressInformation(bytes32 key) + external + view + returns ( + address homeAddress, + address controller, + uint256 deploys, + bytes32 currentRuntimeCodeHash + ); + + /** + * @notice View function to determine if no contract has ever been deployed to + * a home address given the corresponding key. This can be used to ensure that + * a given key or corresponding token is "new" or not. + * @param key bytes32 The unique value used to derive the home address. + * @return A boolean signifying if a contract has never been deployed using + * the supplied key before. + */ + function hasNeverBeenDeployed(bytes32 key) + external + view + returns (bool neverBeenDeployed); + + /** + * @notice View function to search for a known key, salt, and/or submitter + * given a supplied home address. Keys can be controlled directly by an + * address that matches the first 20 bytes of the key, or they can be derived + * from a salt and a submitter - if the key is not a derived key, the salt and + * submitter fields will both have a value of zero. + * @param homeAddress address The home address to check for key information. + * @return The key, salt, and/or submitter used to deploy to the home address, + * assuming they have been submitted to the reverse lookup. + * @dev To populate these values, call `setReverseLookup` for cases where keys + * are used directly or are the only value known, or `setDerivedReverseLookup` + * for cases where keys are derived from a known salt and submitter. + */ + function reverseLookup(address homeAddress) + external + view + returns (bytes32 key, bytes32 salt, address submitter); + + /** + * @notice Pure function to determine the key that is derived from a given + * salt and submitting address. + * @param salt bytes32 The salt value that is used to derive the key. + * @param submitter address The submitter of the salt value used to derive the + * key. + * @return The derived key. + */ + function getDerivedKey(bytes32 salt, address submitter) + external + pure + returns (bytes32 key); + + /** + * @notice Pure function to determine the home address that corresponds to + * a given key. + * @param key bytes32 The unique value used to derive the home address. + * @return The home address. + */ + function getHomeAddress(bytes32 key) + external + pure + returns (address homeAddress); + + /** + * @notice Pure function for retrieving the metamorphic initialization code + * used to deploy arbitrary contracts to home addresses. Provided for easy + * verification and for use in other applications. + * @return The 32-byte metamorphic initialization code. + * @dev This metamorphic init code works via the "metamorphic delegator" + * mechanism, which is explained in greater detail at `_deployToHomeAddress`. + */ + function getMetamorphicDelegatorInitializationCode() + external + pure + returns (bytes32 metamorphicDelegatorInitializationCode); + + /** + * @notice Pure function for retrieving the keccak256 of the metamorphic + * initialization code used to deploy arbitrary contracts to home addresses. + * This is the value that you should use, along with this contract's address + * and a caller address that you control, to mine for an partucular type of + * home address (such as one at a compact or gas-efficient address). + * @return The keccak256 hash of the metamorphic initialization code. + */ + function getMetamorphicDelegatorInitializationCodeHash() + external + pure + returns (bytes32 metamorphicDelegatorInitializationCodeHash); + + /** + * @notice Pure function for retrieving the prelude that will be inserted + * ahead of the code payload in order to deploy a runtime storage contract. + * @return The 11-byte "arbitrary runtime" prelude. + */ + function getArbitraryRuntimeCodePrelude() + external + pure + returns (bytes11 prelude); +} + + +/** + * @title ERC721 Non-Fungible Token Standard basic interface + * @dev see https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721 { + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + function balanceOf(address owner) external view returns (uint256 balance); + function ownerOf(uint256 tokenId) external view returns (address owner); + + function approve(address to, uint256 tokenId) external; + function getApproved(uint256 tokenId) external view returns (address operator); + + function setApprovalForAll(address operator, bool _approved) external; + function isApprovedForAll(address owner, address operator) external view returns (bool); + + function transferFrom(address from, address to, uint256 tokenId) external; + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; +} + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable { + function totalSupply() external view returns (uint256); + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + function tokenByIndex(uint256 index) external view returns (uint256); +} + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata { + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function tokenURI(uint256 tokenId) external view returns (string memory); +} + + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @notice Handle the receipt of an NFT + * @dev The ERC721 smart contract calls this function on the recipient + * after a `safeTransfer`. This function MUST return the function selector, + * otherwise the caller will revert the transaction. The selector to be + * returned can be obtained as `this.onERC721Received.selector`. This + * function MAY throw to revert and reject the transfer. + * Note: the ERC721 contract address is always the message sender. + * @param operator The address which called `safeTransferFrom` function + * @param from The address which previously owned the token + * @param tokenId The NFT identifier which is being transferred + * @param data Additional data with no specified format + * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + */ + function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) + external + returns (bytes4); +} + + +/** + * @title ERC1412 Batch Transfers For Non-Fungible Tokens + * @dev the ERC-165 identifier for this interface is 0x2b89bcaa + */ +interface IERC1412 { + /// @notice Transfers the ownership of multiple NFTs from one address to another address + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenIds The NFTs to transfer + /// @param _data Additional data with no specified format, sent in call to `_to` + function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _tokenIds, bytes calldata _data) external; + + /// @notice Transfers the ownership of multiple NFTs from one address to another address + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenIds The NFTs to transfer + function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _tokenIds) external; +} + + +/** + * @title IERC165 + * @dev https://eips.ethereum.org/EIPS/eip-165 + */ +interface IERC165 { + /** + * @notice Query if a contract implements an interface + * @param interfaceId The interface identifier, as specified in ERC-165 + * @dev Interface identification is specified in ERC-165. This function + * uses less than 30,000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + + +/** + * @title ERC20 interface + * @dev see https://eips.ethereum.org/EIPS/eip-20 + */ +interface IERC20 { + function transfer(address to, uint256 value) external returns (bool); + + function approve(address spender, uint256 value) external returns (bool); + + function transferFrom(address from, address to, uint256 value) external returns (bool); + + function totalSupply() external view returns (uint256); + + function balanceOf(address who) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + event Transfer(address indexed from, address indexed to, uint256 value); + + event Approval(address indexed owner, address indexed spender, uint256 value); +} + + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, "SafeMath: division by zero"); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b != 0, "SafeMath: modulo by zero"); + return a % b; + } +} + + +/** + * Utility library of inline functions on addresses + */ +library Address { + /** + * Returns whether the target address is a contract + * @dev This function will return false if invoked during the constructor of a contract, + * as the code is not actually created until after the constructor finishes. + * @param account address of the account to check + * @return whether the target address is a contract + */ + function isContract(address account) internal view returns (bool) { + uint256 size; + // XXX Currently there is no better way to check if there is a contract in an address + // than to check the size of the code at that address. + // See https://ethereum.stackexchange.com/a/14016/36603 + // for more details about how this works. + // TODO Check this again before the Serenity release, because all addresses will be + // contracts then. + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } +} + + +/** + * @title Counters + * @author Matt Condon (@shrugs) + * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number + * of elements in a mapping, issuing ERC721 ids, or counting request ids + * + * Include with `using Counters for Counters.Counter;` + * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the SafeMath + * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never + * directly accessed. + */ +library Counters { + using SafeMath for uint256; + + struct Counter { + // This variable should never be directly accessed by users of the library: interactions must be restricted to + // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add + // this feature: see https://github.com/ethereum/solidity/issues/4637 + uint256 _value; // default: 0 + } + + function current(Counter storage counter) internal view returns (uint256) { + return counter._value; + } + + function increment(Counter storage counter) internal { + counter._value += 1; + } + + function decrement(Counter storage counter) internal { + counter._value = counter._value.sub(1); + } +} + + +/** + * @dev Implementation of the `IERC165` interface. + * + * Contracts may inherit from this and call `_registerInterface` to declare + * their support of an interface. + */ +contract ERC165 is IERC165 { + /* + * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 + */ + bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; + + /** + * @dev Mapping of interface ids to whether or not it's supported. + */ + mapping(bytes4 => bool) private _supportedInterfaces; + + constructor () internal { + // Derived contracts need only register support for their own interfaces, + // we register support for ERC165 itself here + _registerInterface(_INTERFACE_ID_ERC165); + } + + /** + * @dev See `IERC165.supportsInterface`. + * + * Time complexity O(1), guaranteed to always use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool) { + return _supportedInterfaces[interfaceId]; + } + + /** + * @dev Registers the contract as an implementer of the interface defined by + * `interfaceId`. Support of the actual ERC165 interface is automatic and + * registering its interface id is not required. + * + * See `IERC165.supportsInterface`. + * + * Requirements: + * + * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`). + */ + function _registerInterface(bytes4 interfaceId) internal { + require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); + _supportedInterfaces[interfaceId] = true; + } +} + + +/** + * @title ERC721 Non-Fungible Token Standard basic implementation + * @dev see https://eips.ethereum.org/EIPS/eip-721 + */ +contract ERC721 is ERC165, IERC721 { + using SafeMath for uint256; + using Address for address; + using Counters for Counters.Counter; + + // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector` + bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; + + // Mapping from token ID to owner + mapping (uint256 => address) private _tokenOwner; + + // Mapping from token ID to approved address + mapping (uint256 => address) private _tokenApprovals; + + // Mapping from owner to number of owned token + mapping (address => Counters.Counter) private _ownedTokensCount; + + // Mapping from owner to operator approvals + mapping (address => mapping (address => bool)) private _operatorApprovals; + + bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd; + /* + * 0x80ac58cd === + * bytes4(keccak256('balanceOf(address)')) ^ + * bytes4(keccak256('ownerOf(uint256)')) ^ + * bytes4(keccak256('approve(address,uint256)')) ^ + * bytes4(keccak256('getApproved(uint256)')) ^ + * bytes4(keccak256('setApprovalForAll(address,bool)')) ^ + * bytes4(keccak256('isApprovedForAll(address,address)')) ^ + * bytes4(keccak256('transferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) + */ + + constructor () public { + // register the supported interfaces to conform to ERC721 via ERC165 + _registerInterface(_INTERFACE_ID_ERC721); + } + + /** + * @dev Gets the balance of the specified address + * @param owner address to query the balance of + * @return uint256 representing the amount owned by the passed address + */ + function balanceOf(address owner) public view returns (uint256) { + require(owner != address(0)); + return _ownedTokensCount[owner].current(); + } + + /** + * @dev Gets the owner of the specified token ID + * @param tokenId uint256 ID of the token to query the owner of + * @return address currently marked as the owner of the given token ID + */ + function ownerOf(uint256 tokenId) public view returns (address) { + address owner = _tokenOwner[tokenId]; + require(owner != address(0)); + return owner; + } + + /** + * @dev Approves another address to transfer the given token ID + * The zero address indicates there is no approved address. + * There can only be one approved address per token at a given time. + * Can only be called by the token owner or an approved operator. + * @param to address to be approved for the given token ID + * @param tokenId uint256 ID of the token to be approved + */ + function approve(address to, uint256 tokenId) public { + address owner = ownerOf(tokenId); + require(to != owner); + require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); + + _tokenApprovals[tokenId] = to; + emit Approval(owner, to, tokenId); + } + + /** + * @dev Gets the approved address for a token ID, or zero if no address set + * Reverts if the token ID does not exist. + * @param tokenId uint256 ID of the token to query the approval of + * @return address currently approved for the given token ID + */ + function getApproved(uint256 tokenId) public view returns (address) { + require(_exists(tokenId)); + return _tokenApprovals[tokenId]; + } + + /** + * @dev Sets or unsets the approval of a given operator + * An operator is allowed to transfer all tokens of the sender on their behalf + * @param to operator address to set the approval + * @param approved representing the status of the approval to be set + */ + function setApprovalForAll(address to, bool approved) public { + require(to != msg.sender); + _operatorApprovals[msg.sender][to] = approved; + emit ApprovalForAll(msg.sender, to, approved); + } + + /** + * @dev Tells whether an operator is approved by a given owner + * @param owner owner address which you want to query the approval of + * @param operator operator address which you want to query the approval of + * @return bool whether the given operator is approved by the given owner + */ + function isApprovedForAll(address owner, address operator) public view returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev Transfers the ownership of a given token ID to another address + * Usage of this method is discouraged, use `safeTransferFrom` whenever possible + * Requires the msg.sender to be the owner, approved, or operator + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function transferFrom(address from, address to, uint256 tokenId) public { + require(_isApprovedOrOwner(msg.sender, tokenId)); + + _transferFrom(from, to, tokenId); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * Requires the msg.sender to be the owner, approved, or operator + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * Requires the msg.sender to be the owner, approved, or operator + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes data to send along with a safe transfer check + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public { + transferFrom(from, to, tokenId); + require(_checkOnERC721Received(from, to, tokenId, _data)); + } + + /** + * @dev Returns whether the specified token exists + * @param tokenId uint256 ID of the token to query the existence of + * @return bool whether the token exists + */ + function _exists(uint256 tokenId) internal view returns (bool) { + address owner = _tokenOwner[tokenId]; + return owner != address(0); + } + + /** + * @dev Returns whether the given spender can transfer a given token ID + * @param spender address of the spender to query + * @param tokenId uint256 ID of the token to be transferred + * @return bool whether the msg.sender is approved for the given token ID, + * is an operator of the owner, or is the owner of the token + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { + address owner = ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + + /** + * @dev Internal function to mint a new token + * Reverts if the given token ID already exists + * @param to The address that will own the minted token + * @param tokenId uint256 ID of the token to be minted + */ + function _mint(address to, uint256 tokenId) internal { + require(to != address(0)); + require(!_exists(tokenId)); + + _tokenOwner[tokenId] = to; + _ownedTokensCount[to].increment(); + + emit Transfer(address(0), to, tokenId); + } + + /** + * @dev Internal function to burn a specific token + * Reverts if the token does not exist + * Deprecated, use _burn(uint256) instead. + * @param owner owner of the token to burn + * @param tokenId uint256 ID of the token being burned + */ + function _burn(address owner, uint256 tokenId) internal { + require(ownerOf(tokenId) == owner); + + _clearApproval(tokenId); + + _ownedTokensCount[owner].decrement(); + _tokenOwner[tokenId] = address(0); + + emit Transfer(owner, address(0), tokenId); + } + + /** + * @dev Internal function to burn a specific token + * Reverts if the token does not exist + * @param tokenId uint256 ID of the token being burned + */ + function _burn(uint256 tokenId) internal { + _burn(ownerOf(tokenId), tokenId); + } + + /** + * @dev Internal function to transfer ownership of a given token ID to another address. + * As opposed to transferFrom, this imposes no restrictions on msg.sender. + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function _transferFrom(address from, address to, uint256 tokenId) internal { + require(ownerOf(tokenId) == from); + require(to != address(0)); + + _clearApproval(tokenId); + + _ownedTokensCount[from].decrement(); + _ownedTokensCount[to].increment(); + + _tokenOwner[tokenId] = to; + + emit Transfer(from, to, tokenId); + } + + /** + * @dev Internal function to invoke `onERC721Received` on a target address + * The call is not executed if the target address is not a contract + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return bool whether the call correctly returned the expected magic value + */ + function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) + internal returns (bool) + { + if (!to.isContract()) { + return true; + } + + bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data); + return (retval == _ERC721_RECEIVED); + } + + /** + * @dev Private function to clear current approval of a given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function _clearApproval(uint256 tokenId) private { + if (_tokenApprovals[tokenId] != address(0)) { + _tokenApprovals[tokenId] = address(0); + } + } +} + + +/** + * @title ERC-721 Non-Fungible Token with optional enumeration extension logic + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable { + // Mapping from owner to list of owned token IDs + mapping(address => uint256[]) private _ownedTokens; + + // Mapping from token ID to index of the owner tokens list + mapping(uint256 => uint256) private _ownedTokensIndex; + + // Array with all token ids, used for enumeration + uint256[] private _allTokens; + + // Mapping from token id to position in the allTokens array + mapping(uint256 => uint256) private _allTokensIndex; + + /* + * bytes4(keccak256('totalSupply()')) == 0x18160ddd + * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59 + * bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7 + * + * => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63 + */ + bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63; + + /** + * @dev Constructor function. + */ + constructor () public { + // register the supported interface to conform to ERC721Enumerable via ERC165 + _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE); + } + + /** + * @dev Gets the token ID at a given index of the tokens list of the requested owner. + * @param owner address owning the tokens list to be accessed + * @param index uint256 representing the index to be accessed of the requested tokens list + * @return uint256 token ID at the given index of the tokens list owned by the requested address + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) { + require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds"); + return _ownedTokens[owner][index]; + } + + /** + * @dev Gets the total amount of tokens stored by the contract. + * @return uint256 representing the total amount of tokens + */ + function totalSupply() public view returns (uint256) { + return _allTokens.length; + } + + /** + * @dev Gets the token ID at a given index of all the tokens in this contract + * Reverts if the index is greater or equal to the total number of tokens. + * @param index uint256 representing the index to be accessed of the tokens list + * @return uint256 token ID at the given index of the tokens list + */ + function tokenByIndex(uint256 index) public view returns (uint256) { + require(index < totalSupply(), "ERC721Enumerable: global index out of bounds"); + return _allTokens[index]; + } + + /** + * @dev Internal function to transfer ownership of a given token ID to another address. + * As opposed to transferFrom, this imposes no restrictions on msg.sender. + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function _transferFrom(address from, address to, uint256 tokenId) internal { + super._transferFrom(from, to, tokenId); + + _removeTokenFromOwnerEnumeration(from, tokenId); + + _addTokenToOwnerEnumeration(to, tokenId); + } + + /** + * @dev Internal function to mint a new token. + * Reverts if the given token ID already exists. + * @param to address the beneficiary that will own the minted token + * @param tokenId uint256 ID of the token to be minted + */ + function _mint(address to, uint256 tokenId) internal { + super._mint(to, tokenId); + + _addTokenToOwnerEnumeration(to, tokenId); + + _addTokenToAllTokensEnumeration(tokenId); + } + + /** + * @dev Internal function to burn a specific token. + * Reverts if the token does not exist. + * Deprecated, use _burn(uint256) instead. + * @param owner owner of the token to burn + * @param tokenId uint256 ID of the token being burned + */ + function _burn(address owner, uint256 tokenId) internal { + super._burn(owner, tokenId); + + _removeTokenFromOwnerEnumeration(owner, tokenId); + // Since tokenId will be deleted, we can clear its slot in _ownedTokensIndex to trigger a gas refund + _ownedTokensIndex[tokenId] = 0; + + _removeTokenFromAllTokensEnumeration(tokenId); + } + + /** + * @dev Private function to add a token to this extension's ownership-tracking data structures. + * @param to address representing the new owner of the given token ID + * @param tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private { + _ownedTokensIndex[tokenId] = _ownedTokens[to].length; + _ownedTokens[to].push(tokenId); + } + + /** + * @dev Private function to add a token to this extension's token tracking data structures. + * @param tokenId uint256 ID of the token to be added to the tokens list + */ + function _addTokenToAllTokensEnumeration(uint256 tokenId) private { + _allTokensIndex[tokenId] = _allTokens.length; + _allTokens.push(tokenId); + } + + /** + * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that + * while the token is not assigned a new owner, the _ownedTokensIndex mapping is _not_ updated: this allows for + * gas optimizations e.g. when performing a transfer operation (avoiding double writes). + * This has O(1) time complexity, but alters the order of the _ownedTokens array. + * @param from address representing the previous owner of the given token ID + * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private { + // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = _ownedTokens[from].length.sub(1); + uint256 tokenIndex = _ownedTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary + if (tokenIndex != lastTokenIndex) { + uint256 lastTokenId = _ownedTokens[from][lastTokenIndex]; + + _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + } + + // This also deletes the contents at the last position of the array + _ownedTokens[from].length--; + + // Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occupied by + // lastTokenId, or just over the end of the array if the token was the last one). + } + + /** + * @dev Private function to remove a token from this extension's token tracking data structures. + * This has O(1) time complexity, but alters the order of the _allTokens array. + * @param tokenId uint256 ID of the token to be removed from the tokens list + */ + function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private { + // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = _allTokens.length.sub(1); + uint256 tokenIndex = _allTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so + // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding + // an 'if' statement (like in _removeTokenFromOwnerEnumeration) + uint256 lastTokenId = _allTokens[lastTokenIndex]; + + _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + + // This also deletes the contents at the last position of the array + _allTokens.length--; + _allTokensIndex[tokenId] = 0; + } +} + + +/** + * @title HomeWork (version 1) + * @author 0age + * @notice Homework is a utility to find, share, and reuse "home" addresses for + * contracts. Anyone can work to find a new home address by searching for keys, + * a 32-byte value with the first 20 bytes equal to the finder's calling address + * (or derived by hashing an arbitrary 32-byte salt and the caller's address), + * and can then deploy any contract they like (even one with a constructor) to + * the address, or mint an ERC721 token that the owner can redeem that will then + * allow them to do the same. Also, if the contract is `SELFDESTRUCT`ed, a new + * contract can be redeployed by the current controller to the same address! + * @dev This contract allows contract addresses to be located ahead of time, and + * for arbitrary bytecode to be deployed (and redeployed if so desired, i.e. + * metamorphic contracts) to the located address by a designated controller. To + * enable this, the contract first deploys an "initialization-code-in-runtime" + * contract, with the creation code of the contract you want to deploy stored in + * RUNTIME code. Then, to deploy the actual contract, it retrieves the address + * of the storage contract and `DELEGATECALL`s into it to execute the init code + * and, if successful, retrieves and returns the contract runtime code. Rather + * than using a located address directly, you can also lock it in the contract + * and mint and ERC721 token for it, which can then be redeemed in order to gain + * control over deployment to the address (note that tokens may not be minted if + * the contract they control currently has a deployed contract at that address). + * Once a contract undergoes metamorphosis, all existing storage will be deleted + * and any existing contract code will be replaced with the deployed contract + * code of the new implementation contract. The mechanisms behind this contract + * are highly experimental - proceed with caution and please share any exploits + * or optimizations you discover. + */ +contract HomeWork is IHomeWork, ERC721Enumerable, IERC721Metadata, IERC1412 { + // Allocate storage to track the current initialization-in-runtime contract. + address private _initializationRuntimeStorageContract; + + // Finder of home address with lowest uint256 value can recover lost funds. + bytes32 private _highScoreKey; + + // Track information on the Home address corresponding to each key. + mapping (bytes32 => HomeAddress) private _home; + + // Provide optional reverse-lookup for key derivation of a given home address. + mapping (address => KeyInformation) private _key; + + // Set 0xff + address(this) as a constant to use when deriving home addresses. + bytes21 private constant _FF_AND_THIS_CONTRACT = bytes21( + 0xff0000000000001b84b1cb32787B0D64758d019317 + ); + + // Set the address of the tokenURI runtime storage contract as a constant. + address private constant _URI_END_SEGMENT_STORAGE = address( + 0x000000000071C1c84915c17BF21728BfE4Dac3f3 + ); + + // Deploy arbitrary contracts to home addresses using metamorphic init code. + bytes32 private constant _HOME_INIT_CODE = bytes32( + 0x5859385958601c335a585952fa1582838382515af43d3d93833e601e57fd5bf3 + ); + + // Compute hash of above metamorphic init code in order to compute addresses. + bytes32 private constant _HOME_INIT_CODE_HASH = bytes32( + 0x7816562e7f85866cae07183593075f3b5ec32aeff914a0693e20aaf39672babc + ); + + // Write arbitrary code to a contract's runtime using the following prelude. + bytes11 private constant _ARBITRARY_RUNTIME_PRELUDE = bytes11( + 0x600b5981380380925939f3 + ); + + // Set EIP165 interface IDs as constants (already set 165 and 721+enumerable). + bytes4 private constant _INTERFACE_ID_HOMEWORK = 0xe5399799; + /* this.deploy.selector ^ this.lock.selector ^ this.redeem.selector ^ + this.assignController.selector ^ this.relinquishControl.selector ^ + this.redeemAndDeploy.selector ^ this.deriveKey.selector ^ + this.deriveKeyAndLock.selector ^ + this.deriveKeyAndAssignController.selector ^ + this.deriveKeyAndRelinquishControl.selector ^ + this.setReverseLookup.selector ^ this.setDerivedReverseLookup.selector ^ + this.deployRuntimeStorageContract.selector ^ + this.deployViaExistingRuntimeStorageContract.selector ^ + this.redeemAndDeployViaExistingRuntimeStorageContract.selector ^ + this.deriveKeyAndDeploy.selector ^ + this.deriveKeyAndDeployViaExistingRuntimeStorageContract.selector ^ + this.batchLock.selector ^ this.deriveKeysAndBatchLock.selector ^ + this.batchLock_63efZf.selector ^ this.claimHighScore.selector ^ + this.recover.selector ^ this.isDeployable.selector ^ + this.getHighScore.selector ^ this.getHomeAddressInformation.selector ^ + this.hasNeverBeenDeployed.selector ^ this.reverseLookup.selector ^ + this.getDerivedKey.selector ^ this.getHomeAddress.selector ^ + this.getMetamorphicDelegatorInitializationCode.selector ^ + this.getMetamorphicDelegatorInitializationCodeHash.selector ^ + this.getArbitraryRuntimeCodePrelude.selector == 0xe5399799 + */ + + bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f; + + bytes4 private constant _INTERFACE_ID_ERC1412_BATCH_TRANSFERS = 0x2b89bcaa; + + // Set name of this contract as a constant (hex encoding is to support emoji). + string private constant _NAME = ( + hex"486f6d65576f726b20f09f8fa0f09f9ba0efb88f" + ); + + // Set symbol of this contract as a constant. + string private constant _SYMBOL = "HWK"; + + // Set the start of each token URI for issued ERC721 tokens as a constant. + bytes private constant _URI_START_SEGMENT = abi.encodePacked( + hex"646174613a6170706c69636174696f6e2f6a736f6e2c7b226e616d65223a22486f6d65", + hex"253230416464726573732532302d2532303078" + ); /* data:application/json,{"name":"Home%20Address%20-%200x */ + + // Store reused revert messages as constants. + string private constant _ACCOUNT_EXISTS = string( + "Only non-existent accounts can be deployed or used to mint tokens." + ); + + string private constant _ONLY_CONTROLLER = string( + "Only the designated controller can call this function." + ); + + string private constant _NO_INIT_CODE_SUPPLIED = string( + "Cannot deploy a contract with no initialization code supplied." + ); + + /** + * @notice In the constructor, verify that deployment addresses are correct + * and that supplied constant hash value of the contract creation code used to + * deploy arbitrary contracts to home addresses is valid, and set an initial + * high score key with the null address as the high score "holder". ERC165 + * supported interfaces are all registered during initizialization as well. + */ + constructor() public { + // Verify that the deployment address is set correctly as a constant. + assert(address(this) == address(uint160(uint168(_FF_AND_THIS_CONTRACT)))); + + // Verify the derivation of the deployment address. + bytes32 initialDeployKey = bytes32( + 0x486f6d65576f726b20f09f8fa0f09f9ba0efb88faa3c548a76f9bd3c000c0000 + ); + assert(address(this) == address( + uint160( // Downcast to match the address type. + uint256( // Convert to uint to truncate upper digits. + keccak256( // Compute the CREATE2 hash using 4 inputs. + abi.encodePacked( // Pack all inputs to the hash together. + bytes1(0xff), // Start with 0xff to distinguish from RLP. + msg.sender, // The deployer will be the caller. + initialDeployKey, // Pass in the supplied key as the salt. + _HOME_INIT_CODE_HASH // The metamorphic initialization code hash. + ) + ) + ) + ) + )); + + // Verify the derivation of the tokenURI runtime storage address. + bytes32 uriDeployKey = bytes32( + 0x486f6d65576f726b202d20746f6b656e55524920c21352fee5a62228db000000 + ); + bytes32 uriInitCodeHash = bytes32( + 0xdea98294867e3fdc48eb5975ecc53a79e2e1ea6e7e794137a9c34c4dd1565ba2 + ); + assert(_URI_END_SEGMENT_STORAGE == address( + uint160( // Downcast to match the address type. + uint256( // Convert to uint to truncate upper digits. + keccak256( // Compute the CREATE2 hash using 4 inputs. + abi.encodePacked( // Pack all inputs to the hash together. + bytes1(0xff), // Start with 0xff to distinguish from RLP. + msg.sender, // The deployer will be the caller. + uriDeployKey, // Pass in the supplied key as the salt. + uriInitCodeHash // The storage contract init code hash. + ) + ) + ) + ) + )); + + // Verify that the correct runtime code is at the tokenURI storage contract. + bytes32 expectedRuntimeStorageHash = bytes32( + 0x8834602968080bb1df9c44c9834c0a93533b72bbfa3865ee2c5be6a0c4125fc3 + ); + address runtimeStorage = _URI_END_SEGMENT_STORAGE; + bytes32 runtimeStorageHash; + assembly { runtimeStorageHash := extcodehash(runtimeStorage) } + assert(runtimeStorageHash == expectedRuntimeStorageHash); + + // Verify that the supplied hash for the metamorphic init code is valid. + assert(keccak256(abi.encode(_HOME_INIT_CODE)) == _HOME_INIT_CODE_HASH); + + // Set an initial high score key with the null address as the submitter. + _highScoreKey = bytes32( + 0x0000000000000000000000000000000000000000ffffffffffffffffffffffff + ); + + // Register EIP165 interface for HomeWork. + _registerInterface(_INTERFACE_ID_HOMEWORK); + + // Register EIP165 interface for ERC721 metadata. + _registerInterface(_INTERFACE_ID_ERC721_METADATA); + + // Register EIP165 interface for ERC1412 (batch transfers). + _registerInterface(_INTERFACE_ID_ERC1412_BATCH_TRANSFERS); + } + + /** + * @notice Deploy a new contract with the desired initialization code to the + * home address corresponding to a given key. Two conditions must be met: the + * submitter must be designated as the controller of the home address (with + * the initial controller set to the address corresponding to the first twenty + * bytes of the key), and there must not be a contract currently deployed at + * the home address. These conditions can be checked by calling + * `getHomeAddressInformation` and `isDeployable` with the same key. + * @param key bytes32 The unique value used to derive the home address. + * @param initializationCode bytes The contract creation code that will be + * used to deploy the contract to the home address. + * @return The home address of the deployed contract. + * @dev In order to deploy the contract to the home address, a new contract + * will be deployed with runtime code set to the initialization code of the + * contract that will be deployed to the home address. Then, metamorphic + * initialization code will retrieve that initialization code and use it to + * set up and deploy the desired contract to the home address. Bear in mind + * that the deployed contract will interpret msg.sender as the address of THIS + * contract, and not the address of the submitter - if the constructor of the + * deployed contract uses msg.sender to set up ownership or other variables, + * you must modify it to accept a constructor argument with the appropriate + * address, or alternately to hard-code the intended address. Also, if your + * contract DOES have constructor arguments, remember to include them as + * ABI-encoded arguments at the end of the initialization code, just as you + * would when performing a standard deploy. You may also want to provide the + * key to `setReverseLookup` in order to find it again using only the home + * address to prevent accidentally losing the key. + */ + function deploy(bytes32 key, bytes calldata initializationCode) + external + payable + onlyEmpty(key) + onlyControllerDeployer(key) + returns (address homeAddress, bytes32 runtimeCodeHash) + { + // Ensure that initialization code was supplied. + require(initializationCode.length > 0, _NO_INIT_CODE_SUPPLIED); + + // Deploy the initialization storage contract and set address in storage. + _initializationRuntimeStorageContract = _deployRuntimeStorageContract( + initializationCode + ); + + // Use metamorphic initialization code to deploy contract to home address. + (homeAddress, runtimeCodeHash) = _deployToHomeAddress(key); + } + + /** + * @notice Mint an ERC721 token to the supplied owner that can be redeemed in + * order to gain control of a home address corresponding to a given key. Two + * conditions must be met: the submitter must be designated as the controller + * of the home address (with the initial controller set to the address + * corresponding to the first 20 bytes of the key), and there must not be a + * contract currently deployed at the home address. These conditions can be + * checked by calling `getHomeAddressInformation` and `isDeployable` with the + * same key. + * @param key bytes32 The unique value used to derive the home address. + * @param owner address The account that will be granted ownership of the + * ERC721 token. + * @dev In order to mint an ERC721 token, the assocated home address cannot be + * in use, or else the token will not be able to deploy to the home address. + * The controller is set to this contract until the token is redeemed, at + * which point the redeemer designates a new controller for the home address. + * The key of the home address and the tokenID of the ERC721 token are the + * same value, but different types (bytes32 vs. uint256). + */ + function lock(bytes32 key, address owner) + external + onlyEmpty(key) + onlyController(key) + { + // Ensure that the specified owner is a valid ERC721 receiver. + _validateOwner(owner, key); + + // Get the HomeAddress storage struct from the mapping using supplied key. + HomeAddress storage home = _home[key]; + + // Set the exists flag to true and the controller to this contract. + home.exists = true; + home.controller = address(this); + + // Emit an event signifying that this contract is now the controller. + emit NewController(key, address(this)); + + // Mint the ERC721 token to the designated owner. + _mint(owner, uint256(key)); + } + + /** + * @notice Burn an ERC721 token to allow the supplied controller to gain the + * ability to deploy to the home address corresponding to the key matching the + * burned token. The submitter must be designated as either the owner of the + * token or as an approved spender. + * @param tokenId uint256 The ID of the ERC721 token to redeem. + * @param controller address The account that will be granted control of the + * home address corresponding to the given token. + * @dev The controller cannot be designated as the address of this contract, + * the null address, or the home address (the restriction on setting the home + * address as the controller is due to the fact that the home address will not + * be able to deploy to itself, as it needs to be empty before a contract can + * be deployed to it). + */ + function redeem(uint256 tokenId, address controller) + external + onlyTokenOwnerOrApprovedSpender(tokenId) + { + // Convert the token ID to a bytes32 key. + bytes32 key = bytes32(tokenId); + + // Prevent the controller from being set to prohibited account values. + _validateController(controller, key); + + // Burn the ERC721 token in question. + _burn(tokenId); + + // Assign the new controller to the corresponding home address. + _home[key].controller = controller; + + // Emit an event with the new controller. + emit NewController(key, controller); + } + + /** + * @notice Transfer control over deployment to the home address corresponding + * to a given key. The caller must be designated as the current controller of + * the home address (with the initial controller set to the address + * corresponding to the first 20 bytes of the key) - This condition can be + * checked by calling `getHomeAddressInformation` with the same key. + * @param key bytes32 The unique value used to derive the home address. + * @param controller address The account that will be granted control of the + * home address corresponding to the given key. + * @dev The controller cannot be designated as the address of this contract, + * the null address, or the home address (the restriction on setting the home + * address as the controller is due to the fact that the home address will not + * be able to deploy to itself, as it needs to be empty before a contract can + * be deployed to it). + */ + function assignController(bytes32 key, address controller) + external + onlyController(key) + { + // Prevent the controller from being set to prohibited account values. + _validateController(controller, key); + + // Assign the new controller to the corresponding home address. + HomeAddress storage home = _home[key]; + home.exists = true; + home.controller = controller; + + // Emit an event with the new controller. + emit NewController(key, controller); + } + + /** + * @notice Transfer control over deployment to the home address corresponding + * to a given key to the null address, which will prevent it from being + * deployed to again in the future. The caller must be designated as the + * current controller of the corresponding home address (with the initial + * controller set to the address corresponding to the first 20 bytes of the + * key) - This condition can be checked by calling `getHomeAddressInformation` + * with the same key. + * @param key bytes32 The unique value used to derive the home address. + */ + function relinquishControl(bytes32 key) + external + onlyController(key) + { + // Assign the null address as the controller of the given key. + HomeAddress storage home = _home[key]; + home.exists = true; + home.controller = address(0); + + // Emit an event with the null address as the controller. + emit NewController(key, address(0)); + } + + /** + * @notice Burn an ERC721 token, set a supplied controller, and deploy a new + * contract with the supplied initialization code to the corresponding home + * address for the given token. The submitter must be designated as either the + * owner of the token or as an approved spender. + * @param tokenId uint256 The ID of the ERC721 token to redeem. + * @param controller address The account that will be granted control of the + * home address corresponding to the given token. + * @param initializationCode bytes The contract creation code that will be + * used to deploy the contract to the home address. + * @return The home address and runtime code hash of the deployed contract. + * @dev In order to deploy the contract to the home address, a new contract + * will be deployed with runtime code set to the initialization code of the + * contract that will be deployed to the home address. Then, metamorphic + * initialization code will retrieve that initialization code and use it to + * set up and deploy the desired contract to the home address. Bear in mind + * that the deployed contract will interpret msg.sender as the address of THIS + * contract, and not the address of the submitter - if the constructor of the + * deployed contract uses msg.sender to set up ownership or other variables, + * you must modify it to accept a constructor argument with the appropriate + * address, or alternately to hard-code the intended address. Also, if your + * contract DOES have constructor arguments, remember to include them as + * ABI-encoded arguments at the end of the initialization code, just as you + * would when performing a standard deploy. You may also want to provide the + * key to `setReverseLookup` in order to find it again using only the home + * address to prevent accidentally losing the key. The controller cannot be + * designated as the address of this contract, the null address, or the home + * address (the restriction on setting the home address as the controller is + * due to the fact that the home address will not be able to deploy to itself, + * as it needs to be empty before a contract can be deployed to it). Also, + * checks on the contract at the home address being empty or not having the + * correct controller are unnecessary, as they are performed when minting the + * token and cannot be altered until the token is redeemed. + */ + function redeemAndDeploy( + uint256 tokenId, + address controller, + bytes calldata initializationCode + ) + external + payable + onlyTokenOwnerOrApprovedSpender(tokenId) + returns (address homeAddress, bytes32 runtimeCodeHash) + { + // Ensure that initialization code was supplied. + require(initializationCode.length > 0, _NO_INIT_CODE_SUPPLIED); + + // Convert the token ID to a bytes32 key. + bytes32 key = bytes32(tokenId); + + // Prevent the controller from being set to prohibited account values. + _validateController(controller, key); + + // Burn the ERC721 token in question. + _burn(tokenId); + + // Deploy the initialization storage contract and set address in storage. + _initializationRuntimeStorageContract = _deployRuntimeStorageContract( + initializationCode + ); + + // Set provided controller and increment contract deploy count at once. + HomeAddress storage home = _home[key]; + home.exists = true; + home.controller = controller; + home.deploys += 1; + + // Emit an event with the new controller. + emit NewController(key, controller); + + // Use metamorphic initialization code to deploy contract to home address. + (homeAddress, runtimeCodeHash) = _deployToHomeAddress(key); + } + + /** + * @notice Derive a new key by concatenating an arbitrary 32-byte salt value + * and the address of the caller and performing a keccak256 hash. This allows + * for the creation of keys with additional entropy where desired while also + * preventing collisions with standard keys. The caller will be set as the + * controller of the derived key. + * @param salt bytes32 The desired salt value to use (along with the address + * of the caller) when deriving the resultant key and corresponding home + * address. + * @return The derived key. + * @dev Home addresses from derived keys will take longer to "mine" or locate, + * as an additional hash must be performed when computing the corresponding + * home address for each given salt input. Each caller will derive a different + * key even if they are supplying the same salt value. + */ + function deriveKey(bytes32 salt) external returns (bytes32 key) { + // Derive the key using the supplied salt and the calling address. + key = _deriveKey(salt, msg.sender); + + // Register key and set caller as controller if it is not yet registered. + HomeAddress storage home = _home[key]; + if (!home.exists) { + home.exists = true; + home.controller = msg.sender; + + // Emit an event with the sender as the new controller. + emit NewController(key, msg.sender); + } + } + + /** + * @notice Mint an ERC721 token to the supplied owner that can be redeemed in + * order to gain control of a home address corresponding to a given derived + * key. Two conditions must be met: the submitter must be designated as the + * current controller of the home address, and there must not be a contract + * currently deployed at the home address. These conditions can be checked by + * calling `getHomeAddressInformation` and `isDeployable` with the key + * determined by calling `getDerivedKey`. + * @param salt bytes32 The salt value that is used to derive the key. + * @param owner address The account that will be granted ownership of the + * ERC721 token. + * @return The derived key. + * @dev In order to mint an ERC721 token, the assocated home address cannot be + * in use, or else the token will not be able to deploy to the home address. + * The controller is set to this contract until the token is redeemed, at + * which point the redeemer designates a new controller for the home address. + * The key of the home address and the tokenID of the ERC721 token are the + * same value, but different types (bytes32 vs. uint256). + */ + function deriveKeyAndLock(bytes32 salt, address owner) + external + returns (bytes32 key) + { + // Derive the key using the supplied salt and the calling address. + key = _deriveKey(salt, msg.sender); + + // Ensure that the specified owner is a valid ERC721 receiver. + _validateOwner(owner, key); + + // Ensure that a contract is not currently deployed to the home address. + require(_isNotDeployed(key), _ACCOUNT_EXISTS); + + // Ensure that the caller is the controller of the derived key. + HomeAddress storage home = _home[key]; + if (home.exists) { + require(home.controller == msg.sender, _ONLY_CONTROLLER); + } + + // Set the exists flag to true and the controller to this contract. + home.exists = true; + home.controller = address(this); + + // Mint the ERC721 token to the designated owner. + _mint(owner, uint256(key)); + + // Emit an event signifying that this contract is now the controller. + emit NewController(key, address(this)); + } + + /** + * @notice Transfer control over deployment to the home address corresponding + * to a given derived key. The caller must be designated as the current + * controller of the home address - This condition can be checked by calling + * `getHomeAddressInformation` with the key obtained via `getDerivedKey`. + * @param salt bytes32 The salt value that is used to derive the key. + * @param controller address The account that will be granted control of the + * home address corresponding to the given derived key. + * @return The derived key. + * @dev The controller cannot be designated as the address of this contract, + * the null address, or the home address (the restriction on setting the home + * address as the controller is due to the fact that the home address will not + * be able to deploy to itself, as it needs to be empty before a contract can + * be deployed to it). + */ + function deriveKeyAndAssignController(bytes32 salt, address controller) + external + returns (bytes32 key) + { + // Derive the key using the supplied salt and the calling address. + key = _deriveKey(salt, msg.sender); + + // Prevent the controller from being set to prohibited account values. + _validateController(controller, key); + + // Ensure that the caller is the controller of the derived key. + HomeAddress storage home = _home[key]; + if (home.exists) { + require(home.controller == msg.sender, _ONLY_CONTROLLER); + } + + // Assign the new controller to the corresponding home address. + home.exists = true; + home.controller = controller; + + // Emit an event with the new controller. + emit NewController(key, controller); + } + + /** + * @notice Transfer control over deployment to the home address corresponding + * to a given derived key to the null address, which will prevent it from + * being deployed to again in the future. The caller must be designated as the + * current controller of the home address - This condition can be checked by + * calling `getHomeAddressInformation` with the key determined by calling + * `getDerivedKey`. + * @param salt bytes32 The salt value that is used to derive the key. + * @return The derived key. + */ + function deriveKeyAndRelinquishControl(bytes32 salt) + external + returns (bytes32 key) + { + // Derive the key using the supplied salt and the calling address. + key = _deriveKey(salt, msg.sender); + + // Ensure that the caller is the controller of the derived key. + HomeAddress storage home = _home[key]; + if (home.exists) { + require(home.controller == msg.sender, _ONLY_CONTROLLER); + } + + // Assign the null address as the controller of the given derived key. + home.exists = true; + home.controller = address(0); + + // Emit an event with the null address as the controller. + emit NewController(key, address(0)); + } + + /** + * @notice Record a key that corresponds to a given home address by supplying + * said key and using it to derive the address. This enables reverse lookup + * of a key using only the home address in question. This method may be called + * by anyone - control of the key is not required. + * @param key bytes32 The unique value used to derive the home address. + * @dev This does not set the salt or submitter fields, as those apply only to + * derived keys (although a derived key may also be set with this method, just + * without the derived fields). + */ + function setReverseLookup(bytes32 key) external { + // Derive home address of given key and set home address and key in mapping. + _key[_getHomeAddress(key)].key = key; + } + + /** + * @notice Record the derived key that corresponds to a given home address by + * supplying the salt and submitter that were used to derive the key. This + * facititates reverse lookup of the derivation method of a key using only the + * home address in question. This method may be called by anyone - control of + * the derived key is not required. + * @param salt bytes32 The salt value that is used to derive the key. + * @param submitter address The account that submits the salt that is used to + * derive the key. + */ + function setDerivedReverseLookup(bytes32 salt, address submitter) external { + // Derive the key using the supplied salt and submitter. + bytes32 key = _deriveKey(salt, submitter); + + // Derive home address and set it along with all other relevant information. + _key[_getHomeAddress(key)] = KeyInformation({ + key: key, + salt: salt, + submitter: submitter + }); + } + + /** + * @notice Deploy a new storage contract with the supplied code as runtime + * code without deploying a contract to a home address. This can be used to + * store the contract creation code for use in future deployments of contracts + * to home addresses. + * @param codePayload bytes The code to set as the runtime code of the + * deployed contract. + * @return The address of the deployed storage contract. + * @dev Consider placing adequate protections on the storage contract to + * prevent unwanted callers from modifying or destroying it. Also, if you are + * placing contract contract creation code into the runtime storage contract, + * remember to include any constructor parameters as ABI-encoded arguments at + * the end of the contract creation code, similar to how you would perform a + * standard deployment. + */ + function deployRuntimeStorageContract(bytes calldata codePayload) + external + returns (address runtimeStorageContract) + { + // Ensure that a code payload was supplied. + require(codePayload.length > 0, "No runtime code payload supplied."); + + // Deploy payload to the runtime storage contract and return the address. + runtimeStorageContract = _deployRuntimeStorageContract(codePayload); + } + + /** + * @notice Deploy a new contract with the initialization code stored in the + * runtime code at the specified initialization runtime storage contract to + * the home address corresponding to a given key. Two conditions must be met: + * the submitter must be designated as the controller of the home address + * (with the initial controller set to the address corresponding to the first + * 20 bytes of the key), and there must not be a contract currently deployed + * at the home address. These conditions can be checked by calling + * `getHomeAddressInformation` and `isDeployable` with the same key. + * @param key bytes32 The unique value used to derive the home address. + * @param initializationRuntimeStorageContract address The storage contract + * with runtime code equal to the contract creation code that will be used to + * deploy the contract to the home address. + * @return The home address and runtime code hash of the deployed contract. + * @dev When deploying a contract to a home address via this method, the + * metamorphic initialization code will retrieve whatever initialization code + * currently resides at the specified address and use it to set up and deploy + * the desired contract to the home address. Bear in mind that the deployed + * contract will interpret msg.sender as the address of THIS contract, and not + * the address of the submitter - if the constructor of the deployed contract + * uses msg.sender to set up ownership or other variables, you must modify it + * to accept a constructor argument with the appropriate address, or + * alternately to hard-code the intended address. Also, if your contract DOES + * have constructor arguments, remember to include them as ABI-encoded + * arguments at the end of the initialization code, just as you would when + * performing a standard deploy. You may also want to provide the key to + * `setReverseLookup` in order to find it again using only the home address to + * prevent accidentally losing the key. + */ + function deployViaExistingRuntimeStorageContract( + bytes32 key, + address initializationRuntimeStorageContract + ) + external + payable + onlyEmpty(key) + onlyControllerDeployer(key) + returns (address homeAddress, bytes32 runtimeCodeHash) + { + // Ensure that the supplied runtime storage contract is not empty. + _validateRuntimeStorageIsNotEmpty(initializationRuntimeStorageContract); + + // Set initialization runtime storage contract address in contract storage. + _initializationRuntimeStorageContract = initializationRuntimeStorageContract; + + // Use metamorphic initialization code to deploy contract to home address. + (homeAddress, runtimeCodeHash) = _deployToHomeAddress(key); + } + + /** + * @notice Burn an ERC721 token, set a supplied controller, and deploy a new + * contract with the initialization code stored in the runtime code at the + * specified initialization runtime storage contract to the home address + * corresponding to a given key. The submitter must be designated as either + * the owner of the token or as an approved spender. + * @param tokenId uint256 The ID of the ERC721 token to redeem. + * @param controller address The account that will be granted control of the + * home address corresponding to the given token. + * @param initializationRuntimeStorageContract address The storage contract + * with runtime code equal to the contract creation code that will be used to + * deploy the contract to the home address. + * @return The home address and runtime code hash of the deployed contract. + * @dev When deploying a contract to a home address via this method, the + * metamorphic initialization code will retrieve whatever initialization code + * currently resides at the specified address and use it to set up and deploy + * the desired contract to the home address. Bear in mind that the deployed + * contract will interpret msg.sender as the address of THIS contract, and not + * the address of the submitter - if the constructor of the deployed contract + * uses msg.sender to set up ownership or other variables, you must modify it + * to accept a constructor argument with the appropriate address, or + * alternately to hard-code the intended address. Also, if your contract DOES + * have constructor arguments, remember to include them as ABI-encoded + * arguments at the end of the initialization code, just as you would when + * performing a standard deploy. You may also want to provide the key to + * `setReverseLookup` in order to find it again using only the home address to + * prevent accidentally losing the key. The controller cannot be designated as + * the address of this contract, the null address, or the home address (the + * restriction on setting the home address as the controller is due to the + * fact that the home address will not be able to deploy to itself, as it + * needs to be empty before a contract can be deployed to it). Also, checks on + * the contract at the home address being empty or not having the correct + * controller are unnecessary, as they are performed when minting the token + * and cannot be altered until the token is redeemed. + */ + function redeemAndDeployViaExistingRuntimeStorageContract( + uint256 tokenId, + address controller, + address initializationRuntimeStorageContract + ) + external + payable + onlyTokenOwnerOrApprovedSpender(tokenId) + returns (address homeAddress, bytes32 runtimeCodeHash) + { + // Ensure that the supplied runtime storage contract is not empty. + _validateRuntimeStorageIsNotEmpty(initializationRuntimeStorageContract); + + // Convert the token ID to a bytes32 key. + bytes32 key = bytes32(tokenId); + + // Prevent the controller from being set to prohibited account values. + _validateController(controller, key); + + // Burn the ERC721 token in question. + _burn(tokenId); + + // Set initialization runtime storage contract address in contract storage. + _initializationRuntimeStorageContract = initializationRuntimeStorageContract; + + // Set provided controller and increment contract deploy count at once. + HomeAddress storage home = _home[key]; + home.exists = true; + home.controller = controller; + home.deploys += 1; + + // Emit an event with the new controller. + emit NewController(key, controller); + + // Use metamorphic initialization code to deploy contract to home address. + (homeAddress, runtimeCodeHash) = _deployToHomeAddress(key); + } + + /** + * @notice Deploy a new contract with the desired initialization code to the + * home address corresponding to a given derived key. Two conditions must be + * met: the submitter must be designated as the controller of the home + * address, and there must not be a contract currently deployed at the home + * address. These conditions can be checked by calling + * `getHomeAddressInformation` and `isDeployable` with the key obtained by + * calling `getDerivedKey`. + * @param salt bytes32 The salt value that is used to derive the key. + * @param initializationCode bytes The contract creation code that will be + * used to deploy the contract to the home address. + * @return The home address, derived key, and runtime code hash of the + * deployed contract. + * @dev In order to deploy the contract to the home address, a new contract + * will be deployed with runtime code set to the initialization code of the + * contract that will be deployed to the home address. Then, metamorphic + * initialization code will retrieve that initialization code and use it to + * set up and deploy the desired contract to the home address. Bear in mind + * that the deployed contract will interpret msg.sender as the address of THIS + * contract, and not the address of the submitter - if the constructor of the + * deployed contract uses msg.sender to set up ownership or other variables, + * you must modify it to accept a constructor argument with the appropriate + * address, or alternately to hard-code the intended address. Also, if your + * contract DOES have constructor arguments, remember to include them as + * ABI-encoded arguments at the end of the initialization code, just as you + * would when performing a standard deploy. You may want to provide the salt + * and submitter to `setDerivedReverseLookup` in order to find the salt, + * submitter, and derived key using only the home address to prevent + * accidentally losing them. + */ + function deriveKeyAndDeploy(bytes32 salt, bytes calldata initializationCode) + external + payable + returns (address homeAddress, bytes32 key, bytes32 runtimeCodeHash) + { + // Ensure that initialization code was supplied. + require(initializationCode.length > 0, _NO_INIT_CODE_SUPPLIED); + + // Derive key and prepare to deploy using supplied salt and calling address. + key = _deriveKeyAndPrepareToDeploy(salt); + + // Deploy the initialization storage contract and set address in storage. + _initializationRuntimeStorageContract = _deployRuntimeStorageContract( + initializationCode + ); + + // Use metamorphic initialization code to deploy contract to home address. + (homeAddress, runtimeCodeHash) = _deployToHomeAddress(key); + } + + /** + * @notice Deploy a new contract with the initialization code stored in the + * runtime code at the specified initialization runtime storage contract to + * the home address corresponding to a given derived key. Two conditions must + * be met: the submitter must be designated as the controller of the home + * address, and there must not be a contract currently deployed at the home + * address. These conditions can be checked by calling + * `getHomeAddressInformation` and `isDeployable` with the key obtained by + * calling `getDerivedKey`. + * @param salt bytes32 The salt value that is used to derive the key. + * @param initializationRuntimeStorageContract address The storage contract + * with runtime code equal to the contract creation code that will be used to + * deploy the contract to the home address. + * @return The home address, derived key, and runtime code hash of the + * deployed contract. + * @dev When deploying a contract to a home address via this method, the + * metamorphic initialization code will retrieve whatever initialization code + * currently resides at the specified address and use it to set up and deploy + * the desired contract to the home address. Bear in mind that the deployed + * contract will interpret msg.sender as the address of THIS contract, and not + * the address of the submitter - if the constructor of the deployed contract + * uses msg.sender to set up ownership or other variables, you must modify it + * to accept a constructor argument with the appropriate address, or + * alternately to hard-code the intended address. Also, if your contract DOES + * have constructor arguments, remember to include them as ABI-encoded + * arguments at the end of the initialization code, just as you would when + * performing a standard deploy. You may want to provide the salt and + * submitter to `setDerivedReverseLookup` in order to find the salt, + * submitter, and derived key using only the home address to prevent + * accidentally losing them. + */ + function deriveKeyAndDeployViaExistingRuntimeStorageContract( + bytes32 salt, + address initializationRuntimeStorageContract + ) + external + payable + returns (address homeAddress, bytes32 key, bytes32 runtimeCodeHash) + { + // Ensure that the supplied runtime storage contract is not empty. + _validateRuntimeStorageIsNotEmpty(initializationRuntimeStorageContract); + + // Derive key and prepare to deploy using supplied salt and calling address. + key = _deriveKeyAndPrepareToDeploy(salt); + + // Set the initialization runtime storage contract in contract storage. + _initializationRuntimeStorageContract = initializationRuntimeStorageContract; + + // Use metamorphic initialization code to deploy contract to home address. + (homeAddress, runtimeCodeHash) = _deployToHomeAddress(key); + } + + /** + * @notice Mint multiple ERC721 tokens, designated by their keys, to the + * specified owner. Keys that aren't controlled, or that point to home + * addresses that are currently deployed, will be skipped. + * @param owner address The account that will be granted ownership of the + * ERC721 tokens. + * @param keys bytes32[] An array of values used to derive each home address. + * @dev If you plan to use this method regularly or want to keep gas costs to + * an absolute minimum, and are willing to go without standard ABI encoding, + * see `batchLock_63efZf` for a more efficient (and unforgiving) + * implementation. For batch token minting with *derived* keys, see + * `deriveKeysAndBatchLock`. + */ + function batchLock(address owner, bytes32[] calldata keys) external { + // Track each key in the array of keys. + bytes32 key; + + // Ensure that the specified owner is a valid ERC721 receiver. + if (keys.length > 0) { + _validateOwner(owner, keys[0]); + } + + // Iterate through each provided key argument. + for (uint256 i; i < keys.length; i++) { + key = keys[i]; + + // Skip if the key currently has a contract deployed to its home address. + if (!_isNotDeployed(key)) { + continue; + } + + // Skip if the caller is not the controller. + if (_getController(key) != msg.sender) { + continue; + } + + // Set the exists flag to true and the controller to this contract. + HomeAddress storage home = _home[key]; + home.exists = true; + home.controller = address(this); + + // Emit an event signifying that this contract is now the controller. + emit NewController(key, address(this)); + + // Mint the ERC721 token to the designated owner. + _mint(owner, uint256(key)); + } + } + + /** + * @notice Mint multiple ERC721 tokens, designated by salts that are hashed + * with the caller's address to derive each key, to the specified owner. + * Derived keys that aren't controlled, or that point to home addresses that + * are currently deployed, will be skipped. + * @param owner address The account that will be granted ownership of the + * ERC721 tokens. + * @param salts bytes32[] An array of values used to derive each key and + * corresponding home address. + * @dev See `batchLock` for batch token minting with standard, non-derived + * keys. + */ + function deriveKeysAndBatchLock(address owner, bytes32[] calldata salts) + external + { + // Track each key derived from the array of salts. + bytes32 key; + + // Ensure that the specified owner is a valid ERC721 receiver. + if (salts.length > 0) { + _validateOwner(owner, _deriveKey(salts[0], msg.sender)); + } + + // Iterate through each provided salt argument. + for (uint256 i; i < salts.length; i++) { + // Derive the key using the supplied salt and the calling address. + key = _deriveKey(salts[i], msg.sender); + + // Skip if the key currently has a contract deployed to its home address. + if (!_isNotDeployed(key)) { + continue; + } + + // Skip if the caller is not the controller. + HomeAddress storage home = _home[key]; + if (home.exists && home.controller != msg.sender) { + continue; + } + + // Set the exists flag to true and the controller to this contract. + home.exists = true; + home.controller = address(this); + + // Emit an event signifying that this contract is now the controller. + emit NewController(key, address(this)); + + // Mint the ERC721 token to the designated owner. + _mint(owner, uint256(key)); + } + } + + /** + * @notice Safely transfers the ownership of a group of token IDs to another + * address in a batch. If the target address is a contract, it must implement + * `onERC721Received`, called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; + * otherwise, or if another error occurs, the entire batch is reverted. + * Requires msg.sender to be the owner, approved, or operator of the tokens. + * @param from address The current owner of the tokens. + * @param to address The account to receive ownership of the given tokens. + * @param tokenIds uint256[] ID of the tokens to be transferred. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata tokenIds + ) + external + { + // Track each token ID in the batch. + uint256 tokenId; + + // Iterate over each supplied token ID. + for (uint256 i = 0; i < tokenIds.length; i++) { + // Set the current token ID. + tokenId = tokenIds[i]; + + // Perform the token transfer. + safeTransferFrom(from, to, tokenId); + } + } + + /** + * @notice Safely transfers the ownership of a group of token IDs to another + * address in a batch. If the target address is a contract, it must implement + * `onERC721Received`, called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; + * otherwise, or if another error occurs, the entire batch is reverted. + * Requires msg.sender to be the owner, approved, or operator of the tokens. + * @param from address The current owner of the tokens. + * @param to address The account to receive ownership of the given tokens. + * @param tokenIds uint256[] ID of the tokens to be transferred. + * @param data bytes A data payload to include with each transfer. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata tokenIds, + bytes calldata data + ) + external + { + // Track each token ID in the batch. + uint256 tokenId; + + // Iterate over each supplied token ID. + for (uint256 i = 0; i < tokenIds.length; i++) { + // Set the current token ID. + tokenId = tokenIds[i]; + + // Perform the token transfer. + safeTransferFrom(from, to, tokenId, data); + } + } + + /** + * @notice Efficient version of `batchLock` that uses less gas. The first 20 + * bytes of each key are automatically populated using msg.sender, and the + * remaining key segments are passed in as a packed byte array, using twelve + * bytes per segment, with a function selector of 0x00000000 followed by a + * twenty-byte segment for the desired owner of the minted ERC721 tokens. Note + * that an attempt to lock a key that is not controlled or with its contract + * already deployed will cause the entire batch to revert. Checks on whether + * the owner is a valid ERC721 receiver are also skipped, similar to using + * `transferFrom` instead of `safeTransferFrom`. + */ + function batchLock_63efZf(/* packed owner and key segments */) external { + // Get the owner from calldata, located at bytes 4-23 (sig is bytes 0-3). + address owner; + + // Determine number of 12-byte key segments in calldata from byte 24 on. + uint256 passedSaltSegments; + + // Get the owner and calculate the total number of key segments. + assembly { + owner := shr(0x60, calldataload(4)) // comes after sig + passedSaltSegments := div(sub(calldatasize, 24), 12) // after sig & owner + } + + // Track each key, located at each 12-byte segment from byte 24 on. + bytes32 key; + + // Iterate through each provided key segment argument. + for (uint256 i; i < passedSaltSegments; i++) { + // Construct keys by concatenating msg.sender with each key segment. + assembly { + key := add( // Combine msg.sender & provided key. + shl(0x60, caller), // Place msg.sender at start of word. + shr(0xa0, calldataload(add(24, mul(i, 12)))) // Segment at end. + ) + } + + // Ensure that the key does not currently have a deployed contract. + require(_isNotDeployed(key), _ACCOUNT_EXISTS); + + // Ensure that the caller is the controller of the key. + HomeAddress storage home = _home[key]; + if (home.exists) { + require(home.controller == msg.sender, _ONLY_CONTROLLER); + } + + // Set the exists flag to true and the controller to this contract. + home.exists = true; + home.controller = address(this); + + // Emit an event signifying that this contract is now the controller. + emit NewController(key, address(this)); + + // Mint the ERC721 token to the designated owner. + _mint(owner, uint256(key)); + } + } + + /** + * @notice Perform a dry-run of the deployment of a contract using a given key + * and revert on successful deployment. It cannot be called from outside the + * contract (even though it is marked as external). + * @param key bytes32 The unique value used to derive the home address. + * @dev This contract is called by `_isNotDeployable` in extreme cases where + * the deployability of the contract cannot be determined conclusively. + */ + function staticCreate2Check(bytes32 key) external { + require( + msg.sender == address(this), + "This function can only be called by this contract." + ); + + assembly { + // Write the 32-byte metamorphic initialization code to scratch space. + mstore( + 0, + 0x5859385958601c335a585952fa1582838382515af43d3d93833e601e57fd5bf3 + ) + + // Call `CREATE2` using metamorphic init code with supplied key as salt. + let deploymentAddress := create2(0, 0, 32, key) + + // Revert and return the metamorphic init code on successful deployment. + if deploymentAddress { + revert(0, 32) + } + } + } + + /** + * @notice Submit a key to claim the "high score" - the lower the uint160 + * value of the key's home address, the higher the score. The high score + * holder has the exclusive right to recover lost ether and tokens on this + * contract. + * @param key bytes32 The unique value used to derive the home address that + * will determine the resultant score. + * @dev The high score must be claimed by a direct key (one that is submitted + * by setting the first 20 bytes of the key to the address of the submitter) + * and not by a derived key, and is non-transferrable. If you want to help + * people recover their lost tokens, you might consider deploying a contract + * to the high score address (probably a metamorphic one so that you can use + * the home address later) with your contact information. + */ + function claimHighScore(bytes32 key) external { + require( + msg.sender == address(bytes20(key)), + "Only submitters directly encoded in a given key may claim a high score." + ); + + // Derive the "home address" of the current high score key. + address currentHighScore = _getHomeAddress(_highScoreKey); + + // Derive the "home address" of the new high score key. + address newHighScore = _getHomeAddress(key); + + // Use addresses to ensure that supplied key is in fact a new high score. + require( + uint160(newHighScore) < uint160(currentHighScore), + "Submitted high score is not better than the current high score." + ); + + // Set the new high score to the supplied key. + _highScoreKey = key; + + // The score is equal to (2^160 - 1) - ("home address" of high score key). + uint256 score = uint256(uint160(-1) - uint160(newHighScore)); + + // Emit an event to signify that a new high score has been reached. + emit NewHighScore(key, msg.sender, score); + } + + /** + * @notice Transfer any ether or ERC20 tokens that have somehow ended up at + * this contract by specifying a token address (set to the null address for + * ether) as well as a recipient address. Only the high score holder can + * recover lost ether and tokens on this contract. + * @param token address The contract address of the ERC20 token to recover, or + * the null address for recovering Ether. + * @param recipient address payable The account where recovered funds should + * be transferred. + * @dev If you are trying to recover funds that were accidentally sent into + * this contract, see if you can contact the holder of the current high score, + * found by calling `getHighScore`. Better yet, try to find a new high score + * yourself! + */ + function recover(IERC20 token, address payable recipient) external { + require( + msg.sender == address(bytes20(_highScoreKey)), + "Only the current high score holder may recover tokens." + ); + + if (address(token) == address(0)) { + // Recover ETH if the token's contract address is set to the null address. + recipient.transfer(address(this).balance); + } else { + // Determine the given ERC20 token balance and transfer to the recipient. + uint256 balance = token.balanceOf(address(this)); + token.transfer(recipient, balance); + } + } + + /** + * @notice "View" function to determine if a contract can currently be + * deployed to a home address given the corresponding key. A contract is only + * deployable if no account currently exists at the address - any existing + * contract must be destroyed via `SELFDESTRUCT` before a new contract can be + * deployed to a home address. This method does not modify state but is + * inaccessible via staticcall. + * @param key bytes32 The unique value used to derive the home address. + * @return A boolean signifying if a contract can be deployed to the home + * address that corresponds to the provided key. + * @dev This will not detect if a contract is not deployable due control + * having been relinquished on the key. + */ + function isDeployable(bytes32 key) + external + /* view */ + returns (bool deployable) + { + deployable = _isNotDeployed(key); + } + + /** + * @notice View function to get the current "high score", or the lowest + * uint160 value of a home address of all keys submitted. The high score + * holder has the exclusive right to recover lost ether and tokens on this + * contract. + * @return The current high score holder, their score, and the submitted key. + */ + function getHighScore() + external + view + returns (address holder, uint256 score, bytes32 key) + { + // Get the key and subbmitter holding the current high score. + key = _highScoreKey; + holder = address(bytes20(key)); + + // The score is equal to (2^160 - 1) - ("home address" of high score key). + score = uint256(uint160(-1) - uint160(_getHomeAddress(key))); + } + + /** + * @notice View function to get information on a home address given the + * corresponding key. + * @param key bytes32 The unique value used to derive the home address. + * @return The home address, the current controller of the address, the number + * of times the home address has been deployed to, and the code hash of the + * runtime currently found at the home address, if any. + * @dev There is also an `isDeployable` method for determining if a contract + * can be deployed to the address, but in extreme cases it must actually + * perform a dry-run to determine if the contract is deployable, which means + * that it does not support staticcalls. There is also a convenience method, + * `hasNeverBeenDeployed`, but the information it conveys can be determined + * from this method alone as well. + */ + function getHomeAddressInformation(bytes32 key) + external + view + returns ( + address homeAddress, + address controller, + uint256 deploys, + bytes32 currentRuntimeCodeHash + ) + { + // Derive home address and retrieve other information using supplied key. + homeAddress = _getHomeAddress(key); + HomeAddress memory home = _home[key]; + + // If the home address has not been seen before, use the default controller. + if (!home.exists) { + controller = address(bytes20(key)); + } else { + controller = home.controller; + } + + // Retrieve the count of total deploys to the home address. + deploys = home.deploys; + + // Retrieve keccak256 hash of runtime code currently at the home address. + assembly { currentRuntimeCodeHash := extcodehash(homeAddress) } + } + + /** + * @notice View function to determine if no contract has ever been deployed to + * a home address given the corresponding key. This can be used to ensure that + * a given key or corresponding token is "new" or not. + * @param key bytes32 The unique value used to derive the home address. + * @return A boolean signifying if a contract has never been deployed using + * the supplied key before. + */ + function hasNeverBeenDeployed(bytes32 key) + external + view + returns (bool neverBeenDeployed) + { + neverBeenDeployed = (_home[key].deploys == 0); + } + + /** + * @notice View function to search for a known key, salt, and/or submitter + * given a supplied home address. Keys can be controlled directly by an + * address that matches the first 20 bytes of the key, or they can be derived + * from a salt and a submitter - if the key is not a derived key, the salt and + * submitter fields will both have a value of zero. + * @param homeAddress address The home address to check for key information. + * @return The key, salt, and/or submitter used to deploy to the home address, + * assuming they have been submitted to the reverse lookup. + * @dev To populate these values, call `setReverseLookup` for cases where keys + * are used directly or are the only value known, or `setDerivedReverseLookup` + * for cases where keys are derived from a known salt and submitter. + */ + function reverseLookup(address homeAddress) + external + view + returns (bytes32 key, bytes32 salt, address submitter) + { + KeyInformation memory keyInformation = _key[homeAddress]; + key = keyInformation.key; + salt = keyInformation.salt; + submitter = keyInformation.submitter; + } + + /** + * @notice View function used by the metamorphic initialization code when + * deploying a contract to a home address. It returns the address of the + * runtime storage contract that holds the contract creation code, which the + * metamorphic creation code then `DELEGATECALL`s into in order to set up the + * contract and deploy the target runtime code. + * @return The current runtime storage contract that contains the target + * contract creation code. + * @dev This method is not meant to be part of the user-facing contract API, + * but is rather a mechanism for enabling the deployment of arbitrary code via + * fixed initialization code. The odd naming is chosen so that function + * selector will be 0x00000009 - that way, the metamorphic contract can simply + * use the `PC` opcode in order to push the selector to the stack. + */ + function getInitializationCodeFromContractRuntime_6CLUNS() + external + view + returns (address initializationRuntimeStorageContract) + { + // Return address of contract with initialization code set as runtime code. + initializationRuntimeStorageContract = _initializationRuntimeStorageContract; + } + + /** + * @notice View function to return an URI for a given token ID. Throws if the + * token ID does not exist. + * @param tokenId uint256 ID of the token to query. + * @return String representing the URI data encoding of JSON metadata. + * @dev The URI returned by this method takes the following form (with all + * returns and initial whitespace removed - it's just here for clarity): + * + * data:application/json,{ + * "name":"Home%20Address%20-%200x********************", + * "description":"< ... HomeWork NFT desription ... >", + * "image":"data:image/svg+xml;charset=utf-8;base64,< ... Image ... >"} + * + * where ******************** represents the checksummed home address that the + * token confers control over. + */ + function tokenURI(uint256 tokenId) + external + view + returns (string memory) + { + // Only return a URI for tokens that exist. + require(_exists(tokenId), "A token with the given ID does not exist."); + + // Get the home address that the given tokenId corresponds to. + address homeAddress = _getHomeAddress(bytes32(tokenId)); + + // Get the checksummed, ascii-encoded representation of the home address. + string memory asciiHomeAddress = _toChecksummedAsciiString(homeAddress); + + bytes memory uriEndSegment = _getTokenURIStorageRuntime(); + + // Insert checksummed address into URI in name and image fields and return. + return string( + abi.encodePacked( // Concatenate all the string segments together. + _URI_START_SEGMENT, // Data URI ID and initial formatting is constant. + asciiHomeAddress, // Checksummed home address is in the name field. + uriEndSegment // Description, image, and formatting is constant. + ) + ); + } + + /** + * @notice Pure function to get the token name. + * @return String representing the token name. + */ + function name() external pure returns (string memory) { + return _NAME; + } + + /** + * @notice Pure function to get the token symbol. + * @return String representing the token symbol. + */ + function symbol() external pure returns (string memory) { + return _SYMBOL; + } + + /** + * @notice Pure function to determine the key that is derived from a given + * salt and submitting address. + * @param salt bytes32 The salt value that is used to derive the key. + * @param submitter address The submitter of the salt value used to derive the + * key. + * @return The derived key. + */ + function getDerivedKey(bytes32 salt, address submitter) + external + pure + returns (bytes32 key) + { + // Derive the key using the supplied salt and submitter. + key = _deriveKey(salt, submitter); + } + + /** + * @notice Pure function to determine the home address that corresponds to + * a given key. + * @param key bytes32 The unique value used to derive the home address. + * @return The home address. + */ + function getHomeAddress(bytes32 key) + external + pure + returns (address homeAddress) + { + // Derive the home address using the supplied key. + homeAddress = _getHomeAddress(key); + } + + /** + * @notice Pure function for retrieving the metamorphic initialization code + * used to deploy arbitrary contracts to home addresses. Provided for easy + * verification and for use in other applications. + * @return The 32-byte metamorphic initialization code. + * @dev This metamorphic init code works via the "metamorphic delegator" + * mechanism, which is explained in greater detail at `_deployToHomeAddress`. + */ + function getMetamorphicDelegatorInitializationCode() + external + pure + returns (bytes32 metamorphicDelegatorInitializationCode) + { + metamorphicDelegatorInitializationCode = _HOME_INIT_CODE; + } + + /** + * @notice Pure function for retrieving the keccak256 of the metamorphic + * initialization code used to deploy arbitrary contracts to home addresses. + * This is the value that you should use, along with this contract's address + * and a caller address that you control, to mine for an partucular type of + * home address (such as one at a compact or gas-efficient address). + * @return The keccak256 hash of the metamorphic initialization code. + */ + function getMetamorphicDelegatorInitializationCodeHash() + external + pure + returns (bytes32 metamorphicDelegatorInitializationCodeHash) + { + metamorphicDelegatorInitializationCodeHash = _HOME_INIT_CODE_HASH; + } + + /** + * @notice Pure function for retrieving the prelude that will be inserted + * ahead of the code payload in order to deploy a runtime storage contract. + * @return The 11-byte "arbitrary runtime" prelude. + */ + function getArbitraryRuntimeCodePrelude() + external + pure + returns (bytes11 prelude) + { + prelude = _ARBITRARY_RUNTIME_PRELUDE; + } + + /** + * @notice Internal function for deploying a runtime storage contract given a + * particular payload. + * @return The address of the runtime storage contract. + * @dev To take the provided code payload and deploy a contract with that + * payload as its runtime code, use the following prelude: + * + * 0x600b5981380380925939f3... + * + * 00 60 push1 0b [11 -> offset] + * 02 59 msize [offset, 0] + * 03 81 dup2 [offset, 0, offset] + * 04 38 codesize [offset, 0, offset, codesize] + * 05 03 sub [offset, 0, codesize - offset] + * 06 80 dup1 [offset, 0, codesize - offset, codesize - offset] + * 07 92 swap3 [codesize - offset, 0, codesize - offset, offset] + * 08 59 msize [codesize - offset, 0, codesize - offset, offset, 0] + * 09 39 codecopy [codesize - offset, 0] + * 10 f3 return [] *init_code_in_runtime* + * ... init_code + */ + function _deployRuntimeStorageContract(bytes memory payload) + internal + returns (address runtimeStorageContract) + { + // Construct the contract creation code using the prelude and the payload. + bytes memory runtimeStorageContractCreationCode = abi.encodePacked( + _ARBITRARY_RUNTIME_PRELUDE, + payload + ); + + assembly { + // Get the location and length of the newly-constructed creation code. + let encoded_data := add(0x20, runtimeStorageContractCreationCode) + let encoded_size := mload(runtimeStorageContractCreationCode) + + // Deploy the runtime storage contract via standard `CREATE`. + runtimeStorageContract := create(0, encoded_data, encoded_size) + + // Pass along revert message if the contract did not deploy successfully. + if iszero(runtimeStorageContract) { + returndatacopy(0, 0, returndatasize) + revert(0, returndatasize) + } + } + + // Emit an event with address of newly-deployed runtime storage contract. + emit NewRuntimeStorageContract(runtimeStorageContract, keccak256(payload)); + } + + /** + * @notice Internal function for deploying arbitrary contract code to the home + * address corresponding to a suppied key via metamorphic initialization code. + * @return The home address and the hash of the deployed runtime code. + * @dev This deployment method uses the "metamorphic delegator" pattern, where + * it will retrieve the address of the contract that contains the target + * initialization code, then delegatecall into it, which executes the + * initialization code stored there and returns the runtime code (or reverts). + * Then, the runtime code returned by the delegatecall is returned, and since + * we are still in the initialization context, it will be set as the runtime + * code of the metamorphic contract. The 32-byte metamorphic initialization + * code is as follows: + * + * 0x5859385958601c335a585952fa1582838382515af43d3d93833e601e57fd5bf3 + * + * 00 58 PC [0] + * 01 59 MSIZE [0, 0] + * 02 38 CODESIZE [0, 0, codesize -> 32] + * returndatac03 59 MSIZE [0, 0, 32, 0] + * 04 58 PC [0, 0, 32, 0, 4] + * 05 60 PUSH1 0x1c [0, 0, 32, 0, 4, 28] + * 07 33 CALLER [0, 0, 32, 0, 4, 28, caller] + * 08 5a GAS [0, 0, 32, 0, 4, 28, caller, gas] + * 09 58 PC [0, 0, 32, 0, 4, 28, caller, gas, 9 -> selector] + * 10 59 MSIZE [0, 0, 32, 0, 4, 28, caller, gas, selector, 0] + * 11 52 MSTORE [0, 0, 32, 0, 4, 28, caller, gas] + * 12 fa STATICCALL [0, 0, 1 => success] + * 13 15 ISZERO [0, 0, 0] + * 14 82 DUP3 [0, 0, 0, 0] + * 15 83 DUP4 [0, 0, 0, 0, 0] + * 16 83 DUP4 [0, 0, 0, 0, 0, 0] + * 17 82 DUP3 [0, 0, 0, 0, 0, 0, 0] + * 18 51 MLOAD [0, 0, 0, 0, 0, 0, init_in_runtime_address] + * 19 5a GAS [0, 0, 0, 0, 0, 0, init_in_runtime_address, gas] + * 20 f4 DELEGATECALL [0, 0, 1 => success] {runtime_code} + * 21 3d RETURNDATASIZE [0, 0, 1 => success, size] + * 22 3d RETURNDATASIZE [0, 0, 1 => success, size, size] + * 23 93 SWAP4 [size, 0, 1 => success, size, 0] + * 24 83 DUP4 [size, 0, 1 => success, size, 0, 0] + * 25 3e RETURNDATACOPY [size, 0, 1 => success] + * 26 60 PUSH1 0x1e [size, 0, 1 => success, 30] + * 28 57 JUMPI [size, 0] + * 29 fd REVERT [] *runtime_code* + * 30 5b JUMPDEST [size, 0] + * 31 f3 RETURN [] + */ + function _deployToHomeAddress(bytes32 key) + internal + returns (address homeAddress, bytes32 runtimeCodeHash) + { + assembly { + // Write the 32-byte metamorphic initialization code to scratch space. + mstore( + 0, + 0x5859385958601c335a585952fa1582838382515af43d3d93833e601e57fd5bf3 + ) + + // Call `CREATE2` using above init code with the supplied key as the salt. + homeAddress := create2(callvalue, 0, 32, key) + + // Pass along revert message if the contract did not deploy successfully. + if iszero(homeAddress) { + returndatacopy(0, 0, returndatasize) + revert(0, returndatasize) + } + + // Get the runtime hash of the deployed contract. + runtimeCodeHash := extcodehash(homeAddress) + } + + // Clear the address of the runtime storage contract from storage. + delete _initializationRuntimeStorageContract; + + // Emit an event with home address, key, and runtime hash of new contract. + emit NewResident(homeAddress, key, runtimeCodeHash); + } + + /** + * @notice Internal function for deriving a key given a particular salt and + * caller and for performing verifications of, and modifications to, the + * information set on that key. + * @param salt bytes32 The value used to derive the key. + * @return The derived key. + */ + function _deriveKeyAndPrepareToDeploy(bytes32 salt) + internal + returns (bytes32 key) + { + // Derive the key using the supplied salt and the calling address. + key = _deriveKey(salt, msg.sender); + + // Ensure that a contract is not currently deployed to the home address. + require(_isNotDeployed(key), _ACCOUNT_EXISTS); + + // Set appropriate controller and increment contract deploy count at once. + HomeAddress storage home = _home[key]; + if (!home.exists) { + home.exists = true; + home.controller = msg.sender; + home.deploys += 1; + + // Emit an event signifying that this contract is now the controller. + emit NewController(key, msg.sender); + + } else { + home.deploys += 1; + } + + // Ensure that the caller is the designated controller before proceeding. + require(home.controller == msg.sender, _ONLY_CONTROLLER); + } + + /** + * @notice Internal function for verifying that an owner that cannot accept + * ERC721 tokens has not been supplied. + * @param owner address The specified owner. + * @param key bytes32 The unique value used to derive the home address. + */ + function _validateOwner(address owner, bytes32 key) internal { + // Ensure that the specified owner is a valid ERC721 receiver. + require( + _checkOnERC721Received(address(0), owner, uint256(key), bytes("")), + "Owner must be an EOA or a contract that implements `onERC721Received`." + ); + } + + /** + * @notice Internal "view" function for determining if a contract currently + * exists at a given home address corresponding to a particular key. + * @param key bytes32 The unique value used to derive the home address. + * @return A boolean signifying whether the home address has a contract + * deployed or not. + */ + function _isNotDeployed(bytes32 key) + internal + /* view */ + returns (bool notDeployed) + { + // Derive the home address using the supplied key. + address homeAddress = _getHomeAddress(key); + + // Check whether account at home address is non-existent using EXTCODEHASH. + bytes32 hash; + assembly { hash := extcodehash(homeAddress) } + + // Account does not exist, and contract is not deployed, if hash equals 0. + if (hash == bytes32(0)) { + return true; + } + + // Contract is deployed (notDeployed = false) if codesize is greater than 0. + uint256 size; + assembly { size := extcodesize(homeAddress) } + if (size > 0) { + return false; + } + + // Declare variable to move current runtime storage from storage to memory. + address currentStorage; + + // Set runtime storage contract to null address temporarily if necessary. + if (_initializationRuntimeStorageContract != address(0)) { + // Place the current runtime storage contract address in memory. + currentStorage = _initializationRuntimeStorageContract; + + // Remove the existing runtime storage contract address from storage. + delete _initializationRuntimeStorageContract; + } + + // Set gas to use when performing dry-run deployment (future-proof a bit). + uint256 checkGas = 27000 + (block.gaslimit / 1000); + + // As a last resort, deploy a contract to the address and revert on success. + (bool contractExists, bytes memory code) = address(this).call.gas(checkGas)( + abi.encodeWithSelector(this.staticCreate2Check.selector, key) + ); + + // Place runtime storage contract back in storage if necessary. + if (currentStorage != address(0)) { + _initializationRuntimeStorageContract = currentStorage; + } + + // Check revert string to ensure failure is due to successful deployment. + bytes32 revertMessage; + assembly { revertMessage := mload(add(code, 32)) } + + // Contract is not deployed if `staticCreate2Check` reverted with message. + notDeployed = !contractExists && revertMessage == _HOME_INIT_CODE; + } + + /** + * @notice Internal view function for verifying that a restricted controller + * has not been supplied. + * @param controller address The specified controller. + * @param key bytes32 The unique value used to derive the home address. + */ + function _validateController(address controller, bytes32 key) internal view { + // Prevent the controller from being set to prohibited account values. + require( + controller != address(0), + "The null address may not be set as the controller using this function." + ); + require( + controller != address(this), + "This contract may not be set as the controller using this function." + ); + require( + controller != _getHomeAddress(key), + "Home addresses cannot be set as the controller of themselves." + ); + } + + /** + * @notice Internal view function for verifying that a supplied runtime + * storage contract is not empty. + * @param target address The runtime storage contract. + */ + function _validateRuntimeStorageIsNotEmpty(address target) internal view { + // Ensure that the runtime storage contract is not empty. + require( + target.isContract(), + "No runtime code found at the supplied runtime storage address." + ); + } + + /** + * @notice Internal view function for retrieving the controller of a home + * address corresponding to a particular key. + * @param key bytes32 The unique value used to derive the home address. + * @return The controller of the home address corresponding to the supplied + * key. + */ + function _getController(bytes32 key) + internal + view + returns (address controller) + { + // Get controller from mapping, defaulting to first 20 bytes of the key. + HomeAddress memory home = _home[key]; + if (!home.exists) { + controller = address(bytes20(key)); + } else { + controller = home.controller; + } + } + + /** + * @notice Internal view function for getting the runtime code at the tokenURI + * data storage address. + * @return The runtime code at the tokenURI storage address. + */ + function _getTokenURIStorageRuntime() + internal + view + returns (bytes memory runtime) + { + // Bring the tokenURI storage address into memory for use in assembly block. + address target = _URI_END_SEGMENT_STORAGE; + + assembly { + // Retrieve the size of the external code. + let size := extcodesize(target) + + // Allocate output byte array. + runtime := mload(0x40) + + // Set new "memory end" including padding. + mstore(0x40, add(runtime, and(add(size, 0x3f), not(0x1f)))) + + // Store length in memory. + mstore(runtime, size) + + // Get the code using extcodecopy. + extcodecopy(target, add(runtime, 0x20), 0, size) + } + } + + /** + * @notice Internal pure function for calculating a home address given a + * particular key. + * @param key bytes32 The unique value used to derive the home address. + * @return The home address corresponding to the supplied key. + */ + function _getHomeAddress(bytes32 key) + internal + pure + returns (address homeAddress) + { + // Determine the home address by replicating CREATE2 logic. + homeAddress = address( + uint160( // Downcast to match the address type. + uint256( // Cast to uint to truncate upper digits. + keccak256( // Compute CREATE2 hash using 4 inputs. + abi.encodePacked( // Pack all inputs to the hash together. + _FF_AND_THIS_CONTRACT, // This contract will be the caller. + key, // Pass in the supplied key as the salt. + _HOME_INIT_CODE_HASH // The metamorphic init code hash. + ) + ) + ) + ) + ); + } + + /** + * @notice Internal pure function for deriving a key given a particular salt + * and caller. + * @param salt bytes32 The value used to derive the key. + * @param submitter address The submitter of the salt used to derive the key. + * @return The derived key. + */ + function _deriveKey(bytes32 salt, address submitter) + internal + pure + returns (bytes32 key) + { + // Set the key as the keccak256 hash of the salt and submitter. + key = keccak256(abi.encodePacked(salt, submitter)); + } + + /** + * @notice Internal pure function for converting the bytes representation of + * an address to an ASCII string. This function is derived from the function + * at https://ethereum.stackexchange.com/a/56499/48410 + * @param data bytes20 The account address to be converted. + * @return The account string in ASCII format. Note that leading "0x" is not + * included. + */ + function _toAsciiString(bytes20 data) + internal + pure + returns (string memory asciiString) + { + // Create an in-memory fixed-size bytes array. + bytes memory asciiBytes = new bytes(40); + + // Declare variable types. + uint8 oneByte; + uint8 leftNibble; + uint8 rightNibble; + + // Iterate over bytes, processing left and right nibble in each iteration. + for (uint256 i = 0; i < data.length; i++) { + // locate the byte and extract each nibble. + oneByte = uint8(uint160(data) / (2 ** (8 * (19 - i)))); + leftNibble = oneByte / 16; + rightNibble = oneByte - 16 * leftNibble; + + // To convert to ascii characters, add 48 to 0-9 and 87 to a-f. + asciiBytes[2 * i] = byte(leftNibble + (leftNibble < 10 ? 48 : 87)); + asciiBytes[2 * i + 1] = byte(rightNibble + (rightNibble < 10 ? 48 : 87)); + } + + asciiString = string(asciiBytes); + } + + /** + * @notice Internal pure function for getting a fixed-size array of whether or + * not each character in an account will be capitalized in the checksum. + * @param account address The account to get the checksum capitalization + * information for. + * @return A fixed-size array of booleans that signify if each character or + * "nibble" of the hex encoding of the address will be capitalized by the + * checksum. + */ + function _getChecksumCapitalizedCharacters(address account) + internal + pure + returns (bool[40] memory characterIsCapitalized) + { + // Convert the address to bytes. + bytes20 addressBytes = bytes20(account); + + // Hash the address (used to calculate checksum). + bytes32 hash = keccak256(abi.encodePacked(_toAsciiString(addressBytes))); + + // Declare variable types. + uint8 leftNibbleAddress; + uint8 rightNibbleAddress; + uint8 leftNibbleHash; + uint8 rightNibbleHash; + + // Iterate over bytes, processing left and right nibble in each iteration. + for (uint256 i; i < addressBytes.length; i++) { + // locate the byte and extract each nibble for the address and the hash. + rightNibbleAddress = uint8(addressBytes[i]) % 16; + leftNibbleAddress = (uint8(addressBytes[i]) - rightNibbleAddress) / 16; + rightNibbleHash = uint8(hash[i]) % 16; + leftNibbleHash = (uint8(hash[i]) - rightNibbleHash) / 16; + + // Set the capitalization flags based on the characters and the checksums. + characterIsCapitalized[2 * i] = ( + leftNibbleAddress > 9 && + leftNibbleHash > 7 + ); + characterIsCapitalized[2 * i + 1] = ( + rightNibbleAddress > 9 && + rightNibbleHash > 7 + ); + } + } + + /** + * @notice Internal pure function for converting the bytes representation of + * an address to a checksummed ASCII string. + * @param account address The account address to be converted. + * @return The checksummed account string in ASCII format. Note that leading + * "0x" is not included. + */ + function _toChecksummedAsciiString(address account) + internal + pure + returns (string memory checksummedAsciiString) + { + // Get capitalized characters in the checksum. + bool[40] memory caps = _getChecksumCapitalizedCharacters(account); + + // Create an in-memory fixed-size bytes array. + bytes memory asciiBytes = new bytes(40); + + // Declare variable types. + uint8 oneByte; + uint8 leftNibble; + uint8 rightNibble; + uint8 leftNibbleOffset; + uint8 rightNibbleOffset; + + // Convert account to bytes20. + bytes20 data = bytes20(account); + + // Iterate over bytes, processing left and right nibble in each iteration. + for (uint256 i = 0; i < data.length; i++) { + // locate the byte and extract each nibble. + oneByte = uint8(uint160(data) / (2 ** (8 * (19 - i)))); + leftNibble = oneByte / 16; + rightNibble = oneByte - 16 * leftNibble; + + // To convert to ascii characters, add 48 to 0-9, 55 to A-F, & 87 to a-f. + if (leftNibble < 10) { + leftNibbleOffset = 48; + } else if (caps[i * 2]) { + leftNibbleOffset = 55; + } else { + leftNibbleOffset = 87; + } + + if (rightNibble < 10) { + rightNibbleOffset = 48; + } else { + rightNibbleOffset = caps[(i * 2) + 1] ? 55 : 87; // instrumentation fix + } + + asciiBytes[2 * i] = byte(leftNibble + leftNibbleOffset); + asciiBytes[2 * i + 1] = byte(rightNibble + rightNibbleOffset); + } + + checksummedAsciiString = string(asciiBytes); + } + + /** + * @notice Modifier to ensure that a contract is not currently deployed to the + * home address corresponding to a given key on the decorated function. + * @param key bytes32 The unique value used to derive the home address. + */ + modifier onlyEmpty(bytes32 key) { + require(_isNotDeployed(key), _ACCOUNT_EXISTS); + _; + } + + /** + * @notice Modifier to ensure that the caller of the decorated function is the + * controller of the home address corresponding to a given key. + * @param key bytes32 The unique value used to derive the home address. + */ + modifier onlyController(bytes32 key) { + require(_getController(key) == msg.sender, _ONLY_CONTROLLER); + _; + } + + /** + * @notice Modifier to track initial controllers and to count deploys, and to + * validate that only the designated controller has access to the decorated + * function. + * @param key bytes32 The unique value used to derive the home address. + */ + modifier onlyControllerDeployer(bytes32 key) { + HomeAddress storage home = _home[key]; + + // Set appropriate controller and increment contract deploy count at once. + if (!home.exists) { + home.exists = true; + home.controller = address(bytes20(key)); + home.deploys += 1; + } else { + home.deploys += 1; + } + + require(home.controller == msg.sender, _ONLY_CONTROLLER); + _; + } + + /** + * @notice Modifier to ensure that only the owner of the supplied ERC721 + * token, or an approved spender, can access the decorated function. + * @param tokenId uint256 The ID of the ERC721 token. + */ + modifier onlyTokenOwnerOrApprovedSpender(uint256 tokenId) { + require( + _isApprovedOrOwner(msg.sender, tokenId), + "Only the token owner or an approved spender may call this function." + ); + _; + } +} + +/** + * @title HomeWork Deployer (alpha version) + * @author 0age + * @notice This contract is a stripped-down version of HomeWork that is used to + * deploy HomeWork itself. + * HomeWork Deploy code at runtime: 0x7Cf7708ab4A064B14B02F34aecBd2511f3605395 + * HomeWork Runtime code at: 0x0000000000001b84b1cb32787b0d64758d019317 + */ +contract HomeWorkDeployer { + // Fires when HomeWork has been deployed. + event HomeWorkDeployment(address homeAddress, bytes32 key); + + // Fires HomeWork's initialization-in-runtime storage contract is deployed. + event StorageContractDeployment(address runtimeStorageContract); + + // Allocate storage to track the current initialization-in-runtime contract. + address private _initializationRuntimeStorageContract; + + // Once HomeWork has been deployed, disable this contract. + bool private _disabled; + + // Write arbitrary code to a contract's runtime using the following prelude. + bytes11 private constant _ARBITRARY_RUNTIME_PRELUDE = bytes11( + 0x600b5981380380925939f3 + ); + + /** + * @notice Perform phase one of the deployment. + * @param code bytes The contract creation code for HomeWork. + */ + function phaseOne(bytes calldata code) external onlyUntilDisabled { + // Deploy payload to the runtime storage contract and set the address. + _initializationRuntimeStorageContract = _deployRuntimeStorageContract( + bytes32(0), + code + ); + } + + /** + * @notice Perform phase two of the deployment (tokenURI data). + * @param key bytes32 The salt to provide to create2. + */ + function phaseTwo(bytes32 key) external onlyUntilDisabled { + // Deploy runtime storage contract with the string used to construct end of + // token URI for issued ERC721s (data URI with a base64-encoded jpeg image). + bytes memory code = abi.encodePacked( + hex"222c226465736372697074696f6e223a22546869732532304e465425323063616e25", + hex"3230626525323072656465656d65642532306f6e253230486f6d65576f726b253230", + hex"746f2532306772616e7425323061253230636f6e74726f6c6c657225323074686525", + hex"32306578636c75736976652532307269676874253230746f2532306465706c6f7925", + hex"3230636f6e7472616374732532307769746825323061726269747261727925323062", + hex"797465636f6465253230746f25323074686525323064657369676e61746564253230", + hex"686f6d65253230616464726573732e222c22696d616765223a22646174613a696d61", + hex"67652f7376672b786d6c3b636861727365743d7574662d383b6261736536342c5048", + hex"4e325a79423462577875637a30696148523063446f764c336433647935334d793576", + hex"636d63764d6a41774d43397a646d636949485a705a58644362336739496a41674d43", + hex"41784e4451674e7a4969506a787a64486c735a543438495674445245465551567375", + hex"516e747a64484a766132557462476c755a57707661573436636d3931626d52394c6b", + hex"4e37633352796232746c4c5731706447567962476c74615851364d5442394c6b5237", + hex"633352796232746c4c5864705a48526f4f6a4a394c6b56375a6d6c7362446f6a4f57", + hex"4935596a6c686653354765334e30636d39725a5331736157356c593246774f6e4a76", + hex"6457356b66563164506a7776633352356247552b5047636764484a68626e4e6d6233", + hex"4a7450534a74595852796158676f4d5334774d694177494441674d5334774d694134", + hex"4c6a45674d436b69506a78775958526f49475a706247773949694e6d5a6d59694947", + hex"5139496b30784f53417a4d6d677a4e4859794e4567784f586f694c7a34385a79427a", + hex"64484a766132553949694d774d44416949474e7359584e7a50534a4349454d675243", + hex"492b50484268644767675a6d6c7362443069493245314e7a6b7a4f5349675a443069", + hex"545449314944517761446c324d545a6f4c546c364969382b50484268644767675a6d", + hex"6c7362443069497a6b795a444e6d4e5349675a443069545451774944517761446832", + hex"4e3267744f486f694c7a3438634746306143426d615778735053496a5a5745315954", + hex"51334969426b50534a4e4e544d674d7a4a494d546c324c5446734d5459744d545967", + hex"4d5467674d545a364969382b50484268644767675a6d6c7362443069626d39755a53", + hex"49675a4430695454453549444d7961444d30646a49305344453565694976506a7877", + hex"5958526f49475a706247773949694e6c595456684e44636949475139496b30794f53", + hex"41794d5777744e53413164693035614456364969382b5043396e506a77765a7a3438", + hex"5a794230636d467563325a76636d3039496d316864484a70654367754f4451674d43", + hex"4177494334344e4341324e5341314b53492b50484268644767675a44306954546b75", + hex"4e5341794d693435624451754f4341324c6a52684d7934784d69417a4c6a45794944", + hex"41674d4341784c544d674d693479624330304c6a67744e6934305979347a4c544575", + hex"4e4341784c6a59744d69343049444d744d693479656949675a6d6c73624430694932", + hex"517759325a6a5a534976506a78775958526f49475a706247773949694d774d544178", + hex"4d44456949475139496b30304d53343349444d344c6a56734e5334784c5459754e53", + hex"4976506a78775958526f49475139496b30304d693435494449334c6a684d4d546775", + hex"4e4341314f4334784944493049445979624449784c6a67744d6a63754d7941794c6a", + hex"4d744d693434656949675932786863334d39496b55694c7a3438634746306143426d", + hex"615778735053496a4d4445774d5441784969426b50534a4e4e444d754e4341794f53", + hex"347a624330304c6a63674e5334344969382b50484268644767675a44306954545132", + hex"4c6a67674d7a4a6a4d793479494449754e6941344c6a63674d533479494445794c6a", + hex"45744d793479637a4d754e6930354c6a6b754d7930784d693431624330314c6a4567", + hex"4e6934314c5449754f4330754d5330754e7930794c6a63674e5334784c5459754e57", + hex"4d744d7934794c5449754e6930344c6a63744d5334794c5445794c6a45674d793479", + hex"6379307a4c6a59674f5334354c53347a494445794c6a556949474e7359584e7a5053", + hex"4a464969382b50484268644767675a6d6c7362443069493245314e7a6b7a4f534967", + hex"5a443069545449334c6a4d674d6a5a734d5445754f4341784e53343349444d754e43", + hex"41794c6a51674f533478494445304c6a51744d793479494449754d79307849433433", + hex"4c5445774c6a49744d544d754e6930784c6a4d744d7934354c5445784c6a67744d54", + hex"55754e336f694c7a3438634746306143426b50534a4e4d5449674d546b754f577731", + hex"4c6a6b674e793435494445774c6a49744e7934324c544d754e4330304c6a567a4e69", + hex"34344c5455754d5341784d4334334c5451754e574d77494441744e6934324c544d74", + hex"4d544d754d7941784c6a46544d5449674d546b754f5341784d6941784f5334356569", + hex"49675932786863334d39496b55694c7a34385a79426d6157787350534a756232356c", + hex"4969427a64484a766132553949694d774d44416949474e7359584e7a50534a434945", + hex"4d675243492b50484268644767675a44306954545579494455344c6a6c4d4e444175", + hex"4f5341304d7934796243307a4c6a45744d69347a4c5445774c6a59744d5451754e79", + hex"30794c6a6b674d693479494445774c6a59674d5451754e7941784c6a45674d793432", + hex"494445784c6a55674d5455754e58704e4d5449754e5341784f533434624455754f43", + hex"4134494445774c6a4d744e7934304c544d754d7930304c6a5a7a4e6934354c545567", + hex"4d5441754f4330304c6a4e6a4d4341774c5459754e69307a4c6a45744d544d754d79", + hex"3435637930784d43347a494463754e4330784d43347a494463754e4870744c544975", + hex"4e6941794c6a6c734e433433494459754e574d744c6a55674d53347a4c5445754e79", + hex"41794c6a45744d7941794c6a4a734c5451754e7930324c6a566a4c6a4d744d533430", + hex"494445754e6930794c6a51674d7930794c6a4a364969382b50484268644767675a44", + hex"3069545451784c6a4d674d7a67754e5777314c6a45744e6934316253307a4c6a5574", + hex"4d693433624330304c6a59674e533434625467754d53307a4c6a466a4d7934794944", + hex"49754e6941344c6a63674d533479494445794c6a45744d793479637a4d754e693035", + hex"4c6a6b754d7930784d693431624330314c6a45674e6934314c5449754f4330754d53", + hex"30754f4330794c6a63674e5334784c5459754e574d744d7934794c5449754e693034", + hex"4c6a63744d5334794c5445794c6a45674d7934794c544d754e4341304c6a4d744d79", + hex"343249446b754f5330754d7941784d6934314969426a6247467a637a306952694976", + hex"506a78775958526f49475139496b307a4d433434494451304c6a524d4d546b674e54", + hex"67754f57773049444d674d5441744d5449754e7949675932786863334d39496b5969", + hex"4c7a34384c32632b5043396e506a777663335a6e50673d3d227d" + ); /* ","description":"This%20NFT%20can%20be%20redeemed%20on%20HomeWork%20 + to%20grant%20a%20controller%20the%20exclusive%20right%20to%20deploy%20 + contracts%20with%20arbitrary%20bytecode%20to%20the%20designated%20home + %20address.","image":"data:image/svg+xml;charset=utf-8;base64,..."} */ + + // Deploy payload to the runtime storage contract. + _deployRuntimeStorageContract(key, code); + } + + /** + * @notice Perform phase three of the deployment and disable this contract. + * @param key bytes32 The salt to provide to create2. + */ + function phaseThree(bytes32 key) external onlyUntilDisabled { + // Use metamorphic initialization code to deploy contract to home address. + _deployToHomeAddress(key); + + // Disable this contract from here on out - use HomeWork itself instead. + _disabled = true; + } + + /** + * @notice View function used by the metamorphic initialization code when + * deploying a contract to a home address. It returns the address of the + * runtime storage contract that holds the contract creation code, which the + * metamorphic creation code then `DELEGATECALL`s into in order to set up the + * contract and deploy the target runtime code. + * @return The current runtime storage contract that contains the target + * contract creation code. + * @dev This method is not meant to be part of the user-facing contract API, + * but is rather a mechanism for enabling the deployment of arbitrary code via + * fixed initialization code. The odd naming is chosen so that function + * selector will be 0x00000009 - that way, the metamorphic contract can simply + * use the `PC` opcode in order to push the selector to the stack. + */ + function getInitializationCodeFromContractRuntime_6CLUNS() + external + view + returns (address initializationRuntimeStorageContract) + { + // Return address of contract with initialization code set as runtime code. + initializationRuntimeStorageContract = _initializationRuntimeStorageContract; + } + + /** + * @notice Internal function for deploying a runtime storage contract given a + * particular payload. + * @dev To take the provided code payload and deploy a contract with that + * payload as its runtime code, use the following prelude: + * + * 0x600b5981380380925939f3... + * + * 00 60 push1 0b [11 -> offset] + * 02 59 msize [offset, 0] + * 03 81 dup2 [offset, 0, offset] + * 04 38 codesize [offset, 0, offset, codesize] + * 05 03 sub [offset, 0, codesize - offset] + * 06 80 dup1 [offset, 0, codesize - offset, codesize - offset] + * 07 92 swap3 [codesize - offset, 0, codesize - offset, offset] + * 08 59 msize [codesize - offset, 0, codesize - offset, offset, 0] + * 09 39 codecopy [codesize - offset, 0] + * 10 f3 return [] *init_code_in_runtime* + * ... init_code + */ + function _deployRuntimeStorageContract(bytes32 key, bytes memory payload) + internal + returns (address runtimeStorageContract) + { + // Construct the contract creation code using the prelude and the payload. + bytes memory runtimeStorageContractCreationCode = abi.encodePacked( + _ARBITRARY_RUNTIME_PRELUDE, + payload + ); + + assembly { + // Get the location and length of the newly-constructed creation code. + let encoded_data := add(0x20, runtimeStorageContractCreationCode) + let encoded_size := mload(runtimeStorageContractCreationCode) + + // Deploy the runtime storage contract via `CREATE2`. + runtimeStorageContract := create2(0, encoded_data, encoded_size, key) + + // Pass along revert message if the contract did not deploy successfully. + if iszero(runtimeStorageContract) { + returndatacopy(0, 0, returndatasize) + revert(0, returndatasize) + } + } + + // Emit an event with address of newly-deployed runtime storage contract. + emit StorageContractDeployment(runtimeStorageContract); + } + + /** + * @notice Internal function for deploying arbitrary contract code to the home + * address corresponding to a suppied key via metamorphic initialization code. + * @dev This deployment method uses the "metamorphic delegator" pattern, where + * it will retrieve the address of the contract that contains the target + * initialization code, then delegatecall into it, which executes the + * initialization code stored there and returns the runtime code (or reverts). + * Then, the runtime code returned by the delegatecall is returned, and since + * we are still in the initialization context, it will be set as the runtime + * code of the metamorphic contract. The 32-byte metamorphic initialization + * code is as follows: + * + * 0x5859385958601c335a585952fa1582838382515af43d3d93833e601e57fd5bf3 + * + * 00 58 PC [0] + * 01 59 MSIZE [0, 0] + * 02 38 CODESIZE [0, 0, codesize -> 32] + * returndatac03 59 MSIZE [0, 0, 32, 0] + * 04 58 PC [0, 0, 32, 0, 4] + * 05 60 PUSH1 0x1c [0, 0, 32, 0, 4, 28] + * 07 33 CALLER [0, 0, 32, 0, 4, 28, caller] + * 08 5a GAS [0, 0, 32, 0, 4, 28, caller, gas] + * 09 58 PC [0, 0, 32, 0, 4, 28, caller, gas, 9 -> selector] + * 10 59 MSIZE [0, 0, 32, 0, 4, 28, caller, gas, selector, 0] + * 11 52 MSTORE [0, 0, 32, 0, 4, 28, caller, gas] + * 12 fa STATICCALL [0, 0, 1 => success] + * 13 15 ISZERO [0, 0, 0] + * 14 82 DUP3 [0, 0, 0, 0] + * 15 83 DUP4 [0, 0, 0, 0, 0] + * 16 83 DUP4 [0, 0, 0, 0, 0, 0] + * 17 82 DUP3 [0, 0, 0, 0, 0, 0, 0] + * 18 51 MLOAD [0, 0, 0, 0, 0, 0, init_in_runtime_address] + * 19 5a GAS [0, 0, 0, 0, 0, 0, init_in_runtime_address, gas] + * 20 f4 DELEGATECALL [0, 0, 1 => success] {runtime_code} + * 21 3d RETURNDATASIZE [0, 0, 1 => success, size] + * 22 3d RETURNDATASIZE [0, 0, 1 => success, size, size] + * 23 93 SWAP4 [size, 0, 1 => success, size, 0] + * 24 83 DUP4 [size, 0, 1 => success, size, 0, 0] + * 25 3e RETURNDATACOPY [size, 0, 1 => success] + * 26 60 PUSH1 0x1e [size, 0, 1 => success, 30] + * 28 57 JUMPI [size, 0] + * 29 fd REVERT [] *runtime_code* + * 30 5b JUMPDEST [size, 0] + * 31 f3 RETURN [] + */ + function _deployToHomeAddress(bytes32 key) internal { + // Declare a variable for the home address. + address homeAddress; + + assembly { + // Write the 32-byte metamorphic initialization code to scratch space. + mstore( + 0, + 0x5859385958601c335a585952fa1582838382515af43d3d93833e601e57fd5bf3 + ) + + // Call `CREATE2` using above init code with the supplied key as the salt. + homeAddress := create2(callvalue, 0, 32, key) + + // Pass along revert message if the contract did not deploy successfully. + if iszero(homeAddress) { + returndatacopy(0, 0, returndatasize) + revert(0, returndatasize) + } + } + + // Clear the address of the runtime storage contract from storage. + delete _initializationRuntimeStorageContract; + + // Emit an event with home address and key for the newly-deployed contract. + emit HomeWorkDeployment(homeAddress, key); + } + + /** + * @notice Modifier to disable the contract once deployment is complete. + */ + modifier onlyUntilDisabled() { + require(!_disabled, "Contract is disabled."); + _; + } +} \ No newline at end of file diff --git a/mix.lock b/mix.lock index 5bcd8cacf1..258c71f03f 100644 --- a/mix.lock +++ b/mix.lock @@ -11,6 +11,7 @@ "benchee": {:hex, :benchee, "0.13.2", "30cd4ff5f593fdd218a9b26f3c24d580274f297d88ad43383afe525b1543b165", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, "benchee_csv": {:hex, :benchee_csv, "0.8.0", "0ca094677d6e2b2f601b7ee7864b754789ef9d24d079432e5e3d6f4fb83a4d80", [:mix], [{:benchee, "~> 0.12", [hex: :benchee, optional: false]}, {:csv, "~> 2.0", [hex: :csv, optional: false]}]}, "binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], []}, + "briefly": {:git, "https://github.com/CargoSense/briefly.git", "2526e9674a4e6996137e066a1295ea60962712b8", []}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []}, "bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm"}, "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, From a1eaa412804fad347e8e142bb290699bd407a84c Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Thu, 20 Jun 2019 15:51:38 +0300 Subject: [PATCH 45/90] added styles for download transactions csv button --- .../assets/css/components/_transaction.scss | 50 +++++++++++++++++++ .../address_transaction/index.html.eex | 14 ++++-- apps/block_scout_web/priv/gettext/default.pot | 19 +++---- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/apps/block_scout_web/assets/css/components/_transaction.scss b/apps/block_scout_web/assets/css/components/_transaction.scss index 5214b0b301..b949e0f055 100644 --- a/apps/block_scout_web/assets/css/components/_transaction.scss +++ b/apps/block_scout_web/assets/css/components/_transaction.scss @@ -4,3 +4,53 @@ line-height: 1.2; margin: 0 0 12px; } + +.transaction-top-panel, .transaction-bottom-panel { + display: flex; + justify-content: space-between; + flex-direction: column; + @media (min-width: 500px) { + flex-direction: row; + } +} + +.transaction-top-panel { + .pagination-container { + margin-top: 30px; + } + @media (min-width: 500px) { + align-items: flex-start; + .pagination-container { + margin-top: 0; + } + } +} + +.transaction-bottom-panel { + @media (min-width: 500px) { + align-items: flex-end; + } +} + +.download-csv { + display: inline-block; + height: 24px; + background: #f5f6fa; + border-radius: 2px; + outline: none; + font-family: Nunito, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 12px; + line-height: 25px; + padding: 0 10px; + font-weight: 600; + cursor: pointer; + color: #a3a9b5; + text-align: center; + transition: .1s ease-in; + text-decoration: none !important; + &:hover { + background-color: $primary; + color: #fff; + border-color: $primary; + } +} \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex index 87ab7eba56..5c1552e943 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex @@ -6,7 +6,6 @@
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %> - - <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
+ to_string(@address.hash)}) %>><%= gettext("Download All Transactions as CSV") %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
- - <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
+ to_string(@address.hash)}) %>><%= gettext("Download All Transactions as CSV") %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 90c5b8751b..e498b3dbfb 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -111,7 +111,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:24 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:23 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -244,7 +244,7 @@ msgid "Connection Lost, click to load newer internal transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:12 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:11 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:15 #: lib/block_scout_web/templates/transaction/index.html.eex:15 msgid "Connection Lost, click to load newer transactions" @@ -404,7 +404,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:44 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:41 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:40 #: lib/block_scout_web/views/address_internal_transaction_view.ex:7 #: lib/block_scout_web/views/address_transaction_view.ex:7 msgid "From" @@ -742,7 +742,7 @@ msgid "There are no tokens." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:63 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:64 msgid "There are no transactions for this address." msgstr "" @@ -763,7 +763,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:33 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:30 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:29 #: lib/block_scout_web/views/address_internal_transaction_view.ex:6 #: lib/block_scout_web/views/address_transaction_view.ex:6 msgid "To" @@ -867,7 +867,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:3 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:16 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:15 #: lib/block_scout_web/templates/block_transaction/index.html.eex:10 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/chain/show.html.eex:108 @@ -1211,7 +1211,7 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:21 #: lib/block_scout_web/templates/address_token/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:58 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:59 #: lib/block_scout_web/templates/address_validation/index.html.eex:22 #: lib/block_scout_web/templates/block_transaction/index.html.eex:23 #: lib/block_scout_web/templates/chain/show.html.eex:91 @@ -1696,6 +1696,7 @@ msgid "New Smart Contract Verification" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:9 -msgid "Download all transactions as csv" +#: lib/block_scout_web/templates/address_transaction/index.html.eex:54 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:71 +msgid "Download All Transactions as CSV" msgstr "" From 2024c0691846cb8f599cb8b04b9cc95df77f2580 Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Thu, 20 Jun 2019 15:55:08 +0300 Subject: [PATCH 46/90] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b3987ac2..e16b56929c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - [#2064](https://github.com/poanetwork/blockscout/pull/2064) - feat: add fields to tx apis, small cleanups ### Fixes +- [#2206](https://github.com/poanetwork/blockscout/pull/2206) - added styles for 'Download All Transactions as CSV' button - [#2099](https://github.com/poanetwork/blockscout/pull/2099) - logs search input width - [#2098](https://github.com/poanetwork/blockscout/pull/2098) - nav dropdown issue, logo size issue - [#2082](https://github.com/poanetwork/blockscout/pull/2082) - dropdown styles, tooltip gap fix, 404 page added From e15142f82b7e7b46040d0ca9cdcd8883695d0ede Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 20 Jun 2019 15:57:08 +0300 Subject: [PATCH 47/90] add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9ca13594..2629a797b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions - [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining - [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test +- [#2204](https://github.com/poanetwork/blockscout/pull/2204) - fix large contract verification ### Chore - [#2127](https://github.com/poanetwork/blockscout/pull/2127) - use previouse chromedriver version From f8966e259c3503797e50a887e86def511e592c70 Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Thu, 20 Jun 2019 17:31:22 +0300 Subject: [PATCH 48/90] redesigned 'download csv' button --- .../assets/css/components/_transaction.scss | 73 +++++++------------ .../address_transaction/index.html.eex | 20 +++-- apps/block_scout_web/priv/gettext/default.pot | 5 +- 3 files changed, 42 insertions(+), 56 deletions(-) diff --git a/apps/block_scout_web/assets/css/components/_transaction.scss b/apps/block_scout_web/assets/css/components/_transaction.scss index b949e0f055..1fd1c6241e 100644 --- a/apps/block_scout_web/assets/css/components/_transaction.scss +++ b/apps/block_scout_web/assets/css/components/_transaction.scss @@ -1,56 +1,35 @@ -.transaction-details-address { - font-size: 12px; - font-weight: bold; - line-height: 1.2; - margin: 0 0 12px; -} - -.transaction-top-panel, .transaction-bottom-panel { +.transaction-bottom-panel { display: flex; - justify-content: space-between; flex-direction: column; - @media (min-width: 500px) { - flex-direction: row; + @media (min-width: 768px) { + flex-direction: row; + justify-content: space-between; + align-items: flex-end; } } -.transaction-top-panel { - .pagination-container { - margin-top: 30px; +.download-all-transactions { + text-align: center; + color: #a3a9b5; + font-size: 14px; + margin-top: 10px; + @media (min-width: 768px) { + margin-top: 0; } - @media (min-width: 500px) { - align-items: flex-start; - .pagination-container { - margin-top: 0; + .download-all-transactions-link { + text-decoration: none; + svg { + position: relative; + margin-left: 2px; + top: -3px; + path { + fill: $primary; + } + } + &:hover { + span { + text-decoration: underline; + } } } -} - -.transaction-bottom-panel { - @media (min-width: 500px) { - align-items: flex-end; - } -} - -.download-csv { - display: inline-block; - height: 24px; - background: #f5f6fa; - border-radius: 2px; - outline: none; - font-family: Nunito, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - font-size: 12px; - line-height: 25px; - padding: 0 10px; - font-weight: 600; - cursor: pointer; - color: #a3a9b5; - text-align: center; - transition: .1s ease-in; - text-decoration: none !important; - &:hover { - background-color: $primary; - color: #fff; - border-color: $primary; - } } \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex index 5c1552e943..a24df921c6 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex @@ -50,10 +50,10 @@
-
- to_string(@address.hash)}) %>><%= gettext("Download All Transactions as CSV") %> - <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> -
+ + + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
- +
- to_string(@address.hash)}) %>><%= gettext("Download All Transactions as CSV") %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index e498b3dbfb..baa5de07df 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1696,7 +1696,6 @@ msgid "New Smart Contract Verification" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:54 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:71 -msgid "Download All Transactions as CSV" +#: lib/block_scout_web/templates/address_transaction/index.html.eex:74 +msgid "CSV" msgstr "" From faabdbbbe6c8efd21410374bbc608fc7a7cb619d Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Thu, 20 Jun 2019 17:35:39 +0300 Subject: [PATCH 49/90] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e16b56929c..315009fd25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - [#2064](https://github.com/poanetwork/blockscout/pull/2064) - feat: add fields to tx apis, small cleanups ### Fixes +- [#2207](https://github.com/poanetwork/blockscout/pull/2207) - new 'download csv' button design - [#2206](https://github.com/poanetwork/blockscout/pull/2206) - added styles for 'Download All Transactions as CSV' button - [#2099](https://github.com/poanetwork/blockscout/pull/2099) - logs search input width - [#2098](https://github.com/poanetwork/blockscout/pull/2098) - nav dropdown issue, logo size issue From 262b59abcee564a53492d256d031a4b955bdeab4 Mon Sep 17 00:00:00 2001 From: zachdaniel Date: Tue, 18 Jun 2019 11:08:33 -0400 Subject: [PATCH 50/90] feat: add BLOCKSCOUT_HOST, and use it in API docs --- CHANGELOG.md | 1 + apps/block_scout_web/config/config.exs | 2 +- .../lib/block_scout_web/templates/api_docs/eth_rpc.html.eex | 2 +- .../lib/block_scout_web/templates/api_docs/index.html.eex | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e62d4751..4d440f38ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache - [#2151](https://github.com/poanetwork/blockscout/pull/2151) - hide dropdown menu then other networks list is empty - [#2146](https://github.com/poanetwork/blockscout/pull/2146) - feat: add eth_getLogs rpc endpoint +- [#2193](https://github.com/poanetwork/blockscout/pull/2193) - feat: add BLOCKSCOUT_HOST, and use it in API docs ### Fixes - [#2201](https://github.com/poanetwork/blockscout/pull/2201) - footer columns fix diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index e601e299f8..28d142eb5c 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -41,7 +41,7 @@ config :block_scout_web, BlockScoutWeb.Endpoint, ] ], url: [ - host: "localhost", + host: System.get_env("BLOCKSCOUT_HOST") || "localhost", path: System.get_env("NETWORK_PATH") || "/" ], render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)], diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex index f85620681a..3a0338f4a3 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex @@ -2,7 +2,7 @@

<%= gettext("ETH RPC API Documentation") %>

-

[ <%= gettext "Base URL:" %> <%= @conn.host %>/api/eth_rpc ]

+

[ <%= gettext "Base URL:" %> <%= BlockScoutWeb.Endpoint.url() %>/api/eth_rpc ]

<%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex index c665fb88e6..0f97b67d45 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex @@ -2,7 +2,7 @@

<%= gettext("API Documentation") %>

-

[ <%= gettext "Base URL:" %> <%= @conn.host %>/api ]

+

[ <%= gettext "Base URL:" %> <%= BlockScoutWeb.Endpoint.url() %>/api ]

<%= gettext "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." %>

From 7a733e66b575839e59526fdb030c745664fa6acd Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Fri, 21 Jun 2019 09:52:21 -0300 Subject: [PATCH 51/90] (update) internationalization files --- apps/block_scout_web/priv/gettext/default.pot | 39 +++++++++++++++-- .../priv/gettext/en/LC_MESSAGES/default.po | 43 ++++++++++++++++--- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 4ca9c47102..1cf4275fcf 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -112,6 +112,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -666,8 +667,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:116 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:133 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:102 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:119 msgid "Search" msgstr "" @@ -1480,8 +1481,8 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:110 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:96 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:100 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1748,3 +1749,33 @@ msgstr "" #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 msgid "here." msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 +msgid "Change Network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +msgid "Favorites" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 +msgid "Mainnet" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 +msgid "Search network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +msgid "Testnet" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index a78a1ce9e8..b1e80dd1ac 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -112,6 +112,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -666,8 +667,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:116 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:133 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:102 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:119 msgid "Search" msgstr "" @@ -1480,8 +1481,8 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:110 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:96 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:100 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1714,7 +1715,7 @@ msgstr "" msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 msgid "ETH RPC API Documentation" msgstr "" @@ -1744,7 +1745,37 @@ msgstr "" msgid "custom RPC" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 msgid "here." msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 +msgid "Change Network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +msgid "Favorites" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 +msgid "Mainnet" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 +msgid "Search network" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +msgid "Testnet" +msgstr "" From 4f8dceed1cb09f87e753554e023424f4edaf560a Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 24 Jun 2019 10:41:45 +0300 Subject: [PATCH 52/90] fix metadata decomdiing in smart contract verification --- apps/explorer/lib/explorer/smart_contract/verifier.ex | 6 ++++++ .../smart_contract/verifier/constructor_arguments.ex | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/apps/explorer/lib/explorer/smart_contract/verifier.ex b/apps/explorer/lib/explorer/smart_contract/verifier.ex index 2dd6545010..ea25d24818 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier.ex @@ -107,6 +107,12 @@ defmodule Explorer.SmartContract.Verifier do |> Enum.reverse() |> :binary.list_to_bin() + # Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst + "a265627a7a72305820" <> <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> _constructor_arguments -> + extracted + |> Enum.reverse() + |> :binary.list_to_bin() + <> <> rest -> do_extract_bytecode([next | extracted], rest) end diff --git a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex index aa32d85bb6..de888c0f97 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex @@ -21,6 +21,11 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do constructor_arguments end + # Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst + defp extract_contrstructor_arguments("a265627a7a72305820" <> <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments) do + constructor_arguments + end + defp extract_constructor_arguments(<<_::binary-size(2)>> <> rest) do extract_constructor_arguments(rest) end From 680c28866c3dc90765e23c27ca60ab97915da0a8 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 24 Jun 2019 11:27:02 +0300 Subject: [PATCH 53/90] fix typo --- .../smart_contract/verifier/constructor_arguments.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex index de888c0f97..d6d1572f10 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex @@ -22,7 +22,10 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do end # Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst - defp extract_contrstructor_arguments("a265627a7a72305820" <> <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments) do + defp extract_constructor_arguments( + "a265627a7a72305820" <> + <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments + ) do constructor_arguments end From 74e969a45d4318c6efb9a70b5acaf65d5ae6c18a Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 24 Jun 2019 11:51:15 +0300 Subject: [PATCH 54/90] add regression test --- .../lib/explorer/smart_contract/verifier.ex | 3 +- .../explorer/smart_contract/verifier_test.exs | 32 ++++++++ .../solidity_0.5.9_smart_contract.sol | 76 +++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 apps/explorer/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol diff --git a/apps/explorer/lib/explorer/smart_contract/verifier.ex b/apps/explorer/lib/explorer/smart_contract/verifier.ex index ea25d24818..f4e486fd50 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier.ex @@ -108,7 +108,8 @@ defmodule Explorer.SmartContract.Verifier do |> :binary.list_to_bin() # Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst - "a265627a7a72305820" <> <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> _constructor_arguments -> + "a265627a7a72305820" <> + <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> _constructor_arguments -> extracted |> Enum.reverse() |> :binary.list_to_bin() diff --git a/apps/explorer/test/explorer/smart_contract/verifier_test.exs b/apps/explorer/test/explorer/smart_contract/verifier_test.exs index ef635c2573..9633804c60 100644 --- a/apps/explorer/test/explorer/smart_contract/verifier_test.exs +++ b/apps/explorer/test/explorer/smart_contract/verifier_test.exs @@ -117,6 +117,38 @@ defmodule Explorer.SmartContract.VerifierTest do assert abi != nil end + test "verifies smart contract compiled with Solidity 0.5.9 (includes new metadata in bytecode) with constructor args" do + path = File.cwd!() <> "/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol" + contract = File.read!(path) + + constructor_arguments = + "00000000000000000000000000000000000000000000003635c9adc5dea000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000a54657374546f6b656e32000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006546f6b656e320000000000000000000000000000000000000000000000000000" + + bytecode = + "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c80633177029f116100715780633177029f1461025f57806354fd4d50146102c557806370a082311461034857806395d89b41146103a0578063a9059cbb14610423578063dd62ed3e14610489576100a9565b806306fdde03146100ae578063095ea7b31461013157806318160ddd1461019757806323b872dd146101b5578063313ce5671461023b575b600080fd5b6100b6610501565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f65780820151818401526020810190506100db565b50505050905090810190601f1680156101235780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61017d6004803603604081101561014757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061059f565b604051808215151515815260200191505060405180910390f35b61019f610691565b6040518082815260200191505060405180910390f35b610221600480360360608110156101cb57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610696565b604051808215151515815260200191505060405180910390f35b61024361090f565b604051808260ff1660ff16815260200191505060405180910390f35b6102ab6004803603604081101561027557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610922565b604051808215151515815260200191505060405180910390f35b6102cd610a14565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561030d5780820151818401526020810190506102f2565b50505050905090810190601f16801561033a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61038a6004803603602081101561035e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ab2565b6040518082815260200191505060405180910390f35b6103a8610afa565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103e85780820151818401526020810190506103cd565b50505050905090810190601f1680156104155780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61046f6004803603604081101561043957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610b98565b604051808215151515815260200191505060405180910390f35b6104eb6004803603604081101561049f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610cfe565b6040518082815260200191505060405180910390f35b60038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105975780601f1061056c57610100808354040283529160200191610597565b820191906000526020600020905b81548152906001019060200180831161057a57829003601f168201915b505050505081565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b600090565b6000816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410158015610762575081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410155b801561076e5750600082115b1561090357816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a360019050610908565b600090505b9392505050565b600460009054906101000a900460ff1681565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60068054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610aaa5780601f10610a7f57610100808354040283529160200191610aaa565b820191906000526020600020905b815481529060010190602001808311610a8d57829003601f168201915b505050505081565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60058054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610b905780601f10610b6557610100808354040283529160200191610b90565b820191906000526020600020905b815481529060010190602001808311610b7357829003601f168201915b505050505081565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410158015610be85750600082115b15610cf357816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a360019050610cf8565b600090505b92915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490509291505056fea265627a7a72305820fe0ba5210ac95870683c2cb054304b04565703bd16c7d7e956df694c9643c6d264736f6c63430005090032" + + contract_address = insert(:contract_address, contract_code: bytecode) + + :transaction + |> insert( + created_contract_address_hash: contract_address.hash, + input: bytecode <> constructor_arguments + ) + |> with_block() + + params = %{ + "contract_source_code" => contract, + "compiler_version" => "v0.5.9+commit.e560f70d", + "evm_version" => "petersburg", + "name" => "TestToken", + "optimization" => false, + "constructor_arguments" => constructor_arguments + } + + assert {:ok, %{abi: abi}} = Verifier.evaluate_authenticity(contract_address.hash, params) + assert abi != nil + end + test "returns error when bytecode doesn't match", %{contract_code_info: contract_code_info} do contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode) diff --git a/apps/explorer/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol b/apps/explorer/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol new file mode 100644 index 0000000000..25601a98b6 --- /dev/null +++ b/apps/explorer/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol @@ -0,0 +1,76 @@ +pragma solidity ^0.5.9; +contract Token { + function totalSupply() public view returns (uint256 supply) {} + function balanceOf(address _owner) public view returns (uint256 balance) {} + function transfer(address _to, uint256 _value) public returns (bool success) {} + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {} + function approve(address _spender, uint256 _value) public returns (bool success) {} + function allowance(address _owner, address _spender) public view returns (uint256 remaining) {} + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); +} + + +contract StandardToken is Token { + function transfer(address _to, uint256 _value) public returns (bool success) { + //if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) { + if (balances[msg.sender] >= _value && _value > 0) { + balances[msg.sender] -= _value; + balances[_to] += _value; + emit Transfer(msg.sender, _to, _value); + return true; + } else { return false; } + } + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { + //if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) { + if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) { + balances[_to] += _value; + balances[_from] -= _value; + allowed[_from][msg.sender] -= _value; + emit Transfer(_from, _to, _value); + return true; + } else { return false; } + } + function balanceOf(address _owner) public view returns (uint256 balance) { + return balances[_owner]; + } + function approve(address _spender, uint256 _value) public returns (bool success) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + function allowance(address _owner, address _spender) public view returns (uint256 remaining) { + return allowed[_owner][_spender]; + } + mapping (address => uint256) balances; + mapping (address => mapping (address => uint256)) allowed; + uint256 totalTokenSupply; +} + +contract TestToken is StandardToken { + + /* Public variables */ + string public name; + uint8 public decimals; + string public symbol; + string public version = '0.1'; + + constructor( + uint256 _initialAmount, + string memory _tokenName, + uint8 _decimalUnits, + string memory _tokenSymbol + ) public { + balances[msg.sender] = _initialAmount; + totalTokenSupply = _initialAmount; + name = _tokenName; + decimals = _decimalUnits; + symbol = _tokenSymbol; + } + + function approveAndCall(address _spender, uint256 _value) public returns (bool success) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } +} \ No newline at end of file From 402775c457a292f567569e902c6d92ddc018a824 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 24 Jun 2019 11:54:09 +0300 Subject: [PATCH 55/90] add CHANGELOG entry --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e62d4751..a59abeecac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,8 @@ - [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions - [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining - [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test -- [#2167](https://github.com/poanetwork/blockscout/pull/2168) - feat: document eth rpc api mimicking endpoints +- [#2167](https://github.com/poanetwork/blockscout/pull/2168) - feat: document eth rpc api mimicking endpoints +- [#2225](https://github.com/poanetwork/blockscout/pull/2225) - fix metadata decoding in Solidity 0.5.9 smart contract verification ### Chore - [#2127](https://github.com/poanetwork/blockscout/pull/2127) - use previouse chromedriver version From 368b08ec457a72ad2cdaecd2c6dc502ac2302a51 Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Mon, 24 Jun 2019 16:14:14 +0300 Subject: [PATCH 56/90] Update favorites.js --- apps/block_scout_web/assets/js/pages/favorites.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/block_scout_web/assets/js/pages/favorites.js b/apps/block_scout_web/assets/js/pages/favorites.js index 9f9d8f39cc..e5c7627575 100644 --- a/apps/block_scout_web/assets/js/pages/favorites.js +++ b/apps/block_scout_web/assets/js/pages/favorites.js @@ -12,12 +12,14 @@ if (localStorage.getItem('favoritesNetworksUrls') === null) { $(document).on('change', ".network-selector-item-favorite input[type='checkbox']", function () { var networkUrl = $(this).attr('data-url') var thisStatus = $(this).is(':checked') - var parent = $(".network-selector-item[data-url='" + networkUrl + "'").clone() var workWith = $(".network-selector-item[data-url='" + networkUrl + "'") // Add new checkbox status to same network in another tabs $(".network-selector-item-favorite input[data-url='" + networkUrl + "']").prop('checked', thisStatus) + // Clone + var parent = $(".network-selector-item[data-url='" + networkUrl + "'").clone() + // Push or remove favorite networks to array var found = $.inArray(networkUrl, favoritesNetworksUrls) if (found < 0 && thisStatus === true) { @@ -29,6 +31,10 @@ $(document).on('change', ".network-selector-item-favorite input[type='checkbox'] } } + // Push to localstorage + var willBePushed = JSON.stringify(favoritesNetworksUrls) + localStorage.setItem('favoritesNetworksUrls', willBePushed) + // Append or remove item from 'favorites' tab if (thisStatus === true) { favoritesContainer.append(parent[0]) @@ -41,15 +47,12 @@ $(document).on('change', ".network-selector-item-favorite input[type='checkbox'] } } - // Push to localstorage - var willBePushed = JSON.stringify(favoritesNetworksUrls) - localStorage.setItem('favoritesNetworksUrls', willBePushed) }) if (favoritesNetworksUrls.length > 0) { $('.js-favorites-tab .network-selector-tab-content-empty').hide() for (var i = 0; i < favoritesNetworksUrls.length + 1; i++) { - $(".network-selector-item[data-url='" + favoritesNetworksUrls[i] + "'").find('input').prop('checked', true) + $(".network-selector-item[data-url='" + favoritesNetworksUrls[i] + "'").find('input[data-url]').prop('checked', true) var parent = $(".network-selector-item[data-url='" + favoritesNetworksUrls[i] + "'").clone() favoritesContainer.append(parent[0]) } From c947a8d67bcb01d0ce19b2816c093b19fed4dc0f Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Mon, 24 Jun 2019 16:15:24 +0300 Subject: [PATCH 57/90] Update _network_selector_item.html.eex --- .../templates/layout/_network_selector_item.html.eex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex index af6d8df375..55ea7a6023 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex @@ -1,7 +1,7 @@
-
\ No newline at end of file +
From f6c48f01f02b8ce1acc7aaf2202e5b413b0293a2 Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Mon, 24 Jun 2019 16:18:04 +0300 Subject: [PATCH 58/90] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2c1aa2d25..7dc1eb29a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - [#2064](https://github.com/poanetwork/blockscout/pull/2064) - feat: add fields to tx apis, small cleanups ### Fixes +- [#2228](https://github.com/poanetwork/blockscout/pull/2228) - favorites duplication issues, active radio issue - [#2082](https://github.com/poanetwork/blockscout/pull/2082) - dropdown styles, tooltip gap fix, 404 page added - [#2077](https://github.com/poanetwork/blockscout/pull/2077) - ui issues - [#2072](https://github.com/poanetwork/blockscout/pull/2072) - Fixed checkmarks not showing correctly in tabs. From 0a255264f66735a984c38878e9227c1d08657fe7 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 24 Jun 2019 16:19:12 +0300 Subject: [PATCH 59/90] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fbda34c9c..fdbf8b678f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,7 @@ - [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions - [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining - [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test -- [#2167](https://github.com/poanetwork/blockscout/pull/2168) - feat: document eth rpc api mimicking endpoints +- [#2167](https://github.com/poanetwork/blockscout/pull/2167) - feat: document eth rpc api mimicking endpoints - [#2225](https://github.com/poanetwork/blockscout/pull/2225) - fix metadata decoding in Solidity 0.5.9 smart contract verification ### Chore From f6a284cf6b9f1ead52763bfc0f83a037d9c8397d Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Mon, 24 Jun 2019 16:24:20 +0300 Subject: [PATCH 60/90] Update favorites.js --- apps/block_scout_web/assets/js/pages/favorites.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/block_scout_web/assets/js/pages/favorites.js b/apps/block_scout_web/assets/js/pages/favorites.js index e5c7627575..055cac8c0f 100644 --- a/apps/block_scout_web/assets/js/pages/favorites.js +++ b/apps/block_scout_web/assets/js/pages/favorites.js @@ -46,7 +46,6 @@ $(document).on('change', ".network-selector-item-favorite input[type='checkbox'] $('.js-favorites-tab .network-selector-tab-content-empty').show() } } - }) if (favoritesNetworksUrls.length > 0) { From 91f33e2f38f9dfb154f861f3c48f2cfc8e1b72a5 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 25 Jun 2019 10:33:49 +0300 Subject: [PATCH 61/90] fix gettext --- apps/block_scout_web/priv/gettext/default.pot | 57 +++++++++++++- .../priv/gettext/en/LC_MESSAGES/default.po | 77 ++++++++++++++++--- 2 files changed, 122 insertions(+), 12 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index b6fcc69fcc..d21d2703bd 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1692,4 +1692,59 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27 msgid "There is no decompilded contracts for this address." -msgstr "" \ No newline at end of file +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 +msgid " is recommended." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 +msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:44 +msgid "ERC-20 " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:45 +msgid "ERC-721 " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 +msgid "ETH RPC API Documentation" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +msgid "Eth RPC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 +msgid "However, in general, the" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 +msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 +msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 +msgid "custom RPC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 +msgid "here." +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index be7eb43b58..ee32dea6ce 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -105,7 +105,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/index.html.eex:4 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:59 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:60 msgid "Addresses" msgstr "" @@ -210,8 +210,8 @@ msgstr "" #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 #: lib/block_scout_web/templates/address/overview.html.eex:144 #: lib/block_scout_web/templates/address/overview.html.eex:152 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:115 msgid "Close" msgstr "" @@ -626,8 +626,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:33 #: lib/block_scout_web/templates/address/overview.html.eex:143 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 msgid "QR Code" msgstr "" @@ -684,7 +684,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:34 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:37 msgid "Show QR Code" msgstr "" @@ -834,7 +834,7 @@ msgid "Total Difficulty" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:74 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:75 msgid "Total Supply" msgstr "" @@ -881,7 +881,7 @@ msgid "Transactions sent" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:60 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:61 msgid "Transfers" msgstr "" @@ -943,7 +943,7 @@ msgid "Verify & publish" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:54 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:55 msgid "View Contract" msgstr "" @@ -1048,7 +1048,7 @@ msgid "Self-Destruct" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:62 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:63 msgid "Decimals" msgstr "" @@ -1692,4 +1692,59 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27 msgid "There is no decompilded contracts for this address." -msgstr "" \ No newline at end of file +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 +msgid " is recommended." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 +msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:44 +msgid "ERC-20 " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:45 +msgid "ERC-721 " +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 +msgid "ETH RPC API Documentation" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +msgid "Eth RPC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 +msgid "However, in general, the" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 +msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 +msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 +msgid "custom RPC" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 +msgid "here." +msgstr "" From 7cb662d09999b76443ecb16640d401110ea5d10f Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 25 Jun 2019 10:44:47 +0300 Subject: [PATCH 62/90] fix unidentified token transfers --- apps/explorer/lib/explorer/chain.ex | 61 +++++++++++++++-------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4834a20adf..01a44e1cbe 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2935,47 +2935,50 @@ defmodule Explorer.Chain do zero_wei = %Wei{value: Decimal.new(0)} # https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721.sol#L35 - case {to_string(input), value} do - # transferFrom(address,address,uint256) - {"0x23b872dd" <> params, ^zero_wei} -> - types = [:address, :address, {:uint, 256}] - [from_address, to_address, _value] = decode_params(params, types) + result = + case {to_string(input), value} do + # transferFrom(address,address,uint256) + {"0x23b872dd" <> params, ^zero_wei} -> + types = [:address, :address, {:uint, 256}] + [from_address, to_address, _value] = decode_params(params, types) - find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) + find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) - # safeTransferFrom(address,address,uint256) - {"0x42842e0e" <> params, ^zero_wei} -> - types = [:address, :address, {:uint, 256}] - [from_address, to_address, _value] = decode_params(params, types) + # safeTransferFrom(address,address,uint256) + {"0x42842e0e" <> params, ^zero_wei} -> + types = [:address, :address, {:uint, 256}] + [from_address, to_address, _value] = decode_params(params, types) - find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) + find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) - # safeTransferFrom(address,address,uint256,bytes) - {"0xb88d4fde" <> params, ^zero_wei} -> - types = [:address, :address, {:uint, 256}, :bytes] - [from_address, to_address, _value, _data] = decode_params(params, types) + # safeTransferFrom(address,address,uint256,bytes) + {"0xb88d4fde" <> params, ^zero_wei} -> + types = [:address, :address, {:uint, 256}, :bytes] + [from_address, to_address, _value, _data] = decode_params(params, types) - find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) + find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) - {"0xf907fc5b" <> _params, ^zero_wei} -> - :erc20 + {"0xf907fc5b" <> _params, ^zero_wei} -> + :erc20 - # check for ERC 20 or for old ERC 721 token versions - {unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} -> - types = [:address, {:uint, 256}] + # check for ERC 20 or for old ERC 721 token versions + {unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} -> + types = [:address, {:uint, 256}] - [address, value] = decode_params(params, types) + [address, value] = decode_params(params, types) - decimal_value = Decimal.new(value) + decimal_value = Decimal.new(value) - find_erc721_or_erc20_token_transfer(transaction.token_transfers, {address, decimal_value}) + find_erc721_or_erc20_token_transfer(transaction.token_transfers, {address, decimal_value}) - {_params, ^zero_wei} -> - if Enum.count(transaction.token_transfers) > 0, do: :token_transfer + {_params, ^zero_wei} -> + if Enum.count(transaction.token_transfers) > 0, do: :token_transfer - _ -> - nil - end + _ -> + nil + end + + if is_nil(result) && Enum.count(transaction.token_transfers) > 0, do: :token_transfer, else: result rescue _ -> nil end From d8f904f063c9628a2eb08f3e2015aadf8a865337 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 25 Jun 2019 11:04:26 +0300 Subject: [PATCH 63/90] fix spaces --- .../block_scout_web/templates/transaction/overview.html.eex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index b168045fe1..fe347ee904 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -176,14 +176,16 @@

<%= token_type_name(type)%><%= gettext " Token Transfer" %>

- <%= for transfer <- transaction_with_transfers.token_transfers do %>
+ <%= for transfer <- transaction_with_transfers.token_transfers do %> +

<%= token_transfer_amount(transfer) %> <%= link(token_symbol(transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, transfer.token.contract_address_hash)) %>

-
+ <% end %> +
<% _ -> %> From 41b069c1e1fd09137ee09cb04557cf4676de1aca Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 25 Jun 2019 11:11:22 +0300 Subject: [PATCH 64/90] fix credo --- apps/explorer/lib/explorer/chain.ex | 75 +++++++++++++++-------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 01a44e1cbe..e4416030b0 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2933,58 +2933,61 @@ defmodule Explorer.Chain do } = transaction ) do zero_wei = %Wei{value: Decimal.new(0)} + result = find_token_transfer_type(transaction, input, value) - # https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721.sol#L35 - result = - case {to_string(input), value} do - # transferFrom(address,address,uint256) - {"0x23b872dd" <> params, ^zero_wei} -> - types = [:address, :address, {:uint, 256}] - [from_address, to_address, _value] = decode_params(params, types) + if is_nil(result) && Enum.count(transaction.token_transfers) > 0 && value == zero_wei, + do: :token_transfer, + else: result + rescue + _ -> nil + end + + def transaction_token_transfer_type(_), do: nil - find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) + defp find_token_transfer_type(transaction, input, value) do + zero_wei = %Wei{value: Decimal.new(0)} - # safeTransferFrom(address,address,uint256) - {"0x42842e0e" <> params, ^zero_wei} -> - types = [:address, :address, {:uint, 256}] - [from_address, to_address, _value] = decode_params(params, types) + # https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721.sol#L35 + case {to_string(input), value} do + # transferFrom(address,address,uint256) + {"0x23b872dd" <> params, ^zero_wei} -> + types = [:address, :address, {:uint, 256}] + [from_address, to_address, _value] = decode_params(params, types) - find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) + find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) - # safeTransferFrom(address,address,uint256,bytes) - {"0xb88d4fde" <> params, ^zero_wei} -> - types = [:address, :address, {:uint, 256}, :bytes] - [from_address, to_address, _value, _data] = decode_params(params, types) + # safeTransferFrom(address,address,uint256) + {"0x42842e0e" <> params, ^zero_wei} -> + types = [:address, :address, {:uint, 256}] + [from_address, to_address, _value] = decode_params(params, types) - find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) + find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) - {"0xf907fc5b" <> _params, ^zero_wei} -> - :erc20 + # safeTransferFrom(address,address,uint256,bytes) + {"0xb88d4fde" <> params, ^zero_wei} -> + types = [:address, :address, {:uint, 256}, :bytes] + [from_address, to_address, _value, _data] = decode_params(params, types) - # check for ERC 20 or for old ERC 721 token versions - {unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} -> - types = [:address, {:uint, 256}] + find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address}) - [address, value] = decode_params(params, types) + {"0xf907fc5b" <> _params, ^zero_wei} -> + :erc20 - decimal_value = Decimal.new(value) + # check for ERC 20 or for old ERC 721 token versions + {unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} -> + types = [:address, {:uint, 256}] - find_erc721_or_erc20_token_transfer(transaction.token_transfers, {address, decimal_value}) + [address, value] = decode_params(params, types) - {_params, ^zero_wei} -> - if Enum.count(transaction.token_transfers) > 0, do: :token_transfer + decimal_value = Decimal.new(value) - _ -> - nil - end + find_erc721_or_erc20_token_transfer(transaction.token_transfers, {address, decimal_value}) - if is_nil(result) && Enum.count(transaction.token_transfers) > 0, do: :token_transfer, else: result - rescue - _ -> nil + _ -> + nil + end end - def transaction_token_transfer_type(_), do: nil - defp find_erc721_token_transfer(token_transfers, {from_address, to_address}) do token_transfer = Enum.find(token_transfers, fn token_transfer -> From 6d2e355ece5007f8c86edbcb840633d0f0a0aeb0 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 25 Jun 2019 11:12:00 +0300 Subject: [PATCH 65/90] fix gettext --- apps/block_scout_web/priv/gettext/default.pot | 10 +++++----- .../priv/gettext/en/LC_MESSAGES/default.po | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index d21d2703bd..5a0edf2bfe 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -378,7 +378,7 @@ msgstr "" #: lib/block_scout_web/templates/layout/app.html.eex:55 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_tile.html.eex:30 -#: lib/block_scout_web/templates/transaction/overview.html.eex:194 +#: lib/block_scout_web/templates/transaction/overview.html.eex:196 #: lib/block_scout_web/views/wei_helpers.ex:78 msgid "Ether" msgstr "" @@ -490,7 +490,7 @@ msgid "Less than" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:222 +#: lib/block_scout_web/templates/transaction/overview.html.eex:224 msgid "Limit" msgstr "" @@ -907,7 +907,7 @@ msgid "Unique Token" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:216 +#: lib/block_scout_web/templates/transaction/overview.html.eex:218 msgid "Used" msgstr "" @@ -927,7 +927,7 @@ msgid "Validations" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:194 +#: lib/block_scout_web/templates/transaction/overview.html.eex:196 msgid "Value" msgstr "" @@ -1536,7 +1536,7 @@ msgid "View All Transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:212 +#: lib/block_scout_web/templates/transaction/overview.html.eex:214 msgid "Gas" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index ee32dea6ce..45a2e7d8de 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -378,7 +378,7 @@ msgstr "" #: lib/block_scout_web/templates/layout/app.html.eex:55 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_tile.html.eex:30 -#: lib/block_scout_web/templates/transaction/overview.html.eex:194 +#: lib/block_scout_web/templates/transaction/overview.html.eex:196 #: lib/block_scout_web/views/wei_helpers.ex:78 msgid "Ether" msgstr "POA" @@ -490,7 +490,7 @@ msgid "Less than" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:222 +#: lib/block_scout_web/templates/transaction/overview.html.eex:224 msgid "Limit" msgstr "" @@ -907,7 +907,7 @@ msgid "Unique Token" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:216 +#: lib/block_scout_web/templates/transaction/overview.html.eex:218 msgid "Used" msgstr "" @@ -927,7 +927,7 @@ msgid "Validations" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:194 +#: lib/block_scout_web/templates/transaction/overview.html.eex:196 msgid "Value" msgstr "" @@ -1536,7 +1536,7 @@ msgid "View All Transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:212 +#: lib/block_scout_web/templates/transaction/overview.html.eex:214 msgid "Gas" msgstr "" @@ -1714,7 +1714,7 @@ msgstr "" msgid "ERC-721 " msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 msgid "ETH RPC API Documentation" msgstr "" @@ -1744,7 +1744,7 @@ msgstr "" msgid "custom RPC" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 msgid "here." msgstr "" From d47cb2bda41c79230be0054ee6baea82972f7e4d Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 25 Jun 2019 15:59:17 +0300 Subject: [PATCH 66/90] fix port --- .../templates/api_docs/eth_rpc.html.eex | 4 ++-- .../block_scout_web/templates/api_docs/index.html.eex | 2 +- .../lib/block_scout_web/views/api_docs_view.ex | 10 +++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex index 3a0338f4a3..37b9ecdaa2 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex @@ -2,9 +2,9 @@

<%= gettext("ETH RPC API Documentation") %>

-

[ <%= gettext "Base URL:" %> <%= BlockScoutWeb.Endpoint.url() %>/api/eth_rpc ]

+

[ <%= gettext "Base URL:" %> <%= url() %>/api/eth_rpc ]

- <%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %> + <%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %> <%= gettext "here." %> <%= gettext "This is useful to allow sending requests to blockscout without having to change anything about the request." %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex index 0f97b67d45..5244458bb6 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex @@ -2,7 +2,7 @@

<%= gettext("API Documentation") %>

-

[ <%= gettext "Base URL:" %> <%= BlockScoutWeb.Endpoint.url() %>/api ]

+

[ <%= gettext "Base URL:" %> <%= url() %>/api ]

<%= gettext "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." %>

diff --git a/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex index cbc096aeb8..267099fa2b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex @@ -1,7 +1,7 @@ defmodule BlockScoutWeb.APIDocsView do use BlockScoutWeb, :view - alias BlockScoutWeb.LayoutView + alias BlockScoutWeb.{Endpoint, LayoutView} def action_tile_id(module, action) do "#{module}-#{action}" @@ -33,4 +33,12 @@ defmodule BlockScoutWeb.APIDocsView do "&#{param.key}=" <> "{#{param.placeholder}}" end) end + + defp url do + if System.get_env("BLOCKSCOUT_HOST") do + "http://" <> System.get_env("BLOCKSCOUT_HOST") + else + Endpoint.url() + end + end end From 5f9caa80636a35af989f05f95c316fec14c4e433 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 25 Jun 2019 16:49:11 +0300 Subject: [PATCH 67/90] fix gettext --- apps/block_scout_web/priv/gettext/default.pot | 57 +++++++++++++- .../priv/gettext/en/LC_MESSAGES/default.po | 77 ++++++++++++++++--- 2 files changed, 122 insertions(+), 12 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index a62c9c0844..a566768750 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1697,4 +1697,59 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:178 msgid " Token Transfer" -msgstr "" \ No newline at end of file +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 +msgid " is recommended." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 +msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_token/index.html.eex:8 +msgid "Download all token transfers as csv" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 +msgid "ETH RPC API Documentation" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +msgid "Eth RPC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 +msgid "However, in general, the" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27 +msgid "There is no decompilded contracts for this address." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 +msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 +msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 +msgid "custom RPC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 +msgid "here." +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 5e193fdeeb..f114a87464 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -105,7 +105,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/index.html.eex:4 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:59 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:60 msgid "Addresses" msgstr "" @@ -210,8 +210,8 @@ msgstr "" #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 #: lib/block_scout_web/templates/address/overview.html.eex:144 #: lib/block_scout_web/templates/address/overview.html.eex:152 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:115 msgid "Close" msgstr "" @@ -626,8 +626,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:33 #: lib/block_scout_web/templates/address/overview.html.eex:143 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 msgid "QR Code" msgstr "" @@ -684,7 +684,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:34 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:37 msgid "Show QR Code" msgstr "" @@ -834,7 +834,7 @@ msgid "Total Difficulty" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:74 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:75 msgid "Total Supply" msgstr "" @@ -881,7 +881,7 @@ msgid "Transactions sent" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:60 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:61 msgid "Transfers" msgstr "" @@ -943,7 +943,7 @@ msgid "Verify & publish" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:54 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:55 msgid "View Contract" msgstr "" @@ -1048,7 +1048,7 @@ msgid "Self-Destruct" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:62 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:63 msgid "Decimals" msgstr "" @@ -1697,4 +1697,59 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:178 msgid " Token Transfer" -msgstr "" \ No newline at end of file +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 +msgid " is recommended." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 +msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_token/index.html.eex:8 +msgid "Download all token transfers as csv" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 +msgid "ETH RPC API Documentation" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +msgid "Eth RPC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 +msgid "However, in general, the" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27 +msgid "There is no decompilded contracts for this address." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 +msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 +msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 +msgid "custom RPC" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 +msgid "here." +msgstr "" From 5b8c1f882c288e82fe679a6825694fc5a1869ded Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 25 Jun 2019 16:59:40 +0300 Subject: [PATCH 68/90] fix gettext --- apps/block_scout_web/priv/gettext/default.pot | 62 +++++++++++- .../priv/gettext/en/LC_MESSAGES/default.po | 96 +++++++++++++++---- 2 files changed, 139 insertions(+), 19 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index fec78a8778..95780b61dc 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1692,4 +1692,64 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:3 msgid "New Smart Contract Verification" -msgstr "" \ No newline at end of file +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:178 +msgid " Token Transfer" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 +msgid " is recommended." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 +msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_transaction/index.html.eex:74 +msgid "CSV" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 +msgid "ETH RPC API Documentation" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +msgid "Eth RPC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 +msgid "However, in general, the" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27 +msgid "There is no decompilded contracts for this address." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 +msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 +msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 +msgid "custom RPC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 +msgid "here." +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 5b9551710a..96ec21b201 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -105,13 +105,13 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/index.html.eex:4 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:59 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:60 msgid "Addresses" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:24 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:23 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -210,8 +210,8 @@ msgstr "" #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 #: lib/block_scout_web/templates/address/overview.html.eex:144 #: lib/block_scout_web/templates/address/overview.html.eex:152 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:115 msgid "Close" msgstr "" @@ -245,7 +245,7 @@ msgid "Connection Lost, click to load newer internal transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:12 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:11 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:15 #: lib/block_scout_web/templates/transaction/index.html.eex:15 msgid "Connection Lost, click to load newer transactions" @@ -405,7 +405,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:44 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:41 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:40 #: lib/block_scout_web/views/address_internal_transaction_view.ex:7 #: lib/block_scout_web/views/address_transaction_view.ex:7 msgid "From" @@ -626,8 +626,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:33 #: lib/block_scout_web/templates/address/overview.html.eex:143 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 msgid "QR Code" msgstr "" @@ -684,7 +684,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:34 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:37 msgid "Show QR Code" msgstr "" @@ -743,7 +743,7 @@ msgid "There are no tokens." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:63 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:64 msgid "There are no transactions for this address." msgstr "" @@ -764,7 +764,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:33 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:30 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:29 #: lib/block_scout_web/views/address_internal_transaction_view.ex:6 #: lib/block_scout_web/views/address_transaction_view.ex:6 msgid "To" @@ -834,7 +834,7 @@ msgid "Total Difficulty" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:74 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:75 msgid "Total Supply" msgstr "" @@ -866,7 +866,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:3 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:16 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:15 #: lib/block_scout_web/templates/block_transaction/index.html.eex:10 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/chain/show.html.eex:108 @@ -881,7 +881,7 @@ msgid "Transactions sent" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:60 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:61 msgid "Transfers" msgstr "" @@ -943,7 +943,7 @@ msgid "Verify & publish" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:54 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:55 msgid "View Contract" msgstr "" @@ -1048,7 +1048,7 @@ msgid "Self-Destruct" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:62 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:63 msgid "Decimals" msgstr "" @@ -1210,7 +1210,7 @@ msgstr "" #: lib/block_scout_web/templates/address_logs/index.html.eex:21 #: lib/block_scout_web/templates/address_token/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:58 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:59 #: lib/block_scout_web/templates/address_validation/index.html.eex:22 #: lib/block_scout_web/templates/block_transaction/index.html.eex:23 #: lib/block_scout_web/templates/chain/show.html.eex:91 @@ -1692,4 +1692,64 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:3 msgid "New Smart Contract Verification" -msgstr "" \ No newline at end of file +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/transaction/overview.html.eex:178 +msgid " Token Transfer" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 +msgid " is recommended." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 +msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_transaction/index.html.eex:74 +msgid "CSV" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 +msgid "ETH RPC API Documentation" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +msgid "Eth RPC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 +msgid "However, in general, the" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27 +msgid "There is no decompilded contracts for this address." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 +msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 +msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 +msgid "custom RPC" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 +msgid "here." +msgstr "" From c9dca5a319c97e2e9961867e89a1d1e1c01451b4 Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Tue, 25 Jun 2019 17:18:25 +0300 Subject: [PATCH 69/90] added styles for 'download csv' button --- .../assets/css/components/_transaction.scss | 36 +++++++++++++++++++ .../templates/address_token/index.html.eex | 15 ++++++-- apps/block_scout_web/priv/gettext/default.pot | 10 +++--- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/css/components/_transaction.scss b/apps/block_scout_web/assets/css/components/_transaction.scss index 5214b0b301..81bf95b9ee 100644 --- a/apps/block_scout_web/assets/css/components/_transaction.scss +++ b/apps/block_scout_web/assets/css/components/_transaction.scss @@ -4,3 +4,39 @@ line-height: 1.2; margin: 0 0 12px; } + +.transaction-bottom-panel { + display: flex; + flex-direction: column; + @media (min-width: 768px) { + flex-direction: row; + justify-content: space-between; + align-items: flex-end; + } +} + +.download-all-transactions { + text-align: center; + color: #a3a9b5; + font-size: 13px; + margin-top: 10px; + @media (min-width: 768px) { + margin-top: 30px; + } + .download-all-transactions-link { + text-decoration: none; + svg { + position: relative; + margin-left: 2px; + top: -3px; + path { + fill: $primary; + } + } + &:hover { + span { + text-decoration: underline; + } + } + } +} \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex index fa9dc12bd7..ed5ffe2c0d 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex @@ -5,7 +5,6 @@
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
- to_string(@address.hash)}) %>><%= gettext("Download all token transfers as csv") %>

<%= gettext "Tokens" %>

<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> @@ -21,8 +20,18 @@
- - <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
+ + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index f71be7b3dd..77f10d1581 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -732,7 +732,7 @@ msgid "There are no token transfers for this address." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_token/index.html.eex:19 +#: lib/block_scout_web/templates/address_token/index.html.eex:18 msgid "There are no tokens for this address." msgstr "" @@ -810,7 +810,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:8 -#: lib/block_scout_web/templates/address_token/index.html.eex:9 +#: lib/block_scout_web/templates/address_token/index.html.eex:8 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:9 #: lib/block_scout_web/views/address_view.ex:304 msgid "Tokens" @@ -1207,7 +1207,7 @@ msgstr "" #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:61 #: lib/block_scout_web/templates/address_logs/index.html.eex:21 -#: lib/block_scout_web/templates/address_token/index.html.eex:14 +#: lib/block_scout_web/templates/address_token/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 #: lib/block_scout_web/templates/address_transaction/index.html.eex:57 #: lib/block_scout_web/templates/address_validation/index.html.eex:22 @@ -1699,6 +1699,6 @@ msgid " Token Transfer" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_token/index.html.eex:8 -msgid "Download all token transfers as csv" +#: lib/block_scout_web/templates/address_token/index.html.eex:27 +msgid "CSV" msgstr "" From 89260ab9f3c4ce178df2631640c6fa0cc6ceb618 Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Tue, 25 Jun 2019 17:20:28 +0300 Subject: [PATCH 70/90] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf08d79d76..7f6a2ebb65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache ### Fixes +- [#2242](https://github.com/poanetwork/blockscout/pull/2242) - added styles for 'download csv' button - [#2142](https://github.com/poanetwork/blockscout/pull/2142) - Removed posdao theme and logo, added 'page not found' image for goerli - [#2138](https://github.com/poanetwork/blockscout/pull/2138) - badge colors issue, api titles issue - [#2129](https://github.com/poanetwork/blockscout/pull/2129) - Fix for width of explorer elements From 073c52f8295ba4b660a6e49e56028358e019f54c Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Wed, 26 Jun 2019 10:32:48 +0300 Subject: [PATCH 71/90] gettext fix --- .../lib/block_scout_web/templates/address_token/index.html.eex | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex index ed5ffe2c0d..4e78daee2f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex @@ -32,6 +32,7 @@
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
+
From 3b06941b60396225783089764ff7f9bc3ee06e48 Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Wed, 26 Jun 2019 10:46:10 +0300 Subject: [PATCH 72/90] new pot generated --- .../templates/address_token/index.html.eex | 5 ++--- apps/block_scout_web/priv/gettext/default.pot | 12 +++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex index 4e78daee2f..4b180fa761 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex @@ -23,8 +23,7 @@ - +
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 2821aa97b4..a0c6482690 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1700,8 +1700,6 @@ msgid " Token Transfer" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_token/index.html.eex:27 -msgid "CSV" #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 msgid " is recommended." msgstr "" @@ -1711,11 +1709,6 @@ msgstr "" msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_token/index.html.eex:8 -msgid "Download all token transfers as csv" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 msgid "ETH RPC API Documentation" @@ -1755,3 +1748,8 @@ msgstr "" #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 msgid "here." msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_token/index.html.eex:26 +msgid "CSV" +msgstr "" From 623be25cc208cd88924e6ca2c16e95877f29d989 Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Wed, 26 Jun 2019 18:27:50 +0300 Subject: [PATCH 73/90] search length issue, tile link wrapping issue --- .../assets/css/components/_navbar.scss | 12 +++--------- .../templates/address/_responsive_hash.html.eex | 4 ++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/block_scout_web/assets/css/components/_navbar.scss b/apps/block_scout_web/assets/css/components/_navbar.scss index 1058c529c6..b8855a63ae 100644 --- a/apps/block_scout_web/assets/css/components/_navbar.scss +++ b/apps/block_scout_web/assets/css/components/_navbar.scss @@ -151,19 +151,13 @@ $navbar-logo-width: auto !default; } @include media-breakpoint-up(xl) { - width: 280px; + width: 340px; } @media (min-width: 1366px) { - width: 330px; + width: 500px; } @media (min-width: 1440px) { - width: 380px; - } - @media (min-width: 1580px) { - width: 430px; - } - @media (min-width: 1800px) { - width: 520px; + width: 580px; } } .input-group-append { diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex index 22e0f76870..cad11f8ee6 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex @@ -5,8 +5,8 @@ <%= if assigns[:truncate] do %> <%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %> <% else %> - <%= @address %> - <%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %> + <%= @address %> + <%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %> <% end %> <% end %> From baae13933d7913ab310b462bce7eb6f944042376 Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Wed, 26 Jun 2019 18:31:01 +0300 Subject: [PATCH 74/90] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bba5170e0b..a578eab389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [#2193](https://github.com/poanetwork/blockscout/pull/2193) - feat: add BLOCKSCOUT_HOST, and use it in API docs ### Fixes +- [#2254](https://github.com/poanetwork/blockscout/pull/2254) - search length issue, tile link wrapping issue - [#2238](https://github.com/poanetwork/blockscout/pull/2238) - header content alignment issue, hide navbar on outside click - [#2229](https://github.com/poanetwork/blockscout/pull/2229) - gap issue between qr and copy button in token transfers, top cards width and height issue - [#2201](https://github.com/poanetwork/blockscout/pull/2201) - footer columns fix From 5edc046dfccda270351806bc7cc4430480d4f14d Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 27 Jun 2019 10:10:12 +0300 Subject: [PATCH 75/90] use the latest version of chromedriver --- bin/install_chrome_headless.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/install_chrome_headless.sh b/bin/install_chrome_headless.sh index d27bb6f9d8..9721e84ea3 100755 --- a/bin/install_chrome_headless.sh +++ b/bin/install_chrome_headless.sh @@ -1,6 +1,6 @@ export DISPLAY=:99.0 sh -e /etc/init.d/xvfb start -export CHROMEDRIVER_VERSION=74.0.3729.6 +export CHROMEDRIVER_VERSION=`curl -s http://chromedriver.storage.googleapis.com/LATEST_RELEASE` curl -L -O "http://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" unzip chromedriver_linux64.zip sudo chmod +x chromedriver From b9c10aaa7aae493bd52bd8fe30b30706cbb10808 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 27 Jun 2019 10:21:43 +0300 Subject: [PATCH 76/90] use the latest version of chromedriver --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a578eab389..20fe7af997 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ ### Chore - [#2127](https://github.com/poanetwork/blockscout/pull/2127) - use previouse chromedriver version - [#2118](https://github.com/poanetwork/blockscout/pull/2118) - show only the last decompiled contract +- [#2256](https://github.com/poanetwork/blockscout/pull/2256) - use the latest version of chromedriver ### Chore From 9e1b6b3752ff0bcaf6cf86be6356caba8ead2356 Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Thu, 27 Jun 2019 10:46:22 +0300 Subject: [PATCH 77/90] 'download csv' button added to different tabs --- .../address_internal_transaction/index.html.eex | 13 +++++++++++-- .../templates/address_transaction/index.html.eex | 13 +++++++++++-- .../templates/tokens/transfer/index.html.eex | 13 +++++++++++-- apps/block_scout_web/priv/gettext/default.pot | 3 +++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex index 3bc2e7fe9b..b3d92333a7 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex @@ -67,8 +67,17 @@
- - <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
+ + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex index 11b4de110c..0dff8dbb20 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex @@ -64,8 +64,17 @@
- - <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
+ + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex index 980cc4b05a..8e12926fb9 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex @@ -27,8 +27,17 @@
- - <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + +
+ + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> +
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index a0c6482690..1ce79f9565 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1750,6 +1750,9 @@ msgid "here." msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:73 #: lib/block_scout_web/templates/address_token/index.html.eex:26 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:70 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:33 msgid "CSV" msgstr "" From 9f82ea43f53a7f1b8ffc79149a2446667ea64811 Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Thu, 27 Jun 2019 10:49:33 +0300 Subject: [PATCH 78/90] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c65d4c942..fdb24d1081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#2146](https://github.com/poanetwork/blockscout/pull/2146) - feat: add eth_getLogs rpc endpoint ### Fixes +- [#2257](https://github.com/poanetwork/blockscout/pull/2257) - 'download csv' button added to different tabs - [#2242](https://github.com/poanetwork/blockscout/pull/2242) - added styles for 'download csv' button - [#2238](https://github.com/poanetwork/blockscout/pull/2238) - header content alignment issue, hide navbar on outside click - [#2229](https://github.com/poanetwork/blockscout/pull/2229) - gap issue between qr and copy button in token transfers, top cards width and height issue From 830c30ae6afb20f3f7ba4225cb9a2afa152833a0 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 27 Jun 2019 14:45:24 +0300 Subject: [PATCH 79/90] hide csv button if there are no transactions --- apps/block_scout_web/assets/js/lib/async_listing_load.js | 8 ++++++++ .../templates/address_transaction/index.html.eex | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/block_scout_web/assets/js/lib/async_listing_load.js b/apps/block_scout_web/assets/js/lib/async_listing_load.js index 3a3eb2fd6c..8610d901f6 100644 --- a/apps/block_scout_web/assets/js/lib/async_listing_load.js +++ b/apps/block_scout_web/assets/js/lib/async_listing_load.js @@ -219,6 +219,14 @@ export const elements = { $el.hide() } + }, + '[csv-download]': { + render ($el, state) { + if (state.emptyResponse) { + return $el.hide() + } + return $el.show() + } } } diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex index a24df921c6..3750c772cd 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex @@ -50,8 +50,8 @@ - - + + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> @@ -66,9 +66,9 @@
- +
-
+
Download  to_string(@address.hash)}) %>> <%= gettext("CSV") %> From 3a7f4974d503dcfc23b786ef1fda501f933e0361 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 27 Jun 2019 14:53:42 +0300 Subject: [PATCH 80/90] fix gettext --- apps/block_scout_web/priv/gettext/default.pot | 16 ++++++++++++++- .../priv/gettext/en/LC_MESSAGES/default.po | 20 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index bf9db8c12b..6aec2d5849 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1699,7 +1699,6 @@ msgstr "" msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." msgstr "" - #, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 msgid "ETH RPC API Documentation" @@ -1739,3 +1738,18 @@ msgstr "" #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 msgid "here." msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_transaction/index.html.eex:74 +msgid "CSV" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:44 +msgid "ERC-20 " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:45 +msgid "ERC-721 " +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index d3d4e833d6..27af6d233c 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -1684,7 +1684,7 @@ msgstr "" msgid "New Smart Contract Verification" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:178 msgid " Token Transfer" msgstr "" @@ -1699,6 +1699,7 @@ msgstr "" msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 msgid "ETH RPC API Documentation" msgstr "" @@ -1713,7 +1714,7 @@ msgstr "" msgid "However, in general, the" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27 msgid "There is no decompilded contracts for this address." msgstr "" @@ -1737,3 +1738,18 @@ msgstr "" #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 msgid "here." msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_transaction/index.html.eex:74 +msgid "CSV" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:44 +msgid "ERC-20 " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:45 +msgid "ERC-721 " +msgstr "" From eb544a3c0a80f000696aa8be7e2be486418269f7 Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Thu, 27 Jun 2019 14:54:07 +0300 Subject: [PATCH 81/90] header logo aligned to the center properly --- apps/block_scout_web/assets/css/components/_navbar.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/block_scout_web/assets/css/components/_navbar.scss b/apps/block_scout_web/assets/css/components/_navbar.scss index b8855a63ae..48c6c50f02 100644 --- a/apps/block_scout_web/assets/css/components/_navbar.scss +++ b/apps/block_scout_web/assets/css/components/_navbar.scss @@ -217,6 +217,7 @@ $navbar-logo-width: auto !default; .navbar-brand { margin-left: 0; flex-shrink: 1; + display: inline-flex; .navbar-logo { max-width: 100%; } From 29471090d240ab1902acf61d18177c8ee2af80aa Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Thu, 27 Jun 2019 14:56:09 +0300 Subject: [PATCH 82/90] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db258a24ee..1e131d5af3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#2193](https://github.com/poanetwork/blockscout/pull/2193) - feat: add BLOCKSCOUT_HOST, and use it in API docs ### Fixes +- [#2261](https://github.com/poanetwork/blockscout/pull/2261) - header logo aligned to the center properly - [#2254](https://github.com/poanetwork/blockscout/pull/2254) - search length issue, tile link wrapping issue - [#2238](https://github.com/poanetwork/blockscout/pull/2238) - header content alignment issue, hide navbar on outside click - [#2229](https://github.com/poanetwork/blockscout/pull/2229) - gap issue between qr and copy button in token transfers, top cards width and height issue From ffd966dd76219e239e978ba1d8fae1170daec1fa Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 27 Jun 2019 14:57:02 +0300 Subject: [PATCH 83/90] hide csv button --- apps/block_scout_web/assets/js/lib/async_listing_load.js | 8 ++++++++ .../templates/address_token/index.html.eex | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/assets/js/lib/async_listing_load.js b/apps/block_scout_web/assets/js/lib/async_listing_load.js index 3a3eb2fd6c..8610d901f6 100644 --- a/apps/block_scout_web/assets/js/lib/async_listing_load.js +++ b/apps/block_scout_web/assets/js/lib/async_listing_load.js @@ -219,6 +219,14 @@ export const elements = { $el.hide() } + }, + '[csv-download]': { + render ($el, state) { + if (state.emptyResponse) { + return $el.hide() + } + return $el.show() + } } } diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex index 4b180fa761..840d9cc1da 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex @@ -20,9 +20,9 @@
- +
-
+
Download to_string(@address.hash)}) %>><%= gettext("CSV") %> From 76657e8dab35357e5fe8ee671126f6615219c9b5 Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Thu, 27 Jun 2019 15:23:49 +0300 Subject: [PATCH 84/90] added an ability to close network selector on outside click --- .../assets/css/components/_network-selector.scss | 9 +++++++++ .../assets/js/lib/network_selector.js | 6 ++++++ .../templates/layout/_network_selector.html.eex | 1 + apps/block_scout_web/priv/gettext/default.pot | 14 +++++++------- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/apps/block_scout_web/assets/css/components/_network-selector.scss b/apps/block_scout_web/assets/css/components/_network-selector.scss index 3086639050..ad0864bf61 100644 --- a/apps/block_scout_web/assets/css/components/_network-selector.scss +++ b/apps/block_scout_web/assets/css/components/_network-selector.scss @@ -26,6 +26,14 @@ $network-selector-item-icon-dimensions: 30px !default; z-index: 123; } +.network-selector-overlay-close { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + .network-selector-wrapper { display: flex; height: 100%; @@ -44,6 +52,7 @@ $network-selector-item-icon-dimensions: 30px !default; padding-top: 28px; position: relative; transition: right 0.25s ease-out; + z-index: 2; } .network-selector-close { diff --git a/apps/block_scout_web/assets/js/lib/network_selector.js b/apps/block_scout_web/assets/js/lib/network_selector.js index f7765329a0..20c8e3d206 100644 --- a/apps/block_scout_web/assets/js/lib/network_selector.js +++ b/apps/block_scout_web/assets/js/lib/network_selector.js @@ -4,6 +4,7 @@ $(function () { const mainBody = $('body') const showNetworkSelector = $('.js-show-network-selector') const hideNetworkSelector = $('.js-network-selector-close') + const hideNetworkSelectorOverlay = $('.js-network-selector-overlay-close') const networkSelector = $('.js-network-selector') const networkSelectorOverlay = $('.js-network-selector-overlay') const networkSelectorTab = $('.js-network-selector-tab') @@ -21,6 +22,11 @@ $(function () { closeNetworkSelector() }) + hideNetworkSelectorOverlay.on('click', (e) => { + e.preventDefault() + closeNetworkSelector() + }) + networkSelectorTab.on('click', function (e) { e.preventDefault() setNetworkTab($(this)) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex index 41f990b589..4744d7d371 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex @@ -1,4 +1,5 @@
+
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 1cf4275fcf..21dc7d4afc 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -112,7 +112,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -1751,31 +1751,31 @@ msgid "here." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 msgid "Change Network" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:24 msgid "Favorites" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:12 msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 msgid "Mainnet" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:18 msgid "Search network" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 msgid "Testnet" msgstr "" From 5c085b27e53757c120c29e09da93ab24d9897cea Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Thu, 27 Jun 2019 15:25:27 +0300 Subject: [PATCH 85/90] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7444ad265a..ac5dd3a500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#2146](https://github.com/poanetwork/blockscout/pull/2146) - feat: add eth_getLogs rpc endpoint ### Fixes +- [#2263](https://github.com/poanetwork/blockscout/pull/2263) - added an ability to close network selector on outside click - [#2201](https://github.com/poanetwork/blockscout/pull/2201) - footer columns fix - [#2179](https://github.com/poanetwork/blockscout/pull/2179) - fix docker build error - [#2165](https://github.com/poanetwork/blockscout/pull/2165) - sort blocks by timestamp when calculating average block time From da348dfc73ced9869b50f84143a292ace3ee2c69 Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Thu, 27 Jun 2019 15:30:27 +0300 Subject: [PATCH 86/90] commented 'download csv' button where is no logic --- .../templates/address_internal_transaction/index.html.eex | 6 +++--- .../templates/tokens/transfer/index.html.eex | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex index b3d92333a7..2b4fd18674 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex @@ -68,16 +68,16 @@
-
+ <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> -
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex index 8e12926fb9..66038da607 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex @@ -28,16 +28,16 @@
-
+ <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> -
+
From 8df146b4e621d999b308f0206287b0eb807d7d82 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 27 Jun 2019 17:34:50 +0300 Subject: [PATCH 87/90] Remove duplicate record from internalization files --- apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 0cc1ee30e2..ee5b704cba 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -1719,11 +1719,6 @@ msgstr "" msgid "There is no decompilded contracts for this address." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27 -msgid "There is no decompilded contracts for this address." -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " From de58a6f7e70a8f36b0c7cc7ab1281945d3d1a197 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 27 Jun 2019 17:45:20 +0300 Subject: [PATCH 88/90] Fix gettext and format tests --- apps/block_scout_web/priv/gettext/default.pot | 7 +------ .../controllers/address_transaction_controller_test.exs | 4 ++++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 700328ec74..660be028e5 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1742,16 +1742,11 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:73 #: lib/block_scout_web/templates/address_token/index.html.eex:26 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:70 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:72 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:33 msgid "CSV" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_transaction/index.html.eex:74 -msgid "CSV" -msgstr "" - #, elixir-format #: lib/block_scout_web/views/transaction_view.ex:44 msgid "ERC-20 " diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs index 5469ab6cc7..a29d6b9be6 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs @@ -147,6 +147,10 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do conn = get(conn, "/token_transfers_csv", %{"address_id" => to_string(address.hash)}) + assert conn.resp_body |> String.split("\n") |> Enum.count() == 4 + end + end + describe "GET transactions_csv/2" do test "download csv file with transactions", %{conn: conn} do address = insert(:address) From 9a301038cb848d2b890e93027106b9efb140d754 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 27 Jun 2019 17:51:33 +0300 Subject: [PATCH 89/90] credo test fix --- .../controllers/address_transaction_controller.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index 8d144199c6..51503fd7c1 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -10,8 +10,7 @@ defmodule BlockScoutWeb.AddressTransactionController do alias BlockScoutWeb.TransactionView alias Explorer.{Chain, Market} - alias Explorer.Chain.AddressTokenTransferCsvExporter - alias Explorer.Chain.AddressTransactionCsvExporter + alias Explorer.Chain.{AddressTokenTransferCsvExporter, AddressTransactionCsvExporter} alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View From 0f1d8e1c51d0dbda2503bc61ac4c979423f518d3 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 27 Jun 2019 18:48:31 +0300 Subject: [PATCH 90/90] dropdown_main_nets and dropdown_test_nets to show only chains, that are not marked to hide in dropdown --- .../templates/layout/_network_selector.html.eex | 4 ++-- .../lib/block_scout_web/views/layout_view.ex | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex index 4744d7d371..1e31d205e1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex @@ -24,8 +24,8 @@
<%= gettext("Favorites") %>
- <% main_nets = main_nets(other_networks()) %> - <% test_nets = test_nets(other_networks()) %> + <% main_nets = dropdown_main_nets() %> + <% test_nets = dropdown_test_nets() %>
<%= for %{url: url, title: title} <- main_nets do %> <%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Mainnet" %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex index 12df6d3f0a..b33b1dd4c2 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex @@ -198,6 +198,16 @@ defmodule BlockScoutWeb.LayoutView do |> Enum.reject(&Map.get(&1, :hide_in_dropdown?)) end + def dropdown_main_nets do + dropdown_nets() + |> main_nets() + end + + def dropdown_test_nets do + dropdown_nets() + |> test_nets() + end + def dropdown_head_main_nets do dropdown_nets() |> main_nets()