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 +3 -0
- pysol/build.py +144 -0
- pysol/cli/__init__.py +0 -0
- pysol/cli/main.py +104 -0
- pysol/contracts/__init__.py +0 -0
- pysol/contracts/artifacts/PythonCompiler.json +173 -0
- pysol/contracts/artifacts/VM.json +412 -0
- pysol/executor.py +127 -0
- solpython-0.1.0.dist-info/METADATA +195 -0
- solpython-0.1.0.dist-info/RECORD +14 -0
- solpython-0.1.0.dist-info/WHEEL +5 -0
- solpython-0.1.0.dist-info/entry_points.txt +3 -0
- solpython-0.1.0.dist-info/licenses/LICENSE +21 -0
- solpython-0.1.0.dist-info/top_level.txt +1 -0
pysol/__init__.py
ADDED
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
|