Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
70d17ce1d8 | ||
|
3b14bcf29a | ||
|
4fc69e11d6 | ||
|
8e4df824b0 | ||
|
e1f2e840cb | ||
|
2a36a89c36 | ||
|
b643f4011d | ||
|
814304bdd7 | ||
|
f90607302d | ||
|
af826f877e | ||
|
f9a0aa0022 | ||
|
c4196a5de3 | ||
|
3b56e8eded | ||
|
b3ccbbe913 | ||
|
fe0f037f23 |
@ -1,8 +1,8 @@
|
|||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
- js-build
|
- js-build
|
||||||
- build
|
|
||||||
- push-release
|
- push-release
|
||||||
|
- build
|
||||||
variables:
|
variables:
|
||||||
GIT_DEPTH: "3"
|
GIT_DEPTH: "3"
|
||||||
SIMPLECOV: "true"
|
SIMPLECOV: "true"
|
||||||
@ -460,9 +460,8 @@ windows:
|
|||||||
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
|
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
|
||||||
- md5sums win-installer.zip > win-installer.zip.md5
|
- md5sums win-installer.zip > win-installer.zip.md5
|
||||||
- cd ..\target\release\
|
- cd ..\target\release\
|
||||||
- md5sums parity.exe parity.pdb > parity.md5
|
|
||||||
- md5sums parity.exe > parity.exe.md5
|
- md5sums parity.exe > parity.exe.md5
|
||||||
- zip parity.zip parity.exe parity.pdb parity.md5
|
- zip parity.zip parity.exe parity.md5
|
||||||
- md5sums parity.zip > parity.zip.md5
|
- md5sums parity.zip > parity.zip.md5
|
||||||
- cd ..\..
|
- cd ..\..
|
||||||
- aws configure set aws_access_key_id %s3_key%
|
- aws configure set aws_access_key_id %s3_key%
|
||||||
@ -488,7 +487,6 @@ windows:
|
|||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- target/release/parity.exe
|
- target/release/parity.exe
|
||||||
- target/release/parity.pdb
|
|
||||||
- nsis/InstallParity.exe
|
- nsis/InstallParity.exe
|
||||||
name: "x86_64-pc-windows-msvc_parity"
|
name: "x86_64-pc-windows-msvc_parity"
|
||||||
docker-build:
|
docker-build:
|
||||||
|
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1692,7 +1692,7 @@ name = "parity-ui"
|
|||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-ui-dev 1.4.0",
|
"parity-ui-dev 1.4.0",
|
||||||
"parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)",
|
"parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git?branch=beta)",
|
||||||
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1706,7 +1706,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-ui-precompiled"
|
name = "parity-ui-precompiled"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "git+https://github.com/ethcore/js-precompiled.git#94da980fb81d6145e38ca87d37a9137e8440086a"
|
source = "git+https://github.com/ethcore/js-precompiled.git?branch=beta#2dd61a4479a1a91114a9bd6d6d4e41b8ac2f355e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
@ -2682,7 +2682,7 @@ dependencies = [
|
|||||||
"checksum order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb"
|
"checksum order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb"
|
||||||
"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7"
|
"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7"
|
||||||
"checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab"
|
"checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab"
|
||||||
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>"
|
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git?branch=beta)" = "<none>"
|
||||||
"checksum parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e1435e7a2a00dfebededd6c6bdbd54008001e94b4a2aadd6aef0dc4c56317621"
|
"checksum parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e1435e7a2a00dfebededd6c6bdbd54008001e94b4a2aadd6aef0dc4c56317621"
|
||||||
"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068"
|
"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068"
|
||||||
"checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026"
|
"checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026"
|
||||||
|
@ -39,7 +39,11 @@ pub struct RestApi {
|
|||||||
impl RestApi {
|
impl RestApi {
|
||||||
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
||||||
Box::new(RestApi {
|
Box::new(RestApi {
|
||||||
cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
|
cors_domains: Some(cors_domains.into_iter().map(|domain| match domain.as_ref() {
|
||||||
|
"all" | "*" | "any" => AccessControlAllowOrigin::Any,
|
||||||
|
"null" => AccessControlAllowOrigin::Null,
|
||||||
|
other => AccessControlAllowOrigin::Value(other.into()),
|
||||||
|
}).collect()),
|
||||||
endpoints: endpoints,
|
endpoints: endpoints,
|
||||||
fetcher: fetcher,
|
fetcher: fetcher,
|
||||||
})
|
})
|
||||||
|
@ -111,6 +111,7 @@ pub struct ServerBuilder<T: Fetch = FetchClient> {
|
|||||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||||
signer_address: Option<(String, u16)>,
|
signer_address: Option<(String, u16)>,
|
||||||
allowed_hosts: Option<Vec<String>>,
|
allowed_hosts: Option<Vec<String>>,
|
||||||
|
extra_cors: Option<Vec<String>>,
|
||||||
remote: Remote,
|
remote: Remote,
|
||||||
fetch: Option<T>,
|
fetch: Option<T>,
|
||||||
}
|
}
|
||||||
@ -126,6 +127,7 @@ impl ServerBuilder {
|
|||||||
web_proxy_tokens: Arc::new(|_| false),
|
web_proxy_tokens: Arc::new(|_| false),
|
||||||
signer_address: None,
|
signer_address: None,
|
||||||
allowed_hosts: Some(vec![]),
|
allowed_hosts: Some(vec![]),
|
||||||
|
extra_cors: None,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
}
|
}
|
||||||
@ -143,6 +145,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
|||||||
web_proxy_tokens: self.web_proxy_tokens,
|
web_proxy_tokens: self.web_proxy_tokens,
|
||||||
signer_address: self.signer_address,
|
signer_address: self.signer_address,
|
||||||
allowed_hosts: self.allowed_hosts,
|
allowed_hosts: self.allowed_hosts,
|
||||||
|
extra_cors: self.extra_cors,
|
||||||
remote: self.remote,
|
remote: self.remote,
|
||||||
fetch: Some(fetch),
|
fetch: Some(fetch),
|
||||||
}
|
}
|
||||||
@ -174,6 +177,13 @@ impl<T: Fetch> ServerBuilder<T> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extra cors headers.
|
||||||
|
/// `None` - no additional CORS URLs
|
||||||
|
pub fn extra_cors_headers(mut self, cors: Option<Vec<String>>) -> Self {
|
||||||
|
self.extra_cors = cors;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Change extra dapps paths (apart from `dapps_path`)
|
/// Change extra dapps paths (apart from `dapps_path`)
|
||||||
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
|
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
|
||||||
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
|
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
|
||||||
@ -187,6 +197,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
|||||||
Server::start_http(
|
Server::start_http(
|
||||||
addr,
|
addr,
|
||||||
self.allowed_hosts,
|
self.allowed_hosts,
|
||||||
|
self.extra_cors,
|
||||||
NoAuth,
|
NoAuth,
|
||||||
handler,
|
handler,
|
||||||
self.dapps_path,
|
self.dapps_path,
|
||||||
@ -207,6 +218,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
|||||||
Server::start_http(
|
Server::start_http(
|
||||||
addr,
|
addr,
|
||||||
self.allowed_hosts,
|
self.allowed_hosts,
|
||||||
|
self.extra_cors,
|
||||||
HttpBasicAuth::single_user(username, password),
|
HttpBasicAuth::single_user(username, password),
|
||||||
handler,
|
handler,
|
||||||
self.dapps_path,
|
self.dapps_path,
|
||||||
@ -251,8 +263,8 @@ impl Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of CORS domains for API endpoint.
|
/// Returns a list of CORS domains for API endpoint.
|
||||||
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> {
|
fn cors_domains(signer_address: Option<(String, u16)>, extra_cors: Option<Vec<String>>) -> Vec<String> {
|
||||||
match signer_address {
|
let basic_cors = match signer_address {
|
||||||
Some(signer_address) => vec![
|
Some(signer_address) => vec![
|
||||||
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
||||||
format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
|
format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
|
||||||
@ -260,15 +272,20 @@ impl Server {
|
|||||||
format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
||||||
format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
|
format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
|
||||||
format!("https://{}", address(&signer_address)),
|
format!("https://{}", address(&signer_address)),
|
||||||
|
|
||||||
],
|
],
|
||||||
None => vec![],
|
None => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
match extra_cors {
|
||||||
|
None => basic_cors,
|
||||||
|
Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>(
|
fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>(
|
||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
hosts: Option<Vec<String>>,
|
hosts: Option<Vec<String>>,
|
||||||
|
extra_cors: Option<Vec<String>>,
|
||||||
authorization: A,
|
authorization: A,
|
||||||
handler: RpcHandler<Metadata, T>,
|
handler: RpcHandler<Metadata, T>,
|
||||||
dapps_path: PathBuf,
|
dapps_path: PathBuf,
|
||||||
@ -297,7 +314,7 @@ impl Server {
|
|||||||
remote.clone(),
|
remote.clone(),
|
||||||
fetch.clone(),
|
fetch.clone(),
|
||||||
));
|
));
|
||||||
let cors_domains = Self::cors_domains(signer_address.clone());
|
let cors_domains = Self::cors_domains(signer_address.clone(), extra_cors);
|
||||||
|
|
||||||
let special = Arc::new({
|
let special = Arc::new({
|
||||||
let mut special = HashMap::new();
|
let mut special = HashMap::new();
|
||||||
@ -413,8 +430,9 @@ mod util_tests {
|
|||||||
// given
|
// given
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let none = Server::cors_domains(None);
|
let none = Server::cors_domains(None, None);
|
||||||
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)));
|
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None);
|
||||||
|
let extra = Server::cors_domains(None, Some(vec!["all".to_owned()]));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(none, Vec::<String>::new());
|
assert_eq!(none, Vec::<String>::new());
|
||||||
@ -425,7 +443,7 @@ mod util_tests {
|
|||||||
"https://parity.web3.site".into(),
|
"https://parity.web3.site".into(),
|
||||||
"https://parity.web3.site:18180".into(),
|
"https://parity.web3.site:18180".into(),
|
||||||
"https://127.0.0.1:18180".into()
|
"https://127.0.0.1:18180".into()
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
assert_eq!(extra, vec!["all".to_owned()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers};
|
use tests::helpers::{serve, serve_with_registrar, serve_extra_cors, request, assert_security_headers};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_return_error() {
|
fn should_return_error() {
|
||||||
@ -212,3 +212,25 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_port() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_extra_cors_headers() {
|
||||||
|
// given
|
||||||
|
let server = serve_extra_cors(Some(vec!["all".to_owned()]));
|
||||||
|
|
||||||
|
// when
|
||||||
|
let response = request(server,
|
||||||
|
"\
|
||||||
|
POST /api/ping HTTP/1.1\r\n\
|
||||||
|
Host: localhost:8080\r\n\
|
||||||
|
Origin: http://somedomain.io\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
{}
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
// then
|
||||||
|
response.assert_status("HTTP/1.1 200 OK");
|
||||||
|
response.assert_header("Access-Control-Allow-Origin", "http://somedomain.io");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -109,6 +109,10 @@ pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop {
|
|||||||
init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0
|
init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn serve_extra_cors(extra_cors: Option<Vec<String>>) -> ServerLoop {
|
||||||
|
init_server(|builder| builder.allowed_hosts(None).extra_cors_headers(extra_cors), Default::default(), Remote::new_sync()).0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) {
|
pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) {
|
||||||
init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync())
|
init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync())
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ rustc_version = "0.1"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
parity-ui-dev = { path = "../../js", optional = true }
|
parity-ui-dev = { path = "../../js", optional = true }
|
||||||
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", optional = true }
|
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", branch = "beta", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
no-precompiled-js = ["parity-ui-dev"]
|
no-precompiled-js = ["parity-ui-dev"]
|
||||||
|
@ -83,6 +83,8 @@ pub struct AuthorityRound {
|
|||||||
client: RwLock<Option<Weak<EngineClient>>>,
|
client: RwLock<Option<Weak<EngineClient>>>,
|
||||||
signer: EngineSigner,
|
signer: EngineSigner,
|
||||||
validators: Box<ValidatorSet + Send + Sync>,
|
validators: Box<ValidatorSet + Send + Sync>,
|
||||||
|
/// Is this Engine just for testing (prevents step calibration).
|
||||||
|
calibrate_step: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
|
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
|
||||||
@ -122,6 +124,7 @@ impl AuthorityRound {
|
|||||||
client: RwLock::new(None),
|
client: RwLock::new(None),
|
||||||
signer: Default::default(),
|
signer: Default::default(),
|
||||||
validators: new_validator_set(our_params.validators),
|
validators: new_validator_set(our_params.validators),
|
||||||
|
calibrate_step: our_params.start_step.is_none(),
|
||||||
});
|
});
|
||||||
// Do not initialize timeouts for tests.
|
// Do not initialize timeouts for tests.
|
||||||
if should_timeout {
|
if should_timeout {
|
||||||
@ -131,6 +134,12 @@ impl AuthorityRound {
|
|||||||
Ok(engine)
|
Ok(engine)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calibrate_step(&self) {
|
||||||
|
if self.calibrate_step {
|
||||||
|
self.step.store((unix_now().as_secs() / self.step_duration.as_secs()) as usize, AtomicOrdering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn remaining_step_duration(&self) -> Duration {
|
fn remaining_step_duration(&self) -> Duration {
|
||||||
let now = unix_now();
|
let now = unix_now();
|
||||||
let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
|
let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
|
||||||
@ -148,6 +157,16 @@ impl AuthorityRound {
|
|||||||
fn is_step_proposer(&self, step: usize, address: &Address) -> bool {
|
fn is_step_proposer(&self, step: usize, address: &Address) -> bool {
|
||||||
self.step_proposer(step) == *address
|
self.step_proposer(step) == *address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_future_step(&self, step: usize) -> bool {
|
||||||
|
if step > self.step.load(AtomicOrdering::SeqCst) + 1 {
|
||||||
|
// Make absolutely sure that the step is correct.
|
||||||
|
self.calibrate_step();
|
||||||
|
step > self.step.load(AtomicOrdering::SeqCst) + 1
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unix_now() -> Duration {
|
fn unix_now() -> Duration {
|
||||||
@ -285,7 +304,11 @@ impl Engine for AuthorityRound {
|
|||||||
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
let header_step = header_step(header)?;
|
let header_step = header_step(header)?;
|
||||||
// Give one step slack if step is lagging, double vote is still not possible.
|
// Give one step slack if step is lagging, double vote is still not possible.
|
||||||
if header_step <= self.step.load(AtomicOrdering::SeqCst) + 1 {
|
if self.is_future_step(header_step) {
|
||||||
|
trace!(target: "engine", "verify_block_unordered: block from the future");
|
||||||
|
self.validators.report_benign(header.author());
|
||||||
|
Err(BlockError::InvalidSeal)?
|
||||||
|
} else {
|
||||||
let proposer_signature = header_signature(header)?;
|
let proposer_signature = header_signature(header)?;
|
||||||
let correct_proposer = self.step_proposer(header_step);
|
let correct_proposer = self.step_proposer(header_step);
|
||||||
if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
|
if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
|
||||||
@ -294,10 +317,6 @@ impl Engine for AuthorityRound {
|
|||||||
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step);
|
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step);
|
||||||
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
|
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
trace!(target: "engine", "verify_block_unordered: block from the future");
|
|
||||||
self.validators.report_benign(header.author());
|
|
||||||
Err(BlockError::InvalidSeal)?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +166,9 @@ pub trait Engine : Sync + Send {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The network ID that transactions should be signed with.
|
/// The network ID that transactions should be signed with.
|
||||||
fn signing_network_id(&self, _env_info: &EnvInfo) -> Option<u64> { None }
|
fn signing_network_id(&self, _env_info: &EnvInfo) -> Option<u64> {
|
||||||
|
Some(self.params().chain_id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Verify the seal of a block. This is an auxilliary method that actually just calls other `verify_` methods
|
/// Verify the seal of a block. This is an auxilliary method that actually just calls other `verify_` methods
|
||||||
/// to get the job done. By default it must pass `verify_basic` and `verify_block_unordered`. If more or fewer
|
/// to get the job done. By default it must pass `verify_basic` and `verify_block_unordered`. If more or fewer
|
||||||
|
@ -107,34 +107,26 @@ export default class Contract {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deploy (options, values, statecb) {
|
deploy (options, values, statecb = () => {}) {
|
||||||
const setState = (state) => {
|
statecb(null, { state: 'estimateGas' });
|
||||||
if (!statecb) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return statecb(null, state);
|
|
||||||
};
|
|
||||||
|
|
||||||
setState({ state: 'estimateGas' });
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
.deployEstimateGas(options, values)
|
.deployEstimateGas(options, values)
|
||||||
.then(([gasEst, gas]) => {
|
.then(([gasEst, gas]) => {
|
||||||
options.gas = gas.toFixed(0);
|
options.gas = gas.toFixed(0);
|
||||||
|
|
||||||
setState({ state: 'postTransaction', gas });
|
statecb(null, { state: 'postTransaction', gas });
|
||||||
|
|
||||||
const _options = this._encodeOptions(this.constructors[0], options, values);
|
const encodedOptions = this._encodeOptions(this.constructors[0], options, values);
|
||||||
|
|
||||||
return this._api.parity
|
return this._api.parity
|
||||||
.postTransaction(_options)
|
.postTransaction(encodedOptions)
|
||||||
.then((requestId) => {
|
.then((requestId) => {
|
||||||
setState({ state: 'checkRequest', requestId });
|
statecb(null, { state: 'checkRequest', requestId });
|
||||||
return this._pollCheckRequest(requestId);
|
return this._pollCheckRequest(requestId);
|
||||||
})
|
})
|
||||||
.then((txhash) => {
|
.then((txhash) => {
|
||||||
setState({ state: 'getTransactionReceipt', txhash });
|
statecb(null, { state: 'getTransactionReceipt', txhash });
|
||||||
return this._pollTransactionReceipt(txhash, gas);
|
return this._pollTransactionReceipt(txhash, gas);
|
||||||
})
|
})
|
||||||
.then((receipt) => {
|
.then((receipt) => {
|
||||||
@ -142,14 +134,13 @@ export default class Contract {
|
|||||||
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
|
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
setState({ state: 'hasReceipt', receipt });
|
statecb(null, { state: 'hasReceipt', receipt });
|
||||||
this._receipt = receipt;
|
this._receipt = receipt;
|
||||||
this._address = receipt.contractAddress;
|
this._address = receipt.contractAddress;
|
||||||
return this._address;
|
return this._address;
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.then((address) => {
|
.then((address) => {
|
||||||
setState({ state: 'getCode' });
|
statecb(null, { state: 'getCode' });
|
||||||
return this._api.eth.getCode(this._address);
|
return this._api.eth.getCode(this._address);
|
||||||
})
|
})
|
||||||
.then((code) => {
|
.then((code) => {
|
||||||
@ -157,9 +148,10 @@ export default class Contract {
|
|||||||
throw new Error('Contract not deployed, getCode returned 0x');
|
throw new Error('Contract not deployed, getCode returned 0x');
|
||||||
}
|
}
|
||||||
|
|
||||||
setState({ state: 'completed' });
|
statecb(null, { state: 'completed' });
|
||||||
return this._address;
|
return this._address;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
parseEventLogs (logs) {
|
parseEventLogs (logs) {
|
||||||
|
466
js/src/contracts/abi/old-wallet.json
Normal file
466
js/src/contracts/abi/old-wallet.json
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_owner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "removeOwner",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_addr",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "isOwner",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "m_numOwners",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "m_lastDay",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "resetSpentToday",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "m_spentToday",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_owner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "addOwner",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "m_required",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_h",
|
||||||
|
"type": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "confirm",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_newLimit",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "setDailyLimit",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_to",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_value",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_data",
|
||||||
|
"type": "bytes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "execute",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "_r",
|
||||||
|
"type": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "revoke",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_newRequired",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "changeRequirement",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_owner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "hasConfirmed",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "ownerIndex",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getOwner",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_to",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "kill",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_from",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_to",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "changeOwner",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "m_dailyLimit",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_owners",
|
||||||
|
"type": "address[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_required",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_daylimit",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "constructor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Confirmation",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Revoke",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "oldOwner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "newOwner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "OwnerChanged",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "newOwner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "OwnerAdded",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "oldOwner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "OwnerRemoved",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "newRequirement",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "RequirementChanged",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "_from",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "value",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Deposit",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "value",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "to",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "data",
|
||||||
|
"type": "bytes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "SingleTransact",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "value",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "to",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "data",
|
||||||
|
"type": "bytes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "MultiTransact",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "initiator",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "value",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "to",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "data",
|
||||||
|
"type": "bytes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "ConfirmationNeeded",
|
||||||
|
"type": "event"
|
||||||
|
}
|
||||||
|
]
|
@ -1 +1,476 @@
|
|||||||
[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}]
|
[
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_owner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "removeOwner",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_addr",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "isOwner",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "m_numOwners",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "m_lastDay",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "resetSpentToday",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "m_spentToday",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_owner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "addOwner",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "m_required",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_h",
|
||||||
|
"type": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "confirm",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_newLimit",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "setDailyLimit",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_to",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_value",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_data",
|
||||||
|
"type": "bytes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "execute",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "_r",
|
||||||
|
"type": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "revoke",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_newRequired",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "changeRequirement",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_owner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "hasConfirmed",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "ownerIndex",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "getOwner",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_to",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "kill",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_from",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_to",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "changeOwner",
|
||||||
|
"outputs": [],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": true,
|
||||||
|
"inputs": [],
|
||||||
|
"name": "m_dailyLimit",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_owners",
|
||||||
|
"type": "address[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_required",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_daylimit",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "constructor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Confirmation",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Revoke",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "oldOwner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "newOwner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "OwnerChanged",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "newOwner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "OwnerAdded",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "oldOwner",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "OwnerRemoved",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "newRequirement",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "RequirementChanged",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "_from",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "value",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Deposit",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "value",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "to",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "data",
|
||||||
|
"type": "bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "created",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "SingleTransact",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "value",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "to",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "data",
|
||||||
|
"type": "bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "created",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "MultiTransact",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "operation",
|
||||||
|
"type": "bytes32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "initiator",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "value",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "to",
|
||||||
|
"type": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"name": "data",
|
||||||
|
"type": "bytes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "ConfirmationNeeded",
|
||||||
|
"type": "event"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
File diff suppressed because one or more lines are too long
@ -8,12 +8,13 @@
|
|||||||
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
|
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
|
||||||
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
||||||
// interior is executed.
|
// interior is executed.
|
||||||
pragma solidity ^0.4.6;
|
|
||||||
|
|
||||||
contract multisig {
|
pragma solidity ^0.4.9;
|
||||||
|
|
||||||
|
contract WalletEvents {
|
||||||
// EVENTS
|
// EVENTS
|
||||||
|
|
||||||
// this contract can accept a confirmation, in which case
|
// this contract only has six types of events: it can accept a confirmation, in which case
|
||||||
// we record owner and operation (hash) alongside it.
|
// we record owner and operation (hash) alongside it.
|
||||||
event Confirmation(address owner, bytes32 operation);
|
event Confirmation(address owner, bytes32 operation);
|
||||||
event Revoke(address owner, bytes32 operation);
|
event Revoke(address owner, bytes32 operation);
|
||||||
@ -29,38 +30,38 @@ contract multisig {
|
|||||||
// Funds has arrived into the wallet (record how much).
|
// Funds has arrived into the wallet (record how much).
|
||||||
event Deposit(address _from, uint value);
|
event Deposit(address _from, uint value);
|
||||||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||||
event SingleTransact(address owner, uint value, address to, bytes data);
|
event SingleTransact(address owner, uint value, address to, bytes data, address created);
|
||||||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
||||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);
|
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
|
||||||
// Confirmation still needed for a transaction.
|
// Confirmation still needed for a transaction.
|
||||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||||
}
|
}
|
||||||
|
|
||||||
contract multisigAbi is multisig {
|
contract WalletAbi {
|
||||||
function isOwner(address _addr) returns (bool);
|
// Revokes a prior confirmation of the given operation
|
||||||
|
function revoke(bytes32 _operation) external;
|
||||||
|
|
||||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool);
|
// Replaces an owner `_from` with another `_to`.
|
||||||
|
function changeOwner(address _from, address _to) external;
|
||||||
|
|
||||||
function confirm(bytes32 _h) returns(bool);
|
function addOwner(address _owner) external;
|
||||||
|
|
||||||
|
function removeOwner(address _owner) external;
|
||||||
|
|
||||||
|
function changeRequirement(uint _newRequired) external;
|
||||||
|
|
||||||
|
function isOwner(address _addr) constant returns (bool);
|
||||||
|
|
||||||
|
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool);
|
||||||
|
|
||||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||||
function setDailyLimit(uint _newLimit);
|
function setDailyLimit(uint _newLimit) external;
|
||||||
|
|
||||||
function addOwner(address _owner);
|
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
|
||||||
|
function confirm(bytes32 _h) returns (bool o_success);
|
||||||
function removeOwner(address _owner);
|
|
||||||
|
|
||||||
function changeRequirement(uint _newRequired);
|
|
||||||
|
|
||||||
// Revokes a prior confirmation of the given operation
|
|
||||||
function revoke(bytes32 _operation);
|
|
||||||
|
|
||||||
function changeOwner(address _from, address _to);
|
|
||||||
|
|
||||||
function execute(address _to, uint _value, bytes _data) returns(bool);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contract WalletLibrary is multisig {
|
contract WalletLibrary is WalletEvents {
|
||||||
// TYPES
|
// TYPES
|
||||||
|
|
||||||
// struct for the status of a pending operation.
|
// struct for the status of a pending operation.
|
||||||
@ -77,10 +78,6 @@ contract WalletLibrary is multisig {
|
|||||||
bytes data;
|
bytes data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************
|
|
||||||
***** MULTI OWNED SECTION ****
|
|
||||||
******************************/
|
|
||||||
|
|
||||||
// MODIFIERS
|
// MODIFIERS
|
||||||
|
|
||||||
// simple single-sig function modifier.
|
// simple single-sig function modifier.
|
||||||
@ -98,23 +95,29 @@ contract WalletLibrary is multisig {
|
|||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
|
|
||||||
|
// gets called when no other function matches
|
||||||
|
function() payable {
|
||||||
|
// just being sent some cash?
|
||||||
|
if (msg.value > 0)
|
||||||
|
Deposit(msg.sender, msg.value);
|
||||||
|
}
|
||||||
|
|
||||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
||||||
// as well as the selection of addresses capable of confirming them.
|
// as well as the selection of addresses capable of confirming them.
|
||||||
function initMultiowned(address[] _owners, uint _required) {
|
function initMultiowned(address[] _owners, uint _required) {
|
||||||
m_numOwners = _owners.length + 1;
|
m_numOwners = _owners.length + 1;
|
||||||
m_owners[1] = uint(msg.sender);
|
m_owners[1] = uint(msg.sender);
|
||||||
m_ownerIndex[uint(msg.sender)] = 1;
|
m_ownerIndex[uint(msg.sender)] = 1;
|
||||||
m_required = _required;
|
|
||||||
|
|
||||||
for (uint i = 0; i < _owners.length; ++i)
|
for (uint i = 0; i < _owners.length; ++i)
|
||||||
{
|
{
|
||||||
m_owners[2 + i] = uint(_owners[i]);
|
m_owners[2 + i] = uint(_owners[i]);
|
||||||
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
||||||
}
|
}
|
||||||
|
m_required = _required;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revokes a prior confirmation of the given operation
|
// Revokes a prior confirmation of the given operation
|
||||||
function revoke(bytes32 _operation) {
|
function revoke(bytes32 _operation) external {
|
||||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||||
// make sure they're an owner
|
// make sure they're an owner
|
||||||
if (ownerIndex == 0) return;
|
if (ownerIndex == 0) return;
|
||||||
@ -128,7 +131,7 @@ contract WalletLibrary is multisig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replaces an owner `_from` with another `_to`.
|
// Replaces an owner `_from` with another `_to`.
|
||||||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) {
|
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
|
||||||
if (isOwner(_to)) return;
|
if (isOwner(_to)) return;
|
||||||
uint ownerIndex = m_ownerIndex[uint(_from)];
|
uint ownerIndex = m_ownerIndex[uint(_from)];
|
||||||
if (ownerIndex == 0) return;
|
if (ownerIndex == 0) return;
|
||||||
@ -140,7 +143,7 @@ contract WalletLibrary is multisig {
|
|||||||
OwnerChanged(_from, _to);
|
OwnerChanged(_from, _to);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) {
|
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||||
if (isOwner(_owner)) return;
|
if (isOwner(_owner)) return;
|
||||||
|
|
||||||
clearPending();
|
clearPending();
|
||||||
@ -154,7 +157,7 @@ contract WalletLibrary is multisig {
|
|||||||
OwnerAdded(_owner);
|
OwnerAdded(_owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) {
|
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||||
if (ownerIndex == 0) return;
|
if (ownerIndex == 0) return;
|
||||||
if (m_required > m_numOwners - 1) return;
|
if (m_required > m_numOwners - 1) return;
|
||||||
@ -166,19 +169,23 @@ contract WalletLibrary is multisig {
|
|||||||
OwnerRemoved(_owner);
|
OwnerRemoved(_owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) {
|
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
|
||||||
if (_newRequired > m_numOwners) return;
|
if (_newRequired > m_numOwners) return;
|
||||||
m_required = _newRequired;
|
m_required = _newRequired;
|
||||||
clearPending();
|
clearPending();
|
||||||
RequirementChanged(_newRequired);
|
RequirementChanged(_newRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOwner(address _addr) returns (bool) {
|
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||||
|
function getOwner(uint ownerIndex) external constant returns (address) {
|
||||||
|
return address(m_owners[ownerIndex + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOwner(address _addr) constant returns (bool) {
|
||||||
return m_ownerIndex[uint(_addr)] > 0;
|
return m_ownerIndex[uint(_addr)] > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
|
||||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
|
|
||||||
var pending = m_pending[_operation];
|
var pending = m_pending[_operation];
|
||||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||||
|
|
||||||
@ -190,6 +197,88 @@ contract WalletLibrary is multisig {
|
|||||||
return !(pending.ownersDone & ownerIndexBit == 0);
|
return !(pending.ownersDone & ownerIndexBit == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// constructor - stores initial daily limit and records the present day's index.
|
||||||
|
function initDaylimit(uint _limit) {
|
||||||
|
m_dailyLimit = _limit;
|
||||||
|
m_lastDay = today();
|
||||||
|
}
|
||||||
|
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||||
|
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
|
||||||
|
m_dailyLimit = _newLimit;
|
||||||
|
}
|
||||||
|
// resets the amount already spent today. needs many of the owners to confirm.
|
||||||
|
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
|
||||||
|
m_spentToday = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructor - just pass on the owner array to the multiowned and
|
||||||
|
// the limit to daylimit
|
||||||
|
function initWallet(address[] _owners, uint _required, uint _daylimit) {
|
||||||
|
initDaylimit(_daylimit);
|
||||||
|
initMultiowned(_owners, _required);
|
||||||
|
}
|
||||||
|
|
||||||
|
// kills the contract sending everything to `_to`.
|
||||||
|
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
|
||||||
|
suicide(_to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
|
||||||
|
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
||||||
|
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
||||||
|
// and _data arguments). They still get the option of using them if they want, anyways.
|
||||||
|
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
|
||||||
|
// first, take the opportunity to check that we're under the daily limit.
|
||||||
|
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
|
||||||
|
// yes - just execute the call.
|
||||||
|
address created;
|
||||||
|
if (_to == 0) {
|
||||||
|
created = create(_value, _data);
|
||||||
|
} else {
|
||||||
|
if (!_to.call.value(_value)(_data))
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
SingleTransact(msg.sender, _value, _to, _data, created);
|
||||||
|
} else {
|
||||||
|
// determine our operation hash.
|
||||||
|
o_hash = sha3(msg.data, block.number);
|
||||||
|
// store if it's new
|
||||||
|
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
|
||||||
|
m_txs[o_hash].to = _to;
|
||||||
|
m_txs[o_hash].value = _value;
|
||||||
|
m_txs[o_hash].data = _data;
|
||||||
|
}
|
||||||
|
if (!confirm(o_hash)) {
|
||||||
|
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function create(uint _value, bytes _code) internal returns (address o_addr) {
|
||||||
|
assembly {
|
||||||
|
o_addr := create(_value, add(_code, 0x20), mload(_code))
|
||||||
|
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||||
|
// to determine the body of the transaction from the hash provided.
|
||||||
|
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
|
||||||
|
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
|
||||||
|
address created;
|
||||||
|
if (m_txs[_h].to == 0) {
|
||||||
|
created = create(m_txs[_h].value, m_txs[_h].data);
|
||||||
|
} else {
|
||||||
|
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
|
||||||
|
delete m_txs[_h];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// INTERNAL METHODS
|
// INTERNAL METHODS
|
||||||
|
|
||||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
||||||
@ -244,45 +333,6 @@ contract WalletLibrary is multisig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearPending() internal {
|
|
||||||
uint length = m_pendingIndex.length;
|
|
||||||
for (uint i = 0; i < length; ++i)
|
|
||||||
if (m_pendingIndex[i] != 0)
|
|
||||||
delete m_pending[m_pendingIndex[i]];
|
|
||||||
delete m_pendingIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/******************************
|
|
||||||
****** DAY LIMIT SECTION *****
|
|
||||||
******************************/
|
|
||||||
|
|
||||||
// MODIFIERS
|
|
||||||
|
|
||||||
// simple modifier for daily limit.
|
|
||||||
modifier limitedDaily(uint _value) {
|
|
||||||
if (underLimit(_value))
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// METHODS
|
|
||||||
|
|
||||||
// constructor - stores initial daily limit and records the present day's index.
|
|
||||||
function initDaylimit(uint _limit) {
|
|
||||||
m_dailyLimit = _limit;
|
|
||||||
m_lastDay = today();
|
|
||||||
}
|
|
||||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
|
||||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) {
|
|
||||||
m_dailyLimit = _newLimit;
|
|
||||||
}
|
|
||||||
// resets the amount already spent today. needs many of the owners to confirm.
|
|
||||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) {
|
|
||||||
m_spentToday = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// INTERNAL METHODS
|
|
||||||
|
|
||||||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
||||||
// returns true. otherwise just returns false.
|
// returns true. otherwise just returns false.
|
||||||
function underLimit(uint _value) internal onlyowner returns (bool) {
|
function underLimit(uint _value) internal onlyowner returns (bool) {
|
||||||
@ -303,74 +353,26 @@ contract WalletLibrary is multisig {
|
|||||||
// determines today's index.
|
// determines today's index.
|
||||||
function today() private constant returns (uint) { return now / 1 days; }
|
function today() private constant returns (uint) { return now / 1 days; }
|
||||||
|
|
||||||
|
function clearPending() internal {
|
||||||
/******************************
|
|
||||||
********* WALLET SECTION *****
|
|
||||||
******************************/
|
|
||||||
|
|
||||||
// METHODS
|
|
||||||
|
|
||||||
// constructor - just pass on the owner array to the multiowned and
|
|
||||||
// the limit to daylimit
|
|
||||||
function initWallet(address[] _owners, uint _required, uint _daylimit) {
|
|
||||||
initMultiowned(_owners, _required);
|
|
||||||
initDaylimit(_daylimit) ;
|
|
||||||
}
|
|
||||||
|
|
||||||
// kills the contract sending everything to `_to`.
|
|
||||||
function kill(address _to) onlymanyowners(sha3(msg.data)) {
|
|
||||||
suicide(_to);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
|
|
||||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
|
||||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
|
||||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
|
||||||
function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) {
|
|
||||||
// first, take the opportunity to check that we're under the daily limit.
|
|
||||||
if (underLimit(_value)) {
|
|
||||||
SingleTransact(msg.sender, _value, _to, _data);
|
|
||||||
// yes - just execute the call.
|
|
||||||
_callValue =_to.call.value(_value)(_data);
|
|
||||||
} else {
|
|
||||||
// determine our operation hash.
|
|
||||||
bytes32 _r = sha3(msg.data, block.number);
|
|
||||||
if (!confirm(_r) && m_txs[_r].to == 0) {
|
|
||||||
m_txs[_r].to = _to;
|
|
||||||
m_txs[_r].value = _value;
|
|
||||||
m_txs[_r].data = _data;
|
|
||||||
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
|
||||||
// to determine the body of the transaction from the hash provided.
|
|
||||||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
|
|
||||||
if (m_txs[_h].to != 0) {
|
|
||||||
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
|
|
||||||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
|
|
||||||
delete m_txs[_h];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// INTERNAL METHODS
|
|
||||||
|
|
||||||
function clearWalletPending() internal {
|
|
||||||
uint length = m_pendingIndex.length;
|
uint length = m_pendingIndex.length;
|
||||||
for (uint i = 0; i < length; ++i)
|
|
||||||
|
for (uint i = 0; i < length; ++i) {
|
||||||
delete m_txs[m_pendingIndex[i]];
|
delete m_txs[m_pendingIndex[i]];
|
||||||
clearPending();
|
|
||||||
|
if (m_pendingIndex[i] != 0)
|
||||||
|
delete m_pending[m_pendingIndex[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
delete m_pendingIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIELDS
|
// FIELDS
|
||||||
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
||||||
|
|
||||||
// the number of owners that must confirm the same operation before it is run.
|
// the number of owners that must confirm the same operation before it is run.
|
||||||
uint m_required;
|
uint public m_required;
|
||||||
// pointer used to find a free slot in m_owners
|
// pointer used to find a free slot in m_owners
|
||||||
uint m_numOwners;
|
uint public m_numOwners;
|
||||||
|
|
||||||
uint public m_dailyLimit;
|
uint public m_dailyLimit;
|
||||||
uint public m_spentToday;
|
uint public m_spentToday;
|
||||||
@ -378,8 +380,8 @@ contract WalletLibrary is multisig {
|
|||||||
|
|
||||||
// list of owners
|
// list of owners
|
||||||
uint[256] m_owners;
|
uint[256] m_owners;
|
||||||
uint constant c_maxOwners = 250;
|
|
||||||
|
|
||||||
|
uint constant c_maxOwners = 250;
|
||||||
// index on the list of owners to allow reverse lookup
|
// index on the list of owners to allow reverse lookup
|
||||||
mapping(uint => uint) m_ownerIndex;
|
mapping(uint => uint) m_ownerIndex;
|
||||||
// the ongoing operations.
|
// the ongoing operations.
|
||||||
@ -390,8 +392,7 @@ contract WalletLibrary is multisig {
|
|||||||
mapping (bytes32 => Transaction) m_txs;
|
mapping (bytes32 => Transaction) m_txs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contract Wallet is WalletEvents {
|
||||||
contract Wallet is multisig {
|
|
||||||
|
|
||||||
// WALLET CONSTRUCTOR
|
// WALLET CONSTRUCTOR
|
||||||
// calls the `initWallet` method of the Library in this context
|
// calls the `initWallet` method of the Library in this context
|
||||||
@ -435,11 +436,11 @@ contract Wallet is multisig {
|
|||||||
|
|
||||||
// As return statement unavailable in fallback, explicit the method here
|
// As return statement unavailable in fallback, explicit the method here
|
||||||
|
|
||||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
|
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
|
||||||
return _walletLibrary.delegatecall(msg.data);
|
return _walletLibrary.delegatecall(msg.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOwner(address _addr) returns (bool) {
|
function isOwner(address _addr) constant returns (bool) {
|
||||||
return _walletLibrary.delegatecall(msg.data);
|
return _walletLibrary.delegatecall(msg.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
|
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
|
||||||
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
||||||
// interior is executed.
|
// interior is executed.
|
||||||
pragma solidity ^0.4.6;
|
|
||||||
|
pragma solidity ^0.4.9;
|
||||||
|
|
||||||
contract multiowned {
|
contract multiowned {
|
||||||
|
|
||||||
@ -130,7 +131,7 @@ contract multiowned {
|
|||||||
return address(m_owners[ownerIndex + 1]);
|
return address(m_owners[ownerIndex + 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOwner(address _addr) returns (bool) {
|
function isOwner(address _addr) constant returns (bool) {
|
||||||
return m_ownerIndex[uint(_addr)] > 0;
|
return m_ownerIndex[uint(_addr)] > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,14 +231,6 @@ contract multiowned {
|
|||||||
// uses is specified in the modifier.
|
// uses is specified in the modifier.
|
||||||
contract daylimit is multiowned {
|
contract daylimit is multiowned {
|
||||||
|
|
||||||
// MODIFIERS
|
|
||||||
|
|
||||||
// simple modifier for daily limit.
|
|
||||||
modifier limitedDaily(uint _value) {
|
|
||||||
if (underLimit(_value))
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
|
|
||||||
// constructor - stores initial daily limit and records the present day's index.
|
// constructor - stores initial daily limit and records the present day's index.
|
||||||
@ -291,18 +284,17 @@ contract multisig {
|
|||||||
// Funds has arrived into the wallet (record how much).
|
// Funds has arrived into the wallet (record how much).
|
||||||
event Deposit(address _from, uint value);
|
event Deposit(address _from, uint value);
|
||||||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||||
event SingleTransact(address owner, uint value, address to, bytes data);
|
event SingleTransact(address owner, uint value, address to, bytes data, address created);
|
||||||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
||||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);
|
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
|
||||||
// Confirmation still needed for a transaction.
|
// Confirmation still needed for a transaction.
|
||||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||||
|
|
||||||
// FUNCTIONS
|
// FUNCTIONS
|
||||||
|
|
||||||
// TODO: document
|
// TODO: document
|
||||||
function changeOwner(address _from, address _to) external;
|
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
|
||||||
function execute(address _to, uint _value, bytes _data) external returns (bytes32);
|
function confirm(bytes32 _h) external returns (bool o_success);
|
||||||
function confirm(bytes32 _h) returns (bool);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// usage:
|
// usage:
|
||||||
@ -343,30 +335,53 @@ contract Wallet is multisig, multiowned, daylimit {
|
|||||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
||||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
||||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
// and _data arguments). They still get the option of using them if they want, anyways.
|
||||||
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) {
|
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
|
||||||
// first, take the opportunity to check that we're under the daily limit.
|
// first, take the opportunity to check that we're under the daily limit.
|
||||||
if (underLimit(_value)) {
|
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
|
||||||
SingleTransact(msg.sender, _value, _to, _data);
|
|
||||||
// yes - just execute the call.
|
// yes - just execute the call.
|
||||||
_to.call.value(_value)(_data);
|
address created;
|
||||||
return 0;
|
if (_to == 0) {
|
||||||
|
created = create(_value, _data);
|
||||||
|
} else {
|
||||||
|
if (!_to.call.value(_value)(_data))
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
SingleTransact(msg.sender, _value, _to, _data, created);
|
||||||
|
} else {
|
||||||
// determine our operation hash.
|
// determine our operation hash.
|
||||||
_r = sha3(msg.data, block.number);
|
o_hash = sha3(msg.data, block.number);
|
||||||
if (!confirm(_r) && m_txs[_r].to == 0) {
|
// store if it's new
|
||||||
m_txs[_r].to = _to;
|
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
|
||||||
m_txs[_r].value = _value;
|
m_txs[o_hash].to = _to;
|
||||||
m_txs[_r].data = _data;
|
m_txs[o_hash].value = _value;
|
||||||
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
|
m_txs[o_hash].data = _data;
|
||||||
|
}
|
||||||
|
if (!confirm(o_hash)) {
|
||||||
|
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function create(uint _value, bytes _code) internal returns (address o_addr) {
|
||||||
|
assembly {
|
||||||
|
o_addr := create(_value, add(_code, 0x20), mload(_code))
|
||||||
|
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||||
// to determine the body of the transaction from the hash provided.
|
// to determine the body of the transaction from the hash provided.
|
||||||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
|
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
|
||||||
if (m_txs[_h].to != 0) {
|
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
|
||||||
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
|
address created;
|
||||||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
|
if (m_txs[_h].to == 0) {
|
||||||
|
created = create(m_txs[_h].value, m_txs[_h].data);
|
||||||
|
} else {
|
||||||
|
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
|
||||||
delete m_txs[_h];
|
delete m_txs[_h];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -102,3 +102,4 @@ if (module.hot) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { omitBy } from 'lodash';
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
@ -106,9 +105,6 @@ export default class WalletDetails extends Component {
|
|||||||
renderMultisigDetails () {
|
renderMultisigDetails () {
|
||||||
const { accounts, wallet, errors } = this.props;
|
const { accounts, wallet, errors } = this.props;
|
||||||
|
|
||||||
// Wallets cannot create contracts
|
|
||||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<Input
|
<Input
|
||||||
@ -148,7 +144,7 @@ export default class WalletDetails extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<AddressSelect
|
<AddressSelect
|
||||||
accounts={ _accounts }
|
accounts={ accounts }
|
||||||
error={ errors.account }
|
error={ errors.account }
|
||||||
hint={
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -22,10 +22,11 @@ import Contract from '~/api/contract';
|
|||||||
import { ERROR_CODES } from '~/api/transport/error';
|
import { ERROR_CODES } from '~/api/transport/error';
|
||||||
import Contracts from '~/contracts';
|
import Contracts from '~/contracts';
|
||||||
import { wallet as walletAbi } from '~/contracts/abi';
|
import { wallet as walletAbi } from '~/contracts/abi';
|
||||||
import { wallet as walletCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';
|
import { wallet as walletCode, walletLibrary as walletLibraryCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';
|
||||||
|
|
||||||
import { validateUint, validateAddress, validateName } from '~/util/validation';
|
import { validateUint, validateAddress, validateName } from '~/util/validation';
|
||||||
import { toWei } from '~/api/util/wei';
|
import { toWei } from '~/api/util/wei';
|
||||||
|
import { deploy } from '~/util/tx';
|
||||||
import WalletsUtils from '~/util/wallets';
|
import WalletsUtils from '~/util/wallets';
|
||||||
|
|
||||||
const STEPS = {
|
const STEPS = {
|
||||||
@ -179,6 +180,8 @@ export default class CreateWalletStore {
|
|||||||
this.wallet.owners = owners;
|
this.wallet.owners = owners;
|
||||||
this.wallet.required = require.toNumber();
|
this.wallet.required = require.toNumber();
|
||||||
this.wallet.dailylimit = dailylimit.limit;
|
this.wallet.dailylimit = dailylimit.limit;
|
||||||
|
|
||||||
|
this.wallet = this.getWalletWithMeta(this.wallet);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.addWallet(this.wallet);
|
return this.addWallet(this.wallet);
|
||||||
@ -202,21 +205,51 @@ export default class CreateWalletStore {
|
|||||||
return null; // exception when registry is not available
|
return null; // exception when registry is not available
|
||||||
})
|
})
|
||||||
.then((address) => {
|
.then((address) => {
|
||||||
const walletLibraryAddress = (address || '').replace(/^0x/, '').toLowerCase();
|
if (!address || /^(0x)?0*$/.test(address)) {
|
||||||
const code = walletLibraryAddress.length && !/^0+$/.test(walletLibraryAddress)
|
return null;
|
||||||
? walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress)
|
}
|
||||||
: fullWalletCode;
|
|
||||||
|
// Check that it's actually the expected code
|
||||||
|
return this.api.eth
|
||||||
|
.getCode(address)
|
||||||
|
.then((code) => {
|
||||||
|
const strippedCode = code.replace(/^0x/, '');
|
||||||
|
|
||||||
|
// The actual deployed code is included in the wallet
|
||||||
|
// library code (which might have some more data)
|
||||||
|
if (walletLibraryCode.indexOf(strippedCode) >= 0) {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((address) => {
|
||||||
|
let code = fullWalletCode;
|
||||||
|
|
||||||
|
if (address) {
|
||||||
|
const walletLibraryAddress = address.replace(/^0x/, '').toLowerCase();
|
||||||
|
|
||||||
|
code = walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress);
|
||||||
|
} else {
|
||||||
|
console.warn('wallet library has not been found in the registry');
|
||||||
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
data: code,
|
data: code,
|
||||||
from: account
|
from: account
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.api
|
const contract = this.api.newContract(walletAbi);
|
||||||
.newContract(walletAbi)
|
|
||||||
.deploy(options, [ owners, required, daylimit ], this.onDeploymentState);
|
this.wallet = this.getWalletWithMeta(this.wallet);
|
||||||
|
return deploy(contract, options, [ owners, required, daylimit ], this.wallet.metadata, this.onDeploymentState);
|
||||||
})
|
})
|
||||||
.then((address) => {
|
.then((address) => {
|
||||||
|
if (!address || /^(0x)?0*$/.test(address)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
this.deployed = true;
|
this.deployed = true;
|
||||||
this.wallet.address = address;
|
this.wallet.address = address;
|
||||||
return this.addWallet(this.wallet);
|
return this.addWallet(this.wallet);
|
||||||
@ -233,26 +266,37 @@ export default class CreateWalletStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action addWallet = (wallet) => {
|
@action addWallet = (wallet) => {
|
||||||
const { address, name, description } = wallet;
|
const { address, name, metadata } = wallet;
|
||||||
|
|
||||||
return Promise
|
return Promise
|
||||||
.all([
|
.all([
|
||||||
this.api.parity.setAccountName(address, name),
|
this.api.parity.setAccountName(address, name),
|
||||||
this.api.parity.setAccountMeta(address, {
|
this.api.parity.setAccountMeta(address, metadata)
|
||||||
abi: walletAbi,
|
|
||||||
wallet: true,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
deleted: false,
|
|
||||||
description,
|
|
||||||
name,
|
|
||||||
tags: ['wallet']
|
|
||||||
})
|
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.step = 'INFO';
|
this.step = 'INFO';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWalletWithMeta = (wallet) => {
|
||||||
|
const { name, description } = wallet;
|
||||||
|
|
||||||
|
const metadata = {
|
||||||
|
abi: walletAbi,
|
||||||
|
wallet: true,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
deleted: false,
|
||||||
|
tags: [ 'wallet' ],
|
||||||
|
description,
|
||||||
|
name
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...wallet,
|
||||||
|
metadata
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
onDeploymentState = (error, data) => {
|
onDeploymentState = (error, data) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return console.error('createWallet::onDeploymentState', error);
|
return console.error('createWallet::onDeploymentState', error);
|
||||||
@ -298,6 +342,15 @@ export default class CreateWalletStore {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case 'confirmationNeeded':
|
||||||
|
this.deployState = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='createWallet.states.confirmationNeeded'
|
||||||
|
defaultMessage='The contract deployment needs confirmations from other owners of the Wallet'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
|
||||||
case 'completed':
|
case 'completed':
|
||||||
this.deployState = (
|
this.deployState = (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { pick, omitBy } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
@ -23,6 +23,7 @@ import { connect } from 'react-redux';
|
|||||||
import { BusyStep, Button, CompletedStep, CopyToClipboard, GasPriceEditor, IdentityIcon, Portal, TxHash, Warning } from '~/ui';
|
import { BusyStep, Button, CompletedStep, CopyToClipboard, GasPriceEditor, IdentityIcon, Portal, TxHash, Warning } from '~/ui';
|
||||||
import { CancelIcon, DoneIcon } from '~/ui/Icons';
|
import { CancelIcon, DoneIcon } from '~/ui/Icons';
|
||||||
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
|
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
|
||||||
|
import { deploy, deployEstimateGas } from '~/util/tx';
|
||||||
|
|
||||||
import DetailsStep from './DetailsStep';
|
import DetailsStep from './DetailsStep';
|
||||||
import ParametersStep from './ParametersStep';
|
import ParametersStep from './ParametersStep';
|
||||||
@ -73,7 +74,7 @@ class DeployContract extends Component {
|
|||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired,
|
api: PropTypes.object.isRequired,
|
||||||
store: PropTypes.object.isRequired
|
store: PropTypes.object.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
@ -422,9 +423,9 @@ class DeployContract extends Component {
|
|||||||
from: fromAddress
|
from: fromAddress
|
||||||
};
|
};
|
||||||
|
|
||||||
api
|
const contract = api.newContract(abiParsed);
|
||||||
.newContract(abiParsed)
|
|
||||||
.deployEstimateGas(options, params)
|
deployEstimateGas(contract, options, params)
|
||||||
.then(([gasEst, gas]) => {
|
.then(([gasEst, gas]) => {
|
||||||
this.gasStore.setEstimated(gasEst.toFixed(0));
|
this.gasStore.setEstimated(gasEst.toFixed(0));
|
||||||
this.gasStore.setGas(gas.toFixed(0));
|
this.gasStore.setGas(gas.toFixed(0));
|
||||||
@ -490,6 +491,17 @@ class DeployContract extends Component {
|
|||||||
const { api, store } = this.context;
|
const { api, store } = this.context;
|
||||||
const { source } = this.props;
|
const { source } = this.props;
|
||||||
const { abiParsed, code, description, name, params, fromAddress } = this.state;
|
const { abiParsed, code, description, name, params, fromAddress } = this.state;
|
||||||
|
|
||||||
|
const metadata = {
|
||||||
|
abi: abiParsed,
|
||||||
|
contract: true,
|
||||||
|
deleted: false,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
source
|
||||||
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
data: code,
|
data: code,
|
||||||
from: fromAddress
|
from: fromAddress
|
||||||
@ -499,28 +511,25 @@ class DeployContract extends Component {
|
|||||||
|
|
||||||
const contract = api.newContract(abiParsed);
|
const contract = api.newContract(abiParsed);
|
||||||
|
|
||||||
contract
|
deploy(contract, options, params, metadata, this.onDeploymentState)
|
||||||
.deploy(options, params, this.onDeploymentState)
|
|
||||||
.then((address) => {
|
.then((address) => {
|
||||||
const blockNumber = contract._receipt
|
// No contract address given, might need some confirmations
|
||||||
|
// from the wallet owners...
|
||||||
|
if (!address || /^(0x)?0*$/.test(address)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.blockNumber = contract._receipt
|
||||||
? contract.receipt.blockNumber.toNumber()
|
? contract.receipt.blockNumber.toNumber()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
api.parity.setAccountName(address, name),
|
api.parity.setAccountName(address, name),
|
||||||
api.parity.setAccountMeta(address, {
|
api.parity.setAccountMeta(address, metadata)
|
||||||
abi: abiParsed,
|
|
||||||
contract: true,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
deleted: false,
|
|
||||||
blockNumber,
|
|
||||||
description,
|
|
||||||
source
|
|
||||||
})
|
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(`contract deployed at ${address}`);
|
console.log(`contract deployed at ${address}`);
|
||||||
this.setState({ step: 'DEPLOYMENT', address });
|
this.setState({ step: 'COMPLETED', address });
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -589,6 +598,17 @@ class DeployContract extends Component {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case 'confirmationNeeded':
|
||||||
|
this.setState({
|
||||||
|
deployState: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='deployContract.state.confirmationNeeded'
|
||||||
|
defaultMessage='The operation needs confirmations from the other owners of the contract'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
|
||||||
case 'completed':
|
case 'completed':
|
||||||
this.setState({
|
this.setState({
|
||||||
deployState: (
|
deployState: (
|
||||||
@ -614,17 +634,14 @@ class DeployContract extends Component {
|
|||||||
function mapStateToProps (initState, initProps) {
|
function mapStateToProps (initState, initProps) {
|
||||||
const { accounts } = initProps;
|
const { accounts } = initProps;
|
||||||
|
|
||||||
// Skip Wallet accounts : they can't create Contracts
|
const fromAddresses = Object.keys(accounts);
|
||||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
|
||||||
|
|
||||||
const fromAddresses = Object.keys(_accounts);
|
|
||||||
|
|
||||||
return (state) => {
|
return (state) => {
|
||||||
const balances = pick(state.balances.balances, fromAddresses);
|
const balances = pick(state.balances.balances, fromAddresses);
|
||||||
const { gasLimit } = state.nodeStatus;
|
const { gasLimit } = state.nodeStatus;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts: _accounts,
|
accounts,
|
||||||
balances,
|
balances,
|
||||||
gasLimit
|
gasLimit
|
||||||
};
|
};
|
||||||
|
162
js/src/modals/Faucet/faucet.js
Normal file
162
js/src/modals/Faucet/faucet.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { txLink } from '~/3rdparty/etherscan/links';
|
||||||
|
import { Button, ModalBox, Portal, ShortenedHash } from '~/ui';
|
||||||
|
import { CloseIcon, DialIcon, DoneIcon, ErrorIcon, SendIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
|
import Store from './store';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class Faucet extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
address: PropTypes.string.isRequired,
|
||||||
|
netVersion: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
store = new Store(this.props.netVersion, this.props.address);
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { error, isBusy, isCompleted } = this.store;
|
||||||
|
|
||||||
|
let icon = <DialIcon />;
|
||||||
|
|
||||||
|
if (isCompleted) {
|
||||||
|
icon = error
|
||||||
|
? <ErrorIcon />
|
||||||
|
: <DoneIcon />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal
|
||||||
|
buttons={ this.renderActions() }
|
||||||
|
busy={ isBusy }
|
||||||
|
isSmallModal
|
||||||
|
onClose={ this.onClose }
|
||||||
|
open
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.title'
|
||||||
|
defaultMessage='Kovan ETH Faucet'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ModalBox
|
||||||
|
icon={ icon }
|
||||||
|
summary={
|
||||||
|
isCompleted
|
||||||
|
? this.renderSummaryDone()
|
||||||
|
: this.renderSummaryRequest()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderActions = () => {
|
||||||
|
const { canTransact, isBusy, isCompleted } = this.store;
|
||||||
|
|
||||||
|
return isCompleted || isBusy
|
||||||
|
? (
|
||||||
|
<Button
|
||||||
|
disabled={ isBusy }
|
||||||
|
icon={ <DoneIcon /> }
|
||||||
|
key='done'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.buttons.done'
|
||||||
|
defaultMessage='close'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onClose }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: [
|
||||||
|
<Button
|
||||||
|
icon={ <CloseIcon /> }
|
||||||
|
key='close'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.buttons.close'
|
||||||
|
defaultMessage='close'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onClose }
|
||||||
|
/>,
|
||||||
|
<Button
|
||||||
|
disabled={ !canTransact }
|
||||||
|
icon={ <SendIcon /> }
|
||||||
|
key='request'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.buttons.request'
|
||||||
|
defaultMessage='request'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.onExecute }
|
||||||
|
/>
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSummaryDone () {
|
||||||
|
const { error, responseText, responseTxHash } = this.store;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.summary.done'
|
||||||
|
defaultMessage='Your Kovan ETH has been requested from the faucet which responded with -'
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
error
|
||||||
|
? (
|
||||||
|
<p>{ error }</p>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<p>
|
||||||
|
<span>{ responseText } </span>
|
||||||
|
<a href={ txLink(responseTxHash, false, '42') } target='_blank'>
|
||||||
|
<ShortenedHash data={ responseTxHash } />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSummaryRequest () {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='faucet.summary.info'
|
||||||
|
defaultMessage='To request a deposit of Kovan ETH to this address, you need to ensure that the address is sms-verified on the mainnet. Once executed the faucet will deposit Kovan ETH into the current account.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
onExecute = () => {
|
||||||
|
return this.store.makeItRain();
|
||||||
|
}
|
||||||
|
}
|
17
js/src/modals/Faucet/index.js
Normal file
17
js/src/modals/Faucet/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './faucet';
|
126
js/src/modals/Faucet/store.js
Normal file
126
js/src/modals/Faucet/store.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { action, computed, observable, transaction } from 'mobx';
|
||||||
|
import apiutil from '~/api/util';
|
||||||
|
|
||||||
|
const ENDPOINT = 'http://faucet.kovan.network/api/';
|
||||||
|
|
||||||
|
export default class Store {
|
||||||
|
@observable addressReceive = null;
|
||||||
|
@observable addressVerified = null;
|
||||||
|
@observable error = null;
|
||||||
|
@observable responseText = null;
|
||||||
|
@observable responseTxHash = null;
|
||||||
|
@observable isBusy = false;
|
||||||
|
@observable isCompleted = false;
|
||||||
|
@observable isDestination = false;
|
||||||
|
@observable isDone = false;
|
||||||
|
|
||||||
|
constructor (netVersion, address) {
|
||||||
|
transaction(() => {
|
||||||
|
this.setDestination(netVersion === '42');
|
||||||
|
|
||||||
|
this.setAddressReceive(address);
|
||||||
|
this.setAddressVerified(address);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get canTransact () {
|
||||||
|
return !this.isBusy && this.addressReceiveValid && this.addressVerifiedValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get addressReceiveValid () {
|
||||||
|
return apiutil.isAddressValid(this.addressReceive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get addressVerifiedValid () {
|
||||||
|
return apiutil.isAddressValid(this.addressVerified);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setAddressReceive = (address) => {
|
||||||
|
this.addressReceive = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setAddressVerified = (address) => {
|
||||||
|
this.addressVerified = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setBusy = (isBusy) => {
|
||||||
|
this.isBusy = isBusy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setCompleted = (isCompleted) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.setBusy(false);
|
||||||
|
this.isCompleted = isCompleted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setDestination = (isDestination) => {
|
||||||
|
this.isDestination = isDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setError = (error) => {
|
||||||
|
if (error.indexOf('not certified') !== -1) {
|
||||||
|
this.error = `${error}. Please ensure that this account is sms certified on the mainnet.`;
|
||||||
|
} else {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setResponse = (response) => {
|
||||||
|
this.responseText = response.result;
|
||||||
|
this.responseTxHash = response.tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeItRain = () => {
|
||||||
|
this.setBusy(true);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: 'GET',
|
||||||
|
mode: 'cors'
|
||||||
|
};
|
||||||
|
const url = `${ENDPOINT}${this.addressVerified}`;
|
||||||
|
|
||||||
|
return fetch(url, options)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
transaction(() => {
|
||||||
|
if (!response || response.error) {
|
||||||
|
this.setError(
|
||||||
|
response
|
||||||
|
? response.error
|
||||||
|
: 'Unable to complete request to the faucet, the server may be unavailable. Please try again later.'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.setResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setCompleted(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -401,6 +401,7 @@ class WalletSettings extends Component {
|
|||||||
const cancelBtn = (
|
const cancelBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <CancelIcon /> }
|
icon={ <CancelIcon /> }
|
||||||
|
key='cancelBtn'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='walletSettings.buttons.cancel'
|
id='walletSettings.buttons.cancel'
|
||||||
@ -414,6 +415,7 @@ class WalletSettings extends Component {
|
|||||||
const closeBtn = (
|
const closeBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <CancelIcon /> }
|
icon={ <CancelIcon /> }
|
||||||
|
key='closeBtn'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='walletSettings.buttons.close'
|
id='walletSettings.buttons.close'
|
||||||
@ -427,6 +429,7 @@ class WalletSettings extends Component {
|
|||||||
const sendingBtn = (
|
const sendingBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <DoneIcon /> }
|
icon={ <DoneIcon /> }
|
||||||
|
key='sendingBtn'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='walletSettings.buttons.sending'
|
id='walletSettings.buttons.sending'
|
||||||
@ -440,6 +443,7 @@ class WalletSettings extends Component {
|
|||||||
const nextBtn = (
|
const nextBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <NextIcon /> }
|
icon={ <NextIcon /> }
|
||||||
|
key='nextBtn'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='walletSettings.buttons.next'
|
id='walletSettings.buttons.next'
|
||||||
@ -454,6 +458,7 @@ class WalletSettings extends Component {
|
|||||||
const sendBtn = (
|
const sendBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <NextIcon /> }
|
icon={ <NextIcon /> }
|
||||||
|
key='sendBtn'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='walletSettings.buttons.send'
|
id='walletSettings.buttons.send'
|
||||||
|
@ -24,6 +24,7 @@ export DeleteAccount from './DeleteAccount';
|
|||||||
export DeployContract from './DeployContract';
|
export DeployContract from './DeployContract';
|
||||||
export EditMeta from './EditMeta';
|
export EditMeta from './EditMeta';
|
||||||
export ExecuteContract from './ExecuteContract';
|
export ExecuteContract from './ExecuteContract';
|
||||||
|
export Faucet from './Faucet';
|
||||||
export FirstRun from './FirstRun';
|
export FirstRun from './FirstRun';
|
||||||
export LoadContract from './LoadContract';
|
export LoadContract from './LoadContract';
|
||||||
export PasswordManager from './PasswordManager';
|
export PasswordManager from './PasswordManager';
|
||||||
|
@ -49,7 +49,7 @@ export default class Personal {
|
|||||||
.filter((address) => {
|
.filter((address) => {
|
||||||
const account = accountsInfo[address];
|
const account = accountsInfo[address];
|
||||||
|
|
||||||
return !account.uuid && account.meta.deleted;
|
return !account.uuid && account.meta && account.meta.deleted;
|
||||||
})
|
})
|
||||||
.map((address) => this._api.parity.removeAddress(address))
|
.map((address) => this._api.parity.removeAddress(address))
|
||||||
);
|
);
|
||||||
|
@ -26,7 +26,6 @@ import WalletsUtils from '~/util/wallets';
|
|||||||
import { wallet as WalletAbi } from '~/contracts/abi';
|
import { wallet as WalletAbi } from '~/contracts/abi';
|
||||||
|
|
||||||
export function personalAccountsInfo (accountsInfo) {
|
export function personalAccountsInfo (accountsInfo) {
|
||||||
const addresses = [];
|
|
||||||
const accounts = {};
|
const accounts = {};
|
||||||
const contacts = {};
|
const contacts = {};
|
||||||
const contracts = {};
|
const contracts = {};
|
||||||
@ -35,10 +34,9 @@ export function personalAccountsInfo (accountsInfo) {
|
|||||||
|
|
||||||
Object.keys(accountsInfo || {})
|
Object.keys(accountsInfo || {})
|
||||||
.map((address) => Object.assign({}, accountsInfo[address], { address }))
|
.map((address) => Object.assign({}, accountsInfo[address], { address }))
|
||||||
.filter((account) => account.uuid || !account.meta.deleted)
|
.filter((account) => account.meta && (account.uuid || !account.meta.deleted))
|
||||||
.forEach((account) => {
|
.forEach((account) => {
|
||||||
if (account.uuid) {
|
if (account.uuid) {
|
||||||
addresses.push(account.address);
|
|
||||||
accounts[account.address] = account;
|
accounts[account.address] = account;
|
||||||
} else if (account.meta.wallet) {
|
} else if (account.meta.wallet) {
|
||||||
account.wallet = true;
|
account.wallet = true;
|
||||||
@ -87,18 +85,45 @@ export function personalAccountsInfo (accountsInfo) {
|
|||||||
return [];
|
return [];
|
||||||
})
|
})
|
||||||
.then((_wallets) => {
|
.then((_wallets) => {
|
||||||
_wallets.forEach((wallet) => {
|
// We want to separate owned wallets and other wallets
|
||||||
|
// However, wallets can be owned by wallets, that can
|
||||||
|
// be owned by an account...
|
||||||
|
let otherWallets = [].concat(_wallets);
|
||||||
|
let prevLength;
|
||||||
|
let nextLength;
|
||||||
|
|
||||||
|
// If no more other wallets, or if the size decreased, continue...
|
||||||
|
do {
|
||||||
|
prevLength = otherWallets.length;
|
||||||
|
|
||||||
|
otherWallets = otherWallets
|
||||||
|
.map((wallet) => {
|
||||||
|
const addresses = Object.keys(accounts);
|
||||||
const owners = wallet.owners.map((o) => o.address);
|
const owners = wallet.owners.map((o) => o.address);
|
||||||
|
|
||||||
// Owners ∩ Addresses not null : Wallet is owned
|
// Owners ∩ Addresses not null : Wallet is owned
|
||||||
// by one of the accounts
|
// by one of the accounts
|
||||||
if (intersection(owners, addresses).length > 0) {
|
if (intersection(owners, addresses).length > 0) {
|
||||||
accounts[wallet.address] = wallet;
|
accounts[wallet.address] = wallet;
|
||||||
} else {
|
return false;
|
||||||
contacts[wallet.address] = wallet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
})
|
||||||
|
.filter((wallet) => wallet);
|
||||||
|
|
||||||
|
nextLength = otherWallets.length;
|
||||||
|
} while (nextLength < prevLength);
|
||||||
|
|
||||||
|
// And other wallets to contacts...
|
||||||
|
otherWallets.forEach((wallet) => {
|
||||||
|
contacts[wallet.address] = wallet;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cache the _real_ accounts for
|
||||||
|
// WalletsUtils (used for sending transactions)
|
||||||
|
WalletsUtils.cacheAccounts(accounts);
|
||||||
|
|
||||||
dispatch(_personalAccountsInfo({
|
dispatch(_personalAccountsInfo({
|
||||||
accountsInfo,
|
accountsInfo,
|
||||||
accounts,
|
accounts,
|
||||||
|
@ -431,22 +431,7 @@ function parseLogs (logs) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { wallet } = getState();
|
const WalletSignatures = WalletsUtils.getWalletSignatures();
|
||||||
const { contract } = wallet;
|
|
||||||
const walletInstance = contract.instance;
|
|
||||||
|
|
||||||
const signatures = {
|
|
||||||
OwnerChanged: toHex(walletInstance.OwnerChanged.signature),
|
|
||||||
OwnerAdded: toHex(walletInstance.OwnerAdded.signature),
|
|
||||||
OwnerRemoved: toHex(walletInstance.OwnerRemoved.signature),
|
|
||||||
RequirementChanged: toHex(walletInstance.RequirementChanged.signature),
|
|
||||||
Confirmation: toHex(walletInstance.Confirmation.signature),
|
|
||||||
Revoke: toHex(walletInstance.Revoke.signature),
|
|
||||||
Deposit: toHex(walletInstance.Deposit.signature),
|
|
||||||
SingleTransact: toHex(walletInstance.SingleTransact.signature),
|
|
||||||
MultiTransact: toHex(walletInstance.MultiTransact.signature),
|
|
||||||
ConfirmationNeeded: toHex(walletInstance.ConfirmationNeeded.signature)
|
|
||||||
};
|
|
||||||
|
|
||||||
const updates = {};
|
const updates = {};
|
||||||
|
|
||||||
@ -459,25 +444,25 @@ function parseLogs (logs) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
switch (eventSignature) {
|
switch (eventSignature) {
|
||||||
case signatures.OwnerChanged:
|
case WalletSignatures.OwnerChanged:
|
||||||
case signatures.OwnerAdded:
|
case WalletSignatures.OwnerAdded:
|
||||||
case signatures.OwnerRemoved:
|
case WalletSignatures.OwnerRemoved:
|
||||||
updates[address] = {
|
updates[address] = {
|
||||||
...prev,
|
...prev,
|
||||||
[ UPDATE_OWNERS ]: true
|
[ UPDATE_OWNERS ]: true
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case signatures.RequirementChanged:
|
case WalletSignatures.RequirementChanged:
|
||||||
updates[address] = {
|
updates[address] = {
|
||||||
...prev,
|
...prev,
|
||||||
[ UPDATE_REQUIRE ]: true
|
[ UPDATE_REQUIRE ]: true
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case signatures.ConfirmationNeeded:
|
case WalletSignatures.ConfirmationNeeded:
|
||||||
case signatures.Confirmation:
|
case WalletSignatures.Confirmation:
|
||||||
case signatures.Revoke:
|
case WalletSignatures.Revoke:
|
||||||
const operation = bytesToHex(log.params.operation.value);
|
const operation = bytesToHex(log.params.operation.value);
|
||||||
|
|
||||||
updates[address] = {
|
updates[address] = {
|
||||||
@ -489,9 +474,11 @@ function parseLogs (logs) {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case signatures.Deposit:
|
case WalletSignatures.Deposit:
|
||||||
case signatures.SingleTransact:
|
case WalletSignatures.SingleTransact:
|
||||||
case signatures.MultiTransact:
|
case WalletSignatures.MultiTransact:
|
||||||
|
case WalletSignatures.Old.SingleTransact:
|
||||||
|
case WalletSignatures.Old.MultiTransact:
|
||||||
updates[address] = {
|
updates[address] = {
|
||||||
...prev,
|
...prev,
|
||||||
[ UPDATE_TRANSACTIONS ]: true
|
[ UPDATE_TRANSACTIONS ]: true
|
||||||
|
@ -110,12 +110,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
.address {
|
|
||||||
&:hover {
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.accountName {
|
.accountName {
|
||||||
|
@ -28,15 +28,15 @@ import styles from './accountCard.css';
|
|||||||
export default class AccountCard extends Component {
|
export default class AccountCard extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: PropTypes.object.isRequired,
|
account: PropTypes.object.isRequired,
|
||||||
allowAddressClick: PropTypes.bool,
|
|
||||||
balance: PropTypes.object,
|
balance: PropTypes.object,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
disableAddressClick: PropTypes.bool,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
onFocus: PropTypes.func
|
onFocus: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
allowAddressClick: false
|
disableAddressClick: false
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -87,7 +87,6 @@ export default class AccountCard extends Component {
|
|||||||
balance={ balance }
|
balance={ balance }
|
||||||
className={ styles.balance }
|
className={ styles.balance }
|
||||||
showOnlyEth
|
showOnlyEth
|
||||||
showZeroValues
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -138,14 +137,14 @@ export default class AccountCard extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleAddressClick = (event) => {
|
handleAddressClick = (event) => {
|
||||||
const { allowAddressClick } = this.props;
|
const { disableAddressClick } = this.props;
|
||||||
|
|
||||||
// Don't stop the event if address click is allowed
|
// Stop the event if address click is disallowed
|
||||||
if (allowAddressClick) {
|
if (disableAddressClick) {
|
||||||
return this.onClick(event);
|
return this.preventEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.preventEvent(event);
|
return this.onClick(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown = (event) => {
|
handleKeyDown = (event) => {
|
||||||
|
@ -74,9 +74,8 @@ describe('ui/AccountCard', () => {
|
|||||||
expect(balance.length).to.equal(1);
|
expect(balance.length).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets showOnlyEth & showZeroValues', () => {
|
it('sets showOnlyEth', () => {
|
||||||
expect(balance.props().showOnlyEth).to.be.true;
|
expect(balance.props().showOnlyEth).to.be.true;
|
||||||
expect(balance.props().showZeroValues).to.be.true;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,11 +20,16 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin: 0.75em 0 0 0;
|
margin: 0.75em 0 0 0;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
|
||||||
|
&:not(.full) {
|
||||||
|
height: 2.5em;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance,
|
.balance,
|
||||||
.empty {
|
.empty {
|
||||||
margin: 0.75em 0.5em 0 0;
|
margin: 0.75em 0.5em 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
@ -37,28 +42,35 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.balance {
|
.balance {
|
||||||
background: rgba(255, 255, 255, 0.07);
|
align-items: center;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
max-height: 24px;
|
max-height: 24px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
&.full {
|
||||||
|
background: rgba(255, 255, 255, 0.07);
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance img {
|
img {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
margin: -4px 1em 0 0;
|
margin-top: -4px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balanceValue {
|
.tag {
|
||||||
margin: 0 0.5em 0 0;
|
padding-right: 0.75rem;
|
||||||
text-overflow: ellipsis;
|
font-size: 0.85em;
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.balanceTag {
|
.value {
|
||||||
font-size: 0.85em;
|
margin: 0 0.5em 0 0;
|
||||||
padding-right: 0.75rem;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ export default class Balance extends Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { balance, className, showZeroValues, showOnlyEth } = this.props;
|
const { balance, className, showOnlyEth } = this.props;
|
||||||
|
|
||||||
if (!balance || !balance.tokens) {
|
if (!balance || !balance.tokens) {
|
||||||
return null;
|
return null;
|
||||||
@ -49,12 +49,13 @@ export default class Balance extends Component {
|
|||||||
|
|
||||||
let body = balance.tokens
|
let body = balance.tokens
|
||||||
.filter((balance) => {
|
.filter((balance) => {
|
||||||
const hasBalance = showZeroValues || new BigNumber(balance.value).gt(0);
|
const isEthToken = (balance.token.tag || '').toLowerCase() === 'eth';
|
||||||
const isValidToken = !showOnlyEth || (balance.token.tag || '').toLowerCase() === 'eth';
|
const hasBalance = new BigNumber(balance.value).gt(0);
|
||||||
|
|
||||||
return hasBalance && isValidToken;
|
return hasBalance || isEthToken;
|
||||||
})
|
})
|
||||||
.map((balance, index) => {
|
.map((balance, index) => {
|
||||||
|
const isFullToken = !showOnlyEth || (balance.token.tag || '').toLowerCase() === 'eth';
|
||||||
const token = balance.token;
|
const token = balance.token;
|
||||||
|
|
||||||
let value;
|
let value;
|
||||||
@ -77,16 +78,36 @@ export default class Balance extends Component {
|
|||||||
value = api.util.fromWei(balance.value).toFormat(3);
|
value = api.util.fromWei(balance.value).toFormat(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const classNames = [styles.balance];
|
||||||
|
let details = null;
|
||||||
|
|
||||||
|
if (isFullToken) {
|
||||||
|
classNames.push(styles.full);
|
||||||
|
details = [
|
||||||
|
<div
|
||||||
|
className={ styles.value }
|
||||||
|
key='value'
|
||||||
|
>
|
||||||
|
<span title={ value }>
|
||||||
|
{ value }
|
||||||
|
</span>
|
||||||
|
</div>,
|
||||||
|
<div
|
||||||
|
className={ styles.tag }
|
||||||
|
key='tag'
|
||||||
|
>
|
||||||
|
{ token.tag }
|
||||||
|
</div>
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={ styles.balance }
|
className={ classNames.join(' ') }
|
||||||
key={ `${index}_${token.tag}` }
|
key={ `${index}_${token.tag}` }
|
||||||
>
|
>
|
||||||
<TokenImage token={ token } />
|
<TokenImage token={ token } />
|
||||||
<div className={ styles.balanceValue }>
|
{ details }
|
||||||
<span title={ value }> { value } </span>
|
|
||||||
</div>
|
|
||||||
<div className={ styles.balanceTag }> { token.tag } </div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -103,7 +124,17 @@ export default class Balance extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ [styles.balances, className].join(' ') }>
|
<div
|
||||||
|
className={
|
||||||
|
[
|
||||||
|
styles.balances,
|
||||||
|
showOnlyEth
|
||||||
|
? ''
|
||||||
|
: styles.full,
|
||||||
|
className
|
||||||
|
].join(' ')
|
||||||
|
}
|
||||||
|
>
|
||||||
{ body }
|
{ body }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -79,28 +79,9 @@ describe('ui/Balance', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('render specifiers', () => {
|
describe('render specifiers', () => {
|
||||||
it('renders only the single token with showOnlyEth', () => {
|
|
||||||
render({ showOnlyEth: true });
|
|
||||||
expect(component.find('Connect(TokenImage)')).to.have.length(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders all the tokens with showZeroValues', () => {
|
it('renders all the tokens with showZeroValues', () => {
|
||||||
render({ showZeroValues: true });
|
render({ showZeroValues: true });
|
||||||
expect(component.find('Connect(TokenImage)')).to.have.length(3);
|
expect(component.find('Connect(TokenImage)')).to.have.length(2);
|
||||||
});
|
|
||||||
|
|
||||||
it('shows ETH with zero value with showOnlyEth & showZeroValues', () => {
|
|
||||||
render({
|
|
||||||
showOnlyEth: true,
|
|
||||||
showZeroValues: true,
|
|
||||||
balance: {
|
|
||||||
tokens: [
|
|
||||||
{ value: '0', token: { tag: 'ETH' } },
|
|
||||||
{ value: '345', token: { tag: 'GAV', format: 1 } }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(component.find('Connect(TokenImage)')).to.have.length(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -348,7 +348,6 @@ class AddressSelect extends Component {
|
|||||||
return (
|
return (
|
||||||
<AccountCard
|
<AccountCard
|
||||||
account={ account }
|
account={ account }
|
||||||
allowAddressClick
|
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
className={ styles.account }
|
className={ styles.account }
|
||||||
key={ `account_${index}` }
|
key={ `account_${index}` }
|
||||||
|
@ -31,6 +31,7 @@ export DashboardIcon from 'material-ui/svg-icons/action/dashboard';
|
|||||||
export DeleteIcon from 'material-ui/svg-icons/action/delete';
|
export DeleteIcon from 'material-ui/svg-icons/action/delete';
|
||||||
export DevelopIcon from 'material-ui/svg-icons/action/description';
|
export DevelopIcon from 'material-ui/svg-icons/action/description';
|
||||||
export DoneIcon from 'material-ui/svg-icons/action/done-all';
|
export DoneIcon from 'material-ui/svg-icons/action/done-all';
|
||||||
|
export DialIcon from 'material-ui/svg-icons/communication/dialpad';
|
||||||
export EditIcon from 'material-ui/svg-icons/content/create';
|
export EditIcon from 'material-ui/svg-icons/content/create';
|
||||||
export ErrorIcon from 'material-ui/svg-icons/alert/error';
|
export ErrorIcon from 'material-ui/svg-icons/alert/error';
|
||||||
export FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
|
export FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
|
||||||
|
@ -120,6 +120,18 @@ class MethodDecoding extends Component {
|
|||||||
<span className={ styles.highlight }>
|
<span className={ styles.highlight }>
|
||||||
{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)
|
{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)
|
||||||
</span>
|
</span>
|
||||||
|
{
|
||||||
|
transaction.gasUsed
|
||||||
|
? (
|
||||||
|
<span>
|
||||||
|
<span>used</span>
|
||||||
|
<span className={ styles.highlight }>
|
||||||
|
{ transaction.gasUsed.toFormat(0) } gas
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
<span> for a total transaction value of </span>
|
<span> for a total transaction value of </span>
|
||||||
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
|
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
|
||||||
{ this.renderMinBlock() }
|
{ this.renderMinBlock() }
|
||||||
|
@ -148,7 +148,19 @@ export default class MethodDecodingStore {
|
|||||||
|
|
||||||
// Contract deployment
|
// Contract deployment
|
||||||
if (!signature || signature === CONTRACT_CREATE || transaction.creates) {
|
if (!signature || signature === CONTRACT_CREATE || transaction.creates) {
|
||||||
return this.decodeContractCreation(result, contractAddress || transaction.creates);
|
const address = contractAddress || transaction.creates;
|
||||||
|
|
||||||
|
return this.isContractCreation(input, address)
|
||||||
|
.then((isContractCreation) => {
|
||||||
|
if (!isContractCreation) {
|
||||||
|
result.contract = false;
|
||||||
|
result.deploy = false;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.decodeContractCreation(result, address);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this
|
return this
|
||||||
@ -204,7 +216,7 @@ export default class MethodDecodingStore {
|
|||||||
const { input } = data;
|
const { input } = data;
|
||||||
const abi = this._contractsAbi[contractAddress];
|
const abi = this._contractsAbi[contractAddress];
|
||||||
|
|
||||||
if (!input || !abi || !abi.constructors || abi.constructors.length === 0) {
|
if (!abi || !abi.constructors || abi.constructors.length === 0) {
|
||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,6 +318,30 @@ export default class MethodDecodingStore {
|
|||||||
return Promise.resolve(this._isContract[contractAddress]);
|
return Promise.resolve(this._isContract[contractAddress]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the input resulted in a contract creation
|
||||||
|
* by checking that the contract address code contains
|
||||||
|
* a part of the input, or vice-versa
|
||||||
|
*/
|
||||||
|
isContractCreation (input, contractAddress) {
|
||||||
|
return this.api.eth
|
||||||
|
.getCode(contractAddress)
|
||||||
|
.then((code) => {
|
||||||
|
if (/^(0x)?0*$/.test(code)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const strippedCode = code.replace(/^0x/, '');
|
||||||
|
const strippedInput = input.replace(/^0x/, '');
|
||||||
|
|
||||||
|
return strippedInput.indexOf(strippedInput) >= 0 || strippedCode.indexOf(strippedInput) >= 0;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getCode (contractAddress) {
|
getCode (contractAddress) {
|
||||||
// If zero address, resolve to '0x'
|
// If zero address, resolve to '0x'
|
||||||
if (!contractAddress || /^(0x)?0*$/.test(contractAddress)) {
|
if (!contractAddress || /^(0x)?0*$/.test(contractAddress)) {
|
||||||
|
@ -22,13 +22,13 @@ import styles from './modalBox.css';
|
|||||||
|
|
||||||
export default class ModalBox extends Component {
|
export default class ModalBox extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node,
|
||||||
icon: PropTypes.node.isRequired,
|
icon: PropTypes.node.isRequired,
|
||||||
summary: nodeOrStringProptype()
|
summary: nodeOrStringProptype()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, icon } = this.props;
|
const { icon } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.body }>
|
<div className={ styles.body }>
|
||||||
@ -37,11 +37,23 @@ export default class ModalBox extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className={ styles.content }>
|
<div className={ styles.content }>
|
||||||
{ this.renderSummary() }
|
{ this.renderSummary() }
|
||||||
|
{ this.renderBody() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBody () {
|
||||||
|
const { children } = this.props;
|
||||||
|
|
||||||
|
if (!children) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<div className={ styles.body }>
|
<div className={ styles.body }>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
$transition: all 0.25s;
|
$transition: all 0.25s;
|
||||||
$widthNormal: 33.33%;
|
$widthNormal: 33.33%;
|
||||||
$widthShrunk: 29%;
|
|
||||||
$widthExpanded: 42%;
|
$widthExpanded: 42%;
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
@ -39,18 +38,19 @@ $widthExpanded: 42%;
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
/* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for */
|
/* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for
|
||||||
/* case where <> 3 columns are required should the need arrise from a UI pov. */
|
/* case where <> 3 columns are required should the need arrise from a UI pov. */
|
||||||
.item {
|
.item {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 1 $widthNormal;
|
flex: 0 1 $widthNormal;
|
||||||
max-width: $widthNormal;
|
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
|
|
||||||
/* https://www.binarymoon.co.uk/2014/02/fixing-css-transitions-in-google-chrome/ */
|
/* https://www.binarymoon.co.uk/2014/02/fixing-css-transitions-in-google-chrome/ */
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
transition: $transition;
|
transition: $transition;
|
||||||
|
width: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@ -58,21 +58,12 @@ $widthExpanded: 42%;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
.item.stretchOn:hover {
|
||||||
.item {
|
|
||||||
&.stretchOn {
|
|
||||||
flex: 0 1 $widthShrunk;
|
|
||||||
max-width: $widthShrunk;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
flex: 0 0 $widthExpanded;
|
flex: 0 0 $widthExpanded;
|
||||||
max-width: $widthExpanded;
|
max-width: $widthExpanded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.section + .section {
|
.section + .section {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
border: 2px solid transparent;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -25,7 +24,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: transparent;
|
|
||||||
filter: none;
|
filter: none;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@ -35,7 +33,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.5);
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,15 +66,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
border-color: rgba(255, 255, 255, 0.25);
|
|
||||||
filter: none;
|
filter: none;
|
||||||
|
|
||||||
&.default {
|
&::after {
|
||||||
border-color: rgba(255, 255, 255, 0.75);
|
background: rgb(0, 151, 167);
|
||||||
|
content: '';
|
||||||
|
height: 4px;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.default::after {
|
||||||
|
background: rgb(167, 151, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.unselected {
|
.unselected {
|
||||||
filter: grayscale(10%);
|
filter: grayscale(50%);
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ export default class Store {
|
|||||||
|
|
||||||
return bnB.comparedTo(bnA);
|
return bnB.comparedTo(bnA);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0));
|
this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -85,26 +86,53 @@ export default class Store {
|
|||||||
this._subscriptionId = 0;
|
this._subscriptionId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadTransactions (_txhashes) {
|
loadTransactions (_txhashes = []) {
|
||||||
const txhashes = _txhashes.filter((hash) => !this.transactions[hash] || this._pendingHashes.includes(hash));
|
const promises = _txhashes
|
||||||
|
.filter((txhash) => !this.transactions[txhash] || this._pendingHashes.includes(txhash))
|
||||||
|
.map((txhash) => {
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this._api.eth.getTransactionByHash(txhash),
|
||||||
|
this._api.eth.getTransactionReceipt(txhash)
|
||||||
|
])
|
||||||
|
.then(([
|
||||||
|
transaction = {},
|
||||||
|
transactionReceipt = {}
|
||||||
|
]) => {
|
||||||
|
return {
|
||||||
|
...transactionReceipt,
|
||||||
|
...transaction
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (!txhashes || !txhashes.length) {
|
if (!promises.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise
|
Promise
|
||||||
.all(txhashes.map((txhash) => this._api.eth.getTransactionByHash(txhash)))
|
.all(promises)
|
||||||
.then((_transactions) => {
|
.then((_transactions) => {
|
||||||
const transactions = _transactions.filter((tx) => tx);
|
const blockNumbers = [];
|
||||||
|
const transactions = _transactions
|
||||||
|
.filter((tx) => tx && tx.hash)
|
||||||
|
.reduce((txs, tx) => {
|
||||||
|
txs[tx.hash] = tx;
|
||||||
|
|
||||||
this.addTransactions(
|
if (tx.blockNumber && tx.blockNumber.gt(0)) {
|
||||||
transactions.reduce((transactions, tx, index) => {
|
blockNumbers.push(tx.blockNumber.toNumber());
|
||||||
transactions[txhashes[index]] = tx;
|
}
|
||||||
return transactions;
|
|
||||||
}, {})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.loadBlocks(transactions.map((tx) => tx.blockNumber ? tx.blockNumber.toNumber() : 0));
|
return txs;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// No need to add transactions if there are none
|
||||||
|
if (Object.keys(transactions).length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addTransactions(transactions);
|
||||||
|
this.loadBlocks(blockNumbers);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('loadTransactions', error);
|
console.warn('loadTransactions', error);
|
||||||
|
@ -27,7 +27,7 @@ import styles from './txList.css';
|
|||||||
class TxList extends Component {
|
class TxList extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
@ -36,7 +36,7 @@ class TxList extends Component {
|
|||||||
PropTypes.object
|
PropTypes.object
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
netVersion: PropTypes.string.isRequired
|
netVersion: PropTypes.string.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
store = new Store(this.context.api);
|
store = new Store(this.context.api);
|
||||||
|
|
||||||
|
@ -61,17 +61,6 @@ export function estimateGas (_func, _options, _values = []) {
|
|||||||
const { func, options, values } = callArgs;
|
const { func, options, values } = callArgs;
|
||||||
|
|
||||||
return func._estimateGas(options, values);
|
return func._estimateGas(options, values);
|
||||||
})
|
|
||||||
.then((gas) => {
|
|
||||||
return WalletsUtils
|
|
||||||
.isWallet(_func.contract.api, _options.from)
|
|
||||||
.then((isWallet) => {
|
|
||||||
if (isWallet) {
|
|
||||||
return gas.mul(1.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
return gas;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +73,114 @@ export function postTransaction (_func, _options, _values = []) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deploy (contract, _options, values, metadata = {}, statecb = () => {}) {
|
||||||
|
const options = { ..._options };
|
||||||
|
const { api } = contract;
|
||||||
|
const address = options.from;
|
||||||
|
|
||||||
|
return WalletsUtils
|
||||||
|
.isWallet(api, address)
|
||||||
|
.then((isWallet) => {
|
||||||
|
if (!isWallet) {
|
||||||
|
return contract.deploy(options, values, statecb);
|
||||||
|
}
|
||||||
|
|
||||||
|
statecb(null, { state: 'estimateGas' });
|
||||||
|
|
||||||
|
return deployEstimateGas(contract, options, values)
|
||||||
|
.then(([gasEst, gas]) => {
|
||||||
|
options.gas = gas.toFixed(0);
|
||||||
|
|
||||||
|
statecb(null, { state: 'postTransaction', gas });
|
||||||
|
|
||||||
|
return WalletsUtils.getDeployArgs(contract, options, values);
|
||||||
|
})
|
||||||
|
.then((callArgs) => {
|
||||||
|
const { func, options, values } = callArgs;
|
||||||
|
|
||||||
|
return func._postTransaction(options, values)
|
||||||
|
.then((requestId) => {
|
||||||
|
statecb(null, { state: 'checkRequest', requestId });
|
||||||
|
return contract._pollCheckRequest(requestId);
|
||||||
|
})
|
||||||
|
.then((txhash) => {
|
||||||
|
statecb(null, { state: 'getTransactionReceipt', txhash });
|
||||||
|
return contract._pollTransactionReceipt(txhash, options.gas);
|
||||||
|
})
|
||||||
|
.then((receipt) => {
|
||||||
|
if (receipt.gasUsed.eq(options.gas)) {
|
||||||
|
throw new Error(`Contract not deployed, gasUsed == ${options.gas.toFixed(0)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const logs = WalletsUtils.parseLogs(api, receipt.logs || []);
|
||||||
|
|
||||||
|
const confirmationLog = logs.find((log) => log.event === 'ConfirmationNeeded');
|
||||||
|
const transactionLog = logs.find((log) => log.event === 'SingleTransact');
|
||||||
|
|
||||||
|
if (!confirmationLog && !transactionLog) {
|
||||||
|
throw new Error('Something went wrong in the Wallet Contract (no logs have been emitted)...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirmations are needed from the other owners
|
||||||
|
if (confirmationLog) {
|
||||||
|
const operationHash = api.util.bytesToHex(confirmationLog.params.operation.value);
|
||||||
|
|
||||||
|
// Add the contract to pending contracts
|
||||||
|
WalletsUtils.addPendingContract(address, operationHash, metadata);
|
||||||
|
statecb(null, { state: 'confirmationNeeded' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the contract address in the receip
|
||||||
|
receipt.contractAddress = transactionLog.params.created.value;
|
||||||
|
|
||||||
|
const contractAddress = receipt.contractAddress;
|
||||||
|
|
||||||
|
statecb(null, { state: 'hasReceipt', receipt });
|
||||||
|
contract._receipt = receipt;
|
||||||
|
contract._address = contractAddress;
|
||||||
|
|
||||||
|
statecb(null, { state: 'getCode' });
|
||||||
|
|
||||||
|
return api.eth.getCode(contractAddress)
|
||||||
|
.then((code) => {
|
||||||
|
if (code === '0x') {
|
||||||
|
throw new Error('Contract not deployed, getCode returned 0x');
|
||||||
|
}
|
||||||
|
|
||||||
|
statecb(null, { state: 'completed' });
|
||||||
|
return contractAddress;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deployEstimateGas (contract, _options, values) {
|
||||||
|
const options = { ..._options };
|
||||||
|
const { api } = contract;
|
||||||
|
const address = options.from;
|
||||||
|
|
||||||
|
return WalletsUtils
|
||||||
|
.isWallet(api, address)
|
||||||
|
.then((isWallet) => {
|
||||||
|
if (!isWallet) {
|
||||||
|
return contract.deployEstimateGas(options, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return WalletsUtils
|
||||||
|
.getDeployArgs(contract, options, values)
|
||||||
|
.then((callArgs) => {
|
||||||
|
const { func, options, values } = callArgs;
|
||||||
|
|
||||||
|
return func.estimateGas(options, values);
|
||||||
|
})
|
||||||
|
.then((gasEst) => {
|
||||||
|
return [gasEst, gasEst.mul(1.05)];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function patchApi (api) {
|
export function patchApi (api) {
|
||||||
api.patch = {
|
api.patch = {
|
||||||
...api.patch,
|
...api.patch,
|
||||||
|
@ -16,59 +16,154 @@
|
|||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { intersection, range, uniq } from 'lodash';
|
import { intersection, range, uniq } from 'lodash';
|
||||||
|
import store from 'store';
|
||||||
|
|
||||||
|
import Abi from '~/abi';
|
||||||
import Contract from '~/api/contract';
|
import Contract from '~/api/contract';
|
||||||
import { bytesToHex, toHex } from '~/api/util/format';
|
import { bytesToHex, toHex } from '~/api/util/format';
|
||||||
import { validateAddress } from '~/util/validation';
|
import { validateAddress } from '~/util/validation';
|
||||||
import WalletAbi from '~/contracts/abi/wallet.json';
|
import WalletAbi from '~/contracts/abi/wallet.json';
|
||||||
|
import OldWalletAbi from '~/contracts/abi/old-wallet.json';
|
||||||
|
|
||||||
|
const LS_PENDING_CONTRACTS_KEY = '_parity::wallets::pendingContracts';
|
||||||
|
|
||||||
const _cachedWalletLookup = {};
|
const _cachedWalletLookup = {};
|
||||||
|
let _cachedAccounts = {};
|
||||||
|
|
||||||
|
const walletAbi = new Abi(WalletAbi);
|
||||||
|
const oldWalletAbi = new Abi(OldWalletAbi);
|
||||||
|
|
||||||
|
const walletEvents = walletAbi.events.reduce((events, event) => {
|
||||||
|
events[event.name] = event;
|
||||||
|
return events;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const oldWalletEvents = oldWalletAbi.events.reduce((events, event) => {
|
||||||
|
events[event.name] = event;
|
||||||
|
return events;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const WalletSignatures = {
|
||||||
|
OwnerChanged: toHex(walletEvents.OwnerChanged.signature),
|
||||||
|
OwnerAdded: toHex(walletEvents.OwnerAdded.signature),
|
||||||
|
OwnerRemoved: toHex(walletEvents.OwnerRemoved.signature),
|
||||||
|
RequirementChanged: toHex(walletEvents.RequirementChanged.signature),
|
||||||
|
Confirmation: toHex(walletEvents.Confirmation.signature),
|
||||||
|
Revoke: toHex(walletEvents.Revoke.signature),
|
||||||
|
Deposit: toHex(walletEvents.Deposit.signature),
|
||||||
|
SingleTransact: toHex(walletEvents.SingleTransact.signature),
|
||||||
|
MultiTransact: toHex(walletEvents.MultiTransact.signature),
|
||||||
|
ConfirmationNeeded: toHex(walletEvents.ConfirmationNeeded.signature),
|
||||||
|
|
||||||
|
Old: {
|
||||||
|
SingleTransact: toHex(oldWalletEvents.SingleTransact.signature),
|
||||||
|
MultiTransact: toHex(oldWalletEvents.MultiTransact.signature)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default class WalletsUtils {
|
export default class WalletsUtils {
|
||||||
|
static getWalletSignatures () {
|
||||||
|
return WalletSignatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getPendingContracts () {
|
||||||
|
return store.get(LS_PENDING_CONTRACTS_KEY) || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static setPendingContracts (contracts = {}) {
|
||||||
|
return store.set(LS_PENDING_CONTRACTS_KEY, contracts);
|
||||||
|
}
|
||||||
|
|
||||||
|
static removePendingContract (operationHash) {
|
||||||
|
const nextContracts = WalletsUtils.getPendingContracts();
|
||||||
|
|
||||||
|
delete nextContracts[operationHash];
|
||||||
|
WalletsUtils.setPendingContracts(nextContracts);
|
||||||
|
}
|
||||||
|
|
||||||
|
static addPendingContract (address, operationHash, metadata) {
|
||||||
|
const nextContracts = {
|
||||||
|
...WalletsUtils.getPendingContracts(),
|
||||||
|
[ operationHash ]: {
|
||||||
|
address,
|
||||||
|
metadata,
|
||||||
|
operationHash
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
WalletsUtils.setPendingContracts(nextContracts);
|
||||||
|
}
|
||||||
|
|
||||||
|
static cacheAccounts (accounts) {
|
||||||
|
_cachedAccounts = accounts;
|
||||||
|
}
|
||||||
|
|
||||||
static getCallArgs (api, options, values = []) {
|
static getCallArgs (api, options, values = []) {
|
||||||
const walletContract = new Contract(api, WalletAbi);
|
const walletContract = new Contract(api, WalletAbi);
|
||||||
|
const walletAddress = options.from;
|
||||||
|
|
||||||
const promises = [
|
return WalletsUtils
|
||||||
api.parity.accountsInfo(),
|
.fetchOwners(walletContract.at(walletAddress))
|
||||||
WalletsUtils.fetchOwners(walletContract.at(options.from))
|
.then((owners) => {
|
||||||
];
|
const addresses = Object.keys(_cachedAccounts);
|
||||||
|
const ownerAddress = intersection(addresses, owners).pop();
|
||||||
|
|
||||||
return Promise
|
if (!ownerAddress) {
|
||||||
.all(promises)
|
|
||||||
.then(([ accounts, owners ]) => {
|
|
||||||
const addresses = Object.keys(accounts);
|
|
||||||
const owner = intersection(addresses, owners).pop();
|
|
||||||
|
|
||||||
if (!owner) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return owner;
|
const account = _cachedAccounts[ownerAddress];
|
||||||
})
|
const _options = { ...options };
|
||||||
.then((owner) => {
|
const { to, value = new BigNumber(0), data } = _options;
|
||||||
if (!owner) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _options = Object.assign({}, options);
|
|
||||||
const { from, to, value = new BigNumber(0), data } = options;
|
|
||||||
|
|
||||||
delete _options.data;
|
delete _options.data;
|
||||||
|
|
||||||
const nextValues = [ to, value, data ];
|
const nextValues = [ to, value, data ];
|
||||||
const nextOptions = {
|
const nextOptions = {
|
||||||
..._options,
|
..._options,
|
||||||
from: owner,
|
from: ownerAddress,
|
||||||
to: from,
|
to: walletAddress,
|
||||||
value: new BigNumber(0)
|
value: new BigNumber(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
const execFunc = walletContract.instance.execute;
|
const execFunc = walletContract.instance.execute;
|
||||||
|
const callArgs = { func: execFunc, options: nextOptions, values: nextValues };
|
||||||
|
|
||||||
return { func: execFunc, options: nextOptions, values: nextValues };
|
if (!account.wallet) {
|
||||||
|
return callArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextData = walletContract.getCallData(execFunc, nextOptions, nextValues);
|
||||||
|
|
||||||
|
return WalletsUtils.getCallArgs(api, { ...nextOptions, data: nextData }, nextValues);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDeployArgs (contract, options, values) {
|
||||||
|
const { api } = contract;
|
||||||
|
const func = contract.constructors[0];
|
||||||
|
|
||||||
|
options.data = contract.getCallData(func, options, values);
|
||||||
|
options.to = '0x';
|
||||||
|
|
||||||
|
return WalletsUtils
|
||||||
|
.getCallArgs(api, options, values)
|
||||||
|
.then((callArgs) => {
|
||||||
|
if (!callArgs) {
|
||||||
|
console.error('no call args', callArgs);
|
||||||
|
throw new Error('you do not own this wallet');
|
||||||
|
}
|
||||||
|
|
||||||
|
return callArgs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseLogs (api, logs = []) {
|
||||||
|
const walletContract = new Contract(api, WalletAbi);
|
||||||
|
|
||||||
|
return walletContract.parseEventLogs(logs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the given address could be
|
* Check whether the given address could be
|
||||||
* a Wallet. The result is cached in order not
|
* a Wallet. The result is cached in order not
|
||||||
@ -199,16 +294,18 @@ export default class WalletsUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fetchTransactions (walletContract) {
|
static fetchTransactions (walletContract) {
|
||||||
const walletInstance = walletContract.instance;
|
const { api } = walletContract;
|
||||||
const signatures = {
|
const pendingContracts = WalletsUtils.getPendingContracts();
|
||||||
single: toHex(walletInstance.SingleTransact.signature),
|
|
||||||
multi: toHex(walletInstance.MultiTransact.signature),
|
|
||||||
deposit: toHex(walletInstance.Deposit.signature)
|
|
||||||
};
|
|
||||||
|
|
||||||
return walletContract
|
return walletContract
|
||||||
.getAllLogs({
|
.getAllLogs({
|
||||||
topics: [ [ signatures.single, signatures.multi, signatures.deposit ] ]
|
topics: [ [
|
||||||
|
WalletSignatures.SingleTransact,
|
||||||
|
WalletSignatures.MultiTransact,
|
||||||
|
WalletSignatures.Deposit,
|
||||||
|
WalletSignatures.Old.SingleTransact,
|
||||||
|
WalletSignatures.Old.MultiTransact
|
||||||
|
] ]
|
||||||
})
|
})
|
||||||
.then((logs) => {
|
.then((logs) => {
|
||||||
return logs.sort((logA, logB) => {
|
return logs.sort((logA, logB) => {
|
||||||
@ -226,11 +323,11 @@ export default class WalletsUtils {
|
|||||||
const signature = toHex(log.topics[0]);
|
const signature = toHex(log.topics[0]);
|
||||||
|
|
||||||
const value = log.params.value.value;
|
const value = log.params.value.value;
|
||||||
const from = signature === signatures.deposit
|
const from = signature === WalletSignatures.Deposit
|
||||||
? log.params['_from'].value
|
? log.params['_from'].value
|
||||||
: walletContract.address;
|
: walletContract.address;
|
||||||
|
|
||||||
const to = signature === signatures.deposit
|
const to = signature === WalletSignatures.Deposit
|
||||||
? walletContract.address
|
? walletContract.address
|
||||||
: log.params.to.value;
|
: log.params.to.value;
|
||||||
|
|
||||||
@ -240,8 +337,53 @@ export default class WalletsUtils {
|
|||||||
from, to, value
|
from, to, value
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (log.params.created && log.params.created.value && !/^(0x)?0*$/.test(log.params.created.value)) {
|
||||||
|
transaction.creates = log.params.created.value;
|
||||||
|
delete transaction.to;
|
||||||
|
}
|
||||||
|
|
||||||
if (log.params.operation) {
|
if (log.params.operation) {
|
||||||
transaction.operation = bytesToHex(log.params.operation.value);
|
const operation = bytesToHex(log.params.operation.value);
|
||||||
|
|
||||||
|
// Add the pending contract to the contracts
|
||||||
|
if (pendingContracts[operation]) {
|
||||||
|
const { metadata } = pendingContracts[operation];
|
||||||
|
const contractName = metadata.name;
|
||||||
|
|
||||||
|
metadata.blockNumber = log.blockNumber;
|
||||||
|
|
||||||
|
// The contract creation might not be in the same log,
|
||||||
|
// but must be in the same transaction (eg. Contract creation
|
||||||
|
// from Wallet within a Wallet)
|
||||||
|
api.eth
|
||||||
|
.getTransactionReceipt(log.transactionHash)
|
||||||
|
.then((transactionReceipt) => {
|
||||||
|
const transactionLogs = WalletsUtils.parseLogs(api, transactionReceipt.logs);
|
||||||
|
const creationLog = transactionLogs.find((log) => {
|
||||||
|
return log.params.created && !/^(0x)?0*$/.test(log.params.created.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!creationLog) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contractAddress = creationLog.params.created.value;
|
||||||
|
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
api.parity.setAccountName(contractAddress, contractName),
|
||||||
|
api.parity.setAccountMeta(contractAddress, metadata)
|
||||||
|
])
|
||||||
|
.then(() => {
|
||||||
|
WalletsUtils.removePendingContract(operation);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('adding wallet contract', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.operation = operation;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log.params.data) {
|
if (log.params.data) {
|
||||||
|
@ -22,11 +22,11 @@ import { bindActionCreators } from 'redux';
|
|||||||
|
|
||||||
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
|
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
|
||||||
import HardwareStore from '~/mobx/hardwareStore';
|
import HardwareStore from '~/mobx/hardwareStore';
|
||||||
import { EditMeta, DeleteAccount, Shapeshift, Verification, Transfer, PasswordManager } from '~/modals';
|
import { DeleteAccount, EditMeta, Faucet, PasswordManager, Shapeshift, Transfer, Verification } from '~/modals';
|
||||||
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
||||||
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
|
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
|
||||||
import { Actionbar, Button, Page } from '~/ui';
|
import { Actionbar, Button, Page } from '~/ui';
|
||||||
import { DeleteIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon } from '~/ui/Icons';
|
import { DeleteIcon, DialIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import DeleteAddress from '../Address/Delete';
|
import DeleteAddress from '../Address/Delete';
|
||||||
|
|
||||||
@ -48,6 +48,8 @@ class Account extends Component {
|
|||||||
|
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
balances: PropTypes.object,
|
||||||
|
certifications: PropTypes.object,
|
||||||
|
netVersion: PropTypes.string.isRequired,
|
||||||
params: PropTypes.object
|
params: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +99,7 @@ class Account extends Component {
|
|||||||
<div>
|
<div>
|
||||||
{ this.renderDeleteDialog(account) }
|
{ this.renderDeleteDialog(account) }
|
||||||
{ this.renderEditDialog(account) }
|
{ this.renderEditDialog(account) }
|
||||||
|
{ this.renderFaucetDialog() }
|
||||||
{ this.renderFundDialog() }
|
{ this.renderFundDialog() }
|
||||||
{ this.renderPasswordDialog(account) }
|
{ this.renderPasswordDialog(account) }
|
||||||
{ this.renderTransferDialog(account, balance) }
|
{ this.renderTransferDialog(account, balance) }
|
||||||
@ -117,8 +120,35 @@ class Account extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isKovan = (netVersion) => {
|
||||||
|
return netVersion === '42';
|
||||||
|
}
|
||||||
|
|
||||||
|
isMainnet = (netVersion) => {
|
||||||
|
return netVersion === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
isFaucettable = (netVersion, certifications, address) => {
|
||||||
|
return this.isKovan(netVersion) || (
|
||||||
|
this.isMainnet(netVersion) &&
|
||||||
|
this.isSmsCertified(certifications, address)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSmsCertified = (_certifications, address) => {
|
||||||
|
const certifications = _certifications && _certifications[address]
|
||||||
|
? _certifications[address].filter((cert) => cert.name.indexOf('smsverification') === 0)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return certifications.length !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
renderActionbar (account, balance) {
|
renderActionbar (account, balance) {
|
||||||
|
const { certifications, netVersion } = this.props;
|
||||||
|
const { address } = this.props.params;
|
||||||
const showTransferButton = !!(balance && balance.tokens);
|
const showTransferButton = !!(balance && balance.tokens);
|
||||||
|
const isVerifiable = this.isMainnet(netVersion);
|
||||||
|
const isFaucettable = this.isFaucettable(netVersion, certifications, address);
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
<Button
|
<Button
|
||||||
@ -149,9 +179,11 @@ class Account extends Component {
|
|||||||
}
|
}
|
||||||
onClick={ this.store.toggleFundDialog }
|
onClick={ this.store.toggleFundDialog }
|
||||||
/>,
|
/>,
|
||||||
|
isVerifiable
|
||||||
|
? (
|
||||||
<Button
|
<Button
|
||||||
icon={ <VerifyIcon /> }
|
icon={ <VerifyIcon /> }
|
||||||
key='sms-verification'
|
key='verification'
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='account.button.verify'
|
id='account.button.verify'
|
||||||
@ -159,7 +191,24 @@ class Account extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onClick={ this.store.toggleVerificationDialog }
|
onClick={ this.store.toggleVerificationDialog }
|
||||||
/>,
|
/>
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
isFaucettable
|
||||||
|
? (
|
||||||
|
<Button
|
||||||
|
icon={ <DialIcon /> }
|
||||||
|
key='faucet'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.button.faucet'
|
||||||
|
defaultMessage='Kovan ETH'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={ this.store.toggleFaucetDialog }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null,
|
||||||
<Button
|
<Button
|
||||||
icon={ <EditIcon /> }
|
icon={ <EditIcon /> }
|
||||||
key='editmeta'
|
key='editmeta'
|
||||||
@ -253,6 +302,24 @@ class Account extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderFaucetDialog () {
|
||||||
|
const { netVersion } = this.props;
|
||||||
|
|
||||||
|
if (!this.store.isFaucetVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { address } = this.props.params;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Faucet
|
||||||
|
address={ address }
|
||||||
|
netVersion={ netVersion }
|
||||||
|
onClose={ this.store.toggleFaucetDialog }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderFundDialog () {
|
renderFundDialog () {
|
||||||
if (!this.store.isFundVisible) {
|
if (!this.store.isFundVisible) {
|
||||||
return null;
|
return null;
|
||||||
@ -317,10 +384,14 @@ class Account extends Component {
|
|||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts } = state.personal;
|
const { accounts } = state.personal;
|
||||||
const { balances } = state.balances;
|
const { balances } = state.balances;
|
||||||
|
const certifications = state.certifications;
|
||||||
|
const { netVersion } = state.nodeStatus;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
balances
|
balances,
|
||||||
|
certifications,
|
||||||
|
netVersion
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,57 +80,16 @@ describe('views/Account', () => {
|
|||||||
describe('sub-renderers', () => {
|
describe('sub-renderers', () => {
|
||||||
describe('renderActionBar', () => {
|
describe('renderActionBar', () => {
|
||||||
let bar;
|
let bar;
|
||||||
let barShallow;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render();
|
render();
|
||||||
|
|
||||||
bar = instance.renderActionbar({ tokens: {} });
|
bar = instance.renderActionbar({ tokens: {} });
|
||||||
barShallow = shallow(bar);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the bar', () => {
|
it('renders the bar', () => {
|
||||||
expect(bar.type).to.match(/Actionbar/);
|
expect(bar.type).to.match(/Actionbar/);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Finding by index is not optimal, however couldn't find a better method atm
|
|
||||||
// since we cannot find by key (prop not visible in shallow debug())
|
|
||||||
describe('clicks', () => {
|
|
||||||
it('toggles transfer on click', () => {
|
|
||||||
barShallow.find('Button').at(0).simulate('click');
|
|
||||||
expect(store.isTransferVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles fund on click', () => {
|
|
||||||
barShallow.find('Button').at(1).simulate('click');
|
|
||||||
expect(store.isFundVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles fund on click', () => {
|
|
||||||
barShallow.find('Button').at(1).simulate('click');
|
|
||||||
expect(store.isFundVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles verify on click', () => {
|
|
||||||
barShallow.find('Button').at(2).simulate('click');
|
|
||||||
expect(store.isVerificationVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles edit on click', () => {
|
|
||||||
barShallow.find('Button').at(3).simulate('click');
|
|
||||||
expect(store.isEditVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles password on click', () => {
|
|
||||||
barShallow.find('Button').at(4).simulate('click');
|
|
||||||
expect(store.isPasswordVisible).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles delete on click', () => {
|
|
||||||
barShallow.find('Button').at(5).simulate('click');
|
|
||||||
expect(store.isDeleteVisible).to.be.true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('renderDeleteDialog', () => {
|
describe('renderDeleteDialog', () => {
|
||||||
|
@ -19,6 +19,7 @@ import { action, observable } from 'mobx';
|
|||||||
export default class Store {
|
export default class Store {
|
||||||
@observable isDeleteVisible = false;
|
@observable isDeleteVisible = false;
|
||||||
@observable isEditVisible = false;
|
@observable isEditVisible = false;
|
||||||
|
@observable isFaucetVisible = false;
|
||||||
@observable isFundVisible = false;
|
@observable isFundVisible = false;
|
||||||
@observable isPasswordVisible = false;
|
@observable isPasswordVisible = false;
|
||||||
@observable isTransferVisible = false;
|
@observable isTransferVisible = false;
|
||||||
@ -32,6 +33,10 @@ export default class Store {
|
|||||||
this.isEditVisible = !this.isEditVisible;
|
this.isEditVisible = !this.isEditVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action toggleFaucetDialog = () => {
|
||||||
|
this.isFaucetVisible = !this.isFaucetVisible;
|
||||||
|
}
|
||||||
|
|
||||||
@action toggleFundDialog = () => {
|
@action toggleFundDialog = () => {
|
||||||
this.isFundVisible = !this.isFundVisible;
|
this.isFundVisible = !this.isFundVisible;
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ describe('views/Account/Store', () => {
|
|||||||
it('sets all modal visibility to false', () => {
|
it('sets all modal visibility to false', () => {
|
||||||
expect(store.isDeleteVisible).to.be.false;
|
expect(store.isDeleteVisible).to.be.false;
|
||||||
expect(store.isEditVisible).to.be.false;
|
expect(store.isEditVisible).to.be.false;
|
||||||
|
expect(store.isFaucetVisible).to.be.false;
|
||||||
expect(store.isFundVisible).to.be.false;
|
expect(store.isFundVisible).to.be.false;
|
||||||
expect(store.isPasswordVisible).to.be.false;
|
expect(store.isPasswordVisible).to.be.false;
|
||||||
expect(store.isTransferVisible).to.be.false;
|
expect(store.isTransferVisible).to.be.false;
|
||||||
@ -53,6 +54,13 @@ describe('views/Account/Store', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('toggleFaucetDialog', () => {
|
||||||
|
it('toggles the visibility', () => {
|
||||||
|
store.toggleFaucetDialog();
|
||||||
|
expect(store.isFaucetVisible).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('toggleFundDialog', () => {
|
describe('toggleFundDialog', () => {
|
||||||
it('toggles the visibility', () => {
|
it('toggles the visibility', () => {
|
||||||
store.toggleFundDialog();
|
store.toggleFundDialog();
|
||||||
|
@ -116,7 +116,6 @@ class Summary extends Component {
|
|||||||
{ this.renderBalance(false) }
|
{ this.renderBalance(false) }
|
||||||
{ this.renderDescription(account.meta) }
|
{ this.renderDescription(account.meta) }
|
||||||
{ this.renderOwners() }
|
{ this.renderOwners() }
|
||||||
{ this.renderCertifications() }
|
|
||||||
{ this.renderVault(account.meta) }
|
{ this.renderVault(account.meta) }
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -155,8 +154,8 @@ class Summary extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className={ styles.summary }>
|
<div className={ styles.summary }>
|
||||||
{ this.renderBalance(true) }
|
{ this.renderBalance(true) }
|
||||||
{ this.renderCertifications(true) }
|
|
||||||
</div>
|
</div>
|
||||||
|
{ this.renderCertifications(true) }
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -41,19 +41,24 @@
|
|||||||
margin-top: 1.5em;
|
margin-top: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iconCertifications {
|
||||||
|
top: 72px;
|
||||||
|
opacity: 1;
|
||||||
|
position: absolute;
|
||||||
|
left: 88px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 1em !important;
|
||||||
|
width: 1em !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.summary {
|
.summary {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.ethBalances {
|
.ethBalances {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconCertifications {
|
|
||||||
bottom: -0.25em;
|
|
||||||
opacity: 1;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
@ -82,10 +87,6 @@
|
|||||||
.ethBalances {
|
.ethBalances {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconCertifications {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import LinearProgress from 'material-ui/LinearProgress';
|
import LinearProgress from 'material-ui/LinearProgress';
|
||||||
@ -24,6 +25,7 @@ import { bindActionCreators } from 'redux';
|
|||||||
import { newError } from '~/redux/actions';
|
import { newError } from '~/redux/actions';
|
||||||
import { Button, TypedInput } from '~/ui';
|
import { Button, TypedInput } from '~/ui';
|
||||||
import { arrayOrObjectProptype } from '~/util/proptypes';
|
import { arrayOrObjectProptype } from '~/util/proptypes';
|
||||||
|
import { parseAbiType } from '~/util/abi';
|
||||||
|
|
||||||
import styles from './queries.css';
|
import styles from './queries.css';
|
||||||
|
|
||||||
@ -44,11 +46,35 @@ class InputQuery extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
inputs: [],
|
||||||
isValid: true,
|
isValid: true,
|
||||||
results: [],
|
results: [],
|
||||||
values: {}
|
values: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
this.parseInputs();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
const prevInputTypes = this.props.inputs.map((input) => input.type);
|
||||||
|
const nextInputTypes = nextProps.inputs.map((input) => input.type);
|
||||||
|
|
||||||
|
if (!isEqual(prevInputTypes, nextInputTypes)) {
|
||||||
|
this.parseInputs(nextProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseInputs (props = this.props) {
|
||||||
|
const inputs = props.inputs.map((input) => ({ ...input, parsed: parseAbiType(input.type) }));
|
||||||
|
const values = inputs.reduce((values, input, index) => {
|
||||||
|
values[index] = input.parsed.default;
|
||||||
|
return values;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
this.setState({ inputs, values });
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { name, className } = this.props;
|
const { name, className } = this.props;
|
||||||
|
|
||||||
@ -64,10 +90,9 @@ class InputQuery extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderContent () {
|
renderContent () {
|
||||||
const { inputs } = this.props;
|
const { inputs } = this.state;
|
||||||
|
|
||||||
const { isValid } = this.state;
|
const { isValid } = this.state;
|
||||||
|
|
||||||
const inputsFields = inputs
|
const inputsFields = inputs
|
||||||
.map((input, index) => this.renderInput(input, index));
|
.map((input, index) => this.renderInput(input, index));
|
||||||
|
|
||||||
@ -190,15 +215,15 @@ class InputQuery extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
const { values } = this.state;
|
const { inputs, values } = this.state;
|
||||||
const { inputs, contract, name, outputs, signature } = this.props;
|
const { contract, name, outputs, signature } = this.props;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
results: []
|
results: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const inputValues = inputs.map((input, index) => values[index] || '');
|
const inputValues = inputs.map((input, index) => values[index]);
|
||||||
|
|
||||||
contract
|
contract
|
||||||
.instance[signature]
|
.instance[signature]
|
||||||
|
@ -41,8 +41,9 @@ export default class SignerStore {
|
|||||||
this.balances = Object.assign({}, this.balances, balances);
|
this.balances = Object.assign({}, this.balances, balances);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action setLocalHashes = (localHashes) => {
|
@action setLocalHashes = (localHashes = []) => {
|
||||||
if (!isEqual(localHashes, this.localHashes)) {
|
// Use slice to make sure they are both Arrays (MobX uses Objects for Observable Arrays)
|
||||||
|
if (!isEqual(localHashes.slice(), this.localHashes.slice())) {
|
||||||
this.localHashes = localHashes;
|
this.localHashes = localHashes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ export default class WalletTransactions extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const txRows = transactions.slice(0, 15).map((transaction, index) => {
|
const txRows = transactions.slice(0, 15).map((transaction, index) => {
|
||||||
const { transactionHash, blockNumber, from, to, value, data } = transaction;
|
const { transactionHash, data } = transaction;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TxRow
|
<TxRow
|
||||||
@ -79,12 +79,9 @@ export default class WalletTransactions extends Component {
|
|||||||
netVersion={ netVersion }
|
netVersion={ netVersion }
|
||||||
key={ `${transactionHash}_${index}` }
|
key={ `${transactionHash}_${index}` }
|
||||||
tx={ {
|
tx={ {
|
||||||
blockNumber,
|
|
||||||
from,
|
|
||||||
hash: transactionHash,
|
hash: transactionHash,
|
||||||
input: data && bytesToHex(data) || '',
|
input: data && bytesToHex(data) || '',
|
||||||
to,
|
...transaction
|
||||||
value
|
|
||||||
} }
|
} }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -181,6 +181,8 @@ usage! {
|
|||||||
or |c: &Config| otry!(c.dapps).interface.clone(),
|
or |c: &Config| otry!(c.dapps).interface.clone(),
|
||||||
flag_dapps_hosts: String = "none",
|
flag_dapps_hosts: String = "none",
|
||||||
or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| vec.join(",")),
|
or |c: &Config| otry!(c.dapps).hosts.as_ref().map(|vec| vec.join(",")),
|
||||||
|
flag_dapps_cors: Option<String> = None,
|
||||||
|
or |c: &Config| otry!(c.dapps).cors.clone().map(Some),
|
||||||
flag_dapps_path: String = "$BASE/dapps",
|
flag_dapps_path: String = "$BASE/dapps",
|
||||||
or |c: &Config| otry!(c.dapps).path.clone(),
|
or |c: &Config| otry!(c.dapps).path.clone(),
|
||||||
flag_dapps_user: Option<String> = None,
|
flag_dapps_user: Option<String> = None,
|
||||||
@ -428,6 +430,7 @@ struct Dapps {
|
|||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
interface: Option<String>,
|
interface: Option<String>,
|
||||||
hosts: Option<Vec<String>>,
|
hosts: Option<Vec<String>>,
|
||||||
|
cors: Option<String>,
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
pass: Option<String>,
|
pass: Option<String>,
|
||||||
@ -674,6 +677,7 @@ mod tests {
|
|||||||
flag_dapps_port: 8080u16,
|
flag_dapps_port: 8080u16,
|
||||||
flag_dapps_interface: "local".into(),
|
flag_dapps_interface: "local".into(),
|
||||||
flag_dapps_hosts: "none".into(),
|
flag_dapps_hosts: "none".into(),
|
||||||
|
flag_dapps_cors: None,
|
||||||
flag_dapps_path: "$HOME/.parity/dapps".into(),
|
flag_dapps_path: "$HOME/.parity/dapps".into(),
|
||||||
flag_dapps_user: Some("test_user".into()),
|
flag_dapps_user: Some("test_user".into()),
|
||||||
flag_dapps_pass: Some("test_pass".into()),
|
flag_dapps_pass: Some("test_pass".into()),
|
||||||
@ -873,6 +877,7 @@ mod tests {
|
|||||||
path: None,
|
path: None,
|
||||||
interface: None,
|
interface: None,
|
||||||
hosts: None,
|
hosts: None,
|
||||||
|
cors: None,
|
||||||
user: Some("username".into()),
|
user: Some("username".into()),
|
||||||
pass: Some("password".into())
|
pass: Some("password".into())
|
||||||
}),
|
}),
|
||||||
|
@ -164,6 +164,8 @@ API and Console Options:
|
|||||||
is additional security against some attack
|
is additional security against some attack
|
||||||
vectors. Special options: "all", "none",
|
vectors. Special options: "all", "none",
|
||||||
(default: {flag_dapps_hosts}).
|
(default: {flag_dapps_hosts}).
|
||||||
|
--dapps-cors URL Specify CORS headers for Dapps server APIs.
|
||||||
|
(default: {flag_dapps_cors:?})
|
||||||
--dapps-user USERNAME Specify username for Dapps server. It will be
|
--dapps-user USERNAME Specify username for Dapps server. It will be
|
||||||
used in HTTP Basic Authentication Scheme.
|
used in HTTP Basic Authentication Scheme.
|
||||||
If --dapps-pass is not specified you will be
|
If --dapps-pass is not specified you will be
|
||||||
|
@ -546,6 +546,7 @@ impl Configuration {
|
|||||||
interface: self.dapps_interface(),
|
interface: self.dapps_interface(),
|
||||||
port: self.args.flag_dapps_port,
|
port: self.args.flag_dapps_port,
|
||||||
hosts: self.dapps_hosts(),
|
hosts: self.dapps_hosts(),
|
||||||
|
cors: self.dapps_cors(),
|
||||||
user: self.args.flag_dapps_user.clone(),
|
user: self.args.flag_dapps_user.clone(),
|
||||||
pass: self.args.flag_dapps_pass.clone(),
|
pass: self.args.flag_dapps_pass.clone(),
|
||||||
dapps_path: PathBuf::from(self.directories().dapps),
|
dapps_path: PathBuf::from(self.directories().dapps),
|
||||||
@ -722,6 +723,10 @@ impl Configuration {
|
|||||||
Self::cors(self.args.flag_ipfs_api_cors.as_ref())
|
Self::cors(self.args.flag_ipfs_api_cors.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dapps_cors(&self) -> Option<Vec<String>> {
|
||||||
|
Self::cors(self.args.flag_dapps_cors.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
fn hosts(hosts: &str) -> Option<Vec<String>> {
|
fn hosts(hosts: &str) -> Option<Vec<String>> {
|
||||||
match hosts {
|
match hosts {
|
||||||
"none" => return Some(Vec::new()),
|
"none" => return Some(Vec::new()),
|
||||||
|
@ -33,6 +33,7 @@ pub struct Configuration {
|
|||||||
pub interface: String,
|
pub interface: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub hosts: Option<Vec<String>>,
|
pub hosts: Option<Vec<String>>,
|
||||||
|
pub cors: Option<Vec<String>>,
|
||||||
pub user: Option<String>,
|
pub user: Option<String>,
|
||||||
pub pass: Option<String>,
|
pub pass: Option<String>,
|
||||||
pub dapps_path: PathBuf,
|
pub dapps_path: PathBuf,
|
||||||
@ -48,6 +49,7 @@ impl Default for Configuration {
|
|||||||
interface: "127.0.0.1".into(),
|
interface: "127.0.0.1".into(),
|
||||||
port: 8080,
|
port: 8080,
|
||||||
hosts: Some(Vec::new()),
|
hosts: Some(Vec::new()),
|
||||||
|
cors: None,
|
||||||
user: None,
|
user: None,
|
||||||
pass: None,
|
pass: None,
|
||||||
dapps_path: replace_home(&data_dir, "$BASE/dapps").into(),
|
dapps_path: replace_home(&data_dir, "$BASE/dapps").into(),
|
||||||
@ -93,6 +95,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<We
|
|||||||
configuration.extra_dapps,
|
configuration.extra_dapps,
|
||||||
&addr,
|
&addr,
|
||||||
configuration.hosts,
|
configuration.hosts,
|
||||||
|
configuration.cors,
|
||||||
auth,
|
auth,
|
||||||
configuration.all_apis,
|
configuration.all_apis,
|
||||||
)?))
|
)?))
|
||||||
@ -114,6 +117,7 @@ mod server {
|
|||||||
_extra_dapps: Vec<PathBuf>,
|
_extra_dapps: Vec<PathBuf>,
|
||||||
_url: &SocketAddr,
|
_url: &SocketAddr,
|
||||||
_allowed_hosts: Option<Vec<String>>,
|
_allowed_hosts: Option<Vec<String>>,
|
||||||
|
_cors: Option<Vec<String>>,
|
||||||
_auth: Option<(String, String)>,
|
_auth: Option<(String, String)>,
|
||||||
_all_apis: bool,
|
_all_apis: bool,
|
||||||
) -> Result<WebappServer, String> {
|
) -> Result<WebappServer, String> {
|
||||||
@ -147,6 +151,7 @@ mod server {
|
|||||||
extra_dapps: Vec<PathBuf>,
|
extra_dapps: Vec<PathBuf>,
|
||||||
url: &SocketAddr,
|
url: &SocketAddr,
|
||||||
allowed_hosts: Option<Vec<String>>,
|
allowed_hosts: Option<Vec<String>>,
|
||||||
|
cors: Option<Vec<String>>,
|
||||||
auth: Option<(String, String)>,
|
auth: Option<(String, String)>,
|
||||||
all_apis: bool,
|
all_apis: bool,
|
||||||
) -> Result<WebappServer, String> {
|
) -> Result<WebappServer, String> {
|
||||||
@ -167,7 +172,8 @@ mod server {
|
|||||||
.web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token)))
|
.web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token)))
|
||||||
.extra_dapps(&extra_dapps)
|
.extra_dapps(&extra_dapps)
|
||||||
.signer_address(deps.signer.address())
|
.signer_address(deps.signer.address())
|
||||||
.allowed_hosts(allowed_hosts);
|
.allowed_hosts(allowed_hosts)
|
||||||
|
.extra_cors_headers(cors);
|
||||||
|
|
||||||
let api_set = if all_apis {
|
let api_set = if all_apis {
|
||||||
warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Dapps with all APIs exposed."));
|
warn!("{}", Colour::Red.bold().paint("*** INSECURE *** Running Dapps with all APIs exposed."));
|
||||||
|
@ -24,7 +24,7 @@ include!(concat!(env!("OUT_DIR"), "/version.rs"));
|
|||||||
include!(concat!(env!("OUT_DIR"), "/rustc_version.rs"));
|
include!(concat!(env!("OUT_DIR"), "/rustc_version.rs"));
|
||||||
|
|
||||||
#[cfg(feature = "final")]
|
#[cfg(feature = "final")]
|
||||||
const THIS_TRACK: &'static str = "nightly";
|
const THIS_TRACK: &'static str = "beta";
|
||||||
// ^^^ should be reset to "stable" or "beta" according to the release branch.
|
// ^^^ should be reset to "stable" or "beta" according to the release branch.
|
||||||
|
|
||||||
#[cfg(not(feature = "final"))]
|
#[cfg(not(feature = "final"))]
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 114 KiB |
Loading…
Reference in New Issue
Block a user