From 260559a8ef1e4adee5ef2b5837ef67c4a9f5b84d Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 25 Feb 2023 23:24:46 +0100 Subject: [PATCH] move integration etherscan tests to tests/ dir --- ethers-etherscan/resources/IERC20.sol | 15 - .../resources/UniswapExchange.sol | 333 ------------------ ethers-etherscan/src/account.rs | 199 ----------- ethers-etherscan/src/contract.rs | 134 ------- ethers-etherscan/src/gas.rs | 57 --- ethers-etherscan/src/lib.rs | 26 +- ethers-etherscan/src/transaction.rs | 79 ----- ethers-etherscan/src/utils.rs | 34 +- ethers-etherscan/src/verify.rs | 52 --- ethers-etherscan/tests/it/account.rs | 168 +++++++++ ethers-etherscan/tests/it/contract.rs | 99 ++++++ ethers-etherscan/tests/it/gas.rs | 45 +++ ethers-etherscan/tests/it/main.rs | 76 ++++ ethers-etherscan/tests/it/transaction.rs | 65 ++++ ethers-etherscan/tests/it/verify.rs | 41 +++ ethers-etherscan/tests/it/version.rs | 21 ++ 16 files changed, 519 insertions(+), 925 deletions(-) delete mode 100644 ethers-etherscan/resources/IERC20.sol delete mode 100644 ethers-etherscan/resources/UniswapExchange.sol create mode 100644 ethers-etherscan/tests/it/account.rs create mode 100644 ethers-etherscan/tests/it/contract.rs create mode 100644 ethers-etherscan/tests/it/gas.rs create mode 100644 ethers-etherscan/tests/it/main.rs create mode 100644 ethers-etherscan/tests/it/transaction.rs create mode 100644 ethers-etherscan/tests/it/verify.rs create mode 100644 ethers-etherscan/tests/it/version.rs diff --git a/ethers-etherscan/resources/IERC20.sol b/ethers-etherscan/resources/IERC20.sol deleted file mode 100644 index 7d38ff38..00000000 --- a/ethers-etherscan/resources/IERC20.sol +++ /dev/null @@ -1,15 +0,0 @@ -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/ethers-etherscan/resources/UniswapExchange.sol b/ethers-etherscan/resources/UniswapExchange.sol deleted file mode 100644 index dd13149e..00000000 --- a/ethers-etherscan/resources/UniswapExchange.sol +++ /dev/null @@ -1,333 +0,0 @@ -/** - * 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 diff --git a/ethers-etherscan/src/account.rs b/ethers-etherscan/src/account.rs index 92965031..8a51813c 100644 --- a/ethers-etherscan/src/account.rs +++ b/ethers-etherscan/src/account.rs @@ -733,202 +733,3 @@ impl Client { Ok(response.result) } } - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use serial_test::serial; - - use crate::{tests::run_at_least_duration, Chain}; - - use super::*; - - #[tokio::test] - #[serial] - async fn get_ether_balance_single_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let balance = client - .get_ether_balance_single( - &"0x58eB28A67731c570Ef827C365c89B5751F9E6b0a".parse().unwrap(), - None, - ) - .await; - balance.unwrap(); - }) - .await - } - - #[tokio::test] - #[serial] - async fn get_ether_balance_multi_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let balances = client - .get_ether_balance_multi( - &[&"0x58eB28A67731c570Ef827C365c89B5751F9E6b0a".parse().unwrap()], - None, - ) - .await; - assert!(balances.is_ok()); - let balances = balances.unwrap(); - assert_eq!(balances.len(), 1); - }) - .await - } - - #[tokio::test] - #[serial] - async fn get_transactions_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let txs = client - .get_transactions( - &"0x4F26FfBe5F04ED43630fdC30A87638d53D0b0876".parse().unwrap(), - None, - ) - .await; - txs.unwrap(); - }) - .await - } - - #[tokio::test] - #[serial] - async fn get_internal_transactions_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let txs = client - .get_internal_transactions( - InternalTxQueryOption::ByAddress( - "0x2c1ba59d6f58433fb1eaee7d20b26ed83bda51a3".parse().unwrap(), - ), - None, - ) - .await; - txs.unwrap(); - }) - .await - } - - #[tokio::test] - #[serial] - async fn get_internal_transactions_by_tx_hash_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let txs = client - .get_internal_transactions( - InternalTxQueryOption::ByTransactionHash( - "0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170" - .parse() - .unwrap(), - ), - None, - ) - .await; - txs.unwrap(); - }) - .await - } - - #[tokio::test] - #[serial] - async fn get_erc20_transfer_events_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let txs = client - .get_erc20_token_transfer_events( - TokenQueryOption::ByAddress( - "0x4e83362442b8d1bec281594cea3050c8eb01311c".parse().unwrap(), - ), - None, - ) - .await - .unwrap(); - let tx = txs.get(0).unwrap(); - assert_eq!(tx.gas_used, 93657u64.into()); - assert_eq!(tx.nonce, 10u64.into()); - assert_eq!(tx.block_number, 2228258u64.into()); - }) - .await - } - - #[tokio::test] - #[serial] - async fn get_erc721_transfer_events_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let txs = client - .get_erc721_token_transfer_events( - TokenQueryOption::ByAddressAndContract( - "0x6975be450864c02b4613023c2152ee0743572325".parse().unwrap(), - "0x06012c8cf97bead5deae237070f9587f8e7a266d".parse().unwrap(), - ), - None, - ) - .await; - txs.unwrap(); - }) - .await - } - - #[tokio::test] - #[serial] - async fn get_erc1155_transfer_events_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let txs = client - .get_erc1155_token_transfer_events( - TokenQueryOption::ByAddressAndContract( - "0x216CD350a4044e7016f14936663e2880Dd2A39d7".parse().unwrap(), - "0x495f947276749ce646f68ac8c248420045cb7b5e".parse().unwrap(), - ), - None, - ) - .await; - txs.unwrap(); - }) - .await - } - - #[tokio::test] - #[serial] - async fn get_mined_blocks_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let blocks = client - .get_mined_blocks( - &"0x9dd134d14d1e65f84b706d6f205cd5b1cd03a46b".parse().unwrap(), - None, - None, - ) - .await; - blocks.unwrap(); - }) - .await - } - - #[tokio::test] - #[serial] - async fn get_avalanche_transactions() { - if std::env::var("SNOWTRACE_API_KEY").is_err() { - // nothing to do if api key unset - return - } - let client = Client::new_from_env(Chain::Avalanche).unwrap(); - let txs = client - .get_transactions(&"0x1549ea9b546ba9ffb306d78a1e1f304760cc4abf".parse().unwrap(), None) - .await; - txs.unwrap(); - } -} diff --git a/ethers-etherscan/src/contract.rs b/ethers-etherscan/src/contract.rs index 2b5adb23..3b699395 100644 --- a/ethers-etherscan/src/contract.rs +++ b/ethers-etherscan/src/contract.rs @@ -408,137 +408,3 @@ impl Client { Ok(result) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::run_at_least_duration; - use ethers_core::types::Chain; - use serial_test::serial; - use std::time::Duration; - - /// Abi of [0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413](https://api.etherscan.io/api?module=contract&action=getsourcecode&address=0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413). - const DAO_ABI: &str = "[{\"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\"}]"; - - #[allow(unused)] - fn init_tracing() { - tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .init(); - } - - #[tokio::test] - #[serial] - #[ignore] - async fn can_fetch_ftm_contract_abi() { - init_tracing(); - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Fantom).unwrap(); - - let _abi = client - .contract_abi("0x80AA7cb0006d5DDD91cce684229Ac6e398864606".parse().unwrap()) - .await - .unwrap(); - }) - .await; - } - - #[tokio::test] - #[serial] - #[ignore] - async fn can_fetch_contract_abi() { - init_tracing(); - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let abi = client - .contract_abi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap()) - .await - .unwrap(); - assert_eq!(abi, serde_json::from_str(DAO_ABI).unwrap()); - }) - .await; - } - - #[tokio::test] - #[serial] - #[ignore] - async fn can_fetch_contract_source_code() { - init_tracing(); - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let meta = client - .contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap()) - .await - .unwrap(); - - assert_eq!(meta.items.len(), 1); - let item = &meta.items[0]; - assert!(matches!(item.source_code, SourceCodeMetadata::SourceCode(_))); - assert_eq!(item.source_code.sources().len(), 1); - assert_eq!(item.abi().unwrap(), serde_json::from_str(DAO_ABI).unwrap()); - }) - .await - } - - #[tokio::test] - #[serial] - #[ignore] - async fn can_get_error_on_unverified_contract() { - init_tracing(); - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - let addr = "0xb5c31a0e22cae98ac08233e512bd627885aa24e5".parse().unwrap(); - let err = client.contract_source_code(addr).await.unwrap_err(); - assert!(matches!(err, EtherscanError::ContractCodeNotVerified(_))); - }) - .await - } - - /// Query a contract that has a single string source entry instead of underlying JSON metadata. - #[tokio::test] - #[serial] - #[ignore] - async fn can_fetch_contract_source_tree_for_singleton_contract() { - init_tracing(); - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let meta = client - .contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap()) - .await - .unwrap(); - - assert_eq!(meta.items.len(), 1); - let item = &meta.items[0]; - assert!(matches!(item.source_code, SourceCodeMetadata::SourceCode(_))); - assert_eq!(item.source_code.sources().len(), 1); - assert_eq!(item.abi().unwrap(), serde_json::from_str(DAO_ABI).unwrap()); - }) - .await - } - - /// Query a contract that has many source entries as JSON metadata and ensure they are - /// reflected. - #[tokio::test] - #[serial] - #[ignore] - async fn can_fetch_contract_source_tree_for_multi_entry_contract() { - init_tracing(); - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let meta = client - .contract_source_code("0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63".parse().unwrap()) - .await - .unwrap(); - - assert_eq!(meta.items.len(), 1); - assert!(matches!(meta.items[0].source_code, SourceCodeMetadata::Metadata { .. })); - let source_tree = meta.source_tree(); - assert_eq!(source_tree.entries.len(), 15); - }) - .await - } -} diff --git a/ethers-etherscan/src/gas.rs b/ethers-etherscan/src/gas.rs index da573ffc..11fd311d 100644 --- a/ethers-etherscan/src/gas.rs +++ b/ethers-etherscan/src/gas.rs @@ -67,60 +67,3 @@ impl Client { Ok(response.result) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::run_at_least_duration; - use ethers_core::types::Chain; - use serial_test::serial; - use std::time::Duration; - - #[tokio::test] - #[serial] - async fn gas_estimate_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let result = client.gas_estimate(2000000000u32.into()).await; - - result.unwrap(); - }) - .await - } - - #[tokio::test] - #[serial] - async fn gas_estimate_error() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let err = client.gas_estimate(7123189371829732819379218u128.into()).await.unwrap_err(); - - assert!(matches!(err, EtherscanError::GasEstimationFailed)); - }) - .await - } - - #[tokio::test] - #[serial] - async fn gas_oracle_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let result = client.gas_oracle().await; - - assert!(result.is_ok()); - - let oracle = result.unwrap(); - - assert!(oracle.safe_gas_price > 0); - assert!(oracle.propose_gas_price > 0); - assert!(oracle.fast_gas_price > 0); - assert!(oracle.last_block > 0); - assert!(oracle.suggested_base_fee > 0.0); - assert!(!oracle.gas_used_ratio.is_empty()); - }) - .await - } -} diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs index 27682909..6efe4b7d 100644 --- a/ethers-etherscan/src/lib.rs +++ b/ethers-etherscan/src/lib.rs @@ -325,7 +325,8 @@ impl ClientBuilder { /// Returns a Client that uses this ClientBuilder configuration. /// /// # Errors - /// if required fields are missing: + /// + /// If the following required fields are missing: /// - `etherscan_api_url` /// - `etherscan_url` pub fn build(self) -> Result { @@ -468,10 +469,6 @@ fn into_url(url: impl IntoUrl) -> std::result::Result { mod tests { use crate::{Client, EtherscanError}; use ethers_core::types::{Address, Chain, H256}; - use std::{ - future::Future, - time::{Duration, SystemTime}, - }; #[test] fn test_api_paths() { @@ -526,23 +523,4 @@ mod tests { let err = Client::new_from_env(Chain::Dev).unwrap_err(); assert!(matches!(err, EtherscanError::LocalNetworksNotSupported)); } - - #[tokio::test] - async fn check_wrong_etherscan_api_key() { - let client = Client::new(Chain::Mainnet, "ABCDEFG").unwrap(); - let resp = client - .contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap()) - .await - .unwrap_err(); - - assert!(matches!(resp, EtherscanError::InvalidApiKey)); - } - - pub async fn run_at_least_duration(duration: Duration, block: impl Future) { - let start = SystemTime::now(); - block.await; - if let Some(sleep) = duration.checked_sub(start.elapsed().unwrap()) { - tokio::time::sleep(sleep).await; - } - } } diff --git a/ethers-etherscan/src/transaction.rs b/ethers-etherscan/src/transaction.rs index c54c71da..1ae76b58 100644 --- a/ethers-etherscan/src/transaction.rs +++ b/ethers-etherscan/src/transaction.rs @@ -49,82 +49,3 @@ impl Client { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::{tests::run_at_least_duration, Chain}; - use serial_test::serial; - use std::time::Duration; - - #[tokio::test] - #[serial] - async fn check_contract_execution_status_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let status = client - .check_contract_execution_status( - "0x16197e2a0eacc44c1ebdfddcfcfcafb3538de557c759a66e0ba95263b23d9007", - ) - .await; - - status.unwrap(); - }) - .await - } - - #[tokio::test] - #[serial] - async fn check_contract_execution_status_error() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let err = client - .check_contract_execution_status( - "0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a", - ) - .await - .unwrap_err(); - - assert!(matches!(err, EtherscanError::ExecutionFailed(_))); - assert_eq!(err.to_string(), "Contract execution call failed: Bad jump destination"); - }) - .await - } - - #[tokio::test] - #[serial] - async fn check_transaction_receipt_status_success() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let success = client - .check_transaction_receipt_status( - "0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76", - ) - .await; - - success.unwrap(); - }) - .await - } - - #[tokio::test] - #[serial] - async fn check_transaction_receipt_status_failed() { - run_at_least_duration(Duration::from_millis(250), async { - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let err = client - .check_transaction_receipt_status( - "0x21a29a497cb5d4bf514c0cca8d9235844bd0215c8fab8607217546a892fd0758", - ) - .await - .unwrap_err(); - - assert!(matches!(err, EtherscanError::TransactionReceiptFailed)); - }) - .await - } -} diff --git a/ethers-etherscan/src/utils.rs b/ethers-etherscan/src/utils.rs index 5549b1a9..b285998b 100644 --- a/ethers-etherscan/src/utils.rs +++ b/ethers-etherscan/src/utils.rs @@ -3,8 +3,7 @@ use ethers_core::types::Address; use semver::Version; use serde::{Deserialize, Deserializer}; -static SOLC_BIN_LIST_URL: &str = - "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.txt"; +static SOLC_BIN_LIST_URL: &str = "https://binaries.soliditylang.org/bin/list.txt"; /// Given a Solc [Version], lookup the build metadata and return the full SemVer. /// e.g. `0.8.13` -> `0.8.13+commit.abaa5c0e` @@ -71,36 +70,7 @@ pub fn deserialize_source_code<'de, D: Deserializer<'de>>( #[cfg(test)] mod tests { use super::*; - use crate::{contract::SourceCodeLanguage, tests::run_at_least_duration}; - use semver::{BuildMetadata, Prerelease}; - use serial_test::serial; - use std::time::Duration; - - #[tokio::test] - #[serial] - async fn can_lookup_compiler_version_build_metadata() { - run_at_least_duration(Duration::from_millis(250), async { - let v = Version::new(0, 8, 13); - let version = lookup_compiler_version(&v).await.unwrap(); - assert_eq!(v.major, version.major); - assert_eq!(v.minor, version.minor); - assert_eq!(v.patch, version.patch); - assert_ne!(version.build, BuildMetadata::EMPTY); - assert_eq!(version.pre, Prerelease::EMPTY); - }) - .await - } - - #[tokio::test] - #[serial] - async fn errors_on_invalid_solc() { - run_at_least_duration(Duration::from_millis(250), async { - let v = Version::new(100, 0, 0); - let err = lookup_compiler_version(&v).await.unwrap_err(); - assert!(matches!(err, EtherscanError::MissingSolcVersion(_))); - }) - .await - } + use crate::contract::SourceCodeLanguage; #[test] fn can_deserialize_address_opt() { diff --git a/ethers-etherscan/src/verify.rs b/ethers-etherscan/src/verify.rs index ebe15255..e76b4eca 100644 --- a/ethers-etherscan/src/verify.rs +++ b/ethers-etherscan/src/verify.rs @@ -153,55 +153,3 @@ impl Client { self.post_form(&body).await } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::{tests::run_at_least_duration, Client}; - use ethers_core::types::Chain; - use ethers_solc::{Project, ProjectPathsConfig}; - use serial_test::serial; - use std::{path::PathBuf, time::Duration}; - - #[allow(unused)] - fn init_tracing() { - tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .init(); - } - - #[tokio::test] - #[serial] - #[ignore] - async fn can_flatten_and_verify_contract() { - init_tracing(); - run_at_least_duration(Duration::from_millis(250), async { - let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources"); - let paths = ProjectPathsConfig::builder() - .sources(&root) - .build() - .expect("failed to resolve project paths"); - let project = Project::builder() - .paths(paths) - .build() - .expect("failed to build the project"); - - let address = "0x9e744c9115b74834c0f33f4097f40c02a9ac5c33".parse().unwrap(); - let compiler_version = "v0.5.17+commit.d19bba13"; - let constructor_args = "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000007596179537761700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035941590000000000000000000000000000000000000000000000000000000000"; - let contract = project.flatten(&root.join("UniswapExchange.sol")).expect("failed to flatten contract"); - let contract_name = "UniswapExchange".to_owned(); - - let client = Client::new_from_env(Chain::Mainnet).unwrap(); - - let contract = - VerifyContract::new(address, contract_name, contract, compiler_version.to_string()) - .constructor_arguments(Some(constructor_args)) - .optimization(true) - .runs(200); - let resp = client.submit_contract_verification(&contract).await.expect("failed to send the request"); - assert_ne!(resp.result, "Error!"); // `Error!` result means that request was malformatted - }) - .await - } -} diff --git a/ethers-etherscan/tests/it/account.rs b/ethers-etherscan/tests/it/account.rs new file mode 100644 index 00000000..035ba365 --- /dev/null +++ b/ethers-etherscan/tests/it/account.rs @@ -0,0 +1,168 @@ +use crate::*; +use ethers_etherscan::account::{InternalTxQueryOption, TokenQueryOption}; +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn get_ether_balance_single_success() { + run_with_client(Chain::Mainnet, |client| async move { + let balance = client + .get_ether_balance_single( + &"0x58eB28A67731c570Ef827C365c89B5751F9E6b0a".parse().unwrap(), + None, + ) + .await; + balance.unwrap(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn get_ether_balance_multi_success() { + run_with_client(Chain::Mainnet, |client| async move { + let balances = client + .get_ether_balance_multi( + &[&"0x58eB28A67731c570Ef827C365c89B5751F9E6b0a".parse().unwrap()], + None, + ) + .await; + assert!(balances.is_ok()); + let balances = balances.unwrap(); + assert_eq!(balances.len(), 1); + }) + .await +} + +#[tokio::test] +#[serial] +async fn get_transactions_success() { + run_with_client(Chain::Mainnet, |client| async move { + let txs = client + .get_transactions(&"0x4F26FfBe5F04ED43630fdC30A87638d53D0b0876".parse().unwrap(), None) + .await; + txs.unwrap(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn get_internal_transactions_success() { + run_with_client(Chain::Mainnet, |client| async move { + let txs = client + .get_internal_transactions( + InternalTxQueryOption::ByAddress( + "0x2c1ba59d6f58433fb1eaee7d20b26ed83bda51a3".parse().unwrap(), + ), + None, + ) + .await; + txs.unwrap(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn get_internal_transactions_by_tx_hash_success() { + run_with_client(Chain::Mainnet, |client| async move { + let txs = client + .get_internal_transactions( + InternalTxQueryOption::ByTransactionHash( + "0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170" + .parse() + .unwrap(), + ), + None, + ) + .await; + txs.unwrap(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn get_erc20_transfer_events_success() { + run_with_client(Chain::Mainnet, |client| async move { + let txs = client + .get_erc20_token_transfer_events( + TokenQueryOption::ByAddress( + "0x4e83362442b8d1bec281594cea3050c8eb01311c".parse().unwrap(), + ), + None, + ) + .await + .unwrap(); + let tx = txs.get(0).unwrap(); + assert_eq!(tx.gas_used, 93657u64.into()); + assert_eq!(tx.nonce, 10u64.into()); + assert_eq!(tx.block_number, 2228258u64.into()); + }) + .await +} + +#[tokio::test] +#[serial] +async fn get_erc721_transfer_events_success() { + run_with_client(Chain::Mainnet, |client| async move { + let txs = client + .get_erc721_token_transfer_events( + TokenQueryOption::ByAddressAndContract( + "0x6975be450864c02b4613023c2152ee0743572325".parse().unwrap(), + "0x06012c8cf97bead5deae237070f9587f8e7a266d".parse().unwrap(), + ), + None, + ) + .await; + txs.unwrap(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn get_erc1155_transfer_events_success() { + run_with_client(Chain::Mainnet, |client| async move { + let txs = client + .get_erc1155_token_transfer_events( + TokenQueryOption::ByAddressAndContract( + "0x216CD350a4044e7016f14936663e2880Dd2A39d7".parse().unwrap(), + "0x495f947276749ce646f68ac8c248420045cb7b5e".parse().unwrap(), + ), + None, + ) + .await; + txs.unwrap(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn get_mined_blocks_success() { + run_with_client(Chain::Mainnet, |client| async move { + client + .get_mined_blocks( + &"0x9dd134d14d1e65f84b706d6f205cd5b1cd03a46b".parse().unwrap(), + None, + None, + ) + .await + .unwrap(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn get_avalanche_transactions() { + run_with_client(Chain::Avalanche, |client| async move { + let txs = client + .get_transactions(&"0x1549ea9b546ba9ffb306d78a1e1f304760cc4abf".parse().unwrap(), None) + .await; + txs.unwrap(); + }) + .await +} diff --git a/ethers-etherscan/tests/it/contract.rs b/ethers-etherscan/tests/it/contract.rs new file mode 100644 index 00000000..3ba30edc --- /dev/null +++ b/ethers-etherscan/tests/it/contract.rs @@ -0,0 +1,99 @@ +use crate::*; +use ethers_core::types::Chain; +use ethers_etherscan::contract::SourceCodeMetadata; +use serial_test::serial; + +/// Abi of [0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413](https://api.etherscan.io/api?module=contract&action=getsourcecode&address=0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413). +const DAO_ABI: &str = include!("../../../tests/testdata/the_dao_abi.expr"); + +#[tokio::test] +#[serial] +async fn can_fetch_ftm_contract_abi() { + run_with_client(Chain::Fantom, |client| async move { + let _abi = client + .contract_abi("0x80AA7cb0006d5DDD91cce684229Ac6e398864606".parse().unwrap()) + .await + .unwrap(); + }) + .await; +} + +#[tokio::test] +#[serial] +async fn can_fetch_contract_abi() { + run_with_client(Chain::Mainnet, |client| async move { + let abi = client + .contract_abi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap()) + .await + .unwrap(); + assert_eq!(abi, serde_json::from_str(DAO_ABI).unwrap()); + }) + .await; +} + +#[tokio::test] +#[serial] +async fn can_fetch_contract_source_code() { + run_with_client(Chain::Mainnet, |client| async move { + let meta = client + .contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap()) + .await + .unwrap(); + + assert_eq!(meta.items.len(), 1); + let item = &meta.items[0]; + assert!(matches!(item.source_code, SourceCodeMetadata::SourceCode(_))); + assert_eq!(item.source_code.sources().len(), 1); + assert_eq!(item.abi().unwrap(), serde_json::from_str(DAO_ABI).unwrap()); + }) + .await +} + +#[tokio::test] +#[serial] +async fn can_get_error_on_unverified_contract() { + init_tracing(); + run_with_client(Chain::Mainnet, |client| async move { + let addr = "0xb5c31a0e22cae98ac08233e512bd627885aa24e5".parse().unwrap(); + let err = client.contract_source_code(addr).await.unwrap_err(); + assert!(matches!(err, EtherscanError::ContractCodeNotVerified(_))); + }) + .await +} + +/// Query a contract that has a single string source entry instead of underlying JSON metadata. +#[tokio::test] +#[serial] +async fn can_fetch_contract_source_tree_for_singleton_contract() { + run_with_client(Chain::Mainnet, |client| async move { + let meta = client + .contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap()) + .await + .unwrap(); + + assert_eq!(meta.items.len(), 1); + let item = &meta.items[0]; + assert!(matches!(item.source_code, SourceCodeMetadata::SourceCode(_))); + assert_eq!(item.source_code.sources().len(), 1); + assert_eq!(item.abi().unwrap(), serde_json::from_str(DAO_ABI).unwrap()); + }) + .await +} + +/// Query a contract that has many source entries as JSON metadata and ensure they are reflected. +#[tokio::test] +#[serial] +async fn can_fetch_contract_source_tree_for_multi_entry_contract() { + run_with_client(Chain::Mainnet, |client| async move { + let meta = client + .contract_source_code("0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63".parse().unwrap()) + .await + .unwrap(); + + assert_eq!(meta.items.len(), 1); + assert!(matches!(meta.items[0].source_code, SourceCodeMetadata::Metadata { .. })); + let source_tree = meta.source_tree(); + assert_eq!(source_tree.entries.len(), 15); + }) + .await +} diff --git a/ethers-etherscan/tests/it/gas.rs b/ethers-etherscan/tests/it/gas.rs new file mode 100644 index 00000000..a8754ee5 --- /dev/null +++ b/ethers-etherscan/tests/it/gas.rs @@ -0,0 +1,45 @@ +use crate::*; +use ethers_core::types::Chain; +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn gas_estimate_success() { + run_with_client(Chain::Mainnet, |client| async move { + let result = client.gas_estimate(2000000000u32.into()).await; + + result.unwrap(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn gas_estimate_error() { + run_with_client(Chain::Mainnet, |client| async move { + let err = client.gas_estimate(7123189371829732819379218u128.into()).await.unwrap_err(); + + assert!(matches!(err, EtherscanError::GasEstimationFailed)); + }) + .await +} + +#[tokio::test] +#[serial] +async fn gas_oracle_success() { + run_with_client(Chain::Mainnet, |client| async move { + let result = client.gas_oracle().await; + + assert!(result.is_ok()); + + let oracle = result.unwrap(); + + assert!(oracle.safe_gas_price > 0); + assert!(oracle.propose_gas_price > 0); + assert!(oracle.fast_gas_price > 0); + assert!(oracle.last_block > 0); + assert!(oracle.suggested_base_fee > 0.0); + assert!(!oracle.gas_used_ratio.is_empty()); + }) + .await +} diff --git a/ethers-etherscan/tests/it/main.rs b/ethers-etherscan/tests/it/main.rs new file mode 100644 index 00000000..e0548705 --- /dev/null +++ b/ethers-etherscan/tests/it/main.rs @@ -0,0 +1,76 @@ +//! Etherscan integration tests + +#![cfg(not(target_arch = "wasm32"))] + +use ethers_core::types::Chain; +use ethers_etherscan::{errors::EtherscanError, Client}; +use std::{ + future::Future, + time::{Duration, Instant}, +}; + +mod account; +mod contract; +mod gas; +mod transaction; +mod verify; +mod version; + +#[tokio::test] +async fn check_wrong_etherscan_api_key() { + let client = Client::new(Chain::Mainnet, "ABCDEFG").unwrap(); + let resp = client + .contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap()) + .await + .unwrap_err(); + + assert!(matches!(resp, EtherscanError::InvalidApiKey)); +} + +/// Calls the function with a new Etherscan Client. +pub async fn run_with_client(chain: Chain, f: F) -> T +where + F: FnOnce(Client) -> Fut, + Fut: Future, +{ + init_tracing(); + let (client, duration) = match Client::new_from_env(chain) { + Ok(c) => (c, rate_limit(chain, true)), + Err(_) => { + (Client::builder().chain(chain).unwrap().build().unwrap(), rate_limit(chain, false)) + } + }; + run_at_least_duration(duration, f(client)).await +} + +#[track_caller] +fn rate_limit(chain: Chain, key: bool) -> Duration { + match (chain, key) { + // Rate limit with an API key is 5 call per second. + (_, true) => Duration::from_millis(250), + + // Rate limit without an API key is 1 call every 5 seconds. + // (Chain::Mainnet, false) => Duration::from_millis(5100), + (Chain::Mainnet, false) => panic!("ETHERSCAN_API_KEY is not set"), + + // Ignore other chains since we don't have more than 1 test with each. + (_, false) => Duration::ZERO, + } +} + +async fn run_at_least_duration(duration: Duration, block: impl Future) -> T { + let start = Instant::now(); + let output = block.await; + let elapsed = start.elapsed(); + if elapsed < duration { + tokio::time::sleep(duration - elapsed).await; + } + output +} + +#[track_caller] +fn init_tracing() { + let _ = tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .try_init(); +} diff --git a/ethers-etherscan/tests/it/transaction.rs b/ethers-etherscan/tests/it/transaction.rs new file mode 100644 index 00000000..3720f3cd --- /dev/null +++ b/ethers-etherscan/tests/it/transaction.rs @@ -0,0 +1,65 @@ +use crate::*; +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn check_contract_execution_status_success() { + run_with_client(Chain::Mainnet, |client| async move { + let status = client + .check_contract_execution_status( + "0x16197e2a0eacc44c1ebdfddcfcfcafb3538de557c759a66e0ba95263b23d9007", + ) + .await; + + status.unwrap(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn check_contract_execution_status_error() { + run_with_client(Chain::Mainnet, |client| async move { + let err = client + .check_contract_execution_status( + "0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a", + ) + .await + .unwrap_err(); + + assert!(matches!(err, EtherscanError::ExecutionFailed(_))); + assert_eq!(err.to_string(), "Contract execution call failed: Bad jump destination"); + }) + .await +} + +#[tokio::test] +#[serial] +async fn check_transaction_receipt_status_success() { + run_with_client(Chain::Mainnet, |client| async move { + let success = client + .check_transaction_receipt_status( + "0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76", + ) + .await; + + success.unwrap(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn check_transaction_receipt_status_failed() { + run_with_client(Chain::Mainnet, |client| async move { + let err = client + .check_transaction_receipt_status( + "0x21a29a497cb5d4bf514c0cca8d9235844bd0215c8fab8607217546a892fd0758", + ) + .await + .unwrap_err(); + + assert!(matches!(err, EtherscanError::TransactionReceiptFailed)); + }) + .await +} diff --git a/ethers-etherscan/tests/it/verify.rs b/ethers-etherscan/tests/it/verify.rs new file mode 100644 index 00000000..555a7de0 --- /dev/null +++ b/ethers-etherscan/tests/it/verify.rs @@ -0,0 +1,41 @@ +use crate::*; +use ethers_core::types::Chain; +use ethers_etherscan::verify::VerifyContract; +use ethers_solc::{Project, ProjectPathsConfig}; +use serial_test::serial; +use std::path::Path; + +#[tokio::test] +#[serial] +#[ignore] +async fn can_flatten_and_verify_contract() { + let root = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../../tests/testdata/uniswap")); + let paths = ProjectPathsConfig::builder() + .sources(root) + .build() + .expect("failed to resolve project paths"); + let project = Project::builder().paths(paths).build().expect("failed to build the project"); + + let address = "0x9e744c9115b74834c0f33f4097f40c02a9ac5c33".parse().unwrap(); + let compiler_version = "v0.5.17+commit.d19bba13"; + let constructor_args = "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000007596179537761700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035941590000000000000000000000000000000000000000000000000000000000"; + let contract = + project.flatten(&root.join("UniswapExchange.sol")).expect("failed to flatten contract"); + let contract_name = "UniswapExchange".to_owned(); + let contract = + VerifyContract::new(address, contract_name, contract, compiler_version.to_string()) + .constructor_arguments(Some(constructor_args)) + .optimization(true) + .runs(200); + + run_with_client(Chain::Mainnet, |client| async move { + let resp = client + .submit_contract_verification(&contract) + .await + .expect("failed to send the request"); + // `Error!` result means that request was malformatted + assert_ne!(resp.result, "Error!", "{resp:?}"); + assert_ne!(resp.message, "NOTOK", "{resp:?}"); + }) + .await +} diff --git a/ethers-etherscan/tests/it/version.rs b/ethers-etherscan/tests/it/version.rs new file mode 100644 index 00000000..34f325ef --- /dev/null +++ b/ethers-etherscan/tests/it/version.rs @@ -0,0 +1,21 @@ +use crate::*; +use ethers_etherscan::utils::lookup_compiler_version; +use semver::{BuildMetadata, Prerelease, Version}; + +#[tokio::test] +async fn can_lookup_compiler_version_build_metadata() { + let v = Version::new(0, 8, 13); + let version = lookup_compiler_version(&v).await.unwrap(); + assert_eq!(v.major, version.major); + assert_eq!(v.minor, version.minor); + assert_eq!(v.patch, version.patch); + assert_ne!(version.build, BuildMetadata::EMPTY); + assert_eq!(version.pre, Prerelease::EMPTY); +} + +#[tokio::test] +async fn errors_on_invalid_solc() { + let v = Version::new(100, 0, 0); + let err = lookup_compiler_version(&v).await.unwrap_err(); + assert!(matches!(err, EtherscanError::MissingSolcVersion(_))); +}