solpython 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pysol/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """solpython — Python-to-EVM compiler, runs Python on-chain."""
2
+
3
+ __version__ = "0.1.0"
pysol/build.py ADDED
@@ -0,0 +1,144 @@
1
+ """Compile Solidity contracts and produce JSON artifacts for the Python package."""
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ SOLC_VERSION = "0.8.20"
10
+ SRC_DIR = Path(__file__).parent.parent / "src"
11
+ ARTIFACTS_DIR = Path(__file__).parent / "contracts" / "artifacts"
12
+
13
+ # Contracts we need to deploy from Python
14
+ TARGET_CONTRACTS = ["PythonCompiler", "VM"]
15
+
16
+
17
+ def _find_solc() -> str:
18
+ """Find solc binary — try solcx, then system solc, then forge."""
19
+ try:
20
+ import solcx
21
+ solcx.install_solc(SOLC_VERSION)
22
+ return str(Path(solcx.get_solcx_install_folder()) / f"solc-{SOLC_VERSION}")
23
+ except ImportError:
24
+ pass
25
+
26
+ # Try system solc
27
+ result = subprocess.run(["which", "solc"], capture_output=True, text=True)
28
+ if result.returncode == 0:
29
+ return result.stdout.strip()
30
+
31
+ # Try forge
32
+ result = subprocess.run(["which", "forge"], capture_output=True, text=True)
33
+ if result.returncode == 0:
34
+ # Use forge to compile, then extract artifacts
35
+ return "forge"
36
+
37
+ print("Error: No Solidity compiler found.")
38
+ print("Install one of:")
39
+ print(" pip install py-solc-x")
40
+ print(" brew install solidity (or equivalent)")
41
+ print(" curl -L https://foundry.paradigm.xyz | bash && foundryup")
42
+ sys.exit(1)
43
+
44
+
45
+ def _compile_with_forge():
46
+ """Compile using Foundry and extract artifacts."""
47
+ project_dir = Path(__file__).parent.parent
48
+ result = subprocess.run(
49
+ ["forge", "build"],
50
+ cwd=project_dir,
51
+ capture_output=True,
52
+ text=True,
53
+ )
54
+ if result.returncode != 0:
55
+ print(f"Forge build failed:\n{result.stderr}")
56
+ sys.exit(1)
57
+
58
+ ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True)
59
+
60
+ for name in TARGET_CONTRACTS:
61
+ # Foundry stores artifacts in out/ContractName.sol/ContractName.json
62
+ artifact_path = project_dir / "out" / f"{name}.sol" / f"{name}.json"
63
+ if not artifact_path.exists():
64
+ print(f"Warning: artifact not found at {artifact_path}")
65
+ continue
66
+
67
+ data = json.loads(artifact_path.read_text())
68
+ # Extract just abi and bytecode
69
+ output = {
70
+ "abi": data.get("abi", []),
71
+ "bytecode": data.get("bytecode", {}).get("object", ""),
72
+ }
73
+ out_path = ARTIFACTS_DIR / f"{name}.json"
74
+ out_path.write_text(json.dumps(output, indent=2))
75
+ print(f" Wrote {out_path}")
76
+
77
+
78
+ def _compile_with_solcx(solc_path: str):
79
+ """Compile using solcx."""
80
+ import solcx
81
+
82
+ sources = {}
83
+ src_dir = SRC_DIR
84
+
85
+ # Collect all .sol files
86
+ for sol_file in src_dir.rglob("*.sol"):
87
+ rel = sol_file.relative_to(src_dir.parent)
88
+ sources[str(rel)] = {"content": sol_file.read_text()}
89
+
90
+ # Need to also find OpenZeppelin or other deps if used
91
+ # For now, our contracts are self-contained
92
+
93
+ output = solcx.compile_standard(
94
+ {
95
+ "language": "Solidity",
96
+ "sources": {k: {"content": v["content"]} for k, v in sources.items()},
97
+ "settings": {
98
+ "outputSelection": {
99
+ "*": {
100
+ "*": ["abi", "evm.bytecode.object"],
101
+ }
102
+ },
103
+ "remappings": [],
104
+ },
105
+ },
106
+ solc_binary=solc_path,
107
+ allow_paths=[str(src_dir.parent)],
108
+ )
109
+
110
+ ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True)
111
+
112
+ for name in TARGET_CONTRACTS:
113
+ # Find the contract in the output
114
+ for source_path, contracts in output.get("contracts", {}).items():
115
+ if name in contracts:
116
+ contract = contracts[name]
117
+ artifact = {
118
+ "abi": contract.get("abi", []),
119
+ "bytecode": contract.get("evm", {}).get("bytecode", {}).get("object", ""),
120
+ }
121
+ if artifact["bytecode"]:
122
+ out_path = ARTIFACTS_DIR / f"{name}.json"
123
+ out_path.write_text(json.dumps(artifact, indent=2))
124
+ print(f" Wrote {out_path}")
125
+ break
126
+ else:
127
+ print(f"Warning: contract '{name}' not found in compilation output")
128
+
129
+
130
+ def build():
131
+ """Compile Solidity contracts and generate artifacts."""
132
+ print("Compiling Solidity contracts...")
133
+ solc = _find_solc()
134
+
135
+ if solc == "forge":
136
+ _compile_with_forge()
137
+ else:
138
+ _compile_with_solcx(solc)
139
+
140
+ print("Done!")
141
+
142
+
143
+ if __name__ == "__main__":
144
+ build()
pysol/cli/__init__.py ADDED
File without changes
pysol/cli/main.py ADDED
@@ -0,0 +1,104 @@
1
+ """CLI entry point — mimics CPython's interface."""
2
+
3
+ import argparse
4
+ import sys
5
+ import os
6
+ from pathlib import Path
7
+
8
+
9
+ def main():
10
+ parser = argparse.ArgumentParser(
11
+ prog="solpython",
12
+ description="Python-to-EVM compiler — run Python on-chain",
13
+ )
14
+ parser.add_argument("script", nargs="?", help="Python script file to run")
15
+ parser.add_argument("-c", dest="command", help="Python command to execute")
16
+ parser.add_argument("-v", "--verbose", action="store_true", help="Show compilation details")
17
+ parser.add_argument("--version", action="store_true", help="Show version and exit")
18
+ parser.add_argument("--build", action="store_true", help="Compile Solidity contracts and exit")
19
+
20
+ args = parser.parse_args()
21
+
22
+ if args.version:
23
+ from pysol import __version__
24
+ print(f"solpython {__version__}")
25
+ return
26
+
27
+ if args.build:
28
+ from pysol.build import build
29
+ build()
30
+ return
31
+
32
+ if args.command:
33
+ source = args.command
34
+ if not source.endswith("\n"):
35
+ source += "\n"
36
+ _execute(source, verbose=args.verbose)
37
+ return
38
+
39
+ if args.script:
40
+ if not os.path.exists(args.script):
41
+ print(f"solpython: can't open file '{args.script}': No such file or directory")
42
+ sys.exit(1)
43
+ source = Path(args.script).read_text()
44
+ _execute(source, verbose=args.verbose)
45
+ return
46
+
47
+ _repl(verbose=args.verbose)
48
+
49
+
50
+ def _execute(source: str, *, verbose: bool = False):
51
+ from pysol.executor import run
52
+ try:
53
+ output = run(source, verbose=verbose)
54
+ if output:
55
+ print(output)
56
+ except FileNotFoundError as e:
57
+ print(str(e))
58
+ sys.exit(1)
59
+ except Exception as e:
60
+ print(f"solpython: error: {e}", file=sys.stderr)
61
+ if verbose:
62
+ import traceback
63
+ traceback.print_exc()
64
+ sys.exit(1)
65
+
66
+
67
+ def _repl(*, verbose: bool = False):
68
+ from pysol import __version__
69
+ print(f"solpython {__version__}")
70
+ print('Type "exit()" or Ctrl-D to exit.')
71
+ print()
72
+
73
+ while True:
74
+ try:
75
+ line = input(">>> ")
76
+ except (EOFError, KeyboardInterrupt):
77
+ print()
78
+ break
79
+
80
+ if not line.strip():
81
+ continue
82
+ if line.strip() in ("exit()", "quit()"):
83
+ break
84
+
85
+ source = line + "\n"
86
+ if line.rstrip().endswith(":"):
87
+ while True:
88
+ try:
89
+ cont = input("... ")
90
+ except (EOFError, KeyboardInterrupt):
91
+ print()
92
+ break
93
+ if not cont.strip():
94
+ break
95
+ source += cont + "\n"
96
+
97
+ try:
98
+ _execute(source, verbose=verbose)
99
+ except SystemExit:
100
+ pass
101
+
102
+
103
+ if __name__ == "__main__":
104
+ main()
File without changes