Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions tests/cli/vyper_json/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ def test_interfaces_output():
}


def test_manifest_output():
input_json = {
"interfaces": {
"/bar.json": {"contractTypes": {"Bar": {"abi": BAR_ABI}}},
}
}
result = get_input_dict_interfaces(input_json)
assert isinstance(result, dict)
assert result == {"Bar": {"type": "json", "code": BAR_ABI}}


# get_interface_codes tests


Expand Down
10 changes: 10 additions & 0 deletions vyper/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,20 @@
def get_interface_file_path(base_paths: Sequence, import_path: str) -> Path:
relative_path = Path(import_path)
for path in base_paths:
# Find ABI JSON files
file_path = path.joinpath(relative_path)
suffix = next((i for i in (".vy", ".json") if file_path.with_suffix(i).exists()), None)
if suffix:
return file_path.with_suffix(suffix)

# Find ethPM Manifest files (`from path.to.Manifest import InterfaceName`)
# NOTE: Use file parent because this assumes that `file_path`
# coincides with an ABI interface file
file_path = file_path.parent
suffix = next((i for i in (".vy", ".json") if file_path.with_suffix(i).exists()), None)
if suffix:
return file_path.with_suffix(suffix)

raise FileNotFoundError(f" Cannot locate interface '{import_path}{{.vy,.json}}'")


Expand Down
34 changes: 30 additions & 4 deletions vyper/cli/vyper_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,36 @@ def get_interface_codes(root_path: Path, contract_sources: ContractCodes) -> Dic
with valid_path.open() as fh:
code = fh.read()
if valid_path.suffix == ".json":
interfaces[file_path][interface_name] = {
"type": "json",
"code": json.loads(code.encode()),
}
contents = json.loads(code.encode())

# EthPM Manifest (EIP-2678)
if "contractTypes" in contents:
if (
interface_name not in contents["contractTypes"]
or "abi" not in contents["contractTypes"][interface_name]
):
raise ValueError(
f"Could not find interface '{interface_name}'"
f" in manifest '{valid_path}'."
)

interfaces[file_path][interface_name] = {
"type": "json",
"code": contents["contractTypes"][interface_name]["abi"],
}

# ABI JSON file (either `List[ABI]` or `{"abi": List[ABI]}`)
elif isinstance(contents, list) or (
"abi" in contents and isinstance(contents["abi"], list)
):
interfaces[file_path][interface_name] = {
"type": "json",
"code": contents,
}

else:
raise ValueError(f"Corrupted file: '{valid_path}'")

else:
interfaces[file_path][interface_name] = {"type": "vyper", "code": code}

Expand Down
58 changes: 55 additions & 3 deletions vyper/cli/vyper_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,22 +182,47 @@ def get_input_dict_contracts(input_dict: Dict) -> ContractCodes:

def get_input_dict_interfaces(input_dict: Dict) -> Dict:
interface_sources: Dict = {}

for path, value in input_dict.get("interfaces", {}).items():
key = _standardize_path(path)

if key.endswith(".json"):
if "abi" not in value:
# EthPM Manifest v3 (EIP-2678)
if "contractTypes" in value:
for name, ct in value["contractTypes"].items():
if name in interface_sources:
raise JSONError(f"Interface namespace collision: {name}")

interface_sources[name] = {"type": "json", "code": ct["abi"]}

continue # Skip to next interface

# ABI JSON file (`{"abi": List[ABI]}`)
elif "abi" in value:
interface = {"type": "json", "code": value["abi"]}

# ABI JSON file (`List[ABI]`)
elif isinstance(value, list):
interface = {"type": "json", "code": value}

else:
raise JSONError(f"Interface '{path}' must have 'abi' field")
interface = {"type": "json", "code": value["abi"]}

elif key.endswith(".vy"):
if "content" not in value:
raise JSONError(f"Interface '{path}' must have 'content' field")

interface = {"type": "vyper", "code": value["content"]}

else:
raise JSONError(f"Interface '{path}' must have suffix '.vy' or '.json'")

key = key.rsplit(".", maxsplit=1)[0]
if key in interface_sources:
raise JSONError(f"Interface namespace collision: {key}")

interface_sources[key] = interface

return interface_sources


Expand Down Expand Up @@ -248,6 +273,11 @@ def get_interface_codes(
code = contract_sources[contract_path]
interface_codes = extract_file_interface_imports(code)
for interface_name, interface_path in interface_codes.items():
# If we know the interfaces already (e.g. EthPM Manifest file)
if interface_name in interface_sources:
interfaces[interface_name] = interface_sources[interface_name]
continue

path = Path(contract_path).parent.joinpath(interface_path).as_posix()
keys = [_standardize_path(path)]
if not interface_path.startswith("."):
Expand Down Expand Up @@ -281,7 +311,29 @@ def get_interface_codes(
with valid_path.open() as fh:
code = fh.read()
if valid_path.suffix == ".json":
interfaces[interface_name] = {"type": "json", "code": json.loads(code.encode())}
code_dict = json.loads(code.encode())
# EthPM Manifest v3 (EIP-2678)
if "contractTypes" in code_dict:
if interface_name not in code_dict["contractTypes"]:
raise JSONError(f"'{interface_name}' not found in '{valid_path}'")

if "abi" not in code_dict["contractTypes"][interface_name]:
raise JSONError(f"Missing abi for '{interface_name}' in '{valid_path}'")

abi = code_dict["contractTypes"][interface_name]["abi"]
interfaces[interface_name] = {"type": "json", "code": abi}

# ABI JSON (`{"abi": List[ABI]}`)
elif "abi" in code_dict:
interfaces[interface_name] = {"type": "json", "code": code_dict["abi"]}

# ABI JSON (`List[ABI]`)
elif isinstance(code_dict, list):
interfaces[interface_name] = {"type": "json", "code": code_dict}

else:
raise JSONError(f"Unexpected type in file: '{valid_path}'")

else:
interfaces[interface_name] = {"type": "vyper", "code": code}

Expand Down