From 5f4c915124b5ed59715e29d5ae751971439e963b Mon Sep 17 00:00:00 2001 From: Elichai Turkel Date: Tue, 16 Apr 2019 23:46:39 +0300 Subject: [PATCH] Adding support for truffle's library linking (#209) * Added support for linked contracts * Added a test for linked libs * Fixed the tests * Replaced Regex by std::fmt rules * Updated examples and tests * rustfmt + adding trait constraints to the linker hashmap --- examples/contract.rs | 3 +- examples/simple_log_filter.rs | 3 +- examples/simple_log_sub.rs | 3 +- examples/simple_storage.rs | 3 +- src/contract/deploy.rs | 118 ++++++++++++++++++++++++++++++-- src/contract/mod.rs | 24 ++++++- src/contract/res/Main.json | 28 ++++++++ src/contract/res/MyLibrary.json | 21 ++++++ 8 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 src/contract/res/Main.json create mode 100644 src/contract/res/MyLibrary.json diff --git a/examples/contract.rs b/examples/contract.rs index 14ca1324..9c17bed4 100644 --- a/examples/contract.rs +++ b/examples/contract.rs @@ -2,7 +2,6 @@ extern crate env_logger; extern crate rustc_hex; extern crate web3; -use rustc_hex::FromHex; use web3::contract::{Contract, Options}; use web3::futures::Future; use web3::types::{Address, U256}; @@ -14,7 +13,7 @@ fn main() { let my_account: Address = "d028d24f16a8893bd078259d413372ac01580769".parse().unwrap(); // Get the contract bytecode for instance from Solidity compiler - let bytecode: Vec = include_str!("./contract_token.code").from_hex().unwrap(); + let bytecode = include_str!("./contract_token.code"); // Deploying a contract let contract = Contract::deploy(web3.eth(), include_bytes!("../src/contract/res/token.json")) .unwrap() diff --git a/examples/simple_log_filter.rs b/examples/simple_log_filter.rs index d824d780..57f32e27 100644 --- a/examples/simple_log_filter.rs +++ b/examples/simple_log_filter.rs @@ -2,7 +2,6 @@ extern crate rustc_hex; extern crate tokio_core; extern crate web3; -use rustc_hex::FromHex; use std::time; use web3::contract::{Contract, Options}; use web3::futures::{Future, Stream}; @@ -14,7 +13,7 @@ fn main() { web3::Web3::new(web3::transports::Http::with_event_loop("http://localhost:8545", &eloop.handle(), 1).unwrap()); // Get the contract bytecode for instance from Solidity compiler - let bytecode: Vec = include_str!("./build/SimpleEvent.bin").from_hex().unwrap(); + let bytecode = include_str!("./build/SimpleEvent.bin"); eloop .run(web3.eth().accounts().then(|accounts| { diff --git a/examples/simple_log_sub.rs b/examples/simple_log_sub.rs index 47cd3cf6..ade3d932 100644 --- a/examples/simple_log_sub.rs +++ b/examples/simple_log_sub.rs @@ -2,7 +2,6 @@ extern crate rustc_hex; extern crate tokio_core; extern crate web3; -use rustc_hex::FromHex; use std::time; use web3::contract::{Contract, Options}; use web3::futures::{Future, Stream}; @@ -14,7 +13,7 @@ fn main() { web3::Web3::new(web3::transports::WebSocket::with_event_loop("ws://localhost:8546", &eloop.handle()).unwrap()); // Get the contract bytecode for instance from Solidity compiler - let bytecode: Vec = include_str!("./build/SimpleEvent.bin").from_hex().unwrap(); + let bytecode = include_str!("./build/SimpleEvent.bin"); eloop .run(web3.eth().accounts().then(|accounts| { diff --git a/examples/simple_storage.rs b/examples/simple_storage.rs index f8fd04a9..ac5d68a1 100644 --- a/examples/simple_storage.rs +++ b/examples/simple_storage.rs @@ -2,7 +2,6 @@ extern crate rustc_hex; extern crate web3; -use rustc_hex::FromHex; use std::time; use web3::contract::{Contract, Options}; use web3::futures::Future; @@ -19,7 +18,7 @@ fn main() { println!("Balance: {}", balance); // Get the contract bytecode for instance from Solidity compiler - let bytecode: Vec = include_str!("./build/SimpleStorage.bin").from_hex().unwrap(); + let bytecode = include_str!("./build/SimpleStorage.bin"); // Deploying a contract let contract = Contract::deploy(web3.eth(), include_bytes!("./build/SimpleStorage.abi")) .unwrap() diff --git a/src/contract/deploy.rs b/src/contract/deploy.rs index e764b876..28391f09 100644 --- a/src/contract/deploy.rs +++ b/src/contract/deploy.rs @@ -2,7 +2,8 @@ use ethabi; use futures::{Async, Future, Poll}; -use std::time; +use rustc_hex::{FromHex, ToHex}; +use std::{collections::HashMap, time}; use crate::api::{Eth, Namespace}; use crate::confirm; @@ -21,6 +22,7 @@ pub struct Builder { pub(crate) options: Options, pub(crate) confirmations: usize, pub(crate) poll_interval: time::Duration, + pub(crate) linker: HashMap, } impl Builder { @@ -46,19 +48,34 @@ impl Builder { pub fn execute(self, code: V, params: P, from: Address) -> Result, ethabi::Error> where P: Tokenize, - V: Into>, + V: AsRef, { let options = self.options; let eth = self.eth; let abi = self.abi; + let mut code_hex = code.as_ref().to_string(); + + for (lib, address) in self.linker { + if lib.len() > 38 { + return Err( + ethabi::ErrorKind::Msg(String::from("The library name should be under 39 characters.")).into(), + ); + } + let replace = format!("__{:_<38}", lib); // This makes the required width 38 characters and will pad with `_` to match it. + let address: String = address.as_ref().to_hex(); + code_hex = code_hex.replacen(&replace, &address, 1); + } + code_hex = code_hex.replace("\"", "").replace("0x", ""); // This is to fix truffle + serde_json redundant `"` and `0x` + let code = code_hex.from_hex().map_err(|e| ethabi::ErrorKind::Hex(e))?; + let params = params.into_tokens(); let data = match (abi.constructor(), params.is_empty()) { (None, false) => { return Err(ethabi::ErrorKind::Msg(format!("Constructor is not defined in the ABI.")).into()); } - (None, true) => code.into(), - (Some(constructor), _) => constructor.encode_input(code.into(), ¶ms)?, + (None, true) => code, + (Some(constructor), _) => constructor.encode_input(code, ¶ms)?, }; let tx = TransactionRequest { @@ -118,6 +135,8 @@ mod tests { use crate::rpc; use crate::types::U256; use futures::Future; + use serde_json::Value; + use std::collections::HashMap; #[test] fn should_deploy_a_contract() { @@ -154,7 +173,7 @@ mod tests { .options(Options::with(|opt| opt.value = Some(5.into()))) .confirmations(1) .execute( - vec![1, 2, 3, 4], + "0x01020304", (U256::from(1_000_000), "My Token".to_owned(), 3u64, "MT".to_owned()), 5.into(), ) @@ -181,4 +200,93 @@ mod tests { ); transport.assert_no_more_requests(); } + + #[test] + fn deploy_linked_contract() { + use serde_json::{to_string, to_vec}; + let mut transport = TestTransport::default(); + let receipt = ::serde_json::from_str::( + "{\"blockHash\":\"0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c54878f\",\"blockNumber\":\"0x256\",\"contractAddress\":\"0x600515dfe465f600f0c9793fa27cd2794f3ec0e1\",\"cumulativeGasUsed\":\"0xe57e0\",\"gasUsed\":\"0xe57e0\",\"logs\":[],\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"root\":null,\"transactionHash\":\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\",\"transactionIndex\":\"0x0\"}" + ).unwrap(); + + for _ in 0..2 { + transport.add_response(rpc::Value::String( + "0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1".into(), + )); + transport.add_response(rpc::Value::String("0x0".into())); + transport.add_response(rpc::Value::Array(vec![rpc::Value::String( + "0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c54878f".into(), + )])); + transport.add_response(rpc::Value::Array(vec![rpc::Value::String( + "0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c548790".into(), + )])); + transport.add_response(receipt.clone()); + transport.add_response(rpc::Value::String("0x25a".into())); + transport.add_response(receipt.clone()); + } + + let lib: Value = serde_json::from_slice(include_bytes!("./res/MyLibrary.json")).unwrap(); + let lib_abi: Vec = to_vec(&lib["abi"]).unwrap(); + let lib_code = to_string(&lib["bytecode"]).unwrap(); + + let main: Value = serde_json::from_slice(include_bytes!("./res/Main.json")).unwrap(); + let main_abi: Vec = to_vec(&main["abi"]).unwrap(); + let main_code = to_string(&main["bytecode"]).unwrap(); + + let lib_address; + { + let builder = Contract::deploy(api::Eth::new(&transport), &lib_abi).unwrap(); + lib_address = builder + .execute(lib_code, (), 0.into()) + .unwrap() + .wait() + .unwrap() + .address(); + } + + transport.assert_request("eth_sendTransaction", &[ + "{\"data\":\"0x60ad61002f600b82828239805160001a6073146000811461001f57610021565bfe5b5030600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106056576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f8a8fd6d14605b575b600080fd5b60616077565b6040518082815260200191505060405180910390f35b600061010090509056fea165627a7a72305820b50091adcb7ef9987dd8daa665cec572801bf8243530d70d52631f9d5ddb943e0029\",\"from\":\"0x0000000000000000000000000000000000000000\"}" + .into()]); + transport.assert_request("eth_newBlockFilter", &[]); + transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]); + transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]); + transport.assert_request( + "eth_getTransactionReceipt", + &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()], + ); + transport.assert_request("eth_blockNumber", &[]); + transport.assert_request( + "eth_getTransactionReceipt", + &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()], + ); + transport.assert_no_more_requests(); + { + let builder = Contract::deploy_from_truffle(api::Eth::new(&transport), &main_abi, { + let mut linker = HashMap::new(); + linker.insert("MyLibrary", lib_address); + linker + }) + .unwrap(); + let _ = builder.execute(main_code, (), 0.into()).unwrap().wait().unwrap(); + } + + transport.assert_request("eth_sendTransaction", &[ + "{\"data\":\"0x608060405234801561001057600080fd5b5061013f806100206000396000f3fe608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f8a8fd6d14610046575b600080fd5b34801561005257600080fd5b5061005b610071565b6040518082815260200191505060405180910390f35b600073600515dfe465f600f0c9793fa27cd2794f3ec0e163f8a8fd6d6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b1580156100d357600080fd5b505af41580156100e7573d6000803e3d6000fd5b505050506040513d60208110156100fd57600080fd5b810190808051906020019092919050505090509056fea165627a7a72305820580d3776b3d132142f431e141a2e20bd4dd4907fa304feea7b604e8f39ed59520029\",\"from\":\"0x0000000000000000000000000000000000000000\"}" + .into()]); + + transport.assert_request("eth_newBlockFilter", &[]); + transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]); + transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]); + transport.assert_request( + "eth_getTransactionReceipt", + &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()], + ); + transport.assert_request("eth_blockNumber", &[]); + transport.assert_request( + "eth_getTransactionReceipt", + &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()], + ); + transport.assert_no_more_requests(); + } + } diff --git a/src/contract/mod.rs b/src/contract/mod.rs index e37324bd..cbe11111 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -7,7 +7,7 @@ use crate::confirm; use crate::contract::tokens::{Detokenize, Tokenize}; use crate::types::{Address, BlockNumber, Bytes, CallRequest, TransactionCondition, TransactionRequest, H256, U256}; use crate::Transport; -use std::time; +use std::{collections::HashMap, hash::Hash, time}; pub mod deploy; mod error; @@ -62,6 +62,28 @@ impl Contract { options: Options::default(), confirmations: 1, poll_interval: time::Duration::from_secs(7), + linker: HashMap::default(), + }) + } + + /// test + pub fn deploy_from_truffle( + eth: Eth, + json: &[u8], + linker: HashMap, + ) -> Result, ethabi::Error> + where + S: AsRef + Eq + Hash, + { + let abi = ethabi::Contract::load(json)?; + let linker: HashMap = linker.into_iter().map(|(s, a)| (s.as_ref().to_string(), a)).collect(); + Ok(deploy::Builder { + eth, + abi, + options: Options::default(), + confirmations: 1, + poll_interval: time::Duration::from_secs(7), + linker, }) } } diff --git a/src/contract/res/Main.json b/src/contract/res/Main.json new file mode 100644 index 00000000..3e1131c9 --- /dev/null +++ b/src/contract/res/Main.json @@ -0,0 +1,28 @@ +{ + "contractName": "Main", + "abi": [ + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor", + "signature": "constructor" + }, + { + "constant": false, + "inputs": [], + "name": "test", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0xf8a8fd6d" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b5061013f806100206000396000f3fe608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f8a8fd6d14610046575b600080fd5b34801561005257600080fd5b5061005b610071565b6040518082815260200191505060405180910390f35b600073__MyLibrary_____________________________63f8a8fd6d6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b1580156100d357600080fd5b505af41580156100e7573d6000803e3d6000fd5b505050506040513d60208110156100fd57600080fd5b810190808051906020019092919050505090509056fea165627a7a72305820580d3776b3d132142f431e141a2e20bd4dd4907fa304feea7b604e8f39ed59520029" +} \ No newline at end of file diff --git a/src/contract/res/MyLibrary.json b/src/contract/res/MyLibrary.json new file mode 100644 index 00000000..024ce86b --- /dev/null +++ b/src/contract/res/MyLibrary.json @@ -0,0 +1,21 @@ +{ + "contractName": "MyLibrary", + "abi": [ + { + "constant": true, + "inputs": [], + "name": "test", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function", + "signature": "0xf8a8fd6d" + } + ], + "bytecode": "0x60ad61002f600b82828239805160001a6073146000811461001f57610021565bfe5b5030600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106056576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f8a8fd6d14605b575b600080fd5b60616077565b6040518082815260200191505060405180910390f35b600061010090509056fea165627a7a72305820b50091adcb7ef9987dd8daa665cec572801bf8243530d70d52631f9d5ddb943e0029" +} \ No newline at end of file