Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for proxy contracts for from_etherscan #122

Merged
merged 12 commits into from
Jan 31, 2024
Merged
36 changes: 28 additions & 8 deletions boa/explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,42 @@

SESSION = requests.Session()


def fetch_abi_from_etherscan(
address: str, uri: str = "https://api.etherscan.io/api", api_key: str = None
):
address = Address(address)

params = dict(module="contract", action="getabi", address=address)
def _fetch_etherscan(uri: str, api_key: str = None, **params) -> dict:
if api_key is not None:
params["apikey"] = api_key

res = SESSION.get(uri, params=params)
res.raise_for_status()

data = res.json()

if int(data["status"]) != 1:
raise ValueError(f"Failed to retrieve data from API: {data}")
mo-anon marked this conversation as resolved.
Show resolved Hide resolved

return data


def fetch_abi_from_etherscan(
address: str, uri: str = "https://api.etherscan.io/api", api_key: str = None
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
):
# resolve implementation address if `address` is a proxy contract
address = _resolve_implementation_address(address, uri, api_key)

# fetch ABI of `address`
params = dict(module="contract", action="getabi", address=address)
data = _fetch_etherscan(uri, api_key, **params)

return json.loads(data["result"].strip())


# fetch the address of a contract; resolves at most one layer of indirection
# if the address is a proxy contract.
def _resolve_implementation_address(address: str, uri: str, api_key: str):
params = dict(module="contract", action="getsourcecode", address=address)
data = _fetch_etherscan(uri, api_key, **params)
source_data = data["result"][0]

# check if the contract is a proxy
if int(source_data["Proxy"]) == 1:
return source_data["Implementation"]
else:
return address
2 changes: 1 addition & 1 deletion boa/interpret.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,4 @@ def from_etherscan(
return ABIContractFactory.from_abi_dict(abi, name=name).at(addr)


__all__ = ["BoaError"]
__all__ = []
36 changes: 36 additions & 0 deletions tests/integration/fork/test_from_etherscan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import os
import pytest

import boa

crvusd = "0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E"
voting_agent = "0xE478de485ad2fe566d49342Cbd03E49ed7DB3356"

@pytest.fixture(scope="module")
def api_key():
return os.environ.get("ETHERSCAN_API_KEY")


@pytest.fixture(scope="module")
def crvusd_contract(api_key):
contract = boa.from_etherscan(crvusd, name="crvUSD", api_key=api_key)

return contract


@pytest.fixture(scope="module")
def proxy_contract(api_key):
contract = boa.from_etherscan(voting_agent, name="VotingAgent", api_key=api_key)

return contract


def test_crvusd_contract(crvusd_contract):
assert crvusd_contract.totalSupply() > 0
assert crvusd_contract.symbol() == "crvUSD"


def test_proxy_contract(proxy_contract):
assert proxy_contract.minTime() == 43200
assert proxy_contract.voteTime() == 604800
assert proxy_contract.minBalance() == 2500000000000000000000
Loading