Skip to content

Commit

Permalink
Adding support for truffle's library linking (#209)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
elichai authored and tomusdrw committed Apr 16, 2019
1 parent 8b488f0 commit 5f4c915
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 14 deletions.
3 changes: 1 addition & 2 deletions examples/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<u8> = 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()
Expand Down
3 changes: 1 addition & 2 deletions examples/simple_log_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<u8> = include_str!("./build/SimpleEvent.bin").from_hex().unwrap();
let bytecode = include_str!("./build/SimpleEvent.bin");

eloop
.run(web3.eth().accounts().then(|accounts| {
Expand Down
3 changes: 1 addition & 2 deletions examples/simple_log_sub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<u8> = include_str!("./build/SimpleEvent.bin").from_hex().unwrap();
let bytecode = include_str!("./build/SimpleEvent.bin");

eloop
.run(web3.eth().accounts().then(|accounts| {
Expand Down
3 changes: 1 addition & 2 deletions examples/simple_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,7 +18,7 @@ fn main() {
println!("Balance: {}", balance);

// Get the contract bytecode for instance from Solidity compiler
let bytecode: Vec<u8> = 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()
Expand Down
118 changes: 113 additions & 5 deletions src/contract/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,6 +22,7 @@ pub struct Builder<T: Transport> {
pub(crate) options: Options,
pub(crate) confirmations: usize,
pub(crate) poll_interval: time::Duration,
pub(crate) linker: HashMap<String, Address>,
}

impl<T: Transport> Builder<T> {
Expand All @@ -46,19 +48,34 @@ impl<T: Transport> Builder<T> {
pub fn execute<P, V>(self, code: V, params: P, from: Address) -> Result<PendingContract<T>, ethabi::Error>
where
P: Tokenize,
V: Into<Vec<u8>>,
V: AsRef<str>,
{
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(), &params)?,
(None, true) => code,
(Some(constructor), _) => constructor.encode_input(code, &params)?,
};

let tx = TransactionRequest {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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(),
)
Expand All @@ -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::<rpc::Value>(
"{\"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<u8> = 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<u8> = 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();
}

}
24 changes: 23 additions & 1 deletion src/contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,6 +62,28 @@ impl<T: Transport> Contract<T> {
options: Options::default(),
confirmations: 1,
poll_interval: time::Duration::from_secs(7),
linker: HashMap::default(),
})
}

/// test
pub fn deploy_from_truffle<S>(
eth: Eth<T>,
json: &[u8],
linker: HashMap<S, Address>,
) -> Result<deploy::Builder<T>, ethabi::Error>
where
S: AsRef<str> + Eq + Hash,
{
let abi = ethabi::Contract::load(json)?;
let linker: HashMap<String, Address> = 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,
})
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/contract/res/Main.json
Original file line number Diff line number Diff line change
@@ -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"
}
21 changes: 21 additions & 0 deletions src/contract/res/MyLibrary.json
Original file line number Diff line number Diff line change
@@ -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"
}

0 comments on commit 5f4c915

Please sign in to comment.