parent
b40758e36f
commit
51e7fd0571
@ -0,0 +1,541 @@ |
|||||||
|
/* |
||||||
|
* The MIT License |
||||||
|
* Copyright (c) 2012 Matias Meno <m@tias.me> |
||||||
|
*/ |
||||||
|
|
||||||
|
@-webkit-keyframes passing-through { |
||||||
|
0% { |
||||||
|
opacity: 0; |
||||||
|
-webkit-transform: translateY(40px); |
||||||
|
-moz-transform: translateY(40px); |
||||||
|
-ms-transform: translateY(40px); |
||||||
|
-o-transform: translateY(40px); |
||||||
|
transform: translateY(40px); |
||||||
|
} |
||||||
|
30%, |
||||||
|
70% { |
||||||
|
opacity: 1; |
||||||
|
-webkit-transform: translateY(0px); |
||||||
|
-moz-transform: translateY(0px); |
||||||
|
-ms-transform: translateY(0px); |
||||||
|
-o-transform: translateY(0px); |
||||||
|
transform: translateY(0px); |
||||||
|
} |
||||||
|
100% { |
||||||
|
opacity: 0; |
||||||
|
-webkit-transform: translateY(-40px); |
||||||
|
-moz-transform: translateY(-40px); |
||||||
|
-ms-transform: translateY(-40px); |
||||||
|
-o-transform: translateY(-40px); |
||||||
|
transform: translateY(-40px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@-moz-keyframes passing-through { |
||||||
|
0% { |
||||||
|
opacity: 0; |
||||||
|
-webkit-transform: translateY(40px); |
||||||
|
-moz-transform: translateY(40px); |
||||||
|
-ms-transform: translateY(40px); |
||||||
|
-o-transform: translateY(40px); |
||||||
|
transform: translateY(40px); |
||||||
|
} |
||||||
|
30%, |
||||||
|
70% { |
||||||
|
opacity: 1; |
||||||
|
-webkit-transform: translateY(0px); |
||||||
|
-moz-transform: translateY(0px); |
||||||
|
-ms-transform: translateY(0px); |
||||||
|
-o-transform: translateY(0px); |
||||||
|
transform: translateY(0px); |
||||||
|
} |
||||||
|
100% { |
||||||
|
opacity: 0; |
||||||
|
-webkit-transform: translateY(-40px); |
||||||
|
-moz-transform: translateY(-40px); |
||||||
|
-ms-transform: translateY(-40px); |
||||||
|
-o-transform: translateY(-40px); |
||||||
|
transform: translateY(-40px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes passing-through { |
||||||
|
0% { |
||||||
|
opacity: 0; |
||||||
|
-webkit-transform: translateY(40px); |
||||||
|
-moz-transform: translateY(40px); |
||||||
|
-ms-transform: translateY(40px); |
||||||
|
-o-transform: translateY(40px); |
||||||
|
transform: translateY(40px); |
||||||
|
} |
||||||
|
30%, |
||||||
|
70% { |
||||||
|
opacity: 1; |
||||||
|
-webkit-transform: translateY(0px); |
||||||
|
-moz-transform: translateY(0px); |
||||||
|
-ms-transform: translateY(0px); |
||||||
|
-o-transform: translateY(0px); |
||||||
|
transform: translateY(0px); |
||||||
|
} |
||||||
|
100% { |
||||||
|
opacity: 0; |
||||||
|
-webkit-transform: translateY(-40px); |
||||||
|
-moz-transform: translateY(-40px); |
||||||
|
-ms-transform: translateY(-40px); |
||||||
|
-o-transform: translateY(-40px); |
||||||
|
transform: translateY(-40px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@-webkit-keyframes slide-in { |
||||||
|
0% { |
||||||
|
opacity: 0; |
||||||
|
-webkit-transform: translateY(40px); |
||||||
|
-moz-transform: translateY(40px); |
||||||
|
-ms-transform: translateY(40px); |
||||||
|
-o-transform: translateY(40px); |
||||||
|
transform: translateY(40px); |
||||||
|
} |
||||||
|
30% { |
||||||
|
opacity: 1; |
||||||
|
-webkit-transform: translateY(0px); |
||||||
|
-moz-transform: translateY(0px); |
||||||
|
-ms-transform: translateY(0px); |
||||||
|
-o-transform: translateY(0px); |
||||||
|
transform: translateY(0px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@-moz-keyframes slide-in { |
||||||
|
0% { |
||||||
|
opacity: 0; |
||||||
|
-webkit-transform: translateY(40px); |
||||||
|
-moz-transform: translateY(40px); |
||||||
|
-ms-transform: translateY(40px); |
||||||
|
-o-transform: translateY(40px); |
||||||
|
transform: translateY(40px); |
||||||
|
} |
||||||
|
30% { |
||||||
|
opacity: 1; |
||||||
|
-webkit-transform: translateY(0px); |
||||||
|
-moz-transform: translateY(0px); |
||||||
|
-ms-transform: translateY(0px); |
||||||
|
-o-transform: translateY(0px); |
||||||
|
transform: translateY(0px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes slide-in { |
||||||
|
0% { |
||||||
|
opacity: 0; |
||||||
|
-webkit-transform: translateY(40px); |
||||||
|
-moz-transform: translateY(40px); |
||||||
|
-ms-transform: translateY(40px); |
||||||
|
-o-transform: translateY(40px); |
||||||
|
transform: translateY(40px); |
||||||
|
} |
||||||
|
30% { |
||||||
|
opacity: 1; |
||||||
|
-webkit-transform: translateY(0px); |
||||||
|
-moz-transform: translateY(0px); |
||||||
|
-ms-transform: translateY(0px); |
||||||
|
-o-transform: translateY(0px); |
||||||
|
transform: translateY(0px); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@-webkit-keyframes pulse { |
||||||
|
0% { |
||||||
|
-webkit-transform: scale(1); |
||||||
|
-moz-transform: scale(1); |
||||||
|
-ms-transform: scale(1); |
||||||
|
-o-transform: scale(1); |
||||||
|
transform: scale(1); |
||||||
|
} |
||||||
|
10% { |
||||||
|
-webkit-transform: scale(1.1); |
||||||
|
-moz-transform: scale(1.1); |
||||||
|
-ms-transform: scale(1.1); |
||||||
|
-o-transform: scale(1.1); |
||||||
|
transform: scale(1.1); |
||||||
|
} |
||||||
|
20% { |
||||||
|
-webkit-transform: scale(1); |
||||||
|
-moz-transform: scale(1); |
||||||
|
-ms-transform: scale(1); |
||||||
|
-o-transform: scale(1); |
||||||
|
transform: scale(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@-moz-keyframes pulse { |
||||||
|
0% { |
||||||
|
-webkit-transform: scale(1); |
||||||
|
-moz-transform: scale(1); |
||||||
|
-ms-transform: scale(1); |
||||||
|
-o-transform: scale(1); |
||||||
|
transform: scale(1); |
||||||
|
} |
||||||
|
10% { |
||||||
|
-webkit-transform: scale(1.1); |
||||||
|
-moz-transform: scale(1.1); |
||||||
|
-ms-transform: scale(1.1); |
||||||
|
-o-transform: scale(1.1); |
||||||
|
transform: scale(1.1); |
||||||
|
} |
||||||
|
20% { |
||||||
|
-webkit-transform: scale(1); |
||||||
|
-moz-transform: scale(1); |
||||||
|
-ms-transform: scale(1); |
||||||
|
-o-transform: scale(1); |
||||||
|
transform: scale(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes pulse { |
||||||
|
0% { |
||||||
|
-webkit-transform: scale(1); |
||||||
|
-moz-transform: scale(1); |
||||||
|
-ms-transform: scale(1); |
||||||
|
-o-transform: scale(1); |
||||||
|
transform: scale(1); |
||||||
|
} |
||||||
|
10% { |
||||||
|
-webkit-transform: scale(1.1); |
||||||
|
-moz-transform: scale(1.1); |
||||||
|
-ms-transform: scale(1.1); |
||||||
|
-o-transform: scale(1.1); |
||||||
|
transform: scale(1.1); |
||||||
|
} |
||||||
|
20% { |
||||||
|
-webkit-transform: scale(1); |
||||||
|
-moz-transform: scale(1); |
||||||
|
-ms-transform: scale(1); |
||||||
|
-o-transform: scale(1); |
||||||
|
transform: scale(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1, |
||||||
|
.dropzone-1 * { |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 { |
||||||
|
min-height: 150px; |
||||||
|
border: 2px solid rgba(0, 0, 0, 0.3); |
||||||
|
background: white; |
||||||
|
padding: 20px 20px; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1.dz-clickable { |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1.dz-clickable * { |
||||||
|
cursor: default; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1.dz-clickable .dz-message, |
||||||
|
.dropzone-1.dz-clickable .dz-message * { |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1.dz-started .dz-message { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1.dz-drag-hover { |
||||||
|
border-style: solid; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1.dz-drag-hover .dz-message { |
||||||
|
opacity: 0.5; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-message { |
||||||
|
text-align: center; |
||||||
|
margin: 2em 0; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-message .dz-button { |
||||||
|
background: none; |
||||||
|
color: inherit; |
||||||
|
border: none; |
||||||
|
padding: 0; |
||||||
|
font: inherit; |
||||||
|
cursor: pointer; |
||||||
|
outline: inherit; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview { |
||||||
|
position: relative; |
||||||
|
display: inline-block; |
||||||
|
vertical-align: top; |
||||||
|
margin: 16px; |
||||||
|
min-height: 100px; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview:hover { |
||||||
|
z-index: 1000; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview:hover .dz-details { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview.dz-file-preview .dz-image { |
||||||
|
border-radius: 20px; |
||||||
|
background: #999; |
||||||
|
background: linear-gradient(to bottom, #eee, #ddd); |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview.dz-file-preview .dz-details { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview.dz-image-preview { |
||||||
|
background: white; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview.dz-image-preview .dz-details { |
||||||
|
-webkit-transition: opacity 0.2s linear; |
||||||
|
-moz-transition: opacity 0.2s linear; |
||||||
|
-ms-transition: opacity 0.2s linear; |
||||||
|
-o-transition: opacity 0.2s linear; |
||||||
|
transition: opacity 0.2s linear; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-remove { |
||||||
|
font-size: 14px; |
||||||
|
text-align: center; |
||||||
|
display: block; |
||||||
|
cursor: pointer; |
||||||
|
border: none; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-remove:hover { |
||||||
|
text-decoration: underline; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview:hover .dz-details { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-details { |
||||||
|
z-index: 20; |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
opacity: 0; |
||||||
|
font-size: 13px; |
||||||
|
min-width: 100%; |
||||||
|
max-width: 100%; |
||||||
|
padding: 2em 1em; |
||||||
|
text-align: center; |
||||||
|
color: rgba(0, 0, 0, 0.9); |
||||||
|
line-height: 150%; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-details .dz-size { |
||||||
|
margin-bottom: 1em; |
||||||
|
font-size: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-details .dz-filename { |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-details .dz-filename:hover span { |
||||||
|
border: 1px solid rgba(200, 200, 200, 0.8); |
||||||
|
background-color: rgba(255, 255, 255, 0.8); |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-details .dz-filename:not(:hover) { |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-details .dz-filename:not(:hover) span { |
||||||
|
border: 1px solid transparent; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-details .dz-filename span, |
||||||
|
.dropzone-1 .dz-preview .dz-details .dz-size span { |
||||||
|
background-color: rgba(255, 255, 255, 0.4); |
||||||
|
padding: 0 0.4em; |
||||||
|
border-radius: 3px; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview:hover .dz-image img { |
||||||
|
-webkit-transform: scale(1.05, 1.05); |
||||||
|
-moz-transform: scale(1.05, 1.05); |
||||||
|
-ms-transform: scale(1.05, 1.05); |
||||||
|
-o-transform: scale(1.05, 1.05); |
||||||
|
transform: scale(1.05, 1.05); |
||||||
|
-webkit-filter: blur(8px); |
||||||
|
filter: blur(8px); |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-image { |
||||||
|
border-radius: 20px; |
||||||
|
overflow: hidden; |
||||||
|
width: 120px; |
||||||
|
height: 120px; |
||||||
|
position: relative; |
||||||
|
display: block; |
||||||
|
z-index: 10; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-image img { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview.dz-success .dz-success-mark { |
||||||
|
-webkit-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); |
||||||
|
-moz-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); |
||||||
|
-ms-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); |
||||||
|
-o-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); |
||||||
|
animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview.dz-error .dz-error-mark { |
||||||
|
opacity: 1; |
||||||
|
-webkit-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); |
||||||
|
-moz-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); |
||||||
|
-ms-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); |
||||||
|
-o-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); |
||||||
|
animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-success-mark, |
||||||
|
.dropzone-1 .dz-preview .dz-error-mark { |
||||||
|
pointer-events: none; |
||||||
|
opacity: 0; |
||||||
|
z-index: 500; |
||||||
|
position: absolute; |
||||||
|
display: block; |
||||||
|
top: 50%; |
||||||
|
left: 50%; |
||||||
|
margin-left: -27px; |
||||||
|
margin-top: -27px; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-success-mark svg, |
||||||
|
.dropzone-1 .dz-preview .dz-error-mark svg { |
||||||
|
display: block; |
||||||
|
width: 54px; |
||||||
|
height: 54px; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview.dz-processing .dz-progress { |
||||||
|
opacity: 1; |
||||||
|
-webkit-transition: all 0.2s linear; |
||||||
|
-moz-transition: all 0.2s linear; |
||||||
|
-ms-transition: all 0.2s linear; |
||||||
|
-o-transition: all 0.2s linear; |
||||||
|
transition: all 0.2s linear; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview.dz-complete .dz-progress { |
||||||
|
opacity: 0; |
||||||
|
-webkit-transition: opacity 0.4s ease-in; |
||||||
|
-moz-transition: opacity 0.4s ease-in; |
||||||
|
-ms-transition: opacity 0.4s ease-in; |
||||||
|
-o-transition: opacity 0.4s ease-in; |
||||||
|
transition: opacity 0.4s ease-in; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview:not(.dz-processing) .dz-progress { |
||||||
|
-webkit-animation: pulse 6s ease infinite; |
||||||
|
-moz-animation: pulse 6s ease infinite; |
||||||
|
-ms-animation: pulse 6s ease infinite; |
||||||
|
-o-animation: pulse 6s ease infinite; |
||||||
|
animation: pulse 6s ease infinite; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-progress { |
||||||
|
opacity: 1; |
||||||
|
z-index: 1000; |
||||||
|
pointer-events: none; |
||||||
|
position: absolute; |
||||||
|
height: 16px; |
||||||
|
left: 50%; |
||||||
|
top: 50%; |
||||||
|
margin-top: -8px; |
||||||
|
width: 80px; |
||||||
|
margin-left: -40px; |
||||||
|
background: rgba(255, 255, 255, 0.9); |
||||||
|
-webkit-transform: scale(1); |
||||||
|
border-radius: 8px; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-progress .dz-upload { |
||||||
|
background: #333; |
||||||
|
background: linear-gradient(to bottom, #666, #444); |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
bottom: 0; |
||||||
|
width: 0; |
||||||
|
-webkit-transition: width 300ms ease-in-out; |
||||||
|
-moz-transition: width 300ms ease-in-out; |
||||||
|
-ms-transition: width 300ms ease-in-out; |
||||||
|
-o-transition: width 300ms ease-in-out; |
||||||
|
transition: width 300ms ease-in-out; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview.dz-error .dz-error-message { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview.dz-error:hover .dz-error-message { |
||||||
|
opacity: 1; |
||||||
|
pointer-events: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-error-message { |
||||||
|
pointer-events: none; |
||||||
|
z-index: 1000; |
||||||
|
position: absolute; |
||||||
|
display: block; |
||||||
|
display: none; |
||||||
|
opacity: 0; |
||||||
|
-webkit-transition: opacity 0.3s ease; |
||||||
|
-moz-transition: opacity 0.3s ease; |
||||||
|
-ms-transition: opacity 0.3s ease; |
||||||
|
-o-transition: opacity 0.3s ease; |
||||||
|
transition: opacity 0.3s ease; |
||||||
|
border-radius: 8px; |
||||||
|
font-size: 13px; |
||||||
|
top: 130px; |
||||||
|
left: -10px; |
||||||
|
width: 140px; |
||||||
|
background: #be2626; |
||||||
|
background: linear-gradient(to bottom, #be2626, #a92222); |
||||||
|
padding: 0.5em 1.2em; |
||||||
|
color: white; |
||||||
|
} |
||||||
|
|
||||||
|
.dropzone-1 .dz-preview .dz-error-message:after { |
||||||
|
content: ''; |
||||||
|
position: absolute; |
||||||
|
top: -6px; |
||||||
|
left: 64px; |
||||||
|
width: 0; |
||||||
|
height: 0; |
||||||
|
border-left: 6px solid transparent; |
||||||
|
border-right: 6px solid transparent; |
||||||
|
border-bottom: 6px solid #be2626; |
||||||
|
} |
||||||
|
|
||||||
|
// custom styles |
||||||
|
.dropzone-1 { |
||||||
|
border-color: $primary !important; |
||||||
|
border-style: dashed !important; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
display: flex; |
||||||
|
background: rgba($primary, 0.1) !important; |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
import 'dropzone' |
@ -0,0 +1,49 @@ |
|||||||
|
defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do |
||||||
|
use BlockScoutWeb, :controller |
||||||
|
|
||||||
|
alias Explorer.Chain.SmartContract |
||||||
|
alias Explorer.SmartContract.{PublisherWorker, Solidity.CodeCompiler, Solidity.CompilerVersion} |
||||||
|
|
||||||
|
def new(conn, %{"address_id" => address_hash_string}) do |
||||||
|
changeset = |
||||||
|
SmartContract.changeset( |
||||||
|
%SmartContract{address_hash: address_hash_string}, |
||||||
|
%{} |
||||||
|
) |
||||||
|
|
||||||
|
compiler_versions = |
||||||
|
case CompilerVersion.fetch_versions() do |
||||||
|
{:ok, compiler_versions} -> |
||||||
|
compiler_versions |
||||||
|
|
||||||
|
{:error, _} -> |
||||||
|
[] |
||||||
|
end |
||||||
|
|
||||||
|
render(conn, "new.html", |
||||||
|
changeset: changeset, |
||||||
|
compiler_versions: compiler_versions, |
||||||
|
evm_versions: CodeCompiler.allowed_evm_versions(), |
||||||
|
address_hash: address_hash_string |
||||||
|
) |
||||||
|
end |
||||||
|
|
||||||
|
def create( |
||||||
|
conn, |
||||||
|
%{ |
||||||
|
"smart_contract" => smart_contract, |
||||||
|
"external_libraries" => external_libraries |
||||||
|
} |
||||||
|
) do |
||||||
|
Que.add(PublisherWorker, {smart_contract["address_hash"], smart_contract, external_libraries, conn}) |
||||||
|
|
||||||
|
send_resp(conn, 204, "") |
||||||
|
end |
||||||
|
|
||||||
|
def parse_optimization_runs(%{"runs" => runs}) do |
||||||
|
case Integer.parse(runs) do |
||||||
|
{integer, ""} -> integer |
||||||
|
_ -> 200 |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,7 @@ |
|||||||
|
defmodule BlockScoutWeb.AddressContractVerificationViaJsonController do |
||||||
|
use BlockScoutWeb, :controller |
||||||
|
|
||||||
|
def new(conn, %{"address_id" => address_hash_string}) do |
||||||
|
render(conn, "new.html", address_hash: address_hash_string) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,314 @@ |
|||||||
|
<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> |
||||||
|
<% contract_name_value = if metadata_for_verification, do: metadata_for_verification.name, else: "" %> |
||||||
|
<% optimization_runs_value = if metadata_for_verification, do: metadata_for_verification.optimization_runs, else: "200" %> |
||||||
|
<% optimization = if metadata_for_verification, do: metadata_for_verification.optimization, else: true %> |
||||||
|
<% evm_version = if metadata_for_verification, do: metadata_for_verification.evm_version, else: "default" %> |
||||||
|
<% compiler_version = if metadata_for_verification, do: metadata_for_verification.compiler_version, else: "latest" %> |
||||||
|
<% contract_source_code_value = if metadata_for_verification, do: metadata_for_verification.contract_source_code, else: "" %> |
||||||
|
<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: false %> |
||||||
|
<% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %> |
||||||
|
<section data-page="contract-verification" class="container new-smart-contract-container"> |
||||||
|
<div data-selector="channel-disconnected-message" style="display: none;"> |
||||||
|
<div data-selector="reload-button" class="alert alert-danger"> |
||||||
|
<a href="#" class="alert-link"><%= gettext "Connection Lost" %></a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="new-smart-contract-form"> |
||||||
|
<h1 class="smart-contract-title"><%= gettext "New Smart Contract Verification" %></h1> |
||||||
|
|
||||||
|
<%= form_for @changeset, |
||||||
|
address_contract_verification_path(@conn, :create), |
||||||
|
[], |
||||||
|
fn f -> %> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label f, :address_hash, gettext("Contract Address") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input f, :address_hash, class: "form-control border-rounded", "aria-describedby": "contract-address-help-block", readonly: true %> |
||||||
|
<%= error_tag f, :address_hash, id: "contract-address-help-block", class: "text-danger form-error" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">The 0x address supplied on contract creation.</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label f, :name, gettext("Contract Name") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input f, :name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block", "data-test": "contract_name", value: contract_name_value %> |
||||||
|
<%= error_tag f, :name, id: "contract-name-help-block", class: "text-danger form-error" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">Must match the name specified in the code. For example, in <span class="tooltip-quote">contract MyContract {..}</span> <strong>MyContract</strong> is the contract name.</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label f, "Include nightly builds" %> |
||||||
|
<div class="center-column"> |
||||||
|
<div class="form-radios-group"> |
||||||
|
<div class="radio-big"> |
||||||
|
<%= radio_button f, :nightly_builds, false, checked: true, class: "form-check-input nightly-builds-false" %> |
||||||
|
<div class="radio-icon"></div> |
||||||
|
<%= label :nightly_builds, :false, gettext("No"), class: "radio-text" %> |
||||||
|
</div> |
||||||
|
<div class="radio-big"> |
||||||
|
<%= radio_button f, :nightly_builds, true, class: "form-check-input nightly-builds-true", "aria-describedby": "nightly_builds-help-block" %> |
||||||
|
<div class="radio-icon"></div> |
||||||
|
<%= label :nightly_builds, :true, gettext("Yes"), class: "radio-text" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<%= error_tag f, :nightly_builds, id: "nightly_builds-help-block", class: "text-danger form-error" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">Select yes if you want to show nightly builds.</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label f, :compiler_version, gettext("Compiler") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= select f, :compiler_version, @compiler_versions, class: "form-control border-rounded", selected: compiler_version, "aria-describedby": "compiler-help-block" %> |
||||||
|
<%= error_tag f, :compiler_version, id: "compiler-help-block", class: "text-danger form-error" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">The compiler version is specified in <span class="tooltip-quote">pragma solidity X.X.X</span>. Use the compiler version rather than the nightly build. If using the Solidity compiler, run <span class="tooltip-quote">solc —version</span> to check.</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label :evm_version, :evm_version, gettext("EVM Version") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= select f, :evm_version, @evm_versions, class: "form-control border-rounded", selected: evm_version, "aria-describedby": "evm-version-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version. <a href="https://forum.poa.network/t/smart-contract-verification-evm-version-details/2318" target="_blank">EVM version details</a>.</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label f, "Optimization" %> |
||||||
|
<div class="center-column"> |
||||||
|
<div class="form-radios-group"> |
||||||
|
<div class="radio-big"> |
||||||
|
<%= radio_button f, :optimization, false, checked: !optimization, class: "form-check-input optimization-false" %> |
||||||
|
<div class="radio-icon"></div> |
||||||
|
<%= label :smart_contract_optimization, :false, gettext("No"), class: "radio-text" %> |
||||||
|
</div> |
||||||
|
<div class="radio-big"> |
||||||
|
<%= radio_button f, :optimization, true, checked: optimization, class: "form-check-input optimization-true", "aria-describedby": "optimization-help-block" %> |
||||||
|
<div class="radio-icon"></div> |
||||||
|
<%= label :smart_contract_optimization, :true, gettext("Yes"), class: "radio-text" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<%= error_tag f, :optimization, id: "optimization-help-block", class: "text-danger form-error" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">If you enabled optimization during compilation, select yes.</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group optimization-runs"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label f, :name, gettext("Optimization runs") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input f, :optimization_runs, value: 200, class: "form-control border-rounded", "aria-describedby": "optimization-runs-help-block", "data-test": "optimization-runs", value: optimization_runs_value %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label f, :contract_source_code, gettext("Enter the Solidity Contract Code") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= textarea f, :contract_source_code, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-source-code-help-block", value: contract_source_code_value %> |
||||||
|
<%= error_tag f, :contract_source_code, id: "contract-source-code-help-block", class: "text-danger form-error", "data-test": "contract-source-code-error" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the <a href="https://github.com/poanetwork/solidity-flattener" target="_blank">POA solidity flattener or the <a href="https://www.npmjs.com/package/truffle-flattener" target="_blank">truffle flattener</a>.</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label f, "Try to fetch contructor arguments automatically" %> |
||||||
|
<div class="center-column"> |
||||||
|
<div class="form-radios-group"> |
||||||
|
<div class="radio-big"> |
||||||
|
<%= radio_button f, :autodetect_contructor_args, false, checked: !fetch_constructor_arguments_automatically, class: "form-check-input autodetectfalse" %> |
||||||
|
<div class="radio-icon"></div> |
||||||
|
<%= label :autodetect_contructor_args, :false, gettext("No"), class: "radio-text" %> |
||||||
|
</div> |
||||||
|
<div class="radio-big"> |
||||||
|
<%= radio_button f, :autodetect_contructor_args, true, checked: fetch_constructor_arguments_automatically, class: "form-check-input autodetecttrue", "aria-describedby": "autodetect_contructor_args-help-block" %> |
||||||
|
<div class="radio-icon"></div> |
||||||
|
<%= label :autodetect_contructor_args, :true, gettext("Yes"), class: "radio-text" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<%= error_tag f, :autodetect_contructor_args, id: "autodetect_contructor_args-help-block", class: "text-danger form-error" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group constructor-arguments" style="display: <%= display_constructor_arguments_text_area %>"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label f, :contructor_arguments, gettext("ABI-encoded Constructor Arguments (if required by the contract)") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= textarea f, :constructor_arguments, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-constructor-arguments-help-block" %> |
||||||
|
<%= error_tag f, :constructor_arguments, id: "contract-constructor-arguments-help-block", class: "text-danger form-error", "data-test": "contract-constructor-arguments-error" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">Add arguments in <a href="https://solidity.readthedocs.io/en/develop/abi-spec.html" target="_blank">ABI hex encoded form</a>. Constructor arguments are written right to left, and will be found at the end of the input created bytecode. They may also be <a href="https://abi.hashex.org/" target="_blank">parsed here.</a></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="add-contract-libraries-wrapper"> |
||||||
|
<span class="btn-line js-btn-add-contract-libraries">Add Contract Libraries</span> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-libraries-wrapper js-smart-contract-libraries-wrapper"> |
||||||
|
<h2 class="smart-contract-title margin-bottom-md"><%= gettext "Contract Libraries" %></h2> |
||||||
|
|
||||||
|
<div class="contract-library-form-group js-contract-library-form-group active"> |
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label :external_libraries, :library1, gettext("Library Name") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input :external_libraries, :library1_name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">A library name called in the .sol file. Multiple libraries (up to 5) may be added for each contract. Click the Add Library button to add an additional one.</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label :external_libraries, :library1, gettext("Library Address") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input :external_libraries, :library1_address, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">The 0x library address. This can be found in the generated json file or Truffle output (if using truffle).</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="contract-library-form-group js-contract-library-form-group"> |
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label :external_libraries, :library2, gettext("Library Name") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input :external_libraries, :library2_name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label :external_libraries, :library2, gettext("Library Address") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input :external_libraries, :library2_address, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="contract-library-form-group js-contract-library-form-group"> |
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label :external_libraries, :library3, gettext("Library Name") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input :external_libraries, :library3_name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label :external_libraries, :library3, gettext("Library Address") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input :external_libraries, :library3_address, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="contract-library-form-group js-contract-library-form-group"> |
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label :external_libraries, :library4, gettext("Library Name") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input :external_libraries, :library4_name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label :external_libraries, :library4, gettext("Library Address") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input :external_libraries, :library4_address, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="contract-library-form-group js-contract-library-form-group"> |
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label :external_libraries, :library5, gettext("Library Name") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input :external_libraries, :library5_name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<%= label f, :library5, gettext("Library Address") %> |
||||||
|
<div class="center-column"> |
||||||
|
<%= text_input f, :library5_address, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="add-contract-library-wrapper js-add-contract-library-wrapper"> |
||||||
|
<span class="btn-line js-btn-add-contract-library">Add Library</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-buttons"> |
||||||
|
<button |
||||||
|
class="position-absolute w-118 btn-full-primary d-none mr-2" |
||||||
|
disabled="true" |
||||||
|
id="loading" |
||||||
|
name="button" |
||||||
|
type="button" |
||||||
|
> |
||||||
|
<span class="loading-spinner-small mr-2"> |
||||||
|
<span class="loading-spinner-block-1"></span> |
||||||
|
<span class="loading-spinner-block-2"></span> |
||||||
|
</span> |
||||||
|
<%= gettext("Loading....") %> |
||||||
|
</button> |
||||||
|
<%= submit gettext("Verify & publish"), class: "btn-full-primary mr-2", "data-button-loading": "animation" %> |
||||||
|
<%= reset gettext("Reset"), class: "btn-line mr-2 js-smart-contract-form-reset" %> |
||||||
|
<%= |
||||||
|
link( |
||||||
|
gettext("Cancel"), |
||||||
|
class: "btn-no-border", |
||||||
|
to: address_contract_path(@conn, :index, @address_hash) |
||||||
|
) |
||||||
|
%> |
||||||
|
</div> |
||||||
|
<% end %> |
||||||
|
</div> |
||||||
|
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/verification-form.js") %>"></script> |
||||||
|
</section> |
@ -0,0 +1,59 @@ |
|||||||
|
<section data-page="contract-verification" class="container new-smart-contract-container"> |
||||||
|
<div data-selector="channel-disconnected-message" style="display: none;"> |
||||||
|
<div data-selector="reload-button" class="alert alert-danger"> |
||||||
|
<a href="#" class="alert-link"><%= gettext "Connection Lost" %></a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="new-smart-contract-form"> |
||||||
|
<h1 class="smart-contract-title"><%= gettext "New Smart Contract Verification" %></h1> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<label for="smart_contract_address_hash"><%= gettext("Contract Address") %></label> |
||||||
|
<div class="center-column"> |
||||||
|
<input aria-describedby="contract-address-help-block" class="form-control border-rounded" id="smart_contract_address_hash" name="smart_contract[address_hash]" type="text" value=<%= @address_hash %> readonly=""> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">The 0x address supplied on contract creation.</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="smart-contract-form-group"> |
||||||
|
<div class="smart-contract-form-group-inner-wrapper"> |
||||||
|
<label for="smart_contract_metadata_json"><%= gettext("Sources and Metadata JSON") %></label> |
||||||
|
<div class="center-column"> |
||||||
|
<form action="<%= address_contract_verification_path(@conn, :create) %>" class="dropzone-1" id="metadata-json-dropzone" method="post"> |
||||||
|
<button class="dz-message btn-full-primary"><%= gettext("Drop sources and metadata JSON file or click here") %></button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-group-tooltip">Drop all Solidity contract source files and JSON metadata file(s) created during contract compilation into the drop zone.</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="smart-contract-form-buttons"> |
||||||
|
<button |
||||||
|
class="position-absolute w-118 btn-full-primary d-none mr-2" |
||||||
|
disabled="true" |
||||||
|
id="loading" |
||||||
|
name="button" |
||||||
|
type="button" |
||||||
|
> |
||||||
|
<span class="loading-spinner-small mr-2"> |
||||||
|
<span class="loading-spinner-block-1"></span> |
||||||
|
<span class="loading-spinner-block-2"></span> |
||||||
|
</span> |
||||||
|
<%= gettext("Loading....") %> |
||||||
|
</button> |
||||||
|
<button id="verify-via-json-submit" class="btn-full-primary mr-2" data-button-loading="animation"><%= gettext("Verify & publish") %></button> |
||||||
|
<%= reset gettext("Reset"), class: "btn-line mr-2 js-smart-contract-form-reset" %> |
||||||
|
<%= |
||||||
|
link( |
||||||
|
gettext("Cancel"), |
||||||
|
class: "btn-no-border", |
||||||
|
to: address_contract_path(@conn, :index, @address_hash) |
||||||
|
) |
||||||
|
%> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/verification-form.js") %>"></script> |
||||||
|
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/dropzone.min.js") %>"></script> |
||||||
|
</section> |
@ -0,0 +1,5 @@ |
|||||||
|
defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeView do |
||||||
|
use BlockScoutWeb, :view |
||||||
|
|
||||||
|
alias Explorer.Chain |
||||||
|
end |
@ -0,0 +1,3 @@ |
|||||||
|
defmodule BlockScoutWeb.AddressContractVerificationViaJsonView do |
||||||
|
use BlockScoutWeb, :view |
||||||
|
end |
@ -1,5 +1,3 @@ |
|||||||
defmodule BlockScoutWeb.AddressContractVerificationView do |
defmodule BlockScoutWeb.AddressContractVerificationView do |
||||||
use BlockScoutWeb, :view |
use BlockScoutWeb, :view |
||||||
|
|
||||||
alias Explorer.Chain |
|
||||||
end |
end |
||||||
|
@ -0,0 +1,63 @@ |
|||||||
|
defmodule Explorer.Chain.SmartContractAdditionalSource do |
||||||
|
@moduledoc """ |
||||||
|
The representation of a verified Smart Contract additional sources. |
||||||
|
It is used when contract is verified with Sourcify utility. |
||||||
|
""" |
||||||
|
|
||||||
|
require Logger |
||||||
|
|
||||||
|
use Explorer.Schema |
||||||
|
|
||||||
|
alias Explorer.Chain.{Hash, SmartContract} |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
* `file_name` - the name of the Solidity file with contract code (with extension). |
||||||
|
* `contract_source_code` - the Solidity source code from the file with `file_name`. |
||||||
|
""" |
||||||
|
|
||||||
|
@type t :: %Explorer.Chain.SmartContractAdditionalSource{ |
||||||
|
file_name: String.t(), |
||||||
|
contract_source_code: String.t() |
||||||
|
} |
||||||
|
|
||||||
|
schema "smart_contracts_additional_sources" do |
||||||
|
field(:file_name, :string) |
||||||
|
field(:contract_source_code, :string) |
||||||
|
|
||||||
|
belongs_to( |
||||||
|
:smart_contract, |
||||||
|
SmartContract, |
||||||
|
foreign_key: :address_hash, |
||||||
|
references: :address_hash, |
||||||
|
type: Hash.Address |
||||||
|
) |
||||||
|
|
||||||
|
timestamps() |
||||||
|
end |
||||||
|
|
||||||
|
def changeset(%__MODULE__{} = smart_contract_additional_source, attrs) do |
||||||
|
smart_contract_additional_source |
||||||
|
|> cast(attrs, [ |
||||||
|
:file_name, |
||||||
|
:contract_source_code, |
||||||
|
:address_hash |
||||||
|
]) |
||||||
|
|> validate_required([:file_name, :contract_source_code, :address_hash]) |
||||||
|
|> unique_constraint(:address_hash) |
||||||
|
end |
||||||
|
|
||||||
|
def invalid_contract_changeset(%__MODULE__{} = smart_contract_additional_source, attrs, error) do |
||||||
|
validated = |
||||||
|
smart_contract_additional_source |
||||||
|
|> cast(attrs, [ |
||||||
|
:file_name, |
||||||
|
:contract_source_code, |
||||||
|
:address_hash |
||||||
|
]) |
||||||
|
|> validate_required([:file_name, :address_hash]) |
||||||
|
|
||||||
|
add_error(validated, :contract_source_code, error_message(error)) |
||||||
|
end |
||||||
|
|
||||||
|
defp error_message(_), do: "There was an error validating your contract, please try again." |
||||||
|
end |
@ -0,0 +1,184 @@ |
|||||||
|
defmodule Explorer.ThirdPartyIntegrations.Sourcify do |
||||||
|
@moduledoc """ |
||||||
|
Adapter for contracts verification with https://sourcify.dev/ |
||||||
|
""" |
||||||
|
use Tesla |
||||||
|
|
||||||
|
alias HTTPoison.{Error, Response} |
||||||
|
alias Tesla.Multipart |
||||||
|
|
||||||
|
def check_by_address(address_hash_string) do |
||||||
|
chain_id = config(:chain_id) |
||||||
|
params = [addresses: address_hash_string, chainIds: chain_id] |
||||||
|
http_get_request(check_by_address_url(), params) |
||||||
|
end |
||||||
|
|
||||||
|
def get_metadata(address_hash_string) do |
||||||
|
get_metadata_full_url = get_metadata_url() <> "/" <> address_hash_string |
||||||
|
http_get_request(get_metadata_full_url, []) |
||||||
|
end |
||||||
|
|
||||||
|
def verify(address_hash_string, files) do |
||||||
|
chain_id = config(:chain_id) |
||||||
|
|
||||||
|
multipart_text_params = |
||||||
|
Multipart.new() |
||||||
|
|> Multipart.add_field("chain", chain_id) |
||||||
|
|> Multipart.add_field("address", address_hash_string) |
||||||
|
|
||||||
|
multipart_body = |
||||||
|
files |
||||||
|
|> Enum.reduce(multipart_text_params, fn file, acc -> |
||||||
|
if file do |
||||||
|
acc |
||||||
|
|> Multipart.add_file(file.path, |
||||||
|
name: "files", |
||||||
|
file_name: Path.basename(file.path) |
||||||
|
) |
||||||
|
else |
||||||
|
acc |
||||||
|
end |
||||||
|
end) |
||||||
|
|
||||||
|
http_post_request(verify_url(), multipart_body) |
||||||
|
end |
||||||
|
|
||||||
|
def http_get_request(url, params) do |
||||||
|
request = HTTPoison.get(url, [], params: params) |
||||||
|
|
||||||
|
case request do |
||||||
|
{:ok, %Response{body: body, status_code: 200}} -> |
||||||
|
process_sourcify_response(url, body) |
||||||
|
|
||||||
|
{:ok, %Response{body: body, status_code: status_code}} when status_code in 400..526 -> |
||||||
|
parse_http_error_response(body) |
||||||
|
|
||||||
|
{:ok, %Response{status_code: status_code}} when status_code in 300..308 -> |
||||||
|
{:error, "Sourcify redirected"} |
||||||
|
|
||||||
|
{:ok, %Response{status_code: _status_code}} -> |
||||||
|
{:error, "Sourcify unexpected status code"} |
||||||
|
|
||||||
|
{:error, %Error{reason: reason}} -> |
||||||
|
{:error, reason} |
||||||
|
|
||||||
|
{:error, :nxdomain} -> |
||||||
|
{:error, "Sourcify is not responsive"} |
||||||
|
|
||||||
|
{:error, _} -> |
||||||
|
{:error, "Unexpected response from Sourcify"} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def http_post_request(url, body) do |
||||||
|
request = Tesla.post(url, body) |
||||||
|
|
||||||
|
case request do |
||||||
|
{:ok, %Tesla.Env{body: body}} -> |
||||||
|
process_sourcify_response(url, body) |
||||||
|
|
||||||
|
_ -> |
||||||
|
{:error, "Unexpected response from Sourcify verify method"} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp process_sourcify_response(url, body) do |
||||||
|
cond do |
||||||
|
url =~ "checkByAddresses" -> |
||||||
|
parse_check_by_address_http_response(body) |
||||||
|
|
||||||
|
url =~ "/verify" -> |
||||||
|
parse_verify_http_response(body) |
||||||
|
|
||||||
|
url =~ "/files/" -> |
||||||
|
parse_get_metadata_http_response(body) |
||||||
|
|
||||||
|
true -> |
||||||
|
{:error, body} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp parse_verify_http_response(body) do |
||||||
|
body_json = decode_json(body) |
||||||
|
|
||||||
|
case body_json do |
||||||
|
%{"result" => [%{"status" => "perfect"}]} -> |
||||||
|
{:ok, body_json} |
||||||
|
|
||||||
|
%{"result" => [%{"status" => unknown_status}]} -> |
||||||
|
{:error, unknown_status} |
||||||
|
|
||||||
|
body -> |
||||||
|
{:error, body} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp parse_check_by_address_http_response(body) do |
||||||
|
body_json = decode_json(body) |
||||||
|
|
||||||
|
case body_json do |
||||||
|
[%{"status" => "perfect"}] -> |
||||||
|
{:ok, body_json} |
||||||
|
|
||||||
|
[%{"status" => "false"}] -> |
||||||
|
{:error, "Contract is not verified"} |
||||||
|
|
||||||
|
[%{"status" => unknown_status}] -> |
||||||
|
{:error, unknown_status} |
||||||
|
|
||||||
|
body -> |
||||||
|
{:error, body} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp parse_get_metadata_http_response(body) do |
||||||
|
body_json = decode_json(body) |
||||||
|
|
||||||
|
case body_json do |
||||||
|
%{"message" => message, "errors" => errors} -> |
||||||
|
{:error, "#{message}: #{decode_json(errors)}"} |
||||||
|
|
||||||
|
metadata -> |
||||||
|
{:ok, metadata} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp parse_http_error_response(body) do |
||||||
|
body_json = decode_json(body) |
||||||
|
|
||||||
|
if is_map(body_json) do |
||||||
|
{:error, body_json["error"]} |
||||||
|
else |
||||||
|
{:error, body} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def decode_json(data) do |
||||||
|
Jason.decode!(data) |
||||||
|
rescue |
||||||
|
_ -> data |
||||||
|
end |
||||||
|
|
||||||
|
defp config(key) do |
||||||
|
:explorer |
||||||
|
|> Application.get_env(__MODULE__) |
||||||
|
|> Keyword.get(key) |
||||||
|
end |
||||||
|
|
||||||
|
defp base_server_url do |
||||||
|
config(:server_url) |
||||||
|
end |
||||||
|
|
||||||
|
defp verify_url do |
||||||
|
"#{base_server_url()}" <> "/verify" |
||||||
|
end |
||||||
|
|
||||||
|
defp check_by_address_url do |
||||||
|
"#{base_server_url()}" <> "/checkByAddresses" |
||||||
|
end |
||||||
|
|
||||||
|
defp get_metadata_url do |
||||||
|
chain_id = config(:chain_id) |
||||||
|
"#{base_server_url()}" <> "/files/" <> chain_id |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,22 @@ |
|||||||
|
defmodule Explorer.Repo.Migrations.SupportSourcify do |
||||||
|
use Ecto.Migration |
||||||
|
|
||||||
|
def change do |
||||||
|
alter table(:smart_contracts) do |
||||||
|
add(:verified_via_sourcify, :boolean, null: true) |
||||||
|
end |
||||||
|
|
||||||
|
create table(:smart_contracts_additional_sources) do |
||||||
|
add(:file_name, :string, null: false) |
||||||
|
add(:contract_source_code, :text, null: false) |
||||||
|
|
||||||
|
add(:address_hash, references(:smart_contracts, column: :address_hash, on_delete: :delete_all, type: :bytea), |
||||||
|
null: false |
||||||
|
) |
||||||
|
|
||||||
|
timestamps() |
||||||
|
end |
||||||
|
|
||||||
|
create(index(:smart_contracts_additional_sources, :address_hash)) |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue